Skip to main content

rigsql_rules/convention/
cv08.rs

1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6/// CV08: Use LEFT JOIN instead of RIGHT JOIN.
7///
8/// LEFT JOIN is more readable and intuitive. Any RIGHT JOIN can be rewritten as a LEFT JOIN.
9#[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        // Look for RIGHT keyword in the join clause
42        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::new(
48                        self.code(),
49                        "Use LEFT JOIN instead of RIGHT JOIN.",
50                        t.token.span,
51                    )];
52                }
53            }
54        }
55
56        vec![]
57    }
58}