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