Skip to main content

harper_core/linting/
map_phrase_linter.rs

1use 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}