Skip to main content

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