rigsql_rules/convention/
cv07.rs1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6#[derive(Debug, Default)]
11pub struct RuleCV07;
12
13impl Rule for RuleCV07 {
14 fn code(&self) -> &'static str {
15 "CV07"
16 }
17 fn name(&self) -> &'static str {
18 "convention.statement_brackets"
19 }
20 fn description(&self) -> &'static str {
21 "Top-level statements should not be wrapped in brackets."
22 }
23 fn explanation(&self) -> &'static str {
24 "Wrapping an entire statement in parentheses is unnecessary and can be \
25 confusing. Remove the outer brackets to improve readability."
26 }
27 fn groups(&self) -> &[RuleGroup] {
28 &[RuleGroup::Convention]
29 }
30 fn is_fixable(&self) -> bool {
31 true
32 }
33
34 fn crawl_type(&self) -> CrawlType {
35 CrawlType::Segment(vec![SegmentType::Statement])
36 }
37
38 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39 let children = ctx.segment.children();
40 let non_trivia: Vec<_> = children
41 .iter()
42 .filter(|c| !c.segment_type().is_trivia())
43 .collect();
44
45 if non_trivia.len() < 2 {
46 return vec![];
47 }
48
49 let first = non_trivia.first().unwrap();
50 let last_idx = non_trivia.len() - 1;
51 let (check_last, _has_semi) =
53 if non_trivia[last_idx].segment_type() == SegmentType::Semicolon && last_idx >= 2 {
54 (non_trivia[last_idx - 1], true)
55 } else {
56 (non_trivia[last_idx], false)
57 };
58
59 let is_lparen = first.segment_type() == SegmentType::LParen
60 || matches!(first, Segment::Token(t) if t.token.text.as_str() == "(");
61 let is_rparen = check_last.segment_type() == SegmentType::RParen
62 || matches!(check_last, Segment::Token(t) if t.token.text.as_str() == ")");
63
64 if is_lparen && is_rparen {
65 vec![LintViolation::with_fix(
66 self.code(),
67 "Unnecessary brackets around statement.",
68 ctx.segment.span(),
69 vec![
70 SourceEdit::delete(first.span()),
71 SourceEdit::delete(check_last.span()),
72 ],
73 )]
74 } else {
75 vec![]
76 }
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::test_utils::lint_sql;
84
85 #[test]
86 fn test_cv07_accepts_normal_statement() {
87 let violations = lint_sql("SELECT 1", RuleCV07);
88 assert_eq!(violations.len(), 0);
89 }
90}