1use 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 EXTRA_SPACES_LINTER,
16 ExtraSpacesLinter,
17 ["extra-spaces"]
18}
19
20declare_lint! {
21 pub EXTRA_SPACES_LINT,
22 "extra-spaces",
23 Warning,
24 "extra spaces should be removed"
25}
26
27#[async_trait]
28impl Linter for ExtraSpacesLinter {
29 async fn apply(&self, sess: &Session) -> Result<()> {
30 for mut apml in walk_apml(sess) {
31 debug!("Looking for extra spaces in {apml:?}");
32 let mut ranges = apml
33 .lst()
34 .0
35 .iter()
36 .enumerate()
37 .batching(|iter| {
38 let mut ret = Some(
39 iter.take_while(|(_, token)| {
40 !matches!(token, lst::Token::Newline)
41 })
42 .collect_vec(),
43 );
44 _ = iter.next();
46 ret.take_if(|tokens| !tokens.is_empty())
47 })
48 .filter(|line| {
49 matches!(line.first(), Some((_, lst::Token::Spacy(_))))
50 || matches!(
51 line.last(),
52 Some((_, lst::Token::Spacy(_)))
53 )
54 })
55 .inspect(|line| {
56 let index = line[0].0;
57 LintMessage::new(EXTRA_SPACES_LINT)
58 .snippet(Snippet::new_index(sess, &apml, index))
59 .emit(sess);
60 })
61 .map(|line| {
62 let mut before = 0;
63 while let Some((_, lst::Token::Spacy(_))) = line.get(before)
64 {
65 before += 1;
66 }
67 let mut after = 0;
68 while let Some((_, lst::Token::Spacy(_))) =
69 line.get(line.len() - after)
70 {
71 after += 1;
72 }
73 let first_idx = line.first().unwrap().0;
74 let last_idx = line.last().unwrap().0;
75 (first_idx..first_idx + before, last_idx - after..last_idx)
76 })
77 .collect_vec();
78 debug!(
79 "Found {} lines with extra spaces in {:?}",
80 ranges.len(),
81 apml
82 );
83 if !sess.dry && !ranges.is_empty() {
84 ranges.reverse();
87 apml.with_upgraded(|apml| {
88 apml.with_lst(|lst| {
89 for (range1, range2) in ranges {
90 lst.0.drain(range2);
91 lst.0.drain(range1);
92 }
93 });
94 });
95 }
96 }
97 Ok(())
98 }
99}