Skip to main content

lint/rules/builtin/
no_consecutive_empty_lines.rs

1use crate::output::LintMessage;
2use crate::rules::Rule;
3use std::path::Path;
4
5pub struct NoConsecutiveEmptyLinesRule;
6
7impl Rule for NoConsecutiveEmptyLinesRule {
8    fn name(&self) -> &str {
9        "no-consecutive-empty-lines"
10    }
11
12    fn category(&self) -> &str {
13        "style"
14    }
15
16    fn description(&self) -> &str {
17        "More than one consecutive empty line."
18    }
19
20    fn has_fix(&self) -> bool {
21        true
22    }
23
24    fn check(&self, content: &str, _file_path: &Path) -> Vec<LintMessage> {
25        let mut messages = Vec::new();
26        let lines: Vec<&str> = content.lines().collect();
27        let mut prev_empty = false;
28        let mut had_consecutive = false;
29
30        for (i, line) in lines.iter().enumerate() {
31            let is_empty = line.trim().is_empty();
32            if is_empty && prev_empty {
33                messages.push(LintMessage::new(
34                    i + 1,
35                    1,
36                    self.default_severity(),
37                    "Consecutive empty lines detected".to_string(),
38                    self.name().to_string(),
39                    Some("Remove extra blank lines; files should not contain more than one consecutive empty line.".to_string()),
40                ));
41                had_consecutive = true;
42            }
43            prev_empty = is_empty;
44        }
45
46        if had_consecutive {
47            let fixed = collapse_empty_lines(content);
48            if let Some(first) = messages.first_mut() {
49                first.fix = Some(crate::output::Fix {
50                    line: 0,
51                    replacement: fixed,
52                    is_safe: true,
53                });
54            }
55        }
56
57        messages
58    }
59}
60
61fn collapse_empty_lines(content: &str) -> String {
62    let lines: Vec<&str> = content.lines().collect();
63    let mut result = Vec::new();
64    let mut prev_empty = false;
65
66    for line in lines {
67        let is_empty = line.trim().is_empty();
68        if is_empty && prev_empty {
69            continue;
70        }
71        result.push(line);
72        prev_empty = is_empty;
73    }
74
75    let mut output = result.join("\n");
76    if content.ends_with('\n') || content.ends_with("\r\n") {
77        output.push('\n');
78    }
79    output
80}