harper_core/linting/
how_to.rs

1use crate::{
2    Token, TokenStringExt,
3    linting::{Lint, LintKind, PatternLinter, Suggestion},
4    patterns::{All, InflectionOfBe, Invert, OwnedPatternExt, Pattern, SequencePattern},
5};
6
7pub struct HowTo {
8    pattern: Box<dyn Pattern>,
9}
10
11impl Default for HowTo {
12    fn default() -> Self {
13        let mut pattern = All::default();
14
15        let pos_pattern = SequencePattern::default()
16            .t_aco("how")
17            .then_whitespace()
18            .then_verb();
19        pattern.add(Box::new(pos_pattern));
20
21        let exceptions = SequencePattern::default()
22            .then_anything()
23            .then_anything()
24            .then(
25                InflectionOfBe::new().or(Box::new(|tok: &Token, src: &[char]| {
26                    tok.kind.is_auxiliary_verb()
27                        || tok.kind.is_adjective()
28                        || tok.kind.is_present_tense_verb()
29                        // Special case for "did" as in "how did you do that?"
30                        || tok.span.get_content_string(src).eq_ignore_ascii_case("did")
31                })),
32            );
33
34        pattern.add(Box::new(Invert::new(exceptions)));
35
36        Self {
37            pattern: Box::new(pattern),
38        }
39    }
40}
41
42impl PatternLinter for HowTo {
43    fn pattern(&self) -> &dyn Pattern {
44        self.pattern.as_ref()
45    }
46
47    fn match_to_lint(&self, toks: &[Token], _src: &[char]) -> Option<Lint> {
48        let span = toks[0..2].span()?;
49        let fix: Vec<char> = "to ".chars().collect();
50
51        Some(Lint {
52            span,
53            lint_kind: LintKind::WordChoice,
54            suggestions: vec![Suggestion::InsertAfter(fix)],
55            message: "Insert `to` after `how` (e.g., `how to clone`).".into(),
56            priority: 63,
57        })
58    }
59
60    fn description(&self) -> &str {
61        "Detects the omission of `to` in constructions like `how clone / how install` and suggests `how to …`."
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::HowTo;
68    use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
69
70    #[test]
71    fn flags_missing_to() {
72        assert_suggestion_result(
73            "Here's how clone the repository.",
74            HowTo::default(),
75            "Here's how to clone the repository.",
76        );
77    }
78
79    #[test]
80    fn ignores_correct_phrase() {
81        assert_lint_count("Here's how to clone the repository.", HowTo::default(), 0);
82    }
83
84    #[test]
85    fn flags_other_verbs() {
86        assert_suggestion_result(
87            "Learn how install Rust.",
88            HowTo::default(),
89            "Learn how to install Rust.",
90        );
91    }
92
93    #[test]
94    fn ros_package_install() {
95        assert_suggestion_result(
96            "Can someone explain how install this ROS package on Humble?",
97            HowTo::default(),
98            "Can someone explain how to install this ROS package on Humble?",
99        );
100    }
101
102    #[test]
103    fn extract_and_install_app() {
104        assert_suggestion_result(
105            "Here’s a quick guide on how install an app you’ve extracted from a tarball.",
106            HowTo::default(),
107            "Here’s a quick guide on how to install an app you’ve extracted from a tarball.",
108        );
109    }
110
111    #[test]
112    fn dll_files() {
113        assert_suggestion_result(
114            "This video shows how fix missing DLL files on Windows.",
115            HowTo::default(),
116            "This video shows how to fix missing DLL files on Windows.",
117        );
118    }
119
120    #[test]
121    fn dofus_on_ubuntu() {
122        assert_suggestion_result(
123            "Full tutorial on how install Dofus under Ubuntu.",
124            HowTo::default(),
125            "Full tutorial on how to install Dofus under Ubuntu.",
126        );
127    }
128
129    #[test]
130    fn tar_gz_install() {
131        assert_suggestion_result(
132            "Find out how install software shipped as a .tar.gz archive.",
133            HowTo::default(),
134            "Find out how to install software shipped as a .tar.gz archive.",
135        );
136    }
137
138    #[test]
139    fn thrift_libraries() {
140        assert_suggestion_result(
141            "Anyone know how install the Thrift libraries from source?",
142            HowTo::default(),
143            "Anyone know how to install the Thrift libraries from source?",
144        );
145    }
146
147    #[test]
148    fn windows_adk() {
149        assert_suggestion_result(
150            "Lost the Windows ADK again—remind me how install it?",
151            HowTo::default(),
152            "Lost the Windows ADK again—remind me how to install it?",
153        );
154    }
155
156    #[test]
157    fn accounting_errors() {
158        assert_suggestion_result(
159            "Eight common accounting errors and how fix them.",
160            HowTo::default(),
161            "Eight common accounting errors and how to fix them.",
162        );
163    }
164
165    #[test]
166    fn sentence_fragments() {
167        assert_suggestion_result(
168            "Here’s what sentence fragments are and how fix them.",
169            HowTo::default(),
170            "Here’s what sentence fragments are and how to fix them.",
171        );
172    }
173
174    #[test]
175    fn zipper_slider() {
176        assert_suggestion_result(
177            "Quick demo on how fix a broken zipper slider.",
178            HowTo::default(),
179            "Quick demo on how to fix a broken zipper slider.",
180        );
181    }
182
183    #[test]
184    fn door_lock() {
185        assert_suggestion_result(
186            "Tips on how fix a door that won’t lock.",
187            HowTo::default(),
188            "Tips on how to fix a door that won’t lock.",
189        );
190    }
191
192    #[test]
193    fn already_correct_install() {
194        assert_lint_count(
195            "See how to install the package with apt.",
196            HowTo::default(),
197            0,
198        );
199    }
200
201    #[test]
202    fn already_correct_fix() {
203        assert_lint_count(
204            "He showed me how to fix the zipper in ten minutes.",
205            HowTo::default(),
206            0,
207        );
208    }
209
210    #[test]
211    fn how_are_you() {
212        assert_lint_count("How are you?", HowTo::default(), 0);
213    }
214
215    #[test]
216    fn how_calm_you_are() {
217        assert_lint_count("I like how calm you are.", HowTo::default(), 0);
218    }
219
220    #[test]
221    fn how_will_you_make_up() {
222        assert_lint_count(
223            "How will you make up for your mistakes?",
224            HowTo::default(),
225            0,
226        );
227    }
228
229    #[test]
230    fn storytelling_clause() {
231        assert_lint_count(
232            "I will tell about how leaving my husband led to my dog winning a Nobel Prize.",
233            HowTo::default(),
234            0,
235        );
236    }
237
238    #[test]
239    #[ignore]
240    fn dont_flag_how_did_you() {
241        assert_lint_count("How did you get to school every day?", HowTo::default(), 0);
242    }
243}