rigsql_rules/structure/
st12.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
10pub struct RuleST12;
11
12impl Rule for RuleST12 {
13 fn code(&self) -> &'static str {
14 "ST12"
15 }
16 fn name(&self) -> &'static str {
17 "structure.consecutive_semicolons"
18 }
19 fn description(&self) -> &'static str {
20 "Consecutive semicolons indicate empty statements."
21 }
22 fn explanation(&self) -> &'static str {
23 "Multiple consecutive semicolons with only whitespace between them indicate \
24 empty statements, which are likely unintentional. Remove the extra semicolons."
25 }
26 fn groups(&self) -> &[RuleGroup] {
27 &[RuleGroup::Structure]
28 }
29 fn is_fixable(&self) -> bool {
30 false
31 }
32
33 fn crawl_type(&self) -> CrawlType {
34 CrawlType::RootOnly
35 }
36
37 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
38 let mut violations = Vec::new();
39 let mut last_semicolon_span: Option<rigsql_core::Span> = None;
40 let mut only_trivia_since_last = true;
41
42 ctx.segment.walk(&mut |seg| {
43 let st = seg.segment_type();
44 if st == SegmentType::Semicolon {
45 if only_trivia_since_last && last_semicolon_span.is_some() {
46 violations.push(LintViolation::new(
47 self.code(),
48 "Consecutive semicolons found (empty statement).",
49 seg.span(),
50 ));
51 }
52 last_semicolon_span = Some(seg.span());
53 only_trivia_since_last = true;
54 } else if !st.is_trivia()
55 && st != SegmentType::File
56 && st != SegmentType::Statement
57 && st != SegmentType::Unparsable
58 {
59 only_trivia_since_last = false;
60 }
61 });
62
63 violations
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::test_utils::lint_sql;
71
72 #[test]
73 fn test_st12_flags_consecutive_semicolons() {
74 let violations = lint_sql("SELECT 1;;", RuleST12);
75 assert_eq!(violations.len(), 1);
76 assert!(violations[0].message.contains("Consecutive"));
77 }
78
79 #[test]
80 fn test_st12_accepts_single_semicolon() {
81 let violations = lint_sql("SELECT 1;", RuleST12);
82 assert_eq!(violations.len(), 0);
83 }
84
85 #[test]
86 fn test_st12_accepts_separate_statements() {
87 let violations = lint_sql("SELECT 1; SELECT 2;", RuleST12);
88 assert_eq!(violations.len(), 0);
89 }
90}