rigsql_rules/layout/
lt03.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6#[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 if ctx.index_in_parent > 0 {
47 let prev = &ctx.siblings[ctx.index_in_parent - 1];
48 let prev_type = prev.segment_type();
49 if prev_type != SegmentType::Whitespace && prev_type != SegmentType::Newline {
50 violations.push(LintViolation::with_fix_and_msg_key(
51 self.code(),
52 "Missing space before operator.",
53 span,
54 vec![SourceEdit::insert(span.start, " ")],
55 "rules.LT03.msg.before",
56 vec![],
57 ));
58 }
59 }
60
61 if ctx.index_in_parent + 1 < ctx.siblings.len() {
64 let next = &ctx.siblings[ctx.index_in_parent + 1];
65 let next_type = next.segment_type();
66 if next_type != SegmentType::Whitespace && next_type != SegmentType::Newline {
67 violations.push(LintViolation::with_fix_and_msg_key(
68 self.code(),
69 "Missing space after operator.",
70 span,
71 vec![SourceEdit::insert(span.end, " ")],
72 "rules.LT03.msg.after",
73 vec![],
74 ));
75 }
76 }
77
78 violations
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::test_utils::lint_sql;
86
87 #[test]
88 fn test_lt03_flags_missing_space() {
89 let violations = lint_sql("SELECT * FROM t WHERE x=1", RuleLT03);
90 assert!(!violations.is_empty());
91 assert!(violations.iter().all(|v| v.rule_code == "LT03"));
92 }
93
94 #[test]
95 fn test_lt03_accepts_proper_spacing() {
96 let violations = lint_sql("SELECT * FROM t WHERE x = 1", RuleLT03);
97 assert_eq!(violations.len(), 0);
98 }
99
100 #[test]
101 fn test_lt03_accepts_newline_after_operator() {
102 let violations = lint_sql("SELECT 'Data' =\n 'n'", RuleLT03);
104 assert_eq!(violations.len(), 0);
105 }
106
107 #[test]
108 fn test_lt03_accepts_newline_before_operator() {
109 let violations = lint_sql("SELECT *\nFROM t WHERE x\n= 1", RuleLT03);
111 let after_violations: Vec<_> = violations
112 .iter()
113 .filter(|v| v.message.contains("before"))
114 .collect();
115 assert_eq!(after_violations.len(), 0);
116 }
117}