rigsql_rules/convention/
cv03.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6#[derive(Debug, Default)]
11pub struct RuleCV03;
12
13impl Rule for RuleCV03 {
14 fn code(&self) -> &'static str {
15 "CV03"
16 }
17 fn name(&self) -> &'static str {
18 "convention.select_trailing_comma"
19 }
20 fn description(&self) -> &'static str {
21 "Trailing comma in SELECT clause."
22 }
23 fn explanation(&self) -> &'static str {
24 "A trailing comma at the end of a SELECT column list (before FROM or end of \
25 statement) is a syntax error in most SQL databases. Remove the extraneous comma."
26 }
27 fn groups(&self) -> &[RuleGroup] {
28 &[RuleGroup::Convention]
29 }
30 fn is_fixable(&self) -> bool {
31 true
32 }
33
34 fn crawl_type(&self) -> CrawlType {
35 CrawlType::Segment(vec![SegmentType::SelectClause])
36 }
37
38 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39 let children = ctx.segment.children();
40
41 let last_non_trivia = children
43 .iter()
44 .rev()
45 .find(|c| !c.segment_type().is_trivia());
46
47 if let Some(seg) = last_non_trivia {
48 if seg.segment_type() == SegmentType::Comma {
49 return vec![LintViolation::with_fix(
50 self.code(),
51 "Trailing comma found in SELECT clause.",
52 seg.span(),
53 vec![SourceEdit::delete(seg.span())],
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_cv03_flags_trailing_comma() {
69 let violations = lint_sql("SELECT a, b,", RuleCV03);
70 assert_eq!(violations.len(), 1);
71 }
72
73 #[test]
74 fn test_cv03_accepts_no_trailing_comma() {
75 let violations = lint_sql("SELECT a, b FROM t", RuleCV03);
76 assert_eq!(violations.len(), 0);
77 }
78}