From 34d9cfdf15b9c47094150cdebc1239cee68001a2 Mon Sep 17 00:00:00 2001 From: hippietrail Date: Sat, 30 May 2026 12:19:37 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20cache/cash=2022=E2=86=92catch=2022?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- harper-core/default_config.json | 6 + harper-core/src/linting/catch_22.rs | 129 ++++++++++++++++++++++ harper-core/src/linting/lint_group/mod.rs | 2 + harper-core/src/linting/mod.rs | 1 + 4 files changed, 138 insertions(+) create mode 100644 harper-core/src/linting/catch_22.rs diff --git a/harper-core/default_config.json b/harper-core/default_config.json index 00143ccb3..5c98c5e7a 100644 --- a/harper-core/default_config.json +++ b/harper-core/default_config.json @@ -4983,6 +4983,12 @@ "state": true, "label": "Every Single One Of" } + },{ + "Bool": { + "name": "Catch22", + "state": true, + "label": "Catch-22" + } } ] } diff --git a/harper-core/src/linting/catch_22.rs b/harper-core/src/linting/catch_22.rs new file mode 100644 index 000000000..af963d25a --- /dev/null +++ b/harper-core/src/linting/catch_22.rs @@ -0,0 +1,129 @@ +use crate::{ + Lint, Token, TokenStringExt, + char_string::CharStringExt, + expr::{Expr, SequenceExpr}, + linting::{ExprLinter, LintKind, Suggestion, debug::format_lint_match, expr_linter::Chunk}, +}; + +pub struct Catch22 { + expr: SequenceExpr, +} + +impl Default for Catch22 { + fn default() -> Self { + Self { + expr: SequenceExpr::word_set(&["cache", "cash"]).then_any_of(vec![ + Box::new( + SequenceExpr::default() + .t_ws_h() + .then(|t: &Token, s: &[char]| { + t.kind.is_number() && t.get_ch(s).eq_ch(&['2', '2']) + }), + ), + Box::new( + SequenceExpr::default() + .t_ws() + .t_aco("twenty") + .t_ws_h() + .t_aco("two"), + ), + ]), + } + } +} + +impl ExprLinter for Catch22 { + type Unit = Chunk; + + fn match_to_lint_with_context( + &self, + matched_tokens: &[Token], + source: &[char], + context: Option<(&[Token], &[Token])>, + ) -> Option { + eprintln!("🚨 {}", format_lint_match(matched_tokens, context, source)); + let span = matched_tokens[0].span; + let lint_kind = LintKind::Malapropism; + let suggestions = vec![Suggestion::replace_with_match_case_str( + "catch", + span.get_content(source), + )]; + let message = "This idiom uses 'catch' instead of 'cache' or 'cash'.".to_string(); + Some(Lint { + span, + lint_kind, + suggestions, + message, + ..Default::default() + }) + } + + fn expr(&self) -> &dyn Expr { + &self.expr + } + + fn description(&self) -> &str { + "Corrects mistakenly using similar-sounding words in the idiom `catch 22`." + } +} + +#[cfg(test)] +mod tests { + use crate::linting::tests::assert_suggestion_result; + + use super::Catch22; + + #[test] + fn fix_cache_22_space() { + assert_suggestion_result( + "So, i'm in a bit of a cache 22 situation.", + Catch22::default(), + "So, i'm in a bit of a catch 22 situation.", + ); + } + + #[test] + fn fix_cache_22_hyphen() { + assert_suggestion_result( + "This leads to a cache-22 situation.", + Catch22::default(), + "This leads to a catch-22 situation.", + ); + } + + #[test] + fn fix_cash_22() { + assert_suggestion_result( + "It's literally a cash 22 it's selectivity but also specific criteria", + Catch22::default(), + "It's literally a catch 22 it's selectivity but also specific criteria", + ); + } + + #[test] + fn fix_cash_twenty_two_space() { + assert_suggestion_result( + "It’s a bit of a cash twenty two situation. You see?", + Catch22::default(), + "It’s a bit of a catch twenty two situation. You see?", + ); + } + + #[test] + fn fix_cash_twenty_two_hyphen() { + assert_suggestion_result( + "So, they're they're in a cash twenty-two but in the middle of all of that, we gotta deal with the presenting problem.", + Catch22::default(), + "So, they're they're in a catch twenty-two but in the middle of all of that, we gotta deal with the presenting problem.", + ); + } + + #[test] + fn fix_cache_twenty_two_hyphen() { + assert_suggestion_result( + "The club is in such a cache twenty-two situation given it past experiences with previous managers.", + Catch22::default(), + "The club is in such a catch twenty-two situation given it past experiences with previous managers.", + ); + } +} diff --git a/harper-core/src/linting/lint_group/mod.rs b/harper-core/src/linting/lint_group/mod.rs index 3fa746c2f..1a8d8ee63 100644 --- a/harper-core/src/linting/lint_group/mod.rs +++ b/harper-core/src/linting/lint_group/mod.rs @@ -44,6 +44,7 @@ use super::by_the_book::ByTheBook; use super::call_them::CallThem; use super::cant::Cant; use super::capitalize_personal_pronouns::CapitalizePersonalPronouns; +use super::catch_22::Catch22; use super::cautionary_tale::CautionaryTale; use super::change_tack::ChangeTack; use super::chock_full::ChockFull; @@ -600,6 +601,7 @@ impl LintGroup { insert_expr_rule!(CallThem, true); insert_expr_rule!(Cant, true); insert_struct_rule!(CapitalizePersonalPronouns, true); + insert_expr_rule!(Catch22, true); insert_expr_rule!(CautionaryTale, true); insert_expr_rule!(ChangeTack, true); insert_expr_rule!(ChockFull, true); diff --git a/harper-core/src/linting/mod.rs b/harper-core/src/linting/mod.rs index f04b37bf3..788b329ad 100644 --- a/harper-core/src/linting/mod.rs +++ b/harper-core/src/linting/mod.rs @@ -37,6 +37,7 @@ mod by_the_book; mod call_them; mod cant; mod capitalize_personal_pronouns; +mod catch_22; mod cautionary_tale; mod change_tack; mod chock_full; From 8a813a3f86927b8f885e6499ad5a16fd61fa5555 Mon Sep 17 00:00:00 2001 From: hippietrail Date: Sat, 30 May 2026 12:27:51 +0800 Subject: [PATCH 2/3] fix: cleanup debug stuff --- harper-core/src/linting/catch_22.rs | 30 ++++++++++------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/harper-core/src/linting/catch_22.rs b/harper-core/src/linting/catch_22.rs index af963d25a..b535fe137 100644 --- a/harper-core/src/linting/catch_22.rs +++ b/harper-core/src/linting/catch_22.rs @@ -1,8 +1,8 @@ use crate::{ - Lint, Token, TokenStringExt, + Lint, Token, char_string::CharStringExt, expr::{Expr, SequenceExpr}, - linting::{ExprLinter, LintKind, Suggestion, debug::format_lint_match, expr_linter::Chunk}, + linting::{ExprLinter, LintKind, Suggestion, expr_linter::Chunk}, }; pub struct Catch22 { @@ -35,25 +35,15 @@ impl Default for Catch22 { impl ExprLinter for Catch22 { type Unit = Chunk; - fn match_to_lint_with_context( - &self, - matched_tokens: &[Token], - source: &[char], - context: Option<(&[Token], &[Token])>, - ) -> Option { - eprintln!("🚨 {}", format_lint_match(matched_tokens, context, source)); - let span = matched_tokens[0].span; - let lint_kind = LintKind::Malapropism; - let suggestions = vec![Suggestion::replace_with_match_case_str( - "catch", - span.get_content(source), - )]; - let message = "This idiom uses 'catch' instead of 'cache' or 'cash'.".to_string(); + fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option { Some(Lint { - span, - lint_kind, - suggestions, - message, + span: toks[0].span, + lint_kind: LintKind::Malapropism, + suggestions: vec![Suggestion::replace_with_match_case_str( + "catch", + toks[0].span.get_content(src), + )], + message: "This idiom uses 'catch' instead of 'cache' or 'cash'.".to_string(), ..Default::default() }) } From 758714a9fd134916b32635da4e89348259d24021 Mon Sep 17 00:00:00 2001 From: hippietrail Date: Sat, 30 May 2026 15:05:56 +0800 Subject: [PATCH 3/3] fix: `just format` --- harper-core/default_config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/harper-core/default_config.json b/harper-core/default_config.json index 5c98c5e7a..e03932eca 100644 --- a/harper-core/default_config.json +++ b/harper-core/default_config.json @@ -4983,7 +4983,8 @@ "state": true, "label": "Every Single One Of" } - },{ + }, + { "Bool": { "name": "Catch22", "state": true,