Skip to main content

rigsql_rules/convention/
cv03.rs

1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6/// CV03: Trailing comma in SELECT clause.
7///
8/// The last column in a SELECT list should not be followed by a comma.
9/// A trailing comma before FROM is a syntax error in most databases.
10#[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.trailing_comma_select"
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        // Find the last non-trivia child
42        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}