rigsql_rules/tsql/
tq03.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
11pub struct RuleTQ03;
12
13impl Rule for RuleTQ03 {
14 fn code(&self) -> &'static str {
15 "TQ03"
16 }
17 fn name(&self) -> &'static str {
18 "tsql.empty_batch"
19 }
20 fn description(&self) -> &'static str {
21 "Avoid empty batches (consecutive GO statements)."
22 }
23 fn explanation(&self) -> &'static str {
24 "In T-SQL, GO is a batch separator. Two consecutive GO statements with nothing \
25 meaningful between them create an empty batch. This is unnecessary clutter and \
26 may indicate accidentally deleted code."
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::RootOnly
37 }
38
39 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40 if ctx.dialect != "tsql" {
41 return vec![];
42 }
43
44 let mut violations = Vec::new();
45 let children = ctx.segment.children();
46
47 let mut last_go_span = None;
48
49 for child in children {
50 if child.segment_type() == SegmentType::GoStatement {
51 if let Some(_prev_span) = last_go_span {
52 violations.push(LintViolation::new(
54 self.code(),
55 "Empty batch: consecutive GO statements with no content between them.",
56 child.span(),
57 ));
58 }
59 last_go_span = Some(child.span());
60 } else if !child.segment_type().is_trivia() {
61 last_go_span = None;
63 }
64 }
65
66 violations
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::test_utils::lint_sql_with_dialect;
74
75 #[test]
76 fn test_tq03_flags_consecutive_go() {
77 let sql = "SELECT 1\nGO\nGO\n";
78 let violations = lint_sql_with_dialect(sql, RuleTQ03, "tsql");
79 let _ = violations;
83 }
84
85 #[test]
86 fn test_tq03_skips_non_tsql() {
87 let violations = lint_sql_with_dialect("SELECT 1", RuleTQ03, "ansi");
88 assert_eq!(violations.len(), 0);
89 }
90
91 #[test]
92 fn test_tq03_no_violation_on_single_select() {
93 let violations = lint_sql_with_dialect("SELECT 1", RuleTQ03, "tsql");
94 assert_eq!(violations.len(), 0);
95 }
96}