rigsql_rules/convention/
cv08.rs1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
10pub struct RuleCV08;
11
12impl Rule for RuleCV08 {
13 fn code(&self) -> &'static str {
14 "CV08"
15 }
16 fn name(&self) -> &'static str {
17 "convention.left_join"
18 }
19 fn description(&self) -> &'static str {
20 "Use LEFT JOIN instead of RIGHT JOIN."
21 }
22 fn explanation(&self) -> &'static str {
23 "RIGHT JOIN can always be rewritten as LEFT JOIN by swapping the table order. \
24 LEFT JOIN is more intuitive because it reads left-to-right: the 'main' table \
25 is on the left, and the 'optional' table is on the right."
26 }
27 fn groups(&self) -> &[RuleGroup] {
28 &[RuleGroup::Convention]
29 }
30 fn is_fixable(&self) -> bool {
31 false
32 }
33
34 fn crawl_type(&self) -> CrawlType {
35 CrawlType::Segment(vec![SegmentType::JoinClause])
36 }
37
38 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39 let children = ctx.segment.children();
40
41 for child in children {
43 if let Segment::Token(t) = child {
44 if t.segment_type == SegmentType::Keyword
45 && t.token.text.eq_ignore_ascii_case("RIGHT")
46 {
47 return vec![LintViolation::with_msg_key(
48 self.code(),
49 "Use LEFT JOIN instead of RIGHT JOIN.",
50 t.token.span,
51 "rules.CV08.msg",
52 vec![],
53 )];
54 }
55 }
56 }
57
58 vec![]
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use crate::test_utils::lint_sql;
66
67 #[test]
68 fn test_cv08_flags_right_join() {
69 let violations = lint_sql("SELECT * FROM a RIGHT JOIN b ON a.id = b.id", RuleCV08);
70 assert_eq!(violations.len(), 1);
71 }
72
73 #[test]
74 fn test_cv08_accepts_left_join() {
75 let violations = lint_sql("SELECT * FROM a LEFT JOIN b ON a.id = b.id", RuleCV08);
76 assert_eq!(violations.len(), 0);
77 }
78}