rigsql_rules/convention/
cv12.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
11pub struct RuleCV12;
12
13impl Rule for RuleCV12 {
14 fn code(&self) -> &'static str {
15 "CV12"
16 }
17 fn name(&self) -> &'static str {
18 "convention.join_condition"
19 }
20 fn description(&self) -> &'static str {
21 "Use JOIN … ON … instead of implicit join in WHERE."
22 }
23 fn explanation(&self) -> &'static str {
24 "Using comma-separated tables in FROM with join conditions in WHERE (implicit join) \
25 mixes join logic with filtering. Use explicit JOIN … ON … syntax to separate join \
26 conditions from filter conditions, improving readability and maintainability."
27 }
28 fn groups(&self) -> &[RuleGroup] {
29 &[RuleGroup::Convention]
30 }
31 fn is_fixable(&self) -> bool {
32 false
33 }
34
35 fn crawl_type(&self) -> CrawlType {
36 CrawlType::Segment(vec![SegmentType::SelectStatement])
37 }
38
39 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40 let children = ctx.segment.children();
41
42 let from_clause = children
44 .iter()
45 .find(|c| c.segment_type() == SegmentType::FromClause);
46 let where_clause = children
47 .iter()
48 .find(|c| c.segment_type() == SegmentType::WhereClause);
49
50 let (Some(from), Some(where_seg)) = (from_clause, where_clause) else {
51 return vec![];
52 };
53
54 let has_comma = from
56 .children()
57 .iter()
58 .any(|c| c.segment_type() == SegmentType::Comma);
59
60 if !has_comma {
61 return vec![];
62 }
63
64 vec![LintViolation::new(
66 self.code(),
67 "Use explicit JOIN … ON … instead of comma-separated tables with WHERE.",
68 where_seg.span(),
69 )]
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::test_utils::lint_sql;
77
78 #[test]
79 fn test_cv12_flags_implicit_join() {
80 let violations = lint_sql("SELECT * FROM a, b WHERE a.id = b.id", RuleCV12);
81 assert_eq!(violations.len(), 1);
82 }
83
84 #[test]
85 fn test_cv12_accepts_explicit_join() {
86 let violations = lint_sql("SELECT * FROM a JOIN b ON a.id = b.id", RuleCV12);
87 assert_eq!(violations.len(), 0);
88 }
89
90 #[test]
91 fn test_cv12_accepts_single_table_where() {
92 let violations = lint_sql("SELECT * FROM t WHERE x = 1", RuleCV12);
93 assert_eq!(violations.len(), 0);
94 }
95}