harper_core/linting/
map_phrase_linter.rs

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