rigsql_rules/layout/
lt04.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug)]
10pub struct RuleLT04 {
11 pub style: CommaStyle,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum CommaStyle {
16 Trailing,
17 Leading,
18}
19
20impl Default for RuleLT04 {
21 fn default() -> Self {
22 Self {
23 style: CommaStyle::Trailing,
24 }
25 }
26}
27
28impl Rule for RuleLT04 {
29 fn code(&self) -> &'static str {
30 "LT04"
31 }
32 fn name(&self) -> &'static str {
33 "layout.commas"
34 }
35 fn description(&self) -> &'static str {
36 "Commas should be at the end of the line, not the start."
37 }
38 fn explanation(&self) -> &'static str {
39 "Commas in SELECT lists, GROUP BY, and other clauses should consistently appear \
40 at the end of the line (trailing) or the start of the next line (leading). \
41 Mixing styles reduces readability."
42 }
43 fn groups(&self) -> &[RuleGroup] {
44 &[RuleGroup::Layout]
45 }
46 fn is_fixable(&self) -> bool {
47 true
48 }
49
50 fn configure(&mut self, settings: &std::collections::HashMap<String, String>) {
51 if let Some(val) = settings.get("comma_style") {
52 self.style = match val.as_str() {
53 "leading" => CommaStyle::Leading,
54 _ => CommaStyle::Trailing,
55 };
56 }
57 }
58
59 fn crawl_type(&self) -> CrawlType {
60 CrawlType::Segment(vec![SegmentType::Comma])
61 }
62
63 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
64 let span = ctx.segment.span();
65
66 match self.style {
67 CommaStyle::Trailing => {
68 if is_leading_comma(ctx) {
71 return vec![LintViolation::new(
72 self.code(),
73 "Comma should be at the end of the line, not the start.",
74 span,
75 )];
76 }
77 }
78 CommaStyle::Leading => {
79 if is_trailing_comma(ctx) {
81 return vec![LintViolation::new(
82 self.code(),
83 "Comma should be at the start of the line, not the end.",
84 span,
85 )];
86 }
87 }
88 }
89
90 vec![]
91 }
92}
93
94fn is_leading_comma(ctx: &RuleContext) -> bool {
96 if ctx.index_in_parent == 0 {
97 return false;
98 }
99 let mut i = ctx.index_in_parent - 1;
101 loop {
102 let seg = &ctx.siblings[i];
103 match seg.segment_type() {
104 SegmentType::Whitespace => {
105 if i == 0 {
106 return false;
107 }
108 i -= 1;
109 }
110 SegmentType::Newline => return true,
111 _ => return false,
112 }
113 }
114}
115
116fn is_trailing_comma(ctx: &RuleContext) -> bool {
118 let mut i = ctx.index_in_parent + 1;
119 while i < ctx.siblings.len() {
120 let seg = &ctx.siblings[i];
121 match seg.segment_type() {
122 SegmentType::Whitespace => {
123 i += 1;
124 }
125 SegmentType::Newline => return true,
126 _ => return false,
127 }
128 }
129 false
130}