use rigsql_core::SegmentType;
use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
use crate::violation::LintViolation;
#[derive(Debug, Default)]
pub struct RuleTQ03;
impl Rule for RuleTQ03 {
fn code(&self) -> &'static str {
"TQ03"
}
fn name(&self) -> &'static str {
"tsql.empty_batch"
}
fn description(&self) -> &'static str {
"Avoid empty batches (consecutive GO statements)."
}
fn explanation(&self) -> &'static str {
"In T-SQL, GO is a batch separator. Two consecutive GO statements with nothing \
meaningful between them create an empty batch. This is unnecessary clutter and \
may indicate accidentally deleted code."
}
fn groups(&self) -> &[RuleGroup] {
&[RuleGroup::Convention]
}
fn is_fixable(&self) -> bool {
false
}
fn crawl_type(&self) -> CrawlType {
CrawlType::RootOnly
}
fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
if ctx.dialect != "tsql" {
return vec![];
}
let mut violations = Vec::new();
let children = ctx.segment.children();
let mut last_go_span = None;
for child in children {
if child.segment_type() == SegmentType::GoStatement {
if let Some(_prev_span) = last_go_span {
violations.push(LintViolation::with_msg_key(
self.code(),
"Empty batch: consecutive GO statements with no content between them.",
child.span(),
"rules.TQ03.msg",
vec![],
));
}
last_go_span = Some(child.span());
} else if !child.segment_type().is_trivia() {
last_go_span = None;
}
}
violations
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::lint_sql_with_dialect;
#[test]
fn test_tq03_flags_consecutive_go() {
let sql = "SELECT 1\nGO\nGO\n";
let violations = lint_sql_with_dialect(sql, RuleTQ03, "tsql");
let _ = violations;
}
#[test]
fn test_tq03_skips_non_tsql() {
let violations = lint_sql_with_dialect("SELECT 1", RuleTQ03, "ansi");
assert_eq!(violations.len(), 0);
}
#[test]
fn test_tq03_no_violation_on_single_select() {
let violations = lint_sql_with_dialect("SELECT 1", RuleTQ03, "tsql");
assert_eq!(violations.len(), 0);
}
}