harper_core/linting/
how_to.rs1use 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 || 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}