harper_core/linting/
map_phrase_set_linter.rs1use 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}