Skip to main content

rigsql_rules/layout/
lt01.rs

1use rigsql_core::{Segment, SegmentType, TokenKind};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6/// LT01: Inappropriate spacing.
7///
8/// Checks for multiple spaces where a single space is expected,
9/// and missing spaces around operators.
10#[derive(Debug, Default)]
11pub struct RuleLT01;
12
13impl Rule for RuleLT01 {
14    fn code(&self) -> &'static str {
15        "LT01"
16    }
17    fn name(&self) -> &'static str {
18        "layout.spacing"
19    }
20    fn description(&self) -> &'static str {
21        "Inappropriate spacing found."
22    }
23    fn explanation(&self) -> &'static str {
24        "SQL should use single spaces between keywords and expressions. \
25         Multiple consecutive spaces (except for indentation) reduce readability. \
26         Operators should have spaces on both sides."
27    }
28    fn groups(&self) -> &[RuleGroup] {
29        &[RuleGroup::Layout]
30    }
31    fn is_fixable(&self) -> bool {
32        true
33    }
34
35    fn crawl_type(&self) -> CrawlType {
36        CrawlType::Segment(vec![SegmentType::Whitespace])
37    }
38
39    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40        let Segment::Token(t) = ctx.segment else {
41            return vec![];
42        };
43        if t.token.kind != TokenKind::Whitespace {
44            return vec![];
45        }
46
47        let text = t.token.text.as_str();
48
49        // Only flag multiple spaces within a line (not indentation)
50        // Check if this whitespace is preceded by a non-newline token
51        if text.len() > 1 && ctx.index_in_parent > 0 {
52            let prev = &ctx.siblings[ctx.index_in_parent - 1];
53            // If previous is Newline, this is indentation — skip
54            if prev.segment_type() == SegmentType::Newline {
55                return vec![];
56            }
57
58            return vec![LintViolation::with_fix(
59                self.code(),
60                format!("Expected single space, found {} spaces.", text.len()),
61                t.token.span,
62                vec![SourceEdit::replace(t.token.span, " ")],
63            )];
64        }
65
66        vec![]
67    }
68}