From 97865626aa689d4be72f2813570dab54ed1276c7 Mon Sep 17 00:00:00 2001 From: Yokubjon Nurullaev Date: Wed, 8 Jul 2026 09:04:15 +0700 Subject: [PATCH 1/2] fix(that-than): catch 'less/fewer [adj] that' periphrastic comparison typos Added a second SequenceExpr branch to ThatThan that matches 'less' or 'fewer' followed by a plain (positive) adjective, then 'that', then any word except 'way'. This catches patterns like: 'way less common that mp3' -> 'way less common than mp3' 'less reliable that the previous one' -> 'less reliable than the previous one' 'fewer errors that expected' -> 'fewer errors than expected' The original linter only handled comparative adjectives (words ending -er like 'smaller', 'faster') preceding 'that'. Periphrastic comparisons using 'less'/'fewer' + plain adjective were missed. 'more [adj] that [...]' is deliberately excluded because it frequently introduces a correct subordinate clause ('more clear that users need to...'), making it too noisy to flag without deeper grammatical analysis. match_to_lint now derives the 'that' token index from toks.len() instead of hardcoding 2, supporting the 5-token (adj that word) and 7-token (less adj that word) patterns. Fixes #3736. Co-authored-by: IbrokhimN --- harper-core/src/linting/that_than.rs | 56 +++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/harper-core/src/linting/that_than.rs b/harper-core/src/linting/that_than.rs index 6cf62e93ac..1042d3ab79 100644 --- a/harper-core/src/linting/that_than.rs +++ b/harper-core/src/linting/that_than.rs @@ -21,8 +21,23 @@ impl Default for ThatThan { .t_ws() .then_word_except(&["way"]); + // "less/fewer [adj] that [...]" is almost always a comparison where + // "that" should be "than". "more [adj] that" is excluded because it + // frequently introduces a correct subordinate clause ("more clear that + // users need to..."). + let periphrastic_that_nextword = SequenceExpr::word_set(&["less", "fewer"]) + .t_ws() + .then_positive_adjective() + .t_ws() + .t_aco("that") + .t_ws() + .then_word_except(&["way"]); + Self { - expr: adjective_er_that_nextword, + expr: SequenceExpr::any_of(vec![ + Box::new(adjective_er_that_nextword), + Box::new(periphrastic_that_nextword), + ]), } } } @@ -35,11 +50,13 @@ impl ExprLinter for ThatThan { } fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option { - if toks.len() != 5 { - return None; - } + let that_idx = match toks.len() { + 5 => 2, + 7 => 4, + _ => return None, + }; - let that_tok = &toks[2]; + let that_tok = &toks[that_idx]; Some(Lint { span: that_tok.span, @@ -187,6 +204,35 @@ mod tests { ) } + // less/fewer [adj] that + + #[test] + fn fix_less_common_that() { + assert_suggestion_result( + "way less common that mp3", + ThatThan::default(), + "way less common than mp3", + ); + } + + #[test] + fn fix_less_reliable_that() { + assert_suggestion_result( + "this approach is less reliable that the previous one", + ThatThan::default(), + "this approach is less reliable than the previous one", + ); + } + + #[test] + fn fix_fewer_errors_that() { + assert_suggestion_result( + "produces fewer errors that expected", + ThatThan::default(), + "produces fewer errors than expected", + ); + } + // more/less adj that #[test] From fb3dfdbeb5d816fd3dec44b83065012b312d9e41 Mon Sep 17 00:00:00 2001 From: Yokubjon Date: Sat, 11 Jul 2026 16:31:43 +0700 Subject: [PATCH 2/2] fix(that-than): remove fewer from periphrastic pattern, drop bogus test --- harper-core/src/linting/that_than.rs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/harper-core/src/linting/that_than.rs b/harper-core/src/linting/that_than.rs index 1042d3ab79..bb048b8be0 100644 --- a/harper-core/src/linting/that_than.rs +++ b/harper-core/src/linting/that_than.rs @@ -21,11 +21,11 @@ impl Default for ThatThan { .t_ws() .then_word_except(&["way"]); - // "less/fewer [adj] that [...]" is almost always a comparison where - // "that" should be "than". "more [adj] that" is excluded because it - // frequently introduces a correct subordinate clause ("more clear that - // users need to..."). - let periphrastic_that_nextword = SequenceExpr::word_set(&["less", "fewer"]) + // "less [adj] that [...]" is almost always a comparison where "that" + // should be "than". "more [adj] that" is excluded because it frequently + // introduces a correct subordinate clause ("more clear that users + // need to..."). + let periphrastic_that_nextword = SequenceExpr::word("less") .t_ws() .then_positive_adjective() .t_ws() @@ -204,7 +204,7 @@ mod tests { ) } - // less/fewer [adj] that + // less [adj] that #[test] fn fix_less_common_that() { @@ -224,15 +224,6 @@ mod tests { ); } - #[test] - fn fix_fewer_errors_that() { - assert_suggestion_result( - "produces fewer errors that expected", - ThatThan::default(), - "produces fewer errors than expected", - ); - } - // more/less adj that #[test] @@ -267,7 +258,7 @@ mod tests { #[test] fn dont_flag_its_better_that() { assert_lint_count( - "It’s better that the shock should all come at once.", + "It's better that the shock should all come at once.", ThatThan::default(), 0, )