From ad63139576f77767299ac1c0f4bca5b52fe80442 Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Mon, 14 Sep 2026 01:26:21 +0200 Subject: [PATCH 1/6] chore: reformat under rustfmt style edition 2024 Pin style_edition = "2024" in rustfmt.toml and reformat, ahead of the edition 2024 bump. Setting style_edition explicitly decouples the reformat from the crate edition, so this lands on edition 2021 and CI's `cargo fmt --all -- --check` still passes. Run through rustfmt directly rather than `cargo fmt`. Every module under src/cmds/ is currently declared by automod::dir!, and rustfmt only follows literal `mod` items, so `cargo fmt` cannot see those files -- the next commit makes them reachable. Roughly two thirds of the diff is therefore in src/cmds/, mostly the version-sorted imports style edition 2024 introduces. No logic changes. Co-Authored-By: Claude Opus 5 --- rustfmt.toml | 1 + src/analytics/cc_economics.rs | 4 +- src/analytics/gain.rs | 26 ++- src/analytics/session_cmd.rs | 2 +- src/cmds/cloud/aws_cmd.rs | 119 ++++++---- src/cmds/cloud/container.rs | 91 ++++++-- src/cmds/cloud/wget_cmd.rs | 22 +- src/cmds/dotnet/binlog.rs | 26 ++- src/cmds/dotnet/dotnet_cmd.rs | 114 +++++---- src/cmds/dotnet/dotnet_trx.rs | 2 +- src/cmds/git/diff_cmd.rs | 397 +++++++++++++++++++++----------- src/cmds/git/gh_cmd.rs | 17 +- src/cmds/git/git.rs | 291 ++++++++++++++--------- src/cmds/git/glab_cmd.rs | 8 +- src/cmds/git/gt_cmd.rs | 2 +- src/cmds/go/go_cmd.rs | 24 +- src/cmds/go/golangci_cmd.rs | 38 ++- src/cmds/js/bun_cmd.rs | 11 +- src/cmds/js/deno_cmd.rs | 8 +- src/cmds/js/lint_cmd.rs | 26 ++- src/cmds/js/npm_cmd.rs | 10 +- src/cmds/js/playwright_cmd.rs | 4 +- src/cmds/js/pnpm_cmd.rs | 50 +++- src/cmds/js/tsc_cmd.rs | 79 +++++-- src/cmds/js/vitest_cmd.rs | 17 +- src/cmds/jvm/gradlew_cmd.rs | 103 +++++++-- src/cmds/jvm/mvn_cmd.rs | 194 ++++++++++------ src/cmds/php/artisan_cmd.rs | 5 +- src/cmds/php/php_cmd.rs | 2 +- src/cmds/php/phpstan_cmd.rs | 21 +- src/cmds/php/phpt_cmd.rs | 6 +- src/cmds/php/phpunit_cmd.rs | 10 +- src/cmds/php/pint_cmd.rs | 5 +- src/cmds/python/pip_cmd.rs | 4 +- src/cmds/python/pytest_cmd.rs | 18 +- src/cmds/python/sqlfluff_cmd.rs | 82 +++---- src/cmds/python/uv_cmd.rs | 11 +- src/cmds/ruby/rspec_cmd.rs | 2 +- src/cmds/rust/cargo_cmd.rs | 86 +++++-- src/cmds/scala/sbt_cmd.rs | 45 ++-- src/cmds/system/ast_grep_cmd.rs | 9 +- src/cmds/system/ctest_cmd.rs | 75 +++--- src/cmds/system/deps.rs | 32 +-- src/cmds/system/find_cmd.rs | 4 +- src/cmds/system/json_cmd.rs | 14 +- src/cmds/system/log_cmd.rs | 12 +- src/cmds/system/ls.rs | 27 ++- src/cmds/system/pipe_cmd.rs | 26 ++- src/cmds/system/read.rs | 58 +++-- src/cmds/system/search.rs | 41 ++-- src/cmds/system/summary.rs | 5 +- src/core/retriever.rs | 4 +- src/core/tee.rs | 2 +- src/core/telemetry.rs | 14 +- src/core/toml_filter.rs | 8 +- src/core/tracking.rs | 32 +-- src/core/truncate.rs | 6 +- src/core/utils.rs | 6 +- src/discover/lexer.rs | 242 +++++++++++-------- src/discover/mod.rs | 12 +- src/discover/registry.rs | 30 +-- src/discover/report.rs | 4 +- src/hooks/decision.rs | 2 +- src/hooks/hook_check.rs | 8 +- src/hooks/hook_cmd.rs | 70 +++--- src/hooks/init.rs | 129 ++++++----- src/hooks/rewrite_cmd.rs | 2 +- src/learn/mod.rs | 2 +- src/main.rs | 11 +- 69 files changed, 1836 insertions(+), 1034 deletions(-) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000000..3501136812 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +style_edition = "2024" diff --git a/src/analytics/cc_economics.rs b/src/analytics/cc_economics.rs index 0375931027..5e89afcdaf 100644 --- a/src/analytics/cc_economics.rs +++ b/src/analytics/cc_economics.rs @@ -727,7 +727,9 @@ fn export_csv( all: bool, ) -> Result<()> { // Header (new columns: input_tokens, output_tokens, cache_create, cache_read, weighted_savings) - println!("period,spent,input_tokens,output_tokens,cache_create,cache_read,active_tokens,total_tokens,saved_tokens,weighted_savings,active_savings,blended_savings,rtk_commands"); + println!( + "period,spent,input_tokens,output_tokens,cache_create,cache_read,active_tokens,total_tokens,saved_tokens,weighted_savings,active_savings,blended_savings,rtk_commands" + ); if all || daily { let cc = ccusage::fetch(Granularity::Daily) diff --git a/src/analytics/gain.rs b/src/analytics/gain.rs index 11f215d980..fd13426283 100644 --- a/src/analytics/gain.rs +++ b/src/analytics/gain.rs @@ -220,9 +220,17 @@ pub fn run( println!("{}", "─".repeat(table_width)); println!( "{:>3} {:count_width$} {:>saved_width$} {:>6} {:>time_width$} {: Result<()> { fn confirm_reset() -> Result { use std::io::{self, BufRead, IsTerminal, Write}; - eprint!("This will permanently delete all tracking data and recall counters (stored outputs are kept). Continue? [y/N] "); + eprint!( + "This will permanently delete all tracking data and recall counters (stored outputs are kept). Continue? [y/N] " + ); io::stderr().flush().ok(); if !io::stdin().is_terminal() { diff --git a/src/analytics/session_cmd.rs b/src/analytics/session_cmd.rs index 96d19f2d2d..e0a7f33969 100644 --- a/src/analytics/session_cmd.rs +++ b/src/analytics/session_cmd.rs @@ -3,7 +3,7 @@ use crate::core::utils::format_tokens; use crate::discover::is_already_rtk; use crate::discover::provider::{ClaudeProvider, ExtractedCommand, SessionProvider}; -use crate::discover::registry::{classify_command, split_command_chain, Classification}; +use crate::discover::registry::{Classification, classify_command, split_command_chain}; use anyhow::{Context, Result}; use std::fs; use std::path::PathBuf; diff --git a/src/cmds/cloud/aws_cmd.rs b/src/cmds/cloud/aws_cmd.rs index c1250ad3b7..7ce9ffd448 100644 --- a/src/cmds/cloud/aws_cmd.rs +++ b/src/cmds/cloud/aws_cmd.rs @@ -4,13 +4,12 @@ //! Specialized filters for high-frequency commands (STS, S3, EC2, ECS, RDS, CloudFormation). use crate::core::guard::never_worse; -use crate::core::stream::{exec_capture, CaptureResult}; +use crate::core::stream::{CaptureResult, exec_capture}; use crate::core::tee::force_tee_hint; use crate::core::tracking; use crate::core::truncate::{CAP_INVENTORY, CAP_LIST}; use crate::core::utils::{ - human_bytes, join_with_overflow, - resolved_command, shorten_arn, truncate_iso_date, + human_bytes, join_with_overflow, resolved_command, shorten_arn, truncate_iso_date, }; use crate::json_cmd; use anyhow::{Context, Result}; @@ -1555,9 +1554,11 @@ mod tests { let result = filter_ec2_instances(json).unwrap(); assert!(result.text.contains("EC2: 2 instances")); assert!(result.text.contains("i-0a1b2c3d4e5f00001 running t3.micro 10.0.1.10 pub:54.1.2.3 vpc:vpc-123 subnet:subnet-a sg:[sg-001] (web-server-1)")); - assert!(result - .text - .contains("i-0a1b2c3d4e5f00002 stopped t3.large 10.0.2.20")); + assert!( + result + .text + .contains("i-0a1b2c3d4e5f00002 stopped t3.large 10.0.2.20") + ); assert!(!result.truncated); } @@ -1706,7 +1707,10 @@ mod tests { }] }"#; let result = filter_rds_instances(json).unwrap(); - assert_eq!(result.text, "mydb postgres 15.4 db.t3.micro available mydb.cluster-abc.us-east-1.rds.amazonaws.com:5432"); + assert_eq!( + result.text, + "mydb postgres 15.4 db.t3.micro available mydb.cluster-abc.us-east-1.rds.amazonaws.com:5432" + ); } #[test] @@ -1725,9 +1729,11 @@ mod tests { }"#; let result = filter_cfn_list_stacks(json).unwrap(); assert!(result.text.contains("my-stack CREATE_COMPLETE 2024-01-15")); - assert!(result - .text - .contains("other-stack UPDATE_COMPLETE 2024-02-20")); + assert!( + result + .text + .contains("other-stack UPDATE_COMPLETE 2024-02-20") + ); } #[test] @@ -1997,9 +2003,11 @@ mod tests { }"#; let result = filter_lambda_list(json).unwrap(); assert!(result.text.contains("my-api python3.12 512MB 30s Active")); - assert!(result - .text - .contains("my-worker nodejs20.x 256MB 60s Active")); + assert!( + result + .text + .contains("my-worker nodejs20.x 256MB 60s Active") + ); // SECURITY: secrets must NOT appear assert!(!result.text.contains("SECRET_KEY")); assert!(!result.text.contains("s3cr3t")); @@ -2047,9 +2055,11 @@ mod tests { "Tags": {"Team": "backend"} }"#; let result = filter_lambda_get(json).unwrap(); - assert!(result - .text - .contains("my-api python3.12 app.handler 512MB 30s Active 2024-01-15")); + assert!( + result + .text + .contains("my-api python3.12 app.handler 512MB 30s Active 2024-01-15") + ); assert!(result.text.contains("layers: my-layer:5, common-utils:3")); // SECURITY assert!(!result.text.contains("SECRET")); @@ -2098,12 +2108,16 @@ mod tests { ] }"#; let result = filter_iam_roles(json).unwrap(); - assert!(result - .text - .contains("admin-role 2024-01-15 [Admin access] assume:[lambda.amazonaws.com]")); - assert!(result - .text - .contains("lambda-exec 2024-02-20 assume:[lambda.amazonaws.com]")); + assert!( + result + .text + .contains("admin-role 2024-01-15 [Admin access] assume:[lambda.amazonaws.com]") + ); + assert!( + result + .text + .contains("lambda-exec 2024-02-20 assume:[lambda.amazonaws.com]") + ); // Full policy JSON should NOT appear, only extracted principals assert!(!result.text.contains("Statement")); assert!(!result.text.contains("Version")); @@ -2225,15 +2239,21 @@ mod tests { ] }"#; let result = filter_ecs_tasks(json).unwrap(); - assert!(result - .text - .contains("abc123def456 RUNNING containers:[web:RUNNING, sidecar:RUNNING]")); - assert!(result - .text - .contains("def789ghi012 STOPPED containers:[worker:STOPPED(exit:1)]")); - assert!(result - .text - .contains("reason:Essential container in task exited")); + assert!( + result + .text + .contains("abc123def456 RUNNING containers:[web:RUNNING, sidecar:RUNNING]") + ); + assert!( + result + .text + .contains("def789ghi012 STOPPED containers:[worker:STOPPED(exit:1)]") + ); + assert!( + result + .text + .contains("reason:Essential container in task exited") + ); // Attachments and overrides should NOT appear assert!(!result.text.contains("ElasticNetworkInterface")); assert!(!result.text.contains("containerOverrides")); @@ -2332,9 +2352,11 @@ mod tests { } }"#; let result = filter_eks_cluster(json).unwrap(); - assert!(result - .text - .contains("my-cluster ACTIVE k8s/1.28 https://ABC123.gr7.us-east-1.eks.amazonaws.com")); + assert!( + result.text.contains( + "my-cluster ACTIVE k8s/1.28 https://ABC123.gr7.us-east-1.eks.amazonaws.com" + ) + ); // certificateAuthority should NOT appear assert!(!result.text.contains("LS0tLS1CRUdJTi")); assert!(!result.text.contains("VERY_LONG")); @@ -2491,9 +2513,11 @@ upload: file10.txt to s3://bucket/file10.txt }"#; let result = filter_secrets_get(json).unwrap(); assert!(result.text.contains("Name: my-secret")); - assert!(result - .text - .contains(r#"{"username":"admin","password":"secret123"}"#)); + assert!( + result + .text + .contains(r#"{"username":"admin","password":"secret123"}"#) + ); assert!(!result.text.contains("ARN")); assert!(!result.text.contains("VersionId")); } @@ -2631,13 +2655,17 @@ upload: file10.txt to s3://bucket/file10.txt {"Timestamp": "2024-01-15T10:29:00Z", "LogicalResourceId": "VPC", "ResourceType": "AWS::EC2::VPC", "ResourceStatus": "CREATE_COMPLETE"} ]}"#; let result = filter_cfn_events(json).unwrap(); - assert!(result - .text - .starts_with("CloudFormation: 2 events (1 failed, 1 successful)")); + assert!( + result + .text + .starts_with("CloudFormation: 2 events (1 failed, 1 successful)") + ); assert!(result.text.contains("--- FAILURES ---")); - assert!(result - .text - .contains("Bucket S3::Bucket CREATE_FAILED REASON: Already exists")); + assert!( + result + .text + .contains("Bucket S3::Bucket CREATE_FAILED REASON: Already exists") + ); } // === Empty collection edge cases === @@ -2743,9 +2771,8 @@ upload: file10.txt to s3://bucket/file10.txt // type names. Calls the primitive used at aws_cmd.rs run_generic line 259. #[test] fn test_aws_unsupported_subcommand_json_preserves_values() { - let fixture = include_str!( - "../../../tests/fixtures/aws_backup_describe_global_settings.json" - ); + let fixture = + include_str!("../../../tests/fixtures/aws_backup_describe_global_settings.json"); let output = json_cmd::filter_json_compact(fixture, JSON_COMPRESS_DEPTH) .expect("filter_json_compact must not error on valid AWS JSON"); diff --git a/src/cmds/cloud/container.rs b/src/cmds/cloud/container.rs index 348ab14f55..93692c41f9 100644 --- a/src/cmds/cloud/container.rs +++ b/src/cmds/cloud/container.rs @@ -58,8 +58,8 @@ where fn docker_ps(_verbose: u8) -> Result { let timer = tracking::TimedExecution::start(); - let base = exec_capture(resolved_command("docker").args(["ps"])) - .context("Failed to run docker ps")?; + let base = + exec_capture(resolved_command("docker").args(["ps"])).context("Failed to run docker ps")?; if !base.success() { eprint!("{}", base.stderr); print!("{}", base.stdout); @@ -119,7 +119,12 @@ fn docker_ps_all(_verbose: u8) -> Result { if !base.success() { eprint!("{}", base.stderr); print!("{}", base.stdout); - timer.track("docker ps -a", "rtk docker ps -a", &base.stdout, &base.stdout); + timer.track( + "docker ps -a", + "rtk docker ps -a", + &base.stdout, + &base.stdout, + ); return Ok(base.exit_code); } let raw = base.stdout; @@ -186,7 +191,11 @@ fn docker_ps_all(_verbose: u8) -> Result { } } if truncated { - let full: String = running_lines.iter().chain(stopped_lines.iter()).cloned().collect(); + let full: String = running_lines + .iter() + .chain(stopped_lines.iter()) + .cloned() + .collect(); if let Some(hint) = crate::core::tee::force_tee_hint(&full, "docker-ps-a") { rtk.push_str(&format!("{}\n", hint)); } @@ -235,7 +244,12 @@ fn docker_images(_verbose: u8) -> Result { if !base.success() { eprint!("{}", base.stderr); print!("{}", base.stdout); - timer.track("docker images", "rtk docker images", &base.stdout, &base.stdout); + timer.track( + "docker images", + "rtk docker images", + &base.stdout, + &base.stdout, + ); return Ok(base.exit_code); } let raw = base.stdout; @@ -308,7 +322,9 @@ fn docker_images(_verbose: u8) -> Result { } if image_lines.len() > MAX_IMAGES { rtk.push_str(&format!(" … +{} more\n", image_lines.len() - MAX_IMAGES)); - if let Some(hint) = crate::core::tee::force_tee_tail_hint(&full_rtk, "docker-images", MAX_IMAGES + 2) { + if let Some(hint) = + crate::core::tee::force_tee_tail_hint(&full_rtk, "docker-images", MAX_IMAGES + 2) + { rtk.push_str(&format!("{}\n", hint)); } } @@ -421,9 +437,11 @@ fn format_kubectl_pods(json: &Value) -> String { if issues.len() > MAX_PODS_ISSUES { out.push_str(&format!(" … +{} more", issues.len() - MAX_PODS_ISSUES)); let all_issues = issues.join("\n"); - if let Some(hint) = - crate::core::tee::force_tee_tail_hint(&all_issues, "kubectl-pods", MAX_PODS_ISSUES + 1) - { + if let Some(hint) = crate::core::tee::force_tee_tail_hint( + &all_issues, + "kubectl-pods", + MAX_PODS_ISSUES + 1, + ) { out.push_str(&format!(" {}", hint)); } } @@ -480,11 +498,16 @@ fn format_kubectl_services(json: &Value) -> String { out.push_str(&format!("{}\n", line)); } if all_lines.len() > MAX_KUBECTL_SERVICES { - out.push_str(&format!(" … +{} more", all_lines.len() - MAX_KUBECTL_SERVICES)); + out.push_str(&format!( + " … +{} more", + all_lines.len() - MAX_KUBECTL_SERVICES + )); let all_text = all_lines.join("\n"); - if let Some(hint) = - crate::core::tee::force_tee_tail_hint(&all_text, "kubectl-services", MAX_KUBECTL_SERVICES + 1) - { + if let Some(hint) = crate::core::tee::force_tee_tail_hint( + &all_text, + "kubectl-services", + MAX_KUBECTL_SERVICES + 1, + ) { out.push_str(&format!(" {}", hint)); } out.push('\n'); @@ -557,7 +580,10 @@ pub fn format_compose_ps(raw: &str) -> String { format!(" [{}]", compact) } }; - Some(format!(" {} ({}) {}{}", name, short_image, status, port_str)) + Some(format!( + " {} ({}) {}{}", + name, short_image, status, port_str + )) }) .collect(); @@ -566,9 +592,14 @@ pub fn format_compose_ps(raw: &str) -> String { result.push('\n'); } if all_formatted.len() > MAX_COMPOSE_SERVICES { - result.push_str(&format!(" … +{} more\n", all_formatted.len() - MAX_COMPOSE_SERVICES)); + result.push_str(&format!( + " … +{} more\n", + all_formatted.len() - MAX_COMPOSE_SERVICES + )); let all_text = all_formatted.join("\n"); - if let Some(hint) = crate::core::tee::force_tee_tail_hint(&all_text, "compose-ps", MAX_COMPOSE_SERVICES + 1) { + if let Some(hint) = + crate::core::tee::force_tee_tail_hint(&all_text, "compose-ps", MAX_COMPOSE_SERVICES + 1) + { result.push_str(&format!(" {}\n", hint)); } } @@ -659,11 +690,7 @@ fn compact_ports(ports: &str) -> String { if port_nums.len() <= 3 { port_nums.join(", ") } else { - format!( - "{}, … +{}", - port_nums[..2].join(", "), - port_nums.len() - 2 - ) + format!("{}, … +{}", port_nums[..2].join(", "), port_nums.len() - 2) } } @@ -709,8 +736,16 @@ pub fn run_compose_ps(all: bool, verbose: u8) -> Result { let rtk = format_compose_ps(&structured); let shown = never_worse(&raw, &rtk); println!("{}", shown); - let label = if all { "docker compose ps -a" } else { "docker compose ps" }; - let rtk_label = if all { "rtk docker compose ps -a" } else { "rtk docker compose ps" }; + let label = if all { + "docker compose ps -a" + } else { + "docker compose ps" + }; + let rtk_label = if all { + "rtk docker compose ps -a" + } else { + "rtk docker compose ps" + }; timer.track(label, rtk_label, &raw, shown); Ok(0) } @@ -952,14 +987,20 @@ api-1 | Connected to database"; #[test] fn test_compact_ports_many() { - let result = compact_ports("0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:8080->8080/tcp, 0.0.0.0:9090->9090/tcp"); + let result = compact_ports( + "0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:8080->8080/tcp, 0.0.0.0:9090->9090/tcp", + ); assert!(result.contains("…"), "should truncate for >3 ports"); } #[test] fn test_k8s_get_target_pods_aliases() { for resource in ["po", "pod", "pods"] { - let args = vec![resource.to_string(), "-n".to_string(), "default".to_string()]; + let args = vec![ + resource.to_string(), + "-n".to_string(), + "default".to_string(), + ]; assert_eq!( k8s_get_target(&args), diff --git a/src/cmds/cloud/wget_cmd.rs b/src/cmds/cloud/wget_cmd.rs index 1f46262525..1aa2e918ae 100644 --- a/src/cmds/cloud/wget_cmd.rs +++ b/src/cmds/cloud/wget_cmd.rs @@ -105,7 +105,12 @@ pub fn run_stdout(url: &str, args: &[String], verbose: u8) -> Result { let msg = format!("{} FAILED: {}", compact_url(url), error); let shown = never_worse(&result.stderr, &msg); println!("{}", shown); - timer.track(&format!("wget -O - {}", url), "rtk wget -o", &result.stderr, shown); + timer.track( + &format!("wget -O - {}", url), + "rtk wget -o", + &result.stderr, + shown, + ); return Ok(result.exit_code); } @@ -266,15 +271,24 @@ mod tests { #[test] fn test_compact_url_strips_protocol() { - assert_eq!(compact_url("https://example.com/file.zip"), "example.com/file.zip"); - assert_eq!(compact_url("http://example.com/file.zip"), "example.com/file.zip"); + assert_eq!( + compact_url("https://example.com/file.zip"), + "example.com/file.zip" + ); + assert_eq!( + compact_url("http://example.com/file.zip"), + "example.com/file.zip" + ); } #[test] fn test_compact_url_truncates_long_url() { let long = "https://example.com/very/long/path/that/exceeds/fifty/characters/file.zip"; let result = compact_url(long); - assert!(result.contains("..."), "Long URL should be truncated with ..."); + assert!( + result.contains("..."), + "Long URL should be truncated with ..." + ); assert!(result.len() < long.len()); } diff --git a/src/cmds/dotnet/binlog.rs b/src/cmds/dotnet/binlog.rs index d643c41b65..e4dc5a46a1 100644 --- a/src/cmds/dotnet/binlog.rs +++ b/src/cmds/dotnet/binlog.rs @@ -1145,8 +1145,8 @@ fn is_likely_diagnostic_code(code: &str) -> bool { #[cfg(test)] mod tests { use super::*; - use flate2::write::GzEncoder; use flate2::Compression; + use flate2::write::GzEncoder; use std::io::Write; fn write_7bit_i32(buf: &mut Vec, value: i32) { @@ -1263,9 +1263,11 @@ Switch: /tmp/nonexistent.csproj assert_eq!(summary.errors.len(), 1); assert_eq!(summary.errors[0].code, "MSB1009"); assert_eq!(summary.errors[0].file, "MSBUILD"); - assert!(summary.errors[0] - .message - .contains("Project file does not exist")); + assert!( + summary.errors[0] + .message + .contains("Project file does not exist") + ); } #[test] @@ -1286,9 +1288,11 @@ Failed! - Failed: 2, Passed: 245, Skipped: 0, Total: 247, Duration: assert_eq!(summary.failed, 2); assert_eq!(summary.total, 247); assert_eq!(summary.failed_tests.len(), 2); - assert!(summary.failed_tests[0] - .name - .contains("CalculatorTests.Add_ShouldReturnSum")); + assert!( + summary.failed_tests[0] + .name + .contains("CalculatorTests.Add_ShouldReturnSum") + ); } #[test] @@ -1596,9 +1600,11 @@ Time Elapsed 00:00:00.12 assert_eq!(summary.passed, 0); assert_eq!(summary.total, 1); assert_eq!(summary.failed_tests.len(), 1); - assert!(summary.failed_tests[0] - .name - .contains("RtkDotnetSmoke.UnitTest1.Test1")); + assert!( + summary.failed_tests[0] + .name + .contains("RtkDotnetSmoke.UnitTest1.Test1") + ); } #[test] diff --git a/src/cmds/dotnet/dotnet_cmd.rs b/src/cmds/dotnet/dotnet_cmd.rs index e99fa60a4d..2527f4f3c4 100644 --- a/src/cmds/dotnet/dotnet_cmd.rs +++ b/src/cmds/dotnet/dotnet_cmd.rs @@ -11,8 +11,8 @@ use crate::core::utils::{resolved_command, truncate}; use crate::dotnet_format_report; use crate::dotnet_trx; use anyhow::{Context, Result}; -use quick_xml::events::Event; use quick_xml::Reader; +use quick_xml::events::Event; use serde_json::Value; use std::ffi::OsString; use std::path::{Path, PathBuf}; @@ -345,7 +345,9 @@ fn build_effective_dotnet_format_args( // UnparsedTokens, so a `--verify-no-changes` after the boundary never applies and format // rewrites the tree while RTK still reports check mode. let boundary = arg_tokenizer::injection_point(tokens, args.len()); - let write_args: Vec = write_override_tokens(tokens).map(|t| t.source_index).collect(); + let write_args: Vec = write_override_tokens(tokens) + .map(|t| t.source_index) + .collect(); let mut effective: Vec = Vec::with_capacity(args.len() + injected.len()); for (index, arg) in args.iter().enumerate() { if index == boundary { @@ -854,7 +856,6 @@ fn dotnet_has_bare_loose_flag(tokens: &[Token<'_>], name: &str) -> bool { }) } - fn dotnet_has_bare_double_dash_flag(tokens: &[Token<'_>], name: &str) -> bool { tokens.iter().any(|t| { t.kind == TokenKind::Long @@ -1555,11 +1556,20 @@ mod tests { #[test] fn test_double_dash_only_flags_reject_single_dash_and_slash_spellings() { let args = vec!["-results-directory".to_string(), "/tmp/out".to_string()]; - assert!(!has_results_directory_arg(&tokenize_dotnet_args(&args), TestRunnerMode::Classic)); - assert_eq!(extract_results_directory_arg(&tokenize_dotnet_args(&args), TestRunnerMode::Classic), None); + assert!(!has_results_directory_arg( + &tokenize_dotnet_args(&args), + TestRunnerMode::Classic + )); + assert_eq!( + extract_results_directory_arg(&tokenize_dotnet_args(&args), TestRunnerMode::Classic), + None + ); let args = vec!["/results-directory".to_string(), "/tmp/out".to_string()]; - assert!(!has_results_directory_arg(&tokenize_dotnet_args(&args), TestRunnerMode::Classic)); + assert!(!has_results_directory_arg( + &tokenize_dotnet_args(&args), + TestRunnerMode::Classic + )); let args = vec!["-report".to_string(), "/tmp/r.json".to_string()]; assert!(!has_report_arg(&tokenize_dotnet_args(&args))); @@ -1576,7 +1586,10 @@ mod tests { // The canonical "--" forms still work. let args = vec!["--results-directory".to_string(), "/tmp/out".to_string()]; - assert!(has_results_directory_arg(&tokenize_dotnet_args(&args), TestRunnerMode::Classic)); + assert!(has_results_directory_arg( + &tokenize_dotnet_args(&args), + TestRunnerMode::Classic + )); } #[test] @@ -1716,8 +1729,7 @@ mod tests { // The raw stdout contains the inline failure; the filtered section also // contains it. With needs_raw_fallback=false, the failure must appear once. let raw_stdout = " failed MyTests.HasRestriction\n Assert.True() Failure"; - let filtered = - "Failed Tests:\n MyTests.HasRestriction\n Assert.True() Failure\n\nfail dotnet test: 717 passed, 5 failed"; + let filtered = "Failed Tests:\n MyTests.HasRestriction\n Assert.True() Failure\n\nfail dotnet test: 717 passed, 5 failed"; let output = compose_failure_output(false, false, raw_stdout, "", filtered); assert_eq!(output, filtered); @@ -2106,9 +2118,11 @@ mod tests { assert_eq!(merged.skipped, 8); assert_eq!(merged.total, 948); assert_eq!(merged.failed_tests.len(), 1); - assert!(merged.failed_tests[0] - .name - .contains("CreateInstance_should_initialize")); + assert!( + merged.failed_tests[0] + .name + .contains("CreateInstance_should_initialize") + ); } #[test] @@ -2331,11 +2345,7 @@ mod tests { fn test_forwarded_args_do_not_suppress_injection() { // Everything past `--` goes to the test runner, not to dotnet, so a `--logger` there // is not the user asking dotnet for a logger -- RTK still has to inject its own. - let args = vec![ - "--".to_string(), - "--logger".to_string(), - "trx".to_string(), - ]; + let args = vec!["--".to_string(), "--logger".to_string(), "trx".to_string()]; let injected = build_dotnet_args_for_test("test", &args, true); let boundary = injected.iter().position(|a| a == "--").expect("boundary"); let logger = injected @@ -2347,7 +2357,10 @@ mod tests { let args = vec!["--".to_string(), "--nologo".to_string()]; let injected = build_dotnet_args_for_test("test", &args, true); let boundary = injected.iter().position(|a| a == "--").expect("boundary"); - let nologo = injected.iter().position(|a| a == "-nologo").expect("nologo"); + let nologo = injected + .iter() + .position(|a| a == "-nologo") + .expect("nologo"); assert!(nologo < boundary, "{injected:?}"); } @@ -2401,12 +2414,16 @@ mod tests { ]; let injected = build_dotnet_args_for_test("test", &args, true); - assert!(!injected - .windows(2) - .any(|w| w[0] == "--results-directory" && w[1] == "/tmp/test results")); - assert!(injected - .windows(2) - .any(|w| w[0] == "--results-directory" && w[1] == "/custom/results")); + assert!( + !injected + .windows(2) + .any(|w| w[0] == "--results-directory" && w[1] == "/tmp/test results") + ); + assert!( + injected + .windows(2) + .any(|w| w[0] == "--results-directory" && w[1] == "/custom/results") + ); } #[test] @@ -2735,15 +2752,14 @@ mod tests { let binlog_path = Path::new("/tmp/test.binlog"); let trx_dir = Path::new("/tmp/test_results"); - let injected = - build_effective_dotnet_args( - "test", - &args, - &tokens, - binlog_path, - Some(trx_dir), - TestRunnerMode::Classic, - ); + let injected = build_effective_dotnet_args( + "test", + &args, + &tokens, + binlog_path, + Some(trx_dir), + TestRunnerMode::Classic, + ); assert!(injected.contains(&"--logger".to_string())); assert!(injected.contains(&"trx".to_string())); } @@ -3023,13 +3039,22 @@ mod tests { #[test] fn test_has_results_directory_arg_detects_variants() { let args = vec!["--results-directory".to_string(), "/tmp/trx".to_string()]; - assert!(has_results_directory_arg(&tokenize_dotnet_args(&args), TestRunnerMode::Classic)); + assert!(has_results_directory_arg( + &tokenize_dotnet_args(&args), + TestRunnerMode::Classic + )); let args = vec!["--results-directory=/tmp/trx".to_string()]; - assert!(has_results_directory_arg(&tokenize_dotnet_args(&args), TestRunnerMode::Classic)); + assert!(has_results_directory_arg( + &tokenize_dotnet_args(&args), + TestRunnerMode::Classic + )); let args = vec!["--logger".to_string(), "trx".to_string()]; - assert!(!has_results_directory_arg(&tokenize_dotnet_args(&args), TestRunnerMode::Classic)); + assert!(!has_results_directory_arg( + &tokenize_dotnet_args(&args), + TestRunnerMode::Classic + )); } #[test] @@ -3054,7 +3079,11 @@ mod tests { "/custom/results".to_string(), ]; - let (dir, cleanup) = resolve_trx_results_dir("test", &tokenize_dotnet_args(&args), TestRunnerMode::Classic); + let (dir, cleanup) = resolve_trx_results_dir( + "test", + &tokenize_dotnet_args(&args), + TestRunnerMode::Classic, + ); assert_eq!(dir, Some(PathBuf::from("/custom/results"))); assert!(!cleanup); } @@ -3103,7 +3132,11 @@ mod tests { fn test_resolve_trx_results_dir_generated_directory_is_marked_for_cleanup() { let args = Vec::::new(); - let (dir, cleanup) = resolve_trx_results_dir("test", &tokenize_dotnet_args(&args), TestRunnerMode::Classic); + let (dir, cleanup) = resolve_trx_results_dir( + "test", + &tokenize_dotnet_args(&args), + TestRunnerMode::Classic, + ); assert!(dir.is_some()); assert!(cleanup); } @@ -3186,7 +3219,10 @@ mod tests { .iter() .position(|a| a == "--verify-no-changes") .expect("check mode"); - let report = effective.iter().position(|a| a == "--report").expect("report"); + let report = effective + .iter() + .position(|a| a == "--report") + .expect("report"); assert!(verify < boundary, "{effective:?}"); assert!(report < boundary, "{effective:?}"); } diff --git a/src/cmds/dotnet/dotnet_trx.rs b/src/cmds/dotnet/dotnet_trx.rs index ef94f00892..b539227612 100644 --- a/src/cmds/dotnet/dotnet_trx.rs +++ b/src/cmds/dotnet/dotnet_trx.rs @@ -2,8 +2,8 @@ use crate::binlog::{FailedTest, TestSummary}; use chrono::{DateTime, FixedOffset}; -use quick_xml::events::{BytesStart, Event}; use quick_xml::Reader; +use quick_xml::events::{BytesStart, Event}; use std::path::{Path, PathBuf}; use std::time::SystemTime; diff --git a/src/cmds/git/diff_cmd.rs b/src/cmds/git/diff_cmd.rs index f29b9cd224..ddf0e7fa0d 100644 --- a/src/cmds/git/diff_cmd.rs +++ b/src/cmds/git/diff_cmd.rs @@ -694,9 +694,9 @@ fn format_diff_changes(diff: &DiffResult) -> String { } if crossed => { out.push_str(&format!("~{:4}→{} {} → {}\n", line1, line2, old, new)) } - DiffChange::Modified { line1, old, new, .. } => { - out.push_str(&format!("~{:4} {} → {}\n", line1, old, new)) - } + DiffChange::Modified { + line1, old, new, .. + } => out.push_str(&format!("~{:4} {} → {}\n", line1, old, new)), } } } @@ -1548,9 +1548,9 @@ fn is_submodule_range(line: &str) -> bool { fn is_mbox_from(line: &str) -> bool { line.strip_prefix("From ").is_some_and(|rest| { let b = rest.as_bytes(); - [40usize, 64].iter().any(|&n| { - b.len() > n && b[..n].iter().all(|c| c.is_ascii_hexdigit()) && b[n] == b' ' - }) + [40usize, 64] + .iter() + .any(|&n| b.len() > n && b[..n].iter().all(|c| c.is_ascii_hexdigit()) && b[n] == b' ') }) } @@ -2167,8 +2167,7 @@ fn condense_unified_diff_strict(diff: &str) -> Option { // fold when the fact is the head of the decoded section name. let same_file = |p: &FileEntry| { p.name == e.name - || e - .name + || e.name .strip_prefix('"') .and_then(|q| q.strip_suffix('"')) .and_then(|inner| decode_backslashes(inner, false)) @@ -2281,8 +2280,7 @@ fn condense_unified_diff_strict(diff: &str) -> Option { // `hg export` changeset header, or `hg log -p`'s `changeset:` line, // opens the same kind of message region: hg's headers and message // precede its `diff -r` echo. - if is_mbox_from(line) || line == "# HG changeset patch" || line.starts_with("changeset:") - { + if is_mbox_from(line) || line == "# HG changeset patch" || line.starts_with("changeset:") { // Rule 8's shape check: the region this separator closes is judged. if region_exempt_nonprose && !region_reached_body { return None; @@ -2304,7 +2302,10 @@ fn condense_unified_diff_strict(diff: &str) -> Option { .or_else(|| line.strip_prefix("diff --combined ")) { // An svn `Index:` line names the same file this header opens. - if current.as_ref().is_some_and(|e| e.from_index && e.is_empty()) { + if current + .as_ref() + .is_some_and(|e| e.from_index && e.is_empty()) + { current = None; } flush(&mut entries, &mut current)?; @@ -2344,10 +2345,7 @@ fn condense_unified_diff_strict(diff: &str) -> Option { head.split('/').next().unwrap_or("").to_string() }; let (px, py) = (root(&x), root(&y)); - ( - header_name(&x, &y, Some((&px, &py))), - Some((px, py)), - ) + (header_name(&x, &y, Some((&px, &py))), Some((px, py))) } None => { // A rename (`rename to` names it exactly) or two @@ -2357,7 +2355,9 @@ fn condense_unified_diff_strict(diff: &str) -> Option { .rfind(" b/") .or_else(|| rest.rfind(" \"b/")) .or_else(|| rest.rfind(' ')) - .map_or(std::borrow::Cow::Borrowed(rest), |p| dequote(&rest[p + 1..])); + .map_or(std::borrow::Cow::Borrowed(rest), |p| { + dequote(&rest[p + 1..]) + }); (strip_quoted_prefix(&y, "b"), None) } } @@ -2497,9 +2497,10 @@ fn condense_unified_diff_strict(diff: &str) -> Option { match current.as_mut().filter(|e| e.header_only()) { // `rename to`/`copy to` is git's exact path; the pair // under `--no-prefix` would re-derive it wrongly. - Some(e) if e.notes.iter().any(|n| { - n.starts_with("renamed from ") || n.starts_with("copied from ") - }) => {} + Some(e) + if e.notes.iter().any(|n| { + n.starts_with("renamed from ") || n.starts_with("copied from ") + }) => {} Some(e) => { let p = e.prefixes.as_ref().map(|(x, y)| (x.as_str(), y.as_str())); e.name = header_name(minus, plus, p); @@ -3009,11 +3010,10 @@ mod tests { let result = compute_diff(&a, &b); assert_eq!(result.added, 1); assert_eq!(result.modified, 1); - assert!(result - .changes() - .iter() - .any(|c| matches!(c, DiffChange::Modified { line1: 3, line2: 4, old, new } - if *old == "let x = 1;" && *new == "let x = 2;"))); + assert!(result.changes().iter().any( + |c| matches!(c, DiffChange::Modified { line1: 3, line2: 4, old, new } + if *old == "let x = 1;" && *new == "let x = 2;") + )); } #[test] @@ -3135,7 +3135,10 @@ mod tests { aligned += 1; } } - assert!(aligned > 0 && positional > 0, "both branches must be covered"); + assert!( + aligned > 0 && positional > 0, + "both branches must be covered" + ); } #[test] @@ -3220,7 +3223,11 @@ mod tests { "region must be stated as line bounds, got:\n{}", out ); - assert!(!out.contains("spans"), "no span-shaped figure, got:\n{}", out); + assert!( + !out.contains("spans"), + "no span-shaped figure, got:\n{}", + out + ); } #[test] @@ -3303,7 +3310,9 @@ mod tests { /// Deterministic pseudo-random source, so a failure reproduces exactly. fn lcg(state: &mut u64) -> u64 { - *state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); + *state = state + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); *state >> 33 } @@ -3728,8 +3737,12 @@ mod tests { #[test] fn test_render_added_only_needs_no_frame_legend() { // Every listed line comes from file2. One frame, no note. - let (out, _) = - render_file_diff(Path::new("a.txt"), Path::new("b.txt"), "a\nb\n", "a\nNEW\nb\n"); + let (out, _) = render_file_diff( + Path::new("a.txt"), + Path::new("b.txt"), + "a\nb\n", + "a\nNEW\nb\n", + ); assert!(out.contains("+ 2 NEW"), "got:\n{}", out); assert!( @@ -4034,10 +4047,27 @@ mod tests { render_file_diff(Path::new("a.txt"), Path::new("b.txt"), &content1, &content2); assert_eq!(code, 1); - assert!(out.contains("~ 5 line 4 → line 4 EDITED"), "got:\n{}", out); - assert!(out.contains("~2095 line 2094 → line 2094 EDITED"), "got:\n{}", out); - assert_eq!(out.lines().count(), 2, "two changes, nothing else, got:\n{}", out); - assert!(!out.contains("2091"), "region size must not appear, got:\n{}", out); + assert!( + out.contains("~ 5 line 4 → line 4 EDITED"), + "got:\n{}", + out + ); + assert!( + out.contains("~2095 line 2094 → line 2094 EDITED"), + "got:\n{}", + out + ); + assert_eq!( + out.lines().count(), + 2, + "two changes, nothing else, got:\n{}", + out + ); + assert!( + !out.contains("2091"), + "region size must not appear, got:\n{}", + out + ); } #[test] @@ -4132,8 +4162,12 @@ mod tests { fn test_describe_invisible_difference_never_prints_equal_byte_counts() { // Same CRLF count on both sides, different placement. The old fallback // printed "5 vs 5 bytes", which reads as "no difference at all". - let (out, code) = - render_file_diff(Path::new("a.txt"), Path::new("b.txt"), "a\r\nb\n", "a\nb\r\n"); + let (out, code) = render_file_diff( + Path::new("a.txt"), + Path::new("b.txt"), + "a\r\nb\n", + "a\nb\r\n", + ); assert_eq!(code, 1); assert!(!out.contains("5 vs 5 bytes"), "got:\n{}", out); @@ -4157,7 +4191,10 @@ mod tests { let shown = select_file_diff_output(&comparison, &fallback, "", &rendered); assert_eq!(code, 1); - assert!(rendered.len() > fallback.len(), "fixture must make classic the smaller output"); + assert!( + rendered.len() > fallback.len(), + "fixture must make classic the smaller output" + ); assert_eq!(shown, "1a2,4\n> new 1\n> new 2\n> new 3\n"); } @@ -4167,7 +4204,9 @@ mod tests { // "< " / "> " markers, so it is bigger than a plain dump. Measuring // against the dump used to record negative savings. let content1: String = (0..40).map(|i| format!("old line {i}\n")).collect(); - let content2: String = (0..40).map(|i| format!("brand new content {i}\n")).collect(); + let content2: String = (0..40) + .map(|i| format!("brand new content {i}\n")) + .collect(); let both_files = format!("{}\n---\n{}", content1, content2); let comparison = compare_files(&content1, &content2); @@ -4364,7 +4403,11 @@ mod tests { "got:\n{}", listed ); - assert!(listed.contains(&format!("+ 2 {}", inserted)), "got:\n{}", listed); + assert!( + listed.contains(&format!("+ 2 {}", inserted)), + "got:\n{}", + listed + ); } #[test] @@ -4385,7 +4428,10 @@ mod tests { // deletion sitting second. Greedy-by-score pairs each with its own // rewrite; positional pairing would have crossed them. let pairs = pair_rewrites( - &["let alpha = 1;".to_string(), "fn beta_gamma() {}".to_string()], + &[ + "let alpha = 1;".to_string(), + "fn beta_gamma() {}".to_string(), + ], &[ "fn beta_gamma_delta() {}".to_string(), "let alpha = 2;".to_string(), @@ -4405,7 +4451,9 @@ mod tests { // 17 x 17 is over the cap. Every line pairs with its positional twin, // which is right for an in-place rewrite of a block. let old: Vec = (0..17).map(|i| format!("line {} = {};", i, i)).collect(); - let new: Vec = (0..17).map(|i| format!("line {} = {};", i, i + 1)).collect(); + let new: Vec = (0..17) + .map(|i| format!("line {} = {};", i, i + 1)) + .collect(); assert!(old.len() * new.len() > PAIRING_CELL_CAP); let pairs = pair_rewrites(&old, &new); assert_eq!(pairs, (0..17).map(|i| (i, i)).collect::>()); @@ -4429,7 +4477,11 @@ mod tests { let Ok(Aligned::Script(ops)) = myers_ops(&a[10..99_991], &b[10..99_991]) else { panic!("three rewrites must align"); }; - assert!(ops.len() <= 8, "script must not scale with the file, got {}", ops.len()); + assert!( + ops.len() <= 8, + "script must not scale with the file, got {}", + ops.len() + ); let kept: usize = ops .iter() .map(|op| match op { @@ -4481,8 +4533,12 @@ mod tests { // The framing cost more than the change list saved on agent-sized // diffs: the header echoed two paths the caller typed, the blank line // bought nothing. What is left is the counts line and the listing. - let (out, code) = - render_file_diff(Path::new("a.txt"), Path::new("b.txt"), "a\nb\n", "a\nNEW\nb\n"); + let (out, code) = render_file_diff( + Path::new("a.txt"), + Path::new("b.txt"), + "a\nb\n", + "a\nNEW\nb\n", + ); assert_eq!(code, 1); assert_eq!(out, "+ 2 NEW\n"); } @@ -4514,11 +4570,18 @@ mod tests { let content1: String = (0..100).map(|i| format!("line {}\n", i)).collect(); let short = content1.replace("line 5\n", "line 5 EDITED\n"); let (out, _) = render_file_diff(Path::new("a.txt"), Path::new("b.txt"), &content1, &short); - assert!(!out.contains("modified\n"), "one change needs no summary, got:\n{}", out); + assert!( + !out.contains("modified\n"), + "one change needs no summary, got:\n{}", + out + ); let mut long = content1.clone(); for i in 0..COUNTS_MIN_LISTED_LINES { - long = long.replace(&format!("line {}\n", i * 3), &format!("line {} EDITED\n", i * 3)); + long = long.replace( + &format!("line {}\n", i * 3), + &format!("line {} EDITED\n", i * 3), + ); } let (out, _) = render_file_diff(Path::new("a.txt"), Path::new("b.txt"), &content1, &long); assert!( @@ -4533,8 +4596,7 @@ mod tests { #[test] fn test_render_invisible_difference_is_one_line() { - let (out, code) = - render_file_diff(Path::new("a.txt"), Path::new("b.txt"), "x\n", "x\r\n"); + let (out, code) = render_file_diff(Path::new("a.txt"), Path::new("b.txt"), "x\n", "x\r\n"); assert_eq!(code, 1); assert_eq!( out, @@ -4598,7 +4660,11 @@ mod tests { out ); assert_eq!(code, 1); - assert!(out.contains("trailing newline: present vs absent"), "got: {}", out); + assert!( + out.contains("trailing newline: present vs absent"), + "got: {}", + out + ); } #[test] @@ -4618,7 +4684,9 @@ mod tests { fn test_render_partial_crlf_matches_reported_repro() { // The shape actually observed: a 200-line file where a Windows editor // touched 24 lines. Text identical, bytes differ, `cmp` exits 1. - let plain: String = (0..200).map(|i| format!("line {} content here\n", i)).collect(); + let plain: String = (0..200) + .map(|i| format!("line {} content here\n", i)) + .collect(); let mixed: String = (0..200) .map(|i| { if (50..74).contains(&i) { @@ -4729,7 +4797,9 @@ mod tests { assert!(!is_hg_echo("diff -r 0123456789abcd -r 0123456789cd f.txt")); let sha1 = "a".repeat(40); - assert!(is_mbox_from(&format!("From {sha1} Mon Sep 17 00:00:00 2001"))); + assert!(is_mbox_from(&format!( + "From {sha1} Mon Sep 17 00:00:00 2001" + ))); assert!(is_mbox_from(&format!( "From {} Mon Sep 17 00:00:00 2001", "b".repeat(64) @@ -4744,11 +4814,15 @@ mod tests { )); assert!(is_submodule_range("Submodule sub 0123456..89abcde:")); - assert!(is_submodule_range("Submodule sub 0123456..89abcde (rewind):")); + assert!(is_submodule_range( + "Submodule sub 0123456..89abcde (rewind):" + )); assert!(!is_submodule_range("Submodule sub zzzzzzz..89abcde:")); assert!(!is_submodule_range("Submodule sub 0123456..zzzzzzz:")); assert!(!is_submodule_range("Submodule sub 01234..89abcde:")); - assert!(!is_submodule_range("Submodule sub contains modified content")); + assert!(!is_submodule_range( + "Submodule sub contains modified content" + )); } // --- condense_unified_diff --- @@ -4811,8 +4885,7 @@ diff --git a/b.rs b/b.rs assert!( !result.lines().any(|l| { let trimmed = l.trim_start(); - trimmed.len() != l.len() - && (trimmed.starts_with('+') || trimmed.starts_with('-')) + trimmed.len() != l.len() && (trimmed.starts_with('+') || trimmed.starts_with('-')) }), "change lines must not be indented:\n{}", result @@ -5209,10 +5282,11 @@ diff --git a/b.rs b/b.rs for (name, fixture) in CORPUS { let out = condense_unified_diff(fixture); let out_lines: std::collections::HashMap<&str, usize> = - out.split('\n').fold(std::collections::HashMap::new(), |mut m, l| { - *m.entry(l).or_default() += 1; - m - }); + out.split('\n') + .fold(std::collections::HashMap::new(), |mut m, l| { + *m.entry(l).or_default() += 1; + m + }); let mut expected: std::collections::HashMap<&str, usize> = std::collections::HashMap::new(); let mut budget: Option<(Vec, usize)> = None; @@ -5361,11 +5435,13 @@ diff --git a/b.rs b/b.rs // Reproducer 3 + the round-5 lesson: the `-- ` signature is not a // removal, and unindented `- ` commit-message bullets (mbox prose) // neither count nor trigger the fallback. - let fixture = - include_str!("../../../tests/fixtures/diff/git_format_patch_single_raw.txt"); + let fixture = include_str!("../../../tests/fixtures/diff/git_format_patch_single_raw.txt"); let out = condense_unified_diff(fixture); assert_ne!(out, fixture, "format-patch fell back to raw"); - assert!(!out.contains("-- \n"), "signature counted as content:\n{out}"); + assert!( + !out.contains("-- \n"), + "signature counted as content:\n{out}" + ); assert!( !out.contains("- remove the"), "mbox prose bullet leaked into output:\n{out}" @@ -5517,27 +5593,26 @@ diff --git a/b.rs b/b.rs // `git diff` (also `--submodule=log`) reports a dirty submodule as a // sha-less `Submodule contains modified content` line; with // a condensed sibling in the stream it used to be dropped as prose. - let fixture = - include_str!("../../../tests/fixtures/diff/git_diff_submodule_dirty_raw.txt"); + let fixture = include_str!("../../../tests/fixtures/diff/git_diff_submodule_dirty_raw.txt"); let out = condense_unified_diff_strict(fixture).expect("real git diff must parse"); assert!(out.contains("[file] other.txt (+1 -0)"), "got:\n{out}"); assert!( out.contains("[file] sub (submodule, modified content)"), "dirty-submodule fact vanished:\n{out}" ); - let out = condense_unified_diff_strict( - &fixture.replace("modified content", "untracked content"), - ) - .expect("must parse"); - assert!(out.contains("(submodule, untracked content)"), "got:\n{out}"); + let out = + condense_unified_diff_strict(&fixture.replace("modified content", "untracked content")) + .expect("must parse"); + assert!( + out.contains("(submodule, untracked content)"), + "got:\n{out}" + ); // A submodule path containing `..` keeps its name: the range is // the LAST token holding `..`. - let out = condense_unified_diff_strict( - &fixture.replace( - "Submodule sub contains modified content", - "Submodule a..b e139196..b0ac9b1 (rewind):", - ), - ) + let out = condense_unified_diff_strict(&fixture.replace( + "Submodule sub contains modified content", + "Submodule a..b e139196..b0ac9b1 (rewind):", + )) .expect("must parse"); assert!( out.contains("[file] a..b (submodule, e139196..b0ac9b1 rewind)"), @@ -5569,18 +5644,12 @@ diff --git a/b.rs b/b.rs // on every header line; the prefix strip has to see through them. let diff = "diff --git \"a/caf\\303\\251.txt\" \"b/caf\\303\\251.txt\"\nindex 587be6b..975fbec 100644\n--- \"a/caf\\303\\251.txt\"\n+++ \"b/caf\\303\\251.txt\"\n@@ -1 +1 @@\n-x\n+y\n"; let out = condense_unified_diff_strict(diff).expect("must parse"); - assert!( - out.contains("[file] café.txt (+1 -1)"), - "got:\n{out}" - ); + assert!(out.contains("[file] café.txt (+1 -1)"), "got:\n{out}"); // The `diff --git` fallback name (no `+++` follows a binary section) // dequotes the same way. let bin = "diff --git \"a/caf\\303\\251.bin\" \"b/caf\\303\\251.bin\"\nindex 587be6b..975fbec 100644\nBinary files \"a/caf\\303\\251.bin\" and \"b/caf\\303\\251.bin\" differ\n"; let out = condense_unified_diff_strict(bin).expect("must parse"); - assert!( - out.contains("[file] café.bin (binary)"), - "got:\n{out}" - ); + assert!(out.contains("[file] café.bin (binary)"), "got:\n{out}"); // `rename to` / `copy to` name the entry directly and are quoted the // same way; otherwise one file gets two spellings across a stream. let ren = "diff --git \"a/caf\\303\\251.txt\" \"b/na\\303\\257ve.txt\"\nsimilarity index 100%\nrename from \"caf\\303\\251.txt\"\nrename to \"na\\303\\257ve.txt\"\n"; @@ -5626,8 +5695,7 @@ diff --git a/b.rs b/b.rs // SHA-256 repos emit 64-hex `From` separators; without accepting // them the whole stream fell back raw (the signature never earned // its rule-7 tolerance). - let fixture = - include_str!("../../../tests/fixtures/diff/git_format_patch_sha256_raw.txt"); + let fixture = include_str!("../../../tests/fixtures/diff/git_format_patch_sha256_raw.txt"); let out = condense_unified_diff(fixture); assert_ne!(out, fixture, "sha256 format-patch fell back to raw"); assert!(out.contains("[file] f.txt (+1 -1)"), "got:\n{out}"); @@ -5693,9 +5761,10 @@ diff --git a/b.rs b/b.rs let colored = "\u{1b}[1mdiff --git a/x b/x\u{1b}[m\n\u{1b}[1m--- a/x\u{1b}[m\n\u{1b}[1m+++ b/x\u{1b}[m\n\u{1b}[36m@@ -1 +1 @@\u{1b}[m\n\u{1b}[31m-old_line_content\u{1b}[m\n\u{1b}[32m+new_line_content\u{1b}[m\n"; let out = condense_stdin(colored.as_bytes()).expect("colored diff must parse"); assert!(out.contains("[file] x (+1 -1)"), "got:\n{out}"); - assert!(out - .lines() - .any(|l| l == "\u{1b}[31m-old_line_content\u{1b}[m")); + assert!( + out.lines() + .any(|l| l == "\u{1b}[31m-old_line_content\u{1b}[m") + ); // GNU `diff -u --color=always`: a bare header pair whose `@@` is // coloured, so rule 4's opens-a-hunk lookahead must read through // the stripped view too. @@ -5720,7 +5789,10 @@ diff --git a/b.rs b/b.rs // counts as the line's CR for structural purposes. let crlf = "--- a/s\n+++ b/s\n@@ -1 +1 @@\n-x\u{1b}[0m\r\n+y\u{1b}[0m\r\n"; let out = condense_unified_diff(crlf); - assert!(out.split('\n').any(|l| l == "-x\u{1b}[0m\r"), "got:\n{out:?}"); + assert!( + out.split('\n').any(|l| l == "-x\u{1b}[0m\r"), + "got:\n{out:?}" + ); } #[test] @@ -5920,12 +5992,14 @@ diff --git a/b.rs b/b.rs // A symlink turned regular file is two sections for one path, as // git prints it. assert!(out.contains("[file] link (deleted) (+0 -1)"), "got:\n{out}"); - assert!(out.contains("[file] link (new file) (+1 -0)"), "got:\n{out}"); + assert!( + out.contains("[file] link (new file) (+1 -0)"), + "got:\n{out}" + ); // `--src-prefix=old/ --dst-prefix=new/`: the `diff --git X Y` line // settles the prefixes, whatever they are, and they are stripped // exactly — the same way git's own `diff_header_path` reads them. - let custom = - include_str!("../../../tests/fixtures/diff/git_diff_custom_prefix_raw.txt"); + let custom = include_str!("../../../tests/fixtures/diff/git_diff_custom_prefix_raw.txt"); let out = condense_unified_diff_strict(custom).expect("must parse"); assert!(out.contains("[file] f.txt (+1 -1)"), "got:\n{out}"); assert!(out.contains("[file] x b/y.bin (binary)"), "got:\n{out}"); @@ -5936,14 +6010,16 @@ diff --git a/b.rs b/b.rs let mnemonic = include_str!("../../../tests/fixtures/diff/git_diff_mnemonic_raw.txt"); let out = condense_unified_diff_strict(mnemonic).expect("must parse"); assert!(out.contains("[file] indented.py (+1 -1)"), "got:\n{out}"); - let ours = - include_str!("../../../tests/fixtures/diff/git_diff_ours_mnemonic_raw.txt"); + let ours = include_str!("../../../tests/fixtures/diff/git_diff_ours_mnemonic_raw.txt"); let out = condense_unified_diff_strict(ours).expect("must parse"); assert!( out.contains("[file] b/inb.txt (unmerged) (+4 -0)"), "got:\n{out}" ); - assert!(out.contains("[file] café.txt (unmerged) (+4 -0)"), "got:\n{out}"); + assert!( + out.contains("[file] café.txt (unmerged) (+4 -0)"), + "got:\n{out}" + ); assert!(out.contains("[file] b/blob.bin (unmerged)"), "got:\n{out}"); assert_eq!(out.matches("[file] b/inb.txt").count(), 1, "got:\n{out}"); } @@ -6038,7 +6114,10 @@ diff --git a/b.rs b/b.rs out.contains("[file] doomed.txt (deleted) (+0 -3)\n-line a\r\n"), "CRLF content bytes must survive:\n{out}" ); - assert!(out.contains("(renamed from renamed_src.txt)"), "got:\n{out}"); + assert!( + out.contains("(renamed from renamed_src.txt)"), + "got:\n{out}" + ); } #[test] @@ -6070,8 +6149,14 @@ diff --git a/b.rs b/b.rs assert_eq!(shared_tail("f", "f"), Some(1)); assert_eq!(shared_tail("x.bin", "y.bin"), None); assert_eq!(shared_tail("b.bin", "a and b.bin"), None); - assert_eq!(strip_timestamp("g1/ta\tb.txt\t2026-09-07 09:04:54 -0400"), "g1/ta\tb.txt"); - assert_eq!(strip_timestamp("a/tab\there.txt\tMon Sep 07 12:55:24 2026 +0000"), "a/tab\there.txt"); + assert_eq!( + strip_timestamp("g1/ta\tb.txt\t2026-09-07 09:04:54 -0400"), + "g1/ta\tb.txt" + ); + assert_eq!( + strip_timestamp("a/tab\there.txt\tMon Sep 07 12:55:24 2026 +0000"), + "a/tab\there.txt" + ); assert_eq!(strip_timestamp("t.txt\t(revision 1)"), "t.txt"); assert_eq!(strip_timestamp("a/sp ace.txt\t"), "a/sp ace.txt"); assert_eq!(strip_timestamp("a/tab\tb.txt"), "a/tab\tb.txt"); @@ -6135,8 +6220,7 @@ diff --git a/b.rs b/b.rs ); // Inside an mbox message the strict `..:` shape is a fact // too: `format-patch --submodule=log` puts it where a section goes. - let patch = - include_str!("../../../tests/fixtures/diff/git_format_patch_submodule_raw.txt"); + let patch = include_str!("../../../tests/fixtures/diff/git_format_patch_submodule_raw.txt"); let out = condense_unified_diff_strict(patch).expect("must parse"); assert!( out.contains("[file] sub (submodule, e1269cf..4ef7e41)"), @@ -6166,8 +6250,7 @@ diff --git a/b.rs b/b.rs // while file Y is a regular file`, a fifo line) may come BEFORE the // first `Only in` / `Files` line, and `-q` streams never carry an // echo, so those arms are the only thing that can settle it. - let dir_vs_file = - include_str!("../../../tests/fixtures/diff/diff_ru_dir_vs_file_raw.txt"); + let dir_vs_file = include_str!("../../../tests/fixtures/diff/diff_ru_dir_vs_file_raw.txt"); assert!(condense_unified_diff_strict(dir_vs_file).is_none()); let fifo_first = include_str!("../../../tests/fixtures/diff/diff_rq_fifo_first_raw.txt"); assert!(condense_unified_diff_strict(fifo_first).is_none()); @@ -6279,8 +6362,14 @@ diff --git a/b.rs b/b.rs // U+FFFD spelling. git's C-quoted form is kept whole instead. let fixture = include_str!("../../../tests/fixtures/diff/git_diff_latin1_raw.txt"); let out = condense_unified_diff_strict(fixture).expect("must parse"); - assert!(out.contains("[file] \"caf\\350.txt\" (+1 -1)"), "got:\n{out}"); - assert!(out.contains("[file] \"caf\\351.txt\" (+1 -1)"), "got:\n{out}"); + assert!( + out.contains("[file] \"caf\\350.txt\" (+1 -1)"), + "got:\n{out}" + ); + assert!( + out.contains("[file] \"caf\\351.txt\" (+1 -1)"), + "got:\n{out}" + ); assert!(!out.contains('\u{FFFD}'), "got:\n{out}"); } @@ -6346,8 +6435,7 @@ diff --git a/b.rs b/b.rs assert!(condense_unified_diff_strict(lost).is_none()); // `format-patch --interdiff`: the interdiff block is indented and // stays prose; `diff -u --label OLD --label NEW` names by label. - let inter = - include_str!("../../../tests/fixtures/diff/git_format_patch_interdiff_raw.txt"); + let inter = include_str!("../../../tests/fixtures/diff/git_format_patch_interdiff_raw.txt"); let out = condense_unified_diff_strict(inter).expect("must parse"); assert_eq!(out, "[file] v.txt (new file) (+1 -0)\n+v2"); let label = include_str!("../../../tests/fixtures/diff/diff_u_label_raw.txt"); @@ -6400,8 +6488,7 @@ diff --git a/b.rs b/b.rs // is bare, so `e: y: only.txt` is ambiguous on its own; the echo, // the header pair and the `Binary files` line already named the // roots, and the split follows them. - let fixture = - include_str!("../../../tests/fixtures/diff/diff_ru_310_colon_dirs_raw.txt"); + let fixture = include_str!("../../../tests/fixtures/diff/diff_ru_310_colon_dirs_raw.txt"); let out = condense_unified_diff_strict(fixture).expect("must parse"); for label in [ "[file] e: y/b.bin (binary)", @@ -6440,7 +6527,10 @@ diff --git a/b.rs b/b.rs "got:\n{out}" ); assert!(!out.contains("[file] nl "), "phantom entry:\n{out}"); - assert!(!out.contains("[file] \"b/nl"), "prefix left inside the quotes:\n{out}"); + assert!( + !out.contains("[file] \"b/nl"), + "prefix left inside the quotes:\n{out}" + ); } #[test] @@ -6449,20 +6539,34 @@ diff --git a/b.rs b/b.rs // name, a newline name, a `"`-leading name and a backslash name: // the retained quoted spelling never carries the prefix inside the // quotes, so `rename to` and the header pair agree. - let fixture = - include_str!("../../../tests/fixtures/diff/git_log_p_quoted_renames_raw.txt"); + let fixture = include_str!("../../../tests/fixtures/diff/git_log_p_quoted_renames_raw.txt"); let out = condense_unified_diff_strict(fixture).expect("must parse"); assert!( out.contains("(renamed from \"caf\\351.txt\")"), "got:\n{out}" ); - assert!(out.contains("[file] \"caf\\351.txt\" (+1 -1)"), "got:\n{out}"); + assert!( + out.contains("[file] \"caf\\351.txt\" (+1 -1)"), + "got:\n{out}" + ); assert!(out.contains("(renamed from \"nl\\nx.txt\")"), "got:\n{out}"); - assert!(out.contains("[file] back\\slash2.txt (renamed from back\\slash.txt)"), "got:\n{out}"); + assert!( + out.contains("[file] back\\slash2.txt (renamed from back\\slash.txt)"), + "got:\n{out}" + ); // The file that really lives under `b/` keeps exactly one `b/`. - assert!(out.contains("[file] \"b/caf\\351.txt\" (+1 -1)"), "got:\n{out}"); - assert!(!out.contains("\"b/b/"), "prefix left inside the quotes:\n{out}"); - assert!(!out.contains("\"a/"), "prefix left inside the quotes:\n{out}"); + assert!( + out.contains("[file] \"b/caf\\351.txt\" (+1 -1)"), + "got:\n{out}" + ); + assert!( + !out.contains("\"b/b/"), + "prefix left inside the quotes:\n{out}" + ); + assert!( + !out.contains("\"a/"), + "prefix left inside the quotes:\n{out}" + ); } #[test] @@ -6474,14 +6578,18 @@ diff --git a/b.rs b/b.rs include_str!("../../../tests/fixtures/diff/git_format_patch_no_stat_cover_raw.txt"); assert_eq!( condense_unified_diff_strict(no_stat).as_deref(), - Some("[file] bin.dat (binary)\n[file] dst.txt (renamed from src.txt) (+1 -0)\n+three\n[file] f.txt (+1 -1)\n-b\n+B\n[file] f.txt (+1 -1)\n-c\n+C") + Some( + "[file] bin.dat (binary)\n[file] dst.txt (renamed from src.txt) (+1 -0)\n+three\n[file] f.txt (+1 -1)\n-b\n+B\n[file] f.txt (+1 -1)\n-c\n+C" + ) ); // `git diff --cached --color=always`: git colours the marker and // the content separately; content lines keep their own bytes. let color = include_str!("../../../tests/fixtures/diff/git_diff_color_raw.txt"); assert_eq!( condense_unified_diff_strict(color).as_deref(), - Some("[file] bin.dat (binary)\n[file] dst.txt (renamed from src.txt) (+1 -0)\n\u{1b}[32m+\u{1b}[m\u{1b}[32mthree\u{1b}[m\n[file] f.txt (+1 -1)\n\u{1b}[31m-b\u{1b}[m\n\u{1b}[32m+\u{1b}[m\u{1b}[32mB\u{1b}[m") + Some( + "[file] bin.dat (binary)\n[file] dst.txt (renamed from src.txt) (+1 -0)\n\u{1b}[32m+\u{1b}[m\u{1b}[32mthree\u{1b}[m\n[file] f.txt (+1 -1)\n\u{1b}[31m-b\u{1b}[m\n\u{1b}[32m+\u{1b}[m\u{1b}[32mB\u{1b}[m" + ) ); // `git diff --cached -M --no-prefix`: `diff --git src.txt dst.txt` // shares no tail; `rename to` names it and the pair may not. @@ -6495,7 +6603,9 @@ diff --git a/b.rs b/b.rs let sd = include_str!("../../../tests/fixtures/diff/git_diff_submodule_diff_raw.txt"); assert_eq!( condense_unified_diff_strict(sd).as_deref(), - Some("[file] o.txt (+1 -1)\n-k\n+k2\n[file] sub (submodule, 0641fef..19e2e29)\n[file] sub/s.txt (+1 -1)\n-s\n+s2\n[file] sub/sb.bin (binary)") + Some( + "[file] o.txt (+1 -1)\n-k\n+k2\n[file] sub (submodule, 0641fef..19e2e29)\n[file] sub/s.txt (+1 -1)\n-s\n+s2\n[file] sub/sb.bin (binary)" + ) ); // `git log --numstat -p`: `--b.bin` before the diff. let ns = include_str!("../../../tests/fixtures/diff/git_log_numstat_p_raw.txt"); @@ -6513,7 +6623,9 @@ diff --git a/b.rs b/b.rs let fr = include_str!("../../../tests/fixtures/diff/diff_u_fr_no_newline_raw.txt"); assert_eq!( condense_unified_diff_strict(fr).as_deref(), - Some("[file] n2 (+1 -1)\n-x\n\\ Pas de fin de ligne à la fin du fichier\n+y\n\\ Pas de fin de ligne à la fin du fichier") + Some( + "[file] n2 (+1 -1)\n-x\n\\ Pas de fin de ligne à la fin du fichier\n+y\n\\ Pas de fin de ligne à la fin du fichier" + ) ); // A git binary section followed by another producer's header pair // (two streams concatenated): the pair opens its own section @@ -6544,7 +6656,10 @@ diff --git a/b.rs b/b.rs let fake_stat = "From 0e7632a01b00c70cbc9dafcf1f23c71fa6b10de1 Mon Sep 17 00:00:00 2001\nSubject: [PATCH] x\n\n rows | 3\n---\n g | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/g b/g\n--- a/g\n+++ b/g\n@@ -1 +1 @@\n-p\n+q\n-- \n2.54.0\n"; let out = condense_unified_diff_strict(fake_stat).expect("noise, not raw"); assert!(out.contains("[file] g (+1 -1)"), "got:\n{out}"); - assert!(condense_unified_diff_strict(&fake_stat.replace(" rows | 3\n", " rows | 3\n-lost\n")).is_none()); + assert!( + condense_unified_diff_strict(&fake_stat.replace(" rows | 3\n", " rows | 3\n-lost\n")) + .is_none() + ); // Rule 8: a bodyless `--no-stat` region whose prose wraps a short // option to column 0 falls back raw. let short = "From aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Mon Sep 17 00:00:00 2001\nSubject: [PATCH 1/2] one\n\nPass\n-p\nto it.\n-- \n2.54.0\n\nFrom bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb Mon Sep 17 00:00:00 2001\nSubject: [PATCH 2/2] two\n\ndiff --git a/y.txt b/y.txt\n--- a/y.txt\n+++ b/y.txt\n@@ -1 +1 @@\n-p\n+q\n-- \n2.54.0\n"; @@ -6601,7 +6716,11 @@ diff --git a/b.rs b/b.rs condense_unified_diff_strict(fixture).is_none(), "{name}: must fall back raw" ); - assert_eq!(condense_unified_diff(fixture), fixture, "{name}: not byte-exact"); + assert_eq!( + condense_unified_diff(fixture), + fixture, + "{name}: not byte-exact" + ); } } @@ -6691,7 +6810,8 @@ diff --git a/b.rs b/b.rs // Reproducer 12 (first half): `trim_start_matches("b/")` stripped // repeatedly, so `b/b/x.rs` (a file in a literal `b/` directory) // became `x.rs`. - let diff = "diff --git a/b/x.rs b/b/x.rs\n--- a/b/x.rs\n+++ b/b/x.rs\n@@ -1 +1 @@\n-old\n+new\n"; + let diff = + "diff --git a/b/x.rs b/b/x.rs\n--- a/b/x.rs\n+++ b/b/x.rs\n@@ -1 +1 @@\n-old\n+new\n"; let out = condense_unified_diff(diff); assert!(out.contains("[file] b/x.rs (+1 -1)"), "got:\n{out}"); } @@ -6715,7 +6835,10 @@ diff --git a/b.rs b/b.rs "[file] b/x.rs (+1 -1)", "[file] b/z.rs (new file) (+1 -0)", ] { - assert!(out.lines().any(|l| l == want), "missing {want:?} in:\n{out}"); + assert!( + out.lines().any(|l| l == want), + "missing {want:?} in:\n{out}" + ); } // A prefixed pair with no `diff --git` line still strips once, an // unprefixed pair (`diff -u` of two files in a `b/` directory) @@ -6723,7 +6846,8 @@ diff --git a/b.rs b/b.rs // is still two distinct halves. let bare_prefixed = "--- a/b/x.rs\n+++ b/b/x.rs\n@@ -1 +1 @@\n-old\n+new\n"; assert!(condense_unified_diff(bare_prefixed).contains("[file] b/x.rs (+1 -1)")); - let bare_plain = "--- b/x.rs\t2026-01-01\n+++ b/x.rs\t2026-01-02\n@@ -1 +1 @@\n-old\n+new\n"; + let bare_plain = + "--- b/x.rs\t2026-01-01\n+++ b/x.rs\t2026-01-02\n@@ -1 +1 @@\n-old\n+new\n"; assert!(condense_unified_diff(bare_plain).contains("[file] b/x.rs (+1 -1)")); let spaced = "diff --git a/foo bar b/foo bar\n--- a/foo bar\t\n+++ b/foo bar\t\n@@ -1 +1 @@\n-old\n+new\n"; assert!(condense_unified_diff(spaced).contains("[file] foo bar (+1 -1)")); @@ -6807,9 +6931,10 @@ diff --git a/b.rs b/b.rs " {a => b}/file.txt | 2 +-", " a | b.txt | 2 +-", ] { - let lost = base - .replace("STAT", stat) - .replace("\ndiff --git a/x.txt b/x.txt\n--- a/x.txt\n+++ b/x.txt\n@@ -1 +1 @@\n", "\n"); + let lost = base.replace("STAT", stat).replace( + "\ndiff --git a/x.txt b/x.txt\n--- a/x.txt\n+++ b/x.txt\n@@ -1 +1 @@\n", + "\n", + ); assert!( condense_unified_diff_strict(&lost).is_none(), "marked line past the diffstat `{stat}` was dropped" @@ -7190,12 +7315,17 @@ diff --git a/b.rs b/b.rs } } - let mut stream = a.iter().enumerate().filter(|(i, _)| !removed[*i]).map(|(i, l)| { - rewritten[i].clone().unwrap_or_else(|| (*l).to_string()) - }); + let mut stream = a + .iter() + .enumerate() + .filter(|(i, _)| !removed[*i]) + .map(|(i, l)| rewritten[i].clone().unwrap_or_else(|| (*l).to_string())); let out: Vec = fixed .into_iter() - .map(|slot| slot.or_else(|| stream.next()).expect("listing shorter than file2")) + .map(|slot| { + slot.or_else(|| stream.next()) + .expect("listing shorter than file2") + }) .collect(); assert!(stream.next().is_none(), "listing longer than file2"); out @@ -7232,7 +7362,10 @@ diff --git a/b.rs b/b.rs "listing misstates file2:\n{listing}\nfile1: {a:?}\nfile2: {b:?}" ); } - assert!(crossed > 100, "the generator must exercise crossings, saw {crossed}"); + assert!( + crossed > 100, + "the generator must exercise crossings, saw {crossed}" + ); } #[test] diff --git a/src/cmds/git/gh_cmd.rs b/src/cmds/git/gh_cmd.rs index d9a4a6b70a..0f52c826f5 100644 --- a/src/cmds/git/gh_cmd.rs +++ b/src/cmds/git/gh_cmd.rs @@ -267,7 +267,13 @@ fn format_pr_list(json: &Value, ultra_compact: bool) -> String { let state = pr["state"].as_str().unwrap_or("???"); let author = pr["author"]["login"].as_str().unwrap_or("???"); let icon = state_icon(state, ultra_compact); - format!(" {} #{} {} ({})", icon, number, truncate(title, 60), author) + format!( + " {} #{} {} ({})", + icon, + number, + truncate(title, 60), + author + ) }) .collect(); const MAX_LIST: usize = CAP_LIST; @@ -277,7 +283,8 @@ fn format_pr_list(json: &Value, ultra_compact: bool) -> String { if all_lines.len() > MAX_LIST { out.push_str(&format!(" … +{} more\n", all_lines.len() - MAX_LIST)); let all_text = all_lines.join("\n"); - if let Some(hint) = crate::core::tee::force_tee_tail_hint(&all_text, "gh-prs", MAX_LIST + 1) { + if let Some(hint) = crate::core::tee::force_tee_tail_hint(&all_text, "gh-prs", MAX_LIST + 1) + { out.push_str(&format!(" {}\n", hint)); } } @@ -693,7 +700,9 @@ fn format_issue_list(json: &Value, ultra_compact: bool) -> String { if all_lines.len() > MAX_LIST { out.push_str(&format!(" … +{} more\n", all_lines.len() - MAX_LIST)); let all_text = all_lines.join("\n"); - if let Some(hint) = crate::core::tee::force_tee_tail_hint(&all_text, "gh-issues", MAX_LIST + 1) { + if let Some(hint) = + crate::core::tee::force_tee_tail_hint(&all_text, "gh-issues", MAX_LIST + 1) + { out.push_str(&format!(" {}\n", hint)); } } @@ -1080,7 +1089,7 @@ mod tests { // Emoji: 🚀 = 4 bytes, 1 char assert_eq!(truncate("🚀🎉🔥abc", 6), "🚀🎉🔥abc"); // 6 chars, fits assert_eq!(truncate("🚀🎉🔥abcdef", 8), "🚀🎉🔥ab..."); // 10 chars > 8 - // Edge case: all multibyte + // Edge case: all multibyte assert_eq!(truncate("🚀🎉🔥🌟🎯", 5), "🚀🎉🔥🌟🎯"); // exact fit assert_eq!(truncate("🚀🎉🔥🌟🎯x", 5), "🚀🎉..."); // 6 chars > 5 } diff --git a/src/cmds/git/git.rs b/src/cmds/git/git.rs index d7ae7055de..2904e1d0a6 100644 --- a/src/cmds/git/git.rs +++ b/src/cmds/git/git.rs @@ -1,14 +1,14 @@ //! Filters git output — log, status, diff, and more — keeping just the essential info. use crate::core::arg_tokenizer::{ - self, is_digit_run, Attachment, Dialect, Token, TokenKind, ValueSpec, + self, Attachment, Dialect, Token, TokenKind, ValueSpec, is_digit_run, }; use crate::core::args_utils; use crate::core::guard::never_worse; use crate::core::runner::{self, RunOptions}; use crate::core::stream::{ - self, exec_capture, exec_capture_stdin, CaptureResult, FilterMode, LineHandler, - LineStreamFilter, StdinMode, + self, CaptureResult, FilterMode, LineHandler, LineStreamFilter, StdinMode, exec_capture, + exec_capture_stdin, }; use crate::core::tracking; use crate::core::truncate::{CAP_LIST, CAP_WARNINGS}; @@ -177,8 +177,7 @@ fn body_is_suppressed(tokens: &[Token<'_>]) -> bool { // side -- `--quiet -p` and `-p --quiet` both print the diff -- and only suppresses when // nothing else asked for output. Treating it as a third spelling of `-s` dropped a patch // the user had asked for. - suppressed - || (tokens.iter().any(is_quiet_flag) && !tokens.iter().any(requests_patch_output)) + suppressed || (tokens.iter().any(is_quiet_flag) && !tokens.iter().any(requests_patch_output)) } /// `--quiet`, which is `--exit-code`'s companion rather than a shape flag. Only `run_diff` @@ -521,13 +520,19 @@ fn run_show( let label = format!("git show {}", args.join(" ")); let rtk_label = format!("rtk git show {}", args.join(" ")); if !result.success() { - eprint!("{}", crate::core::utils::decode_process_output(&result.stderr)); + eprint!( + "{}", + crate::core::utils::decode_process_output(&result.stderr) + ); return Ok(result.exit_code); } // git can warn on stderr (e.g. CRLF / autocrlf notices) while still exiting 0; // surface it instead of swallowing it just because the command succeeded. if !result.stderr.is_empty() { - eprint!("{}", crate::core::utils::decode_process_output(&result.stderr)); + eprint!( + "{}", + crate::core::utils::decode_process_output(&result.stderr) + ); } // Fidelity invariant: "byte-identical unless we successfully windowed." // @@ -1719,49 +1724,49 @@ fn run_log( fn shared_long_takes_value(name: &str) -> bool { matches!( name, - "after" - | "anchored" - | "author" - | "before" - | "color-moved-ws" - | "committer" - | "date" - | "decorate-refs" - | "decorate-refs-exclude" - | "diff-algorithm" - | "diff-filter" - | "diff-merges" - | "dst-prefix" - | "encoding" - | "exclude" - | "find-object" - | "glob" - | "grep" - | "grep-reflog" - | "ignore-matching-lines" - | "inter-hunk-context" - | "line-prefix" - | "max-age" - | "max-count" - | "max-depth" - | "min-age" - | "output" - | "output-indicator-context" - | "output-indicator-new" - | "output-indicator-old" - | "rotate-to" - | "since" - | "since-as-filter" - | "skip" - | "skip-to" - | "src-prefix" - | "stat-count" - | "stat-graph-width" - | "stat-name-width" - | "stat-width" - | "until" - | "word-diff-regex" - | "ws-error-highlight" + "after" + | "anchored" + | "author" + | "before" + | "color-moved-ws" + | "committer" + | "date" + | "decorate-refs" + | "decorate-refs-exclude" + | "diff-algorithm" + | "diff-filter" + | "diff-merges" + | "dst-prefix" + | "encoding" + | "exclude" + | "find-object" + | "glob" + | "grep" + | "grep-reflog" + | "ignore-matching-lines" + | "inter-hunk-context" + | "line-prefix" + | "max-age" + | "max-count" + | "max-depth" + | "min-age" + | "output" + | "output-indicator-context" + | "output-indicator-new" + | "output-indicator-old" + | "rotate-to" + | "since" + | "since-as-filter" + | "skip" + | "skip-to" + | "src-prefix" + | "stat-count" + | "stat-graph-width" + | "stat-name-width" + | "stat-width" + | "until" + | "word-diff-regex" + | "ws-error-highlight" ) } @@ -2623,11 +2628,7 @@ fn quoted_suffix<'a>(line: &'a str, prefix: &str) -> Option<&'a str> { } fn pluralize<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str { - if count == 1 { - singular - } else { - plural - } + if count == 1 { singular } else { plural } } fn filter_checkout_failure(raw: &str) -> String { @@ -3485,7 +3486,11 @@ fn run_worktree(args: &[String], verbose: u8, global_args: &[String]) -> Result< let result = exec_capture(&mut cmd).context("Failed to run git worktree")?; let combined = result.combined(); - let said = if asked_for_report { combined.trim() } else { "" }; + let said = if asked_for_report { + combined.trim() + } else { + "" + }; // Track what RTK prints, not what git said: on success that is the report or "ok". let msg = if !result.success() { combined.as_str() @@ -3807,7 +3812,11 @@ mod tests { let removed: Vec<&str> = result.lines().filter(|l| l.starts_with('-')).collect(); let added: Vec<&str> = result.lines().filter(|l| l.starts_with('+')).collect(); - assert_eq!(removed, vec!["-DELETED_A", "-DELETED_B"], "`^-` must anchor"); + assert_eq!( + removed, + vec!["-DELETED_A", "-DELETED_B"], + "`^-` must anchor" + ); assert_eq!(added, vec!["+ADDED"], "`^+` must anchor"); // rtk's own tally stays indented so these same greps never count it as @@ -3848,7 +3857,11 @@ mod tests { "added `++i;` must survive, got:\n{}", result ); - assert!(result.contains(" +1 -1"), "tally must count both, got:\n{}", result); + assert!( + result.contains(" +1 -1"), + "tally must count both, got:\n{}", + result + ); } #[test] @@ -3867,7 +3880,11 @@ mod tests { let ctx = result.lines().filter(|l| l.starts_with(" ctx")).count(); let dels = result.lines().filter(|l| l.starts_with("-del")).count(); assert_eq!(ctx, 3, "leading context is capped, got:\n{}", result); - assert_eq!(dels, 100, "every change must still be shown, got:\n{}", result); + assert_eq!( + dels, 100, + "every change must still be shown, got:\n{}", + result + ); assert!( !result.contains("truncated"), "no change was dropped, got:\n{}", @@ -3971,7 +3988,11 @@ mod tests { "mbox envelope must stay out of the body, got:\n{}", result ); - assert!(result.contains(" +1 -1"), "tally counts real changes only, got:\n{}", result); + assert!( + result.contains(" +1 -1"), + "tally counts real changes only, got:\n{}", + result + ); } #[test] @@ -4018,10 +4039,7 @@ mod tests { diff_header_path(r#"diff --git "a/été.txt" "b/été.txt""#), r"été.txt" ); - assert_eq!( - diff_header_path(r#"diff --cc "été.txt""#), - r"été.txt" - ); + assert_eq!(diff_header_path(r#"diff --cc "été.txt""#), r"été.txt"); // A rename quotes each side on its own. assert_eq!( diff_header_path(r#"diff --git a/plain.txt "b/ét.txt""#), @@ -4132,7 +4150,10 @@ mod tests { fn test_compact_diff_extra_range_does_not_strand_a_hunk() { // With the third range untracked, `--x` left it at 1 forever, so the // hunk never closed and the mbox signature became its content. - let out = compact_diff("diff --cc f\n@@@ -1 -1 -1 +0,0 @@@\n--x\n-- \n2.40.0\n", 100); + let out = compact_diff( + "diff --cc f\n@@@ -1 -1 -1 +0,0 @@@\n--x\n-- \n2.40.0\n", + 100, + ); assert!(out.contains("--x"), "got:\n{}", out); assert!(!out.contains("2.40.0"), "got:\n{}", out); assert!(!out.contains("-- "), "got:\n{}", out); @@ -4168,7 +4189,10 @@ mod tests { assert_eq!(diff_header_path("diff --git i/f.txt w/f.txt"), "f.txt"); // A rename's halves disagree past their first component, so the ` b/` // split still names the destination. - assert_eq!(diff_header_path("diff --git a/old.txt b/new.txt"), "new.txt"); + assert_eq!( + diff_header_path("diff --git a/old.txt b/new.txt"), + "new.txt" + ); } #[test] @@ -4183,7 +4207,10 @@ mod tests { #[test] fn test_parse_hunk_header_counts() { let h = parse_hunk_header("@@ -10,3 +10,4 @@ fn ctx() {").expect("unified header"); - assert_eq!((h.parents.as_slice(), h.new, h.prefix_width), (&[3][..], 4, 1)); + assert_eq!( + (h.parents.as_slice(), h.new, h.prefix_width), + (&[3][..], 4, 1) + ); // Omitted counts mean one line. let h = parse_hunk_header("@@ -1 +1 @@").expect("single-line header"); @@ -4275,8 +4302,7 @@ mod tests { } diff }; - let count_changes = - |out: &str| out.lines().filter(|l| l.starts_with("-del")).count(); + let count_changes = |out: &str| out.lines().filter(|l| l.starts_with("-del")).count(); let without = compact_diff(&build(false), 500); let with = compact_diff(&build(true), 500); @@ -4472,7 +4498,10 @@ mod tests { assert!(blob_candidates(&args).is_empty()); // A real blob still routes as a blob even with a preceding value flag. let args = show_args(&["-S", "needle", "HEAD:src/main.rs"]); - assert_eq!(blob_candidates(&args), vec![&"HEAD:src/main.rs".to_string()]); + assert_eq!( + blob_candidates(&args), + vec![&"HEAD:src/main.rs".to_string()] + ); // A value operand that IS a valid-looking blob (`-S 'HEAD:real'`) is the pickaxe // value, not an object: the walker excludes it, so only the trailing commit // remains and nothing is offered as a windowing candidate. @@ -4626,8 +4655,7 @@ mod tests { s.push_str(&format!("line number {i} with a bit of content here\n")); } s.pop(); // drop the final newline - let (head, remaining, offset) = - blob_truncation(&s, MAX_BLOB_BYTES).expect("should window"); + let (head, remaining, offset) = blob_truncation(&s, MAX_BLOB_BYTES).expect("should window"); assert_eq!(offset, head.lines().count() + 1); assert_eq!(remaining, s.lines().count() - head.lines().count()); } @@ -4638,14 +4666,17 @@ mod tests { // so it works at any size (N2) and quotes a path with a space for safe paste. let mut s = String::new(); for i in 0..2000 { - s.push_str(&format!("line {i} with enough content to exceed the byte budget\n")); + s.push_str(&format!( + "line {i} with enough content to exceed the byte budget\n" + )); } - let offset = blob_truncation(&s, MAX_BLOB_BYTES).expect("should window").2; + let offset = blob_truncation(&s, MAX_BLOB_BYTES) + .expect("should window") + .2; let out = compact_blob_show(&s, "HEAD:my dir/big.lock", &[]); assert!(out.len() < s.len(), "windowing must shrink the output"); - let expected = format!( - "[see remaining: git show 'HEAD:my dir/big.lock' | tail -n +{offset}]" - ); + let expected = + format!("[see remaining: git show 'HEAD:my dir/big.lock' | tail -n +{offset}]"); assert!(out.contains(&expected), "hint missing/unquoted: {out:?}"); assert!(!out.contains("tail -n +0")); @@ -4654,7 +4685,10 @@ mod tests { let out_q = compact_blob_show(&s, "HEAD:it's/a.lock", &[]); let expected_q = format!("[see remaining: git show 'HEAD:it'\\''s/a.lock' | tail -n +{offset}]"); - assert!(out_q.contains(&expected_q), "single-quote path unescaped: {out_q:?}"); + assert!( + out_q.contains(&expected_q), + "single-quote path unescaped: {out_q:?}" + ); } #[test] @@ -4671,9 +4705,13 @@ mod tests { // each shell-quoted, so the recovery command targets the same repo. let mut s = String::new(); for i in 0..2000 { - s.push_str(&format!("line {i} with enough content to exceed the byte budget\n")); + s.push_str(&format!( + "line {i} with enough content to exceed the byte budget\n" + )); } - let offset = blob_truncation(&s, MAX_BLOB_BYTES).expect("should window").2; + let offset = blob_truncation(&s, MAX_BLOB_BYTES) + .expect("should window") + .2; let globals = vec![ "-C".to_string(), "/tmp/my repo".to_string(), @@ -4936,7 +4974,11 @@ mod tests { assert!(!report(&["add", "/tmp/w", "-b", "topic"])); assert!(!report(&["prune"])); assert!(!report(&["remove", "/tmp/w"])); - for spelling in [&["prune", "-n"][..], &["prune", "--dry-run"][..], &["prune", "-v"][..]] { + for spelling in [ + &["prune", "-n"][..], + &["prune", "--dry-run"][..], + &["prune", "-v"][..], + ] { assert!(report(spelling), "{spelling:?} asks for a report"); } // A worktree path spelled like the flag is a path, not a request. @@ -5265,7 +5307,8 @@ A added.rs let args = vec![flag.to_string()]; let tokens = tokenize_git_log_args(&args); assert!( - !tokens.iter().any(|t| show_wants_raw_shape(t, &tokens)) && !tokens.iter().any(|t| diff_wants_raw_shape(t, &tokens)), + !tokens.iter().any(|t| show_wants_raw_shape(t, &tokens)) + && !tokens.iter().any(|t| diff_wants_raw_shape(t, &tokens)), "{flag} must stay on diff/show's compact path, not the raw passthrough path" ); } @@ -5288,7 +5331,8 @@ A added.rs let args = vec![flag.to_string()]; let tokens = tokenize_git_log_args(&args); assert!( - tokens.iter().any(|t| show_wants_raw_shape(t, &tokens)) && tokens.iter().any(|t| diff_wants_raw_shape(t, &tokens)), + tokens.iter().any(|t| show_wants_raw_shape(t, &tokens)) + && tokens.iter().any(|t| diff_wants_raw_shape(t, &tokens)), "{flag} changes output shape and should still request the raw passthrough path" ); } @@ -5429,7 +5473,10 @@ A added.rs assert_eq!(rebuild(&["-wU2"]), vec!["-w"]); assert_eq!(rebuild(&["-p"]), Vec::::new()); // Nothing to strip: flags and positionals pass through untouched. - assert_eq!(rebuild(&["-w", "HEAD~1", "f.txt"]), vec!["-w", "HEAD~1", "f.txt"]); + assert_eq!( + rebuild(&["-w", "HEAD~1", "f.txt"]), + vec!["-w", "HEAD~1", "f.txt"] + ); assert_eq!(rebuild(&["--author", "-p"]), vec!["--author", "-p"]); } @@ -5445,7 +5492,10 @@ A added.rs for flag in ["-p", "-u", "--patch", "-U5", "-W", "--function-context"] { let args = vec![flag.to_string()]; let tokens = tokenize_git_diff_args(&args); - assert!(args_without_patch_shape(&args, &tokens).is_empty(), "{flag}"); + assert!( + args_without_patch_shape(&args, &tokens).is_empty(), + "{flag}" + ); } // A flag that only tunes the diff must survive into the header. let args = vec!["-w".to_string()]; @@ -5480,7 +5530,10 @@ A added.rs // `-s`/`--no-patch` compact. Claiming it for show raw-passed that header. let args = vec!["--quiet".to_string()]; let tokens = tokenize_git_diff_args(&args); - assert!(tokens.iter().any(|t| diff_wants_raw_shape(t, &tokens)), "diff needs the exit code"); + assert!( + tokens.iter().any(|t| diff_wants_raw_shape(t, &tokens)), + "diff needs the exit code" + ); assert!( !tokens.iter().any(|t| show_wants_raw_shape(t, &tokens)), "show's --quiet is -s, which suppresses_diff_body renders as the summary" @@ -5493,7 +5546,10 @@ A added.rs let built = |args: &[&str]| { let owned: Vec = args.iter().map(|a| a.to_string()).collect(); let tokens = tokenize_git_log_args(&owned); - assert!(tokens.iter().any(|t| log_wants_raw_shape(t, &tokens)), "{args:?} must route raw"); + assert!( + tokens.iter().any(|t| log_wants_raw_shape(t, &tokens)), + "{args:?} must route raw" + ); raw_log_passthrough_args(&owned, raw_log_is_capped(&tokens)) .iter() .map(|a| a.to_string_lossy().into_owned()) @@ -5565,11 +5621,7 @@ A added.rs for (args, expected) in cases { let owned: Vec = args.iter().map(|a| a.to_string()).collect(); let tokens = tokenize_git_diff_args(&owned); - assert_eq!( - body_is_suppressed(&tokens), - expected, - "{args:?}" - ); + assert_eq!(body_is_suppressed(&tokens), expected, "{args:?}"); } } @@ -5637,22 +5689,46 @@ A added.rs for fmt in ["c", "cc", "combined", "dense-combined"] { // Combined: raw for both -- compact_diff reads the two marker columns as one. - assert_eq!(route(&[&format!("--diff-merges={fmt}")]), (true, true), "={fmt}"); + assert_eq!( + route(&[&format!("--diff-merges={fmt}")]), + (true, true), + "={fmt}" + ); // git takes the value as the next token too, and that spelling must route alike. - assert_eq!(route(&["--diff-merges", fmt]), (true, true), "separate {fmt}"); + assert_eq!( + route(&["--diff-merges", fmt]), + (true, true), + "separate {fmt}" + ); } for fmt in ["1", "first-parent", "m", "on", "r", "remerge", "separate"] { // A single-column patch: log still cannot represent one, show's default already is // one, so the two subcommands disagree here on purpose. - assert_eq!(route(&[&format!("--diff-merges={fmt}")]), (true, false), "={fmt}"); - assert_eq!(route(&["--diff-merges", fmt]), (true, false), "separate {fmt}"); + assert_eq!( + route(&[&format!("--diff-merges={fmt}")]), + (true, false), + "={fmt}" + ); + assert_eq!( + route(&["--diff-merges", fmt]), + (true, false), + "separate {fmt}" + ); } for fmt in ["none", "off"] { // No patch at all, so nothing to escape the compact path for. - assert_eq!(route(&[&format!("--diff-merges={fmt}")]), (false, false), "={fmt}"); - assert_eq!(route(&["--diff-merges", fmt]), (false, false), "separate {fmt}"); + assert_eq!( + route(&[&format!("--diff-merges={fmt}")]), + (false, false), + "={fmt}" + ); + assert_eq!( + route(&["--diff-merges", fmt]), + (false, false), + "separate {fmt}" + ); } // No value is a git error either way; RTK must not read it as a patch request. @@ -5671,13 +5747,19 @@ A added.rs tokens.iter().any(|t| show_wants_raw_shape(t, &tokens)), "{flag} must take show's raw route" ); - assert!(tokens.iter().any(|t| diff_wants_raw_shape(t, &tokens)), "{flag} for diff too"); + assert!( + tokens.iter().any(|t| diff_wants_raw_shape(t, &tokens)), + "{flag} for diff too" + ); } // The other short flags stay on the compact path: they only restate the default. for flag in ["-p", "-u", "-U3"] { let args = vec![flag.to_string()]; let tokens = tokenize_git_diff_args(&args); - assert!(!tokens.iter().any(|t| show_wants_raw_shape(t, &tokens)), "{flag}"); + assert!( + !tokens.iter().any(|t| show_wants_raw_shape(t, &tokens)), + "{flag}" + ); } } @@ -5734,7 +5816,10 @@ A added.rs // separator has to be ignored wherever it sits, not only when it is the only token. assert!(uses_compact_status_path(&[])); assert!(uses_compact_status_path(&["--".to_string()])); - assert!(uses_compact_status_path(&["-sb".to_string(), "--".to_string()])); + assert!(uses_compact_status_path(&[ + "-sb".to_string(), + "--".to_string() + ])); assert!(uses_compact_status_path(&[ "-s".to_string(), "-b".to_string(), @@ -5753,9 +5838,7 @@ A added.rs fn test_split_stash_region_keeps_the_boundary_out_of_the_subcommand() { // `git stash -- -p` is a pathspec, not interactive patch mode: the restored region // starts at the boundary, so nothing is taken as the subcommand. - let owned = |args: &[&str]| -> Vec { - args.iter().map(|a| a.to_string()).collect() - }; + let owned = |args: &[&str]| -> Vec { args.iter().map(|a| a.to_string()).collect() }; let (subcommand, rest) = split_stash_region(&owned(&["--", "-p"])); assert_eq!(subcommand, None); diff --git a/src/cmds/git/glab_cmd.rs b/src/cmds/git/glab_cmd.rs index 6de8199f3e..1fe0870270 100644 --- a/src/cmds/git/glab_cmd.rs +++ b/src/cmds/git/glab_cmd.rs @@ -334,7 +334,9 @@ fn format_mr_list(json: &Value, ultra_compact: bool) -> String { if all_lines.len() > MAX_LIST { filtered.push_str(&format!(" … +{} more\n", all_lines.len() - MAX_LIST)); let all_text = all_lines.join("\n"); - if let Some(hint) = crate::core::tee::force_tee_tail_hint(&all_text, "glab-mrs", MAX_LIST + 1) { + if let Some(hint) = + crate::core::tee::force_tee_tail_hint(&all_text, "glab-mrs", MAX_LIST + 1) + { filtered.push_str(&format!(" {}\n", hint)); } } @@ -567,7 +569,9 @@ fn format_issue_list(json: &Value, ultra_compact: bool) -> String { if all_lines.len() > MAX_LIST { filtered.push_str(&format!(" … +{} more\n", all_lines.len() - MAX_LIST)); let all_text = all_lines.join("\n"); - if let Some(hint) = crate::core::tee::force_tee_tail_hint(&all_text, "glab-issues", MAX_LIST + 1) { + if let Some(hint) = + crate::core::tee::force_tee_tail_hint(&all_text, "glab-issues", MAX_LIST + 1) + { filtered.push_str(&format!(" {}\n", hint)); } } diff --git a/src/cmds/git/gt_cmd.rs b/src/cmds/git/gt_cmd.rs index 296729256d..092a33fff2 100644 --- a/src/cmds/git/gt_cmd.rs +++ b/src/cmds/git/gt_cmd.rs @@ -2,7 +2,7 @@ use crate::core::stream::exec_capture; use crate::core::tracking; -use crate::core::truncate::{reduced, CAP_LIST}; +use crate::core::truncate::{CAP_LIST, reduced}; use crate::core::utils::{ok_confirmation, resolved_command, strip_ansi, truncate}; use anyhow::{Context, Result}; use regex::Regex; diff --git a/src/cmds/go/go_cmd.rs b/src/cmds/go/go_cmd.rs index 6d8c23a032..dd97aa8645 100644 --- a/src/cmds/go/go_cmd.rs +++ b/src/cmds/go/go_cmd.rs @@ -2,7 +2,7 @@ use crate::core::guard::never_worse; use crate::core::runner; -use crate::core::stream::{exec_capture, CaptureResult}; +use crate::core::stream::{CaptureResult, exec_capture}; use crate::core::tracking; use crate::core::truncate::CAP_ERRORS; use crate::core::utils::{resolved_command, truncate}; @@ -150,8 +150,8 @@ pub fn run_other(args: &[OsString], verbose: u8) -> Result { eprintln!("Running: go {} ...", subcommand); } - let captured = exec_capture(&mut cmd) - .with_context(|| format!("Failed to run go {}", subcommand))?; + let captured = + exec_capture(&mut cmd).with_context(|| format!("Failed to run go {}", subcommand))?; let raw = format!("{}\n{}", captured.stdout, captured.stderr); print!("{}", captured.stdout); @@ -592,9 +592,14 @@ fn filter_go_build_with_exit(output: &str, exit_code: i32) -> String { } if errors.len() > MAX_GO_BUILD_ERRORS { - result.push_str(&format!("\n… +{} more errors\n", errors.len() - MAX_GO_BUILD_ERRORS)); + result.push_str(&format!( + "\n… +{} more errors\n", + errors.len() - MAX_GO_BUILD_ERRORS + )); let all_errors = errors.join("\n"); - if let Some(hint) = crate::core::tee::force_tee_tail_hint(&all_errors, "go-build", MAX_GO_BUILD_ERRORS + 1) { + if let Some(hint) = + crate::core::tee::force_tee_tail_hint(&all_errors, "go-build", MAX_GO_BUILD_ERRORS + 1) + { result.push_str(&format!(" {}\n", hint)); } } @@ -715,9 +720,14 @@ fn filter_go_vet(output: &str) -> String { } if issues.len() > MAX_GO_VET_ISSUES { - result.push_str(&format!("\n… +{} more issues\n", issues.len() - MAX_GO_VET_ISSUES)); + result.push_str(&format!( + "\n… +{} more issues\n", + issues.len() - MAX_GO_VET_ISSUES + )); let all_issues = issues.join("\n"); - if let Some(hint) = crate::core::tee::force_tee_tail_hint(&all_issues, "go-vet", MAX_GO_VET_ISSUES + 1) { + if let Some(hint) = + crate::core::tee::force_tee_tail_hint(&all_issues, "go-vet", MAX_GO_VET_ISSUES + 1) + { result.push_str(&format!(" {}\n", hint)); } } diff --git a/src/cmds/go/golangci_cmd.rs b/src/cmds/go/golangci_cmd.rs index be9715ea6c..26e4a7428e 100644 --- a/src/cmds/go/golangci_cmd.rs +++ b/src/cmds/go/golangci_cmd.rs @@ -162,7 +162,11 @@ pub(crate) fn parse_major_version(version_output: &str) -> u32 { // would send it `--out-format=json`, a flag v2 removed. for word in version_output.split_whitespace() { let version = word.strip_prefix('v').unwrap_or(word); - if let Some(major) = version.split('.').next().and_then(|s| s.parse::().ok()) { + if let Some(major) = version + .split('.') + .next() + .and_then(|s| s.parse::().ok()) + { if version.contains('.') { return major; } @@ -531,7 +535,9 @@ mod tests { #[test] fn test_parse_version_v2_format() { assert_eq!( - parse_major_version("golangci-lint has version 2.10.0 built with go1.26.0 from 95dcb68a on 2026-02-17T13:05:51Z"), + parse_major_version( + "golangci-lint has version 2.10.0 built with go1.26.0 from 95dcb68a on 2026-02-17T13:05:51Z" + ), 2 ); } @@ -607,12 +613,7 @@ mod tests { // -c takes a separate-token value; its value ("foo.yml") must not be mistaken for // the subcommand. assert_eq!( - classify_invocation(&[ - "-c".into(), - "foo.yml".into(), - "run".into(), - "./...".into(), - ]), + classify_invocation(&["-c".into(), "foo.yml".into(), "run".into(), "./...".into(),]), Invocation::FilteredRun(RunInvocation { global_args: vec!["-c".into(), "foo.yml".into()], run_args: vec!["./...".into()], @@ -759,7 +760,10 @@ mod tests { "--disable".to_string(), "errcheck".to_string(), ])); - assert!(!has_output_flag(&["--timeout".to_string(), "30s".to_string()])); + assert!(!has_output_flag(&[ + "--timeout".to_string(), + "30s".to_string() + ])); } #[test] @@ -771,7 +775,10 @@ mod tests { assert_eq!(find_subcommand_index(&args), Some(1)); // Solo, it still takes its value -- that spelling golangci-lint does accept. - let args: Vec = ["-c", "cfg.yml", "run"].iter().map(|a| a.to_string()).collect(); + let args: Vec = ["-c", "cfg.yml", "run"] + .iter() + .map(|a| a.to_string()) + .collect(); assert_eq!(find_subcommand_index(&args), Some(2)); // And the attached spelling is untouched by the solo-only rule. @@ -785,7 +792,10 @@ mod tests { // unlinked Positional. let args = vec!["--out-format".to_string(), "json".to_string()]; let tokens = arg_tokenizer::tokenize_grammar(&args, &run_takes_value, Dialect::Posix); - assert!(tokens[0].linked.is_some(), "\"json\" must link to --out-format"); + assert!( + tokens[0].linked.is_some(), + "\"json\" must link to --out-format" + ); } #[test] @@ -958,7 +968,11 @@ mod tests { let filtered_tokens = estimate_tokens(&filtered) as f64; let savings = 100.0 - (filtered_tokens / raw_tokens * 100.0); - assert!(savings >= 60.0, "expected >=60% savings, got {:.1}%", savings); + assert!( + savings >= 60.0, + "expected >=60% savings, got {:.1}%", + savings + ); } /// The filter always has something to say about its input. Whether that is worth diff --git a/src/cmds/js/bun_cmd.rs b/src/cmds/js/bun_cmd.rs index 58e10452f2..10ee13183f 100644 --- a/src/cmds/js/bun_cmd.rs +++ b/src/cmds/js/bun_cmd.rs @@ -311,7 +311,8 @@ error: PackageNotFound - "nonexistent-pkg" not found in registry #[test] fn test_filter_bun_install_handles_remove() { - let output = "bun remove v1.2.20 (6ad208bc)\n- express@4.18.2\n1 package removed [7.00ms]\n"; + let output = + "bun remove v1.2.20 (6ad208bc)\n- express@4.18.2\n1 package removed [7.00ms]\n"; let result = filter_bun_pkg(output); assert!(!result.contains("bun remove v1.2.20")); assert!(result.contains("- express@4.18.2")); @@ -370,7 +371,10 @@ error: PackageNotFound - "nonexistent-pkg" not found in registry let raw = include_str!("../../../tests/fixtures/bun_pm_ls_all_raw.txt"); let out = filter_bun_pm_ls(raw); assert!(out.starts_with("68 deps"), "{out}"); - assert!(!out.contains('\u{251c}'), "tree glyphs must be stripped: {out}"); + assert!( + !out.contains('\u{251c}'), + "tree glyphs must be stripped: {out}" + ); let json = r#"{"express": {"version": "4.18.2"}}"#; let out = filter_bun_pm_ls(json); @@ -383,7 +387,8 @@ error: PackageNotFound - "nonexistent-pkg" not found in registry #[test] fn test_filter_bun_pm_ls_text_strips_ansi() { - let raw = "\x1b[31merror: No package.json was found\x1b[0m\n\x1b[2mnote: Run bun init\x1b[0m"; + let raw = + "\x1b[31merror: No package.json was found\x1b[0m\n\x1b[2mnote: Run bun init\x1b[0m"; let out = filter_bun_pm_ls_text(raw); assert!(!out.contains('\x1b'), "{out:?}"); assert!(out.contains("No package.json"), "{out}"); diff --git a/src/cmds/js/deno_cmd.rs b/src/cmds/js/deno_cmd.rs index 2c26d0d176..1b9c71ebfb 100644 --- a/src/cmds/js/deno_cmd.rs +++ b/src/cmds/js/deno_cmd.rs @@ -209,7 +209,12 @@ error: Module not found "https://deno.land/x/nonexistent/mod.ts" #[test] fn test_chose_output_format_detects_a_named_reporter() { - for a in ["--reporter=junit", "--reporter", "--junit-path", "--junit-path=r.xml"] { + for a in [ + "--reporter=junit", + "--reporter", + "--junit-path", + "--junit-path=r.xml", + ] { let args = vec![a.to_string()]; assert!(chose_output_format(&args), "{a}"); } @@ -219,4 +224,3 @@ error: Module not found "https://deno.land/x/nonexistent/mod.ts" } } } - diff --git a/src/cmds/js/lint_cmd.rs b/src/cmds/js/lint_cmd.rs index 451987d33e..5c5d2efab5 100644 --- a/src/cmds/js/lint_cmd.rs +++ b/src/cmds/js/lint_cmd.rs @@ -1,11 +1,11 @@ //! Filters ESLint and Biome linter output, grouping violations by rule. +use crate::cmds::python::sqlfluff_cmd; use crate::core::config; use crate::core::stream::exec_capture; use crate::core::tracking; use crate::core::truncate::{CAP_ERRORS, CAP_WARNINGS}; -use crate::core::utils::{resolved_command, tool_exec, truncate, MissingTool}; -use crate::cmds::python::sqlfluff_cmd; +use crate::core::utils::{MissingTool, resolved_command, tool_exec, truncate}; use crate::mypy_cmd; use crate::ruff_cmd; use anyhow::{Context, Result}; @@ -76,7 +76,10 @@ fn strip_pm_prefix(args: &[String]) -> usize { /// The package runner named at the front of the args, if any. `bunx eslint` /// and `pnpm exec eslint` both name one; a bare `exec` does not. fn named_runner(args: &[String], skip: usize) -> Option<&str> { - args[..skip].iter().map(String::as_str).find(|a| *a != "exec") + args[..skip] + .iter() + .map(String::as_str) + .find(|a| *a != "exec") } /// Detect the linter name from args (after stripping PM prefixes). @@ -422,7 +425,6 @@ fn filter_pylint_json(output: &str) -> String { result.push('\n'); } - // Show top symbols (rules) let mut symbol_counts: Vec<_> = by_symbol.iter().collect(); symbol_counts.sort_by(|a, b| b.1.cmp(a.1)); @@ -458,7 +460,10 @@ fn filter_pylint_json(output: &str) -> String { } if file_counts.len() > MAX_FILES { - result.push_str(&format!("\n… +{} more files\n", file_counts.len() - MAX_FILES)); + result.push_str(&format!( + "\n… +{} more files\n", + file_counts.len() - MAX_FILES + )); let all_file_lines = file_counts .iter() .map(|(file, count)| format!("{} ({} issues)", compact_path(file), count)) @@ -741,11 +746,17 @@ mod tests { #[test] fn test_named_runner_recovers_the_stripped_prefix() { - let args: Vec = ["bunx", "eslint", "."].iter().map(|s| s.to_string()).collect(); + let args: Vec = ["bunx", "eslint", "."] + .iter() + .map(|s| s.to_string()) + .collect(); let skip = strip_pm_prefix(&args); assert_eq!(named_runner(&args, skip), Some("bunx")); - let args: Vec = ["pnpm", "exec", "eslint"].iter().map(|s| s.to_string()).collect(); + let args: Vec = ["pnpm", "exec", "eslint"] + .iter() + .map(|s| s.to_string()) + .collect(); let skip = strip_pm_prefix(&args); assert_eq!(named_runner(&args, skip), Some("pnpm")); @@ -754,4 +765,3 @@ mod tests { assert_eq!(named_runner(&args, skip), None); } } - diff --git a/src/cmds/js/npm_cmd.rs b/src/cmds/js/npm_cmd.rs index cda6ab7d38..b28b72faf2 100644 --- a/src/cmds/js/npm_cmd.rs +++ b/src/cmds/js/npm_cmd.rs @@ -1,9 +1,9 @@ //! Filters npm output and auto-injects the "run" subcommand when appropriate. -use std::io::IsTerminal; use crate::core::runner; use crate::core::utils::resolved_command; use anyhow::Result; +use std::io::IsTerminal; /// Known npm subcommands that should NOT get "run" injected. /// Shared between production code and tests to avoid drift. @@ -167,13 +167,7 @@ fn run_filtered( opts = opts.inherit_stdin(); } - runner::run_filtered( - cmd, - name, - &args_display, - filter_npm_output, - opts, - ) + runner::run_filtered(cmd, name, &args_display, filter_npm_output, opts) } /// Filter npm run output - strip boilerplate, progress bars, npm WARN diff --git a/src/cmds/js/playwright_cmd.rs b/src/cmds/js/playwright_cmd.rs index 8c763eb964..e6dcf1c35f 100644 --- a/src/cmds/js/playwright_cmd.rs +++ b/src/cmds/js/playwright_cmd.rs @@ -9,8 +9,8 @@ use serde::Deserialize; use std::sync::LazyLock; use crate::parser::{ - emit_degradation_warning, emit_passthrough_warning, truncate_passthrough, FormatMode, - OutputParser, ParseResult, TestFailure, TestResult, TokenFormatter, + FormatMode, OutputParser, ParseResult, TestFailure, TestResult, TokenFormatter, + emit_degradation_warning, emit_passthrough_warning, truncate_passthrough, }; /// Matches real Playwright JSON reporter output (suites → specs → tests → results) diff --git a/src/cmds/js/pnpm_cmd.rs b/src/cmds/js/pnpm_cmd.rs index 4b60775d82..d6087f3e74 100644 --- a/src/cmds/js/pnpm_cmd.rs +++ b/src/cmds/js/pnpm_cmd.rs @@ -11,8 +11,8 @@ use std::collections::HashMap; use std::ffi::OsString; use crate::parser::{ - emit_degradation_warning, emit_passthrough_warning, truncate_passthrough, Dependency, - DependencyState, FormatMode, OutputParser, ParseResult, TokenFormatter, + Dependency, DependencyState, FormatMode, OutputParser, ParseResult, TokenFormatter, + emit_degradation_warning, emit_passthrough_warning, truncate_passthrough, }; const MAX_LISTING: usize = CAP_LIST; @@ -291,8 +291,16 @@ fn extract_outdated_text(output: &str) -> Option { /// `cap = false` for `pnpm list --prod` / `pnpm list --dev` (hint targets, /// must show every package so the LLM can find what was hidden by the cap). fn format_dependency_listing(state: &DependencyState, cap: bool) -> String { - let prod: Vec<_> = state.dependencies.iter().filter(|d| !d.dev_dependency).collect(); - let dev: Vec<_> = state.dependencies.iter().filter(|d| d.dev_dependency).collect(); + let prod: Vec<_> = state + .dependencies + .iter() + .filter(|d| !d.dev_dependency) + .collect(); + let dev: Vec<_> = state + .dependencies + .iter() + .filter(|d| d.dev_dependency) + .collect(); let total = state.total_packages.max(state.dependencies.len()); let mut lines = vec![format!( @@ -304,7 +312,11 @@ fn format_dependency_listing(state: &DependencyState, cap: bool) -> String { if !prod.is_empty() { lines.push("[prod]".to_string()); - let shown = if cap { prod.len().min(MAX_LISTING) } else { prod.len() }; + let shown = if cap { + prod.len().min(MAX_LISTING) + } else { + prod.len() + }; for dep in prod.iter().take(shown) { lines.push(format!(" {} {}", dep.name, dep.current_version)); } @@ -325,7 +337,11 @@ fn format_dependency_listing(state: &DependencyState, cap: bool) -> String { if !dev.is_empty() { lines.push("[dev]".to_string()); - let shown = if cap { dev.len().min(MAX_LISTING) } else { dev.len() }; + let shown = if cap { + dev.len().min(MAX_LISTING) + } else { + dev.len() + }; for dep in dev.iter().take(shown) { lines.push(format!(" {} {}", dep.name, dep.current_version)); } @@ -632,7 +648,10 @@ mod tests { assert!(out.contains("[dev]"), "dev section missing"); assert!(out.contains("react"), "prod package missing"); assert!(out.contains("eslint"), "dev package missing"); - assert!(!out.contains("(dev)"), "per-line (dev) marker should be gone"); + assert!( + !out.contains("(dev)"), + "per-line (dev) marker should be gone" + ); } #[test] @@ -662,15 +681,26 @@ mod tests { let state = make_state(&[], &dev); let out = format_dependency_listing(&state, false); assert!(!out.contains("… +"), "should not truncate when cap=false"); - assert!(!out.contains("[prod]"), "no prod section for dev-only state"); + assert!( + !out.contains("[prod]"), + "no prod section for dev-only state" + ); } #[test] fn test_extract_list_text_tracks_dev_section() { let input = "dependencies:\nreact@18.0.0\ndevDependencies:\neslint@8.0.0\n"; let state = extract_list_text(input).expect("should parse"); - let react = state.dependencies.iter().find(|d| d.name == "react").unwrap(); - let eslint = state.dependencies.iter().find(|d| d.name == "eslint").unwrap(); + let react = state + .dependencies + .iter() + .find(|d| d.name == "react") + .unwrap(); + let eslint = state + .dependencies + .iter() + .find(|d| d.name == "eslint") + .unwrap(); assert!(!react.dev_dependency, "react should be prod"); assert!(eslint.dev_dependency, "eslint should be dev"); } diff --git a/src/cmds/js/tsc_cmd.rs b/src/cmds/js/tsc_cmd.rs index 85d5cd3475..a823f241a3 100644 --- a/src/cmds/js/tsc_cmd.rs +++ b/src/cmds/js/tsc_cmd.rs @@ -2,7 +2,7 @@ use crate::core::runner; use crate::core::stream::{BlockHandler, BlockStreamFilter}; -use crate::core::truncate::{reduced, CAP_WARNINGS}; +use crate::core::truncate::{CAP_WARNINGS, reduced}; use crate::core::utils::{MissingTool, exec_runner, strip_ansi, tool_exec, tool_exists, truncate}; use anyhow::Result; use regex::Regex; @@ -336,11 +336,7 @@ pub(crate) fn filter_tsc_output(output: &str) -> String { if !global_errors.is_empty() { result.push_str(&format!("global ({} errors)\n", global_errors.len())); for err in global_errors { - result.push_str(&format!( - " {} {}\n", - err.code, - truncate(&err.message, 120) - )); + result.push_str(&format!(" {} {}\n", err.code, truncate(&err.message, 120))); for ctx in &err.context_lines { result.push_str(&format!(" {}\n", truncate(ctx, 120))); } @@ -409,9 +405,8 @@ Found 4 errors in 2 files. #[test] fn test_filter_tsc_output_global_error() { - let result = filter_tsc_output( - "error TS5058: The specified path does not exist: 'tsconfig.json'.", - ); + let result = + filter_tsc_output("error TS5058: The specified path does not exist: 'tsconfig.json'."); assert!(result.contains("TS5058")); assert!(result.contains("global (1 errors)")); assert!(!result.contains("completed")); @@ -534,7 +529,11 @@ Found 3 errors in 2 files. ); assert!(result.contains("3 errors in 1 files"), "got: {}", result); assert!(!result.contains("Found 3 errors"), "got: {}", result); - assert!(!result.contains("\x1b["), "emitted block keeps escapes: {}", result); + assert!( + !result.contains("\x1b["), + "emitted block keeps escapes: {}", + result + ); // Real pretty output separates code frames with a blank line; the compact filter drops them. assert!(!result.contains("const x: number"), "got: {}", result); assert!( @@ -591,16 +590,36 @@ src/app.ts(1,7): error TS2322: Type 'string' is not assignable to type 'number'. .collect(); let mut f = BlockStreamFilter::new(TscHandler::new()); let result = run_block_filter(&mut f, &input, 2); - assert!(result.contains("compiler exited with code 2"), "got: {}", result); + assert!( + result.contains("compiler exited with code 2"), + "got: {}", + result + ); for i in 1..=5 { - assert!(result.contains(&format!("junk line {i} {padding}\n")), "got: {}", result); + assert!( + result.contains(&format!("junk line {i} {padding}\n")), + "got: {}", + result + ); } assert!(result.contains("... +20 more lines"), "got: {}", result); for i in 26..=30 { - assert!(result.contains(&format!("junk line {i} {padding}\n")), "got: {}", result); + assert!( + result.contains(&format!("junk line {i} {padding}\n")), + "got: {}", + result + ); } - assert!(!result.contains(&format!("junk line 6 {padding}\n")), "got: {}", result); - assert!(!result.contains(&format!("junk line 25 {padding}\n")), "got: {}", result); + assert!( + !result.contains(&format!("junk line 6 {padding}\n")), + "got: {}", + result + ); + assert!( + !result.contains(&format!("junk line 25 {padding}\n")), + "got: {}", + result + ); assert_eq!(result.lines().count(), 1 + 5 + 1 + 5); } @@ -615,11 +634,19 @@ src/app.ts(1,7): error TS2322: Type 'string' is not assignable to type 'number'. let mut f = BlockStreamFilter::new(TscHandler::new()); let result = run_block_filter(&mut f, &input, 2); for i in 1..=5 { - assert!(result.contains(&format!("real line {i} {padding}\n")), "got: {}", result); + assert!( + result.contains(&format!("real line {i} {padding}\n")), + "got: {}", + result + ); } assert!(result.contains("... +20 more lines"), "got: {}", result); for i in 26..=30 { - assert!(result.contains(&format!("real line {i} {padding}\n")), "got: {}", result); + assert!( + result.contains(&format!("real line {i} {padding}\n")), + "got: {}", + result + ); } assert!(!result.contains("\n\n"), "got: {}", result); assert_eq!(result.lines().count(), 1 + 5 + 1 + 5); @@ -658,9 +685,17 @@ src/app.ts(1,7): error TS2322: Type 'string' is not assignable to type 'number'. let input = include_str!("../../../tests/fixtures/tsc_no_project_raw.txt"); let mut f = BlockStreamFilter::new(TscHandler::new()); let result = run_block_filter(&mut f, input, 1); - assert!(result.contains("compiler exited with code 1"), "got: {}", result); + assert!( + result.contains("compiler exited with code 1"), + "got: {}", + result + ); assert!(result.contains("Version 6.0.3"), "got: {}", result); - assert!(result.contains("tsc: The TypeScript Compiler"), "got: {}", result); + assert!( + result.contains("tsc: The TypeScript Compiler"), + "got: {}", + result + ); assert!(result.contains("... +"), "got: {}", result); } @@ -672,7 +707,11 @@ Use npm install typescript before using npx "; let mut f = BlockStreamFilter::new(TscHandler::new()); let result = run_block_filter(&mut f, input, 1); - assert!(result.contains("compiler exited with code 1"), "got: {}", result); + assert!( + result.contains("compiler exited with code 1"), + "got: {}", + result + ); assert!( result.contains("This is not the tsc command you are looking for"), "got: {}", diff --git a/src/cmds/js/vitest_cmd.rs b/src/cmds/js/vitest_cmd.rs index 32dfa0dac3..1f59b28de9 100644 --- a/src/cmds/js/vitest_cmd.rs +++ b/src/cmds/js/vitest_cmd.rs @@ -5,15 +5,15 @@ use regex::Regex; use serde::Deserialize; use std::sync::LazyLock; +use crate::Commands; use crate::core::stream::exec_capture; use crate::core::tracking; use crate::core::utils::{package_manager_exec, strip_ansi}; use crate::parser::{ + FormatMode, OutputParser, ParseResult, TestFailure, TestResult, TokenFormatter, emit_degradation_warning, emit_passthrough_warning, extract_json_object, truncate_output, - truncate_passthrough, FormatMode, OutputParser, ParseResult, TestFailure, TestResult, - TokenFormatter, + truncate_passthrough, }; -use crate::Commands; /// Vitest JSON output structures (tool-specific format) #[derive(Debug, Deserialize)] @@ -525,7 +525,8 @@ Scope: all 6 workspace projects #[test] fn test_vitest_effective_args_inject_json_reporter_by_default() { - let effective = build_vitest_effective_args(&args(&["run", "constants.test.ts", "--watch"])); + let effective = + build_vitest_effective_args(&args(&["run", "constants.test.ts", "--watch"])); assert!(!effective.passthrough); assert_eq!( @@ -548,8 +549,12 @@ Scope: all 6 workspace projects #[test] fn test_vitest_effective_args_preserve_explicit_reporter_value() { - let effective = - build_vitest_effective_args(&args(&["run", "constants.test.ts", "--reporter", "verbose"])); + let effective = build_vitest_effective_args(&args(&[ + "run", + "constants.test.ts", + "--reporter", + "verbose", + ])); assert!(effective.passthrough); assert_eq!( diff --git a/src/cmds/jvm/gradlew_cmd.rs b/src/cmds/jvm/gradlew_cmd.rs index dac48549de..675f80c054 100644 --- a/src/cmds/jvm/gradlew_cmd.rs +++ b/src/cmds/jvm/gradlew_cmd.rs @@ -397,7 +397,8 @@ fn filter_lint(output: &str) -> String { continue; } - let is_android_lint = ANDROID_LINT_ERROR.is_match(line) || ANDROID_LINT_WARNING.is_match(line); + let is_android_lint = + ANDROID_LINT_ERROR.is_match(line) || ANDROID_LINT_WARNING.is_match(line); if BUILD_STATUS.is_match(line) || ACTIONABLE.is_match(line) @@ -409,7 +410,11 @@ fn filter_lint(output: &str) -> String { result_lines.push(line); // Only Android lint violations have multi-line context; // ktlint/detekt/summary lines are single-line. - context_remaining = if is_android_lint { MAX_CONTEXT_LINES } else { 0 }; + context_remaining = if is_android_lint { + MAX_CONTEXT_LINES + } else { + 0 + }; continue; } @@ -520,10 +525,7 @@ fn filter_dependencies(output: &str) -> String { result.push_str(&format!(" {}\n", dep)); } if deps.len() > MAX_GRADLE_DEPS { - result.push_str(&format!( - " ... +{} more\n", - deps.len() - MAX_GRADLE_DEPS - )); + result.push_str(&format!(" ... +{} more\n", deps.len() - MAX_GRADLE_DEPS)); } } @@ -1049,9 +1051,18 @@ BUILD SUCCESSFUL in 8s .filter(|l| filter_build_line(l)) .collect::>() .join("\n"); - assert!(output.contains("BUILD SUCCESSFUL"), "should keep BUILD SUCCESSFUL"); - assert!(output.contains("actionable tasks"), "should keep actionable tasks line"); - assert!(!output.contains("> Task :"), "should strip task progress lines"); + assert!( + output.contains("BUILD SUCCESSFUL"), + "should keep BUILD SUCCESSFUL" + ); + assert!( + output.contains("actionable tasks"), + "should keep actionable tasks line" + ); + assert!( + !output.contains("> Task :"), + "should strip task progress lines" + ); } #[test] @@ -1065,16 +1076,28 @@ BUILD SUCCESSFUL in 8s assert!(output.contains("BUILD FAILED"), "should keep BUILD FAILED"); assert!(output.contains("FAILURE:"), "should keep failure header"); assert!(output.contains("e: "), "should keep error lines"); - assert!(!output.contains("> Task :"), "should strip task progress lines"); + assert!( + !output.contains("> Task :"), + "should strip task progress lines" + ); } #[test] fn test_test_success_output_format() { let input = include_str!("../../../tests/fixtures/gradlew_test_raw.txt"); let output = filter_test(input); - assert!(output.contains("tests completed"), "should keep test summary"); - assert!(output.contains("BUILD SUCCESSFUL"), "should keep BUILD SUCCESSFUL"); - assert!(!output.contains("PASSED"), "should strip passing test lines"); + assert!( + output.contains("tests completed"), + "should keep test summary" + ); + assert!( + output.contains("BUILD SUCCESSFUL"), + "should keep BUILD SUCCESSFUL" + ); + assert!( + !output.contains("PASSED"), + "should strip passing test lines" + ); } #[test] @@ -1082,18 +1105,33 @@ BUILD SUCCESSFUL in 8s let input = include_str!("../../../tests/fixtures/gradlew_test_failed_raw.txt"); let output = filter_test(input); assert!(output.contains("FAILED"), "should keep failed test names"); - assert!(output.contains("tests completed"), "should keep test summary"); + assert!( + output.contains("tests completed"), + "should keep test summary" + ); assert!(output.contains("BUILD FAILED"), "should keep BUILD FAILED"); - assert!(!output.contains("PASSED"), "should strip passing test lines"); - assert!(!output.contains("at org.junit."), "should strip framework frames"); + assert!( + !output.contains("PASSED"), + "should strip passing test lines" + ); + assert!( + !output.contains("at org.junit."), + "should strip framework frames" + ); } #[test] fn test_connected_output_format() { let input = include_str!("../../../tests/fixtures/gradlew_connected_raw.txt"); let output = filter_connected(input); - assert!(output.contains("BUILD SUCCESSFUL"), "should keep BUILD SUCCESSFUL"); - assert!(!output.contains("INSTRUMENTATION_STATUS"), "should strip instrumentation noise"); + assert!( + output.contains("BUILD SUCCESSFUL"), + "should keep BUILD SUCCESSFUL" + ); + assert!( + !output.contains("INSTRUMENTATION_STATUS"), + "should strip instrumentation noise" + ); } #[test] @@ -1101,9 +1139,15 @@ BUILD SUCCESSFUL in 8s let input = include_str!("../../../tests/fixtures/gradlew_lint_raw.txt"); let output = filter_lint(input); assert!(output.contains("Error:"), "should keep error violations"); - assert!(output.contains("Warning:"), "should keep warning violations"); + assert!( + output.contains("Warning:"), + "should keep warning violations" + ); assert!(output.contains("BUILD FAILED"), "should keep BUILD FAILED"); - assert!(!output.contains("Wrote HTML report"), "should strip report paths"); + assert!( + !output.contains("Wrote HTML report"), + "should strip report paths" + ); } #[test] @@ -1141,10 +1185,19 @@ BUILD SUCCESSFUL in 4s"#; let filtered: Vec<&str> = input.lines().filter(|l| filter_build_line(l)).collect(); let output = filtered.join("\n"); assert!(output.contains("w: "), "kotlinc warnings must be kept"); - assert!(output.contains("warning: [options]"), "javac warnings must be kept"); - assert!(output.contains("Warning: Gradle"), "Gradle warnings must be kept"); + assert!( + output.contains("warning: [options]"), + "javac warnings must be kept" + ); + assert!( + output.contains("Warning: Gradle"), + "Gradle warnings must be kept" + ); assert!(output.contains("BUILD SUCCESSFUL"), "status must be kept"); - assert!(!output.contains("> Task :"), "task progress must be stripped"); + assert!( + !output.contains("> Task :"), + "task progress must be stripped" + ); } // ── CHECK (BUILD FILTER ON MIXED OUTPUT) ──────────────────────────────── @@ -1394,7 +1447,9 @@ BUILD SUCCESSFUL in 3s assert!(is_framework_frame( "at java.lang.reflect.Method.invoke(Method.java:498)" )); - assert!(is_framework_frame("at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)")); + assert!(is_framework_frame( + "at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)" + )); assert!(!is_framework_frame( "at com.example.FooTest.testBar(FooTest.kt:25)" )); diff --git a/src/cmds/jvm/mvn_cmd.rs b/src/cmds/jvm/mvn_cmd.rs index 8b6456c8d1..7a87f942c0 100644 --- a/src/cmds/jvm/mvn_cmd.rs +++ b/src/cmds/jvm/mvn_cmd.rs @@ -437,8 +437,7 @@ fn is_lane_opener(core: &str) -> bool { /// Note: the `[ERROR] Class.test:25 …` failures-summary entries (3-space /// indent, no `<<<` marker) do NOT match. fn is_per_test_subline(line: &str) -> bool { - line.starts_with("[ERROR] ") - && (line.contains("<<< FAILURE!") || line.contains("<<< ERROR!")) + line.starts_with("[ERROR] ") && (line.contains("<<< FAILURE!") || line.contains("<<< ERROR!")) } // ── English-footer guard ──────────────────────────────────────────────────── @@ -1260,7 +1259,10 @@ fn drive_surefire_line<'a>( } } - let step = lanes.get(idx).block.step(line, core, keyed, idx == ROOT_LANE, out); + let step = lanes + .get(idx) + .block + .step(line, core, keyed, idx == ROOT_LANE, out); // A lane inside a Surefire block has no pending javac continuations: // entering a block retires any stale armed claim, so a single lane can't // hold a permanent armed-vs-block tie against raw-line routing. @@ -1275,7 +1277,10 @@ fn drive_surefire_line<'a>( close, } => { if classes.admit() { - lanes.get(idx).block.commit_failing(out, running, &lines, close); + lanes + .get(idx) + .block + .commit_failing(out, running, &lines, close); } else { lanes.get(idx).block.drop_failing(); } @@ -1317,7 +1322,12 @@ fn filter_surefire_with_cap(raw: &str, cap: usize, daemon: bool) -> String { for line in stripped.lines() { let (idx, core, keyed) = match drive_surefire_line( - &mut lanes, line, &mut classes, &mut summary, daemon, &mut out, + &mut lanes, + line, + &mut classes, + &mut summary, + daemon, + &mut out, ) { Some(v) => v, None => continue, @@ -1565,7 +1575,12 @@ fn filter_package_with_cap(raw: &str, cap: usize, daemon: bool) -> String { for line in stripped.lines() { let (idx, core, keyed) = match drive_surefire_line( - &mut lanes, line, &mut classes, &mut summary, daemon, &mut out, + &mut lanes, + line, + &mut classes, + &mut summary, + daemon, + &mut out, ) { Some(v) => v, None => continue, @@ -1711,8 +1726,7 @@ pub fn filter_quiet(raw: &str, daemon: bool) -> String { if CLOSE.is_match(core) { out.push_str(line); out.push('\n'); - failure_trail = - core.contains("<<< FAILURE!") || core.contains("<<< ERROR!"); + failure_trail = core.contains("<<< FAILURE!") || core.contains("<<< ERROR!"); continue; } @@ -1959,7 +1973,8 @@ mod tests { let i = include_str!("../../../tests/fixtures/mvnd_reactor_pass_raw.txt"); let o = filter_package(i, true); assert!( - o.contains("Building child-a 1.0.0-SNAPSHOT") && o.contains("Building child-b 1.0.0-SNAPSHOT"), + o.contains("Building child-a 1.0.0-SNAPSHOT") + && o.contains("Building child-b 1.0.0-SNAPSHOT"), "genuine `[n/m]`-numbered module headers survive on tagged lanes; got:\n{o}" ); } @@ -2020,7 +2035,9 @@ mod tests { \tat com.example.rtk.AFailTest.foo(AFailTest.java:10)\n\ [INFO] BUILD FAILURE\n"; let o = filter_surefire(i, true); - let running = o.find("Running com.example.rtk.AFailTest").expect("Running kept"); + let running = o + .find("Running com.example.rtk.AFailTest") + .expect("Running kept"); let pipeline = o .find("Running the widget pipeline for tenant acme") .expect("app-log line kept, buffered inside the block"); @@ -2058,7 +2075,8 @@ mod tests { /// exactly there. #[test] fn daemon_building_applog_dropped_with_no_block_open() { - let mut i = String::from("[INFO] Scanning for projects...\n[INFO] Building web 2.1.0 [1/1]\n"); + let mut i = + String::from("[INFO] Scanning for projects...\n[INFO] Building web 2.1.0 [1/1]\n"); for n in 0..10 { i.push_str(&format!( "[pool-1] [INFO] Building segment {n} of the data pipeline\n" @@ -2098,7 +2116,10 @@ mod tests { ("same coordinates", "surefire:3.5.5:test (default-test)"), ("different version", "surefire:3.2.2:test (default-test)"), ("different execution id", "surefire:3.5.5:test (unit-tests)"), - ("legacy spelling", "maven-surefire-plugin:2.22.2:test (default-test)"), + ( + "legacy spelling", + "maven-surefire-plugin:2.22.2:test (default-test)", + ), ] { let i = format!( "[INFO] Scanning for projects...\n{}{}[INFO] BUILD FAILURE\n", @@ -2338,11 +2359,13 @@ mod tests { // kept — pre-fix (stale open block swallowing the header/entries) // all 4 would survive. assert!( - o.contains("AMultiFailTest.first:10 a1 boom") && o.contains("BMultiFailTest.first:10 b1 boom"), + o.contains("AMultiFailTest.first:10 a1 boom") + && o.contains("BMultiFailTest.first:10 b1 boom"), "the first entry of each module's summary is kept; got:\n{o}" ); assert!( - !o.contains("AMultiFailTest.second:20 a2 boom") && !o.contains("BMultiFailTest.second:20 b2 boom"), + !o.contains("AMultiFailTest.second:20 a2 boom") + && !o.contains("BMultiFailTest.second:20 b2 boom"), "the second entry of each module's summary is capped, not kept; got:\n{o}" ); @@ -2357,11 +2380,15 @@ mod tests { !o.contains("… +2 more failures"), "no module's tail should absorb the other's drop; got:\n{o}" ); - let a_header = o.find("[child-a] [ERROR] Failures:").expect("child-a header kept"); + let a_header = o + .find("[child-a] [ERROR] Failures:") + .expect("child-a header kept"); let a_agg = o .find("[child-a] [ERROR] Tests run: 2, Failures: 2, Errors: 0, Skipped: 0\n") .expect("child-a AGG kept"); - let b_header = o.find("[child-b] [ERROR] Failures:").expect("child-b header kept"); + let b_header = o + .find("[child-b] [ERROR] Failures:") + .expect("child-b header kept"); let b_agg = o .rfind("[child-b] [ERROR] Tests run: 2, Failures: 2, Errors: 0, Skipped: 0\n") .expect("child-b AGG kept"); @@ -2376,13 +2403,18 @@ mod tests { // Ordering: banners, Running, and the capped summary all survive in // a sane relative order, ending at the reactor footer. - let banner_a = o.find("< com.example.rtk:child-a >").expect("child-a banner kept"); + let banner_a = o + .find("< com.example.rtk:child-a >") + .expect("child-a banner kept"); let running_a = o .find("Running com.example.rtk.AMultiFailTest") .expect("child-a Running kept"); let build_failure = o.rfind("BUILD FAILURE").expect("footer kept"); assert!( - banner_a < running_a && running_a < a_header && a_header < a_agg && a_agg < build_failure, + banner_a < running_a + && running_a < a_header + && a_header < a_agg + && a_agg < build_failure, "banner < Running < header < AGG < footer; got:\n{o}" ); } @@ -2515,10 +2547,7 @@ mod tests { } #[test] fn phase_plugin_goal_passthrough() { - assert_eq!( - detect_phase(&s(["dependency:tree"])), - MvnPhase::Passthrough - ); + assert_eq!(detect_phase(&s(["dependency:tree"])), MvnPhase::Passthrough); } #[test] fn phase_empty_passthrough() { @@ -2642,7 +2671,9 @@ mod tests { "[child-a] [ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed:" )); assert!(o.contains("parallel reactor diagnostic ==> expected: <1> but was: <2>")); - assert!(o.contains("at com.example.rtk.ParallelFailTest.reactorDiagnostic(ParallelFailTest.java:10)")); + assert!(o.contains( + "at com.example.rtk.ParallelFailTest.reactorDiagnostic(ParallelFailTest.java:10)" + )); assert!(o.contains("[child-a] [ERROR] ParallelFailTest.reactorDiagnostic:10")); assert!(o.contains("[child-a] [ERROR] Tests run: 3, Failures: 1, Errors: 0, Skipped: 0")); // Reactor summary keeps the per-module verdicts. @@ -2888,7 +2919,9 @@ mod tests { // child-b's header is legitimately empty (its only entry lost the // race) — but must never look *complete*: its own tail must follow // directly, not vanish silently. - let b_header = o.find("[child-b] [ERROR] Failures:").expect("child-b header kept"); + let b_header = o + .find("[child-b] [ERROR] Failures:") + .expect("child-b header kept"); let b_agg = o .find("[child-b] [ERROR] Tests run:") .expect("child-b AGG kept"); @@ -2964,7 +2997,9 @@ mod tests { for n in 1..=12 { i.push_str(&format!("[ERROR] ITTest.case{n}:{n} boom it{n}\n")); } - i.push_str("[ERROR] Tests run: 12, Failures: 12, Errors: 0, Skipped: 0\n[INFO] BUILD FAILURE\n"); + i.push_str( + "[ERROR] Tests run: 12, Failures: 12, Errors: 0, Skipped: 0\n[INFO] BUILD FAILURE\n", + ); let o = filter_package(&i, false); assert_eq!( @@ -3087,7 +3122,9 @@ mod tests { for n in 1..=12 { i.push_str(&format!("[ERROR] ITTest.case{n}:{n} boom it{n}\n")); } - i.push_str("[ERROR] Tests run: 0, Failures: 0, Errors: 12, Skipped: 0\n[INFO] BUILD FAILURE\n"); + i.push_str( + "[ERROR] Tests run: 0, Failures: 0, Errors: 12, Skipped: 0\n[INFO] BUILD FAILURE\n", + ); let o = filter_package(&i, false); assert_eq!( @@ -3519,7 +3556,10 @@ mod tests { /// has no `[WARNING]` branch but shares the same fall-through reset). fn assert_warning_interloper_does_not_disarm_continuation(filter: fn(&str) -> String) { const WARNING_INTERLOPER: [&str; 1] = ["[main] [WARNING] connection pool exhausted"]; - for (n, m) in merges(&SWEEP_COMPILE_A, &WARNING_INTERLOPER).iter().enumerate() { + for (n, m) in merges(&SWEEP_COMPILE_A, &WARNING_INTERLOPER) + .iter() + .enumerate() + { let i = sweep_input(m); let o = filter(&i); assert!( @@ -3562,7 +3602,10 @@ mod tests { /// position of the compile-error sequence, on all three filter paths. fn assert_error_interloper_does_not_disarm_continuation(filter: fn(&str) -> String) { const ERROR_INTERLOPER: [&str; 1] = ["[main] [ERROR] connection pool exhausted"]; - for (n, m) in merges(&SWEEP_COMPILE_A, &ERROR_INTERLOPER).iter().enumerate() { + for (n, m) in merges(&SWEEP_COMPILE_A, &ERROR_INTERLOPER) + .iter() + .enumerate() + { let i = sweep_input(m); let o = filter(&i); assert!( @@ -3745,8 +3788,7 @@ mod tests { [INFO] BUILD FAILURE\n"; let o = filter(i); assert!( - o.contains("symbol: variable bar") - && o.contains("location: class com.example.rtk.A"), + o.contains("symbol: variable bar") && o.contains("location: class com.example.rtk.A"), "ambiguous continuations preserved verbatim; got:\n{o}" ); } @@ -3864,11 +3906,7 @@ mod tests { "passing-test Running line dropped; got:\n{}", o ); - assert!( - o.contains("BUILD SUCCESS"), - "footer preserved; got:\n{}", - o - ); + assert!(o.contains("BUILD SUCCESS"), "footer preserved; got:\n{}", o); assert!( o.contains("Tests run: 977, Failures: 0"), "aggregate preserved; got:\n{}", @@ -3914,11 +3952,7 @@ mod tests { "2.x ` - in ` close-line matched; passing block dropped; got:\n{}", o ); - assert!( - o.contains("BUILD SUCCESS"), - "footer preserved; got:\n{}", - o - ); + assert!(o.contains("BUILD SUCCESS"), "footer preserved; got:\n{}", o); } /// 3.x WARNING-prefixed close line (class with only skipped tests) must @@ -3949,8 +3983,16 @@ mod tests { \n\ [INFO] BUILD FAILURE\n"; let o = filter_surefire(i, false); - assert!(o.contains("AssertionFailedError"), "exception preserved; got:\n{}", o); - assert!(o.contains("at x.Foo.bar"), "user frame preserved; got:\n{}", o); + assert!( + o.contains("AssertionFailedError"), + "exception preserved; got:\n{}", + o + ); + assert!( + o.contains("at x.Foo.bar"), + "user frame preserved; got:\n{}", + o + ); assert!( !o.contains("at org.junit."), "framework frame stripped in trail; got:\n{}", @@ -4234,7 +4276,11 @@ mod tests { fn surefire_keeps_compile_continuation_on_test_phase() { let i = include_str!("../../../tests/fixtures/mvn_test_compile_fail_slice_raw.txt"); let o = filter_surefire(i, false); - assert!(o.contains("cannot find symbol"), "ERROR line preserved; got:\n{}", o); + assert!( + o.contains("cannot find symbol"), + "ERROR line preserved; got:\n{}", + o + ); assert!( o.contains("symbol: variable bar"), "indented `symbol:` continuation preserved; got:\n{}", @@ -4256,7 +4302,11 @@ mod tests { fn package_still_keeps_compile_error_continuation_after_refactor() { let i = include_str!("../../../tests/fixtures/mvn_compile_error_slice_raw.txt"); let o = filter_package(i, false); - assert!(o.contains("cannot find symbol"), "ERROR line preserved; got:\n{}", o); + assert!( + o.contains("cannot find symbol"), + "ERROR line preserved; got:\n{}", + o + ); assert!( o.contains("symbol: variable bar"), "indented `symbol:` continuation preserved; got:\n{}", @@ -4393,8 +4443,8 @@ mod tests { #[test] fn package_handles_crlf_line_endings() { - let i_lf = include_str!("../../../tests/fixtures/mvn_install_slice_raw.txt") - .replace("\r\n", "\n"); + let i_lf = + include_str!("../../../tests/fixtures/mvn_install_slice_raw.txt").replace("\r\n", "\n"); let o_lf = filter_package(&i_lf, false); let i_crlf = i_lf.replace('\n', "\r\n"); let o_crlf = filter_package(&i_crlf, false); @@ -4597,11 +4647,7 @@ mod tests { "second per-module SUCCESS row preserved; got:\n{}", o ); - assert!( - o.contains("BUILD SUCCESS"), - "footer preserved; got:\n{}", - o - ); + assert!(o.contains("BUILD SUCCESS"), "footer preserved; got:\n{}", o); } /// `mvn install` on a multi-module reactor build where one module fails @@ -4638,7 +4684,11 @@ mod tests { "resume hint preserved (actionable signal); got:\n{}", o ); - assert!(!o.contains("[Help 1]"), "help boilerplate stripped; got:\n{}", o); + assert!( + !o.contains("[Help 1]"), + "help boilerplate stripped; got:\n{}", + o + ); assert!( !o.contains("Re-run Maven"), "re-run hint stripped; got:\n{}", @@ -4735,7 +4785,9 @@ mod tests { #[test] #[ignore] fn print_savings_summary() { - let pf = gunzip(include_bytes!("../../../tests/fixtures/mvn_test_pass_full_raw.txt.gz")); + let pf = gunzip(include_bytes!( + "../../../tests/fixtures/mvn_test_pass_full_raw.txt.gz" + )); let pf_out = filter_surefire(&pf, false); let pf_in_tok = count_tokens(&pf); let pf_out_tok = count_tokens(&pf_out); @@ -4745,7 +4797,9 @@ mod tests { pf_in_tok, pf_out_tok, pf_s ); - let inst = gunzip(include_bytes!("../../../tests/fixtures/mvn_install_full_raw.txt.gz")); + let inst = gunzip(include_bytes!( + "../../../tests/fixtures/mvn_install_full_raw.txt.gz" + )); let inst_out = filter_package(&inst, false); let inst_in_tok = count_tokens(&inst); let inst_out_tok = count_tokens(&inst_out); @@ -4961,7 +5015,10 @@ mod tests { // after — `lane_rest_level`'s documented other spelling of the // daemon-prefixed trail terminator, alongside `[tag] [LEVEL] `). // Pinned so the "both spellings" contract can't rot. - assert_eq!(split_lane("[child-a] [INFO]", true), (Some("child-a"), "[INFO]")); + assert_eq!( + split_lane("[child-a] [INFO]", true), + (Some("child-a"), "[INFO]") + ); } // ── daemon gate: plain `mvn` never routes through the lane layer ──────── @@ -4991,7 +5048,9 @@ mod tests { .find("Running the widget pipeline") .expect("app-log Running-shaped line kept"); let db = o.find("connecting to db").expect("first context line kept"); - let refused = o.find("connection refused").expect("second context line kept"); + let refused = o + .find("connection refused") + .expect("second context line kept"); let footer = o.rfind("BUILD FAILURE").expect("footer kept"); assert!( pipeline < db && db < refused && refused < footer, @@ -5013,7 +5072,9 @@ mod tests { fn timestamp_tag_does_not_mint_phantom_lanes_in_plain_mvn() { let mut i = String::from("[INFO] Scanning for projects...\n"); for n in 0..300 { - i.push_str(&format!("[2026-08-29 10:00:00] [INFO] Building segment {n}\n")); + i.push_str(&format!( + "[2026-08-29 10:00:00] [INFO] Building segment {n}\n" + )); } i.push_str("[INFO] BUILD SUCCESS\n"); let o = filter_surefire(&i, false); @@ -5071,7 +5132,8 @@ mod tests { [INFO] BUILD FAILURE\n"; let o = filter_package_with_cap(i, 2, true); assert!( - o.contains("AMultiFailTest.one:10 a1 boom") && o.contains("AMultiFailTest.two:20 a2 boom"), + o.contains("AMultiFailTest.one:10 a1 boom") + && o.contains("AMultiFailTest.two:20 a2 boom"), "child-a's first two entries fill its Surefire-phase budget; got:\n{o}" ); assert!( @@ -5138,7 +5200,8 @@ mod tests { [INFO] BUILD FAILURE\n"; let o = filter_package_with_cap(i, 2, true); assert!( - o.contains("AMultiFailTest.one:10 a1 boom") && o.contains("AMultiFailTest.two:20 a2 boom"), + o.contains("AMultiFailTest.one:10 a1 boom") + && o.contains("AMultiFailTest.two:20 a2 boom"), "child-a's first two entries fill its Surefire-phase budget; got:\n{o}" ); assert!( @@ -5335,7 +5398,9 @@ mod tests { [child-a] [INFO] \n\ [INFO] BUILD FAILURE\n"; let o = filter_surefire(i, true); - let running = o.find("Running com.example.rtk.SlowTest").expect("Running kept"); + let running = o + .find("Running com.example.rtk.SlowTest") + .expect("Running kept"); let server = o .find("[Server] [ERROR] connection refused") .expect("app log line kept, buffered inside the block"); @@ -5428,7 +5493,9 @@ mod tests { // unrelated 255-way armed tie in `raw_owner` instead of exercising // the fix it targets. for n in 0..(MAX_LANES + 50) { - i.push_str(&format!("[tag{n}] [INFO] Running com.example.rtk.Tag{n}Test\n")); + i.push_str(&format!( + "[tag{n}] [INFO] Running com.example.rtk.Tag{n}Test\n" + )); } // One more never-seen tag, past the cap: a genuine failing test // class (`Running` + failing close), not just a bare `[ERROR]` @@ -5465,8 +5532,3 @@ mod tests { ); } } - - - - - diff --git a/src/cmds/php/artisan_cmd.rs b/src/cmds/php/artisan_cmd.rs index 9fcd696f2c..41c23a42cb 100644 --- a/src/cmds/php/artisan_cmd.rs +++ b/src/cmds/php/artisan_cmd.rs @@ -1,7 +1,7 @@ //! Laravel Artisan output cleanup helpers. use super::test_output::filter_test_runner_output; -use super::utils::{strip_ansi_and_controls, PhpTestRunner}; +use super::utils::{PhpTestRunner, strip_ansi_and_controls}; use regex::Regex; use std::sync::LazyLock; @@ -34,8 +34,7 @@ mod tests { #[test] fn test_artisan_cleanup() { - let out = - "\u{1b}[32mEnvironment .....\u{1b}[0m\n\u{2502} Laravel Version \u{2502} 13.0.0 \u{2502}\n\n\n"; + let out = "\u{1b}[32mEnvironment .....\u{1b}[0m\n\u{2502} Laravel Version \u{2502} 13.0.0 \u{2502}\n\n\n"; let filtered = filter_artisan_output(out); assert!(!filtered.contains('\u{1b}')); assert!(!filtered.contains('\u{2502}')); diff --git a/src/cmds/php/php_cmd.rs b/src/cmds/php/php_cmd.rs index 3bbca99427..21036df411 100644 --- a/src/cmds/php/php_cmd.rs +++ b/src/cmds/php/php_cmd.rs @@ -1,7 +1,7 @@ //! PHP command filter: syntax-check summaries and generic cleanup. use super::artisan_cmd::{filter_artisan_output, filter_artisan_test_output}; -use super::utils::{detect_php_test_runner, strip_ansi_and_controls, PhpTestRunner}; +use super::utils::{PhpTestRunner, detect_php_test_runner, strip_ansi_and_controls}; use crate::core::runner; use crate::core::utils::resolved_command; use anyhow::Result; diff --git a/src/cmds/php/phpstan_cmd.rs b/src/cmds/php/phpstan_cmd.rs index e3c1547bc6..7d0f2af158 100644 --- a/src/cmds/php/phpstan_cmd.rs +++ b/src/cmds/php/phpstan_cmd.rs @@ -322,7 +322,9 @@ mod tests { for j in 1..=error_count { messages.push(format!( r#"{{"message": "Error {} in file {}", "line": {}, "ignorable": false}}"#, - j, i, j * 10 + j, + i, + j * 10 )); } @@ -371,8 +373,16 @@ mod tests { "errors": [] }"#; let result = filter_phpstan_json(json); - assert!(result.starts_with("phpstan: 2 errors in 1 files"), "got: {}", result); - assert!(result.contains("Models/User.php (2 errors)"), "got: {}", result); + assert!( + result.starts_with("phpstan: 2 errors in 1 files"), + "got: {}", + result + ); + assert!( + result.contains("Models/User.php (2 errors)"), + "got: {}", + result + ); } #[test] @@ -472,7 +482,10 @@ mod tests { Found 5 errors in 3 files"#; let result = filter_phpstan_text(text); - assert!(result.starts_with("PHPStan:"), "should have PHPStan: prefix"); + assert!( + result.starts_with("PHPStan:"), + "should have PHPStan: prefix" + ); assert!(result.contains("5 errors"), "should contain error count"); assert!(result.contains("3 files"), "should contain file count"); } diff --git a/src/cmds/php/phpt_cmd.rs b/src/cmds/php/phpt_cmd.rs index 1ecf7a98b2..a3b644214b 100644 --- a/src/cmds/php/phpt_cmd.rs +++ b/src/cmds/php/phpt_cmd.rs @@ -404,7 +404,11 @@ mod tests { let s = |v: &[&str]| v.iter().map(|a| a.to_string()).collect::>(); // Unrelated --show-* flags don't produce diffs, so they must not suppress // our injected --show-diff. - assert!(!args_already_show_diff(&s(&["--show-slow", "1000", "ext/standard/"]))); + assert!(!args_already_show_diff(&s(&[ + "--show-slow", + "1000", + "ext/standard/" + ]))); assert!(!args_already_show_diff(&s(&["ext/standard/"]))); // The two flags that do produce diffs suppress the injection. assert!(args_already_show_diff(&s(&["--show-diff"]))); diff --git a/src/cmds/php/phpunit_cmd.rs b/src/cmds/php/phpunit_cmd.rs index f2d4cd0356..9c17766150 100644 --- a/src/cmds/php/phpunit_cmd.rs +++ b/src/cmds/php/phpunit_cmd.rs @@ -210,7 +210,9 @@ mod tests { #[test] fn test_numbered_failure_heading_anchored() { // Real PHPUnit failure headings match. - assert!(is_numbered_failure_heading("1) App\\Tests\\UserTest::testEmail")); + assert!(is_numbered_failure_heading( + "1) App\\Tests\\UserTest::testEmail" + )); assert!(is_numbered_failure_heading("12) Foo::bar")); // Detail lines that merely start with a digit and contain ')' must not. assert!(!is_numbered_failure_heading( @@ -280,7 +282,11 @@ Tests: 9, Assertions: 15, Failures: 2."#; fn test_phpunit_success() { let result = filter_phpunit_output(REAL_PHPUNIT_SUCCESS); assert!(result.contains("PHPUnit"), "got: {}", result); - assert!(result.contains("OK (9 tests, 20 assertions)"), "got: {}", result); + assert!( + result.contains("OK (9 tests, 20 assertions)"), + "got: {}", + result + ); } #[test] diff --git a/src/cmds/php/pint_cmd.rs b/src/cmds/php/pint_cmd.rs index 51068a7469..76923f474b 100644 --- a/src/cmds/php/pint_cmd.rs +++ b/src/cmds/php/pint_cmd.rs @@ -188,10 +188,7 @@ mod tests { fn test_pint_file_truncation() { let mut files = Vec::new(); for i in 1..=20 { - files.push(format!( - r#"{{"name":"f{}.php","appliedFixers":["x"]}}"#, - i - )); + files.push(format!(r#"{{"name":"f{}.php","appliedFixers":["x"]}}"#, i)); } let json = format!(r#"{{"files":[{}]}}"#, files.join(",")); let result = filter_pint_json(&json); diff --git a/src/cmds/python/pip_cmd.rs b/src/cmds/python/pip_cmd.rs index 09737712b8..bbf5ded3e1 100644 --- a/src/cmds/python/pip_cmd.rs +++ b/src/cmds/python/pip_cmd.rs @@ -74,8 +74,8 @@ fn run_list(base_cmd: &str, args: &[String], verbose: u8) -> Result<(String, Str eprintln!("Running: {} pip list --format=json", base_cmd); } - let result = exec_capture(&mut cmd) - .with_context(|| format!("Failed to run {} pip list", base_cmd))?; + let result = + exec_capture(&mut cmd).with_context(|| format!("Failed to run {} pip list", base_cmd))?; let raw = format!("{}\n{}", result.stdout, result.stderr); diff --git a/src/cmds/python/pytest_cmd.rs b/src/cmds/python/pytest_cmd.rs index 63b76e312f..58d7dd5c0b 100644 --- a/src/cmds/python/pytest_cmd.rs +++ b/src/cmds/python/pytest_cmd.rs @@ -29,7 +29,9 @@ pub fn run(args: &[String], verbose: u8) -> Result { let has_tb_flag = args.iter().any(|a| a.starts_with("--tb")); let has_quiet_flag = args.iter().any(|a| a == "-q" || a == "--quiet"); // Only treat a short `-r…` as pytest's report flag (not `--randomly-seed` etc.) - let has_report_flag = args.iter().any(|a| a.starts_with("-r") && !a.starts_with("--")); + let has_report_flag = args + .iter() + .any(|a| a.starts_with("-r") && !a.starts_with("--")); if !has_tb_flag { cmd.arg("--tb=short"); @@ -225,7 +227,9 @@ fn build_pytest_summary( if xfail_lines.len() > MAX_XFAIL { result.push_str(&format!(" … +{} more\n", xfail_lines.len() - MAX_XFAIL)); let all_xfail = xfail_lines.join("\n"); - if let Some(hint) = crate::core::tee::force_tee_tail_hint(&all_xfail, "pytest-xfail", MAX_XFAIL + 1) { + if let Some(hint) = + crate::core::tee::force_tee_tail_hint(&all_xfail, "pytest-xfail", MAX_XFAIL + 1) + { result.push_str(&format!(" {}\n", hint)); } } @@ -423,10 +427,7 @@ collected 0 items assert_eq!((c.passed, c.failed, c.skipped), (3, 1, 2)); let c = parse_summary_line("=== 2 passed, 1 failed, 2 xfailed, 1 xpassed in 1.0s ==="); - assert_eq!( - (c.passed, c.failed, c.xfailed, c.xpassed), - (2, 1, 2, 1) - ); + assert_eq!((c.passed, c.failed, c.xfailed, c.xpassed), (2, 1, 2, 1)); } #[test] @@ -453,10 +454,7 @@ collected 0 items .lines() .filter(|l| l.trim().starts_with("XFAIL")) .count(); - assert!( - listed <= 10, - "MAX_XFAIL cap not enforced: listed {listed}" - ); + assert!(listed <= 10, "MAX_XFAIL cap not enforced: listed {listed}"); assert!(result.contains("… +5 more"), "missing '+N more': {result}"); } diff --git a/src/cmds/python/sqlfluff_cmd.rs b/src/cmds/python/sqlfluff_cmd.rs index b8ec591e73..12b66daba7 100644 --- a/src/cmds/python/sqlfluff_cmd.rs +++ b/src/cmds/python/sqlfluff_cmd.rs @@ -72,9 +72,9 @@ pub fn plan(args: &[String]) -> Invocation { let is_lint = args.is_empty() || args[0] == "lint"; // Both spellings: injecting a second --format makes sqlfluff reject the call. - let user_set_format = args - .iter() - .any(|a| a == "--format" || a == "-f" || a.starts_with("--format=") || a.starts_with("-f=")); + let user_set_format = args.iter().any(|a| { + a == "--format" || a == "-f" || a.starts_with("--format=") || a.starts_with("-f=") + }); let user_json_format = args.iter().enumerate().any(|(i, a)| { a == "--format=json" || a == "-f=json" @@ -176,10 +176,8 @@ fn render_lint_json(output: &str, exit_code: i32) -> String { }; // One sort feeds the whole report: files, worst first. - let mut files: Vec<&SqlfluffFile> = parsed - .iter() - .filter(|f| !f.violations.is_empty()) - .collect(); + let mut files: Vec<&SqlfluffFile> = + parsed.iter().filter(|f| !f.violations.is_empty()).collect(); let total_violations: usize = files.iter().map(|f| f.violations.len()).sum(); if total_violations == 0 { @@ -209,9 +207,10 @@ fn render_lint_json(output: &str, exit_code: i32) -> String { let mut by_rule: HashMap<&str, (usize, &str, Option)> = HashMap::new(); for file in &parsed { for v in &file.violations { - let entry = by_rule - .entry(v.code.as_str()) - .or_insert((0, file.filepath.as_str(), v.line())); + let entry = + by_rule + .entry(v.code.as_str()) + .or_insert((0, file.filepath.as_str(), v.line())); entry.0 += 1; if entry.2.is_none() && v.line().is_some() { entry.1 = file.filepath.as_str(); @@ -268,9 +267,7 @@ fn render_lint_json(output: &str, exit_code: i32) -> String { let mut file_rules: HashMap<&str, (usize, Option)> = HashMap::new(); for v in &file.violations { - let entry = file_rules - .entry(v.code.as_str()) - .or_insert((0, v.line())); + let entry = file_rules.entry(v.code.as_str()).or_insert((0, v.line())); entry.0 += 1; if entry.1.is_none() && v.line().is_some() { entry.1 = v.line(); @@ -504,7 +501,10 @@ mod tests { "the file ranked first must lead the detail section, got:\n{section}" ); assert_eq!( - section.lines().filter(|l| l.contains("zzz_worst.sql")).count(), + section + .lines() + .filter(|l| l.contains("zzz_worst.sql")) + .count(), 22, "every violation of the worst file must survive truncation" ); @@ -557,10 +557,7 @@ mod tests { #[test] fn test_violation_renders_full_coordinates_when_reported() { let out = filter_sqlfluff_lint_json(V4_JSON); - assert!( - out.contains("models/orders.sql:1:1 LT09"), - "got:\n{out}" - ); + assert!(out.contains("models/orders.sql:1:1 LT09"), "got:\n{out}"); } // ── fixability across sqlfluff versions ───────────────────────────────── @@ -602,7 +599,11 @@ mod tests { #[test] fn test_plan_never_injects_a_second_format() { for user in [ - vec!["lint".to_string(), "--format".to_string(), "human".to_string()], + vec![ + "lint".to_string(), + "--format".to_string(), + "human".to_string(), + ], vec!["lint".to_string(), "-f=human".to_string()], ] { let p = plan(&user); @@ -619,14 +620,7 @@ mod tests { #[test] fn test_plan_keeps_user_json_format_parseable() { assert!(plan(&["lint".to_string(), "--format=json".to_string()]).expect_json); - assert!( - plan(&[ - "lint".to_string(), - "-f".to_string(), - "json".to_string() - ]) - .expect_json - ); + assert!(plan(&["lint".to_string(), "-f".to_string(), "json".to_string()]).expect_json); } #[test] @@ -756,8 +750,7 @@ mod tests { fn test_filter_legacy_line_no_alias() { // Older sqlfluff versions emit line_no/line_pos; the serde alias must // surface sample locations for them too. - let input = - r#"[{"filepath": "models/core/dim_users.sql", "violations": [{"line_no": 7, "line_pos": 2, "code": "LT09", "description": "Select wildcard.", "fixes": []}]}]"#; + let input = r#"[{"filepath": "models/core/dim_users.sql", "violations": [{"line_no": 7, "line_pos": 2, "code": "LT09", "description": "Select wildcard.", "fixes": []}]}]"#; let result = filter_sqlfluff_lint_json(input); assert!( result.contains("models/core/dim_users.sql:7"), @@ -788,7 +781,10 @@ mod tests { let input = std::iter::repeat_n("line of human sqlfluff output\n", 2000).collect::(); let result = filter_sqlfluff_lint_json(&input); - assert_eq!(result, input, "large raw output must pass through unchanged"); + assert_eq!( + result, input, + "large raw output must pass through unchanged" + ); } #[test] @@ -833,12 +829,8 @@ mod tests { } ]"#; let result = filter_sqlfluff_lint_json(input); - let lt02 = result - .find("LT02") - .expect("LT02 should be listed"); - let lt10 = result - .find("LT10") - .expect("LT10 should be listed"); + let lt02 = result.find("LT02").expect("LT02 should be listed"); + let lt10 = result.find("LT10").expect("LT10 should be listed"); assert!( lt02 < lt10, "tied rules must sort alphabetically, got: {result}" @@ -912,13 +904,21 @@ mod tests { fn test_compact_path_disambiguates_non_dbt_layouts() { // The detail section exists to be opened, so paths sharing a filename // across an ordinary migrations/reports/src layout must stay distinct. - let rendered: Vec = ["migrations/orders.sql", "reports/orders.sql", "/repo/src/sql/orders.sql"] - .iter() - .map(|p| compact_path(p)) - .collect(); + let rendered: Vec = [ + "migrations/orders.sql", + "reports/orders.sql", + "/repo/src/sql/orders.sql", + ] + .iter() + .map(|p| compact_path(p)) + .collect(); assert_eq!( rendered, - ["migrations/orders.sql", "reports/orders.sql", "sql/orders.sql"] + [ + "migrations/orders.sql", + "reports/orders.sql", + "sql/orders.sql" + ] ); } diff --git a/src/cmds/python/uv_cmd.rs b/src/cmds/python/uv_cmd.rs index 0bbb4668a0..b03030252d 100644 --- a/src/cmds/python/uv_cmd.rs +++ b/src/cmds/python/uv_cmd.rs @@ -180,7 +180,10 @@ fn program_output(text: &str, tee_slug: &str) -> String { .iter() .map(|line| truncate(line, MAX_PROGRAM_LINE_CHARS)) .collect(); - let line_was_cut = capped.iter().zip(lines).any(|(cut, full)| cut.len() != full.len()); + let line_was_cut = capped + .iter() + .zip(lines) + .any(|(cut, full)| cut.len() != full.len()); if capped.len() <= CAP_INVENTORY { let out = capped.join("\n"); @@ -328,7 +331,9 @@ fn is_error_start(line: &str) -> bool { return true; } - ERROR_START_PATTERNS.iter().any(|pattern| pattern.is_match(line)) + ERROR_START_PATTERNS + .iter() + .any(|pattern| pattern.is_match(line)) } fn is_error_continuation(line: &str) -> bool { @@ -349,7 +354,7 @@ fn is_error_continuation(line: &str) -> bool { #[cfg(test)] mod tests { - use super::{filter_uv_run_output, CAP_INVENTORY, MAX_TRACEBACK_FRAMES}; + use super::{CAP_INVENTORY, MAX_TRACEBACK_FRAMES, filter_uv_run_output}; use crate::core::utils::count_tokens; #[test] diff --git a/src/cmds/ruby/rspec_cmd.rs b/src/cmds/ruby/rspec_cmd.rs index 663f97184f..485a044f68 100644 --- a/src/cmds/ruby/rspec_cmd.rs +++ b/src/cmds/ruby/rspec_cmd.rs @@ -6,7 +6,7 @@ //! fails to parse. use crate::core::runner; -use crate::core::truncate::{reduced, CAP_WARNINGS}; +use crate::core::truncate::{CAP_WARNINGS, reduced}; use crate::core::utils::{fallback_tail, ruby_exec, truncate}; use anyhow::Result; use regex::Regex; diff --git a/src/cmds/rust/cargo_cmd.rs b/src/cmds/rust/cargo_cmd.rs index 6f0dc373a0..2027c8ab51 100644 --- a/src/cmds/rust/cargo_cmd.rs +++ b/src/cmds/rust/cargo_cmd.rs @@ -98,11 +98,8 @@ impl BlockHandler for CargoBuildHandler { fn format_summary(&self, exit_code: i32, raw: &str) -> Option { if self.error_count == 0 && self.warnings == 0 && exit_code == 0 { - let summary = cargo_build_success_line( - self.compiled, - self.finished_line.as_deref(), - self.label, - ); + let summary = + cargo_build_success_line(self.compiled, self.finished_line.as_deref(), self.label); return Some(crate::core::guard::never_worse(raw, &summary).to_string()); } // The streamed path only runs for non-json build/check; error blocks are @@ -380,7 +377,9 @@ fn run_build(args: &[String], verbose: u8) -> Result { "build", args, verbose, - Box::new(BlockStreamFilter::new(CargoBuildHandler::with_label("build"))), + Box::new(BlockStreamFilter::new(CargoBuildHandler::with_label( + "build", + ))), ) } @@ -414,7 +413,9 @@ fn run_check(args: &[String], verbose: u8) -> Result { "check", args, verbose, - Box::new(BlockStreamFilter::new(CargoBuildHandler::with_label("check"))), + Box::new(BlockStreamFilter::new(CargoBuildHandler::with_label( + "check", + ))), ) } @@ -888,7 +889,11 @@ fn extract_json_diagnostics(raw: &str) -> JsonDiagnostics { continue; } if let Some(rendered) = msg.rendered { - bucket.push(crate::core::utils::strip_ansi(&rendered).trim_end().to_string()); + bucket.push( + crate::core::utils::strip_ansi(&rendered) + .trim_end() + .to_string(), + ); } else if !msg.message.is_empty() { bucket.push(msg.message); } @@ -896,7 +901,11 @@ fn extract_json_diagnostics(raw: &str) -> JsonDiagnostics { JsonDiagnostics { errors, warnings } } -fn merge_diag_counts(error_count: usize, warnings: usize, json: &JsonDiagnostics) -> (usize, usize) { +fn merge_diag_counts( + error_count: usize, + warnings: usize, + json: &JsonDiagnostics, +) -> (usize, usize) { ( error_count.max(json.errors.len()), warnings.max(json.warnings.len()), @@ -999,7 +1008,8 @@ fn filter_cargo_build_labeled(output: &str, label: &'static str, exit_code: i32) let (errors, warnings) = merge_diag_counts(handler.error_count, handler.warnings, &json); if errors == 0 && warnings == 0 && exit_code == 0 { - let summary = cargo_build_success_line(handler.compiled, handler.finished_line.as_deref(), label); + let summary = + cargo_build_success_line(handler.compiled, handler.finished_line.as_deref(), label); return crate::core::guard::never_worse(output, &summary).to_string(); } @@ -1221,7 +1231,10 @@ pub(crate) fn filter_cargo_test(output: &str) -> String { result.push_str(&format!("{}. {}\n", i + 1, truncate(failure, 200))); } if failures.len() > MAX_FAILURES { - result.push_str(&format!("\n… +{} more failures\n", failures.len() - MAX_FAILURES)); + result.push_str(&format!( + "\n… +{} more failures\n", + failures.len() - MAX_FAILURES + )); let all_failures = failures.join("\n\n"); if let Some(hint) = crate::core::tee::force_tee_hint(&all_failures, "cargo-test-failures") @@ -1395,8 +1408,7 @@ fn filter_cargo_clippy(output: &str) -> String { .map(|b| b.join("\n")) .collect::>() .join("\n\n"); - if let Some(hint) = - crate::core::tee::force_tee_hint(&all_blocks, "cargo-clippy-errors") + if let Some(hint) = crate::core::tee::force_tee_hint(&all_blocks, "cargo-clippy-errors") { result.push_str(&format!(" {}\n", hint)); } @@ -2479,7 +2491,11 @@ error: aborting due to 1 previous error ); let result = filter_cargo_clippy_json(input, 0); assert!(result.contains("unused variable"), "got: {}", result); - assert!(result.contains("cargo clippy: 0 errors, 1 warnings"), "got: {}", result); + assert!( + result.contains("cargo clippy: 0 errors, 1 warnings"), + "got: {}", + result + ); } #[test] @@ -2496,7 +2512,11 @@ error: aborting due to 1 previous error "a failed build must not be reported as compiled: {}", result ); - assert!(result.contains("cargo build: failed (exit 101)"), "got: {}", result); + assert!( + result.contains("cargo build: failed (exit 101)"), + "got: {}", + result + ); } #[test] @@ -2548,7 +2568,6 @@ error: aborting due to 1 previous error ); } - #[test] fn test_extract_json_diagnostics_skips_generated_summary() { let output = concat!( @@ -2560,7 +2579,11 @@ error: aborting due to 1 previous error "\n", ); let json = extract_json_diagnostics(output); - assert_eq!(json.warnings.len(), 1, "generated summary must not inflate the count"); + assert_eq!( + json.warnings.len(), + 1, + "generated summary must not inflate the count" + ); assert!( !json.warnings.iter().any(|w| w.contains("generated")), "the generated summary line must not appear in the output" @@ -2577,7 +2600,11 @@ error: aborting due to 1 previous error ); let json = extract_json_diagnostics(output); assert_eq!(json.errors.len(), 1, "an ICE must count as an error"); - assert!(json.errors[0].contains("internal compiler error"), "got: {:?}", json.errors[0]); + assert!( + json.errors[0].contains("internal compiler error"), + "got: {:?}", + json.errors[0] + ); } #[test] @@ -2686,7 +2713,11 @@ error: aborting due to 1 previous error let result = filter_cargo_build(&output); let rendered = result.matches("error[E0308]").count(); - assert_eq!(rendered, CAP_ERRORS, "json errors must be capped: {}", result); + assert_eq!( + rendered, CAP_ERRORS, + "json errors must be capped: {}", + result + ); assert!( result.contains(&format!("… +{} more errors", total - CAP_ERRORS)), "expected overflow hint: {}", @@ -2720,7 +2751,11 @@ error: aborting due to 1 previous error // The cap is what bounds the output, so assert it holds here too. let rendered = result.matches("error[E0308]").count(); - assert_eq!(rendered, CAP_ERRORS, "json errors must be capped: {}", result); + assert_eq!( + rendered, CAP_ERRORS, + "json errors must be capped: {}", + result + ); assert!( result.contains(&format!("… +{} more errors", total - CAP_ERRORS)), "expected overflow hint: {}", @@ -2832,10 +2867,17 @@ error: could not compile `rtk` (test "repro_compile_fail") due to 1 previous err ); let mut f = BlockStreamFilter::new(CargoTestHandler::new()); let result = run_block_filter(&mut f, input, 101); - assert!(!result.trim().is_empty(), "json compile error must not be silent"); + assert!( + !result.trim().is_empty(), + "json compile error must not be silent" + ); assert!(result.contains("cargo test:"), "got: {}", result); assert!(result.contains("1 errors"), "got: {}", result); - assert!(result.contains("E0425"), "diagnostic must be surfaced: {}", result); + assert!( + result.contains("E0425"), + "diagnostic must be surfaced: {}", + result + ); } #[test] diff --git a/src/cmds/scala/sbt_cmd.rs b/src/cmds/scala/sbt_cmd.rs index c6adee19d4..401ff34e9c 100644 --- a/src/cmds/scala/sbt_cmd.rs +++ b/src/cmds/scala/sbt_cmd.rs @@ -249,7 +249,10 @@ fn filter_sbt_test(output: &str) -> String { .trim_start_matches('-') .trim() .to_string(); - failures.push(FailureBlock { name, details: Vec::new() }); + failures.push(FailureBlock { + name, + details: Vec::new(), + }); in_failure_detail = true; continue; } @@ -273,8 +276,7 @@ fn filter_sbt_test(output: &str) -> String { // Skip raw JVM stack frames — they add noise without signal. // Keep Mockito "-> at" pointers and ScalaMock locations // (they include the file:line reference). - let is_stack_frame = detail.starts_with("at ") - || detail.starts_with("..."); + let is_stack_frame = detail.starts_with("at ") || detail.starts_with("..."); if !is_stack_frame { if let Some(block) = failures.last_mut() { if block.details.len() < 4 { @@ -541,15 +543,17 @@ mod tests { assert!(output.contains("2 ignored")); assert!(output.contains("5 suites")); assert!(output.contains("5s")); - assert!(!output.contains('\n'), "all-pass output should be a single line"); + assert!( + !output.contains('\n'), + "all-pass output should be a single line" + ); } #[test] fn test_filter_sbt_test_all_pass_token_savings() { let input = include_str!("../../../tests/fixtures/sbt/sbt_test_pass.txt"); let output = filter_sbt_test(input); - let savings = 100.0 - - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); + let savings = 100.0 - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); assert!( savings >= 60.0, "sbt test (pass): expected >=60% savings, got {:.1}%", @@ -594,8 +598,7 @@ mod tests { fn test_filter_sbt_test_fail_token_savings() { let input = include_str!("../../../tests/fixtures/sbt/sbt_test_fail.txt"); let output = filter_sbt_test(input); - let savings = 100.0 - - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); + let savings = 100.0 - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); assert!( savings >= 40.0, "sbt test (fail): expected >=40% savings, got {:.1}%", @@ -628,7 +631,9 @@ mod tests { // Mockito pointer lines ("-> at com.example...") may remain — they // carry the file:line reference that identifies the assertion site. assert!( - !output.lines().any(|l| l.trim_start().starts_with("at com.")), + !output + .lines() + .any(|l| l.trim_start().starts_with("at com.")), "bare stack frame leaked into output: {}", output ); @@ -638,8 +643,7 @@ mod tests { fn test_filter_sbt_test_mockito_token_savings() { let input = include_str!("../../../tests/fixtures/sbt/sbt_test_mockito_fail.txt"); let output = filter_sbt_test(input); - let savings = 100.0 - - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); + let savings = 100.0 - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); assert!( savings >= 40.0, "sbt test (mockito): expected >=40% savings, got {:.1}%", @@ -669,8 +673,7 @@ mod tests { fn test_filter_sbt_test_scalamock_token_savings() { let input = include_str!("../../../tests/fixtures/sbt/sbt_test_scalamock_fail.txt"); let output = filter_sbt_test(input); - let savings = 100.0 - - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); + let savings = 100.0 - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); assert!( savings >= 40.0, "sbt test (scalamock): expected >=40% savings, got {:.1}%", @@ -689,7 +692,10 @@ mod tests { assert!(output.contains("5 passed")); assert!(output.contains("2 suites")); assert!(output.contains("18s")); - assert!(!output.contains('\n'), "all-pass output should be a single line"); + assert!( + !output.contains('\n'), + "all-pass output should be a single line" + ); } #[test] @@ -730,7 +736,10 @@ mod tests { assert!(output.starts_with("sbt test:"), "output: {}", output); assert!(output.contains("12 passed"), "output: {}", output); - assert!(!output.contains('\n'), "all-pass output should be a single line"); + assert!( + !output.contains('\n'), + "all-pass output should be a single line" + ); assert!(!output.contains("parse error"), "output: {}", output); } @@ -749,8 +758,7 @@ mod tests { fn test_filter_sbt_test_munit_token_savings() { let input = include_str!("../../../tests/fixtures/sbt/sbt_test_munit_pass.txt"); let output = filter_sbt_test(input); - let savings = 100.0 - - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); + let savings = 100.0 - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); assert!( savings >= 60.0, "sbt test (munit): expected >=60% savings, got {:.1}%", @@ -787,8 +795,7 @@ mod tests { fn test_filter_sbt_compile_error_token_savings() { let input = include_str!("../../../tests/fixtures/sbt/sbt_compile_error.txt"); let output = filter_sbt_compile(input); - let savings = 100.0 - - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); + let savings = 100.0 - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0); assert!( savings >= 30.0, "sbt compile (error): expected >=30% savings, got {:.1}%", diff --git a/src/cmds/system/ast_grep_cmd.rs b/src/cmds/system/ast_grep_cmd.rs index 3d4661f94e..8280235bf4 100644 --- a/src/cmds/system/ast_grep_cmd.rs +++ b/src/cmds/system/ast_grep_cmd.rs @@ -10,8 +10,9 @@ use std::collections::HashMap; use std::sync::LazyLock; /// Matches the `path:line:` prefix ast-grep emits for every match/context line. -static MATCH_LINE_RE: LazyLock = - LazyLock::new(|| regex::Regex::new(r"^(?P[^:]+):(?P\d+):(?P.*)$").unwrap()); +static MATCH_LINE_RE: LazyLock = LazyLock::new(|| { + regex::Regex::new(r"^(?P[^:]+):(?P\d+):(?P.*)$").unwrap() +}); const DEFAULT_MAX_TOTAL: usize = 50; const DEFAULT_MAX_PER_FILE: usize = 5; @@ -112,7 +113,9 @@ pub fn run(args: &[String]) -> Result { let timer = tracking::TimedExecution::start(); let real_cmd = format!("ast-grep {}", args.join(" ")); - let is_json = args.iter().any(|a| a == "--json" || a.starts_with("--json=")); + let is_json = args + .iter() + .any(|a| a == "--json" || a.starts_with("--json=")); let mut cmd = resolved_command("ast-grep"); cmd.args(args); diff --git a/src/cmds/system/ctest_cmd.rs b/src/cmds/system/ctest_cmd.rs index 83d657de97..2154d29d63 100644 --- a/src/cmds/system/ctest_cmd.rs +++ b/src/cmds/system/ctest_cmd.rs @@ -28,8 +28,7 @@ static TEST_RE: LazyLock = LazyLock::new(|| { .expect("invalid ctest result regex") }); static RESULT_PREFIX_RE: LazyLock = LazyLock::new(|| { - Regex::new(r"^\s*(?:\d+/\d+\s+)?Test\s+#\d+:") - .expect("invalid ctest result prefix regex") + Regex::new(r"^\s*(?:\d+/\d+\s+)?Test\s+#\d+:").expect("invalid ctest result prefix regex") }); static RESULT_TERMINATOR_RE: LazyLock = LazyLock::new(|| { Regex::new(r"[\d.]+\s+sec\s*$").expect("invalid ctest result terminator regex") @@ -136,9 +135,9 @@ fn dashboard_action_changes_output(args: &[String]) -> bool { let mut index = 0; while index < args.len() { let arg = args[index].as_str(); - let (flag, inline_value) = arg.split_once('=').map_or((arg, None), |(flag, value)| { - (flag, Some(value)) - }); + let (flag, inline_value) = arg + .split_once('=') + .map_or((arg, None), |(flag, value)| (flag, Some(value))); match flag { "-T" | "--test-action" => { match inline_value.map(str::to_string).or_else(|| { @@ -201,17 +200,11 @@ pub(crate) fn filter_ctest_output(output: &str) -> String { let lines = build_logical_lines(&clean); let parsed_tests = parse_tests(&lines); - let framing_lines = collect_framing_line_indices( - &lines, - &parsed_tests.tests, - &parsed_tests.result_lines, - ); + let framing_lines = + collect_framing_line_indices(&lines, &parsed_tests.tests, &parsed_tests.result_lines); let tests = parsed_tests.tests; let summary = find_run_summary(&lines, &tests, &framing_lines, parsed_tests.run_total); - let total_time = lines - .iter() - .rev() - .find_map(|line| parse_total_time(line)); + let total_time = lines.iter().rev().find_map(|line| parse_total_time(line)); if tests.is_empty() && summary.is_none() { if let Some(index) = lines.iter().rposition(|line| is_no_tests_line(line)) { @@ -294,7 +287,10 @@ fn build_logical_lines(output: &str) -> Vec { /// lines behind it are kept: on their own, `ctest: no tests found` reads as a /// benign outcome for a run that actually failed. fn format_no_tests(rest: &[String]) -> String { - let mut trailer: Vec = rest.iter().map(|line| line.trim_end().to_string()).collect(); + let mut trailer: Vec = rest + .iter() + .map(|line| line.trim_end().to_string()) + .collect(); trim_blank_edges(&mut trailer); let mut out = String::from("ctest: no tests found"); @@ -331,9 +327,10 @@ fn parse_tests(lines: &[String]) -> ParsedTests { }) }) { result_lines.push(test.line_index); - if let Some(existing) = tests.iter_mut().find(|existing| { - existing.number == test.number && existing.name == test.name - }) { + if let Some(existing) = tests + .iter_mut() + .find(|existing| existing.number == test.number && existing.name == test.name) + { *existing = test; } else { tests.push(test); @@ -371,10 +368,7 @@ fn split_status_reason(raw: &str) -> (String, Option) { return (raw.to_string(), None); }; - let reason = reason - .split_whitespace() - .collect::>() - .join(" "); + let reason = reason.split_whitespace().collect::>().join(" "); let reason = match reason.strip_suffix(" ]") { Some(head) => format!("{head}]"), None => reason, @@ -546,11 +540,7 @@ fn format_failure( out.push_str(&format_meta(total_time)); if !failed_tests.is_empty() { out.push('\n'); - out.push_str(&format_failed_section( - lines, - &failed_tests, - framing_lines, - )); + out.push_str(&format_failed_section(lines, &failed_tests, framing_lines)); } if !unparsed_failed_entries.is_empty() { out.push('\n'); @@ -768,10 +758,7 @@ fn collect_unparsed_failed_entries( let Some(start) = failed_trailer_start(lines, framing_lines) else { return Vec::new(); }; - let parsed_numbers: HashSet = parsed_failed_tests - .iter() - .map(|test| test.number) - .collect(); + let parsed_numbers: HashSet = parsed_failed_tests.iter().map(|test| test.number).collect(); lines[start + 1..] .iter() @@ -788,10 +775,8 @@ fn collect_unparsed_failed_entries( } fn format_unparsed_failed_section(entries: &[String]) -> String { - let formatted_entries: Vec = entries - .iter() - .map(|entry| format!(" {entry}")) - .collect(); + let formatted_entries: Vec = + entries.iter().map(|entry| format!(" {entry}")).collect(); let mut section = String::from("failed (unparsed, raw):"); for entry in formatted_entries.iter().take(MAX_FAILED_LIST_LINES) { section.push('\n'); @@ -1090,7 +1075,10 @@ Total Test time (real) = 0.01 sec vec!["-T", "Test", "--output-on-failure"], ] { let args: Vec = filtered.iter().map(ToString::to_string).collect(); - assert!(!should_passthrough(&args), "{filtered:?} should be filtered"); + assert!( + !should_passthrough(&args), + "{filtered:?} should be filtered" + ); } for passthrough in [ @@ -1124,8 +1112,7 @@ Total Test time (real) = 0.01 sec .lines() .filter(|line| { let trimmed = line.trim_start(); - !trimmed.starts_with("[full output:") - && !trimmed.starts_with("[see remaining:") + !trimmed.starts_with("[full output:") && !trimmed.starts_with("[see remaining:") }) .collect::>() .join("\n") @@ -1134,11 +1121,8 @@ Total Test time (real) = 0.01 sec fn failed_section_parts(input: &str) -> (String, String, bool) { let lines = build_logical_lines(input); let parsed_tests = parse_tests(&lines); - let framing_lines = collect_framing_line_indices( - &lines, - &parsed_tests.tests, - &parsed_tests.result_lines, - ); + let framing_lines = + collect_framing_line_indices(&lines, &parsed_tests.tests, &parsed_tests.result_lines); let failed_tests: Vec<&TestCase> = parsed_tests .tests .iter() @@ -1376,9 +1360,8 @@ extra 3 #[test] fn keeps_failure_output_that_mentions_no_tests_found() { - let input = include_str!( - "../../../tests/fixtures/ctest_discovery_fail_output_on_failure_raw.txt" - ); + let input = + include_str!("../../../tests/fixtures/ctest_discovery_fail_output_on_failure_raw.txt"); assert_eq!( filter_ctest_output(input), diff --git a/src/cmds/system/deps.rs b/src/cmds/system/deps.rs index 9d040d37bf..2a38fdaede 100644 --- a/src/cmds/system/deps.rs +++ b/src/cmds/system/deps.rs @@ -2,7 +2,7 @@ use crate::core::guard::never_worse; use crate::core::tracking; -use crate::core::truncate::{reduced, CAP_WARNINGS}; +use crate::core::truncate::{CAP_WARNINGS, reduced}; use anyhow::{Context, Result}; use regex::Regex; use std::fs; @@ -81,8 +81,8 @@ pub fn run(path: &Path, verbose: u8) -> Result<()> { } fn summarize_cargo_str(path: &Path) -> Result { - let content = fs::read_to_string(path) - .with_context(|| format!("Failed to read {}", path.display()))?; + let content = + fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?; let dep_re = Regex::new(r#"^([a-zA-Z0-9_-]+)\s*=\s*(?:"([^"]+)"|.*version\s*=\s*"([^"]+)")"#).unwrap(); let section_re = Regex::new(r"^\[([^\]]+)\]").unwrap(); @@ -128,15 +128,18 @@ fn summarize_cargo_str(path: &Path) -> Result { out.push_str(&format!(" {}\n", d)); } if dev_deps.len() > MAX_DEV_DEPS { - out.push_str(&format!(" ... +{} more\n", dev_deps.len() - MAX_DEV_DEPS)); + out.push_str(&format!( + " ... +{} more\n", + dev_deps.len() - MAX_DEV_DEPS + )); } } Ok(out) } fn summarize_package_json_str(path: &Path) -> Result { - let content = fs::read_to_string(path) - .with_context(|| format!("Failed to read {}", path.display()))?; + let content = + fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?; let json: serde_json::Value = crate::core::utils::from_json_str(&content) .with_context(|| format!("Failed to parse {}", path.display()))?; let mut out = String::new(); @@ -163,7 +166,10 @@ fn summarize_package_json_str(path: &Path) -> Result { out.push_str(&format!(" Dev Dependencies ({}):\n", dev_deps.len())); for (i, (name, _)) in dev_deps.iter().enumerate() { if i >= MAX_DEV_DEPS { - out.push_str(&format!(" ... +{} more\n", dev_deps.len() - MAX_DEV_DEPS)); + out.push_str(&format!( + " ... +{} more\n", + dev_deps.len() - MAX_DEV_DEPS + )); break; } out.push_str(&format!(" {}\n", name)); @@ -173,8 +179,8 @@ fn summarize_package_json_str(path: &Path) -> Result { } fn summarize_requirements_str(path: &Path) -> Result { - let content = fs::read_to_string(path) - .with_context(|| format!("Failed to read {}", path.display()))?; + let content = + fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?; let dep_re = Regex::new(r"^([a-zA-Z0-9_-]+)([=<>!~]+.*)?$").unwrap(); let mut deps = Vec::new(); let mut out = String::new(); @@ -202,8 +208,8 @@ fn summarize_requirements_str(path: &Path) -> Result { } fn summarize_pyproject_str(path: &Path) -> Result { - let content = fs::read_to_string(path) - .with_context(|| format!("Failed to read {}", path.display()))?; + let content = + fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?; let mut in_deps = false; let mut deps = Vec::new(); let mut out = String::new(); @@ -239,8 +245,8 @@ fn summarize_pyproject_str(path: &Path) -> Result { } fn summarize_gomod_str(path: &Path) -> Result { - let content = fs::read_to_string(path) - .with_context(|| format!("Failed to read {}", path.display()))?; + let content = + fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?; let mut module_name = String::new(); let mut go_version = String::new(); let mut deps = Vec::new(); diff --git a/src/cmds/system/find_cmd.rs b/src/cmds/system/find_cmd.rs index d9916ca025..a0d1a67a4a 100644 --- a/src/cmds/system/find_cmd.rs +++ b/src/cmds/system/find_cmd.rs @@ -907,7 +907,9 @@ mod tests { #[test] fn rtk_flags_mid_expression_reach_find_untouched() { assert_eq!( - class(&[".", "-name", "*.rs", "-m", "5", "-exec", "grep", "-m", "1", "x", "{}", ";"]), + class(&[ + ".", "-name", "*.rs", "-m", "5", "-exec", "grep", "-m", "1", "x", "{}", ";" + ]), "verbatim" ); match dispatch(&args(&[".", "-m", "5", "-mtime", "-7"])).unwrap() { diff --git a/src/cmds/system/json_cmd.rs b/src/cmds/system/json_cmd.rs index f7d75a3f30..2e37e68b47 100644 --- a/src/cmds/system/json_cmd.rs +++ b/src/cmds/system/json_cmd.rs @@ -3,7 +3,7 @@ use crate::core::guard::never_worse; use crate::core::tracking; use crate::core::utils::{from_json_str, strip_leading_bom}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use serde_json::Value; use std::borrow::Cow; use std::fs; @@ -90,11 +90,7 @@ pub fn run_stdin(max_depth: usize, schema_only: bool, verbose: u8) -> Result<()> /// Returns `Cow` rather than an owned `String`: the raw-fallback case can /// then stay a zero-copy borrow of `content` instead of paying for another /// full copy of the (potentially large) input on every fallback. -fn render_json<'a>( - content: &'a str, - max_depth: usize, - schema_only: bool, -) -> Result> { +fn render_json<'a>(content: &'a str, max_depth: usize, schema_only: bool) -> Result> { let content = strip_leading_bom(content); let output = if schema_only { filter_json_string(content, max_depth)? @@ -117,8 +113,7 @@ fn render_json<'a>( /// Parse a JSON string and return compact representation with values preserved. /// Long strings are truncated, arrays are summarized. pub fn filter_json_compact(json_str: &str, max_depth: usize) -> Result { - let value: Value = - from_json_str(json_str).context("Failed to parse JSON")?; + let value: Value = from_json_str(json_str).context("Failed to parse JSON")?; Ok(compact_json(&value, 0, max_depth)) } @@ -209,8 +204,7 @@ fn compact_json(value: &Value, depth: usize, max_depth: usize) -> String { /// Parse a JSON string and return its schema representation (types only, no values). /// Useful for piping JSON from other commands (e.g., `gh api`, `curl`). pub fn filter_json_string(json_str: &str, max_depth: usize) -> Result { - let value: Value = - from_json_str(json_str).context("Failed to parse JSON")?; + let value: Value = from_json_str(json_str).context("Failed to parse JSON")?; Ok(extract_schema(&value, 0, max_depth)) } diff --git a/src/cmds/system/log_cmd.rs b/src/cmds/system/log_cmd.rs index 47ed17925c..496050eaf5 100644 --- a/src/cmds/system/log_cmd.rs +++ b/src/cmds/system/log_cmd.rs @@ -2,7 +2,7 @@ use crate::core::guard::never_worse; use crate::core::tracking; -use crate::core::truncate::{reduced, CAP_WARNINGS}; +use crate::core::truncate::{CAP_WARNINGS, reduced}; use anyhow::Result; use regex::Regex; use std::collections::HashMap; @@ -261,8 +261,14 @@ mod tests { 2024-01-01 10:00:03 SEVERE: data corruption detected\n\ 2024-01-01 10:00:04 notice: config reloaded\n"; let result = analyze_logs(logs); - assert!(result.contains("ERRORS"), "critical/alert/emerg/severe should count as errors"); - assert!(result.contains("WARNINGS"), "notice should count as warning"); + assert!( + result.contains("ERRORS"), + "critical/alert/emerg/severe should count as errors" + ); + assert!( + result.contains("WARNINGS"), + "notice should count as warning" + ); } #[test] diff --git a/src/cmds/system/ls.rs b/src/cmds/system/ls.rs index 13f2162a70..7b8c56da26 100644 --- a/src/cmds/system/ls.rs +++ b/src/cmds/system/ls.rs @@ -520,7 +520,8 @@ mod tests { // is the standard one-shot retrieval command over the hidden entries. if hint.lines().count() > 1 { assert!( - hint.contains("[see remaining: tail -n +1 ") || hint.contains("hidden: rtk recall "), + hint.contains("[see remaining: tail -n +1 ") + || hint.contains("hidden: rtk recall "), "recovery hint must be a standard retrieval form: {hint}" ); } @@ -530,15 +531,21 @@ mod tests { fn test_hidden_hint_note_variants() { let t = vec!["x 1B".to_string()]; let f = vec!["target/".to_string()]; - assert!(hidden_hint(&t, &[]) - .expect("hint") - .starts_with("... (1 more)")); - assert!(hidden_hint(&[], &f) - .expect("hint") - .starts_with("... (1 filtered)")); - assert!(hidden_hint(&t, &f) - .expect("hint") - .starts_with("... (1 more, 1 filtered)")); + assert!( + hidden_hint(&t, &[]) + .expect("hint") + .starts_with("... (1 more)") + ); + assert!( + hidden_hint(&[], &f) + .expect("hint") + .starts_with("... (1 filtered)") + ); + assert!( + hidden_hint(&t, &f) + .expect("hint") + .starts_with("... (1 more, 1 filtered)") + ); } #[test] diff --git a/src/cmds/system/pipe_cmd.rs b/src/cmds/system/pipe_cmd.rs index 6f3cf27bc0..d8a488a7e3 100644 --- a/src/cmds/system/pipe_cmd.rs +++ b/src/cmds/system/pipe_cmd.rs @@ -96,7 +96,10 @@ fn grep_wrapper(input: &str) -> String { if parts.len() == 3 { if let Ok(_line_num) = parts[1].parse::() { total += 1; - by_file.entry(parts[0]).or_default().push((parts[1], parts[2])); + by_file + .entry(parts[0]) + .or_default() + .push((parts[1], parts[2])); } } } @@ -242,11 +245,12 @@ fn identity_filter(input: &str) -> String { } fn apply_filter(filter_fn: fn(&str) -> String, input: &str) -> String { - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| filter_fn(input))) - .unwrap_or_else(|_| { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| filter_fn(input))).unwrap_or_else( + |_| { eprintln!("[rtk] warning: filter panicked — passing through raw output"); input.to_string() - }) + }, + ) } pub fn run(filter_name: Option<&str>, passthrough: bool) -> Result<()> { @@ -317,7 +321,11 @@ mod tests { fn test_resolve_filter_cargo_test() { let f = resolve_filter("cargo-test").expect("cargo-test filter must exist"); let out = f("test result: ok. 5 passed; 0 failed"); - assert!(out.contains("passed") || out.contains("PASS"), "out={}", out); + assert!( + out.contains("passed") || out.contains("PASS"), + "out={}", + out + ); } #[test] @@ -633,7 +641,9 @@ Total Test time (real) = 0.01 sec\n"; assert!( savings >= 40.0, // TODO: grep pipe filter below 60% target — improve grouping "grep filter: expected ≥40% savings, got {:.1}% (in={}, out={})", - savings, count_tokens(&input), count_tokens(&output) + savings, + count_tokens(&input), + count_tokens(&output) ); } @@ -654,7 +664,9 @@ Total Test time (real) = 0.01 sec\n"; assert!( savings >= 40.0, // TODO: find pipe filter below 60% target — improve grouping "find filter: expected ≥40% savings, got {:.1}% (in={}, out={})", - savings, count_tokens(&input), count_tokens(&output) + savings, + count_tokens(&input), + count_tokens(&output) ); } diff --git a/src/cmds/system/read.rs b/src/cmds/system/read.rs index 3875d69d25..c8fd457101 100644 --- a/src/cmds/system/read.rs +++ b/src/cmds/system/read.rs @@ -24,8 +24,8 @@ pub fn run( } // Read file content - let bytes = fs::read(file) - .with_context(|| format!("Failed to read file: {}", file.display()))?; + let bytes = + fs::read(file).with_context(|| format!("Failed to read file: {}", file.display()))?; if level == FilterLevel::None && !line_numbers { if let Some(window) = byte_line_window(&bytes, head_lines, tail_lines) { io::stdout() @@ -95,12 +95,7 @@ pub fn run( }; let shown = never_worse(&raw, &rtk_output); print!("{}", shown); - timer.track( - &format!("cat {}", file.display()), - "rtk read", - &raw, - shown, - ); + timer.track(&format!("cat {}", file.display()), "rtk read", &raw, shown); Ok(()) } @@ -284,7 +279,15 @@ fn main() {{ )?; // Just verify it doesn't panic - run(file.path(), FilterLevel::Minimal, None, None, None, false, 0)?; + run( + file.path(), + FilterLevel::Minimal, + None, + None, + None, + false, + 0, + )?; Ok(()) } @@ -360,7 +363,10 @@ fn main() {{ #[test] fn test_head_window_empty_input() { - assert_eq!(apply_line_window("", None, Some(5), None, &Language::Unknown), ""); + assert_eq!( + apply_line_window("", None, Some(5), None, &Language::Unknown), + "" + ); } #[test] @@ -408,7 +414,10 @@ fn main() {{ #[test] fn test_tail_window_empty_input() { - assert_eq!(apply_line_window("", None, None, Some(5), &Language::Unknown), ""); + assert_eq!( + apply_line_window("", None, None, Some(5), &Language::Unknown), + "" + ); } #[test] @@ -495,7 +504,11 @@ fn main() {{ writeln!(f2, "charlie\ndelta").unwrap(); let output = std::process::Command::new(&bin) - .args(["read", &f1.path().to_string_lossy(), &f2.path().to_string_lossy()]) + .args([ + "read", + &f1.path().to_string_lossy(), + &f2.path().to_string_lossy(), + ]) .output() .expect("failed to run rtk read"); @@ -515,15 +528,28 @@ fn main() {{ writeln!(f1, "valid content").unwrap(); let output = std::process::Command::new(&bin) - .args(["read", &f1.path().to_string_lossy(), "/tmp/rtk_nonexistent_file.txt"]) + .args([ + "read", + &f1.path().to_string_lossy(), + "/tmp/rtk_nonexistent_file.txt", + ]) .output() .expect("failed to run rtk read"); - assert!(!output.status.success(), "should exit non-zero on missing file"); + assert!( + !output.status.success(), + "should exit non-zero on missing file" + ); let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); - assert!(stdout.contains("valid content"), "valid file should still be printed"); - assert!(stderr.contains("rtk_nonexistent_file"), "should report missing file on stderr"); + assert!( + stdout.contains("valid content"), + "valid file should still be printed" + ); + assert!( + stderr.contains("rtk_nonexistent_file"), + "should report missing file on stderr" + ); } #[test] diff --git a/src/cmds/system/search.rs b/src/cmds/system/search.rs index 98a5afb795..416fa368d9 100644 --- a/src/cmds/system/search.rs +++ b/src/cmds/system/search.rs @@ -5,7 +5,7 @@ use crate::core::arg_tokenizer::{self, Dialect, Token, TokenKind, ValueSpec}; use crate::core::stream::{ - self, exec_capture, exec_capture_stdin, CaptureResult, FilterMode, StdinMode, StreamFilter, + self, CaptureResult, FilterMode, StdinMode, StreamFilter, exec_capture, exec_capture_stdin, }; use crate::core::tracking; use crate::core::utils::{resolved_command, strip_ansi}; @@ -291,7 +291,10 @@ fn extract_pattern_path>( recursive = true; } } - if cluster.iter().any(|c| is_context_token(engine, c.kind, c.text)) { + if cluster + .iter() + .any(|c| is_context_token(engine, c.kind, c.text)) + { context = true; } let (bool_chars, value_char) = match cluster.split_last() { @@ -595,10 +598,12 @@ pub fn run( // scoped before the boundary, because `rtk grep -- --version` searches *for* that string. // `-h` is engine-specific: rg's is --help, grep's is --no-filename. let help_tokens = tokenize_search_args(args, engine); - let asks_for_help = arg_tokenizer::before_dashdash(&help_tokens).iter().any(|t| { - (t.kind == TokenKind::Long && matches!(t.text, "version" | "help")) - || (t.kind == TokenKind::Short && t.text == "h" && engine == Engine::Rg) - }); + let asks_for_help = arg_tokenizer::before_dashdash(&help_tokens) + .iter() + .any(|t| { + (t.kind == TokenKind::Long && matches!(t.text, "version" | "help")) + || (t.kind == TokenKind::Short && t.text == "h" && engine == Engine::Rg) + }); let dangling_value_flag = help_tokens.iter().any(|t| { matches!(t.kind, TokenKind::Long | TokenKind::Short) && search_takes_value(engine, t.kind, t.text).is_some() @@ -876,7 +881,9 @@ fn is_format_flag_token(engine: Engine, kind: TokenKind, text: &str) -> bool { // grep's `--initial-tab` pads and tabs every match line, so RTK's own `-H --null -n` // parse reads nothing back and leaked the injected flags into the output. ripgrep has // no such flag, and its `-T` is `--type-not`, a value-taking flag (see rg_takes_value). - TokenKind::Long => LONG.contains(&text) || (engine == Engine::Grep && text == "initial-tab"), + TokenKind::Long => { + LONG.contains(&text) || (engine == Engine::Grep && text == "initial-tab") + } // -c count, -l/-L lists, -o only-matching, -q quiet, -b byte-offset, -Z NUL are shared; // -L/-T/-z mean something unrelated to output shape for rg specifically (see above). TokenKind::Short => match text { @@ -1081,8 +1088,6 @@ mod tests { assert!(compact.len() <= 60); } - - #[test] fn streaming_search_preserves_native_shape() { let mut filter = SearchStreamFilter { @@ -1227,8 +1232,7 @@ mod tests { assert_eq!(detected.show_file, Some(false)); assert!(!flags.iter().any(|f| f == "--no-filename")); - let (_, _, flags, _, detected) = - extract_pattern_path(&["-ih", "x", "a.txt"], Engine::Grep); + let (_, _, flags, _, detected) = extract_pattern_path(&["-ih", "x", "a.txt"], Engine::Grep); assert_eq!(detected.show_file, Some(false)); assert_eq!(flags, vec!["-i"], "the rest of the cluster survives"); @@ -1708,7 +1712,10 @@ mod tests { // Same for show_file's -H/-r/-R and show_line's -n/-N letters. let (_, _, _, _, detected) = extract_pattern_path(&["--replace", "-Hart", "pattern", "src"], Engine::Rg); - assert_eq!(detected.show_file, None, "--replace's value must not trigger -H"); + assert_eq!( + detected.show_file, None, + "--replace's value must not trigger -H" + ); let (_, _, _, _, detected) = extract_pattern_path(&["--replace", "-normal", "pattern", "src"], Engine::Rg); @@ -1874,8 +1881,14 @@ mod tests { for negation in ["-N", "--no-line-number"] { let (_, _, flags, _, detected) = extract_pattern_path(&["pattern", "-n", negation], Engine::Grep); - assert!(detected.show_line, "{negation} is not grep's, so -n still stands"); - assert!(flags.iter().any(|f| f == negation), "{negation} must reach grep"); + assert!( + detected.show_line, + "{negation} is not grep's, so -n still stands" + ); + assert!( + flags.iter().any(|f| f == negation), + "{negation} must reach grep" + ); } } diff --git a/src/cmds/system/summary.rs b/src/cmds/system/summary.rs index 5fdb0c6716..204dee6c7a 100644 --- a/src/cmds/system/summary.rs +++ b/src/cmds/system/summary.rs @@ -258,7 +258,10 @@ fn summarize_json(output: &str, result: &mut Vec) { result.push(format!(" • {}", key)); } if obj.len() > MAX_SUMMARY_KEYS { - result.push(format!(" ... +{} more keys", obj.len() - MAX_SUMMARY_KEYS)); + result.push(format!( + " ... +{} more keys", + obj.len() - MAX_SUMMARY_KEYS + )); } } _ => { diff --git a/src/core/retriever.rs b/src/core/retriever.rs index 86d348635c..c053cc6c17 100644 --- a/src/core/retriever.rs +++ b/src/core/retriever.rs @@ -3,10 +3,10 @@ use super::constants::RECALL_DB; use crate::core::config::Config; use anyhow::{Context, Result}; +use flate2::Compression; use flate2::read::GzDecoder; use flate2::write::GzEncoder; -use flate2::Compression; -use rusqlite::{params, Connection, OptionalExtension}; +use rusqlite::{Connection, OptionalExtension, params}; use serde::{Deserialize, Serialize}; use std::io::{Read, Write}; use std::path::PathBuf; diff --git a/src/core/tee.rs b/src/core/tee.rs index e87e736395..5751653d07 100644 --- a/src/core/tee.rs +++ b/src/core/tee.rs @@ -2,7 +2,7 @@ use crate::core::config::Config; pub(crate) use crate::core::retriever::MIN_FAILURE_BYTES as MIN_TEE_SIZE; -use crate::core::retriever::{self, RecoveryMode, RetrieverConfig, Stored, MIN_FAILURE_BYTES}; +use crate::core::retriever::{self, MIN_FAILURE_BYTES, RecoveryMode, RetrieverConfig, Stored}; fn active() -> Option<(RecoveryMode, RetrieverConfig)> { if retriever::recovery_disabled_by_env() { diff --git a/src/core/telemetry.rs b/src/core/telemetry.rs index 233a73346a..174d6e9298 100644 --- a/src/core/telemetry.rs +++ b/src/core/telemetry.rs @@ -766,9 +766,11 @@ mod tests { stats.avg_savings_per_command ); assert!( - ["claude", "gemini", "codex", "cursor", "copilot", "vibe", "none", "unknown"] - .iter() - .any(|&h| stats.hook_type.starts_with(h)), + [ + "claude", "gemini", "codex", "cursor", "copilot", "vibe", "none", "unknown" + ] + .iter() + .any(|&h| stats.hook_type.starts_with(h)), "Unexpected hook type: {}", stats.hook_type ); @@ -778,8 +780,10 @@ mod tests { fn test_detect_hook_type_returns_known() { let ht = detect_hook_type(); assert!( - ["claude", "gemini", "codex", "cursor", "copilot", "vibe", "none", "unknown"] - .contains(&ht.as_str()), + [ + "claude", "gemini", "codex", "cursor", "copilot", "vibe", "none", "unknown" + ] + .contains(&ht.as_str()), "Unexpected hook type: {}", ht ); diff --git a/src/core/toml_filter.rs b/src/core/toml_filter.rs index 4db12965ad..2e82606a27 100644 --- a/src/core/toml_filter.rs +++ b/src/core/toml_filter.rs @@ -2145,9 +2145,11 @@ match_command = "^make\\b" let unanchored = "schema_version = 1\n[filters.mytool]\nmatch_command = \"(?:^|/)mytool\\\\b\"\n"; assert!(match_patterns_in(unanchored).is_empty()); - assert!(TomlFilterRegistry::parse_and_compile(unanchored, "test") - .expect("schema is valid") - .is_empty()); + assert!( + TomlFilterRegistry::parse_and_compile(unanchored, "test") + .expect("schema is valid") + .is_empty() + ); let anchored = "schema_version = 1\n[filters.mytool]\nmatch_command = \"^mytool\\\\b\"\n"; assert_eq!(match_patterns_in(anchored), vec!["^mytool\\b".to_string()]); diff --git a/src/core/tracking.rs b/src/core/tracking.rs index 918d40d4bb..709d2263fb 100644 --- a/src/core/tracking.rs +++ b/src/core/tracking.rs @@ -31,7 +31,7 @@ use anyhow::{Context, Result}; use chrono::{DateTime, Utc}; -use rusqlite::{params, Connection}; +use rusqlite::{Connection, params}; use serde::Serialize; use std::collections::HashMap; use std::ffi::OsString; @@ -2439,10 +2439,12 @@ mod tests { fn test_earliest_hook_decision_timestamp() { let tracker = Tracker::new_in_memory().expect("Failed to create in-memory tracker"); - assert!(tracker - .earliest_hook_decision_timestamp() - .expect("query failed") - .is_none()); + assert!( + tracker + .earliest_hook_decision_timestamp() + .expect("query failed") + .is_none() + ); tracker .record_hook_decision( @@ -2456,10 +2458,12 @@ mod tests { ) .expect("Failed to record hook decision"); - assert!(tracker - .earliest_hook_decision_timestamp() - .expect("query failed") - .is_some()); + assert!( + tracker + .earliest_hook_decision_timestamp() + .expect("query failed") + .is_some() + ); } #[test] @@ -2480,10 +2484,12 @@ mod tests { tracker.reset_all().expect("Failed to reset"); - assert!(tracker - .earliest_hook_decision_timestamp() - .expect("query failed") - .is_none()); + assert!( + tracker + .earliest_hook_decision_timestamp() + .expect("query failed") + .is_none() + ); } // rtk-ai/rtk#3206 review: hook_decisions grows unbounded for a user whose diff --git a/src/core/truncate.rs b/src/core/truncate.rs index c1776cdcbf..58285f66d0 100644 --- a/src/core/truncate.rs +++ b/src/core/truncate.rs @@ -13,11 +13,7 @@ pub const CAP_INVENTORY: usize = 50; /// A cap reduced for a verbose data class. Falls back to `cap` when `by >= cap` /// so a deviation can never empty the list; `0` stays `0`. `const fn`, underflow-safe. pub const fn reduced(cap: usize, by: usize) -> usize { - if by < cap { - cap - by - } else { - cap - } + if by < cap { cap - by } else { cap } } #[cfg(test)] diff --git a/src/core/utils.rs b/src/core/utils.rs index 92413c076a..4e63fa4aca 100644 --- a/src/core/utils.rs +++ b/src/core/utils.rs @@ -205,11 +205,7 @@ pub fn join_with_overflow(items: &[String], total: usize, max: usize, label: &st /// assert_eq!(truncate_iso_date("short"), "short"); /// ``` pub fn truncate_iso_date(date: &str) -> &str { - if date.len() >= 10 { - &date[..10] - } else { - date - } + if date.len() >= 10 { &date[..10] } else { date } } /// Format a confirmation message: "ok \ \" diff --git a/src/discover/lexer.rs b/src/discover/lexer.rs index 19b0228f40..6e7d7b46b7 100644 --- a/src/discover/lexer.rs +++ b/src/discover/lexer.rs @@ -389,7 +389,7 @@ fn contains_substitution(cmd: &str) -> bool { b'`' if !in_single => return true, b'$' if !in_single && bytes.get(i + 1) == Some(&b'(') => return true, b'<' | b'>' if !in_single && !in_double && bytes.get(i + 1) == Some(&b'(') => { - return true + return true; } _ => {} } @@ -637,9 +637,11 @@ mod tests { #[test] fn test_quoted_operator_not_split() { let tokens = tokenize(r#"git commit -m "Fix && Bug""#); - assert!(!tokens - .iter() - .any(|t| matches!(t.kind, TokenKind::Operator) && t.value == "&&")); + assert!( + !tokens + .iter() + .any(|t| matches!(t.kind, TokenKind::Operator) && t.value == "&&") + ); assert!(tokens.iter().any(|t| t.value.contains("Fix && Bug"))); } @@ -728,25 +730,31 @@ mod tests { #[test] fn test_and_operator() { let tokens = tokenize("cmd1 && cmd2"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Operator && t.value == "&&")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Operator && t.value == "&&") + ); } #[test] fn test_or_operator() { let tokens = tokenize("cmd1 || cmd2"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Operator && t.value == "||")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Operator && t.value == "||") + ); } #[test] fn test_semicolon() { let tokens = tokenize("cmd1 ; cmd2"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Operator && t.value == ";")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Operator && t.value == ";") + ); } #[test] @@ -784,9 +792,11 @@ mod tests { #[test] fn test_pipe_detection() { let tokens = tokenize("cat file | grep pattern"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Pipe(PipeKind::Stdout))); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Pipe(PipeKind::Stdout)) + ); } #[test] @@ -800,9 +810,11 @@ mod tests { assert_eq!(pipes.len(), 1); assert_eq!(pipes[0].kind, TokenKind::Pipe(PipeKind::StdoutAndStderr)); assert_eq!(pipes[0].value, "|&"); - assert!(!tokens - .iter() - .any(|token| token.kind == TokenKind::Shellism && token.value == "&")); + assert!( + !tokens + .iter() + .any(|token| token.kind == TokenKind::Shellism && token.value == "&") + ); assert_eq!( split_for_permissions("cargo test |& grep FAILED"), vec!["cargo test", "grep FAILED"] @@ -922,9 +934,11 @@ mod tests { #[test] fn test_escaped_glob() { let tokens = tokenize("echo \\*.txt"); - assert!(!tokens - .iter() - .any(|t| t.kind == TokenKind::Shellism && t.value == "*")); + assert!( + !tokens + .iter() + .any(|t| t.kind == TokenKind::Shellism && t.value == "*") + ); } #[test] @@ -936,9 +950,11 @@ mod tests { #[test] fn test_redirect_append() { let tokens = tokenize("cmd >> file"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == ">>")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == ">>") + ); } #[test] @@ -950,28 +966,36 @@ mod tests { #[test] fn test_redirect_stderr() { let tokens = tokenize("cmd 2> file"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value.starts_with("2>"))); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value.starts_with("2>")) + ); } #[test] fn test_redirect_stderr_no_space() { let tokens = tokenize("cmd 2>/dev/null"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == "2>")); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Arg && t.value == "/dev/null")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == "2>") + ); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Arg && t.value == "/dev/null") + ); } #[test] fn test_redirect_dev_null() { let tokens = tokenize("cmd > /dev/null"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == ">")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == ">") + ); } #[test] @@ -980,49 +1004,61 @@ mod tests { assert_eq!(tokens.len(), 2); assert_eq!(tokens[1].kind, TokenKind::Redirect); assert_eq!(tokens[1].value, "2>&1"); - assert!(!tokens - .iter() - .any(|t| t.kind == TokenKind::Shellism && t.value == "&")); + assert!( + !tokens + .iter() + .any(|t| t.kind == TokenKind::Shellism && t.value == "&") + ); } #[test] fn test_redirect_1_to_2_single_token() { let tokens = tokenize("cmd 1>&2"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == "1>&2")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == "1>&2") + ); } #[test] fn test_redirect_fd_close() { let tokens = tokenize("cmd 2>&-"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == "2>&-")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == "2>&-") + ); } #[test] fn test_redirect_shorthand_dup() { let tokens = tokenize("cmd >&2"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == ">&2")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == ">&2") + ); } #[test] fn test_redirect_amp_gt() { let tokens = tokenize("cmd &>/dev/null"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == "&>")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == "&>") + ); } #[test] fn test_redirect_amp_gt_gt() { let tokens = tokenize("cmd &>>/dev/null"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == "&>>")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == "&>>") + ); } #[test] @@ -1040,61 +1076,77 @@ mod tests { #[test] fn test_redirect_append_to_file() { let tokens = tokenize("echo hello >> /tmp/output.txt"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == ">>")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == ">>") + ); } #[test] fn test_redirect_heredoc_marker() { let tokens = tokenize("cat <&1 | head"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == "2>&1")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == "2>&1") + ); assert!(tokens.iter().any(|t| matches!(t.kind, TokenKind::Pipe(_)))); } #[test] fn test_redirect_2_to_1_with_and() { let tokens = tokenize("cargo test 2>&1 && echo done"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == "2>&1")); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Operator && t.value == "&&")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == "2>&1") + ); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Operator && t.value == "&&") + ); } #[test] fn test_exclamation_is_shellism() { let tokens = tokenize("if ! grep -q pattern file; then echo missing; fi"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Shellism && t.value == "!")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Shellism && t.value == "!") + ); } #[test] fn test_background_job_is_shellism() { let tokens = tokenize("sleep 10 &"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Shellism && t.value == "&")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Shellism && t.value == "&") + ); } #[test] fn test_background_not_confused_with_amp_redirect() { let tokens = tokenize("cargo test &>/dev/null"); - assert!(!tokens - .iter() - .any(|t| t.kind == TokenKind::Shellism && t.value == "&")); + assert!( + !tokens + .iter() + .any(|t| t.kind == TokenKind::Shellism && t.value == "&") + ); assert!(tokens.iter().any(|t| t.kind == TokenKind::Redirect)); } @@ -1173,23 +1225,31 @@ mod tests { #[test] fn test_fd_redirect_needs_adjacent_digit() { let tokens = tokenize("echo 2 > file"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Arg && t.value == "2")); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == ">")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Arg && t.value == "2") + ); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == ">") + ); } #[test] fn test_fd_redirect_no_space() { let tokens = tokenize("echo 2>file"); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Redirect && t.value == "2>")); - assert!(tokens - .iter() - .any(|t| t.kind == TokenKind::Arg && t.value == "file")); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Redirect && t.value == "2>") + ); + assert!( + tokens + .iter() + .any(|t| t.kind == TokenKind::Arg && t.value == "file") + ); } #[test] diff --git a/src/discover/mod.rs b/src/discover/mod.rs index 4bfba2395d..859dbbcfa7 100644 --- a/src/discover/mod.rs +++ b/src/discover/mod.rs @@ -12,14 +12,14 @@ use std::collections::HashMap; use provider::{ClaudeProvider, SessionProvider}; use registry::{ - category_avg_tokens, classify_command, split_command_chain, strip_disabled_prefix, - Classification, ExcludePattern, + Classification, ExcludePattern, category_avg_tokens, classify_command, split_command_chain, + strip_disabled_prefix, }; use report::{DiscoverReport, SupportedEntry, UnsupportedEntry}; use crate::core::tracking::{HookDecisionRecord, Tracker}; use crate::discover::registry::prefix_contains_rtk_disabled; -use crate::hooks::hook_check::{status as hook_status, HookStatus}; +use crate::hooks::hook_check::{HookStatus, status as hook_status}; use crate::hooks::permissions::{self, PermissionVerdict}; /// Whether a `Supported` command was actually routed through RTK, and how @@ -604,11 +604,7 @@ pub fn run( /// Extract the subcommand from a command string (second word). fn extract_subcmd(cmd: &str) -> &str { let parts: Vec<&str> = cmd.trim().splitn(3, char::is_whitespace).collect(); - if parts.len() >= 2 { - parts[1] - } else { - "" - } + if parts.len() >= 2 { parts[1] } else { "" } } /// Truncate a command for display (keep first meaningful portion). diff --git a/src/discover/registry.rs b/src/discover/registry.rs index a176f25d40..1e49159ae2 100644 --- a/src/discover/registry.rs +++ b/src/discover/registry.rs @@ -6,10 +6,10 @@ use std::path::Path; use std::sync::LazyLock; use super::lexer::{ - advance_quote_state, coalesce_words, is_crlf_at, redirect_has_file_target, shell_split, - split_on_operators, tokenize, tokenize_with_newlines, ParsedToken, PipeKind, TokenKind, + ParsedToken, PipeKind, TokenKind, advance_quote_state, coalesce_words, is_crlf_at, + redirect_has_file_target, shell_split, split_on_operators, tokenize, tokenize_with_newlines, }; -use super::rules::{RtkRule, IGNORED_EXACT, IGNORED_PREFIXES, RULES}; +use super::rules::{IGNORED_EXACT, IGNORED_PREFIXES, RULES, RtkRule}; const PHP_TOOL_NAMES: [&str; 6] = ["phpunit", "phpstan", "ecs", "pest", "paratest", "pint"]; @@ -1018,11 +1018,7 @@ fn rewrite_multiline_block( i = end + 1; } - if any_changed { - Some(result) - } else { - None - } + if any_changed { Some(result) } else { None } } /// Pipeline boundaries used to rewrite its final stage. @@ -1291,11 +1287,7 @@ fn rewrite_compound( } result.push_str(&rewritten); - if any_changed { - Some(result) - } else { - None - } + if any_changed { Some(result) } else { None } } fn rewrite_line_range(cmd: &str) -> Option { @@ -6097,11 +6089,13 @@ mod tests { ); } // A different PHP tool is untouched. - assert!(rewrite_command_no_prefixes( - "php vendor/bin/phpstan analyse src", - &["phpunit".to_string()] - ) - .is_some()); + assert!( + rewrite_command_no_prefixes( + "php vendor/bin/phpstan analyse src", + &["phpunit".to_string()] + ) + .is_some() + ); } #[test] diff --git a/src/discover/report.rs b/src/discover/report.rs index 08828f3772..64a4142e50 100644 --- a/src/discover/report.rs +++ b/src/discover/report.rs @@ -1,8 +1,8 @@ //! Data types for reporting which commands RTK can and cannot optimize. use crate::hooks::constants::{ - COPILOT_HOOK_FILE, CURSOR_DIR, GITHUB_DIR, HERMES_DIR, HERMES_PLUGINS_SUBDIR, - HERMES_PLUGIN_MANIFEST_FILE, HERMES_PLUGIN_NAME, HOOKS_SUBDIR, REWRITE_HOOK_FILE, + COPILOT_HOOK_FILE, CURSOR_DIR, GITHUB_DIR, HERMES_DIR, HERMES_PLUGIN_MANIFEST_FILE, + HERMES_PLUGIN_NAME, HERMES_PLUGINS_SUBDIR, HOOKS_SUBDIR, REWRITE_HOOK_FILE, }; use serde::Serialize; use std::path::Path; diff --git a/src/hooks/decision.rs b/src/hooks/decision.rs index e3d201828d..21fc549529 100644 --- a/src/hooks/decision.rs +++ b/src/hooks/decision.rs @@ -15,7 +15,7 @@ //! machine's settings (#3146); and the no-op-rewrite policy lives in //! [`decide_for_agent`], which every hook shares and the CLI does not. -use super::permissions::{check_command_for, Host, PermissionVerdict}; +use super::permissions::{Host, PermissionVerdict, check_command_for}; use crate::discover::registry::rewrite_command; /// What a hook should do with a command. diff --git a/src/hooks/hook_check.rs b/src/hooks/hook_check.rs index 8c3342c8fc..faf819a10d 100644 --- a/src/hooks/hook_check.rs +++ b/src/hooks/hook_check.rs @@ -135,11 +135,7 @@ pub fn parse_hook_version(content: &str) -> u8 { fn hook_installed_path() -> Option { let claude_dir = resolve_claude_dir().ok()?; let path = claude_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE); - if path.exists() { - Some(path) - } else { - None - } + if path.exists() { Some(path) } else { None } } fn warn_marker_path() -> Option { @@ -152,7 +148,7 @@ mod tests { use super::*; use crate::hooks::constants::{ CODEX_DIR, CONFIG_DIR, CURSOR_DIR, GEMINI_DIR, GEMINI_HOOK_FILE, HERMES_DIR, - HERMES_PLUGINS_SUBDIR, HERMES_PLUGIN_MANIFEST_FILE, HERMES_PLUGIN_NAME, + HERMES_PLUGIN_MANIFEST_FILE, HERMES_PLUGIN_NAME, HERMES_PLUGINS_SUBDIR, OPENCODE_PLUGIN_FILE, OPENCODE_SUBDIR, PLUGIN_SUBDIR, }; diff --git a/src/hooks/hook_cmd.rs b/src/hooks/hook_cmd.rs index c5a95af6c9..e596d7da9e 100644 --- a/src/hooks/hook_cmd.rs +++ b/src/hooks/hook_cmd.rs @@ -7,7 +7,7 @@ use super::constants::PRE_TOOL_USE_KEY; use super::decision::{self, HookDecision}; use super::permissions::{self, PermissionVerdict}; use anyhow::{Context, Result}; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use std::io::{self, Read, Write}; use crate::core::tracking::HookOutcome; @@ -620,14 +620,14 @@ fn process_claude_payload_from_decision( decision: HookOutcome::Deny, reason: "skip:deny_rule", cmd: cmd.to_string(), - } + }; } HookDecision::Defer => { return PayloadAction::Skip { decision: HookOutcome::Defer, reason: "skip:defer", cmd: cmd.to_string(), - } + }; } HookDecision::AllowRewrite(r) => (r, true), HookDecision::AskRewrite(r) => (r, false), @@ -819,14 +819,14 @@ fn process_codex_payload_from_decision( decision: HookOutcome::Deny, reason: "skip:deny_rule", cmd: cmd.to_string(), - } + }; } HookDecision::Defer => { return PayloadAction::Skip { decision: HookOutcome::Defer, reason: "skip:no_rewrite", cmd: cmd.to_string(), - } + }; } }; @@ -1314,24 +1314,28 @@ mod tests { #[test] fn test_copilot_cli_deny_returns_none() { - assert!(copilot_cli_response_from_decision( - &cli_args("cargo test"), - HookDecision::Deny, - "cargo test", - ) - .is_none()); + assert!( + copilot_cli_response_from_decision( + &cli_args("cargo test"), + HookDecision::Deny, + "cargo test", + ) + .is_none() + ); } #[test] fn test_copilot_cli_defer_returns_none() { // Defer covers both "no rewrite available" and the unattestable-construct gate. // The hook must emit NO modifiedArgs for CVE bypass forms — no laundering. - assert!(copilot_cli_response_from_decision( - &cli_args("git status & rm -rf /tmp/x"), - HookDecision::Defer, - "git status & rm -rf /tmp/x", - ) - .is_none()); + assert!( + copilot_cli_response_from_decision( + &cli_args("git status & rm -rf /tmp/x"), + HookDecision::Defer, + "git status & rm -rf /tmp/x", + ) + .is_none() + ); } #[test] @@ -1342,10 +1346,12 @@ mod tests { ) .unwrap(); assert_eq!(response["permissionDecision"], "deny"); - assert!(response["permissionDecisionReason"] - .as_str() - .unwrap() - .contains("rtk git status")); + assert!( + response["permissionDecisionReason"] + .as_str() + .unwrap() + .contains("rtk git status") + ); assert!(response.get("modifiedArgs").is_none()); } @@ -1359,10 +1365,12 @@ mod tests { ) .unwrap(); assert_eq!(response["permissionDecision"], "deny"); - assert!(response["permissionDecisionReason"] - .as_str() - .unwrap() - .contains("rtk git status")); + assert!( + response["permissionDecisionReason"] + .as_str() + .unwrap() + .contains("rtk git status") + ); assert!(response.get("modifiedArgs").is_none()); } @@ -2027,11 +2035,13 @@ mod tests { #[test] fn test_codex_unknown_or_missing_permission_mode_passes_through() { assert!(run_codex_inner(&codex_input_with_permission_mode("git status", None)).is_none()); - assert!(run_codex_inner(&codex_input_with_permission_mode( - "git status", - Some("futureMode") - )) - .is_none()); + assert!( + run_codex_inner(&codex_input_with_permission_mode( + "git status", + Some("futureMode") + )) + .is_none() + ); } #[test] diff --git a/src/hooks/init.rs b/src/hooks/init.rs index e11588249d..d58a7c4783 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -15,13 +15,13 @@ use crate::hooks::constants::{ use super::constants::{ BEFORE_TOOL_KEY, CLAUDE_DIR, CLAUDE_HOOK_COMMAND, CODEX_DIR, CODEX_HOOK_COMMAND, - CURSOR_HOOK_COMMAND, DROID_DIR, DROID_EXECUTE_MATCHER, DROID_HOME_ENV, DROID_HOOKS_FILE, - DROID_HOOKS_SUBDIR, DROID_HOOK_COMMAND, DROID_SETTINGS_FILE, GEMINI_HOOK_FILE, HERMES_DIR, - HERMES_PLUGINS_SUBDIR, HERMES_PLUGIN_INIT_FILE, HERMES_PLUGIN_MANIFEST_FILE, - HERMES_PLUGIN_NAME, HOOKS_JSON, HOOKS_SUBDIR, OMP_DIR, OMP_LOCAL_DIR, PI_AGENT_STATE_FILE, + CURSOR_HOOK_COMMAND, DROID_DIR, DROID_EXECUTE_MATCHER, DROID_HOME_ENV, DROID_HOOK_COMMAND, + DROID_HOOKS_FILE, DROID_HOOKS_SUBDIR, DROID_SETTINGS_FILE, GEMINI_HOOK_FILE, HERMES_DIR, + HERMES_PLUGIN_INIT_FILE, HERMES_PLUGIN_MANIFEST_FILE, HERMES_PLUGIN_NAME, + HERMES_PLUGINS_SUBDIR, HOOKS_JSON, HOOKS_SUBDIR, OMP_DIR, OMP_LOCAL_DIR, PI_AGENT_STATE_FILE, PI_CODING_AGENT_DIR_ENV, PI_DIR, PI_EXTENSIONS_SUBDIR, PI_LOCAL_DIR, PI_PLUGIN_FILE, - PRE_TOOL_USE_KEY, REWRITE_HOOK_FILE, SETTINGS_JSON, VIBE_BASH_MATCH, VIBE_DIR, VIBE_HOOKS_FILE, - VIBE_HOOK_COMMAND, VIBE_HOOK_NAME, VIBE_PROMPTS_SUBDIR, VIBE_PROMPT_FILE, + PRE_TOOL_USE_KEY, REWRITE_HOOK_FILE, SETTINGS_JSON, VIBE_BASH_MATCH, VIBE_DIR, + VIBE_HOOK_COMMAND, VIBE_HOOK_NAME, VIBE_HOOKS_FILE, VIBE_PROMPT_FILE, VIBE_PROMPTS_SUBDIR, }; use super::integrity; use super::{is_claude_hook_command, is_codex_hook_command}; @@ -951,7 +951,9 @@ pub fn uninstall_with_patch_mode( } if !global { - anyhow::bail!("Uninstall only works with --global flag. For local projects, manually remove RTK from CLAUDE.md"); + anyhow::bail!( + "Uninstall only works with --global flag. For local projects, manually remove RTK from CLAUDE.md" + ); } let claude_dir = resolve_claude_dir()?; @@ -4204,11 +4206,7 @@ fn extension_share_status( } fn extension_scope_name(global: bool) -> &'static str { - if global { - "global" - } else { - "project" - } + if global { "global" } else { "project" } } fn warn_if_extension_shared_on_install( @@ -5332,7 +5330,10 @@ fn show_claude_config() -> Result<()> { #[cfg(not(unix))] { - println!("[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)", cursor_hook.display()); + println!( + "[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)", + cursor_hook.display() + ); } } else { println!("[--] Cursor hook: not found"); @@ -5553,7 +5554,9 @@ pub fn run_gemini( println!(" GEMINI.md: {}", gemini_dir.join(GEMINI_MD).display()); } if settings_parse_failed { - println!(" settings.json: NOT patched (existing file could not be parsed; see warning above)"); + println!( + " settings.json: NOT patched (existing file could not be parsed; see warning above)" + ); } println!(" Restart Gemini CLI. Test with: git status\n"); } @@ -6625,13 +6628,15 @@ mod tests { let rtk_md_path = temp.path().join("RTK.md"); let default_ctx = InitContext::default(); - assert!(write_if_changed( - &rtk_md_path, - awareness_content(default_ctx.awareness), - RTK_MD, - default_ctx - ) - .unwrap()); + assert!( + write_if_changed( + &rtk_md_path, + awareness_content(default_ctx.awareness), + RTK_MD, + default_ctx + ) + .unwrap() + ); assert_eq!( fs::read_to_string(&rtk_md_path).unwrap(), RTK_AWARENESS_DEFAULT @@ -6641,25 +6646,29 @@ mod tests { awareness: AwarenessLevel::High, ..Default::default() }; - assert!(write_if_changed( - &rtk_md_path, - awareness_content(high_ctx.awareness), - RTK_MD, - high_ctx - ) - .unwrap()); + assert!( + write_if_changed( + &rtk_md_path, + awareness_content(high_ctx.awareness), + RTK_MD, + high_ctx + ) + .unwrap() + ); assert_eq!( fs::read_to_string(&rtk_md_path).unwrap(), RTK_AWARENESS_HIGH ); - assert!(!write_if_changed( - &rtk_md_path, - awareness_content(high_ctx.awareness), - RTK_MD, - high_ctx - ) - .unwrap()); + assert!( + !write_if_changed( + &rtk_md_path, + awareness_content(high_ctx.awareness), + RTK_MD, + high_ctx + ) + .unwrap() + ); } #[test] @@ -8348,11 +8357,13 @@ mod tests { // Should create full structure assert!(json_content.get("hooks").is_some()); - assert!(json_content - .get("hooks") - .unwrap() - .get("PreToolUse") - .is_some()); + assert!( + json_content + .get("hooks") + .unwrap() + .get("PreToolUse") + .is_some() + ); let pre_tool_use = json_content["hooks"]["PreToolUse"].as_array().unwrap(); assert_eq!(pre_tool_use.len(), 1); @@ -9946,9 +9957,11 @@ mod tests { KNOWN_PI_PLUGIN_HASHES.len() >= 8, "historical Pi extension hashes must not be removed" ); - assert!(KNOWN_PI_PLUGIN_HASHES - .iter() - .all(|hash| hash.len() == 64 && hash.bytes().all(|byte| byte.is_ascii_hexdigit()))); + assert!( + KNOWN_PI_PLUGIN_HASHES + .iter() + .all(|hash| hash.len() == 64 && hash.bytes().all(|byte| byte.is_ascii_hexdigit())) + ); let current_hash = integrity::compute_hash_bytes( normalize_pi_plugin_line_endings(PI_PLUGIN) @@ -11074,10 +11087,12 @@ mod tests { run_vibe_mode_at(&vibe_dir, true, PatchMode::Auto, InitContext::default()).unwrap(); assert!(vibe_dir.join(VIBE_HOOKS_FILE).exists()); - assert!(!vibe_dir - .join(VIBE_PROMPTS_SUBDIR) - .join(VIBE_PROMPT_FILE) - .exists()); + assert!( + !vibe_dir + .join(VIBE_PROMPTS_SUBDIR) + .join(VIBE_PROMPT_FILE) + .exists() + ); } #[test] @@ -11089,20 +11104,24 @@ mod tests { fs::write(vibe_dir.join(VIBE_HOOKS_FILE), user_hook).unwrap(); run_vibe_mode_at(&vibe_dir, false, PatchMode::Auto, InitContext::default()).unwrap(); - assert!(vibe_dir - .join(VIBE_PROMPTS_SUBDIR) - .join(VIBE_PROMPT_FILE) - .exists()); + assert!( + vibe_dir + .join(VIBE_PROMPTS_SUBDIR) + .join(VIBE_PROMPT_FILE) + .exists() + ); let removed_first = uninstall_vibe_at(&vibe_dir, InitContext::default()).unwrap(); let removed_second = uninstall_vibe_at(&vibe_dir, InitContext::default()).unwrap(); assert_eq!(removed_first.len(), 2); assert!(removed_second.is_empty()); - assert!(!vibe_dir - .join(VIBE_PROMPTS_SUBDIR) - .join(VIBE_PROMPT_FILE) - .exists()); + assert!( + !vibe_dir + .join(VIBE_PROMPTS_SUBDIR) + .join(VIBE_PROMPT_FILE) + .exists() + ); let remaining = fs::read_to_string(vibe_dir.join(VIBE_HOOKS_FILE)).unwrap(); assert!(remaining.contains(r#"name = "user-audit""#)); diff --git a/src/hooks/rewrite_cmd.rs b/src/hooks/rewrite_cmd.rs index 34f0697d99..7c8f490fef 100644 --- a/src/hooks/rewrite_cmd.rs +++ b/src/hooks/rewrite_cmd.rs @@ -202,7 +202,7 @@ mod tests { /// See: https://github.com/rtk-ai/rtk/issues/1155 mod exit_code_protocol { use super::registry; - use crate::hooks::permissions::{check_command_with_rules, PermissionVerdict}; + use crate::hooks::permissions::{PermissionVerdict, check_command_with_rules}; /// Exit code that `run()` returns for each verdict: /// Allow → 0 (exit Ok(())) diff --git a/src/learn/mod.rs b/src/learn/mod.rs index 28db65cf05..fa62371be8 100644 --- a/src/learn/mod.rs +++ b/src/learn/mod.rs @@ -5,7 +5,7 @@ pub mod report; use crate::discover::provider::{ClaudeProvider, SessionProvider}; use anyhow::Result; -use detector::{deduplicate_corrections, find_corrections, CommandExecution}; +use detector::{CommandExecution, deduplicate_corrections, find_corrections}; use report::{format_console_report, write_rules_file}; pub fn run( diff --git a/src/main.rs b/src/main.rs index 6e4ae330aa..d72f19c6a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1924,11 +1924,7 @@ fn run_cli() -> Result { had_error = true; } } - if had_error { - 1 - } else { - 0 - } + if had_error { 1 } else { 0 } } Commands::Smart { @@ -4067,7 +4063,10 @@ mod tests { let warning = validate_pnpm_filters(&filter, &command).unwrap(); assert_eq!(filter, vec!["@app1", "@app2"]); - assert_eq!(warning, "[rtk] warning: --filter is not yet supported for pnpm tsc, filters preceding the subcommand will be ignored") + assert_eq!( + warning, + "[rtk] warning: --filter is not yet supported for pnpm tsc, filters preceding the subcommand will be ignored" + ) } _ => panic!("Expected Pnpm Build command"), } From 70ec493d0d1c1025a001ad4400fdfb7b8f724f95 Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Mon, 14 Sep 2026 01:26:43 +0200 Subject: [PATCH 2/6] chore: list command modules explicitly instead of via automod Every ecosystem mod.rs under src/cmds/ generated its module list with automod::dir!(). rustfmt only follows literal `mod` items and cannot expand macros, so `cargo fmt` never reached any of those files -- and neither did CI's `cargo fmt --all -- --check` gate, which has been silently passing over half the crate. Demonstrated by appending `fn __probe( )->u8{let x=1;x}` to a file under src/cmds/: with automod, `cargo fmt --all -- --check` reports no diff; with an explicit `pub mod`, it reports the diff and `cargo fmt --all` fixes it. The preceding commit had to invoke rustfmt directly for the same reason; from here `cargo fmt` covers the crate on its own. Listing modules by hand trades one failure mode for another, so build.rs now guards the new one: automod compiled any stray .rs file, whereas an explicit list silently drops a file whose `pub mod` line is forgotten -- never compiled, never linted, tests never run, and check-test-presence.sh still reporting PASS because it only greps the file for #[cfg(test)]. The build now fails with the missing declaration named. Declaring the modules explicitly also exposed clippy::module_inception on cmds::git::git, which clippy skips inside macro-generated code. git.rs is renamed to git_cmd.rs, which resolves the lint and matches the convention its siblings already follow -- diff_cmd, gh_cmd, glab_cmd, gt_cmd. Docs that point at the module are updated; sample command output that merely shows a "git.rs" path is left alone. Co-Authored-By: Claude Opus 5 --- .claude/agents/rtk-testing-specialist.md | 2 +- .claude/agents/rust-rtk.md | 2 +- .claude/agents/system-architect.md | 2 +- .claude/rules/cli-testing.md | 4 +- .claude/rules/search-strategy.md | 2 +- .../rtk-tdd/references/testing-patterns.md | 2 +- Cargo.lock | 12 ---- Cargo.toml | 1 - build.rs | 51 ++++++++++++++ docs/contributing/ARCHITECTURE.md | 6 +- docs/contributing/CODING_PRACTICES.md | 4 +- src/cmds/README.md | 2 +- src/cmds/cloud/mod.rs | 6 +- src/cmds/dotnet/mod.rs | 5 +- src/cmds/git/README.md | 6 +- src/cmds/git/gh_cmd.rs | 4 +- src/cmds/git/{git.rs => git_cmd.rs} | 0 src/cmds/git/glab_cmd.rs | 4 +- src/cmds/git/gt_cmd.rs | 34 ++++++--- src/cmds/git/mod.rs | 6 +- src/cmds/go/mod.rs | 3 +- src/cmds/js/mod.rs | 12 +++- src/cmds/jvm/mod.rs | 3 +- src/cmds/php/mod.rs | 12 +++- src/cmds/python/mod.rs | 7 +- src/cmds/ruby/mod.rs | 4 +- src/cmds/rust/mod.rs | 3 +- src/cmds/scala/mod.rs | 2 +- src/cmds/system/mod.rs | 18 ++++- src/cmds/system/pipe_cmd.rs | 6 +- src/discover/report.rs | 2 +- src/main.rs | 70 +++++++++++-------- 32 files changed, 208 insertions(+), 89 deletions(-) rename src/cmds/git/{git.rs => git_cmd.rs} (100%) diff --git a/.claude/agents/rtk-testing-specialist.md b/.claude/agents/rtk-testing-specialist.md index 649414e38f..9d1ede0cef 100644 --- a/.claude/agents/rtk-testing-specialist.md +++ b/.claude/agents/rtk-testing-specialist.md @@ -441,7 +441,7 @@ rtk/ ├── src/ │ ├── cmds/ │ │ ├── git/ -│ │ │ ├── git.rs # Filter implementation +│ │ │ ├── git_cmd.rs # Filter implementation │ │ │ │ └── #[cfg(test)] mod tests { ... } # Unit tests │ │ │ └── snapshots/ # Insta snapshots for git module │ │ ├── js/ diff --git a/.claude/agents/rust-rtk.md b/.claude/agents/rust-rtk.md index 7265d9678c..ec5ed45d6e 100644 --- a/.claude/agents/rust-rtk.md +++ b/.claude/agents/rust-rtk.md @@ -315,7 +315,7 @@ fn test_real_git_log() { - `src/core/config.rs` - User configuration (~/.config/rtk/config.toml) **Command modules** (`src/cmds//`): -- `src/cmds/git/` - git.rs, gh_cmd.rs, gt_cmd.rs, diff_cmd.rs +- `src/cmds/git/` - git_cmd.rs, gh_cmd.rs, gt_cmd.rs, diff_cmd.rs - `src/cmds/rust/` - cargo_cmd.rs, runner.rs - `src/cmds/js/` - lint_cmd.rs, tsc_cmd.rs, next_cmd.rs, prettier_cmd.rs, playwright_cmd.rs, prisma_cmd.rs, vitest_cmd.rs, pnpm_cmd.rs, npm_cmd.rs - `src/cmds/python/` - ruff_cmd.rs, pytest_cmd.rs, mypy_cmd.rs, pip_cmd.rs diff --git a/.claude/agents/system-architect.md b/.claude/agents/system-architect.md index 790deb4ade..e439f162d8 100644 --- a/.claude/agents/system-architect.md +++ b/.claude/agents/system-architect.md @@ -32,7 +32,7 @@ Think in terms of filter families, not individual commands. Every new `*_cmd.rs` ``` src/main.rs ├── Commands enum (clap derive) -│ ├── Git(GitArgs) → cmds/git/git.rs +│ ├── Git(GitArgs) → cmds/git/git_cmd.rs │ ├── Cargo(CargoArgs) → cmds/rust/runner.rs │ ├── Gh(GhArgs) → cmds/git/gh_cmd.rs │ ├── Grep(GrepArgs) → cmds/system/grep_cmd.rs diff --git a/.claude/rules/cli-testing.md b/.claude/rules/cli-testing.md index d30f580583..b57266940e 100644 --- a/.claude/rules/cli-testing.md +++ b/.claude/rules/cli-testing.md @@ -30,7 +30,7 @@ mod tests { Two patterns coexist, pick based on what the filter needs: 1. **Inline literal strings** (most common for `src/cmds/**` unit tests) — build a small - representative string directly in the test body. Used throughout `src/cmds/git/git.rs`, + representative string directly in the test body. Used throughout `src/cmds/git/git_cmd.rs`, `src/cmds/git/gh_cmd.rs`, etc. Good for quick coverage of a specific format/edge case. 2. **Real captured fixtures via `include_str!`** — used when the raw output is large or format-sensitive enough that inline strings would be unreadable or drift from reality. @@ -306,7 +306,7 @@ rtk/ ├── src/ │ ├── cmds/ │ │ ├── git/ -│ │ │ ├── git.rs # Filter implementation +│ │ │ ├── git_cmd.rs # Filter implementation │ │ │ │ └── #[cfg(test)] mod tests { ... } │ │ ├── jvm/ # gradlew, mvn — reference example for include_str! fixtures │ │ ├── php/ # php, artisan, phpunit, phpstan, pest, paratest, ecs, pint diff --git a/.claude/rules/search-strategy.md b/.claude/rules/search-strategy.md index c0fdd6f24f..05fa29538f 100644 --- a/.claude/rules/search-strategy.md +++ b/.claude/rules/search-strategy.md @@ -87,7 +87,7 @@ Grep pattern="fn filter_git_log\|fn run\b" type="rust" ``` Glob pattern="src/cmds/**/*_cmd.rs" -# Also: src/cmds/git/git.rs, src/cmds/rust/runner.rs, src/cmds/cloud/container.rs +# Also: src/cmds/git/git_cmd.rs, src/cmds/rust/runner.rs, src/cmds/cloud/container.rs ``` ### "Find all LazyLock regex definitions" diff --git a/.claude/skills/rtk-tdd/references/testing-patterns.md b/.claude/skills/rtk-tdd/references/testing-patterns.md index e594c8a0bf..a371e4d58b 100644 --- a/.claude/skills/rtk-tdd/references/testing-patterns.md +++ b/.claude/skills/rtk-tdd/references/testing-patterns.md @@ -52,7 +52,7 @@ more noise } ``` -Used in: `git.rs`, `grep_cmd.rs`, `lint_cmd.rs`, `tsc_cmd.rs`, `vitest_cmd.rs`, `pnpm_cmd.rs`, `next_cmd.rs`, `prettier_cmd.rs`, `playwright_cmd.rs`, `prisma_cmd.rs` +Used in: `git_cmd.rs`, `grep_cmd.rs`, `lint_cmd.rs`, `tsc_cmd.rs`, `vitest_cmd.rs`, `pnpm_cmd.rs`, `next_cmd.rs`, `prettier_cmd.rs`, `playwright_cmd.rs`, `prisma_cmd.rs` ### Pattern 2: Pure Computation diff --git a/Cargo.lock b/Cargo.lock index 62fa64aa11..cff50b2497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,17 +100,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "automod" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b5778837666541195063243828c5b6139221b47dc4ec3ba81738e532469ab1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "base64" version = "0.22.1" @@ -933,7 +922,6 @@ name = "rtk" version = "0.48.0" dependencies = [ "anyhow", - "automod", "chrono", "clap", "codepage", diff --git a/Cargo.toml b/Cargo.toml index d8dce975fc..13213542c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,6 @@ getrandom = "0.4" flate2 = "1.0" quick-xml = "0.37" which = "8" -automod = "1" [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/build.rs b/build.rs index 1d4ae93d1b..0a68bcf95b 100644 --- a/build.rs +++ b/build.rs @@ -12,6 +12,8 @@ fn main() { println!("cargo:rustc-link-arg=/STACK:8388608"); } + check_cmds_modules_declared(); + let filters_dir = Path::new("src/filters"); let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR must be set by Cargo"); let dest = Path::new(&out_dir).join("builtin_filters.toml"); @@ -64,3 +66,52 @@ fn main() { fs::write(&dest, combined).expect("Failed to write combined builtin_filters.toml"); } + +/// Every `.rs` under `src/cmds//` must be declared in that folder's +/// `mod.rs`. An undeclared file is not part of the crate: it is never compiled, +/// never linted, and its tests never run, all without any diagnostic. +fn check_cmds_modules_declared() { + println!("cargo:rerun-if-changed=src/cmds"); + + let cmds = Path::new("src/cmds"); + let ecosystems = fs::read_dir(cmds).expect("src/cmds/ directory must exist"); + + for ecosystem in ecosystems { + let dir = ecosystem.expect("failed to read src/cmds/ entry").path(); + let mod_rs = dir.join("mod.rs"); + if !dir.is_dir() || !mod_rs.exists() { + continue; + } + + let declared = fs::read_to_string(&mod_rs) + .unwrap_or_else(|e| panic!("Failed to read {}: {}", mod_rs.display(), e)); + + let entries = fs::read_dir(&dir) + .unwrap_or_else(|e| panic!("Failed to read {}: {}", dir.display(), e)); + + for entry in entries { + let path = entry.expect("failed to read directory entry").path(); + if path.extension().is_none_or(|ext| ext != "rs") { + continue; + } + let stem = path + .file_stem() + .expect("a .rs path always has a stem") + .to_string_lossy() + .to_string(); + if stem == "mod" { + continue; + } + if !declared.contains(&format!("mod {stem};")) { + panic!( + "{} is not declared in {}.\n\ + Add `pub mod {};` there, keeping the list alphabetical.\n\ + An undeclared module is never compiled, linted, or tested.", + path.display(), + mod_rs.display(), + stem + ); + } + } + } +} diff --git a/docs/contributing/ARCHITECTURE.md b/docs/contributing/ARCHITECTURE.md index 9b72fc6855..498937dd49 100644 --- a/docs/contributing/ARCHITECTURE.md +++ b/docs/contributing/ARCHITECTURE.md @@ -765,7 +765,7 @@ Levels: │ -vvv │ + Raw output before filtering │ └─────────┴──────────────────────────────────────────────────────┘ -Example (git.rs:67-69): +Example (git_cmd.rs:67-69): if verbose > 0 { eprintln!("Git diff summary:"); } @@ -839,7 +839,7 @@ std::process::exit(1) │ Exit Code Handling Strategy │ └────────────────────────────────────────────────────────────────────────┘ -Standard Pattern (git.rs:45-48, PR #5): +Standard Pattern (git_cmd.rs:45-48, PR #5): let output = Command::new("git").args(args).output()?; @@ -865,7 +865,7 @@ Why This Matters: • Git workflows require proper exit code propagation (PR #5 fix) Modules with Exit Code Preservation: -• git.rs (all git commands) +• git_cmd.rs (all git commands) • lint_cmd.rs (linter failures) • tsc_cmd.rs (TypeScript errors) • vitest_cmd.rs (test failures) diff --git a/docs/contributing/CODING_PRACTICES.md b/docs/contributing/CODING_PRACTICES.md index 63f9ea9189..52e0af8851 100644 --- a/docs/contributing/CODING_PRACTICES.md +++ b/docs/contributing/CODING_PRACTICES.md @@ -13,7 +13,7 @@ Our goal is to keep the codebase consistent and easy to extend. PRs that deviate New to RTK? The fastest path to a mergeable first PR: 1. **Read the flow once.** Start at [`CONTRIBUTING.md`](../../CONTRIBUTING.md), then skim [`docs/contributing/TECHNICAL.md`](TECHNICAL.md) to see how a command flows from `main.rs` → a `*_cmd.rs` filter → tracking → stdout. -2. **Look at a good example.** [`src/cmds/git/git.rs`](../../src/cmds/git/git.rs) is a representative filter — it shows the `run()` entry point, `LazyLock` regex setup, filter helpers, and embedded tests all in one file. +2. **Look at a good example.** [`src/cmds/git/git_cmd.rs`](../../src/cmds/git/git_cmd.rs) is a representative filter — it shows the `run()` entry point, `LazyLock` regex setup, filter helpers, and embedded tests all in one file. 3. **Know the shared helpers before reimplementing.** Two files cover most of what you need: - [`src/core/runner.rs`](../../src/core/runner.rs) — command execution wrappers: `run_filtered()` (run a command, then apply your filter function), `run_passthrough()` (run unfiltered but tracked), `run_streamed()` (streaming filter). - [`src/core/utils.rs`](../../src/core/utils.rs) — shared utilities: `resolved_command()`, `strip_ansi()`, `truncate()`, `count_tokens()`, and more. @@ -81,7 +81,7 @@ Use explicit, descriptive names for variables, just like for functions. Do not hardcode repetitive patterns or values that control behavior — extract them into named constants at the top of the file. For anything a user might want to tune (thresholds, limits, display cutoffs), use `config::limits()` so it flows through `~/.config/rtk/config.toml`. -Example from `src/cmds/git/git.rs`: +Example from `src/cmds/git/git_cmd.rs`: ```rust let limits = config::limits(); diff --git a/src/cmds/README.md b/src/cmds/README.md index a2ac427439..766ecf4c62 100644 --- a/src/cmds/README.md +++ b/src/cmds/README.md @@ -296,7 +296,7 @@ Adding a new filter or command requires changes in multiple places. For TOML-vs- - **Truncation**: if the filter caps any list at N items, emit `force_tee_tail_hint` (flat lists) or `force_tee_hint` (multi-line blocks) so the agent can recover hidden items — see [Internal Truncation Recovery](#internal-truncation-recovery). Use a named constant for the cap; derive the offset from it (`MAX_XXX + 1`) - **Arguments**: if the filter inspects its args at all — which flags were passed, which are paths, what to inject or strip — classify them with [`arg_tokenizer`](../core/README.md#argument-tokenizer-arg_tokenizerrs), never a string scan. Write the `takes_value` predicate from the tool's own `--help` and check it against the real binary 2. **Register module**: - - Ecosystem `mod.rs` files use `automod::dir!()` — any `.rs` file in the directory becomes a public module automatically. No manual `pub mod` needed, but be aware: WIP or helper files will also be exposed. Only commit command-ready modules. + - Add `pub mod mycmd_cmd;` to the ecosystem `mod.rs`, keeping the list alphabetical. Modules are listed explicitly rather than generated from the directory: rustfmt only follows literal `mod` items and cannot expand macros, so a generated list hides every file under `src/cmds/` from `cargo fmt` — and therefore from CI's fmt gate. `build.rs` fails the build if a `.rs` file here is missing from its `mod.rs`, since an undeclared module is never compiled, linted, or tested. - Add variant to `Commands` enum in `main.rs` with `#[arg(trailing_var_arg = true, allow_hyphen_values = true)]` - Add routing match arm in `main.rs`: `Commands::Mycmd { args } => mycmd_cmd::run(&args, cli.verbose)?,` 3. **Add rewrite pattern** — Entry in `src/discover/rules.rs` (PATTERNS + RULES arrays at matching index) so hooks auto-rewrite the command diff --git a/src/cmds/cloud/mod.rs b/src/cmds/cloud/mod.rs index 28af53ca70..753bd8a954 100644 --- a/src/cmds/cloud/mod.rs +++ b/src/cmds/cloud/mod.rs @@ -1 +1,5 @@ -automod::dir!(pub "src/cmds/cloud"); +pub mod aws_cmd; +pub mod container; +pub mod curl_cmd; +pub mod psql_cmd; +pub mod wget_cmd; diff --git a/src/cmds/dotnet/mod.rs b/src/cmds/dotnet/mod.rs index 113c57d6a8..1a9073ea03 100644 --- a/src/cmds/dotnet/mod.rs +++ b/src/cmds/dotnet/mod.rs @@ -1 +1,4 @@ -automod::dir!(pub "src/cmds/dotnet"); +pub mod binlog; +pub mod dotnet_cmd; +pub mod dotnet_format_report; +pub mod dotnet_trx; diff --git a/src/cmds/git/README.md b/src/cmds/git/README.md index 6b801ca25e..3d0f83d394 100644 --- a/src/cmds/git/README.md +++ b/src/cmds/git/README.md @@ -4,7 +4,7 @@ ## Specifics -- **git.rs** uses `trailing_var_arg = true` + `allow_hyphen_values = true` so native git flags (`--oneline`, `--cached`, etc.) pass through correctly +- **git_cmd.rs** uses `trailing_var_arg = true` + `allow_hyphen_values = true` so native git flags (`--oneline`, `--cached`, etc.) pass through correctly - Flag grammar (which flags take a value, what `-u`/`-p` mean) is per-subcommand, not shared wholesale between `log`/`diff`/`show`/`stash show` — see [`src/core/README.md`](../../core/README.md#argument-tokenizer-arg_tokenizerrs) - Default `git status` uses `--porcelain -b` so the compact output never exceeds raw `git status` (an untracked directory collapses to a single line, matching git's default); branch/short-only flags reuse the compact path, other explicit args still pass through unchanged - Global git options (`-C`, `--git-dir`, `--work-tree`, `--no-pager`) are prepended before the subcommand @@ -20,8 +20,8 @@ ## Cross-command -- `gh_cmd.rs` imports `compact_diff()` from `git.rs` for diff formatting; markdown helpers (`filter_markdown_body`, `filter_markdown_segment`) are defined in `gh_cmd.rs` itself -- `glab_cmd.rs` also uses `compact_diff()` from `git.rs` for `mr diff`; its `filter_markdown_body` is currently **duplicated** from `gh_cmd.rs` (shared-module refactor deferred) +- `gh_cmd.rs` imports `compact_diff()` from `git_cmd.rs` for diff formatting; markdown helpers (`filter_markdown_body`, `filter_markdown_segment`) are defined in `gh_cmd.rs` itself +- `glab_cmd.rs` also uses `compact_diff()` from `git_cmd.rs` for `mr diff`; its `filter_markdown_body` is currently **duplicated** from `gh_cmd.rs` (shared-module refactor deferred) - `diff_cmd.rs` is a standalone ultra-condensed diff (separate from `git diff`) ## glab vs gh JSON schema quick-ref diff --git a/src/cmds/git/gh_cmd.rs b/src/cmds/git/gh_cmd.rs index 0f52c826f5..e451755192 100644 --- a/src/cmds/git/gh_cmd.rs +++ b/src/cmds/git/gh_cmd.rs @@ -6,7 +6,7 @@ use crate::core::runner::{self, RunOptions}; use crate::core::truncate::CAP_LIST; use crate::core::utils::{ok_confirmation, resolved_command, truncate}; -use crate::git; +use crate::git_cmd; use anyhow::Result; use regex::Regex; use serde_json::Value; @@ -1021,7 +1021,7 @@ fn pr_diff(args: &[String], _verbose: u8) -> Result { if raw.trim().is_empty() { "No diff".to_string() } else { - git::compact_diff(raw, 500) + git_cmd::compact_diff(raw, 500) } }, RunOptions::stdout_only().early_exit_on_failure(), diff --git a/src/cmds/git/git.rs b/src/cmds/git/git_cmd.rs similarity index 100% rename from src/cmds/git/git.rs rename to src/cmds/git/git_cmd.rs diff --git a/src/cmds/git/glab_cmd.rs b/src/cmds/git/glab_cmd.rs index 1fe0870270..a116e01b6c 100644 --- a/src/cmds/git/glab_cmd.rs +++ b/src/cmds/git/glab_cmd.rs @@ -10,7 +10,7 @@ //! - Merge status: `merge_status` ("can_be_merged") (not `mergeable`) //! - Pipeline: `head_pipeline.status` (not `statusCheckRollup`) -use super::git; +use super::git_cmd; use crate::core::runner::{self, RunOptions}; use crate::core::truncate::{CAP_LIST, CAP_WARNINGS}; use crate::core::utils::{ok_confirmation, resolved_command, strip_ansi, truncate}; @@ -489,7 +489,7 @@ fn mr_diff(args: &[String], _verbose: u8) -> Result { if stdout.trim().is_empty() { "No diff\n".to_string() } else { - git::compact_diff(stdout, 500) + git_cmd::compact_diff(stdout, 500) } }, RunOptions::stdout_only().early_exit_on_failure(), diff --git a/src/cmds/git/gt_cmd.rs b/src/cmds/git/gt_cmd.rs index 092a33fff2..28cbf30e64 100644 --- a/src/cmds/git/gt_cmd.rs +++ b/src/cmds/git/gt_cmd.rs @@ -136,18 +136,26 @@ pub fn run_other(args: &[OsString], verbose: u8) -> Result { // gt passes unknown subcommands to git, so "gt status" = "git status". // Route known git commands to RTK's git filters for token savings. match subcommand.as_ref() { - "status" => crate::git::run(crate::git::GitCommand::Status, &rest, None, verbose, &[]), - "diff" => crate::git::run(crate::git::GitCommand::Diff, &rest, None, verbose, &[]), - "show" => crate::git::run(crate::git::GitCommand::Show, &rest, None, verbose, &[]), - "add" => crate::git::run(crate::git::GitCommand::Add, &rest, None, verbose, &[]), - "push" => crate::git::run(crate::git::GitCommand::Push, &rest, None, verbose, &[]), - "pull" => crate::git::run(crate::git::GitCommand::Pull, &rest, None, verbose, &[]), - "fetch" => crate::git::run(crate::git::GitCommand::Fetch, &rest, None, verbose, &[]), + "status" => crate::git_cmd::run( + crate::git_cmd::GitCommand::Status, + &rest, + None, + verbose, + &[], + ), + "diff" => crate::git_cmd::run(crate::git_cmd::GitCommand::Diff, &rest, None, verbose, &[]), + "show" => crate::git_cmd::run(crate::git_cmd::GitCommand::Show, &rest, None, verbose, &[]), + "add" => crate::git_cmd::run(crate::git_cmd::GitCommand::Add, &rest, None, verbose, &[]), + "push" => crate::git_cmd::run(crate::git_cmd::GitCommand::Push, &rest, None, verbose, &[]), + "pull" => crate::git_cmd::run(crate::git_cmd::GitCommand::Pull, &rest, None, verbose, &[]), + "fetch" => { + crate::git_cmd::run(crate::git_cmd::GitCommand::Fetch, &rest, None, verbose, &[]) + } "stash" => { let stash_sub = rest.first().cloned(); let stash_args = rest.get(1..).unwrap_or(&[]); - crate::git::run( - crate::git::GitCommand::Stash { + crate::git_cmd::run( + crate::git_cmd::GitCommand::Stash { subcommand: stash_sub, }, stash_args, @@ -156,7 +164,13 @@ pub fn run_other(args: &[OsString], verbose: u8) -> Result { &[], ) } - "worktree" => crate::git::run(crate::git::GitCommand::Worktree, &rest, None, verbose, &[]), + "worktree" => crate::git_cmd::run( + crate::git_cmd::GitCommand::Worktree, + &rest, + None, + verbose, + &[], + ), _ => passthrough_gt(&subcommand, &rest, verbose), } } diff --git a/src/cmds/git/mod.rs b/src/cmds/git/mod.rs index 1d0c3ebba6..bbae857f9b 100644 --- a/src/cmds/git/mod.rs +++ b/src/cmds/git/mod.rs @@ -1 +1,5 @@ -automod::dir!(pub "src/cmds/git"); +pub mod diff_cmd; +pub mod gh_cmd; +pub mod git_cmd; +pub mod glab_cmd; +pub mod gt_cmd; diff --git a/src/cmds/go/mod.rs b/src/cmds/go/mod.rs index e3460aece6..975ba0b372 100644 --- a/src/cmds/go/mod.rs +++ b/src/cmds/go/mod.rs @@ -1 +1,2 @@ -automod::dir!(pub "src/cmds/go"); +pub mod go_cmd; +pub mod golangci_cmd; diff --git a/src/cmds/js/mod.rs b/src/cmds/js/mod.rs index dfcdc79934..c0f55ac6bb 100644 --- a/src/cmds/js/mod.rs +++ b/src/cmds/js/mod.rs @@ -1 +1,11 @@ -automod::dir!(pub "src/cmds/js"); +pub mod bun_cmd; +pub mod deno_cmd; +pub mod lint_cmd; +pub mod next_cmd; +pub mod npm_cmd; +pub mod playwright_cmd; +pub mod pnpm_cmd; +pub mod prettier_cmd; +pub mod prisma_cmd; +pub mod tsc_cmd; +pub mod vitest_cmd; diff --git a/src/cmds/jvm/mod.rs b/src/cmds/jvm/mod.rs index cf7f18aa48..c64d0f0c86 100644 --- a/src/cmds/jvm/mod.rs +++ b/src/cmds/jvm/mod.rs @@ -1 +1,2 @@ -automod::dir!(pub "src/cmds/jvm"); +pub mod gradlew_cmd; +pub mod mvn_cmd; diff --git a/src/cmds/php/mod.rs b/src/cmds/php/mod.rs index 90c027f68a..6ecb7ac055 100644 --- a/src/cmds/php/mod.rs +++ b/src/cmds/php/mod.rs @@ -1 +1,11 @@ -automod::dir!(pub "src/cmds/php"); +pub mod artisan_cmd; +pub mod ecs_cmd; +pub mod paratest_cmd; +pub mod pest_cmd; +pub mod php_cmd; +pub mod phpstan_cmd; +pub mod phpt_cmd; +pub mod phpunit_cmd; +pub mod pint_cmd; +pub mod test_output; +pub mod utils; diff --git a/src/cmds/python/mod.rs b/src/cmds/python/mod.rs index 8cadceebc4..d3b94b9997 100644 --- a/src/cmds/python/mod.rs +++ b/src/cmds/python/mod.rs @@ -1 +1,6 @@ -automod::dir!(pub "src/cmds/python"); +pub mod mypy_cmd; +pub mod pip_cmd; +pub mod pytest_cmd; +pub mod ruff_cmd; +pub mod sqlfluff_cmd; +pub mod uv_cmd; diff --git a/src/cmds/ruby/mod.rs b/src/cmds/ruby/mod.rs index 84a39e2fb8..25f6597afb 100644 --- a/src/cmds/ruby/mod.rs +++ b/src/cmds/ruby/mod.rs @@ -1 +1,3 @@ -automod::dir!(pub "src/cmds/ruby"); +pub mod rake_cmd; +pub mod rspec_cmd; +pub mod rubocop_cmd; diff --git a/src/cmds/rust/mod.rs b/src/cmds/rust/mod.rs index bcfcfd851f..e67b7a4e3a 100644 --- a/src/cmds/rust/mod.rs +++ b/src/cmds/rust/mod.rs @@ -1 +1,2 @@ -automod::dir!(pub "src/cmds/rust"); +pub mod cargo_cmd; +pub mod runner; diff --git a/src/cmds/scala/mod.rs b/src/cmds/scala/mod.rs index 56b8f5bf66..c63db03622 100644 --- a/src/cmds/scala/mod.rs +++ b/src/cmds/scala/mod.rs @@ -1 +1 @@ -automod::dir!(pub "src/cmds/scala"); +pub mod sbt_cmd; diff --git a/src/cmds/system/mod.rs b/src/cmds/system/mod.rs index a4404bc1ca..89f467a3c5 100644 --- a/src/cmds/system/mod.rs +++ b/src/cmds/system/mod.rs @@ -1 +1,17 @@ -automod::dir!(pub "src/cmds/system"); +pub mod ast_grep_cmd; +pub mod constants; +pub mod ctest_cmd; +pub mod deps; +pub mod env_cmd; +pub mod find_cmd; +pub mod format_cmd; +pub mod json_cmd; +pub mod local_llm; +pub mod log_cmd; +pub mod ls; +pub mod pipe_cmd; +pub mod read; +pub mod search; +pub mod summary; +pub mod tree; +pub mod wc_cmd; diff --git a/src/cmds/system/pipe_cmd.rs b/src/cmds/system/pipe_cmd.rs index d8a488a7e3..3f70dd622a 100644 --- a/src/cmds/system/pipe_cmd.rs +++ b/src/cmds/system/pipe_cmd.rs @@ -45,15 +45,15 @@ fn go_test_wrapper(input: &str) -> String { } fn git_status_wrapper(input: &str) -> String { - crate::cmds::git::git::format_status_output(input) + crate::cmds::git::git_cmd::format_status_output(input) } fn git_log_wrapper(input: &str) -> String { - crate::cmds::git::git::filter_log_output(input, 50, false, false) + crate::cmds::git::git_cmd::filter_log_output(input, 50, false, false) } fn git_diff_wrapper(input: &str) -> String { - crate::cmds::git::git::compact_diff(input, 200) + crate::cmds::git::git_cmd::compact_diff(input, 200) } fn phpstan_wrapper(input: &str) -> String { diff --git a/src/discover/report.rs b/src/discover/report.rs index 64a4142e50..81b9524252 100644 --- a/src/discover/report.rs +++ b/src/discover/report.rs @@ -10,7 +10,7 @@ use std::path::Path; /// RTK support status for a command. #[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq)] pub enum RtkStatus { - /// Dedicated handler with filtering (e.g., git status → git.rs:run_status()) + /// Dedicated handler with filtering (e.g., git status → git_cmd.rs:run_status()) Existing, /// Works via external_subcommand passthrough, no filtering (e.g., cargo fmt → Other) Passthrough, diff --git a/src/main.rs b/src/main.rs index d72f19c6a3..c4c6aacf9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ mod parser; // Re-export command modules for routing use cmds::cloud::{aws_cmd, container, curl_cmd, psql_cmd, wget_cmd}; use cmds::dotnet::{binlog, dotnet_cmd, dotnet_format_report, dotnet_trx}; -use cmds::git::{diff_cmd, gh_cmd, git, glab_cmd, gt_cmd}; +use cmds::git::{diff_cmd, gh_cmd, git_cmd, glab_cmd, gt_cmd}; use cmds::go::{go_cmd, golangci_cmd}; use cmds::js::{ bun_cmd, deno_cmd, lint_cmd, next_cmd, npm_cmd, playwright_cmd, pnpm_cmd, prettier_cmd, @@ -1979,90 +1979,100 @@ fn run_cli() -> Result { } match command { - GitCommands::Diff { args } => git::run( - git::GitCommand::Diff, + GitCommands::Diff { args } => git_cmd::run( + git_cmd::GitCommand::Diff, &args, None, cli.verbose, &global_args, )?, - GitCommands::Log { args } => { - git::run(git::GitCommand::Log, &args, None, cli.verbose, &global_args)? - } - GitCommands::Status { args } => git::run( - git::GitCommand::Status, + GitCommands::Log { args } => git_cmd::run( + git_cmd::GitCommand::Log, &args, None, cli.verbose, &global_args, )?, - GitCommands::Show { args } => git::run( - git::GitCommand::Show, + GitCommands::Status { args } => git_cmd::run( + git_cmd::GitCommand::Status, &args, None, cli.verbose, &global_args, )?, - GitCommands::Add { args } => { - git::run(git::GitCommand::Add, &args, None, cli.verbose, &global_args)? - } - GitCommands::Commit { args } => git::run( - git::GitCommand::Commit, + GitCommands::Show { args } => git_cmd::run( + git_cmd::GitCommand::Show, + &args, + None, + cli.verbose, + &global_args, + )?, + GitCommands::Add { args } => git_cmd::run( + git_cmd::GitCommand::Add, &args, None, cli.verbose, &global_args, )?, - GitCommands::Checkout { args } => git::run( - git::GitCommand::Checkout, + GitCommands::Commit { args } => git_cmd::run( + git_cmd::GitCommand::Commit, &args, None, cli.verbose, &global_args, )?, - GitCommands::Push { args } => git::run( - git::GitCommand::Push, + GitCommands::Checkout { args } => git_cmd::run( + git_cmd::GitCommand::Checkout, &args, None, cli.verbose, &global_args, )?, - GitCommands::Pull { args } => git::run( - git::GitCommand::Pull, + GitCommands::Push { args } => git_cmd::run( + git_cmd::GitCommand::Push, &args, None, cli.verbose, &global_args, )?, - GitCommands::Branch { args } => git::run( - git::GitCommand::Branch, + GitCommands::Pull { args } => git_cmd::run( + git_cmd::GitCommand::Pull, &args, None, cli.verbose, &global_args, )?, - GitCommands::Fetch { args } => git::run( - git::GitCommand::Fetch, + GitCommands::Branch { args } => git_cmd::run( + git_cmd::GitCommand::Branch, &args, None, cli.verbose, &global_args, )?, - GitCommands::Stash { subcommand, args } => git::run( - git::GitCommand::Stash { subcommand }, + GitCommands::Fetch { args } => git_cmd::run( + git_cmd::GitCommand::Fetch, &args, None, cli.verbose, &global_args, )?, - GitCommands::Worktree { args } => git::run( - git::GitCommand::Worktree, + GitCommands::Stash { subcommand, args } => git_cmd::run( + git_cmd::GitCommand::Stash { subcommand }, &args, None, cli.verbose, &global_args, )?, - GitCommands::Other(args) => git::run_passthrough(&args, &global_args, cli.verbose)?, + GitCommands::Worktree { args } => git_cmd::run( + git_cmd::GitCommand::Worktree, + &args, + None, + cli.verbose, + &global_args, + )?, + GitCommands::Other(args) => { + git_cmd::run_passthrough(&args, &global_args, cli.verbose)? + } } } From 7c18567155308a85714d7cb7f4be1b43e608729b Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Mon, 31 Aug 2026 01:19:25 +0200 Subject: [PATCH 3/6] chore: migrate to Rust edition 2024 Bump edition 2021 -> 2024. rust-version stays at 1.91, already well above the 1.85 floor the edition needs; docs/guide/resources/troubleshooting.md still claimed 1.70+, which is where a failed `cargo install --git` lands. Four things the edition forces: - std::env::{set_var,remove_var} are unsafe in 2024 with no safe std replacement. Rather than wrap the test call sites in unsafe -- which the crate denies and .semgrep.yml flags -- route them through temp-env, a dev-only dependency whose closure API is safe and which restores the previous value even when the body panics. The hand-rolled CLAUDE_DIR_LOCK and PI_DIR_LOCK guards existed only to serialise those mutations and are now redundant; CWD_LOCK and TEST_ENV_LOCK stay, they order more than the env var itself. - unsafe_op_in_unsafe_fn is on by default, so the libc calls in the proxy signal handler and in stream.rs's relay handler need explicit unsafe blocks, scoped to the libc calls themselves. - `gen` is a reserved keyword, so the closure by that name in diff_cmd.rs becomes make_lines. - Tightened tail-expression temporary scopes let clippy prove the binding in setup_test_env is inlinable, so let_and_return now fires there. if_let_rescope changes when the scrutinee temporary drops in an if let/else. The two sites in show_claude_config take cargo fix --edition's match rewrite, which keeps the 2021 drop timing. rustfmt.toml is kept rather than dropped: cargo fmt passes --edition from Cargo.toml, but a bare rustfmt invocation has no crate context and falls back to edition 2015, which cannot parse the let-chains the next commit introduces. Pinning it there keeps format-on-save and pre-commit hooks in agreement with CI. clippy::collapsible_if is allowed crate-wide for now; the follow-up commit adopts let-chains and removes the allow. Co-Authored-By: Claude Opus 5 (1M context) --- Cargo.lock | 57 +++++++++ Cargo.toml | 8 +- docs/guide/resources/troubleshooting.md | 2 +- rustfmt.toml | 5 + src/cmds/git/diff_cmd.rs | 6 +- src/cmds/system/find_cmd.rs | 60 +++++----- src/core/retriever.rs | 6 +- src/core/stream.rs | 12 +- src/core/tee.rs | 12 +- src/core/telemetry_cmd.rs | 45 +++---- src/core/tracking.rs | 110 +++++++++--------- src/hooks/hook_cmd.rs | 5 +- src/hooks/init.rs | 148 ++++++++++-------------- src/hooks/trust.rs | 19 ++- src/main.rs | 15 ++- 15 files changed, 280 insertions(+), 230 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cff50b2497..c39ec756d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -723,6 +723,15 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -781,6 +790,29 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -863,6 +895,15 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -939,6 +980,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "temp-env", "tempfile", "toml", "toml_edit", @@ -1025,6 +1067,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.27" @@ -1159,6 +1207,15 @@ dependencies = [ "syn", ] +[[package]] +name = "temp-env" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050" +dependencies = [ + "parking_lot", +] + [[package]] name = "tempfile" version = "3.26.0" diff --git a/Cargo.toml b/Cargo.toml index 13213542c2..36aa04bbe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rtk" version = "0.48.0" -edition = "2021" +edition = "2024" rust-version = "1.91" authors = ["Patrick Szymkowiak"] description = "Rust Token Killer - High-performance CLI proxy to minimize LLM token consumption" @@ -54,6 +54,12 @@ windows-sys = { version = "0.59", features = [ [build-dependencies] toml = "0.8" +[dev-dependencies] +# Safe, closure-scoped env mutation for tests. std::env::set_var is unsafe +# in edition 2024 and has no safe std replacement; temp-env restores the +# previous value on completion or panic and serializes callers internally. +temp-env = "0.3" + [profile.release] opt-level = 3 lto = true diff --git a/docs/guide/resources/troubleshooting.md b/docs/guide/resources/troubleshooting.md index b85b3357d6..e3aa26b782 100644 --- a/docs/guide/resources/troubleshooting.md +++ b/docs/guide/resources/troubleshooting.md @@ -144,7 +144,7 @@ cargo build --release cargo install --path . --force ``` -Minimum required Rust version: 1.70+. +Minimum required Rust version: 1.91 (edition 2024 needs Cargo 1.85 or newer). ## OpenCode not using RTK diff --git a/rustfmt.toml b/rustfmt.toml index 3501136812..0676c477b9 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,6 @@ +# cargo fmt passes --edition from Cargo.toml, but a bare `rustfmt` invocation -- +# editor format-on-save, rust-analyzer.rustfmt.overrideCommand, a pre-commit +# hook -- has no crate context and falls back to edition 2015, which cannot even +# parse this crate's let-chains. Pin it here so those agree with CI. +edition = "2024" style_edition = "2024" diff --git a/src/cmds/git/diff_cmd.rs b/src/cmds/git/diff_cmd.rs index ddf0e7fa0d..ed666a39e0 100644 --- a/src/cmds/git/diff_cmd.rs +++ b/src/cmds/git/diff_cmd.rs @@ -7342,13 +7342,13 @@ diff --git a/b.rs b/b.rs for _ in 0..3_000 { let n = (lcg(&mut seed) % 12) as usize; let m = (lcg(&mut seed) % 12) as usize; - let mut gen = |k: usize| -> Vec { + let mut make_lines = |k: usize| -> Vec { (0..k) .map(|_| format!("key{} = {}", lcg(&mut seed) % 3, lcg(&mut seed) % 5)) .collect() }; - let a_lines = gen(n); - let b_lines = gen(m); + let a_lines = make_lines(n); + let b_lines = make_lines(m); let a: Vec<&str> = a_lines.iter().map(|s| s.as_str()).collect(); let b: Vec<&str> = b_lines.iter().map(|s| s.as_str()).collect(); diff --git a/src/cmds/system/find_cmd.rs b/src/cmds/system/find_cmd.rs index a0d1a67a4a..01a12b14d1 100644 --- a/src/cmds/system/find_cmd.rs +++ b/src/cmds/system/find_cmd.rs @@ -1346,13 +1346,14 @@ mod tests { #[test] fn native_run_returns_zero_on_success() { let tmp = tempfile::tempdir().unwrap(); - std::env::set_var("RTK_TEE_DIR", tmp.path()); - std::fs::write(tmp.path().join("a.txt"), "x").unwrap(); - let root = tmp.path().to_string_lossy().into_owned(); - assert_eq!( - run("*.txt", &root, 10, false, None, "f", false, 0).unwrap(), - 0 - ); + temp_env::with_var("RTK_TEE_DIR", Some(tmp.path()), || { + std::fs::write(tmp.path().join("a.txt"), "x").unwrap(); + let root = tmp.path().to_string_lossy().into_owned(); + assert_eq!( + run("*.txt", &root, 10, false, None, "f", false, 0).unwrap(), + 0 + ); + }); } #[test] @@ -1411,28 +1412,29 @@ mod tests { #[test] fn disclosure_survives_the_output_guard() { let tee = tempfile::tempdir().unwrap(); - std::env::set_var("RTK_TEE_DIR", tee.path()); - let timer = tracking::TimedExecution::start(); - let shown = render( - vec!["visible.txt".to_string()], - 50, - false, - &["secret.txt".to_string()], - "find . -name '*.txt'", - "visible.txt", - &timer, - ); - assert!(shown.contains("(1 filtered"), "{shown}"); - let shown = render( - vec![], - 50, - false, - &["secret.txt".to_string()], - "find . -name secret.txt", - "", - &timer, - ); - assert!(shown.contains("(1 filtered"), "{shown}"); + temp_env::with_var("RTK_TEE_DIR", Some(tee.path()), || { + let timer = tracking::TimedExecution::start(); + let shown = render( + vec!["visible.txt".to_string()], + 50, + false, + &["secret.txt".to_string()], + "find . -name '*.txt'", + "visible.txt", + &timer, + ); + assert!(shown.contains("(1 filtered"), "{shown}"); + let shown = render( + vec![], + 50, + false, + &["secret.txt".to_string()], + "find . -name secret.txt", + "", + &timer, + ); + assert!(shown.contains("(1 filtered"), "{shown}"); + }); } #[cfg(unix)] diff --git a/src/core/retriever.rs b/src/core/retriever.rs index c053cc6c17..046ecde9f9 100644 --- a/src/core/retriever.rs +++ b/src/core/retriever.rs @@ -1266,9 +1266,9 @@ mod tests { let _guard = crate::core::utils::TEST_ENV_LOCK.lock().unwrap(); let dir = tempfile::tempdir().unwrap(); let cfg = temp_cfg(dir.path()); - std::env::set_var("RTK_RECALL", "0"); - record_tee_recall_with(&cfg, "grep", "/tee/1_grep.log"); - std::env::remove_var("RTK_RECALL"); + temp_env::with_var("RTK_RECALL", Some("0"), || { + record_tee_recall_with(&cfg, "grep", "/tee/1_grep.log"); + }); assert!( !dir.path().join("recall_test.db").exists(), "RTK_RECALL=0 must prevent any recall.db write from the hook path" diff --git a/src/core/stream.rs b/src/core/stream.rs index 525891d1a4..6a77a058a7 100644 --- a/src/core/stream.rs +++ b/src/core/stream.rs @@ -287,11 +287,17 @@ mod signal_relay { unsafe extern "C" fn relay(sig: libc::c_int) { let pid = CHILD_PID.load(Ordering::SeqCst); if pid == 0 || RELAYED.swap(sig, Ordering::SeqCst) != 0 { - libc::signal(sig, libc::SIG_DFL); - libc::raise(sig); + // nosemgrep: unsafe-block + unsafe { + libc::signal(sig, libc::SIG_DFL); + libc::raise(sig); + } return; } - libc::kill(pid as libc::pid_t, sig); + // nosemgrep: unsafe-block + unsafe { + libc::kill(pid as libc::pid_t, sig); + } } fn escalate(pid: u32) { diff --git a/src/core/tee.rs b/src/core/tee.rs index 5751653d07..afd59bbd78 100644 --- a/src/core/tee.rs +++ b/src/core/tee.rs @@ -113,12 +113,14 @@ mod tests { #[test] fn test_disabled_env_emits_nothing() { let _guard = crate::core::utils::TEST_ENV_LOCK.lock().unwrap(); - std::env::set_var("RTK_RECALL", "0"); let big = "x".repeat(1000); - let hint = tee_and_hint(&big, "cmd", 1); - let forced = force_tee_hint(&big, "cmd"); - let tail = force_tee_tail_hint(&big, "cmd", 5); - std::env::remove_var("RTK_RECALL"); + let (hint, forced, tail) = temp_env::with_var("RTK_RECALL", Some("0"), || { + ( + tee_and_hint(&big, "cmd", 1), + force_tee_hint(&big, "cmd"), + force_tee_tail_hint(&big, "cmd", 5), + ) + }); assert!(hint.is_none(), "disabled must never emit tokens"); assert!(forced.is_none()); assert!(tail.is_none()); diff --git a/src/core/telemetry_cmd.rs b/src/core/telemetry_cmd.rs index 971f3bfd32..43c1cde7a8 100644 --- a/src/core/telemetry_cmd.rs +++ b/src/core/telemetry_cmd.rs @@ -275,34 +275,35 @@ mod tests { /// Regression for #1307: the env opt-out must short-circuit telemetry /// consent paths so `rtk init` cannot hang in non-interactive environments. - /// All cases are bundled in one test to serialize env-var mutations. + /// All cases live in one test so the opt-out states cannot interleave. #[test] fn test_telemetry_disabled_by_env_honors_opt_out() { - #[allow(deprecated)] - std::env::remove_var(TELEMETRY_DISABLED_ENV); - assert!( - !telemetry_disabled_by_env(), - "unset env must not count as disabled" - ); - - #[allow(deprecated)] - std::env::set_var(TELEMETRY_DISABLED_ENV, TELEMETRY_DISABLED_VALUE); - assert!( - telemetry_disabled_by_env(), - "RTK_TELEMETRY_DISABLED=1 must disable telemetry prompts (issue #1307)" - ); - - for other in ["0", "true", "false", "yes", "no", ""] { - #[allow(deprecated)] - std::env::set_var(TELEMETRY_DISABLED_ENV, other); + temp_env::with_var_unset(TELEMETRY_DISABLED_ENV, || { assert!( !telemetry_disabled_by_env(), - "value {other:?} must not be treated as disabled" + "unset env must not count as disabled" ); - } + }); + + temp_env::with_var( + TELEMETRY_DISABLED_ENV, + Some(TELEMETRY_DISABLED_VALUE), + || { + assert!( + telemetry_disabled_by_env(), + "RTK_TELEMETRY_DISABLED=1 must disable telemetry prompts (issue #1307)" + ); + }, + ); - #[allow(deprecated)] - std::env::remove_var(TELEMETRY_DISABLED_ENV); + for other in ["0", "true", "false", "yes", "no", ""] { + temp_env::with_var(TELEMETRY_DISABLED_ENV, Some(other), || { + assert!( + !telemetry_disabled_by_env(), + "value {other:?} must not be treated as disabled" + ); + }); + } } // A canned 64-hex-char hash for deterministic `device_hash_line` assertions. diff --git a/src/core/tracking.rs b/src/core/tracking.rs index 709d2263fb..a484979b48 100644 --- a/src/core/tracking.rs +++ b/src/core/tracking.rs @@ -2054,19 +2054,16 @@ mod tests { )); // nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data. let _ = std::fs::remove_file(&db_path); - env::set_var("RTK_DB_PATH", &db_path); - - let timer = TimedExecution::start(); - std::thread::sleep(std::time::Duration::from_millis(10)); - timer.track("test cmd", "rtk test", "raw input data", "filtered"); - - // Verify via DB that record exists - let tracker = Tracker::new().expect("Failed to create tracker"); - let recent = tracker.get_recent(5).expect("Failed to get recent"); - assert!(recent.iter().any(|r| r.rtk_cmd == "rtk test")); - - drop(tracker); - env::remove_var("RTK_DB_PATH"); + temp_env::with_var("RTK_DB_PATH", Some(&db_path), || { + let timer = TimedExecution::start(); + std::thread::sleep(std::time::Duration::from_millis(10)); + timer.track("test cmd", "rtk test", "raw input data", "filtered"); + + // Verify via DB that record exists + let tracker = Tracker::new().expect("Failed to create tracker"); + let recent = tracker.get_recent(5).expect("Failed to get recent"); + assert!(recent.iter().any(|r| r.rtk_cmd == "rtk test")); + }); // nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data. let _ = std::fs::remove_file(&db_path); } @@ -2083,49 +2080,48 @@ mod tests { )); // nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data. let _ = std::fs::remove_file(&db_path); - env::set_var("RTK_DB_PATH", &db_path); - - let timer = TimedExecution::start(); - timer.track_passthrough("git tag", "rtk git tag (passthrough)"); + temp_env::with_var("RTK_DB_PATH", Some(&db_path), || { + let timer = TimedExecution::start(); + timer.track_passthrough("git tag", "rtk git tag (passthrough)"); - let tracker = Tracker::new().expect("Failed to create tracker"); - let recent = tracker.get_recent(5).expect("Failed to get recent"); + let tracker = Tracker::new().expect("Failed to create tracker"); + let recent = tracker.get_recent(5).expect("Failed to get recent"); - let pt = recent - .iter() - .find(|r| r.rtk_cmd.contains("passthrough")) - .expect("Passthrough record not found"); - - // savings_pct should be 0 for passthrough - assert_eq!(pt.savings_pct, 0.0); - assert_eq!(pt.saved_tokens, 0); + let pt = recent + .iter() + .find(|r| r.rtk_cmd.contains("passthrough")) + .expect("Passthrough record not found"); - drop(tracker); - env::remove_var("RTK_DB_PATH"); + // savings_pct should be 0 for passthrough + assert_eq!(pt.savings_pct, 0.0); + assert_eq!(pt.saved_tokens, 0); + }); // nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data. let _ = std::fs::remove_file(&db_path); } // 7. get_db_path respects environment variable RTK_DB_PATH // 8. get_db_path falls back to default when no custom config - // Combined into one test to avoid env var race between parallel tests + // Combined into one test so the set and unset cases cannot interleave. #[test] fn test_db_path_env_and_default() { use std::env; let _guard = ENV_LOCK.lock().unwrap(); let custom_path = env::temp_dir().join("rtk_test_custom.db"); - env::set_var("RTK_DB_PATH", &custom_path); - let db_path = get_db_path().expect("Failed to get db path"); - assert_eq!(db_path, custom_path); + temp_env::with_var("RTK_DB_PATH", Some(&custom_path), || { + let db_path = get_db_path().expect("Failed to get db path"); + assert_eq!(db_path, custom_path); + }); - env::remove_var("RTK_DB_PATH"); - let db_path = get_db_path().expect("Failed to get db path"); - assert!( - db_path.ends_with("rtk/history.db"), - "expected default path ending with rtk/history.db, got: {}", - db_path.display() - ); + temp_env::with_var_unset("RTK_DB_PATH", || { + let db_path = get_db_path().expect("Failed to get db path"); + assert!( + db_path.ends_with("rtk/history.db"), + "expected default path ending with rtk/history.db, got: {}", + db_path.display() + ); + }); } // 8b. Tracker::new() gates schema migration behind PRAGMA user_version, so a @@ -2140,24 +2136,22 @@ mod tests { env::temp_dir().join(format!("rtk_test_schema_version_{}.db", std::process::id())); // nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data. let _ = std::fs::remove_file(&db_path); - env::set_var("RTK_DB_PATH", &db_path); - - let tracker = Tracker::new().expect("first open should run migrations"); - let version: i64 = tracker - .conn - .query_row("PRAGMA user_version", [], |row| row.get(0)) - .expect("user_version should be readable"); - assert_eq!(version, SCHEMA_VERSION); - drop(tracker); - - // Second open on the same file must skip migrations without erroring, and - // the DB must still be fully usable (tables from the first open persist). - let tracker2 = Tracker::new().expect("second open should skip migrations cleanly"); - tracker2 - .record("git status", "rtk git status", 100, 20, 50) - .expect("commands table should already exist and accept writes"); - - env::remove_var("RTK_DB_PATH"); + temp_env::with_var("RTK_DB_PATH", Some(&db_path), || { + let tracker = Tracker::new().expect("first open should run migrations"); + let version: i64 = tracker + .conn + .query_row("PRAGMA user_version", [], |row| row.get(0)) + .expect("user_version should be readable"); + assert_eq!(version, SCHEMA_VERSION); + drop(tracker); + + // Second open on the same file must skip migrations without erroring, and + // the DB must still be fully usable (tables from the first open persist). + let tracker2 = Tracker::new().expect("second open should skip migrations cleanly"); + tracker2 + .record("git status", "rtk git status", 100, 20, 50) + .expect("commands table should already exist and accept writes"); + }); // nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data. let _ = std::fs::remove_file(&db_path); } diff --git a/src/hooks/hook_cmd.rs b/src/hooks/hook_cmd.rs index e596d7da9e..6d4f82fb74 100644 --- a/src/hooks/hook_cmd.rs +++ b/src/hooks/hook_cmd.rs @@ -2264,8 +2264,9 @@ mod tests { #[test] fn test_audit_log_silent_when_disabled() { - std::env::remove_var("RTK_HOOK_AUDIT"); - audit_log("test", "git status", "rtk git status"); + temp_env::with_var_unset("RTK_HOOK_AUDIT", || { + audit_log("test", "git status", "rtk git status"); + }); } #[test] diff --git a/src/hooks/init.rs b/src/hooks/init.rs index d58a7c4783..50dc517644 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -5276,70 +5276,72 @@ fn show_claude_config() -> Result<()> { } // Check OpenCode plugin - if let Ok(opencode_dir) = resolve_opencode_dir() { - let plugin = opencode_plugin_path(&opencode_dir); - if plugin.exists() { - println!("[ok] OpenCode: plugin installed ({})", plugin.display()); - } else { - println!("[--] OpenCode: plugin not found"); + match resolve_opencode_dir() { + Ok(opencode_dir) => { + let plugin = opencode_plugin_path(&opencode_dir); + if plugin.exists() { + println!("[ok] OpenCode: plugin installed ({})", plugin.display()); + } else { + println!("[--] OpenCode: plugin not found"); + } } - } else { - println!("[--] OpenCode: config dir not found"); + _ => println!("[--] OpenCode: config dir not found"), } // Check Cursor hooks - if let Ok(cursor_dir) = resolve_cursor_dir() { - let cursor_hook = cursor_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE); - let cursor_hooks_json = cursor_dir.join(HOOKS_JSON); - - // Check for binary command in hooks.json first - let cursor_binary_registered = if cursor_hooks_json.exists() { - let content = fs::read_to_string(&cursor_hooks_json).unwrap_or_default(); - if let Ok(root) = from_json_str::(&content) { - cursor_hook_already_present(&root) + match resolve_cursor_dir() { + Ok(cursor_dir) => { + let cursor_hook = cursor_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE); + let cursor_hooks_json = cursor_dir.join(HOOKS_JSON); + + // Check for binary command in hooks.json first + let cursor_binary_registered = if cursor_hooks_json.exists() { + let content = fs::read_to_string(&cursor_hooks_json).unwrap_or_default(); + if let Ok(root) = from_json_str::(&content) { + cursor_hook_already_present(&root) + } else { + false + } } else { false - } - } else { - false - }; + }; - if cursor_binary_registered { - println!("[ok] Cursor hook: registered in hooks.json"); - } else if cursor_hook.exists() { - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let meta = fs::metadata(&cursor_hook)?; - let is_executable = meta.permissions().mode() & 0o111 != 0; - let content = fs::read_to_string(&cursor_hook)?; - let _is_thin = content.contains("rtk rewrite"); + if cursor_binary_registered { + println!("[ok] Cursor hook: registered in hooks.json"); + } else if cursor_hook.exists() { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let meta = fs::metadata(&cursor_hook)?; + let is_executable = meta.permissions().mode() & 0o111 != 0; + let content = fs::read_to_string(&cursor_hook)?; + let _is_thin = content.contains("rtk rewrite"); + + if !is_executable { + println!( + "[warn] Cursor hook: {} (legacy script, NOT executable)", + cursor_hook.display() + ); + } else { + println!( + "[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)", + cursor_hook.display() + ); + } + } - if !is_executable { - println!( - "[warn] Cursor hook: {} (legacy script, NOT executable)", - cursor_hook.display() - ); - } else { + #[cfg(not(unix))] + { println!( "[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)", cursor_hook.display() ); } + } else { + println!("[--] Cursor hook: not found"); } - - #[cfg(not(unix))] - { - println!( - "[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)", - cursor_hook.display() - ); - } - } else { - println!("[--] Cursor hook: not found"); } - } else { - println!("[--] Cursor: home dir not found"); + _ => println!("[--] Cursor: home dir not found"), } println!("\nUsage:"); @@ -9026,52 +9028,30 @@ mod tests { } use std::sync::Mutex; - static CLAUDE_DIR_LOCK: Mutex<()> = Mutex::new(()); - static PI_DIR_LOCK: Mutex<()> = Mutex::new(()); /// Serialises all tests that mutate the process-wide working directory. static CWD_LOCK: Mutex<()> = Mutex::new(()); fn with_claude_dir_override(tmp: &TempDir, f: F) { - let _guard = CLAUDE_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let claude_dir = tmp.path().join(CLAUDE_DIR); fs::create_dir_all(&claude_dir).unwrap(); - let orig = std::env::var_os("CLAUDE_CONFIG_DIR"); - std::env::set_var("CLAUDE_CONFIG_DIR", &claude_dir); - f(&claude_dir); - match orig { - Some(v) => std::env::set_var("CLAUDE_CONFIG_DIR", v), - None => std::env::remove_var("CLAUDE_CONFIG_DIR"), - } + temp_env::with_var("CLAUDE_CONFIG_DIR", Some(&claude_dir), || f(&claude_dir)); } fn with_pi_dir_override(tmp: &TempDir, f: F) { - let _guard = PI_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let pi_dir = tmp.path().join("pi_agent"); fs::create_dir_all(&pi_dir).unwrap(); - let orig = std::env::var_os(PI_CODING_AGENT_DIR_ENV); - std::env::set_var(PI_CODING_AGENT_DIR_ENV, &pi_dir); - f(&pi_dir); - match orig { - Some(v) => std::env::set_var(PI_CODING_AGENT_DIR_ENV, v), - None => std::env::remove_var(PI_CODING_AGENT_DIR_ENV), - } + temp_env::with_var(PI_CODING_AGENT_DIR_ENV, Some(&pi_dir), || f(&pi_dir)); } + // OMP reuses PI_CODING_AGENT_DIR, so this overrides the same variable as + // with_pi_dir_override; temp-env serialises the two against each other. fn with_omp_dir_override(tmp: &TempDir, f: F) { - // OMP reuses PI_CODING_AGENT_DIR, so share the Pi environment lock. - let _guard = PI_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let omp_dir = tmp.path().join("omp_agent"); fs::create_dir_all(&omp_dir).unwrap(); - let orig = std::env::var_os(PI_CODING_AGENT_DIR_ENV); - std::env::set_var(PI_CODING_AGENT_DIR_ENV, &omp_dir); - f(&omp_dir); - match orig { - Some(v) => std::env::set_var(PI_CODING_AGENT_DIR_ENV, v), - None => std::env::remove_var(PI_CODING_AGENT_DIR_ENV), - } + temp_env::with_var(PI_CODING_AGENT_DIR_ENV, Some(&omp_dir), || f(&omp_dir)); } #[test] @@ -9604,18 +9584,10 @@ mod tests { fn test_run_pi_mode_global_creates_plugin_when_dir_absent() { let tmp = TempDir::new().unwrap(); let absent_dir = tmp.path().join("no_such_pi_dir"); - let _guard = PI_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner()); - let orig = std::env::var_os(PI_CODING_AGENT_DIR_ENV); - std::env::set_var(PI_CODING_AGENT_DIR_ENV, &absent_dir); - - let result = run_pi_mode(true, InitContext::default()); - - match orig { - Some(v) => std::env::set_var(PI_CODING_AGENT_DIR_ENV, v), - None => std::env::remove_var(PI_CODING_AGENT_DIR_ENV), - } - - result.unwrap(); + temp_env::with_var(PI_CODING_AGENT_DIR_ENV, Some(&absent_dir), || { + run_pi_mode(true, InitContext::default()) + }) + .unwrap(); let plugin = absent_dir.join(PI_EXTENSIONS_SUBDIR).join(PI_PLUGIN_FILE); assert!( diff --git a/src/hooks/trust.rs b/src/hooks/trust.rs index d178635cc0..7e9f58cf6f 100644 --- a/src/hooks/trust.rs +++ b/src/hooks/trust.rs @@ -403,8 +403,7 @@ mod tests { /// Overrides the store path via a scoped env var (not possible with /// the real function), so we test the logic by calling internal fns. fn setup_test_env(temp: &TempDir) -> PathBuf { - let store_file = temp.path().join("trusted_filters.json"); - store_file + temp.path().join("trusted_filters.json") } fn check_trust_with_store(filter_path: &Path, store_file: &Path) -> Result { @@ -565,15 +564,13 @@ mod tests { std::fs::write(&filter, "[filters.test]\nmatch_command = \"echo\"").unwrap(); // Both env vars must be set: trust override + CI indicator - #[allow(deprecated)] - std::env::set_var("RTK_TRUST_PROJECT_FILTERS", "1"); - #[allow(deprecated)] - std::env::set_var("CI", "true"); - let status = check_trust(&filter).unwrap(); - #[allow(deprecated)] - std::env::remove_var("RTK_TRUST_PROJECT_FILTERS"); - #[allow(deprecated)] - std::env::remove_var("CI"); + let status = temp_env::with_vars( + [ + ("RTK_TRUST_PROJECT_FILTERS", Some("1")), + ("CI", Some("true")), + ], + || check_trust(&filter).unwrap(), + ); assert_eq!(status, TrustStatus::EnvOverride); } diff --git a/src/main.rs b/src/main.rs index c4c6aacf9e..c2a71cdf8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![allow(clippy::collapsible_if)] mod analytics; mod cmds; mod core; @@ -2960,11 +2961,17 @@ fn run_cli() -> Result { unsafe extern "C" fn handle_signal(sig: libc::c_int) { let pid = PROXY_CHILD_PID.load(Ordering::SeqCst); if pid != 0 { - libc::kill(pid as libc::pid_t, libc::SIGTERM); - libc::waitpid(pid as libc::pid_t, std::ptr::null_mut(), 0); + // nosemgrep: unsafe-block + unsafe { + libc::kill(pid as libc::pid_t, libc::SIGTERM); + libc::waitpid(pid as libc::pid_t, std::ptr::null_mut(), 0); + } + } + // nosemgrep: unsafe-block + unsafe { + libc::signal(sig, libc::SIG_DFL); + libc::raise(sig); } - libc::signal(sig, libc::SIG_DFL); - libc::raise(sig); } // nosemgrep: unsafe-block unsafe { From d00c514eddaa760930c12e45707c88a102849f0d Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Mon, 14 Sep 2026 01:27:25 +0200 Subject: [PATCH 4/6] refactor: adopt let-chains Let-chains are edition 2024 syntax, so clippy::collapsible_if now fires on every nested `if let` / `if` pair, and with warnings = "deny" they are build errors rather than suggestions. Applied with cargo clippy --fix, which handles all of them in one pass, then cargo fmt to re-indent the collapsed bodies. The deepest cases flatten four levels of nesting into one condition: if let Some(cutoff_time) = cutoff && let Ok(meta) = fs::metadata(file_path) && let Ok(mtime) = meta.modified() && mtime < cutoff_time { continue; } Drops the crate-wide clippy::collapsible_if allow added in the previous commit. Co-Authored-By: Claude Opus 5 --- src/analytics/cc_economics.rs | 52 ++++----- src/cmds/cloud/aws_cmd.rs | 162 +++++++++++++-------------- src/cmds/cloud/container.rs | 32 +++--- src/cmds/cloud/psql_cmd.rs | 8 +- src/cmds/cloud/wget_cmd.rs | 18 +-- src/cmds/dotnet/binlog.rs | 16 +-- src/cmds/dotnet/dotnet_cmd.rs | 48 ++++---- src/cmds/git/diff_cmd.rs | 11 +- src/cmds/git/gh_cmd.rs | 40 +++---- src/cmds/git/git_cmd.rs | 23 ++-- src/cmds/git/glab_cmd.rs | 44 ++++---- src/cmds/go/go_cmd.rs | 29 +++-- src/cmds/go/golangci_cmd.rs | 26 ++--- src/cmds/js/next_cmd.rs | 8 +- src/cmds/js/prettier_cmd.rs | 11 +- src/cmds/js/prisma_cmd.rs | 71 ++++++------ src/cmds/jvm/mvn_cmd.rs | 38 +++---- src/cmds/php/phpt_cmd.rs | 26 ++--- src/cmds/python/mypy_cmd.rs | 27 ++--- src/cmds/python/ruff_cmd.rs | 11 +- src/cmds/python/uv_cmd.rs | 6 +- src/cmds/ruby/rake_cmd.rs | 20 ++-- src/cmds/ruby/rspec_cmd.rs | 10 +- src/cmds/rust/cargo_cmd.rs | 38 +++---- src/cmds/scala/sbt_cmd.rs | 11 +- src/cmds/system/ctest_cmd.rs | 8 +- src/cmds/system/env_cmd.rs | 8 +- src/cmds/system/find_cmd.rs | 28 ++--- src/cmds/system/format_cmd.rs | 33 +++--- src/cmds/system/local_llm.rs | 10 +- src/cmds/system/pipe_cmd.rs | 16 +-- src/cmds/system/read.rs | 58 +++++----- src/cmds/system/search.rs | 12 +- src/cmds/system/summary.rs | 9 +- src/core/arg_tokenizer.rs | 11 +- src/core/filter.rs | 20 ++-- src/core/runner.rs | 9 +- src/core/stream.rs | 34 +++--- src/core/tee_file.rs | 8 +- src/core/telemetry.rs | 42 ++++--- src/core/toml_filter.rs | 59 +++++----- src/core/utils.rs | 8 +- src/discover/lexer.rs | 10 +- src/discover/provider.rs | 75 ++++++------- src/discover/registry.rs | 25 ++--- src/hooks/hook_check.rs | 19 ++-- src/hooks/hook_cmd.rs | 55 +++++---- src/hooks/init.rs | 205 +++++++++++++++++----------------- src/hooks/permissions.rs | 27 +++-- src/hooks/rewrite_cmd.rs | 26 +++-- src/main.rs | 1 - src/parser/formatter.rs | 38 +++---- 52 files changed, 806 insertions(+), 834 deletions(-) diff --git a/src/analytics/cc_economics.rs b/src/analytics/cc_economics.rs index 5e89afcdaf..85bcd39e49 100644 --- a/src/analytics/cc_economics.rs +++ b/src/analytics/cc_economics.rs @@ -112,26 +112,26 @@ impl PeriodEconomics { fn compute_weighted_metrics(&mut self) { // Weighted input CPT derivation using API price ratios - if let (Some(cost), Some(saved)) = (self.cc_cost, self.rtk_saved_tokens) { - if let (Some(input), Some(output), Some(cache_create), Some(cache_read)) = ( + if let (Some(cost), Some(saved)) = (self.cc_cost, self.rtk_saved_tokens) + && let (Some(input), Some(output), Some(cache_create), Some(cache_read)) = ( self.cc_input_tokens, self.cc_output_tokens, self.cc_cache_create_tokens, self.cc_cache_read_tokens, - ) { - // Weighted units = input + 5*output + 1.25*cache_create + 0.1*cache_read - let weighted_units = input as f64 - + WEIGHT_OUTPUT * output as f64 - + WEIGHT_CACHE_CREATE * cache_create as f64 - + WEIGHT_CACHE_READ * cache_read as f64; - - if weighted_units > 0.0 { - let input_cpt = cost / weighted_units; - let savings = saved as f64 * input_cpt; - - self.weighted_input_cpt = Some(input_cpt); - self.savings_weighted = Some(savings); - } + ) + { + // Weighted units = input + 5*output + 1.25*cache_create + 0.1*cache_read + let weighted_units = input as f64 + + WEIGHT_OUTPUT * output as f64 + + WEIGHT_CACHE_CREATE * cache_create as f64 + + WEIGHT_CACHE_READ * cache_read as f64; + + if weighted_units > 0.0 { + let input_cpt = cost / weighted_units; + let savings = saved as f64 * input_cpt; + + self.weighted_input_cpt = Some(input_cpt); + self.savings_weighted = Some(savings); } } } @@ -139,19 +139,19 @@ impl PeriodEconomics { fn compute_dual_metrics(&mut self) { if let (Some(cost), Some(saved)) = (self.cc_cost, self.rtk_saved_tokens) { // Blended CPT (cost / total_tokens including cache) - if let Some(total) = self.cc_total_tokens { - if total > 0 { - self.blended_cpt = Some(cost / total as f64); - self.savings_blended = Some(saved as f64 * (cost / total as f64)); - } + if let Some(total) = self.cc_total_tokens + && total > 0 + { + self.blended_cpt = Some(cost / total as f64); + self.savings_blended = Some(saved as f64 * (cost / total as f64)); } // Active CPT (cost / active_tokens = input+output only) - if let Some(active) = self.cc_active_tokens { - if active > 0 { - self.active_cpt = Some(cost / active as f64); - self.savings_active = Some(saved as f64 * (cost / active as f64)); - } + if let Some(active) = self.cc_active_tokens + && active > 0 + { + self.active_cpt = Some(cost / active as f64); + self.savings_active = Some(saved as f64 * (cost / active as f64)); } } } diff --git a/src/cmds/cloud/aws_cmd.rs b/src/cmds/cloud/aws_cmd.rs index 7ce9ffd448..473b41a149 100644 --- a/src/cmds/cloud/aws_cmd.rs +++ b/src/cmds/cloud/aws_cmd.rs @@ -874,22 +874,22 @@ fn filter_lambda_get(json_str: &str) -> Option { // Show layer names if present // Layer ARNs use colons: arn:aws:lambda:region:acct:layer:name:version - if let Some(layers) = config["Layers"].as_array() { - if !layers.is_empty() { - let layer_names: Vec = layers - .iter() - .filter_map(|l| { - let arn = l["Arn"].as_str()?; - let parts: Vec<&str> = arn.rsplitn(3, ':').collect(); - if parts.len() >= 2 { - Some(format!("{}:{}", parts[1], parts[0])) - } else { - Some(arn.to_string()) - } - }) - .collect(); - text.push_str(&format!("\n layers: {}", layer_names.join(", "))); - } + if let Some(layers) = config["Layers"].as_array() + && !layers.is_empty() + { + let layer_names: Vec = layers + .iter() + .filter_map(|l| { + let arn = l["Arn"].as_str()?; + let parts: Vec<&str> = arn.rsplitn(3, ':').collect(); + if parts.len() >= 2 { + Some(format!("{}:{}", parts[1], parts[0])) + } else { + Some(arn.to_string()) + } + }) + .collect(); + text.push_str(&format!("\n layers: {}", layer_names.join(", "))); } Some(FilterResult::new(text)) @@ -1014,69 +1014,69 @@ fn unwrap_dynamodb_value(val: &Value, depth: usize) -> Value { } if let Some(obj) = val.as_object() { - if obj.len() == 1 { - if let Some((key, inner)) = obj.iter().next() { - match key.as_str() { - "S" | "B" => return inner.clone(), - "N" => { - if let Some(s) = inner.as_str() { - // Try i64 first, then f64 - if let Ok(n) = s.parse::() { - return Value::Number(n.into()); - } - if let Ok(f) = s.parse::() { - if let Some(n) = serde_json::Number::from_f64(f) { - return Value::Number(n); - } - } - return Value::String(s.to_string()); + if obj.len() == 1 + && let Some((key, inner)) = obj.iter().next() + { + match key.as_str() { + "S" | "B" => return inner.clone(), + "N" => { + if let Some(s) = inner.as_str() { + // Try i64 first, then f64 + if let Ok(n) = s.parse::() { + return Value::Number(n.into()); } - return inner.clone(); - } - "BOOL" => return inner.clone(), - "NULL" => return Value::Null, - "L" => { - if let Some(arr) = inner.as_array() { - return Value::Array( - arr.iter() - .map(|v| unwrap_dynamodb_value(v, depth + 1)) - .collect(), - ); + if let Ok(f) = s.parse::() + && let Some(n) = serde_json::Number::from_f64(f) + { + return Value::Number(n); } + return Value::String(s.to_string()); } - "M" => { - if let Some(map) = inner.as_object() { - let unwrapped: serde_json::Map = map - .iter() - .map(|(k, v)| (k.clone(), unwrap_dynamodb_value(v, depth + 1))) - .collect(); - return Value::Object(unwrapped); - } + return inner.clone(); + } + "BOOL" => return inner.clone(), + "NULL" => return Value::Null, + "L" => { + if let Some(arr) = inner.as_array() { + return Value::Array( + arr.iter() + .map(|v| unwrap_dynamodb_value(v, depth + 1)) + .collect(), + ); } - "SS" => return inner.clone(), - "NS" => { - // Parse NS set: try i64 first, then f64 - if let Some(arr) = inner.as_array() { - let nums: Vec = arr - .iter() - .filter_map(|v| { - let s = v.as_str()?; - if let Ok(n) = s.parse::() { - Some(Value::Number(n.into())) - } else if let Ok(f) = s.parse::() { - serde_json::Number::from_f64(f).map(Value::Number) - } else { - Some(Value::String(s.to_string())) - } - }) - .collect(); - return Value::Array(nums); - } - return inner.clone(); + } + "M" => { + if let Some(map) = inner.as_object() { + let unwrapped: serde_json::Map = map + .iter() + .map(|(k, v)| (k.clone(), unwrap_dynamodb_value(v, depth + 1))) + .collect(); + return Value::Object(unwrapped); + } + } + "SS" => return inner.clone(), + "NS" => { + // Parse NS set: try i64 first, then f64 + if let Some(arr) = inner.as_array() { + let nums: Vec = arr + .iter() + .filter_map(|v| { + let s = v.as_str()?; + if let Ok(n) = s.parse::() { + Some(Value::Number(n.into())) + } else if let Ok(f) = s.parse::() { + serde_json::Number::from_f64(f).map(Value::Number) + } else { + Some(Value::String(s.to_string())) + } + }) + .collect(); + return Value::Array(nums); } - "BS" => return inner.clone(), - _ => {} + return inner.clone(); } + "BS" => return inner.clone(), + _ => {} } } // Not a DynamoDB type wrapper — unwrap each field as a potential item @@ -1103,10 +1103,10 @@ fn filter_dynamodb_items(json_str: &str) -> Option { lines.push(format!("Count: {}/{}", count, scanned)); // Show ConsumedCapacity if present - if let Some(capacity) = v["ConsumedCapacity"].as_object() { - if let Some(units) = capacity["CapacityUnits"].as_f64() { - lines.push(format!("Capacity: {} RCU", units)); - } + if let Some(capacity) = v["ConsumedCapacity"].as_object() + && let Some(units) = capacity["CapacityUnits"].as_f64() + { + lines.push(format!("Capacity: {} RCU", units)); } // Show pagination status if LastEvaluatedKey exists @@ -1364,10 +1364,10 @@ fn filter_dynamodb_get_item(json_str: &str) -> Option { } // Show ConsumedCapacity if present - if let Some(capacity) = v["ConsumedCapacity"].as_object() { - if let Some(units) = capacity["CapacityUnits"].as_f64() { - lines.push(format!("Capacity: {} RCU", units)); - } + if let Some(capacity) = v["ConsumedCapacity"].as_object() + && let Some(units) = capacity["CapacityUnits"].as_f64() + { + lines.push(format!("Capacity: {} RCU", units)); } if lines.is_empty() { diff --git a/src/cmds/cloud/container.rs b/src/cmds/cloud/container.rs index 93692c41f9..49e9a470a8 100644 --- a/src/cmds/cloud/container.rs +++ b/src/cmds/cloud/container.rs @@ -281,10 +281,10 @@ fn docker_images(_verbose: u8) -> Result { if let Ok(n) = size_str.replace("GB", "").trim().parse::() { total_size_mb += n * 1024.0; } - } else if size_str.contains("MB") { - if let Ok(n) = size_str.replace("MB", "").trim().parse::() { - total_size_mb += n; - } + } else if size_str.contains("MB") + && let Ok(n) = size_str.replace("MB", "").trim().parse::() + { + total_size_mb += n; } } } @@ -401,11 +401,11 @@ fn format_kubectl_pods(json: &Value) -> String { _ => { if let Some(containers) = pod["status"]["containerStatuses"].as_array() { for c in containers { - if let Some(w) = c["state"]["waiting"]["reason"].as_str() { - if w.contains("CrashLoop") || w.contains("Error") { - failed += 1; - issues.push(format!("{}/{} {}", ns, name, w)); - } + if let Some(w) = c["state"]["waiting"]["reason"].as_str() + && (w.contains("CrashLoop") || w.contains("Error")) + { + failed += 1; + issues.push(format!("{}/{} {}", ns, name, w)); } } } @@ -649,13 +649,13 @@ pub fn format_compose_build(raw: &str) -> String { // find('[') returns byte offset — use byte slicing throughout // '[' and ']' are single-byte ASCII, so byte arithmetic is safe for line in raw.lines() { - if let Some(start) = line.find('[') { - if let Some(end) = line[start + 1..].find(']') { - let bracket = &line[start + 1..start + 1 + end]; - let svc = bracket.split_whitespace().next().unwrap_or(""); - if !svc.is_empty() && svc != "+" && !services.contains(&svc.to_string()) { - services.push(svc.to_string()); - } + if let Some(start) = line.find('[') + && let Some(end) = line[start + 1..].find(']') + { + let bracket = &line[start + 1..start + 1 + end]; + let svc = bracket.split_whitespace().next().unwrap_or(""); + if !svc.is_empty() && svc != "+" && !services.contains(&svc.to_string()) { + services.push(svc.to_string()); } } } diff --git a/src/cmds/cloud/psql_cmd.rs b/src/cmds/cloud/psql_cmd.rs index 5355d5b6af..596c08db29 100644 --- a/src/cmds/cloud/psql_cmd.rs +++ b/src/cmds/cloud/psql_cmd.rs @@ -166,10 +166,10 @@ fn filter_expanded(output: &str) -> String { } // Flush last record - if let Some(rec) = current_record.take() { - if record_count <= MAX_EXPANDED_RECORDS { - result.push(format!("{} {}", rec, current_pairs.join(" "))); - } + if let Some(rec) = current_record.take() + && record_count <= MAX_EXPANDED_RECORDS + { + result.push(format!("{} {}", rec, current_pairs.join(" "))); } if record_count > MAX_EXPANDED_RECORDS { diff --git a/src/cmds/cloud/wget_cmd.rs b/src/cmds/cloud/wget_cmd.rs index 1aa2e918ae..a9837a9e28 100644 --- a/src/cmds/cloud/wget_cmd.rs +++ b/src/cmds/cloud/wget_cmd.rs @@ -120,10 +120,10 @@ pub fn run_stdout(url: &str, args: &[String], verbose: u8) -> Result { fn extract_filename_from_output(stderr: &str, url: &str, args: &[String]) -> String { // Check for -O argument first for (i, arg) in args.iter().enumerate() { - if arg == "-O" || arg == "--output-document" { - if let Some(name) = args.get(i + 1) { - return name.clone(); - } + if (arg == "-O" || arg == "--output-document") + && let Some(name) = args.get(i + 1) + { + return name.clone(); } if let Some(name) = arg.strip_prefix("-O") { return name.to_string(); @@ -148,11 +148,11 @@ fn extract_filename_from_output(stderr: &str, url: &str, args: &[String]) -> Str } } - if let (Some(s), Some(e)) = (start_idx, end_idx) { - if e > s + 1 { - let filename: String = chars[s + 1..e].iter().collect(); - return filename.trim().to_string(); - } + if let (Some(s), Some(e)) = (start_idx, end_idx) + && e > s + 1 + { + let filename: String = chars[s + 1..e].iter().collect(); + return filename.trim().to_string(); } } } diff --git a/src/cmds/dotnet/binlog.rs b/src/cmds/dotnet/binlog.rs index e4dc5a46a1..b400553b62 100644 --- a/src/cmds/dotnet/binlog.rs +++ b/src/cmds/dotnet/binlog.rs @@ -398,18 +398,18 @@ fn parse_event_record( if reader.read_bool()? { skip_build_event_context(reader, file_format_version)?; } - if let Some(project_file) = read_optional_string(reader, parsed)? { - if !project_file.is_empty() { - parsed.project_files.insert(project_file); - } + if let Some(project_file) = read_optional_string(reader, parsed)? + && !project_file.is_empty() + { + parsed.project_files.insert(project_file); } } RECORD_PROJECT_FINISHED => { let _fields = read_event_fields(reader, file_format_version, parsed, false)?; - if let Some(project_file) = read_optional_string(reader, parsed)? { - if !project_file.is_empty() { - parsed.project_files.insert(project_file); - } + if let Some(project_file) = read_optional_string(reader, parsed)? + && !project_file.is_empty() + { + parsed.project_files.insert(project_file); } let _ = reader.read_bool()?; } diff --git a/src/cmds/dotnet/dotnet_cmd.rs b/src/cmds/dotnet/dotnet_cmd.rs index 2527f4f3c4..5ef1722ae6 100644 --- a/src/cmds/dotnet/dotnet_cmd.rs +++ b/src/cmds/dotnet/dotnet_cmd.rs @@ -69,10 +69,8 @@ pub fn run_format(args: &[String], verbose: u8) -> Result { shown, ); - if cleanup_report_path { - if let Some(path) = report_path.as_deref() { - cleanup_temp_file(path); - } + if cleanup_report_path && let Some(path) = report_path.as_deref() { + cleanup_temp_file(path); } Ok(result.exit_code) @@ -259,10 +257,8 @@ fn run_dotnet_with_binlog(subcommand: &str, args: &[String], verbose: u8) -> Res ); cleanup_temp_file(&binlog_path); - if cleanup_trx_results_dir { - if let Some(dir) = trx_results_dir.as_deref() { - cleanup_temp_dir(dir); - } + if cleanup_trx_results_dir && let Some(dir) = trx_results_dir.as_deref() { + cleanup_temp_dir(dir); } if verbose > 0 { @@ -334,11 +330,11 @@ fn build_effective_dotnet_format_args( if !force_write_mode && !has_verify_no_changes_arg(tokens) { injected.push("--verify-no-changes".to_string()); } - if !has_report_arg(tokens) { - if let Some(path) = report_path { - injected.push("--report".to_string()); - injected.push(path.display().to_string()); - } + if !has_report_arg(tokens) + && let Some(path) = report_path + { + injected.push("--report".to_string()); + injected.push(path.display().to_string()); } // Injected flags go before the user's own `--`: dotnet parks everything past it in @@ -494,10 +490,10 @@ fn merge_test_summary_from_trx( } } - if trx_summary.is_none() { - if let Some(trx) = fallback_trx_path { - trx_summary = dotnet_trx::parse_trx_file_since(&trx, command_started_at); - } + if trx_summary.is_none() + && let Some(trx) = fallback_trx_path + { + trx_summary = dotnet_trx::parse_trx_file_since(&trx, command_started_at); } let Some(trx_summary) = trx_summary else { @@ -557,11 +553,11 @@ fn build_effective_dotnet_args( effective.push("--logger".to_string()); effective.push("trx".to_string()); } - if !has_results_directory_arg(tokens, runner_mode) { - if let Some(results_dir) = trx_results_dir { - effective.push("--results-directory".to_string()); - effective.push(results_dir.display().to_string()); - } + if !has_results_directory_arg(tokens, runner_mode) + && let Some(results_dir) = trx_results_dir + { + effective.push("--results-directory".to_string()); + effective.push(results_dir.display().to_string()); } effective.extend(args.iter().cloned()); } @@ -648,10 +644,10 @@ fn scan_mtp_kind_in_file(path: &Path) -> MtpProjectKind { ); } Ok(Event::Text(e)) if inside_mtp_element => { - if let Ok(text) = e.unescape() { - if text.trim().eq_ignore_ascii_case("true") { - return MtpProjectKind::VsTestBridge; - } + if let Ok(text) = e.unescape() + && text.trim().eq_ignore_ascii_case("true") + { + return MtpProjectKind::VsTestBridge; } } Ok(Event::End(_)) => inside_mtp_element = false, diff --git a/src/cmds/git/diff_cmd.rs b/src/cmds/git/diff_cmd.rs index ed666a39e0..632f3ae24c 100644 --- a/src/cmds/git/diff_cmd.rs +++ b/src/cmds/git/diff_cmd.rs @@ -5320,12 +5320,11 @@ diff --git a/b.rs b/b.rs } continue; } - if line.starts_with("@@") { - if let Some(b) = parse_hunk_header(line) { - if !(b.1 == 0 && b.0.iter().all(|&n| n == 0)) { - budget = Some(b); - } - } + if line.starts_with("@@") + && let Some(b) = parse_hunk_header(line) + && !(b.1 == 0 && b.0.iter().all(|&n| n == 0)) + { + budget = Some(b); } } assert!( diff --git a/src/cmds/git/gh_cmd.rs b/src/cmds/git/gh_cmd.rs index e451755192..5f7a742d14 100644 --- a/src/cmds/git/gh_cmd.rs +++ b/src/cmds/git/gh_cmd.rs @@ -432,17 +432,17 @@ fn format_pr_view(json: &Value, ultra_compact: bool) -> String { out.push_str(&format!(" {}\n", url)); - if let Some(body) = json["body"].as_str() { - if !body.is_empty() { - let body_filtered = filter_markdown_body(body); - if !body_filtered.is_empty() { - out.push('\n'); - for line in body_filtered.lines() { - out.push_str(&format!(" {}\n", line)); - } - } else { - out.push_str("\n (body contained only badges/images/comments)\n"); + if let Some(body) = json["body"].as_str() + && !body.is_empty() + { + let body_filtered = filter_markdown_body(body); + if !body_filtered.is_empty() { + out.push('\n'); + for line in body_filtered.lines() { + out.push_str(&format!(" {}\n", line)); } + } else { + out.push_str("\n (body contained only badges/images/comments)\n"); } } @@ -753,17 +753,17 @@ fn format_issue_view(json: &Value) -> String { out.push_str(&format!(" Status: {}\n", state)); out.push_str(&format!(" URL: {}\n", url)); - if let Some(body) = json["body"].as_str() { - if !body.is_empty() { - let body_filtered = filter_markdown_body(body); - if !body_filtered.is_empty() { - out.push_str("\n Description:\n"); - for line in body_filtered.lines() { - out.push_str(&format!(" {}\n", line)); - } - } else { - out.push_str("\n Description: (body contained only badges/images/comments)\n"); + if let Some(body) = json["body"].as_str() + && !body.is_empty() + { + let body_filtered = filter_markdown_body(body); + if !body_filtered.is_empty() { + out.push_str("\n Description:\n"); + for line in body_filtered.lines() { + out.push_str(&format!(" {}\n", line)); } + } else { + out.push_str("\n Description: (body contained only badges/images/comments)\n"); } } out diff --git a/src/cmds/git/git_cmd.rs b/src/cmds/git/git_cmd.rs index 2904e1d0a6..17c5c4fe49 100644 --- a/src/cmds/git/git_cmd.rs +++ b/src/cmds/git/git_cmd.rs @@ -2705,12 +2705,12 @@ impl LineHandler for GitPushLineHandler { if line.contains("Everything up-to-date") { self.up_to_date = true; } - if self.pushed_ref.is_none() { - if let Some(idx) = line.find(" -> ") { - let after = &line[idx + 4..]; - if let Some(dest) = after.split_whitespace().next() { - self.pushed_ref = Some(dest.to_string()); - } + if self.pushed_ref.is_none() + && let Some(idx) = line.find(" -> ") + { + let after = &line[idx + 4..]; + if let Some(dest) = after.split_whitespace().next() { + self.pushed_ref = Some(dest.to_string()); } } } @@ -3352,13 +3352,12 @@ fn compact_stash_stat(raw: &str) -> String { } let total = files.len(); let mut out = join_with_overflow(&files[..total.min(CAP_LIST)], total, CAP_LIST, "files"); - if total > CAP_LIST { - if let Some(hint) = + if total > CAP_LIST + && let Some(hint) = crate::core::tee::force_tee_tail_hint(&files.join("\n"), "git-stash-show", CAP_LIST + 1) - { - out.push(' '); - out.push_str(&hint); - } + { + out.push(' '); + out.push_str(&hint); } if !summary.is_empty() { out.push('\n'); diff --git a/src/cmds/git/glab_cmd.rs b/src/cmds/git/glab_cmd.rs index a116e01b6c..96c53c551c 100644 --- a/src/cmds/git/glab_cmd.rs +++ b/src/cmds/git/glab_cmd.rs @@ -396,24 +396,24 @@ fn format_mr_view(json: &Value, ultra_compact: bool) -> String { } } - if let Some(pipeline) = json.get("head_pipeline") { - if !pipeline.is_null() { - let pipeline_status = pipeline["status"].as_str().unwrap_or("unknown"); - let p_icon = pipeline_icon(pipeline_status, ultra_compact); - filtered.push_str(&format!(" Pipeline: {} {}\n", p_icon, pipeline_status)); - } + if let Some(pipeline) = json.get("head_pipeline") + && !pipeline.is_null() + { + let pipeline_status = pipeline["status"].as_str().unwrap_or("unknown"); + let p_icon = pipeline_icon(pipeline_status, ultra_compact); + filtered.push_str(&format!(" Pipeline: {} {}\n", p_icon, pipeline_status)); } filtered.push_str(&format!(" {}\n", web_url)); - if let Some(desc) = json["description"].as_str() { - if !desc.is_empty() { - let desc_filtered = filter_markdown_body(desc); - if !desc_filtered.is_empty() { - filtered.push('\n'); - for line in desc_filtered.lines() { - filtered.push_str(&format!(" {}\n", line)); - } + if let Some(desc) = json["description"].as_str() + && !desc.is_empty() + { + let desc_filtered = filter_markdown_body(desc); + if !desc_filtered.is_empty() { + filtered.push('\n'); + for line in desc_filtered.lines() { + filtered.push_str(&format!(" {}\n", line)); } } } @@ -610,14 +610,14 @@ fn format_issue_view(json: &Value) -> String { filtered.push_str(&format!(" Status: {}\n", state)); filtered.push_str(&format!(" URL: {}\n", web_url)); - if let Some(desc) = json["description"].as_str() { - if !desc.is_empty() { - let desc_filtered = filter_markdown_body(desc); - if !desc_filtered.is_empty() { - filtered.push_str("\n Description:\n"); - for line in desc_filtered.lines() { - filtered.push_str(&format!(" {}\n", line)); - } + if let Some(desc) = json["description"].as_str() + && !desc.is_empty() + { + let desc_filtered = filter_markdown_body(desc); + if !desc_filtered.is_empty() { + filtered.push_str("\n Description:\n"); + for line in desc_filtered.lines() { + filtered.push_str(&format!(" {}\n", line)); } } } diff --git a/src/cmds/go/go_cmd.rs b/src/cmds/go/go_cmd.rs index dd97aa8645..7f7c389a36 100644 --- a/src/cmds/go/go_cmd.rs +++ b/src/cmds/go/go_cmd.rs @@ -213,12 +213,11 @@ impl GoTool { /// If the first arg is `tool` identify if it is a tool we already handle. fn match_go_tool(args: &[OsString]) -> Option<(GoTool, &[OsString])> { - if args.first().map(|a| a == "tool").unwrap_or(false) { - if let Some(tool_arg) = args.get(1) { - if let Some(tool) = GoTool::from_name(&tool_arg.to_string_lossy()) { - return Some((tool, &args[2..])); - } - } + if args.first().map(|a| a == "tool").unwrap_or(false) + && let Some(tool_arg) = args.get(1) + && let Some(tool) = GoTool::from_name(&tool_arg.to_string_lossy()) + { + return Some((tool, &args[2..])); } None } @@ -350,10 +349,10 @@ pub(crate) fn filter_go_test_json(output: &str) -> String { // Package-level build failure pkg_result.build_failed = true; // Collect build errors from the import path - if let Some(import_path) = &event.failed_build { - if let Some(errors) = build_output.remove(import_path) { - pkg_result.build_errors = errors; - } + if let Some(import_path) = &event.failed_build + && let Some(errors) = build_output.remove(import_path) + { + pkg_result.build_errors = errors; } } else { // Package-level failure without a specific test or build error @@ -518,15 +517,15 @@ fn select_go_test_failure_lines(outputs: &[String]) -> Vec { } } - if relevant.is_empty() { - if let Some(line) = outputs.iter().map(|line| line.trim()).find(|line| { + if relevant.is_empty() + && let Some(line) = outputs.iter().map(|line| line.trim()).find(|line| { !line.is_empty() && !line.starts_with("=== RUN") && !line.starts_with("--- FAIL") && !line.starts_with("--- PASS") - }) { - relevant.push(line.to_string()); - } + }) + { + relevant.push(line.to_string()); } relevant diff --git a/src/cmds/go/golangci_cmd.rs b/src/cmds/go/golangci_cmd.rs index 26e4a7428e..1912011da4 100644 --- a/src/cmds/go/golangci_cmd.rs +++ b/src/cmds/go/golangci_cmd.rs @@ -166,10 +166,9 @@ pub(crate) fn parse_major_version(version_output: &str) -> u32 { .split('.') .next() .and_then(|s| s.parse::().ok()) + && version.contains('.') { - if version.contains('.') { - return major; - } + return major; } } 1 @@ -424,17 +423,16 @@ pub(crate) fn filter_golangci_json(output: &str, version: u32) -> String { result.push_str(&format!(" {} ({})\n", linter, linter_issues.len())); // v2 only: show first source line for this linter-file group - if version >= 2 { - if let Some(first_issue) = linter_issues.first() { - if let Some(source_line) = first_issue.source_lines.first() { - let trimmed = source_line.trim(); - let display = match trimmed.char_indices().nth(80) { - Some((i, _)) => &trimmed[..i], - None => trimmed, - }; - result.push_str(&format!(" → {}\n", display)); - } - } + if version >= 2 + && let Some(first_issue) = linter_issues.first() + && let Some(source_line) = first_issue.source_lines.first() + { + let trimmed = source_line.trim(); + let display = match trimmed.char_indices().nth(80) { + Some((i, _)) => &trimmed[..i], + None => trimmed, + }; + result.push_str(&format!(" → {}\n", display)); } } } diff --git a/src/cmds/js/next_cmd.rs b/src/cmds/js/next_cmd.rs index d87fbdcb15..88bab9ec93 100644 --- a/src/cmds/js/next_cmd.rs +++ b/src/cmds/js/next_cmd.rs @@ -95,10 +95,10 @@ fn filter_next_build(output: &str) -> String { } // Extract build time - if line.contains("Compiled") || line.contains("in") { - if let Some(time_match) = extract_time(line) { - build_time = time_match; - } + if (line.contains("Compiled") || line.contains("in")) + && let Some(time_match) = extract_time(line) + { + build_time = time_match; } } diff --git a/src/cmds/js/prettier_cmd.rs b/src/cmds/js/prettier_cmd.rs index 54ef74064d..2919b69092 100644 --- a/src/cmds/js/prettier_cmd.rs +++ b/src/cmds/js/prettier_cmd.rs @@ -64,12 +64,11 @@ pub fn filter_prettier_output(output: &str) -> String { } // Count total files checked - if trimmed.contains("All matched files use Prettier") { - if let Some(count_str) = trimmed.split_whitespace().next() { - if let Ok(count) = count_str.parse::() { - files_checked = count; - } - } + if trimmed.contains("All matched files use Prettier") + && let Some(count_str) = trimmed.split_whitespace().next() + && let Ok(count) = count_str.parse::() + { + files_checked = count; } } diff --git a/src/cmds/js/prisma_cmd.rs b/src/cmds/js/prisma_cmd.rs index 35593348f8..d25aafab5d 100644 --- a/src/cmds/js/prisma_cmd.rs +++ b/src/cmds/js/prisma_cmd.rs @@ -195,20 +195,21 @@ fn filter_prisma_generate(output: &str) -> String { } // Extract counts - if line.contains("model") && line.contains("generated") { - if let Some(num) = extract_number(line) { - models = num; - } + if line.contains("model") + && line.contains("generated") + && let Some(num) = extract_number(line) + { + models = num; } - if line.contains("enum") { - if let Some(num) = extract_number(line) { - enums = num; - } + if line.contains("enum") + && let Some(num) = extract_number(line) + { + enums = num; } - if line.contains("type") { - if let Some(num) = extract_number(line) { - types = num; - } + if line.contains("type") + && let Some(num) = extract_number(line) + { + types = num; } // Extract output path @@ -245,13 +246,14 @@ fn filter_migrate_dev(output: &str) -> String { for line in output.lines() { // Extract migration name - if line.contains("migration") && line.contains("_") { - if let Some(pos) = line.find("202") { - let end = line[pos..] - .find(|c: char| c.is_whitespace()) - .unwrap_or(line.len() - pos); - migration_name = line[pos..pos + end].to_string(); - } + if line.contains("migration") + && line.contains("_") + && let Some(pos) = line.find("202") + { + let end = line[pos..] + .find(|c: char| c.is_whitespace()) + .unwrap_or(line.len() - pos); + migration_name = line[pos..pos + end].to_string(); } // Count changes @@ -261,15 +263,15 @@ fn filter_migrate_dev(output: &str) -> String { if line.contains("ALTER TABLE") { tables_modified += 1; } - if line.contains("FOREIGN KEY") || line.contains("REFERENCES") { - if let Some(table) = extract_table_name(line) { - relations.push(table); - } + if (line.contains("FOREIGN KEY") || line.contains("REFERENCES")) + && let Some(table) = extract_table_name(line) + { + relations.push(table); } - if line.contains("CREATE INDEX") || line.contains("CREATE UNIQUE INDEX") { - if let Some(idx) = extract_index_name(line) { - indexes.push(idx); - } + if (line.contains("CREATE INDEX") || line.contains("CREATE UNIQUE INDEX")) + && let Some(idx) = extract_index_name(line) + { + indexes.push(idx); } if line.contains("applied") || line.contains("✓") { @@ -314,13 +316,14 @@ fn filter_migrate_status(output: &str) -> String { for line in output.lines() { if line.contains("applied") { applied_count += 1; - if latest_migration.is_empty() && line.contains("202") { - if let Some(pos) = line.find("202") { - let end = line[pos..] - .find(|c: char| c.is_whitespace()) - .unwrap_or(line.len() - pos); - latest_migration = line[pos..pos + end].to_string(); - } + if latest_migration.is_empty() + && line.contains("202") + && let Some(pos) = line.find("202") + { + let end = line[pos..] + .find(|c: char| c.is_whitespace()) + .unwrap_or(line.len() - pos); + latest_migration = line[pos..pos + end].to_string(); } } if line.contains("pending") || line.contains("unapplied") { diff --git a/src/cmds/jvm/mvn_cmd.rs b/src/cmds/jvm/mvn_cmd.rs index 7a87f942c0..90e1d81f29 100644 --- a/src/cmds/jvm/mvn_cmd.rs +++ b/src/cmds/jvm/mvn_cmd.rs @@ -647,25 +647,23 @@ impl<'a> SurefireBlock<'a> { } if self.in_block { - if keyed { - if let Some(caps) = CLOSE.captures(core) { - let fail = caps.get(1).map(|m| m.as_str() != "0").unwrap_or(false); - let err = caps.get(2).map(|m| m.as_str() != "0").unwrap_or(false); - if fail || err { - let lines = std::mem::take(&mut self.block_lines); - let running = self.block_running.take(); - self.in_block = false; - return SurefireStep::FailingClose { - running, - lines, - close: line, - }; - } - self.block_lines.clear(); - self.block_running = None; + if keyed && let Some(caps) = CLOSE.captures(core) { + let fail = caps.get(1).map(|m| m.as_str() != "0").unwrap_or(false); + let err = caps.get(2).map(|m| m.as_str() != "0").unwrap_or(false); + if fail || err { + let lines = std::mem::take(&mut self.block_lines); + let running = self.block_running.take(); self.in_block = false; - return SurefireStep::Consumed; + return SurefireStep::FailingClose { + running, + lines, + close: line, + }; } + self.block_lines.clear(); + self.block_running = None; + self.in_block = false; + return SurefireStep::Consumed; } self.block_lines.push(line); return SurefireStep::Consumed; @@ -1253,10 +1251,8 @@ fn drive_surefire_line<'a>( // repetition (cold-preclear finding, upstream PR #3199, third review // round: that inference silently shared Surefire's leftover budget with // a module whose only failures were integration-test ones). - if keyed { - if let Some(caps) = TEST_PLUGIN_BANNER.captures(core) { - summary.observe_plugin_banner(caps.get(1).map_or("", |m| m.as_str())); - } + if keyed && let Some(caps) = TEST_PLUGIN_BANNER.captures(core) { + summary.observe_plugin_banner(caps.get(1).map_or("", |m| m.as_str())); } let step = lanes diff --git a/src/cmds/php/phpt_cmd.rs b/src/cmds/php/phpt_cmd.rs index a3b644214b..b828e929e5 100644 --- a/src/cmds/php/phpt_cmd.rs +++ b/src/cmds/php/phpt_cmd.rs @@ -211,21 +211,21 @@ fn parse(stripped: &str) -> Parsed { continue; } - if p.env.version.is_empty() { - if let Some(rest) = line.strip_prefix("PHP_VERSION") { - p.env.version = after_colon(rest); - } + if p.env.version.is_empty() + && let Some(rest) = line.strip_prefix("PHP_VERSION") + { + p.env.version = after_colon(rest); } - if p.env.sapi.is_empty() { - if let Some(rest) = line.strip_prefix("PHP_SAPI") { - p.env.sapi = after_colon(rest); - } + if p.env.sapi.is_empty() + && let Some(rest) = line.strip_prefix("PHP_SAPI") + { + p.env.sapi = after_colon(rest); } - if p.env.os.is_empty() { - if let Some(rest) = line.strip_prefix("PHP_OS") { - let val = after_colon(rest); - p.env.os = val.split(" - ").next().unwrap_or(&val).trim().to_string(); - } + if p.env.os.is_empty() + && let Some(rest) = line.strip_prefix("PHP_OS") + { + let val = after_colon(rest); + p.env.os = val.split(" - ").next().unwrap_or(&val).trim().to_string(); } if let Some(cap) = STATUS_RE.captures(line) { diff --git a/src/cmds/python/mypy_cmd.rs b/src/cmds/python/mypy_cmd.rs index b94807aab1..bba06c3818 100644 --- a/src/cmds/python/mypy_cmd.rs +++ b/src/cmds/python/mypy_cmd.rs @@ -89,12 +89,12 @@ pub fn filter_mypy_output(output: &str) -> String { if severity == "note" { // Attach note to preceding error if same file and line - if let Some(last) = errors.last_mut() { - if last.file == file { - last.context_lines.push(message); - i += 1; - continue; - } + if let Some(last) = errors.last_mut() + && last.file == file + { + last.context_lines.push(message); + i += 1; + continue; } // Standalone note with no parent -- display as fileless fileless_lines.push(line.to_string()); @@ -113,13 +113,14 @@ pub fn filter_mypy_output(output: &str) -> String { // Capture continuation note lines i += 1; while i < lines.len() { - if let Some(next_caps) = MYPY_DIAG.captures(lines[i]) { - if &next_caps[3] == "note" && next_caps[1] == err.file { - let note_msg = next_caps[4].to_string(); - err.context_lines.push(note_msg); - i += 1; - continue; - } + if let Some(next_caps) = MYPY_DIAG.captures(lines[i]) + && &next_caps[3] == "note" + && next_caps[1] == err.file + { + let note_msg = next_caps[4].to_string(); + err.context_lines.push(note_msg); + i += 1; + continue; } break; } diff --git a/src/cmds/python/ruff_cmd.rs b/src/cmds/python/ruff_cmd.rs index 485b3bd7fe..6a6202df41 100644 --- a/src/cmds/python/ruff_cmd.rs +++ b/src/cmds/python/ruff_cmd.rs @@ -285,11 +285,12 @@ pub fn filter_ruff_format(output: &str) -> String { let words: Vec<&str> = part.split_whitespace().collect(); // Look for number before "file" or "files" for (i, word) in words.iter().enumerate() { - if (word == &"file" || word == &"files") && i > 0 { - if let Ok(count) = words[i - 1].parse::() { - files_checked = count; - break; - } + if (word == &"file" || word == &"files") + && i > 0 + && let Ok(count) = words[i - 1].parse::() + { + files_checked = count; + break; } } break; diff --git a/src/cmds/python/uv_cmd.rs b/src/cmds/python/uv_cmd.rs index b03030252d..cbf658fc61 100644 --- a/src/cmds/python/uv_cmd.rs +++ b/src/cmds/python/uv_cmd.rs @@ -187,10 +187,8 @@ fn program_output(text: &str, tee_slug: &str) -> String { if capped.len() <= CAP_INVENTORY { let out = capped.join("\n"); - if line_was_cut { - if let Some(hint) = crate::core::tee::force_tee_hint(&clean, tee_slug) { - return format!("{out}\n{hint}"); - } + if line_was_cut && let Some(hint) = crate::core::tee::force_tee_hint(&clean, tee_slug) { + return format!("{out}\n{hint}"); } return out; } diff --git a/src/cmds/ruby/rake_cmd.rs b/src/cmds/ruby/rake_cmd.rs index adef345fcd..d84ceea0cb 100644 --- a/src/cmds/ruby/rake_cmd.rs +++ b/src/cmds/ruby/rake_cmd.rs @@ -242,16 +242,16 @@ fn parse_minitest_summary(summary: &str) -> (usize, usize, usize, usize, usize) for part in summary.split(',') { let part = part.trim(); let words: Vec<&str> = part.split_whitespace().collect(); - if words.len() >= 2 { - if let Ok(n) = words[0].parse::() { - match words[1].trim_end_matches(',') { - "runs" | "run" | "tests" | "test" => runs = n, - "assertions" | "assertion" => assertions = n, - "failures" | "failure" => failures = n, - "errors" | "error" => errors = n, - "skips" | "skip" => skips = n, - _ => {} - } + if words.len() >= 2 + && let Ok(n) = words[0].parse::() + { + match words[1].trim_end_matches(',') { + "runs" | "run" | "tests" | "test" => runs = n, + "assertions" | "assertion" => assertions = n, + "failures" | "failure" => failures = n, + "errors" | "error" => errors = n, + "skips" | "skip" => skips = n, + _ => {} } } } diff --git a/src/cmds/ruby/rspec_cmd.rs b/src/cmds/ruby/rspec_cmd.rs index 485a044f68..31d4766e88 100644 --- a/src/cmds/ruby/rspec_cmd.rs +++ b/src/cmds/ruby/rspec_cmd.rs @@ -144,11 +144,11 @@ fn strip_noise(output: &str) -> String { } // Capybara screenshots: keep only the path - if let Some(caps) = RE_SCREENSHOT.captures(trimmed) { - if let Some(path) = caps.get(1) { - result.push(format!("[screenshot: {}]", path.as_str().trim())); - continue; - } + if let Some(caps) = RE_SCREENSHOT.captures(trimmed) + && let Some(path) = caps.get(1) + { + result.push(format!("[screenshot: {}]", path.as_str().trim())); + continue; } result.push(line.to_string()); diff --git a/src/cmds/rust/cargo_cmd.rs b/src/cmds/rust/cargo_cmd.rs index 2027c8ab51..3960418391 100644 --- a/src/cmds/rust/cargo_cmd.rs +++ b/src/cmds/rust/cargo_cmd.rs @@ -174,12 +174,11 @@ impl CargoTestHandler { } } - if all_parsed { - if let Some(agg) = aggregated { - if agg.suites > 0 { - return Some(format!("{}\n", agg.format_compact())); - } - } + if all_parsed + && let Some(agg) = aggregated + && agg.suites > 0 + { + return Some(format!("{}\n", agg.format_compact())); } // Fallback: show raw summary lines @@ -358,10 +357,10 @@ fn has_json_message_format(args: &[String]) -> bool { while let Some(arg) = iter.next() { if let Some(val) = arg.strip_prefix("--message-format=") { json = val.contains("json"); - } else if arg == "--message-format" { - if let Some(val) = iter.next() { - json = val.contains("json"); - } + } else if arg == "--message-format" + && let Some(val) = iter.next() + { + json = val.contains("json"); } } json @@ -674,10 +673,10 @@ fn filter_cargo_nextest(output: &str) -> String { // Parse binary count from Starting line if trimmed.starts_with("Starting") { - if let Some(caps) = starting_re.captures(trimmed) { - if let Some(m) = caps.get(1) { - binaries = m.as_str().parse().unwrap_or(0); - } + if let Some(caps) = starting_re.captures(trimmed) + && let Some(m) = caps.get(1) + { + binaries = m.as_str().parse().unwrap_or(0); } continue; } @@ -1209,12 +1208,11 @@ pub(crate) fn filter_cargo_test(output: &str) -> String { } // If all lines parsed successfully and we have at least one suite, return compact format - if all_parsed { - if let Some(agg) = aggregated { - if agg.suites > 0 { - return agg.format_compact(); - } - } + if all_parsed + && let Some(agg) = aggregated + && agg.suites > 0 + { + return agg.format_compact(); } // Fallback: use original behavior if regex failed diff --git a/src/cmds/scala/sbt_cmd.rs b/src/cmds/scala/sbt_cmd.rs index 401ff34e9c..de4032dd91 100644 --- a/src/cmds/scala/sbt_cmd.rs +++ b/src/cmds/scala/sbt_cmd.rs @@ -277,12 +277,11 @@ fn filter_sbt_test(output: &str) -> String { // Keep Mockito "-> at" pointers and ScalaMock locations // (they include the file:line reference). let is_stack_frame = detail.starts_with("at ") || detail.starts_with("..."); - if !is_stack_frame { - if let Some(block) = failures.last_mut() { - if block.details.len() < 4 { - block.details.push(detail.to_string()); - } - } + if !is_stack_frame + && let Some(block) = failures.last_mut() + && block.details.len() < 4 + { + block.details.push(detail.to_string()); } } continue; diff --git a/src/cmds/system/ctest_cmd.rs b/src/cmds/system/ctest_cmd.rs index 2154d29d63..c32c4c994b 100644 --- a/src/cmds/system/ctest_cmd.rs +++ b/src/cmds/system/ctest_cmd.rs @@ -614,10 +614,10 @@ fn format_failed_section( ) -> String { let section = build_failed_section(lines, failed_tests, framing_lines); let mut rendered = section.rendered; - if section.truncated { - if let Some(hint) = crate::core::tee::force_tee_hint(§ion.full, "ctest-failed") { - rendered.push_str(&format!("\n {hint}")); - } + if section.truncated + && let Some(hint) = crate::core::tee::force_tee_hint(§ion.full, "ctest-failed") + { + rendered.push_str(&format!("\n {hint}")); } rendered } diff --git a/src/cmds/system/env_cmd.rs b/src/cmds/system/env_cmd.rs index e2cea8a205..10ed3e770a 100644 --- a/src/cmds/system/env_cmd.rs +++ b/src/cmds/system/env_cmd.rs @@ -27,10 +27,10 @@ pub fn run(filter: Option<&str>, verbose: u8) -> Result<()> { for (key, value) in &vars { // Apply filter if provided - if let Some(f) = filter { - if !key.to_lowercase().contains(&f.to_lowercase()) { - continue; - } + if let Some(f) = filter + && !key.to_lowercase().contains(&f.to_lowercase()) + { + continue; } let display_value = if value.len() > 100 { diff --git a/src/cmds/system/find_cmd.rs b/src/cmds/system/find_cmd.rs index 01a12b14d1..e02fcdb569 100644 --- a/src/cmds/system/find_cmd.rs +++ b/src/cmds/system/find_cmd.rs @@ -172,21 +172,21 @@ fn dispatch(original: &[String]) -> Result { }; return Ok(Dispatch::Verbatim(verbatim)); } - if options.is_empty() { - if let Some(mut parsed) = parse_subset(&paths, &expr) { - let repeated_max = max.is_some() && parsed.max_explicit; - let repeated_type = - file_type.is_some() && parsed.file_type != FindArgs::default().file_type; - if !repeated_max && !repeated_type { - if let Some(n) = max { - parsed.max_results = n; - parsed.max_explicit = true; - } - if let Some(t) = file_type { - parsed.file_type = t; - } - return Ok(Dispatch::Native(parsed)); + if options.is_empty() + && let Some(mut parsed) = parse_subset(&paths, &expr) + { + let repeated_max = max.is_some() && parsed.max_explicit; + let repeated_type = + file_type.is_some() && parsed.file_type != FindArgs::default().file_type; + if !repeated_max && !repeated_type { + if let Some(n) = max { + parsed.max_results = n; + parsed.max_explicit = true; + } + if let Some(t) = file_type { + parsed.file_type = t; } + return Ok(Dispatch::Native(parsed)); } } Ok(Dispatch::Compress { diff --git a/src/cmds/system/format_cmd.rs b/src/cmds/system/format_cmd.rs index 0ebfa3ab45..67d3dee795 100644 --- a/src/cmds/system/format_cmd.rs +++ b/src/cmds/system/format_cmd.rs @@ -168,11 +168,12 @@ fn filter_black_output(output: &str) -> String { if part_lower.contains("would be reformatted") { // Parse "X file(s) would be reformatted" for (i, word) in words.iter().enumerate() { - if (word == &"file" || word == &"files") && i > 0 { - if let Ok(count) = words[i - 1].parse::() { - files_would_reformat = count; - break; - } + if (word == &"file" || word == &"files") + && i > 0 + && let Ok(count) = words[i - 1].parse::() + { + files_would_reformat = count; + break; } } } @@ -180,11 +181,12 @@ fn filter_black_output(output: &str) -> String { if part_lower.contains("would be left unchanged") { // Parse "X file(s) would be left unchanged" for (i, word) in words.iter().enumerate() { - if (word == &"file" || word == &"files") && i > 0 { - if let Ok(count) = words[i - 1].parse::() { - files_unchanged = count; - break; - } + if (word == &"file" || word == &"files") + && i > 0 + && let Ok(count) = words[i - 1].parse::() + { + files_unchanged = count; + break; } } } @@ -195,11 +197,12 @@ fn filter_black_output(output: &str) -> String { if lower.contains("left unchanged") && !lower.contains("would be") { let words: Vec<&str> = trimmed.split_whitespace().collect(); for (i, word) in words.iter().enumerate() { - if (word == &"file" || word == &"files") && i > 0 { - if let Ok(count) = words[i - 1].parse::() { - files_unchanged = count; - break; - } + if (word == &"file" || word == &"files") + && i > 0 + && let Ok(count) = words[i - 1].parse::() + { + files_unchanged = count; + break; } } } diff --git a/src/cmds/system/local_llm.rs b/src/cmds/system/local_llm.rs index 20ac7c18f5..a4e53578d4 100644 --- a/src/cmds/system/local_llm.rs +++ b/src/cmds/system/local_llm.rs @@ -184,10 +184,12 @@ fn extract_functions(content: &str, lang: &Language) -> Vec { for line in content.lines() { if let Some(caps) = re.captures(line) { let name = caps.get(1).or(caps.get(2)).map(|m| m.as_str().to_string()); - if let Some(n) = name { - if !n.starts_with("test_") && n != "main" && n != "new" { - functions.push(n); - } + if let Some(n) = name + && !n.starts_with("test_") + && n != "main" + && n != "new" + { + functions.push(n); } } } diff --git a/src/cmds/system/pipe_cmd.rs b/src/cmds/system/pipe_cmd.rs index 3f70dd622a..b133673ac5 100644 --- a/src/cmds/system/pipe_cmd.rs +++ b/src/cmds/system/pipe_cmd.rs @@ -93,14 +93,14 @@ fn grep_wrapper(input: &str) -> String { for line in input.lines() { let parts: Vec<&str> = line.splitn(3, ':').collect(); - if parts.len() == 3 { - if let Ok(_line_num) = parts[1].parse::() { - total += 1; - by_file - .entry(parts[0]) - .or_default() - .push((parts[1], parts[2])); - } + if parts.len() == 3 + && let Ok(_line_num) = parts[1].parse::() + { + total += 1; + by_file + .entry(parts[0]) + .or_default() + .push((parts[1], parts[2])); } } diff --git a/src/cmds/system/read.rs b/src/cmds/system/read.rs index c8fd457101..45131fb22d 100644 --- a/src/cmds/system/read.rs +++ b/src/cmds/system/read.rs @@ -26,20 +26,21 @@ pub fn run( // Read file content let bytes = fs::read(file).with_context(|| format!("Failed to read file: {}", file.display()))?; - if level == FilterLevel::None && !line_numbers { - if let Some(window) = byte_line_window(&bytes, head_lines, tail_lines) { - io::stdout() - .lock() - .write_all(window) - .context("Failed to write line window")?; - timer.track( - &format!("cat {}", file.display()), - "rtk read", - &String::from_utf8_lossy(&bytes), - &String::from_utf8_lossy(window), - ); - return Ok(()); - } + if level == FilterLevel::None + && !line_numbers + && let Some(window) = byte_line_window(&bytes, head_lines, tail_lines) + { + io::stdout() + .lock() + .write_all(window) + .context("Failed to write line window")?; + timer.track( + &format!("cat {}", file.display()), + "rtk read", + &String::from_utf8_lossy(&bytes), + &String::from_utf8_lossy(window), + ); + return Ok(()); } let content = String::from_utf8(bytes) .with_context(|| format!("Failed to decode file: {}", file.display()))?; @@ -119,20 +120,21 @@ pub fn run_stdin( .lock() .read_to_end(&mut bytes) .context("Failed to read from stdin")?; - if level == FilterLevel::None && !line_numbers { - if let Some(window) = byte_line_window(&bytes, head_lines, tail_lines) { - io::stdout() - .lock() - .write_all(window) - .context("Failed to write line window")?; - timer.track( - "cat - (stdin)", - "rtk read -", - &String::from_utf8_lossy(&bytes), - &String::from_utf8_lossy(window), - ); - return Ok(()); - } + if level == FilterLevel::None + && !line_numbers + && let Some(window) = byte_line_window(&bytes, head_lines, tail_lines) + { + io::stdout() + .lock() + .write_all(window) + .context("Failed to write line window")?; + timer.track( + "cat - (stdin)", + "rtk read -", + &String::from_utf8_lossy(&bytes), + &String::from_utf8_lossy(window), + ); + return Ok(()); } let content = String::from_utf8(bytes).context("Failed to decode stdin")?; diff --git a/src/cmds/system/search.rs b/src/cmds/system/search.rs index 416fa368d9..c3238765d7 100644 --- a/src/cmds/system/search.rs +++ b/src/cmds/system/search.rs @@ -1008,12 +1008,12 @@ fn has_format_flag>(engine: Engine, extra_args: &[T]) -> bool { fn clean_line(line: &str, max_len: usize, context_re: Option<&Regex>, pattern: &str) -> String { let trimmed = line.trim(); - if let Some(re) = context_re { - if let Some(m) = re.find(trimmed) { - let matched = m.as_str(); - if matched.len() <= max_len { - return matched.to_string(); - } + if let Some(re) = context_re + && let Some(m) = re.find(trimmed) + { + let matched = m.as_str(); + if matched.len() <= max_len { + return matched.to_string(); } } diff --git a/src/cmds/system/summary.rs b/src/cmds/system/summary.rs index 204dee6c7a..012d8e5804 100644 --- a/src/cmds/system/summary.rs +++ b/src/cmds/system/summary.rs @@ -137,11 +137,10 @@ fn summarize_tests(output: &str, result: &mut Vec) { failures.push(line.to_string()); } } - if lower.contains("skipped") || lower.contains("ignored") { - if let Some(n) = extract_number(&lower, "skipped").or(extract_number(&lower, "ignored")) - { - skipped = n; - } + if (lower.contains("skipped") || lower.contains("ignored")) + && let Some(n) = extract_number(&lower, "skipped").or(extract_number(&lower, "ignored")) + { + skipped = n; } } diff --git a/src/core/arg_tokenizer.rs b/src/core/arg_tokenizer.rs index 544c19a9b2..89b59fe21d 100644 --- a/src/core/arg_tokenizer.rs +++ b/src/core/arg_tokenizer.rs @@ -332,12 +332,11 @@ impl<'a, 'p, T: AsRef> Scanner<'a, 'p, T> { if attached.is_none() && prefix != FlagPrefix::Slash { // `solo_only` cannot apply here: a Long flag is always the whole argument. - if let Some(spec) = (self.takes_value)(TokenKind::Long, name) { - if spec.attachment != Attachment::AttachedOnly - && self.link_next_value(flag_index, self.i, spec) - { - self.i += 1; - } + if let Some(spec) = (self.takes_value)(TokenKind::Long, name) + && spec.attachment != Attachment::AttachedOnly + && self.link_next_value(flag_index, self.i, spec) + { + self.i += 1; } } } diff --git a/src/core/filter.rs b/src/core/filter.rs index 6b1c204573..5a07cb1a16 100644 --- a/src/core/filter.rs +++ b/src/core/filter.rs @@ -198,17 +198,17 @@ impl FilterStrategy for MinimalFilter { } // Skip single-line comments (but keep doc comments) - if let Some(line_comment) = patterns.line { - if trimmed.starts_with(line_comment) { - // Keep doc comments - if let Some(doc) = patterns.doc_line { - if trimmed.starts_with(doc) { - result.push_str(line); - result.push('\n'); - } - } - continue; + if let Some(line_comment) = patterns.line + && trimmed.starts_with(line_comment) + { + // Keep doc comments + if let Some(doc) = patterns.doc_line + && trimmed.starts_with(doc) + { + result.push_str(line); + result.push('\n'); } + continue; } // Skip empty lines at this point, we'll normalize later diff --git a/src/core/runner.rs b/src/core/runner.rs index 024a269adc..dba2517fda 100644 --- a/src/core/runner.rs +++ b/src/core/runner.rs @@ -216,12 +216,11 @@ fn run_inner( stream::run_streaming(&mut cmd, StdinMode::Null, FilterMode::Streaming(filter)) .with_context(|| format!("Failed to run {}", tool_name))?; - if let Some(label) = opts.tee_label { - if let Some(hint) = + if let Some(label) = opts.tee_label + && let Some(hint) = crate::core::tee::tee_and_hint(&result.raw, label, result.exit_code) - { - println!("{}", hint); - } + { + println!("{}", hint); } timer.track( diff --git a/src/core/stream.rs b/src/core/stream.rs index 6a77a058a7..986aa22924 100644 --- a/src/core/stream.rs +++ b/src/core/stream.rs @@ -478,10 +478,10 @@ pub fn run_streaming( let mut writer = BufWriter::new(child_stdin); let stdin_handle = io::stdin(); for line in read_lines_lossy(stdin_handle.lock()) { - if let Some(out) = filter.feed_line(&line) { - if writeln!(writer, "{}", out).is_err() { - break; - } + if let Some(out) = filter.feed_line(&line) + && writeln!(writer, "{}", out).is_err() + { + break; } } let tail = filter.flush(); @@ -666,19 +666,19 @@ pub fn run_streaming( let exit_code = status_to_exit_code(status); let raw = format!("{}{}", raw_stdout, raw_stderr); - if let Some(mut f) = saved_filter { - if let Some(post) = f.on_exit(exit_code, &raw) { - filtered.push_str(&post); - let mut dest: Box = if filter_fd_is_stderr { - Box::new(io::stderr().lock()) - } else { - Box::new(io::stdout().lock()) - }; - match write!(dest, "{}", post) { - Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {} - Err(e) => return Err(e.into()), - Ok(_) => {} - } + if let Some(mut f) = saved_filter + && let Some(post) = f.on_exit(exit_code, &raw) + { + filtered.push_str(&post); + let mut dest: Box = if filter_fd_is_stderr { + Box::new(io::stderr().lock()) + } else { + Box::new(io::stdout().lock()) + }; + match write!(dest, "{}", post) { + Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {} + Err(e) => return Err(e.into()), + Ok(_) => {} } } diff --git a/src/core/tee_file.rs b/src/core/tee_file.rs index 04db46b86a..c782499fd3 100644 --- a/src/core/tee_file.rs +++ b/src/core/tee_file.rs @@ -172,10 +172,10 @@ fn write_tee_file( } fn display_path(path: &Path) -> String { - if let Some(home) = dirs::home_dir() { - if let Ok(relative) = path.strip_prefix(&home) { - return format!("~/{}", relative.display()); - } + if let Some(home) = dirs::home_dir() + && let Ok(relative) = path.strip_prefix(&home) + { + return format!("~/{}", relative.display()); } path.display().to_string() } diff --git a/src/core/telemetry.rs b/src/core/telemetry.rs index 174d6e9298..29a3bb96d9 100644 --- a/src/core/telemetry.rs +++ b/src/core/telemetry.rs @@ -60,14 +60,12 @@ pub fn maybe_ping() { // Check last ping time let marker = telemetry_marker_path(); - if let Ok(metadata) = std::fs::metadata(&marker) { - if let Ok(modified) = metadata.modified() { - if let Ok(elapsed) = modified.elapsed() { - if elapsed.as_secs() < PING_INTERVAL_SECS { - return; - } - } - } + if let Ok(metadata) = std::fs::metadata(&marker) + && let Ok(modified) = metadata.modified() + && let Ok(elapsed) = modified.elapsed() + && elapsed.as_secs() < PING_INTERVAL_SECS + { + return; } // Touch marker file immediately (before sending) to avoid double-ping @@ -506,23 +504,23 @@ fn count_custom_toml_filters() -> usize { let mut count = 0; // Project-local: .rtk/filters/*.toml - if let Ok(cwd) = std::env::current_dir() { - if let Ok(entries) = std::fs::read_dir(cwd.join(".rtk/filters")) { - count += entries - .filter_map(|e| e.ok()) - .filter(|e| e.path().extension().is_some_and(|ext| ext == "toml")) - .count(); - } + if let Ok(cwd) = std::env::current_dir() + && let Ok(entries) = std::fs::read_dir(cwd.join(".rtk/filters")) + { + count += entries + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().is_some_and(|ext| ext == "toml")) + .count(); } // Global: ~/.config/rtk/filters/*.toml - if let Some(config_dir) = dirs::config_dir() { - if let Ok(entries) = std::fs::read_dir(config_dir.join("rtk/filters")) { - count += entries - .filter_map(|e| e.ok()) - .filter(|e| e.path().extension().is_some_and(|ext| ext == "toml")) - .count(); - } + if let Some(config_dir) = dirs::config_dir() + && let Ok(entries) = std::fs::read_dir(config_dir.join("rtk/filters")) + { + count += entries + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().is_some_and(|ext| ext == "toml")) + .count(); } count diff --git a/src/core/toml_filter.rs b/src/core/toml_filter.rs index 2e82606a27..290b70fdf5 100644 --- a/src/core/toml_filter.rs +++ b/src/core/toml_filter.rs @@ -458,10 +458,9 @@ fn collect_match_patterns() -> Vec { status, crate::hooks::trust::TrustStatus::Trusted | crate::hooks::trust::TrustStatus::EnvOverride - ) { - if let Some(content) = content { - patterns.extend(match_patterns_in(&content)); - } + ) && let Some(content) = content + { + patterns.extend(match_patterns_in(&content)); } } patterns.extend(match_patterns_in(BUILTIN_TOML)); @@ -610,10 +609,10 @@ pub fn apply_filter_with_info(filter: &CompiledFilter, stdout: &str) -> (String, let blob = lines.join("\n"); for rule in &filter.match_output { if rule.pattern.is_match(&blob) { - if let Some(ref unless_re) = rule.unless { - if unless_re.is_match(&blob) { - continue; // errors/warnings present — skip this rule - } + if let Some(ref unless_re) = rule.unless + && unless_re.is_match(&blob) + { + continue; // errors/warnings present — skip this rule } return (rule.message.clone(), Lossiness::Whole); } @@ -665,32 +664,32 @@ pub fn apply_filter_with_info(filter: &CompiledFilter, stdout: &str) -> (String, lines.push(format!("... ({} lines omitted)", total - head)); head_cut = Some(head); } - } else if let Some(tail) = filter.tail_lines { - if total > tail { - let omitted = total - tail; - lines = lines[omitted..].to_vec(); - lines.insert(0, format!("... ({} lines omitted)", omitted)); - noncontiguous_drop = true; - } + } else if let Some(tail) = filter.tail_lines + && total > tail + { + let omitted = total - tail; + lines = lines[omitted..].to_vec(); + lines.insert(0, format!("... ({} lines omitted)", omitted)); + noncontiguous_drop = true; } // 7. max_lines — absolute cap applied after head/tail (includes omit messages) let mut max_cut: Option = None; - if let Some(max) = filter.max_lines { - if lines.len() > max { - let dropped = lines.len() - max; - lines.truncate(max); - lines.push(format!("... ({} lines truncated)", dropped)); - max_cut = Some(max); - } + if let Some(max) = filter.max_lines + && lines.len() > max + { + let dropped = lines.len() - max; + lines.truncate(max); + lines.push(format!("... ({} lines truncated)", dropped)); + max_cut = Some(max); } // 8. on_empty let result = lines.join("\n"); - if result.trim().is_empty() { - if let Some(ref msg) = filter.on_empty { - return (msg.clone(), Lossiness::None); - } + if result.trim().is_empty() + && let Some(ref msg) = filter.on_empty + { + return (msg.clone(), Lossiness::None); } let loss = if let Some(snapshot) = pre_cut { @@ -807,10 +806,10 @@ fn collect_test_outcomes( // Run tests for (filter_name, tests) in file.tests { - if let Some(name) = filter_name_opt { - if filter_name != name { - continue; - } + if let Some(name) = filter_name_opt + && filter_name != name + { + continue; } tested_filter_names.insert(filter_name.clone()); diff --git a/src/core/utils.rs b/src/core/utils.rs index 4e63fa4aca..c7ad61644a 100644 --- a/src/core/utils.rs +++ b/src/core/utils.rs @@ -344,10 +344,10 @@ fn set_owner_only(path: &std::path::Path, mode: u32) { // Falls through to chmod on any metadata-read failure, erring toward // enforcing the permission rather than silently skipping it. // - if let Ok(meta) = fs::metadata(path) { - if mode_already_correct(meta.permissions().mode(), mode) { - return; - } + if let Ok(meta) = fs::metadata(path) + && mode_already_correct(meta.permissions().mode(), mode) + { + return; } let _ = fs::set_permissions(path, fs::Permissions::from_mode(mode)); } diff --git a/src/discover/lexer.rs b/src/discover/lexer.rs index 6e7d7b46b7..bd26be3ff4 100644 --- a/src/discover/lexer.rs +++ b/src/discover/lexer.rs @@ -80,11 +80,11 @@ pub(crate) fn coalesce_words<'a>(cmd: &'a str, tokens: &[ParsedToken]) -> Vec<(& let mut run_end: usize = 0; for tok in tokens { - if let Some(start) = run_start { - if tok.offset != run_end { - words.push((&cmd[start..run_end], start)); - run_start = None; - } + if let Some(start) = run_start + && tok.offset != run_end + { + words.push((&cmd[start..run_end], start)); + run_start = None; } if run_start.is_none() { run_start = Some(tok.offset); diff --git a/src/discover/provider.rs b/src/discover/provider.rs index a42b259f9a..3735c9a0d2 100644 --- a/src/discover/provider.rs +++ b/src/discover/provider.rs @@ -111,14 +111,12 @@ impl ClaudeProvider { } // Apply mtime filter - if let Some(cutoff_time) = cutoff { - if let Ok(meta) = fs::metadata(file_path) { - if let Ok(mtime) = meta.modified() { - if mtime < cutoff_time { - continue; - } - } - } + if let Some(cutoff_time) = cutoff + && let Ok(meta) = fs::metadata(file_path) + && let Ok(mtime) = meta.modified() + && mtime < cutoff_time + { + continue; } sessions.push(file_path.to_path_buf()); @@ -211,18 +209,17 @@ impl SessionProvider for ClaudeProvider { for block in content { if block.get("type").and_then(|t| t.as_str()) == Some("tool_use") && block.get("name").and_then(|n| n.as_str()) == Some("Bash") - { - if let (Some(id), Some(cmd)) = ( + && let (Some(id), Some(cmd)) = ( block.get("id").and_then(|i| i.as_str()), block.pointer("/input/command").and_then(|c| c.as_str()), - ) { - pending_tool_uses.push(( - id.to_string(), - cmd.to_string(), - sequence_counter, - )); - sequence_counter += 1; - } + ) + { + pending_tool_uses.push(( + id.to_string(), + cmd.to_string(), + sequence_counter, + )); + sequence_counter += 1; } } } @@ -233,28 +230,26 @@ impl SessionProvider for ClaudeProvider { entry.pointer("/message/content").and_then(|c| c.as_array()) { for block in content { - if block.get("type").and_then(|t| t.as_str()) == Some("tool_result") { - if let Some(id) = block.get("tool_use_id").and_then(|i| i.as_str()) - { - // Get content, length, and error status - let content = - block.get("content").and_then(|c| c.as_str()).unwrap_or(""); - - let output_len = content.len(); - let is_error = block - .get("is_error") - .and_then(|e| e.as_bool()) - .unwrap_or(false); - - // Store first ~1000 chars of content for error detection - let content_preview: String = - content.chars().take(1000).collect(); - - tool_results.insert( - id.to_string(), - (output_len, content_preview, is_error), - ); - } + if block.get("type").and_then(|t| t.as_str()) == Some("tool_result") + && let Some(id) = block.get("tool_use_id").and_then(|i| i.as_str()) + { + // Get content, length, and error status + let content = + block.get("content").and_then(|c| c.as_str()).unwrap_or(""); + + let output_len = content.len(); + let is_error = block + .get("is_error") + .and_then(|e| e.as_bool()) + .unwrap_or(false); + + // Store first ~1000 chars of content for error detection + let content_preview: String = content.chars().take(1000).collect(); + + tool_results.insert( + id.to_string(), + (output_len, content_preview, is_error), + ); } } } diff --git a/src/discover/registry.rs b/src/discover/registry.rs index 1e49159ae2..97d5c45d88 100644 --- a/src/discover/registry.rs +++ b/src/discover/registry.rs @@ -398,13 +398,12 @@ fn normalize_php_tool_path(path: &str) -> String { normalized = stripped.to_string(); } - if let Some((stem, ext)) = normalized.rsplit_once('.') { - if ["bat", "cmd", "exe", "ps1"] + if let Some((stem, ext)) = normalized.rsplit_once('.') + && ["bat", "cmd", "exe", "ps1"] .iter() .any(|candidate| ext.eq_ignore_ascii_case(candidate)) - { - normalized = stem.to_string(); - } + { + normalized = stem.to_string(); } normalized @@ -465,10 +464,10 @@ fn parse_golangci_run_parts(cmd: &str) -> Option> { return None; } - if let Some(flag) = split_golangci_flag_name(token) { - if golangci_flag_takes_separate_value(token, flag) { - i += 1; - } + if let Some(flag) = split_golangci_flag_name(token) + && golangci_flag_takes_separate_value(token, flag) + { + i += 1; } i += 1; @@ -3689,10 +3688,10 @@ mod tests { .ok() .and_then(|p| std::fs::metadata(p).ok()) .and_then(|m| m.modified().ok()); - if let (Some(rtk_t), Some(test_t)) = (rtk_mtime, test_mtime) { - if rtk_t < test_t { - return; - } + if let (Some(rtk_t), Some(test_t)) = (rtk_mtime, test_mtime) + && rtk_t < test_t + { + return; } let output = std::process::Command::new(&rtk_bin) diff --git a/src/hooks/hook_check.rs b/src/hooks/hook_check.rs index faf819a10d..49df0aa8e1 100644 --- a/src/hooks/hook_check.rs +++ b/src/hooks/hook_check.rs @@ -103,12 +103,11 @@ fn check_and_warn() -> Option<()> { // Rate limit: warn once per day let marker = warn_marker_path()?; - if let Ok(meta) = std::fs::metadata(&marker) { - if let Ok(modified) = meta.modified() { - if modified.elapsed().map(|e| e.as_secs()).unwrap_or(u64::MAX) < WARN_INTERVAL_SECS { - return Some(()); - } - } + if let Ok(meta) = std::fs::metadata(&marker) + && let Ok(modified) = meta.modified() + && modified.elapsed().map(|e| e.as_secs()).unwrap_or(u64::MAX) < WARN_INTERVAL_SECS + { + return Some(()); } eprintln!("{}", warning); @@ -123,10 +122,10 @@ fn check_and_warn() -> Option<()> { pub fn parse_hook_version(content: &str) -> u8 { // Version tag must be in the first 5 lines (shebang + header convention) for line in content.lines().take(5) { - if let Some(rest) = line.strip_prefix("# rtk-hook-version:") { - if let Ok(v) = rest.trim().parse::() { - return v; - } + if let Some(rest) = line.strip_prefix("# rtk-hook-version:") + && let Ok(v) = rest.trim().parse::() + { + return v; } } 0 // No version tag = version 0 (outdated) diff --git a/src/hooks/hook_cmd.rs b/src/hooks/hook_cmd.rs index 6d4f82fb74..38672201af 100644 --- a/src/hooks/hook_cmd.rs +++ b/src/hooks/hook_cmd.rs @@ -103,16 +103,14 @@ fn detect_format(v: &Value) -> HookFormat { if matches!( tool_name, "runTerminalCommand" | "run_in_terminal" | "Bash" | "bash" - ) { - if let Some(cmd) = v - .pointer("/tool_input/command") - .and_then(|c| c.as_str()) - .filter(|c| !c.is_empty()) - { - return HookFormat::VsCode { - command: cmd.to_string(), - }; - } + ) && let Some(cmd) = v + .pointer("/tool_input/command") + .and_then(|c| c.as_str()) + .filter(|c| !c.is_empty()) + { + return HookFormat::VsCode { + command: cmd.to_string(), + }; } return HookFormat::PassThrough; } @@ -124,27 +122,24 @@ fn detect_format(v: &Value) -> HookFormat { // registers this schema itself, like JetBrains/IntelliJ's Copilot plugin // (toolName "run_in_terminal"). if let Some(tool_name) = v.get("toolName").and_then(|t| t.as_str()) { - if matches!(tool_name, "bash" | "powershell" | "run_in_terminal") { - if let Some(tool_args_str) = v.get("toolArgs").and_then(|t| t.as_str()) { - if let Ok(tool_args) = serde_json::from_str::(tool_args_str) { - if let Some(cmd) = tool_args - .get("command") - .and_then(|c| c.as_str()) - .filter(|c| !c.is_empty()) - { - return if tool_name == "run_in_terminal" { - HookFormat::CopilotIde { - command: cmd.to_string(), - } - } else { - HookFormat::CopilotCli { - command: cmd.to_string(), - args: tool_args, - } - }; - } + if matches!(tool_name, "bash" | "powershell" | "run_in_terminal") + && let Some(tool_args_str) = v.get("toolArgs").and_then(|t| t.as_str()) + && let Ok(tool_args) = serde_json::from_str::(tool_args_str) + && let Some(cmd) = tool_args + .get("command") + .and_then(|c| c.as_str()) + .filter(|c| !c.is_empty()) + { + return if tool_name == "run_in_terminal" { + HookFormat::CopilotIde { + command: cmd.to_string(), } - } + } else { + HookFormat::CopilotCli { + command: cmd.to_string(), + args: tool_args, + } + }; } return HookFormat::PassThrough; } diff --git a/src/hooks/init.rs b/src/hooks/init.rs index 50dc517644..72599c7edc 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -1561,10 +1561,10 @@ fn migrate_old_hook_script(ctx: InitContext) { eprintln!(" [ok] Removed old hook script: {}", old_hook.display()); } // Clean up the stale settings.json entry that pointed to the deleted script - if let Err(e) = remove_legacy_settings_entries(ctx) { - if verbose > 0 { - eprintln!(" [warn] Failed to clean legacy settings.json entry: {e}"); - } + if let Err(e) = remove_legacy_settings_entries(ctx) + && verbose > 0 + { + eprintln!(" [warn] Failed to clean legacy settings.json entry: {e}"); } } } @@ -1898,10 +1898,11 @@ fn run_claude_md_mode_with( PathBuf::from(CLAUDE_MD) }; - if global && !dry_run { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } + if global + && !dry_run + && let Some(parent) = path.parent() + { + fs::create_dir_all(parent)?; } if verbose > 0 { @@ -2409,10 +2410,10 @@ fn ensure_previous_yaml_line_ends_with_newline(lines: &mut [String], insert_idx: return; } - if let Some(previous) = lines.get_mut(insert_idx - 1) { - if !previous.ends_with('\n') { - previous.push('\n'); - } + if let Some(previous) = lines.get_mut(insert_idx - 1) + && !previous.ends_with('\n') + { + previous.push('\n'); } } @@ -2721,15 +2722,16 @@ fn run_codex_mode_with_paths( ctx: InitContext, ) -> Result<()> { let InitContext { dry_run, .. } = ctx; - if global && !dry_run { - if let Some(parent) = agents_md_path.parent() { - fs::create_dir_all(parent).with_context(|| { - format!( - "Failed to create Codex config directory: {}", - parent.display() - ) - })?; - } + if global + && !dry_run + && let Some(parent) = agents_md_path.parent() + { + fs::create_dir_all(parent).with_context(|| { + format!( + "Failed to create Codex config directory: {}", + parent.display() + ) + })?; } // ISSUE #892: In global mode, use absolute path so @RTK.md resolves @@ -3490,24 +3492,23 @@ fn resolve_droid_install_target(droid_dir: &Path) -> Result { None }; - if let Some(path) = &live_hooks_json { - if let Some(json) = read_json_file(path)? { - if droid_has_pre_tool_use(&json, DroidLayout::Root) { - return Ok(DroidHookFile { - path: path.clone(), - layout: DroidLayout::Root, - }); - } - } + if let Some(path) = &live_hooks_json + && let Some(json) = read_json_file(path)? + && droid_has_pre_tool_use(&json, DroidLayout::Root) + { + return Ok(DroidHookFile { + path: path.clone(), + layout: DroidLayout::Root, + }); } - if let Some(json) = read_json_file(&settings)? { - if droid_has_pre_tool_use(&json, DroidLayout::Nested) { - return Ok(DroidHookFile { - path: settings, - layout: DroidLayout::Nested, - }); - } + if let Some(json) = read_json_file(&settings)? + && droid_has_pre_tool_use(&json, DroidLayout::Nested) + { + return Ok(DroidHookFile { + path: settings, + layout: DroidLayout::Nested, + }); } Ok(DroidHookFile { @@ -3609,10 +3610,10 @@ fn patch_droid_hook_file(file: &DroidHookFile, ctx: InitContext) -> Result return Ok(true); } - if let Some(backup_path) = backup_and_atomic_write(path, &serialized)? { - if verbose > 0 { - eprintln!("Backup: {}", backup_path.display()); - } + if let Some(backup_path) = backup_and_atomic_write(path, &serialized)? + && verbose > 0 + { + eprintln!("Backup: {}", backup_path.display()); } Ok(true) } @@ -3673,14 +3674,14 @@ fn insert_droid_hook_entry(root: &mut serde_json::Value, layout: DroidLayout) -> .get("matcher") .and_then(|m| m.as_str()) .unwrap_or_default(); - if matcher == DROID_EXECUTE_MATCHER { - if let Some(hook_array) = entry.get_mut("hooks").and_then(|h| h.as_array_mut()) { - hook_array.push(serde_json::json!({ - "type": "command", - "command": DROID_HOOK_COMMAND - })); - return Ok(()); - } + if matcher == DROID_EXECUTE_MATCHER + && let Some(hook_array) = entry.get_mut("hooks").and_then(|h| h.as_array_mut()) + { + hook_array.push(serde_json::json!({ + "type": "command", + "command": DROID_HOOK_COMMAND + })); + return Ok(()); } } @@ -3840,10 +3841,9 @@ fn remove_droid_hook_from_json(root: &mut serde_json::Value, layout: DroidLayout .get("hooks") .and_then(|h| h.as_object()) .is_some_and(|o| o.is_empty()) + && let Some(obj) = root.as_object_mut() { - if let Some(obj) = root.as_object_mut() { - obj.remove("hooks"); - } + obj.remove("hooks"); } modified @@ -3861,10 +3861,10 @@ fn resolve_opencode_dir() -> Result { /// Resolve Pi config directory, honouring `PI_CODING_AGENT_DIR` override. fn resolve_pi_dir() -> Result { - if let Ok(dir) = std::env::var(PI_CODING_AGENT_DIR_ENV) { - if !dir.is_empty() { - return Ok(PathBuf::from(dir)); - } + if let Ok(dir) = std::env::var(PI_CODING_AGENT_DIR_ENV) + && !dir.is_empty() + { + return Ok(PathBuf::from(dir)); } resolve_home_subdir(PI_DIR) } @@ -4510,15 +4510,13 @@ fn prepare_opencode_plugin_path() -> Result { fn ensure_opencode_plugin_installed(path: &Path, ctx: InitContext) -> Result { let InitContext { dry_run, .. } = ctx; // Ensure parent dir exists (skip in dry-run) - if !dry_run { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent).with_context(|| { - format!( - "Failed to create OpenCode plugin directory: {}", - parent.display() - ) - })?; - } + if !dry_run && let Some(parent) = path.parent() { + fs::create_dir_all(parent).with_context(|| { + format!( + "Failed to create OpenCode plugin directory: {}", + parent.display() + ) + })?; } write_if_changed(path, OPENCODE_PLUGIN, "OpenCode plugin", ctx) } @@ -4575,10 +4573,10 @@ fn omp_extension_path_for_scope(global: bool) -> Result { /// `PI_CODING_AGENT_DIR` for this relocation, so RTK follows the same /// override instead of introducing a second path configuration. fn resolve_omp_dir() -> Result { - if let Ok(dir) = std::env::var(PI_CODING_AGENT_DIR_ENV) { - if !dir.is_empty() { - return Ok(PathBuf::from(dir)); - } + if let Ok(dir) = std::env::var(PI_CODING_AGENT_DIR_ENV) + && !dir.is_empty() + { + return Ok(PathBuf::from(dir)); } resolve_home_subdir(OMP_DIR) } @@ -4789,10 +4787,10 @@ fn install_cursor_hooks(ctx: InitContext) -> Result<()> { } // Clean stale hooks.json entry pointing to the deleted script let hooks_json_path = cursor_dir.join(HOOKS_JSON); - if let Err(e) = remove_legacy_cursor_hooks_json_entries(&hooks_json_path, ctx) { - if verbose > 0 { - eprintln!(" [warn] Failed to clean legacy Cursor hooks.json entry: {e}"); - } + if let Err(e) = remove_legacy_cursor_hooks_json_entries(&hooks_json_path, ctx) + && verbose > 0 + { + eprintln!(" [warn] Failed to clean legacy Cursor hooks.json entry: {e}"); } } @@ -4850,10 +4848,10 @@ fn patch_cursor_hooks_json(path: &Path, ctx: InitContext) -> Result { return Ok(true); } - if let Some(backup_path) = backup_and_atomic_write(path, &serialized)? { - if verbose > 0 { - eprintln!("Backup: {}", backup_path.display()); - } + if let Some(backup_path) = backup_and_atomic_write(path, &serialized)? + && verbose > 0 + { + eprintln!("Backup: {}", backup_path.display()); } Ok(true) @@ -5007,24 +5005,24 @@ fn remove_cursor_hooks_at(cursor_dir: &Path, ctx: InitContext) -> Result return Err(error), }; - if let Some(mut root) = root { - if remove_cursor_hook_from_json(&mut root) { - if dry_run { - println!( - "[dry-run] would remove RTK entry from Cursor hooks.json: {}", - hooks_json_path.display() - ); - } else { - let serialized = serde_json::to_string_pretty(&root) - .context("Failed to serialize hooks.json")?; - backup_and_atomic_write(&hooks_json_path, &serialized)?; + if let Some(mut root) = root + && remove_cursor_hook_from_json(&mut root) + { + if dry_run { + println!( + "[dry-run] would remove RTK entry from Cursor hooks.json: {}", + hooks_json_path.display() + ); + } else { + let serialized = + serde_json::to_string_pretty(&root).context("Failed to serialize hooks.json")?; + backup_and_atomic_write(&hooks_json_path, &serialized)?; - if verbose > 0 { - eprintln!("Removed RTK hook from Cursor hooks.json"); - } + if verbose > 0 { + eprintln!("Removed RTK hook from Cursor hooks.json"); } - removed.push("Cursor hooks.json: removed RTK entry".to_string()); } + removed.push("Cursor hooks.json: removed RTK entry".to_string()); } Ok(removed) @@ -5626,19 +5624,18 @@ fn patch_gemini_settings( }; let before_tool_pointer = format!("/hooks/{}", BEFORE_TOOL_KEY); - if let Some(hooks) = settings.pointer(&before_tool_pointer) { - if let Some(arr) = hooks.as_array() { - if arr.iter().any(|h| { - h.pointer("/hooks/0/command") - .and_then(|v| v.as_str()) - .is_some_and(|c| c.contains("rtk")) - }) { - if verbose > 0 { - eprintln!("Gemini settings.json already has RTK hook"); - } - return Ok(false); - } + if let Some(hooks) = settings.pointer(&before_tool_pointer) + && let Some(arr) = hooks.as_array() + && arr.iter().any(|h| { + h.pointer("/hooks/0/command") + .and_then(|v| v.as_str()) + .is_some_and(|c| c.contains("rtk")) + }) + { + if verbose > 0 { + eprintln!("Gemini settings.json already has RTK hook"); } + return Ok(false); } // Ask user before patching diff --git a/src/hooks/permissions.rs b/src/hooks/permissions.rs index abc90c90e8..3cc945baf2 100644 --- a/src/hooks/permissions.rs +++ b/src/hooks/permissions.rs @@ -182,10 +182,10 @@ fn append_bash_rules(rules_value: Option<&Value>, target: &mut Vec) { return; }; for rule in arr { - if let Some(s) = rule.as_str() { - if s.starts_with("Bash(") { - target.push(extract_bash_pattern(s).to_string()); - } + if let Some(s) = rule.as_str() + && s.starts_with("Bash(") + { + target.push(extract_bash_pattern(s).to_string()); } } } @@ -283,12 +283,11 @@ fn gemini_settings() -> Option { .and_then(Value::as_bool) }) .unwrap_or(false); - if trusted { - if let Some(root) = find_project_root() { - if let Some(v) = read_json(&root.join(GEMINI_DIR).join(SETTINGS_JSON)) { - return Some(v); - } - } + if trusted + && let Some(root) = find_project_root() + && let Some(v) = read_json(&root.join(GEMINI_DIR).join(SETTINGS_JSON)) + { + return Some(v); } global } @@ -393,10 +392,10 @@ fn find_project_root() -> Option { /// /// Returns the original string unchanged if it does not match the expected format. pub(crate) fn extract_bash_pattern(rule: &str) -> &str { - if let Some(inner) = rule.strip_prefix("Bash(") { - if let Some(pattern) = inner.strip_suffix(')') { - return pattern; - } + if let Some(inner) = rule.strip_prefix("Bash(") + && let Some(pattern) = inner.strip_suffix(')') + { + return pattern; } rule } diff --git a/src/hooks/rewrite_cmd.rs b/src/hooks/rewrite_cmd.rs index 7c8f490fef..9ed32383b3 100644 --- a/src/hooks/rewrite_cmd.rs +++ b/src/hooks/rewrite_cmd.rs @@ -9,15 +9,15 @@ const TEE_READERS: &[&str] = &[ ]; fn expand_home(token: &str) -> String { - if let Some(rest) = token.strip_prefix("~/") { - if let Some(home) = dirs::home_dir() { - return home.join(rest).to_string_lossy().into_owned(); - } + if let Some(rest) = token.strip_prefix("~/") + && let Some(home) = dirs::home_dir() + { + return home.join(rest).to_string_lossy().into_owned(); } - if let Some(rest) = token.strip_prefix("$HOME/") { - if let Some(home) = dirs::home_dir() { - return home.join(rest).to_string_lossy().into_owned(); - } + if let Some(rest) = token.strip_prefix("$HOME/") + && let Some(home) = dirs::home_dir() + { + return home.join(rest).to_string_lossy().into_owned(); } token.to_string() } @@ -39,10 +39,12 @@ fn tee_read_slug(cmd: &str, tee_dir: &std::path::Path) -> Option<(String, String continue; } let stem = path.file_stem()?.to_str()?; - if let Some((epoch, slug)) = stem.split_once('_') { - if !epoch.is_empty() && epoch.chars().all(|c| c.is_ascii_digit()) && !slug.is_empty() { - return Some((slug.to_string(), expanded)); - } + if let Some((epoch, slug)) = stem.split_once('_') + && !epoch.is_empty() + && epoch.chars().all(|c| c.is_ascii_digit()) + && !slug.is_empty() + { + return Some((slug.to_string(), expanded)); } } None diff --git a/src/main.rs b/src/main.rs index c2a71cdf8d..e7d47a6fd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -#![allow(clippy::collapsible_if)] mod analytics; mod cmds; mod core; diff --git a/src/parser/formatter.rs b/src/parser/formatter.rs index 613be8f9ae..e26ef9c165 100644 --- a/src/parser/formatter.rs +++ b/src/parser/formatter.rs @@ -154,13 +154,13 @@ impl TokenFormatter for DependencyState { )]; for dep in self.dependencies.iter().take(10) { - if let Some(latest) = &dep.latest_version { - if &dep.current_version != latest { - lines.push(format!( - "{}: {} → {}", - dep.name, dep.current_version, latest - )); - } + if let Some(latest) = &dep.latest_version + && &dep.current_version != latest + { + lines.push(format!( + "{}: {} → {}", + dep.name, dep.current_version, latest + )); } } @@ -180,18 +180,18 @@ impl TokenFormatter for DependencyState { if self.outdated_count > 0 { lines.push("\nOutdated packages:".to_string()); for dep in &self.dependencies { - if let Some(latest) = &dep.latest_version { - if &dep.current_version != latest { - let dev_marker = if dep.dev_dependency { " (dev)" } else { "" }; - lines.push(format!( - " {}: {} → {}{}", - dep.name, dep.current_version, latest, dev_marker - )); - if let Some(wanted) = &dep.wanted_version { - if wanted != latest { - lines.push(format!(" (wanted: {})", wanted)); - } - } + if let Some(latest) = &dep.latest_version + && &dep.current_version != latest + { + let dev_marker = if dep.dev_dependency { " (dev)" } else { "" }; + lines.push(format!( + " {}: {} → {}{}", + dep.name, dep.current_version, latest, dev_marker + )); + if let Some(wanted) = &dep.wanted_version + && wanted != latest + { + lines.push(format!(" (wanted: {})", wanted)); } } } From 75b30a442c7e6f051d477f18ecc094bce0da09a2 Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Wed, 2 Sep 2026 18:53:00 +0200 Subject: [PATCH 5/6] fix(hooks): don't abort init --status on an unreadable Cursor hook show_claude_config read the Cursor hook file only to compute _is_thin, which nothing reads. The `?` on that read meant an unreadable hook file aborted `rtk init --status` before it printed the rest of the report -- a status command failing on the thing it is meant to report about. The executable-bit check above it is what the branch actually uses. Co-Authored-By: Claude Opus 5 --- src/hooks/init.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hooks/init.rs b/src/hooks/init.rs index 72599c7edc..dc799d6217 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -5312,8 +5312,6 @@ fn show_claude_config() -> Result<()> { use std::os::unix::fs::PermissionsExt; let meta = fs::metadata(&cursor_hook)?; let is_executable = meta.permissions().mode() & 0o111 != 0; - let content = fs::read_to_string(&cursor_hook)?; - let _is_thin = content.contains("rtk rewrite"); if !is_executable { println!( From bf2f7c7c123e8b0b3f0bab28edd802713eba2211 Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Mon, 14 Sep 2026 01:27:25 +0200 Subject: [PATCH 6/6] chore: ignore the style-edition reformat in git blame The style-edition reformat touched 69 files without changing behaviour, which buries the real authorship of those lines. GitHub's web blame applies .git-blame-ignore-revs automatically; locally it needs blame.ignoreRevsFile set, so CONTRIBUTING now says so where contributors first clone. Only the reformat is listed. The let-chain commit also touches many files but changes control flow and drops a crate-wide lint allow, so its authorship is real and should stay visible in blame. Co-Authored-By: Claude Opus 5 --- .git-blame-ignore-revs | 10 ++++++++++ CONTRIBUTING.md | 7 +++++++ 2 files changed, 17 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..06fb71d91a --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,10 @@ +# Bulk-formatting commits, ignored so `git blame` reports the author who wrote +# a line rather than whoever last reformatted it. GitHub's web blame applies +# this automatically; locally it needs opting in once: +# +# git config blame.ignoreRevsFile .git-blame-ignore-revs +# +# Only commits with no behaviour change belong here. + +# chore: reformat under rustfmt style edition 2024 +ad63139576f77767299ac1c0f4bca5b52fe80442 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ae0606aa7..4230fc34d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -189,6 +189,13 @@ git pull origin develop git checkout -b feat/scope-your-clear-description ``` +Opt in once per clone so `git blame` skips the bulk-formatting commits listed in +`.git-blame-ignore-revs` and reports whoever actually wrote each line: + +```bash +git config blame.ignoreRevsFile .git-blame-ignore-revs +``` + ### 2. Make Your Changes **Respect the existing folder structure.** Place new files where similar files already live. Do not reorganize without prior discussion.