Skip to main content

sqrust_rules/layout/
max_blank_lines.rs

1use sqrust_core::{Diagnostic, FileContext, Rule};
2
3pub struct MaxBlankLines {
4    pub max_blank_lines: usize,
5}
6
7impl Default for MaxBlankLines {
8    fn default() -> Self {
9        Self { max_blank_lines: 1 }
10    }
11}
12
13impl Rule for MaxBlankLines {
14    fn name(&self) -> &'static str {
15        "MaxBlankLines"
16    }
17
18    fn check(&self, ctx: &FileContext) -> Vec<Diagnostic> {
19        let mut diags = Vec::new();
20        let lines: Vec<&str> = ctx.source.lines().collect();
21
22        let mut i = 0;
23        while i < lines.len() {
24            if lines[i].trim().is_empty() {
25                // Found the start of a blank-line run.
26                let run_start = i; // 0-indexed position of first blank line in run
27                let mut run_len = 0usize;
28                while i < lines.len() && lines[i].trim().is_empty() {
29                    run_len += 1;
30                    i += 1;
31                }
32                // Flag if the run exceeds the maximum.
33                if run_len > self.max_blank_lines {
34                    // Violation line: the (max_blank_lines + 1)-th blank in the run (1-indexed).
35                    let violation_line = run_start + self.max_blank_lines + 1; // 1-indexed
36                    diags.push(Diagnostic {
37                        rule: self.name(),
38                        message: format!(
39                            "Too many consecutive blank lines ({} found, maximum is {})",
40                            run_len,
41                            self.max_blank_lines
42                        ),
43                        line: violation_line,
44                        col: 1,
45                    });
46                }
47            } else {
48                i += 1;
49            }
50        }
51
52        diags
53    }
54
55    fn fix(&self, ctx: &FileContext) -> Option<String> {
56        let violations = self.check(ctx);
57        if violations.is_empty() {
58            return None;
59        }
60
61        let lines: Vec<&str> = ctx.source.lines().collect();
62        let mut result: Vec<&str> = Vec::with_capacity(lines.len());
63        let mut blank_run = 0usize;
64
65        for line in &lines {
66            if line.trim().is_empty() {
67                blank_run += 1;
68                if blank_run <= self.max_blank_lines {
69                    result.push(line);
70                }
71                // Lines beyond the max are silently dropped.
72            } else {
73                blank_run = 0;
74                result.push(line);
75            }
76        }
77
78        let mut fixed = result.join("\n");
79        if ctx.source.ends_with('\n') {
80            fixed.push('\n');
81        }
82        Some(fixed)
83    }
84}