harper_core/linting/
map_phrase_linter.rs1use super::{ExprLinter, Lint, LintKind};
2use crate::expr::Expr;
3use crate::expr::FixedPhrase;
4use crate::expr::LongestMatchOf;
5use crate::expr::SimilarToPhrase;
6use crate::linting::Suggestion;
7use crate::linting::expr_linter::Chunk;
8use crate::{Token, TokenStringExt};
9
10pub struct MapPhraseLinter {
11 description: String,
12 expr: Box<dyn Expr>,
13 correct_forms: Vec<String>,
14 message: String,
15 lint_kind: LintKind,
16}
17
18impl MapPhraseLinter {
19 pub fn new(
20 expr: Box<dyn Expr>,
21 correct_forms: impl IntoIterator<Item = impl ToString>,
22 message: impl ToString,
23 description: impl ToString,
24 lint_kind: Option<LintKind>,
25 ) -> Self {
26 Self {
27 description: description.to_string(),
28 expr,
29 correct_forms: correct_forms.into_iter().map(|f| f.to_string()).collect(),
30 message: message.to_string(),
31 lint_kind: lint_kind.unwrap_or(LintKind::Miscellaneous),
32 }
33 }
34
35 pub fn new_similar_to_phrase(phrase: &'static str, detectable_distance: u8) -> Self {
36 Self::new(
37 Box::new(SimilarToPhrase::from_phrase(phrase, detectable_distance)),
38 [phrase],
39 format!("Did you mean the phrase `{phrase}`?"),
40 format!("Looks for slight improper modifications to the phrase `{phrase}`."),
41 None,
42 )
43 }
44
45 pub fn new_fixed_phrases(
46 phrase: impl IntoIterator<Item = impl AsRef<str>>,
47 correct_forms: impl IntoIterator<Item = impl ToString>,
48 message: impl ToString,
49 description: impl ToString,
50 lint_kind: Option<LintKind>,
51 ) -> Self {
52 let patterns = LongestMatchOf::new(
53 phrase
54 .into_iter()
55 .map(|p| {
56 let expr: Box<dyn Expr> = Box::new(FixedPhrase::from_phrase(p.as_ref()));
57 expr
58 })
59 .collect(),
60 );
61
62 Self::new(
63 Box::new(patterns),
64 correct_forms,
65 message,
66 description,
67 lint_kind,
68 )
69 }
70
71 pub fn new_fixed_phrase(
72 phrase: impl AsRef<str>,
73 correct_forms: impl IntoIterator<Item = impl ToString>,
74 message: impl ToString,
75 description: impl ToString,
76 lint_kind: Option<LintKind>,
77 ) -> Self {
78 Self::new(
79 Box::new(FixedPhrase::from_phrase(phrase.as_ref())),
80 correct_forms,
81 message,
82 description,
83 lint_kind,
84 )
85 }
86
87 pub fn new_closed_compound(phrase: impl AsRef<str>, correct_form: impl ToString) -> Self {
88 let message = format!(
89 "Did you mean the closed compound `{}`?",
90 correct_form.to_string()
91 );
92
93 let description = format!(
94 "Looks for incorrect spacing inside the closed compound `{}`.",
95 correct_form.to_string()
96 );
97
98 Self::new_fixed_phrase(
99 phrase,
100 [correct_form],
101 message,
102 description,
103 Some(LintKind::Miscellaneous),
104 )
105 }
106}
107
108impl ExprLinter for MapPhraseLinter {
109 type Unit = Chunk;
110
111 fn expr(&self) -> &dyn Expr {
112 self.expr.as_ref()
113 }
114
115 fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
116 let span = matched_tokens.span()?;
117 let matched_text = span.get_content(source);
118
119 Some(Lint {
120 span,
121 lint_kind: self.lint_kind,
122 suggestions: self
123 .correct_forms
124 .iter()
125 .map(|correct_form| {
126 Suggestion::replace_with_match_case(
127 correct_form.chars().collect(),
128 matched_text,
129 )
130 })
131 .collect(),
132 message: self.message.to_string(),
133 priority: 31,
134 })
135 }
136
137 fn description(&self) -> &str {
138 self.description.as_str()
139 }
140}