harper_core/linting/
map_phrase_set_linter.rs

1use super::{ExprLinter, Lint, LintKind};
2use crate::CharStringExt;
3use crate::expr::Expr;
4use crate::expr::FixedPhrase;
5use crate::expr::LongestMatchOf;
6use crate::linting::Suggestion;
7use crate::linting::expr_linter::Chunk;
8use crate::{Token, TokenStringExt};
9
10pub struct MapPhraseSetLinter<'a> {
11    description: String,
12    expr: Box<dyn Expr>,
13    wrong_forms_to_correct_forms: &'a [(&'a str, &'a str)],
14    multi_wrong_forms_to_multi_correct_forms: &'a [(&'a [&'a str], &'a [&'a str])],
15    message: String,
16    lint_kind: LintKind,
17}
18
19impl<'a> MapPhraseSetLinter<'a> {
20    pub fn one_to_one(
21        wrong_forms_to_correct_forms: &'a [(&'a str, &'a str)],
22        message: impl ToString,
23        description: impl ToString,
24        lint_kind: Option<LintKind>,
25    ) -> Self {
26        let expr = Box::new(LongestMatchOf::new(
27            wrong_forms_to_correct_forms
28                .iter()
29                .map(|(wrong_form, _correct_form)| {
30                    let expr: Box<dyn Expr> = Box::new(FixedPhrase::from_phrase(wrong_form));
31                    expr
32                })
33                .collect(),
34        ));
35
36        Self {
37            description: description.to_string(),
38            expr,
39            wrong_forms_to_correct_forms,
40            multi_wrong_forms_to_multi_correct_forms: &[],
41            message: message.to_string(),
42            lint_kind: lint_kind.unwrap_or(LintKind::Miscellaneous),
43        }
44    }
45
46    pub fn many_to_many(
47        multi_wrong_forms_to_multi_correct_forms: &'a [(&'a [&'a str], &'a [&'a str])],
48        message: impl ToString,
49        description: impl ToString,
50        lint_kind: Option<LintKind>,
51    ) -> Self {
52        let mut lmo = LongestMatchOf::new(Vec::new());
53        for (wrong_forms, _correct_forms) in multi_wrong_forms_to_multi_correct_forms {
54            for wrong_form in wrong_forms.iter() {
55                lmo.add(FixedPhrase::from_phrase(wrong_form));
56            }
57        }
58        let expr = Box::new(lmo);
59
60        Self {
61            description: description.to_string(),
62            expr,
63            wrong_forms_to_correct_forms: &[],
64            multi_wrong_forms_to_multi_correct_forms,
65            message: message.to_string(),
66            lint_kind: lint_kind.unwrap_or(LintKind::Miscellaneous),
67        }
68    }
69}
70
71impl<'a> ExprLinter for MapPhraseSetLinter<'a> {
72    type Unit = Chunk;
73
74    fn expr(&self) -> &dyn Expr {
75        self.expr.as_ref()
76    }
77
78    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
79        let span = matched_tokens.span()?;
80        let matched_text = span.get_content(source);
81
82        let mut suggestions: Vec<_> = self
83            .wrong_forms_to_correct_forms
84            .iter()
85            .filter(|(wrong_form, _)| matched_text.eq_ignore_ascii_case_str(wrong_form))
86            .map(|(_, correct_form)| {
87                Suggestion::replace_with_match_case(correct_form.chars().collect(), matched_text)
88            })
89            .collect();
90
91        let many_to_many_suggestions: Vec<_> = self
92            .multi_wrong_forms_to_multi_correct_forms
93            .iter()
94            .flat_map(|(wrong_forms, correct_forms)| {
95                wrong_forms
96                    .iter()
97                    .filter(move |&&wrong_form| matched_text.eq_ignore_ascii_case_str(wrong_form))
98                    .flat_map(move |_| {
99                        correct_forms.iter().map(move |correct_form| {
100                            Suggestion::replace_with_match_case(
101                                correct_form.chars().collect(),
102                                matched_text,
103                            )
104                        })
105                    })
106            })
107            .collect();
108
109        suggestions.extend(many_to_many_suggestions);
110
111        if suggestions.is_empty() {
112            return None;
113        }
114
115        Some(Lint {
116            span,
117            lint_kind: self.lint_kind,
118            suggestions,
119            message: self.message.to_string(),
120            priority: 31,
121        })
122    }
123
124    fn description(&self) -> &str {
125        self.description.as_str()
126    }
127}