1use super::{Lint, LintKind, PatternLinter};
2use crate::Token;
3use crate::linting::Suggestion;
4use crate::patterns::{
5 All, EitherPattern, Invert, OwnedPatternExt, Pattern, SequencePattern, Word, WordSet,
6};
7
8#[doc = "Corrects the misuse of `then` to `than`."]
9pub struct ThenThan {
10 pattern: Box<dyn Pattern>,
11}
12
13impl ThenThan {
14 pub fn new() -> Self {
15 Self {
16 pattern: Box::new(All::new(vec![
17 Box::new(EitherPattern::new(vec![
18 Box::new(
20 SequencePattern::default()
21 .then(Word::new("other").or(Box::new(
22 |tok: &Token, source: &[char]| {
23 is_comparative_adjective(tok, source)
24 },
25 )))
26 .then_whitespace()
27 .then_any_capitalization_of("then")
28 .then_whitespace()
29 .then(Invert::new(Word::new("that"))),
30 ),
31 Box::new(
33 SequencePattern::default()
34 .then(WordSet::new(&["more", "less"]))
35 .then_whitespace()
36 .then_adjective()
37 .then_whitespace()
38 .then_any_capitalization_of("then")
39 .then_whitespace()
40 .then(Invert::new(Word::new("that"))),
41 ),
42 ])),
43 Box::new(Invert::new(WordSet::new(&["back", "this", "so", "but"]))),
45 ])),
46 }
47 }
48}
49
50fn is_comparative_adjective(tok: &Token, source: &[char]) -> bool {
52 tok.kind
53 .is_adjective()
54 .then(|| tok.span.get_content(source))
55 .is_some_and(|src| {
56 src.ends_with(&['e', 'r'])
58 || src == ['l', 'e', 's', 's']
60 || src == ['m', 'o', 'r', 'e']
61 || src == ['w', 'o', 'r', 's', 'e']
62 })
63}
64
65impl Default for ThenThan {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl PatternLinter for ThenThan {
72 fn pattern(&self) -> &dyn Pattern {
73 self.pattern.as_ref()
74 }
75 fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
76 let span = matched_tokens[matched_tokens.len() - 3].span;
78 let offending_text = span.get_content(source);
79
80 Some(Lint {
81 span,
82 lint_kind: LintKind::Miscellaneous,
83 suggestions: vec![Suggestion::replace_with_match_case(
84 "than".chars().collect(),
85 offending_text,
86 )],
87 message: "Did you mean `than`?".to_string(),
88 priority: 31,
89 })
90 }
91 fn description(&self) -> &'static str {
92 "Corrects the misuse of `then` to `than`."
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::ThenThan;
99 use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
100
101 #[test]
102 fn allows_back_then() {
103 assert_lint_count("I was a gross kid back then.", ThenThan::default(), 0);
104 }
105
106 #[test]
107 fn catches_shorter_then() {
108 assert_suggestion_result(
109 "One was shorter then the other.",
110 ThenThan::default(),
111 "One was shorter than the other.",
112 );
113 }
114
115 #[test]
116 fn catches_better_then() {
117 assert_suggestion_result(
118 "One was better then the other.",
119 ThenThan::default(),
120 "One was better than the other.",
121 );
122 }
123
124 #[test]
125 fn catches_longer_then() {
126 assert_suggestion_result(
127 "One was longer then the other.",
128 ThenThan::default(),
129 "One was longer than the other.",
130 );
131 }
132
133 #[test]
134 fn catches_less_then() {
135 assert_suggestion_result(
136 "I eat less then you.",
137 ThenThan::default(),
138 "I eat less than you.",
139 );
140 }
141
142 #[test]
143 fn catches_more_then() {
144 assert_suggestion_result(
145 "I eat more then you.",
146 ThenThan::default(),
147 "I eat more than you.",
148 );
149 }
150
151 #[test]
152 fn stronger_should_change() {
153 assert_suggestion_result(
154 "a chain is no stronger then its weakest link",
155 ThenThan::default(),
156 "a chain is no stronger than its weakest link",
157 );
158 }
159
160 #[test]
161 fn half_a_loaf_should_change() {
162 assert_suggestion_result(
163 "half a loaf is better then no bread",
164 ThenThan::default(),
165 "half a loaf is better than no bread",
166 );
167 }
168
169 #[test]
170 fn then_everyone_clapped_should_be_allowed() {
171 assert_lint_count("and then everyone clapped", ThenThan::default(), 0);
172 }
173
174 #[test]
175 fn crazier_than_rat_should_change() {
176 assert_suggestion_result(
177 "crazier then a shithouse rat",
178 ThenThan::default(),
179 "crazier than a shithouse rat",
180 );
181 }
182
183 #[test]
184 fn poke_in_eye_should_change() {
185 assert_suggestion_result(
186 "better then a poke in the eye with a sharp stick",
187 ThenThan::default(),
188 "better than a poke in the eye with a sharp stick",
189 );
190 }
191
192 #[test]
193 fn other_then_should_change() {
194 assert_suggestion_result(
195 "There was no one other then us at the campsite.",
196 ThenThan::default(),
197 "There was no one other than us at the campsite.",
198 );
199 }
200
201 #[test]
202 fn allows_and_then() {
203 assert_lint_count("And then we left.", ThenThan::default(), 0);
204 }
205
206 #[test]
207 fn allows_this_then() {
208 assert_lint_count("Do this then that.", ThenThan::default(), 0);
209 }
210
211 #[test]
212 fn allows_issue_720() {
213 assert_lint_count(
214 "And if just one of those is set incorrectly or it has the tiniest bit of dirt inside then that will wreak havoc with the engine's running ability.",
215 ThenThan::default(),
216 0,
217 );
218 assert_lint_count("So let's check it out then.", ThenThan::default(), 0);
219 assert_lint_count(
220 "And if just the tiniest bit of dirt gets inside then that will wreak havoc.",
221 ThenThan::default(),
222 0,
223 );
224
225 assert_lint_count(
226 "He was always a top student in school but then his argument is that grades don't define intelligence.",
227 ThenThan::default(),
228 0,
229 );
230 }
231
232 #[test]
233 fn allows_issue_744() {
234 assert_lint_count(
235 "So then after talking about how he would, he didn't.",
236 ThenThan::default(),
237 0,
238 );
239 }
240
241 #[test]
242 fn issue_720_school_but_then_his() {
243 assert_lint_count(
244 "She loved the atmosphere of the school but then his argument is that it lacks proper resources for students.",
245 ThenThan::default(),
246 0,
247 );
248 assert_lint_count(
249 "The teacher praised the efforts of the school but then his argument is that the curriculum needs to be updated.",
250 ThenThan::default(),
251 0,
252 );
253 assert_lint_count(
254 "They were excited about the new program at school but then his argument is that it won't be effective without proper training.",
255 ThenThan::default(),
256 0,
257 );
258 assert_lint_count(
259 "The community supported the school but then his argument is that funding is still a major issue.",
260 ThenThan::default(),
261 0,
262 );
263 }
264
265 #[test]
266 fn issue_720_so_then_these_resistors() {
267 assert_lint_count(
268 "So then these resistors are connected up in parallel to reduce the overall resistance.",
269 ThenThan::default(),
270 0,
271 );
272 assert_lint_count(
273 "So then these resistors are connected up to ensure the current flows properly.",
274 ThenThan::default(),
275 0,
276 );
277 assert_lint_count(
278 "So then these resistors are connected up to achieve the desired voltage drop.",
279 ThenThan::default(),
280 0,
281 );
282 assert_lint_count(
283 "So then these resistors are connected up to demonstrate the principles of series and parallel circuits.",
284 ThenThan::default(),
285 0,
286 );
287 assert_lint_count(
288 "So then these resistors are connected up to optimize the circuit's performance.",
289 ThenThan::default(),
290 0,
291 );
292 }
293
294 #[test]
295 fn issue_720_yes_so_then_sorry() {
296 assert_lint_count(
297 "Yes so then sorry you didn't receive the memo about the meeting changes.",
298 ThenThan::default(),
299 0,
300 );
301 assert_lint_count(
302 "Yes so then sorry you had to wait so long for a response from our team.",
303 ThenThan::default(),
304 0,
305 );
306 assert_lint_count(
307 "Yes so then sorry you felt left out during the discussion; we value your input.",
308 ThenThan::default(),
309 0,
310 );
311 assert_lint_count(
312 "Yes so then sorry you missed the deadline; we can discuss an extension.",
313 ThenThan::default(),
314 0,
315 );
316 assert_lint_count(
317 "Yes so then sorry you encountered issues with the software; let me help you troubleshoot.",
318 ThenThan::default(),
319 0,
320 );
321 }
322
323 #[test]
324 fn more_talented_then_her_issue_720() {
325 assert_suggestion_result(
326 "He was more talented then her at writing code.",
327 ThenThan::default(),
328 "He was more talented than her at writing code.",
329 );
330 }
331
332 #[test]
333 fn simpler_then_hers_issue_720() {
334 assert_suggestion_result(
335 "The design was simpler then hers in layout and color scheme.",
336 ThenThan::default(),
337 "The design was simpler than hers in layout and color scheme.",
338 );
339 }
340
341 #[test]
342 fn earlier_then_him_issue_720() {
343 assert_suggestion_result(
344 "We arrived earlier then him at the event.",
345 ThenThan::default(),
346 "We arrived earlier than him at the event.",
347 );
348 }
349
350 #[test]
351 fn more_robust_then_his_issue_720() {
352 assert_suggestion_result(
353 "This approach is more robust then his for handling edge cases.",
354 ThenThan::default(),
355 "This approach is more robust than his for handling edge cases.",
356 );
357 }
358
359 #[test]
360 fn patch_more_recently_then_last_week_issue_720() {
361 assert_suggestion_result(
362 "We submitted the patch more recently then last week, so they should have it already.",
363 ThenThan::default(),
364 "We submitted the patch more recently than last week, so they should have it already.",
365 );
366 }
367
368 #[test]
369 fn allows_well_then() {
370 assert_lint_count(
371 "Well then we're just going to raise all of these taxes",
372 ThenThan::default(),
373 0,
374 );
375 }
376
377 #[test]
378 fn allows_nervous_then() {
379 assert_lint_count(
380 "I think both of us were getting nervous then because the system would have automatically aborted.",
381 ThenThan::default(),
382 0,
383 );
384 }
385
386 #[test]
387 fn flags_stupider_then_and_more_and_less_stupid_then() {
388 assert_lint_count(
389 "He was stupider then her but she was more stupid then some. Then again he was less stupid then some too.",
390 ThenThan::default(),
391 3,
392 );
393 }
394
395 #[test]
396 fn patch_worse_then() {
397 assert_suggestion_result(
398 "He was worse then her at writing code.",
399 ThenThan::default(),
400 "He was worse than her at writing code.",
401 );
402 }
403}