harper_core/linting/
the_how_why.rs

1use crate::{
2    Token, TokenStringExt,
3    linting::{Lint, LintKind, PatternLinter, Suggestion},
4    patterns::{EitherPattern, Invert, Pattern, SequencePattern},
5};
6
7/// Suggests removing `the` when followed by how/why/who/when/what,
8/// skipping cases like `how to` and `who's who`.
9pub struct TheHowWhy {
10    pattern: EitherPattern,
11}
12
13impl Default for TheHowWhy {
14    fn default() -> Self {
15        let the_how = SequencePattern::default()
16            .t_aco("the")
17            .then_whitespace()
18            .t_aco("how")
19            .then(Invert::new(
20                SequencePattern::default().then_whitespace().t_aco("to"),
21            ));
22
23        let the_who = SequencePattern::default()
24            .t_aco("the")
25            .then_whitespace()
26            .t_aco("who")
27            .then(Invert::new(
28                SequencePattern::default()
29                    .then_whitespace()
30                    .t_aco("'s")
31                    .then_whitespace()
32                    .t_aco("who"),
33            ));
34
35        let the_why = SequencePattern::default()
36            .t_aco("the")
37            .then_whitespace()
38            .t_aco("why");
39
40        let the_when = SequencePattern::default()
41            .t_aco("the")
42            .then_whitespace()
43            .t_aco("when");
44
45        let the_what = SequencePattern::default()
46            .t_aco("the")
47            .then_whitespace()
48            .t_aco("what");
49
50        let pattern = EitherPattern::new(vec![
51            Box::new(the_how),
52            Box::new(the_who),
53            Box::new(the_why),
54            Box::new(the_when),
55            Box::new(the_what),
56        ]);
57
58        Self { pattern }
59    }
60}
61
62impl PatternLinter for TheHowWhy {
63    fn pattern(&self) -> &dyn Pattern {
64        &self.pattern
65    }
66
67    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
68        let the_token_span = matched_tokens[0..2].span()?;
69        let question_word_token = matched_tokens.get(2)?;
70        let question_word = question_word_token.span.get_content(source);
71
72        Some(Lint {
73            span: the_token_span,
74            lint_kind: LintKind::Miscellaneous,
75            message: format!(
76                "Remove `the` before `{}`. In most contexts, `{}` alone is clearer.",
77                question_word.iter().collect::<String>(),
78                question_word.iter().collect::<String>()
79            ),
80            suggestions: vec![Suggestion::Remove],
81            priority: 31,
82        })
83    }
84
85    fn description(&self) -> &str {
86        "Removes the extra `the` from expressions like `the how`, skipping `how to` and `who's who`."
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::TheHowWhy;
93    use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
94
95    #[test]
96    fn basic_the_how() {
97        assert_suggestion_result(
98            "This is the how it all started.",
99            TheHowWhy::default(),
100            "This is how it all started.",
101        );
102    }
103
104    #[test]
105    fn the_why() {
106        assert_suggestion_result(
107            "The important part is the why it matters.",
108            TheHowWhy::default(),
109            "The important part is why it matters.",
110        );
111    }
112
113    #[test]
114    fn skip_how_to() {
115        assert_lint_count(
116            "I'd like to explain the how to install this properly.",
117            TheHowWhy::default(),
118            0,
119        );
120    }
121
122    #[test]
123    fn skip_whos_who() {
124        assert_lint_count(
125            "We covered the who's who of corporate leadership last time.",
126            TheHowWhy::default(),
127            0,
128        );
129    }
130
131    #[test]
132    fn the_who() {
133        assert_suggestion_result(
134            "We must identify the who is responsible.",
135            TheHowWhy::default(),
136            "We must identify who is responsible.",
137        );
138    }
139
140    #[test]
141    fn the_when() {
142        assert_suggestion_result(
143            "He outlined the when the new phase will start.",
144            TheHowWhy::default(),
145            "He outlined when the new phase will start.",
146        );
147    }
148
149    #[test]
150    fn the_what() {
151        assert_suggestion_result(
152            "The presentation clarifies the what we intend to build.",
153            TheHowWhy::default(),
154            "The presentation clarifies what we intend to build.",
155        );
156    }
157
158    #[test]
159    fn no_false_positive() {
160        assert_lint_count(
161            "These tips examine the how to fix your code quickly, plus the what's next.",
162            TheHowWhy::default(),
163            0,
164        );
165    }
166}