Skip to main content

rigsql_rules/structure/
st07.rs

1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6/// ST07: Prefer ON clause over USING clause in joins.
7///
8/// USING clause is less explicit than ON and can cause ambiguity.
9#[derive(Debug, Default)]
10pub struct RuleST07;
11
12impl Rule for RuleST07 {
13    fn code(&self) -> &'static str {
14        "ST07"
15    }
16    fn name(&self) -> &'static str {
17        "structure.using"
18    }
19    fn description(&self) -> &'static str {
20        "Prefer explicit ON clause over USING clause in joins."
21    }
22    fn explanation(&self) -> &'static str {
23        "The USING clause in a JOIN is a shorthand for matching columns with the same name. \
24         While concise, it can be less clear than an explicit ON clause. Using ON makes the \
25         join condition explicit and avoids potential ambiguity."
26    }
27    fn groups(&self) -> &[RuleGroup] {
28        &[RuleGroup::Structure]
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        for child in ctx.segment.children() {
40            if child.segment_type() == SegmentType::UsingClause {
41                return vec![LintViolation::with_msg_key(
42                    self.code(),
43                    "Prefer ON clause over USING clause in joins.",
44                    child.span(),
45                    "rules.ST07.msg",
46                    vec![],
47                )];
48            }
49        }
50
51        vec![]
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use crate::test_utils::lint_sql;
59
60    #[test]
61    fn test_st07_flags_using_clause() {
62        let violations = lint_sql("SELECT * FROM a JOIN b USING (id);", RuleST07);
63        assert_eq!(violations.len(), 1);
64        assert!(violations[0].message.contains("USING"));
65    }
66
67    #[test]
68    fn test_st07_accepts_on_clause() {
69        let violations = lint_sql("SELECT * FROM a JOIN b ON a.id = b.id;", RuleST07);
70        assert_eq!(violations.len(), 0);
71    }
72}