From 2c4413a8a8d0aca9e57f6d9b21063759f1ac111a Mon Sep 17 00:00:00 2001 From: Yashas Gunderia <68075205+ychampion@users.noreply.github.com> Date: Thu, 16 Jul 2026 16:34:50 +0530 Subject: [PATCH] fix(hook): preserve explicit linter rewrites Constraint: explicit package-manager invocations must retain the requested linter identity. Rejected: tighten generic linter detection | would change direct rtk lint extensibility beyond this bug. Confidence: high Scope-risk: narrow Directive: keep generic lint-script rewrites on the existing auto-detection path. Tested: cargo fmt --all --check; cargo clippy --all-targets --locked -- -D warnings; cargo test --locked; manual Biome, ESLint, and generic lint hook checks. Signed-off-by: Yashas Gunderia <68075205+ychampion@users.noreply.github.com> --- src/discover/registry.rs | 49 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/discover/registry.rs b/src/discover/registry.rs index 6a7e9e89ff..c8e9b3110a 100644 --- a/src/discover/registry.rs +++ b/src/discover/registry.rs @@ -960,10 +960,16 @@ fn rewrite_segment_inner( // Try each rewrite prefix (longest first) with word-boundary check for &prefix in rule.rewrite_prefixes { if let Some(rest) = strip_word_prefix(strip_target, prefix) { - let rewritten = if rest.is_empty() { - format!("{}{}", rule.rtk_cmd, redirect_suffix) - } else { - format!("{} {}{}", rule.rtk_cmd, rest, redirect_suffix) + let explicit_linter = explicit_linter_from_prefix(rule.rtk_cmd, prefix); + let rewritten = match (explicit_linter, rest.is_empty()) { + (Some(linter), true) => { + format!("{} {}{}", rule.rtk_cmd, linter, redirect_suffix) + } + (Some(linter), false) => { + format!("{} {} {}{}", rule.rtk_cmd, linter, rest, redirect_suffix) + } + (None, true) => format!("{}{}", rule.rtk_cmd, redirect_suffix), + (None, false) => format!("{} {}{}", rule.rtk_cmd, rest, redirect_suffix), }; return Some(rewritten); } @@ -972,6 +978,17 @@ fn rewrite_segment_inner( None } +fn explicit_linter_from_prefix<'a>(rtk_cmd: &str, prefix: &'a str) -> Option<&'a str> { + if rtk_cmd != "rtk lint" { + return None; + } + + match prefix.split_whitespace().last() { + Some(linter @ ("biome" | "eslint")) => Some(linter), + _ => None, + } +} + /// Strip a command prefix with word-boundary check. /// Returns the remainder of the command after the prefix, or `None` if no match. fn strip_word_prefix<'a>(cmd: &'a str, prefix: &str) -> Option<&'a str> { @@ -2966,15 +2983,37 @@ mod tests { "lint", ]; for command in commands { + let linter = command.split_whitespace().last().unwrap(); + let expected = match linter { + "biome" | "eslint" => format!("rtk lint {linter}"), + "lint" => "rtk lint".to_string(), + _ => unreachable!(), + }; assert_eq!( rewrite_command_no_prefixes(command, &[]), - Some("rtk lint".into()), + Some(expected), "Failed for command: {}", command ); } } + #[test] + fn test_rewrite_lint_preserves_explicit_linter_arguments() { + assert_eq!( + rewrite_command_no_prefixes("pnpm exec biome ci .", &[]), + Some("rtk lint biome ci .".into()) + ); + assert_eq!( + rewrite_command_no_prefixes("pnpm exec eslint --version", &[]), + Some("rtk lint eslint --version".into()) + ); + assert_eq!( + rewrite_command_no_prefixes("RAYON_NUM_THREADS=2 pnpm exec biome ci .", &[]), + Some("RAYON_NUM_THREADS=2 rtk lint biome ci .".into()) + ); + } + #[test] fn test_classify_jest() { let commands = vec![