harper_core/linting/
then_than.rs

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                    // Comparative form of adjective
19                    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                    // Positive form of adjective following "more" or "less"
32                    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                // Exceptions to the rule.
44                Box::new(Invert::new(WordSet::new(&["back", "this", "so", "but"]))),
45            ])),
46        }
47    }
48}
49
50// TODO: This can be simplified or eliminated when the adjective improvements make it into the affix system.
51fn 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            // Regular comparative form?
57            src.ends_with(&['e', 'r'])
58                // Irregular comparatives.
59                || 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        // For both "stupider then X" and "more stupid then X", "then" is 3rd last token.
77        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}