From 409a9fbeaafec3479274ceb6fa645eba4e47f44e Mon Sep 17 00:00:00 2001 From: hippietrail Date: Fri, 29 May 2026 17:58:26 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20curious/unsure=20to=20why=20etc?= =?UTF-8?q?=E2=86=92as=20to=20why=20etc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- harper-core/default_config.json | 7 + .../src/linting/as_to_interrogative.rs | 185 ++++++++++++++++++ harper-core/src/linting/lint_group/mod.rs | 2 + harper-core/src/linting/mod.rs | 1 + 4 files changed, 195 insertions(+) create mode 100644 harper-core/src/linting/as_to_interrogative.rs diff --git a/harper-core/default_config.json b/harper-core/default_config.json index 00143ccb3..b3e89840a 100644 --- a/harper-core/default_config.json +++ b/harper-core/default_config.json @@ -3720,6 +3720,13 @@ "state": true, "label": "Naked Eye" } + }, + { + "Bool": { + "name": "AsToInterrogative", + "state": true, + "label": "As To Interrogative" + } } ] } diff --git a/harper-core/src/linting/as_to_interrogative.rs b/harper-core/src/linting/as_to_interrogative.rs new file mode 100644 index 000000000..db3c833f3 --- /dev/null +++ b/harper-core/src/linting/as_to_interrogative.rs @@ -0,0 +1,185 @@ +use crate::{ + Lint, Token, + expr::{All, Expr, OwnedExprExt, SequenceExpr}, + linting::{ExprLinter, LintKind, Suggestion, expr_linter::Chunk}, +}; + +pub struct AsToInterrogative { + expr: All, +} + +impl Default for AsToInterrogative { + fn default() -> Self { + Self { + expr: SequenceExpr::word_set(&["curious", "unsure"]) + .t_ws() + .t_aco("to") + .t_ws() + .t_set(&[ + "how", "what", "what's", "which", "when", "when's", "where", "whether", "who", + "who's", "whose", "whom", "why", + ]) + .but_not( + SequenceExpr::anything() + .t_any() + .t_any() + .t_any() + .t_any() + .t_ws() + // "Extend" would be a mistake here, but a common and unrelated mistake. + .t_set(&["degree", "extent", "extend"]), + ), + } + } +} + +impl ExprLinter for AsToInterrogative { + type Unit = Chunk; + + fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option { + if matched_tokens.len() < 5 { + return None; + } + + Some(Lint { + span: matched_tokens[2].span, + lint_kind: LintKind::Usage, + suggestions: vec![Suggestion::replace_with_match_case_str( + "as to", + matched_tokens[2].get_ch(source), + )], + message: "This construction requires `as to` instead of just `to`.".to_string(), + ..Default::default() + }) + } + + fn expr(&self) -> &dyn Expr { + &self.expr + } + + fn description(&self) -> &str { + "Corrects `to` to `as to` between certain adjectives and wh-words." + } +} + +#[cfg(test)] +mod tests { + use crate::linting::tests::{assert_no_lints, assert_suggestion_result}; + + use super::AsToInterrogative; + + #[test] + fn fix_curious_how() { + assert_suggestion_result( + "I would be curious to how you dealt with the issues I mentioned above", + AsToInterrogative::default(), + "I would be curious as to how you dealt with the issues I mentioned above", + ); + } + + #[test] + fn fix_curious_what() { + assert_suggestion_result( + "I'm curious to what @Katzmann1983 thinks on this topic.", + AsToInterrogative::default(), + "I'm curious as to what @Katzmann1983 thinks on this topic.", + ); + } + + #[test] + fn dont_flag_curious_what_extent() { + assert_no_lints( + "I really just want to ask these questions / am curious to what extent anything like this is in place or will be put in place.", + AsToInterrogative::default(), + ); + } + + #[test] + fn fix_curious_when() { + assert_suggestion_result( + "So I am curious to when you should return an array to destructure", + AsToInterrogative::default(), + "So I am curious as to when you should return an array to destructure", + ); + } + + #[test] + fn fix_curious_where() { + assert_suggestion_result( + "Curious to where you ended up.", + AsToInterrogative::default(), + "Curious as to where you ended up.", + ); + } + + #[test] + fn fix_curious_whether() { + assert_suggestion_result( + "I have never used them so was curious to whether I could improve my workflow even more", + AsToInterrogative::default(), + "I have never used them so was curious as to whether I could improve my workflow even more", + ); + } + + #[test] + fn fix_curious_which() { + assert_suggestion_result( + "I'm curious to which country you're assigning these noble ideals.", + AsToInterrogative::default(), + "I'm curious as to which country you're assigning these noble ideals.", + ); + } + + #[test] + fn fix_curious_who() { + assert_suggestion_result( + "New to climbing, curious to who create the problems/leads in the Olympics and how/by who are they tested?", + AsToInterrogative::default(), + "New to climbing, curious as to who create the problems/leads in the Olympics and how/by who are they tested?", + ); + } + + #[test] + fn fix_curious_why() { + assert_suggestion_result( + "I am curious to why the below weird_query does not yield a query with the parent relationship loaded", + AsToInterrogative::default(), + "I am curious as to why the below weird_query does not yield a query with the parent relationship loaded", + ); + } + + #[test] + fn fix_unsure_what() { + assert_suggestion_result( + "If you are unsure to what BurpSuite is, or how to set it up please complete our BurpSuite room first.", + AsToInterrogative::default(), + "If you are unsure as to what BurpSuite is, or how to set it up please complete our BurpSuite room first.", + ); + } + + #[test] + fn dont_flag_unsure_what_degree() { + assert_no_lints( + "and I'm unsure to what degree these have already been done", + AsToInterrogative::default(), + ); + } + + #[test] + #[ignore = "This edge case with 'go' is tricky to handle"] + fn dont_flag_unsure_which_go() { + assert_no_lints( + "Two classical music performances on the same day, unsure to which I should go.", + AsToInterrogative::default(), + ); + } + + #[test] + fn fix_unsure_why() { + assert_suggestion_result( + "I am unsure to why this code is wrong.", + AsToInterrogative::default(), + "I am unsure as to why this code is wrong.", + ); + } +} diff --git a/harper-core/src/linting/lint_group/mod.rs b/harper-core/src/linting/lint_group/mod.rs index 3fa746c2f..aa7c2e13c 100644 --- a/harper-core/src/linting/lint_group/mod.rs +++ b/harper-core/src/linting/lint_group/mod.rs @@ -28,6 +28,7 @@ use super::another_thing_coming::AnotherThingComing; use super::another_think_coming::AnotherThinkComing; use super::apart_from::ApartFrom; use super::arrive_to::ArriveTo; +use super::as_to_interrogative::AsToInterrogative; use super::ask_no_preposition::AskNoPreposition; use super::aspire_to::AspireTo; use super::avoid_contractions::AvoidContractions; @@ -585,6 +586,7 @@ impl LintGroup { insert_expr_rule!(AnotherThinkComing, false); insert_expr_rule!(ApartFrom, true); insert_expr_rule!(ArriveTo, true); + insert_expr_rule!(AsToInterrogative, true); insert_expr_rule!(AskNoPreposition, true); insert_expr_rule!(AvoidContractions, false); insert_expr_rule!(AvoidCurses, true); diff --git a/harper-core/src/linting/mod.rs b/harper-core/src/linting/mod.rs index f04b37bf3..abe8f8ed8 100644 --- a/harper-core/src/linting/mod.rs +++ b/harper-core/src/linting/mod.rs @@ -20,6 +20,7 @@ mod another_thing_coming; mod another_think_coming; mod apart_from; mod arrive_to; +mod as_to_interrogative; mod ask_no_preposition; mod aspire_to; mod avoid_contractions; From b9c7c30a475aa7af775e9aa6072cadc088a834d6 Mon Sep 17 00:00:00 2001 From: hippietrail Date: Fri, 29 May 2026 18:05:20 +0800 Subject: [PATCH 2/2] fix: backticke/code quote around `wh-word` to avoid description lint --- harper-core/src/linting/as_to_interrogative.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harper-core/src/linting/as_to_interrogative.rs b/harper-core/src/linting/as_to_interrogative.rs index db3c833f3..148b08960 100644 --- a/harper-core/src/linting/as_to_interrogative.rs +++ b/harper-core/src/linting/as_to_interrogative.rs @@ -58,7 +58,7 @@ impl ExprLinter for AsToInterrogative { } fn description(&self) -> &str { - "Corrects `to` to `as to` between certain adjectives and wh-words." + "Corrects `to` to `as to` between certain adjectives and `wh-words`." } }