rigsql_rules/layout/
lt15.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6#[derive(Debug)]
10pub struct RuleLT15 {
11 pub max_blank_lines: usize,
12}
13
14impl Default for RuleLT15 {
15 fn default() -> Self {
16 Self { max_blank_lines: 1 }
17 }
18}
19
20impl Rule for RuleLT15 {
21 fn code(&self) -> &'static str {
22 "LT15"
23 }
24 fn name(&self) -> &'static str {
25 "layout.newlines"
26 }
27 fn description(&self) -> &'static str {
28 "Too many consecutive blank lines."
29 }
30 fn explanation(&self) -> &'static str {
31 "Files should not contain too many consecutive blank lines. Multiple \
32 blank lines waste vertical space and reduce readability."
33 }
34 fn groups(&self) -> &[RuleGroup] {
35 &[RuleGroup::Layout]
36 }
37 fn is_fixable(&self) -> bool {
38 true
39 }
40
41 fn configure(&mut self, settings: &std::collections::HashMap<String, String>) {
42 if let Some(val) = settings.get("max_blank_lines") {
43 if let Ok(n) = val.parse() {
44 self.max_blank_lines = n;
45 }
46 }
47 }
48
49 fn crawl_type(&self) -> CrawlType {
50 CrawlType::RootOnly
51 }
52
53 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
54 let mut violations = Vec::new();
55 let mut consecutive_newlines = 0usize;
56 let mut newline_spans: Vec<rigsql_core::Span> = Vec::new();
57
58 let max = self.max_blank_lines;
59 let code = self.code();
60
61 let flush = |consecutive: &mut usize,
62 spans: &mut Vec<rigsql_core::Span>,
63 violations: &mut Vec<LintViolation>| {
64 if *consecutive > max + 1 {
66 let keep = max + 1; let delete_start = spans[keep - 1].end;
71 let delete_end = spans.last().unwrap().end;
72 let delete_span = rigsql_core::Span::new(delete_start, delete_end);
73 violations.push(LintViolation::with_fix(
74 code,
75 format!("Too many blank lines ({}, max {}).", *consecutive - 1, max),
76 spans[0],
77 vec![SourceEdit::delete(delete_span)],
78 ));
79 }
80 *consecutive = 0;
81 spans.clear();
82 };
83
84 ctx.root.walk(&mut |seg| {
85 if seg.segment_type() == SegmentType::Newline {
86 consecutive_newlines += 1;
87 newline_spans.push(seg.span());
88 } else if seg.segment_type() == SegmentType::Whitespace {
89 } else if seg.children().is_empty() {
91 flush(
92 &mut consecutive_newlines,
93 &mut newline_spans,
94 &mut violations,
95 );
96 }
97 });
98
99 flush(
101 &mut consecutive_newlines,
102 &mut newline_spans,
103 &mut violations,
104 );
105
106 violations
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::test_utils::lint_sql;
114
115 #[test]
116 fn test_lt15_accepts_single_blank_line() {
117 let violations = lint_sql("SELECT 1;\n\nSELECT 2;\n", RuleLT15::default());
118 assert_eq!(violations.len(), 0);
119 }
120
121 #[test]
122 fn test_lt15_accepts_no_blank_lines() {
123 let violations = lint_sql("SELECT 1;\n", RuleLT15::default());
124 assert_eq!(violations.len(), 0);
125 }
126}