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