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(
54 phrase
55 .into_iter()
56 .map(|p| {
57 let expr: Box<dyn Expr> = Box::new(weir_expr_to_expr(p.as_ref()).unwrap());
58 expr
59 })
60 .collect(),
61 );
62
63 Self::new(
64 Box::new(patterns),
65 correct_forms,
66 message,
67 description,
68 lint_kind,
69 )
70 }
71
72 pub fn new_fixed_phrase(
73 phrase: impl AsRef<str>,
74 correct_forms: impl IntoIterator<Item = impl ToString>,
75 message: impl ToString,
76 description: impl ToString,
77 lint_kind: Option<LintKind>,
78 ) -> Self {
79 Self::new(
80 Box::new(FixedPhrase::from_phrase(phrase.as_ref())),
81 correct_forms,
82 message,
83 description,
84 lint_kind,
85 )
86 }
87
88 pub fn new_closed_compound(
89 phrases: impl IntoIterator<Item = impl AsRef<str>>,
90 correct_form: impl ToString,
91 ) -> Self {
92 let message = format!(
93 "Did you mean the closed compound `{}`?",
94 correct_form.to_string()
95 );
96
97 let description = format!(
98 "Looks for incorrect spacing inside the closed compound `{}`.",
99 correct_form.to_string()
100 );
101
102 Self::new_fixed_phrases(
103 phrases,
104 [correct_form],
105 message,
106 description,
107 Some(LintKind::Miscellaneous),
108 )
109 }
110}
111
112impl ExprLinter for MapPhraseLinter {
113 type Unit = Chunk;
114
115 fn expr(&self) -> &dyn Expr {
116 self.expr.as_ref()
117 }
118
119 fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
120 let span = matched_tokens.span()?;
121 let matched_text = span.get_content(source);
122
123 Some(Lint {
124 span,
125 lint_kind: self.lint_kind,
126 suggestions: self
127 .correct_forms
128 .iter()
129 .map(|correct_form| {
130 Suggestion::replace_with_match_case(
131 correct_form.chars().collect(),
132 matched_text,
133 )
134 })
135 .collect(),
136 message: self.message.to_string(),
137 priority: 31,
138 })
139 }
140
141 fn description(&self) -> &str {
142 self.description.as_str()
143 }
144}