rigsql_rules/structure/
st07.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[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::new(
42 self.code(),
43 "Prefer ON clause over USING clause in joins.",
44 child.span(),
45 )];
46 }
47 }
48
49 vec![]
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use crate::test_utils::lint_sql;
57
58 #[test]
59 fn test_st07_flags_using_clause() {
60 let violations = lint_sql("SELECT * FROM a JOIN b USING (id);", RuleST07);
61 assert_eq!(violations.len(), 1);
62 assert!(violations[0].message.contains("USING"));
63 }
64
65 #[test]
66 fn test_st07_accepts_on_clause() {
67 let violations = lint_sql("SELECT * FROM a JOIN b ON a.id = b.id;", RuleST07);
68 assert_eq!(violations.len(), 0);
69 }
70}