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: 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}