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::with_msg_key(
47 self.code(),
48 "Consecutive semicolons found (empty statement).",
49 seg.span(),
50 "rules.ST12.msg",
51 vec![],
52 ));
53 }
54 last_semicolon_span = Some(seg.span());
55 only_trivia_since_last = true;
56 } else if !st.is_trivia()
57 && st != SegmentType::File
58 && st != SegmentType::Statement
59 && st != SegmentType::Unparsable
60 {
61 only_trivia_since_last = false;
62 }
63 });
64
65 violations
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::test_utils::lint_sql;
73
74 #[test]
75 fn test_st12_flags_consecutive_semicolons() {
76 let violations = lint_sql("SELECT 1;;", RuleST12);
77 assert_eq!(violations.len(), 1);
78 assert!(violations[0].message.contains("Consecutive"));
79 }
80
81 #[test]
82 fn test_st12_accepts_single_semicolon() {
83 let violations = lint_sql("SELECT 1;", RuleST12);
84 assert_eq!(violations.len(), 0);
85 }
86
87 #[test]
88 fn test_st12_accepts_separate_statements() {
89 let violations = lint_sql("SELECT 1; SELECT 2;", RuleST12);
90 assert_eq!(violations.len(), 0);
91 }
92}