Skip to main content

harper_core/linting/
initialism_linter.rs

1use crate::expr::Expr;
2
3use crate::{Token, patterns::Word};
4
5use super::{ExprLinter, Lint, LintKind, Suggestion};
6use crate::linting::expr_linter::Chunk;
7
8/// Alias for a word in an initialism expansion
9type InitialismWord = Vec<char>;
10/// Alias for a phrase an initialism expands to
11type InitialismPhrase = Vec<InitialismWord>;
12
13/// A struct that can be composed to expand initialisms, respecting the capitalization of each
14/// item.
15pub struct InitialismLinter {
16    expr: Word,
17    /// The lowercase-normalized expansion of the initialism.
18    expansions_lower: Vec<InitialismPhrase>,
19}
20
21impl InitialismLinter {
22    /// Construct a linter that can correct an initialism to
23    pub fn new(initialism: &str, expansions: &[&str]) -> Self {
24        let expansions_lower = expansions
25            .iter()
26            .map(|expansion| {
27                expansion
28                    .split(' ')
29                    .map(|s| s.chars().map(|v| v.to_ascii_lowercase()).collect())
30                    .collect()
31            })
32            .collect();
33
34        Self {
35            expr: Word::from_char_string(initialism.chars().collect()),
36            expansions_lower,
37        }
38    }
39}
40
41impl ExprLinter for InitialismLinter {
42    type Unit = Chunk;
43
44    fn expr(&self) -> &dyn Expr {
45        &self.expr
46    }
47
48    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
49        let tok = matched_tokens.first()?;
50        let source = tok.get_ch(source);
51
52        let suggestions = self
53            .expansions_lower
54            .iter()
55            .map(|expansion_lower| {
56                let mut expansion = expansion_lower.clone();
57                let first_letter = &mut expansion[0][0];
58                *first_letter = if source[0].is_ascii_uppercase() {
59                    first_letter.to_ascii_uppercase()
60                } else {
61                    first_letter.to_ascii_lowercase()
62                };
63                Suggestion::ReplaceWith(
64                    expansion
65                        .iter()
66                        .flat_map(|word| std::iter::once(' ').chain(word.iter().copied()))
67                        .skip(1)
68                        .collect::<Vec<_>>(),
69                )
70            })
71            .collect::<Vec<_>>();
72
73        Some(Lint {
74            span: tok.span,
75            lint_kind: LintKind::Miscellaneous,
76            suggestions,
77            message: "Try expanding this initialism.".to_owned(),
78            priority: 127,
79        })
80    }
81
82    fn description(&self) -> &'static str {
83        "Expands an initialism."
84    }
85}
86
87#[cfg(test)]
88mod tests {}