rigsql_rules/layout/
lt01.rs1use rigsql_core::{Segment, SegmentType, TokenKind};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6#[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 if text.len() > 1 && ctx.index_in_parent > 0 {
52 let prev = &ctx.siblings[ctx.index_in_parent - 1];
53 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}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::test_utils::lint_sql;
74
75 #[test]
76 fn test_lt01_flags_double_space() {
77 let violations = lint_sql("SELECT * FROM t", RuleLT01);
78 assert!(!violations.is_empty());
79 assert!(violations.iter().all(|v| v.rule_code == "LT01"));
80 }
81
82 #[test]
83 fn test_lt01_accepts_single_space() {
84 let violations = lint_sql("SELECT * FROM t", RuleLT01);
85 assert_eq!(violations.len(), 0);
86 }
87
88 #[test]
89 fn test_lt01_skips_indentation() {
90 let violations = lint_sql("SELECT *\n FROM t", RuleLT01);
91 assert_eq!(violations.len(), 0);
92 }
93}