rigsql_rules/layout/
lt07.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 RuleLT07;
12
13impl Rule for RuleLT07 {
14 fn code(&self) -> &'static str {
15 "LT07"
16 }
17 fn name(&self) -> &'static str {
18 "layout.with_spacing"
19 }
20 fn description(&self) -> &'static str {
21 "'WITH' keyword not followed by single space."
22 }
23 fn explanation(&self) -> &'static str {
24 "The WITH keyword in a Common Table Expression should be followed by exactly \
25 one space before the CTE name. Multiple spaces or newlines between WITH and \
26 the CTE name reduce readability."
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::WithClause])
37 }
38
39 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40 let children = ctx.segment.children();
41
42 let mut with_idx = None;
44 for (i, child) in children.iter().enumerate() {
45 if let Segment::Token(t) = child {
46 if t.token.text.as_str().eq_ignore_ascii_case("WITH") {
47 with_idx = Some(i);
48 break;
49 }
50 }
51 }
52
53 let Some(with_idx) = with_idx else {
54 return vec![];
55 };
56
57 let mut trivia_start = None;
59 let mut trivia_end = None;
60 let mut raw_trivia = String::new();
61
62 for child in &children[with_idx + 1..] {
63 let st = child.segment_type();
64 if st.is_trivia() {
65 if trivia_start.is_none() {
66 trivia_start = Some(child.span());
67 }
68 trivia_end = Some(child.span());
69 raw_trivia.push_str(&child.raw());
70 } else {
71 break;
72 }
73 }
74
75 if raw_trivia == " " {
77 return vec![];
78 }
79
80 if trivia_start.is_none() {
82 let with_span = children[with_idx].span();
83 return vec![LintViolation::with_fix(
84 self.code(),
85 "Expected single space after WITH keyword.",
86 with_span,
87 vec![SourceEdit::insert(with_span.end, " ")],
88 )];
89 }
90
91 let start = trivia_start.unwrap();
92 let end = trivia_end.unwrap();
93 let full_span = start.merge(end);
94
95 vec![LintViolation::with_fix(
96 self.code(),
97 "Expected single space after WITH keyword.",
98 full_span,
99 vec![SourceEdit::replace(full_span, " ")],
100 )]
101 }
102}