Skip to main content

rigsql_rules/layout/
lt03.rs

1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6/// LT03: Operators should be followed by a single space.
7///
8/// Checks that comparison and arithmetic operators have spaces on both sides.
9#[derive(Debug, Default)]
10pub struct RuleLT03;
11
12impl Rule for RuleLT03 {
13    fn code(&self) -> &'static str {
14        "LT03"
15    }
16    fn name(&self) -> &'static str {
17        "layout.operators"
18    }
19    fn description(&self) -> &'static str {
20        "Operators should be surrounded by single spaces."
21    }
22    fn explanation(&self) -> &'static str {
23        "Binary operators (=, <, >, +, -, etc.) should have a single space on each side \
24         for readability. 'a=b' and 'a  = b' are harder to read than 'a = b'."
25    }
26    fn groups(&self) -> &[RuleGroup] {
27        &[RuleGroup::Layout]
28    }
29    fn is_fixable(&self) -> bool {
30        true
31    }
32
33    fn crawl_type(&self) -> CrawlType {
34        CrawlType::Segment(vec![
35            SegmentType::ComparisonOperator,
36            SegmentType::ArithmeticOperator,
37        ])
38    }
39
40    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
41        let span = ctx.segment.span();
42        let mut violations = Vec::new();
43
44        // Check space before operator
45        if ctx.index_in_parent > 0 {
46            let prev = &ctx.siblings[ctx.index_in_parent - 1];
47            if prev.segment_type() != SegmentType::Whitespace {
48                violations.push(LintViolation::with_fix(
49                    self.code(),
50                    "Missing space before operator.",
51                    span,
52                    vec![SourceEdit::insert(span.start, " ")],
53                ));
54            }
55        }
56
57        // Check space after operator
58        if ctx.index_in_parent + 1 < ctx.siblings.len() {
59            let next = &ctx.siblings[ctx.index_in_parent + 1];
60            if next.segment_type() != SegmentType::Whitespace {
61                violations.push(LintViolation::with_fix(
62                    self.code(),
63                    "Missing space after operator.",
64                    span,
65                    vec![SourceEdit::insert(span.end, " ")],
66                ));
67            }
68        }
69
70        violations
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use crate::test_utils::lint_sql;
78
79    #[test]
80    fn test_lt03_flags_missing_space() {
81        let violations = lint_sql("SELECT * FROM t WHERE x=1", RuleLT03);
82        assert!(!violations.is_empty());
83        assert!(violations.iter().all(|v| v.rule_code == "LT03"));
84    }
85
86    #[test]
87    fn test_lt03_accepts_proper_spacing() {
88        let violations = lint_sql("SELECT * FROM t WHERE x = 1", RuleLT03);
89        assert_eq!(violations.len(), 0);
90    }
91}