libpfu_style/
empty_line.rs1use anyhow::Result;
4use async_trait::async_trait;
5use itertools::Itertools;
6use libabbs::apml::lst;
7use libpfu::{
8 Linter, Session, declare_lint, declare_linter,
9 message::{LintMessage, Snippet},
10 walk_apml,
11};
12use log::debug;
13
14declare_linter! {
15 pub EMPTY_LINE_LINTER,
16 EmptyLineLinter,
17 [
18 "missing-trailing-line",
19 "too-many-trailing-empty-lines",
20 "too-many-empty-lines",
21 ]
22}
23
24declare_lint! {
25 pub MISSING_TRAILING_LINE_LINT,
26 "missing-trailing-line",
27 Warning,
28 "missing empty line at the end"
29}
30
31declare_lint! {
32 pub TOO_MANY_TRAILING_EMPTY_LINES,
33 "too-many-trailing-empty-lines",
34 Warning,
35 "too many trailing empty lines"
36}
37
38declare_lint! {
39 pub TOO_MANY_EMPTY_LINES,
40 "too-many-empty-lines",
41 Warning,
42 "more than two empty lines"
43}
44
45#[async_trait]
46impl Linter for EmptyLineLinter {
47 async fn apply(&self, sess: &Session) -> Result<()> {
48 for mut apml in walk_apml(sess) {
49 {
50 debug!("Looking for missing trailing new lines in {apml:?}");
51 let missing_new_line = apml
52 .lst()
53 .0
54 .iter()
55 .rev()
56 .take_while(|token| !matches!(token, lst::Token::Newline))
57 .any(|token| token.is_empty());
58 if missing_new_line {
59 LintMessage::new(MISSING_TRAILING_LINE_LINT)
60 .snippet(Snippet::new_index(
61 sess,
62 &apml,
63 apml.lst().0.len() - 1,
64 ))
65 .emit(sess);
66 if !sess.dry {
67 apml.with_upgraded(|apml| {
68 apml.with_lst(|lst| lst.0.push(lst::Token::Newline))
69 });
70 }
71 }
72 }
73 {
74 debug!("Counting trailing empty lines in {apml:?}");
75 let trailing_newlines = apml
76 .lst()
77 .0
78 .iter()
79 .enumerate()
80 .rev()
81 .take_while(|(_, token)| token.is_empty())
82 .filter(|(_, token)| matches!(token, lst::Token::Newline))
83 .collect_vec();
84 if trailing_newlines.len() > 1 {
85 LintMessage::new(TOO_MANY_TRAILING_EMPTY_LINES)
86 .snippet(Snippet::new_index(
87 sess,
88 &apml,
89 apml.lst().0.len() - 1,
90 ))
91 .emit(sess);
92 if !sess.dry {
93 let start = trailing_newlines.first().unwrap().0 + 1;
94 apml.with_upgraded(|apml| {
95 apml.with_lst(|lst| lst.0.truncate(start - 1))
96 });
97 }
98 }
99 }
100 {
101 debug!("Counting continuous empty lines in {apml:?}");
102 enum State {
103 NotEmpty,
104 Empty { from: usize, lines: usize },
105 }
106 let mut state = State::NotEmpty;
107 let mut ranges = Vec::new();
108 for (idx, token) in apml.lst().0.iter().enumerate() {
109 if token.is_empty() {
110 match state {
111 State::NotEmpty => {
112 state = State::Empty {
113 from: idx,
114 lines: 0,
115 }
116 }
117 State::Empty { from: _, lines: _ } => {}
118 }
119 if matches!(token, lst::Token::Newline) {
120 if let State::Empty { from: _, lines } = &mut state
121 {
122 *lines += 1;
123 } else {
124 unreachable!()
125 }
126 }
127 } else {
128 match state {
129 State::NotEmpty => {}
130 State::Empty { from, lines } => {
131 state = State::NotEmpty;
132 if lines > 2 {
133 LintMessage::new(TOO_MANY_EMPTY_LINES)
134 .snippet(Snippet::new_index(
135 sess, &apml, from,
136 ))
137 .emit(sess);
138 ranges.push(from..idx);
139 }
140 }
141 }
142 }
143 }
144 if !sess.dry {
147 ranges.reverse();
148 if !ranges.is_empty() {
149 apml.with_upgraded(|apml| {
150 apml.with_lst(|lst| {
151 for range in ranges {
152 lst.0.drain(range.start..range.end);
153 lst.0.insert(
154 range.start,
155 lst::Token::Newline,
156 );
157 lst.0.insert(
158 range.start,
159 lst::Token::Newline,
160 );
161 }
162 })
163 });
164 }
165 }
166 }
167 }
168 Ok(())
169 }
170}