lint/rules/builtin/
no_consecutive_empty_lines.rs1use 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}