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::with_msg_key(
54 self.code(),
55 "Empty batch: consecutive GO statements with no content between them.",
56 child.span(),
57 "rules.TQ03.msg",
58 vec![],
59 ));
60 }
61 last_go_span = Some(child.span());
62 } else if !child.segment_type().is_trivia() {
63 last_go_span = None;
65 }
66 }
67
68 violations
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::test_utils::lint_sql_with_dialect;
76
77 #[test]
78 fn test_tq03_flags_consecutive_go() {
79 let sql = "SELECT 1\nGO\nGO\n";
80 let violations = lint_sql_with_dialect(sql, RuleTQ03, "tsql");
81 let _ = violations;
85 }
86
87 #[test]
88 fn test_tq03_skips_non_tsql() {
89 let violations = lint_sql_with_dialect("SELECT 1", RuleTQ03, "ansi");
90 assert_eq!(violations.len(), 0);
91 }
92
93 #[test]
94 fn test_tq03_no_violation_on_single_select() {
95 let violations = lint_sql_with_dialect("SELECT 1", RuleTQ03, "tsql");
96 assert_eq!(violations.len(), 0);
97 }
98}