From a8e1f636f6eec498138012ecdabbd1c862cf48fd Mon Sep 17 00:00:00 2001 From: can1357 Date: Fri, 12 Jun 2026 12:15:56 +0200 Subject: [PATCH] refactor(minimizer): applied #[must_use] annotations and simplified minimizer parsing internals - Applied #[must_use] annotations across minimizer and process APIs to flag ignored return values. - Replaced direct serde_json field reads with typed Value helper calls in several filters. - Simplified filter parsing with chained if-let guards, rfind checks, and related cleanup. - Updated minimizer tests and fixtures to build outputs via write! and fmt::Write. --- crates/pi-shell/src/cancel.rs | 3 ++ crates/pi-shell/src/fixup.rs | 9 +++- crates/pi-shell/src/minimizer.rs | 3 ++ crates/pi-shell/src/minimizer/config.rs | 4 ++ crates/pi-shell/src/minimizer/detect.rs | 2 + crates/pi-shell/src/minimizer/engine.rs | 12 +++-- .../src/minimizer/filters/binary_tools.rs | 11 +++-- crates/pi-shell/src/minimizer/filters/bun.rs | 2 + .../pi-shell/src/minimizer/filters/cargo.rs | 4 +- .../pi-shell/src/minimizer/filters/cloud.rs | 9 ++-- crates/pi-shell/src/minimizer/filters/cpp.rs | 4 ++ .../pi-shell/src/minimizer/filters/docker.rs | 35 ++++++++++----- .../pi-shell/src/minimizer/filters/dotnet.rs | 4 +- .../pi-shell/src/minimizer/filters/generic.rs | 1 + crates/pi-shell/src/minimizer/filters/gh.rs | 12 +++-- crates/pi-shell/src/minimizer/filters/git.rs | 34 ++++++++------ crates/pi-shell/src/minimizer/filters/glab.rs | 28 ++++++------ crates/pi-shell/src/minimizer/filters/go.rs | 26 ++++++----- crates/pi-shell/src/minimizer/filters/gt.rs | 10 +++-- .../src/minimizer/filters/js_tools.rs | 3 ++ crates/pi-shell/src/minimizer/filters/jvm.rs | 41 ++++++++++------- crates/pi-shell/src/minimizer/filters/lint.rs | 18 +++++--- .../pi-shell/src/minimizer/filters/listing.rs | 44 +++++++++---------- crates/pi-shell/src/minimizer/filters/mod.rs | 7 +-- .../src/minimizer/filters/node_tests.rs | 3 +- crates/pi-shell/src/minimizer/filters/pkg.rs | 22 +++++----- .../pi-shell/src/minimizer/filters/python.rs | 33 +++++++++----- crates/pi-shell/src/minimizer/filters/ruby.rs | 13 ++++-- .../src/minimizer/filters/rust_tools.rs | 11 ++--- .../pi-shell/src/minimizer/filters/system.rs | 2 + crates/pi-shell/src/minimizer/pipeline.rs | 5 +++ crates/pi-shell/src/minimizer/plan.rs | 1 + crates/pi-shell/src/minimizer/primitives.rs | 18 ++++++++ crates/pi-shell/src/process.rs | 9 ++++ crates/pi-shell/tests/minimizer_fixtures.rs | 7 +-- 35 files changed, 293 insertions(+), 157 deletions(-) diff --git a/crates/pi-shell/src/cancel.rs b/crates/pi-shell/src/cancel.rs index b27c05212..233a0784f 100644 --- a/crates/pi-shell/src/cancel.rs +++ b/crates/pi-shell/src/cancel.rs @@ -76,10 +76,12 @@ impl From<()> for CancelToken { } impl CancelToken { + #[must_use] pub fn new(timeout_ms: Option) -> Self { Self::with_timeout(timeout_ms.map(|ms| Duration::from_millis(u64::from(ms)))) } + #[must_use] pub fn with_timeout(timeout: Option) -> Self { Self { deadline: timeout.map(|duration| Instant::now() + duration), flag: None } } @@ -132,6 +134,7 @@ impl CancelToken { AbortToken(Some(Arc::downgrade(self.flag.get_or_insert_default()))) } + #[must_use] pub fn aborted(&self) -> bool { if let Some(flag) = &self.flag && flag.cause().is_some() diff --git a/crates/pi-shell/src/fixup.rs b/crates/pi-shell/src/fixup.rs index f9fe9d70a..958caac1b 100644 --- a/crates/pi-shell/src/fixup.rs +++ b/crates/pi-shell/src/fixup.rs @@ -25,7 +25,13 @@ use std::{io::BufReader, sync::LazyLock}; -use brush_parser::{Parser, ParserOptions, SourceInfo, ast::*}; +use brush_parser::{ + Parser, ParserOptions, SourceInfo, + ast::{ + AndOr, AndOrList, Command, CommandPrefixOrSuffixItem, CompoundListItem, IoFileRedirectKind, + IoFileRedirectTarget, IoRedirect, Pipeline, SourceLocation, + }, +}; use regex::Regex; /// Result of [`apply_bash_fixups`]. @@ -38,6 +44,7 @@ pub struct BashFixupResult { } /// Apply the bash fixups to `cmd`. See module docs for full rules. +#[must_use] pub fn apply_bash_fixups(cmd: &str) -> BashFixupResult { // Multi-line input is out of scope: heredoc/loop bodies can't be safely // rewritten and the agent rarely passes them as bash tool input. Bailing diff --git a/crates/pi-shell/src/minimizer.rs b/crates/pi-shell/src/minimizer.rs index e3a53949e..40d1c1507 100644 --- a/crates/pi-shell/src/minimizer.rs +++ b/crates/pi-shell/src/minimizer.rs @@ -76,6 +76,7 @@ impl MinimizerOutput { /// Transformed output. Caller-supplied `input_bytes` lets the savings /// metric compare pre- and post-filter sizes. + #[must_use] pub const fn transformed(text: String, input_bytes: usize) -> Self { let output_bytes = text.len(); Self { text, changed: true, input_bytes, output_bytes, filter: "", original_text: None } @@ -111,6 +112,7 @@ impl MinimizerOutput { /// Byte count saved by this filter (0 for passthrough). #[allow(dead_code, reason = "test-only API surface")] + #[must_use] pub const fn bytes_saved(&self) -> usize { self.input_bytes.saturating_sub(self.output_bytes) } @@ -141,6 +143,7 @@ pub(crate) fn chain_output( /// Apply the configured filter pipeline to a captured buffer. /// Returns the original text unchanged when minimization is disabled, no /// filter matches, or a filter panics. +#[must_use] pub fn apply( command: &str, captured: &str, diff --git a/crates/pi-shell/src/minimizer/config.rs b/crates/pi-shell/src/minimizer/config.rs index f7c769676..c6bddd123 100644 --- a/crates/pi-shell/src/minimizer/config.rs +++ b/crates/pi-shell/src/minimizer/config.rs @@ -110,6 +110,7 @@ impl Default for MinimizerConfig { impl MinimizerConfig { /// Build a resolved configuration from `MinimizerOptions`, optionally /// merging in a TOML settings file. + #[must_use] pub fn from_options(opts: &MinimizerOptions) -> Self { let mut cfg = Self::default(); if let Some(enabled) = opts.enabled { @@ -185,6 +186,7 @@ impl MinimizerConfig { } /// Whether the engine should attempt to minimize output for `program`. + #[must_use] pub fn is_program_enabled(&self, program: &str) -> bool { if !self.enabled { return false; @@ -200,11 +202,13 @@ impl MinimizerConfig { } /// Fetch a per-command TOML table, if any. + #[must_use] pub fn per_command(&self, program: &str) -> Option<&toml::Value> { self.per_command.get(&program.to_lowercase()) } /// Whether opted-in filters should fall back to pre-PR legacy behavior. + #[must_use] pub const fn legacy_filters_active(&self) -> bool { self.legacy_filters_active } diff --git a/crates/pi-shell/src/minimizer/detect.rs b/crates/pi-shell/src/minimizer/detect.rs index 4b9280649..f0b507d48 100644 --- a/crates/pi-shell/src/minimizer/detect.rs +++ b/crates/pi-shell/src/minimizer/detect.rs @@ -12,12 +12,14 @@ pub struct CommandIdentity { /// The detector intentionally handles the common interactive subset instead /// of emulating a full shell parser. Ambiguous commands return `None` and are /// left streaming unchanged. +#[must_use] pub fn detect(command: &str) -> Option { let tokens = tokenize(command); detect_tokens(&tokens) } /// Extract command identity from an already-expanded argv vector. +#[must_use] pub fn detect_tokens(tokens: &[String]) -> Option { let tokens = strip_launch_prefix(tokens)?; let (program, rest) = tokens.split_first()?; diff --git a/crates/pi-shell/src/minimizer/engine.rs b/crates/pi-shell/src/minimizer/engine.rs index 9c09e8094..d228d8d6c 100644 --- a/crates/pi-shell/src/minimizer/engine.rs +++ b/crates/pi-shell/src/minimizer/engine.rs @@ -26,6 +26,7 @@ pub enum MinimizerMode { } /// Return the minimization mode for a command. +#[must_use] pub fn mode_for(command: &str, config: &MinimizerConfig) -> MinimizerMode { match plan::analyze(command) { plan::CommandPlan::Single { .. } => { @@ -62,6 +63,7 @@ pub fn mode_for(command: &str, config: &MinimizerConfig) -> MinimizerMode { /// Return true when the command should be captured for minimization. #[allow(dead_code, reason = "test-only API surface")] +#[must_use] pub fn should_minimize(command: &str, config: &MinimizerConfig) -> bool { !matches!(mode_for(command, config), MinimizerMode::None) } @@ -77,6 +79,7 @@ pub fn should_minimize(command: &str, config: &MinimizerConfig) -> bool { /// `artifact://` reference back into the visible text before showing it /// to the agent. The minimizer itself never formats the reference — ids are /// assigned by the session store, not content-addressed. +#[must_use] pub fn apply( command: &str, captured: &str, @@ -510,6 +513,7 @@ fn builtin_pipelines() -> &'static PipelineRegistry { /// Expose the built-in registry's inline tests for the verify CLI surface. #[allow(dead_code, reason = "test-only API surface")] +#[must_use] pub fn verify_builtin_filters() -> Vec { pipeline::run_tests(builtin_pipelines()) } @@ -517,6 +521,7 @@ pub fn verify_builtin_filters() -> Vec { #[cfg(test)] mod tests { use std::{ + fmt::Write as _, fs, sync::atomic::{AtomicUsize, Ordering}, }; @@ -628,7 +633,7 @@ only_on_exit = [0] let cfg = MinimizerConfig { enabled: true, ..Default::default() }; let mut input = String::new(); for i in 0..120 { - input.push_str(&format!("/src/file{i}.ts:{i}:1 error Something is wrong rule/name\n")); + let _ = writeln!(input, "/src/file{i}.ts:{i}:1 error Something is wrong rule/name"); } let npx = apply("npx eslint src/", &input, 1, &cfg); let direct = apply("eslint src/", &input, 1, &cfg); @@ -1084,10 +1089,11 @@ strip_lines_matching = [".*"] let cfg = MinimizerConfig { enabled: true, ..Default::default() }; let mut input = String::new(); for i in 0..100 { - input.push_str(&format!( + let _ = write!( + input, "== 20240115 Migration{i}: migrating\n-- step\n -> 0.0s\n== 20240115 Migration{i}: \ migrated\n\n" - )); + ); } let out = apply("rails db:migrate", &input, 0, &cfg); assert!(out.changed); diff --git a/crates/pi-shell/src/minimizer/filters/binary_tools.rs b/crates/pi-shell/src/minimizer/filters/binary_tools.rs index 8626e3052..b0c1e60e1 100644 --- a/crates/pi-shell/src/minimizer/filters/binary_tools.rs +++ b/crates/pi-shell/src/minimizer/filters/binary_tools.rs @@ -14,10 +14,12 @@ use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; const HEAD_LINES: usize = 50; const TAIL_LINES: usize = 20; +#[must_use] pub fn supports(program: &str, _subcommand: Option<&str>) -> bool { matches!(program, "xxd" | "strings" | "od") } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { // Kill-switch parity (M2): legacy_filters_active=true skips this // filter so callers can rollback without recompile. @@ -44,6 +46,8 @@ pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerO #[cfg(test)] mod tests { + use std::fmt::Write as _; + use super::*; use crate::minimizer::MinimizerConfig; @@ -54,7 +58,7 @@ mod tests { fn build_lines(prefix: &str, count: usize) -> String { let mut s = String::new(); for i in 0..count { - s.push_str(&format!("{prefix}{i:08x}\n")); + let _ = writeln!(s, "{prefix}{i:08x}"); } s } @@ -107,9 +111,8 @@ mod tests { #[test] fn binary_tools_legacy_filters_active_passes_through() { // Kill-switch parity (M2). - let mut cfg = MinimizerConfig::default(); - cfg.enabled = true; - cfg.legacy_filters_active = true; + let cfg = + MinimizerConfig { enabled: true, legacy_filters_active: true, ..Default::default() }; let input = build_lines("00000000: ", 5000); for prog in ["xxd", "strings", "od"] { let context = ctx(prog, "binary-tool", &cfg); diff --git a/crates/pi-shell/src/minimizer/filters/bun.rs b/crates/pi-shell/src/minimizer/filters/bun.rs index d207550e9..af86b5710 100644 --- a/crates/pi-shell/src/minimizer/filters/bun.rs +++ b/crates/pi-shell/src/minimizer/filters/bun.rs @@ -13,6 +13,7 @@ const BUN_TOOL_SUBCOMMANDS: &[&str] = &["tsc", "eslint", "biome", "next", "prettier", "prisma", "jest", "vitest", "playwright"]; const BUN_CPP_TOOL_SUBCOMMANDS: &[&str] = &["cmake", "ctest", "ninja", "gtest", "gtest-parallel"]; +#[must_use] pub fn supports(program: &str, subcommand: Option<&str>) -> bool { match program { "bun" => subcommand.is_some_and(|subcommand| { @@ -30,6 +31,7 @@ pub fn supports(program: &str, subcommand: Option<&str>) -> bool { } } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let subcommand = ctx.subcommand; if matches!((ctx.program, subcommand), ("bun", Some(subcommand)) if is_non_exec_package_subcommand(subcommand)) diff --git a/crates/pi-shell/src/minimizer/filters/cargo.rs b/crates/pi-shell/src/minimizer/filters/cargo.rs index 9f9d59803..71bf9dd02 100644 --- a/crates/pi-shell/src/minimizer/filters/cargo.rs +++ b/crates/pi-shell/src/minimizer/filters/cargo.rs @@ -4,6 +4,7 @@ use std::{collections::BTreeMap, fmt::Write as _}; use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn supports(subcommand: Option<&str>) -> bool { matches!( subcommand, @@ -22,6 +23,7 @@ pub fn supports(subcommand: Option<&str>) -> bool { ) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let text = match ctx.subcommand { @@ -67,7 +69,7 @@ fn is_compiling_noise(line: &str) -> bool { || is_generated_warnings_rollup(trimmed) } -/// The per-crate rollup line `warning: \`crate\` (lib) generated N warnings`. +/// The per-crate rollup line warning: `crate` (lib) generated N warnings. /// The individual `warning: ...` diagnostic blocks are kept; this redundant /// tally is dropped. Clippy/install paths already skip it explicitly, so /// stripping it here only affects build/check/doc/run condensing. diff --git a/crates/pi-shell/src/minimizer/filters/cloud.rs b/crates/pi-shell/src/minimizer/filters/cloud.rs index 6ce07eec9..5495585b3 100644 --- a/crates/pi-shell/src/minimizer/filters/cloud.rs +++ b/crates/pi-shell/src/minimizer/filters/cloud.rs @@ -28,10 +28,12 @@ const SENSITIVE_AWS_KEYS: &[&str] = &[ "ResponseMetadata", ]; +#[must_use] pub fn supports(program: &str, _subcommand: Option<&str>) -> bool { matches!(program, "aws" | "curl" | "wget" | "psql") } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let text = match ctx.program { @@ -708,7 +710,7 @@ fn compact_aws_cloudwatch_events(events: &[&Value]) -> String { for event in events { let ts = event .get("timestamp") - .and_then(|v| v.as_i64()) + .and_then(serde_json::Value::as_i64) .map_or_else(|| "?".to_string(), epoch_ms_to_iso); let msg = event.get("message").and_then(|v| v.as_str()).unwrap_or("?"); // Truncate long messages @@ -1479,7 +1481,8 @@ mod tests { assert!(out.text.contains("age=42")); assert!(out.text.contains("active=true")); assert!(out.text.contains("tags=[a,b]")); - assert!(out.text.contains("meta={city:Paris}")); + const META_ATTR: &str = "meta={city:Paris}"; + assert!(out.text.contains(META_ATTR)); assert!(out.text.contains("1 item(s)")); } @@ -1656,7 +1659,7 @@ mod tests { fn sensitive_aws_keys_never_leak_from_generic() { let mut fields = String::new(); for key in SENSITIVE_AWS_KEYS { - fields.push_str(&format!(r#""{key}":"LEAK_SENTINEL","#)); + let _ = write!(fields, r#""{key}":"LEAK_SENTINEL","#); } let input = format!(r#"{{"Unknowns":[{{"Name":"safe",{fields}"Status":"ok"}}]}}"#); let cfg = MinimizerConfig { enabled: true, ..Default::default() }; diff --git a/crates/pi-shell/src/minimizer/filters/cpp.rs b/crates/pi-shell/src/minimizer/filters/cpp.rs index a9c0131af..f4b777c11 100644 --- a/crates/pi-shell/src/minimizer/filters/cpp.rs +++ b/crates/pi-shell/src/minimizer/filters/cpp.rs @@ -12,14 +12,17 @@ enum CppTool { GTest, } +#[must_use] pub fn supports(program: &str, _subcommand: Option<&str>) -> bool { direct_tool(program).is_some() } +#[must_use] pub fn supports_invocation(command: &str) -> bool { command_tokens(command).any(|token| token_tool(token).is_some()) } +#[must_use] pub fn is_gtest_binary_name(program: &str) -> bool { matches!(program, "gtest" | "gtest-parallel") || program.ends_with("_test") @@ -31,6 +34,7 @@ pub fn is_gtest_binary_name(program: &str) -> bool { .is_some_and(|ext| ext.eq_ignore_ascii_case("test")) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let tool = direct_tool(ctx.program).or_else(|| invocation_tool(ctx.command)); diff --git a/crates/pi-shell/src/minimizer/filters/docker.rs b/crates/pi-shell/src/minimizer/filters/docker.rs index fe42d0ef4..bc27d7c70 100644 --- a/crates/pi-shell/src/minimizer/filters/docker.rs +++ b/crates/pi-shell/src/minimizer/filters/docker.rs @@ -6,6 +6,7 @@ use serde_json::Value; use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn supports(subcommand: Option<&str>) -> bool { matches!( subcommand, @@ -36,6 +37,7 @@ pub fn supports(subcommand: Option<&str>) -> bool { ) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let text = match ctx.program { @@ -339,10 +341,17 @@ fn compute_pod_container_stats(status: &Value) -> (usize, usize, i32) { let mut ready = 0usize; let mut restarts = 0i32; for cs in container_statuses { - if cs.get("ready").and_then(|v| v.as_bool()).unwrap_or(false) { + if cs + .get("ready") + .and_then(serde_json::Value::as_bool) + .unwrap_or(false) + { ready += 1; } - restarts += cs.get("restartCount").and_then(|v| v.as_i64()).unwrap_or(0) as i32; + restarts += cs + .get("restartCount") + .and_then(serde_json::Value::as_i64) + .unwrap_or(0) as i32; } (ready, total, restarts) } @@ -408,15 +417,19 @@ fn format_k8s_ports(ports: Option<&Vec>) -> String { .map(|p| { let port = p .get("port") - .and_then(|v| v.as_i64()) + .and_then(serde_json::Value::as_i64) .map_or_else(|| "?".to_string(), |v| v.to_string()); let proto = p.get("protocol").and_then(|v| v.as_str()).unwrap_or("TCP"); - let node_port = p.get("nodePort").and_then(|v| v.as_i64()); + let node_port = p.get("nodePort").and_then(serde_json::Value::as_i64); let target_port = p.get("targetPort"); let target = target_port - .and_then(|v| v.as_i64()) + .and_then(serde_json::Value::as_i64) .map(|v| v.to_string()) - .or_else(|| target_port.and_then(|v| v.as_str()).map(|s| s.to_string())); + .or_else(|| { + target_port + .and_then(|v| v.as_str()) + .map(std::string::ToString::to_string) + }); match (target, node_port) { (Some(t), Some(np)) => format!("{port}/{t}:{np}->{port}/{proto}"), (Some(t), None) => format!("{port}/{t}:{port}/{proto}"), @@ -462,7 +475,7 @@ fn is_glog_prefix(line: &str) -> bool { if bytes.len() < 6 || bytes[0] != b'W' || bytes[5] != b' ' - || !bytes[1..5].iter().all(|b| b.is_ascii_digit()) + || !bytes[1..5].iter().all(u8::is_ascii_digit) { return false; } @@ -890,7 +903,7 @@ mod tests { }; let mut input = String::from("NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS\n"); for idx in 0..20 { - input.push_str(&format!("svc-{idx} img command api 1m running 8080/tcp\n")); + let _ = writeln!(input, "svc-{idx} img command api 1m running 8080/tcp"); } let out = filter(&compose_ctx, &input, 0).text; assert!(out.contains("20 rows")); @@ -1210,7 +1223,7 @@ mod tests { let kubectl_ctx = ctx("kubectl", Some("get"), &cfg); let mut input = String::from("NAME STATUS\n"); for i in 0..25 { - input.push_str(&format!("pod-{} running\n", i)); + let _ = writeln!(input, "pod-{i} running"); } let out = filter(&kubectl_ctx, &input, 0).text; // Should use table compaction, not crash @@ -1330,7 +1343,7 @@ mod tests { }; let mut input = String::from("NAME READY STATUS RESTARTS AGE IP NODE\n"); for i in 0..25 { - input.push_str(&format!("pod-{i} 1/1 Running 0 1h 10.0.0.{i} node\n")); + let _ = writeln!(input, "pod-{i} 1/1 Running 0 1h 10.0.0.{i} node"); } let out = filter(&ctx, input.as_str(), 0).text; assert!(out.contains("rows"), "-owide is a table format and must be compacted, got: {out}"); @@ -1373,7 +1386,7 @@ mod tests { let kubectl_ctx = ctx("kubectl", Some("apply"), &cfg); let mut input = String::new(); for i in 0..250 { - input.push_str(&format!("deployment.apps/app-{i} unchanged\n")); + let _ = writeln!(input, "deployment.apps/app-{i} unchanged"); } let out = filter(&kubectl_ctx, &input, 0).text; assert!( diff --git a/crates/pi-shell/src/minimizer/filters/dotnet.rs b/crates/pi-shell/src/minimizer/filters/dotnet.rs index 4830350d5..51ef76248 100644 --- a/crates/pi-shell/src/minimizer/filters/dotnet.rs +++ b/crates/pi-shell/src/minimizer/filters/dotnet.rs @@ -2,10 +2,12 @@ use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn supports(program: &str, subcommand: Option<&str>) -> bool { program == "dotnet" && matches!(subcommand, Some("build" | "test" | "restore" | "format")) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let text = match ctx.subcommand { @@ -257,7 +259,7 @@ fn first_string<'a>( fn first_number(map: &serde_json::Map, keys: &[&str]) -> Option { keys .iter() - .find_map(|key| map.get(*key).and_then(|value| value.as_u64())) + .find_map(|key| map.get(*key).and_then(serde_json::Value::as_u64)) } fn compact_general(input: &str) -> String { diff --git a/crates/pi-shell/src/minimizer/filters/generic.rs b/crates/pi-shell/src/minimizer/filters/generic.rs index 92159fef6..0d7893272 100644 --- a/crates/pi-shell/src/minimizer/filters/generic.rs +++ b/crates/pi-shell/src/minimizer/filters/generic.rs @@ -2,6 +2,7 @@ use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn filter(_ctx: &MinimizerCtx<'_>, input: &str, _exit_code: i32) -> MinimizerOutput { let stripped = primitives::strip_ansi(input); let deduped = primitives::dedup_consecutive_lines(&stripped); diff --git a/crates/pi-shell/src/minimizer/filters/gh.rs b/crates/pi-shell/src/minimizer/filters/gh.rs index 17d3dd22b..7ca39e0b3 100644 --- a/crates/pi-shell/src/minimizer/filters/gh.rs +++ b/crates/pi-shell/src/minimizer/filters/gh.rs @@ -1,7 +1,10 @@ //! GitHub CLI output filters. +use std::fmt::Write as _; + use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn supports(subcommand: Option<&str>) -> bool { matches!( subcommand, @@ -18,6 +21,7 @@ pub fn supports(subcommand: Option<&str>) -> bool { ) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { if preserves_raw_mode(ctx) { return MinimizerOutput::passthrough(input); @@ -148,12 +152,12 @@ fn filter_pr_checks(input: &str) -> Option { let mut out = String::new(); let failed = failed_rows.len(); - out.push_str(&format!("checks: {passed} passed, {failed} failed")); + let _ = write!(out, "checks: {passed} passed, {failed} failed"); if pending > 0 { - out.push_str(&format!(", {pending} pending")); + let _ = write!(out, ", {pending} pending"); } if skipping > 0 { - out.push_str(&format!(", {skipping} skipping")); + let _ = write!(out, ", {skipping} skipping"); } out.push('\n'); // Cap the verbatim failed rows: a PR with hundreds of failing checks would @@ -305,7 +309,7 @@ mod tests { let ctx = test_ctx(Some("pr"), "gh pr checks 123", &cfg); let mut input = String::new(); for idx in 0..500 { - input.push_str(&format!("X\tcheck{idx}\t1s\thttps://ci.test/{idx}\n")); + let _ = writeln!(input, "X\tcheck{idx}\t1s\thttps://ci.test/{idx}"); } let out = filter(&ctx, &input, 1); diff --git a/crates/pi-shell/src/minimizer/filters/git.rs b/crates/pi-shell/src/minimizer/filters/git.rs index e84de5110..3320433fd 100644 --- a/crates/pi-shell/src/minimizer/filters/git.rs +++ b/crates/pi-shell/src/minimizer/filters/git.rs @@ -4,6 +4,7 @@ use std::fmt::Write as _; use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn supports(subcommand: Option<&str>) -> bool { matches!( subcommand, @@ -29,6 +30,7 @@ pub fn supports(subcommand: Option<&str>) -> bool { ) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { if is_show_path_content(ctx.command) || is_stash_patch(ctx.command) { return MinimizerOutput::passthrough(input); @@ -1739,9 +1741,10 @@ mod tests { let cfg = MinimizerConfig { enabled: true, ..Default::default() }; let ctx = test_ctx(Some("tag"), "git tag --format=%(refname:short)|%(taggerdate:short)", &cfg); - let input = (0..45) - .map(|idx| format!("v1.{idx}|2026-06-06\n")) - .collect::(); + let input = (0..45).fold(String::new(), |mut s, idx| { + let _ = writeln!(s, "v1.{idx}|2026-06-06"); + s + }); let out = filter(&ctx, &input, 0); @@ -1753,9 +1756,10 @@ mod tests { fn tag_delete_output_is_passthrough() { let cfg = MinimizerConfig { enabled: true, ..Default::default() }; let ctx = test_ctx(Some("tag"), "git tag -d v1.0 v1.1", &cfg); - let input = (0..45) - .map(|idx| format!("Deleted tag 'v1.{idx}' (was abc1234)\n")) - .collect::(); + let input = (0..45).fold(String::new(), |mut s, idx| { + let _ = writeln!(s, "Deleted tag 'v1.{idx}' (was abc1234)"); + s + }); let out = filter(&ctx, &input, 0); @@ -1767,7 +1771,10 @@ mod tests { fn tag_listing_is_compacted() { let cfg = MinimizerConfig { enabled: true, ..Default::default() }; let ctx = test_ctx(Some("tag"), "git tag --list", &cfg); - let input = (0..45).map(|idx| format!("v1.{idx}\n")).collect::(); + let input = (0..45).fold(String::new(), |mut s, idx| { + let _ = writeln!(s, "v1.{idx}"); + s + }); let out = filter(&ctx, &input, 0); @@ -2073,10 +2080,8 @@ mod tests { if idx % 3 == 0 { input.push_str(".rs\tnew-"); input.push_str(&idx.to_string()); - input.push_str(".rs\n"); - } else { - input.push_str(".rs\n"); } + input.push_str(".rs\n"); } let out = filter(&ctx, &input, 0); @@ -2762,9 +2767,10 @@ error: could not apply abc1234... fix: something\nhint: Resolve all conflicts ma input.push_str("/home/alice/repo abc1234 (bare)\n"); input.push_str("/home/alice/repo-detached def5678 (detached HEAD)\n"); for idx in 0..20 { - input.push_str(&format!( - "/home/alice/wt-{idx:02} aaaaaaa{idx:02} [wt-{idx}]\n" - )); + let _ = writeln!( + input, + "/home/alice/wt-{idx:02} aaaaaaa{idx:02} [wt-{idx}]" + ); } let out = condense_worktree_with_home(&input, home); @@ -2825,7 +2831,7 @@ error: could not apply abc1234... fix: something\nhint: Resolve all conflicts ma let ctx = test_ctx(Some("worktree"), "git worktree list", &cfg); let mut input = String::new(); for idx in 0..22usize { - input.push_str(&format!("/repo/wt-{idx:02} aaaaaaa{idx:02} [branch-{idx}]\n")); + let _ = writeln!(input, "/repo/wt-{idx:02} aaaaaaa{idx:02} [branch-{idx}]"); } let out = filter(&ctx, &input, 0); assert!( diff --git a/crates/pi-shell/src/minimizer/filters/glab.rs b/crates/pi-shell/src/minimizer/filters/glab.rs index afa0cb729..7ca413aaa 100644 --- a/crates/pi-shell/src/minimizer/filters/glab.rs +++ b/crates/pi-shell/src/minimizer/filters/glab.rs @@ -1,6 +1,6 @@ //! GitLab CLI (glab) output filters. -use std::sync::LazyLock; +use std::{fmt::Write as _, sync::LazyLock}; use regex::Regex; @@ -18,10 +18,12 @@ static BARE_ANSI_RE: LazyLock = LazyLock::new(|| Regex::new(r"\[[\d;]+[A- /// Multiple consecutive blank lines (3+ newlines) collapsed to double newline. static MULTI_BLANK_RE: LazyLock = LazyLock::new(|| Regex::new(r"\n{3,}").unwrap()); +#[must_use] pub fn supports(subcommand: Option<&str>) -> bool { matches!(subcommand, Some("mr" | "issue" | "ci" | "pipeline" | "release")) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { if preserves_raw_mode(ctx) { return MinimizerOutput::passthrough(input); @@ -158,14 +160,12 @@ fn filter_release_list(input: &str) -> Option { break; } // Parse "Showing N releases on owner/repo." or similar - if total.is_none() { - if let Some(rest) = trimmed.strip_prefix("Showing ") { - if let Some(n_str) = rest.split_whitespace().next() { - if let Ok(n) = n_str.parse::() { - total = Some(n); - } - } - } + if total.is_none() + && let Some(rest) = trimmed.strip_prefix("Showing ") + && let Some(n_str) = rest.split_whitespace().next() + && let Ok(n) = n_str.parse::() + { + total = Some(n); } lines.next(); } @@ -196,9 +196,9 @@ fn filter_release_list(input: &str) -> Option { let created = parts[2].trim(); if name == tag { - filtered.push_str(&format!(" {name} ({created})\n")); + let _ = writeln!(filtered, " {name} ({created})"); } else { - filtered.push_str(&format!(" {name} [{tag}] ({created})\n")); + let _ = writeln!(filtered, " {name} [{tag}] ({created})"); } count += 1; @@ -209,9 +209,9 @@ fn filter_release_list(input: &str) -> Option { } // Append omission marker when there are more releases than shown. - let omitted = total.map(|t| t.saturating_sub(count)).unwrap_or(0); + let omitted = total.map_or(0, |t| t.saturating_sub(count)); if omitted > 0 { - filtered.push_str(&format!("… {} releases omitted …\n", omitted)); + let _ = writeln!(filtered, "… {omitted} releases omitted …"); } else if has_more { // Total not parsed from preamble but a 21st row was observed; signal // truncation. @@ -653,7 +653,7 @@ section_end:1711234600:build_script[0K // Build input with 25 releases so the cap of 20 is exceeded. let mut input = String::from("Showing 25 releases on owner/repo.\n\nName\tTag\tCreated\n"); for i in 1..=25 { - input.push_str(&format!("Release {i}\tv{i}.0.0\t{i} days ago\n")); + let _ = writeln!(input, "Release {i}\tv{i}.0.0\t{i} days ago"); } let cfg = MinimizerConfig { enabled: true, ..Default::default() }; let ctx = test_ctx(Some("release"), "glab release list", &cfg); diff --git a/crates/pi-shell/src/minimizer/filters/go.rs b/crates/pi-shell/src/minimizer/filters/go.rs index 86701e34e..e1e893cec 100644 --- a/crates/pi-shell/src/minimizer/filters/go.rs +++ b/crates/pi-shell/src/minimizer/filters/go.rs @@ -1,7 +1,10 @@ //! Go toolchain output filters. +use std::fmt::Write as _; + use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn supports(program: &str, subcommand: Option<&str>) -> bool { match program { "go" => matches!(subcommand, Some("test" | "build" | "vet" | "tool")), @@ -10,6 +13,7 @@ pub fn supports(program: &str, subcommand: Option<&str>) -> bool { } } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let text = if ctx.program == "golangci-lint" || is_go_tool_golangci_lint(ctx) { @@ -119,14 +123,12 @@ fn aggregate_go_test_success(input: &str) -> String { // Also check for JSON-wrapped benchmark output — those lines start with // "{" so the raw-line guard above misses them. Bail to head_tail // early so benchmark results are never collapsed into a package count. - if trimmed.starts_with('{') { - if let Ok(value) = serde_json::from_str::(trimmed) { - if let Some(output) = value.get("Output").and_then(|v| v.as_str()) { - if output.trim_start().starts_with("Benchmark") { - return primitives::head_tail_lines(input, 140, 80); - } - } - } + if trimmed.starts_with('{') + && let Ok(value) = serde_json::from_str::(trimmed) + && let Some(output) = value.get("Output").and_then(|v| v.as_str()) + && output.trim_start().starts_with("Benchmark") + { + return primitives::head_tail_lines(input, 140, 80); } let candidate = render_go_test_json_line(trimmed); @@ -148,10 +150,10 @@ fn aggregate_go_test_success(input: &str) -> String { let mut summary = format!("go test: {packages_ok} packages ok"); if no_tests > 0 { - summary.push_str(&format!(", {no_tests} no tests")); + let _ = write!(summary, ", {no_tests} no tests"); } if tests_skipped > 0 { - summary.push_str(&format!(", {tests_skipped} tests skipped")); + let _ = write!(summary, ", {tests_skipped} tests skipped"); } summary.push('\n'); summary @@ -312,12 +314,12 @@ fn summarize_golangci_json(line: &str) -> Option { let line_no = issue .get("Pos") .and_then(|pos| pos.get("Line")) - .and_then(|v| v.as_u64()) + .and_then(serde_json::Value::as_u64) .map_or(0, |value| value); let col_no = issue .get("Pos") .and_then(|pos| pos.get("Column")) - .and_then(|v| v.as_u64()) + .and_then(serde_json::Value::as_u64) .map_or(0, |value| value); let linter = issue .get("FromLinter") diff --git a/crates/pi-shell/src/minimizer/filters/gt.rs b/crates/pi-shell/src/minimizer/filters/gt.rs index c474f2d59..e5d6c5baf 100644 --- a/crates/pi-shell/src/minimizer/filters/gt.rs +++ b/crates/pi-shell/src/minimizer/filters/gt.rs @@ -1,5 +1,7 @@ //! Graphite (`gt`) output filters. +use std::fmt::Write as _; + use super::git; use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; @@ -8,10 +10,12 @@ const GT_SUBCOMMANDS: &[&str] = &[ "fetch", "stash", "worktree", ]; +#[must_use] pub fn supports(program: &str, subcommand: Option<&str>) -> bool { program == "gt" && subcommand.is_some_and(|subcommand| GT_SUBCOMMANDS.contains(&subcommand)) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { if ctx.subcommand == Some("log") && is_log_short(ctx.command) { return MinimizerOutput::passthrough(input); @@ -172,9 +176,9 @@ fn dense_sync_summary(input: &str, exit_code: i32) -> Option { const DELETED_NAME_CAP: usize = 20; let shown = deleted_names.len().min(DELETED_NAME_CAP); let names = deleted_names[..shown].join(", "); - summary.push_str(&format!(" ({names}")); + let _ = write!(summary, " ({names}"); if deleted_names.len() > DELETED_NAME_CAP { - summary.push_str(&format!(", +{} more", deleted_names.len() - DELETED_NAME_CAP)); + let _ = write!(summary, ", +{} more", deleted_names.len() - DELETED_NAME_CAP); } summary.push(')'); } @@ -440,7 +444,7 @@ mod tests { let ctx = test_ctx(Some("sync"), &cfg); let mut input = String::from("Synced with remote\n"); for idx in 0..500 { - input.push_str(&format!("Deleted branch feat/merged-{idx}\n")); + let _ = writeln!(input, "Deleted branch feat/merged-{idx}"); } let out = filter(&ctx, &input, 0); diff --git a/crates/pi-shell/src/minimizer/filters/js_tools.rs b/crates/pi-shell/src/minimizer/filters/js_tools.rs index a5b263f0f..01828f959 100644 --- a/crates/pi-shell/src/minimizer/filters/js_tools.rs +++ b/crates/pi-shell/src/minimizer/filters/js_tools.rs @@ -8,10 +8,12 @@ use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; const SUPPORTED_TOOLS: &[&str] = &["next", "prettier", "prisma"]; const NPX_ROUTABLE_TOOLS: &[&str] = &["tsc", "eslint", "prisma", "prettier", "next"]; +#[must_use] pub fn supports(program: &str, subcommand: Option<&str>) -> bool { effective_tool(program, subcommand).is_some() } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let tool = effective_tool(ctx.program, ctx.subcommand) @@ -31,6 +33,7 @@ pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerO } } +#[must_use] pub fn effective_tool<'a>(program: &'a str, subcommand: Option<&'a str>) -> Option<&'a str> { if SUPPORTED_TOOLS.contains(&program) { return Some(program); diff --git a/crates/pi-shell/src/minimizer/filters/jvm.rs b/crates/pi-shell/src/minimizer/filters/jvm.rs index 69bae6175..954c8b443 100644 --- a/crates/pi-shell/src/minimizer/filters/jvm.rs +++ b/crates/pi-shell/src/minimizer/filters/jvm.rs @@ -18,7 +18,7 @@ //! with a Rust module capable of state-machine parsing (block collapse, //! continuation tracking, mode toggle) that the TOML DSL cannot express. -use std::{collections::HashSet, sync::LazyLock}; +use std::{collections::HashSet, fmt::Write as _, sync::LazyLock}; use regex::Regex; @@ -97,6 +97,7 @@ static FILE_COORD: LazyLock = /// The active phase is decided inside [`filter`] by re-tokenizing the raw /// command, never by `ctx.subcommand` (which is the FIRST non-flag arg and so /// mis-reports the phase for `mvn clean install` — it would say `clean`). +#[must_use] pub fn supports(program: &str, _subcommand: Option<&str>) -> bool { is_mvn_family(program) || is_gradle_family(program) } @@ -111,6 +112,7 @@ fn is_gradle_family(program: &str) -> bool { matches!(program, "gradle" | "gradlew" | "gradlew.bat") } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, _exit_code: i32) -> MinimizerOutput { if is_gradle_family(ctx.program) { return filter_gradle(ctx, input); @@ -221,7 +223,8 @@ pub enum MvnPhase { Passthrough, // clean, site, plugin goals, version/help, empty } -/// Whether `a` is a Maven lifecycle phase or plugin goal we map to a MvnPhase. +/// Whether `a` is a Maven lifecycle phase or plugin goal we map to a +/// `MvnPhase`. fn is_recognized_mvn_goal(a: &str) -> bool { matches!( a, @@ -246,7 +249,7 @@ fn is_recognized_mvn_goal(a: &str) -> bool { /// mistaken for build goals/tasks. fn jvm_positional_tokens<'a>(command: &'a str, value_flags: &[&str]) -> Vec<&'a str> { let mut out = Vec::new(); - let mut tokens = command.split_whitespace().skip(1).peekable(); + let mut tokens = command.split_whitespace().skip(1); while let Some(tok) = tokens.next() { if tok.starts_with('-') { let bare = tok.trim_start_matches('-'); @@ -270,6 +273,7 @@ fn jvm_positional_tokens<'a>(command: &'a str, value_flags: &[&str]) -> Vec<&'a /// If empty, plugin-form (`:`), or `clean`/`site` → Passthrough. Re-tokenizes /// the raw command because `ctx.subcommand` is the FIRST non-flag arg and so /// reports the wrong goal for `mvn clean install`. +#[must_use] pub fn detect_phase(command: &str) -> MvnPhase { // Use the last RECOGNIZED lifecycle goal so that option-value tokens // (e.g. the module name after -pl) are ignored. @@ -292,8 +296,7 @@ pub fn detect_phase(command: &str) -> MvnPhase { "--threads", ]) .into_iter() - .filter(|a| is_recognized_mvn_goal(a)) - .last() + .rfind(|a| is_recognized_mvn_goal(a)) .unwrap_or(""); // `spring-boot:run` is checked BEFORE the generic `:`-plugin-goal guard @@ -652,7 +655,7 @@ impl FailuresSummaryCap { return; } if self.dropped > 0 { - out.push_str(&format!("\n… +{} more failures\n", self.dropped)); + let _ = write!(out, "\n… +{} more failures\n", self.dropped); } self.in_summary = false; self.emitted = 0; @@ -662,7 +665,7 @@ impl FailuresSummaryCap { /// End-of-stream tail emission for cases where the AGG line never arrives. fn finish(&self, out: &mut String) { if self.in_summary && self.dropped > 0 { - out.push_str(&format!("\n… +{} more failures\n", self.dropped)); + let _ = write!(out, "\n… +{} more failures\n", self.dropped); } } } @@ -671,6 +674,7 @@ impl FailuresSummaryCap { /// /// English-footer guard: if no `BUILD SUCCESS`/`BUILD FAILURE` line is present, /// return the ANSI-stripped raw input (non-English locale or truncated output). +#[must_use] pub fn filter_surefire(raw: &str) -> String { filter_surefire_with_cap(raw, max_mvn_failing_classes()) } @@ -740,7 +744,7 @@ fn filter_surefire_with_cap(raw: &str, cap: usize) -> String { block.finish(&mut out); summary.finish(&mut out); if dropped_failing > 0 { - out.push_str(&format!("\n… +{dropped_failing} more failing test classes\n")); + let _ = write!(out, "\n… +{dropped_failing} more failing test classes\n"); } out } @@ -822,6 +826,7 @@ pub fn filter_compile(raw: &str) -> String { /// `[INFO] Running …` line is seen (via [`SurefireBlock`]). Outside any /// Surefire block, applies the unified keep-list (compile keepers + /// install/artifact lines). +#[must_use] pub fn filter_package(raw: &str) -> String { filter_package_with_cap(raw, max_mvn_failing_classes()) } @@ -898,7 +903,7 @@ fn filter_package_with_cap(raw: &str, cap: usize) -> String { block.finish(&mut out); summary.finish(&mut out); if dropped_failing > 0 { - out.push_str(&format!("\n… +{dropped_failing} more failing test classes\n")); + let _ = write!(out, "\n… +{dropped_failing} more failing test classes\n"); } out } @@ -1592,12 +1597,12 @@ fn filter_gradle_dependencies(input: &str) -> String { let cap = max_gradle_deps(); for (config, deps) in &configs { - result.push_str(&format!("\n{} ({}):\n", config, deps.len())); + let _ = write!(result, "\n{} ({}):\n", config, deps.len()); for dep in deps.iter().take(cap) { - result.push_str(&format!(" {dep}\n")); + let _ = writeln!(result, " {dep}"); } if deps.len() > cap { - result.push_str(&format!(" ... +{} more\n", deps.len() - cap)); + let _ = writeln!(result, " ... +{} more", deps.len() - cap); } } @@ -2188,12 +2193,13 @@ mod tests { fn surefire_caps_failing_blocks_emits_tail() { let mut i = String::from("[INFO] Scanning for projects...\n[INFO] -----< x >-----\n"); for n in 1..=5 { - i.push_str(&format!( + let _ = write!( + i, "[INFO] Running x.Fail{n}\n[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, \ Time elapsed: 0.0{n}1 s <<< FAILURE! -- in x.Fail{n}\n[ERROR] x.Fail{n}.bar -- Time \ elapsed: 0.0{n}0 s <<< FAILURE!\norg.opentest4j.AssertionFailedError: boom{n}\n\tat \ x.Fail{n}.bar(Fail{n}.java:25)\n\n" - )); + ); } i.push_str("[INFO] BUILD FAILURE\n"); let o = filter_surefire_with_cap(&i, 3); @@ -2215,10 +2221,11 @@ mod tests { fn surefire_cap_zero_emits_summary_only() { let mut i = String::from("[INFO] Scanning for projects...\n[INFO] -----< x >-----\n"); for n in 1..=5 { - i.push_str(&format!( + let _ = write!( + i, "[INFO] Running x.Fail{n}\n[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, \ Time elapsed: 0.0{n}1 s <<< FAILURE! -- in x.Fail{n}\n\n" - )); + ); } i.push_str("[INFO] BUILD FAILURE\n"); let o = filter_surefire_with_cap(&i, 0); @@ -2236,7 +2243,7 @@ mod tests { let mut i = String::from("[INFO] -----< x >-----\n[INFO] Results:\n[INFO]\n[ERROR] Failures:\n"); for n in 1..=5 { - i.push_str(&format!("[ERROR] ClassA.test{n}:25 expected: but was: \n")); + let _ = writeln!(i, "[ERROR] ClassA.test{n}:25 expected: but was: "); } i.push_str( "[INFO]\n[ERROR] Tests run: 100, Failures: 5, Errors: 0, Skipped: 0\n[INFO] BUILD \ diff --git a/crates/pi-shell/src/minimizer/filters/lint.rs b/crates/pi-shell/src/minimizer/filters/lint.rs index 70db60257..f89ac0f54 100644 --- a/crates/pi-shell/src/minimizer/filters/lint.rs +++ b/crates/pi-shell/src/minimizer/filters/lint.rs @@ -4,10 +4,12 @@ use std::collections::BTreeMap; use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn supports(subcommand: Option<&str>) -> bool { supports_program("", subcommand) } +#[must_use] pub fn supports_program(program: &str, subcommand: Option<&str>) -> bool { // Program-claim the JS type-checker/linters too: without this, a path-arg // invocation (`tsc --project x`, `eslint src/`, `biome ci app/`, @@ -39,6 +41,7 @@ fn is_js_lint_program(program: &str) -> bool { matches!(program, "tsc" | "eslint" | "biome" | "oxlint") } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { if preserves_machine_readable_output(ctx) { return MinimizerOutput::passthrough(input); @@ -52,6 +55,7 @@ pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerO } } +#[must_use] pub fn condense_lint_output(program: &str, input: &str, exit_code: i32) -> String { let cleaned = primitives::strip_ansi(input); let stripped = strip_lint_noise(program, &cleaned, exit_code); @@ -99,14 +103,13 @@ fn preserves_machine_readable_output(ctx: &MinimizerCtx<'_>) -> bool { if ctx.program == "eslint" { let tokens: Vec<&str> = ctx.command.split_whitespace().collect(); for (i, t) in tokens.iter().enumerate() { - if (*t == "-f" || *t == "--format") && tokens.get(i + 1).map_or(false, |v| *v != "stylish") - { + if (*t == "-f" || *t == "--format") && tokens.get(i + 1).is_some_and(|v| *v != "stylish") { return true; } - if let Some(val) = t.strip_prefix("--format=") { - if val != "stylish" { - return true; - } + if let Some(val) = t.strip_prefix("--format=") + && val != "stylish" + { + return true; } } } @@ -180,7 +183,7 @@ fn is_pyright_banner_noise(line: &str) -> bool { } // `Found 42 source files`: literal prefix, then a count, then `source file`. if let Some(rest) = line.strip_prefix("Found ") { - let digits: String = rest.chars().take_while(|c| c.is_ascii_digit()).collect(); + let digits: String = rest.chars().take_while(char::is_ascii_digit).collect(); if !digits.is_empty() { let after = rest[digits.len()..].trim_start(); if after.starts_with("source file") { @@ -485,6 +488,7 @@ fn is_bare_gutter_numbered_line(trimmed: &str) -> bool { false } +#[must_use] pub fn group_diagnostics(input: &str) -> String { let mut grouped: BTreeMap> = BTreeMap::new(); let mut ungrouped = Vec::new(); diff --git a/crates/pi-shell/src/minimizer/filters/listing.rs b/crates/pi-shell/src/minimizer/filters/listing.rs index 875970eaa..a130a7dd6 100644 --- a/crates/pi-shell/src/minimizer/filters/listing.rs +++ b/crates/pi-shell/src/minimizer/filters/listing.rs @@ -39,6 +39,7 @@ fn find_outputs_paths_only(command: &str) -> bool { }) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let legacy = ctx.config.legacy_filters_active(); @@ -502,24 +503,17 @@ fn parse_ls_long_line(line: &str) -> Option { // Size is the rightmost parseable u64 before the date, but skip // comma-separated device major/minor pairs. let mut size = None; - for i in (0..before_parts.len()).rev() { - let part = before_parts[i]; + if let Some((i, part)) = before_parts.iter().enumerate().next_back() { if part.ends_with(',') { // Major number of a device file — skip. - size = None; - break; - } - if let Ok(s) = part.parse::() { + } else if let Ok(s) = part.parse::() { if i > 0 && before_parts[i - 1].ends_with(',') { // This numeric field is the minor number of a device file. - size = None; - break; + } else { + size = Some(s); } - size = Some(s); - break; } // Non-numeric field (e.g. owner/group word) before size: no size. - break; } Some(LsEntry { name, is_dir: kind == 'd', size, is_file: kind == '-' }) @@ -1215,6 +1209,8 @@ fn has_content(text: &str) -> bool { #[cfg(test)] mod tests { + use std::fmt::Write as _; + use super::*; use crate::minimizer::MinimizerConfig; @@ -1506,9 +1502,10 @@ mod tests { "overlay 52428800 1234 52427566 0% /var/lib/docker/overlay2/abc/merged\n"; let real_fs = "sda1 52428800 5000000 47428800 10% /data\n"; let tmpfs_line = "tmpfs 8192000 0 8192000 0% /dev\n"; - let padding: String = (0..28) - .map(|i| format!("sda{i} 1000 500 500 50% /mnt/disk{i}\n")) - .collect(); + let padding: String = (0..28).fold(String::new(), |mut s, i| { + let _ = writeln!(s, "sda{i} 1000 500 500 50% /mnt/disk{i}"); + s + }); let input = format!("{header}{overlay_root}{overlay_inner}{real_fs}{tmpfs_line}{padding}"); let out = filter(&ctx, &input, 0); @@ -1713,23 +1710,21 @@ mod tests { // --------------------------------------------------------------- fn legacy_cfg() -> MinimizerConfig { - let mut cfg = MinimizerConfig::default(); - cfg.enabled = true; - cfg.legacy_filters_active = true; - cfg + MinimizerConfig { enabled: true, legacy_filters_active: true, ..Default::default() } } fn synthesize_grep(matches_per_file: usize, files: usize) -> String { let mut out = String::new(); for f in 0..files { for m in 0..matches_per_file { - out.push_str(&format!( + let _ = writeln!( + out, "src/module{f}/file{f}.rs:{ln}: pub fn handler_{f}_{m}(req: Request) -> \ - Result {{ /* body */ }}\n", + Result {{ /* body */ }}", ln = m * 7 + 1, f = f, m = m, - )); + ); } } out @@ -1739,10 +1734,11 @@ mod tests { let mut out = String::new(); for d in 0..dirs { for f in 0..per_dir { - out.push_str(&format!( + let _ = writeln!( + out, "./crates/pi-shell/src/minimizer/filters/category{d}/\ - handler_{f}_with_descriptive_name.rs\n", - )); + handler_{f}_with_descriptive_name.rs", + ); } } out diff --git a/crates/pi-shell/src/minimizer/filters/mod.rs b/crates/pi-shell/src/minimizer/filters/mod.rs index 286040dbb..d68963a89 100644 --- a/crates/pi-shell/src/minimizer/filters/mod.rs +++ b/crates/pi-shell/src/minimizer/filters/mod.rs @@ -36,6 +36,7 @@ pub mod ruby; pub mod rust_tools; pub mod system; +#[must_use] pub fn supports(program: &str, subcommand: Option<&str>) -> bool { match program { "git" | "yadm" => git::supports(subcommand), @@ -141,6 +142,7 @@ fn is_lint_script_token(token: &str) -> bool { } /// Apply the matching built-in filter. +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let _ = ctx.command; let _ = ctx.config.per_command(ctx.program); @@ -921,9 +923,8 @@ mod tests { fn pytest_legacy_filters_active_passes_through() { // Kill-switch parity (M2): legacy_filters_active=true skips the // pytest state machine even when invoked via `uv pytest`. - let mut config = MinimizerConfig::default(); - config.enabled = true; - config.legacy_filters_active = true; + let config = + MinimizerConfig { enabled: true, legacy_filters_active: true, ..Default::default() }; let context = ctx("uv", Some("pytest"), "uv pytest tests/", &config); let out = filter(&context, PYTEST_FAILURE_INPUT, 1); assert_eq!(out.text, PYTEST_FAILURE_INPUT); diff --git a/crates/pi-shell/src/minimizer/filters/node_tests.rs b/crates/pi-shell/src/minimizer/filters/node_tests.rs index 186cb0df1..fd4e4b9dd 100644 --- a/crates/pi-shell/src/minimizer/filters/node_tests.rs +++ b/crates/pi-shell/src/minimizer/filters/node_tests.rs @@ -2,6 +2,7 @@ use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn filter(_ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let text = if exit_code == 0 { @@ -202,7 +203,7 @@ fn is_playwright_numbered_failure(trimmed: &str) -> bool { saw_digit = true; continue; } - return saw_digit && ch == ')' && chars.next().is_some_and(|next| next.is_whitespace()); + return saw_digit && ch == ')' && chars.next().is_some_and(char::is_whitespace); } false } diff --git a/crates/pi-shell/src/minimizer/filters/pkg.rs b/crates/pi-shell/src/minimizer/filters/pkg.rs index ef3aa7251..386081175 100644 --- a/crates/pi-shell/src/minimizer/filters/pkg.rs +++ b/crates/pi-shell/src/minimizer/filters/pkg.rs @@ -5,6 +5,7 @@ use std::{collections::HashSet, fmt::Write as _}; use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; const PACKAGE_TREE_HEAD_LINES: usize = 80; +#[must_use] pub fn supports(subcommand: Option<&str>) -> bool { matches!( subcommand, @@ -39,6 +40,7 @@ pub fn supports(subcommand: Option<&str>) -> bool { ) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { if exit_code == 0 && (command_contains_any(ctx.command, &["--json"]) @@ -783,7 +785,7 @@ mod tests { let context = ctx("npm", Some("list"), "npm list --all", &cfg); let mut input = String::from("app@1.0.0\n"); for idx in 0..90 { - input.push_str(&format!("├── dep{idx:03}@1.0.0\n")); + let _ = writeln!(input, "├── dep{idx:03}@1.0.0"); } let out = filter(&context, &input, 0); @@ -800,7 +802,7 @@ mod tests { let context = ctx("npm", Some("ls"), "npm ls --depth=0", &cfg); let mut input = String::from("app@1.0.0\n"); for idx in 0..90 { - input.push_str(&format!("├── dep{idx:03}@1.0.0\n")); + let _ = writeln!(input, "├── dep{idx:03}@1.0.0"); } let out = filter(&context, &input, 0); @@ -816,7 +818,7 @@ mod tests { let mut input = String::from("Legend: production dependency, optional only, dev only\nreact 19.0.0\n"); for idx in 0..90 { - input.push_str(&format!("└─ dependent{idx:03}\n")); + let _ = writeln!(input, "└─ dependent{idx:03}"); } let out = filter(&context, &input, 0); @@ -876,7 +878,7 @@ mod tests { "Resolved 91 packages in 12ms\nPrepared 2 packages in 3ms\nPackage Version\n", ); for idx in 0..90 { - input.push_str(&format!("pkg{idx:03} 1.0.{idx}\n")); + let _ = writeln!(input, "pkg{idx:03} 1.0.{idx}"); } let out = filter(&context, &input, 0); @@ -896,7 +898,7 @@ mod tests { let context = ctx("uv", Some("tree"), "uv tree", &cfg); let mut input = String::from("project v1.0.0\n"); for idx in 0..90 { - input.push_str(&format!("├── pkg{idx:03} v1.0.{idx}\n")); + let _ = writeln!(input, "├── pkg{idx:03} v1.0.{idx}"); } let out = filter(&context, &input, 0); @@ -912,7 +914,7 @@ mod tests { let context = ctx("poetry", Some("show"), "poetry show --tree", &cfg); let mut input = String::from("requests 2.32.0 Python HTTP for Humans.\n"); for idx in 0..90 { - input.push_str(&format!("├── dep{idx:03} 1.0.{idx}\n")); + let _ = writeln!(input, "├── dep{idx:03} 1.0.{idx}"); } let out = filter(&context, &input, 0); @@ -927,7 +929,7 @@ mod tests { let context = ctx("uv", Some("pip"), "uv pip freeze", &cfg); let mut input = String::new(); for idx in 0..90 { - input.push_str(&format!("pkg{idx:03}==1.0.{idx}\n")); + let _ = writeln!(input, "pkg{idx:03}==1.0.{idx}"); } let out = filter(&context, &input, 0); @@ -943,7 +945,7 @@ mod tests { let context = ctx("uv", Some("export"), "uv export -f requirements-txt", &cfg); let mut input = String::from("# generated by uv\n"); for idx in 0..90 { - input.push_str(&format!("pkg{idx:03}==1.0.{idx}\n")); + let _ = writeln!(input, "pkg{idx:03}==1.0.{idx}"); } let out = filter(&context, &input, 0); @@ -958,7 +960,7 @@ mod tests { let context = ctx("poetry", Some("export"), "poetry export -f requirements.txt", &cfg); let mut input = String::from("# generated by poetry\n"); for idx in 0..90 { - input.push_str(&format!("dep{idx:03}==2.0.{idx}\n")); + let _ = writeln!(input, "dep{idx:03}==2.0.{idx}"); } let out = filter(&context, &input, 0); @@ -988,7 +990,7 @@ mod tests { let context = ctx("pip", Some("list"), "pip list", &cfg); let mut input = String::from("Package Version\n---------- -------\n"); for idx in 0..90 { - input.push_str(&format!("pkg{idx:03} 1.0.{idx}\n")); + let _ = writeln!(input, "pkg{idx:03} 1.0.{idx}"); } let out = filter(&context, &input, 0); diff --git a/crates/pi-shell/src/minimizer/filters/python.rs b/crates/pi-shell/src/minimizer/filters/python.rs index 8fe0c2692..943875a66 100644 --- a/crates/pi-shell/src/minimizer/filters/python.rs +++ b/crates/pi-shell/src/minimizer/filters/python.rs @@ -33,6 +33,7 @@ use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; /// overflow independent. const MAX_PYTEST_FAILURES: usize = 10; +#[must_use] pub fn supports(program: &str, subcommand: Option<&str>) -> bool { matches!(program, "pytest" | "ruff" | "mypy") || matches!( @@ -41,6 +42,7 @@ pub fn supports(program: &str, subcommand: Option<&str>) -> bool { ) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { // Kill-switch parity (M2): when `legacy_filters_active`, fall back to // the pre-PR passthrough so callers can rollback an RTK-port regression @@ -409,6 +411,8 @@ fn has_content(text: &str) -> bool { #[cfg(test)] mod tests { + use std::fmt::Write as _; + use super::*; use crate::minimizer::MinimizerConfig; @@ -549,18 +553,19 @@ mod tests { ===================================\n", ); for i in 0..12 { - input.push_str(&format!( + let _ = write!( + input, "____________________________ test_case_{i} ____________________________\n\n def \ test_case_{i}():\n> assert False\nE assert \ False\n\ntests/test_many.py:{}: AssertionError\n", i + 1 - )); + ); } input.push_str( "=========================== short test summary info ===========================\n", ); for i in 0..12 { - input.push_str(&format!("FAILED tests/test_many.py::test_case_{i} - assert False\n")); + let _ = writeln!(input, "FAILED tests/test_many.py::test_case_{i} - assert False"); } input.push_str("========================= 12 failed in 0.30s =========================\n"); @@ -619,29 +624,31 @@ mod tests { ====================================\n", ); for i in 0..9 { - input.push_str(&format!( + let _ = write!( + input, "_____________________ ERROR collecting tests/test_imp_{i}.py \ _____________________\nImportError while importing test module \ 'tests/test_imp_{i}.py'.\nE ImportError: boom\n" - )); + ); } input.push_str( "=================================== FAILURES ===================================\n", ); for i in 0..5 { - input.push_str(&format!( + let _ = write!( + input, "_______________________________ test_critical_{i} \ ________________________________\ntests/pay.py:{}: in test_critical_{i}\n assert \ 0 == 100\nE assert 0 == 100\ntests/pay.py:{}: AssertionError\n", i + 1, i + 1 - )); + ); } input.push_str( "=========================== short test summary info ===========================\n", ); for i in 0..5 { - input.push_str(&format!("FAILED tests/pay.py::test_critical_{i} - assert 0 == 100\n")); + let _ = writeln!(input, "FAILED tests/pay.py::test_critical_{i} - assert 0 == 100"); } input.push_str( "========================= 5 failed, 9 errors in 0.30s =========================\n", @@ -676,19 +683,21 @@ mod tests { ====================================\n", ); for i in 0..12 { - input.push_str(&format!( + let _ = write!( + input, "_____________________ ERROR collecting tests/test_imp_{i}.py \ _____________________\nE ImportError: boom\n" - )); + ); } input.push_str( "=================================== FAILURES ===================================\n", ); for i in 0..2 { - input.push_str(&format!( + let _ = write!( + input, "_______________________________ test_real_{i} ________________________________\n \ assert False\nE assert False\n" - )); + ); } input.push_str( "========================= 2 failed, 12 errors in 0.10s =========================\n", diff --git a/crates/pi-shell/src/minimizer/filters/ruby.rs b/crates/pi-shell/src/minimizer/filters/ruby.rs index e8a532c9b..9e5fe8bdd 100644 --- a/crates/pi-shell/src/minimizer/filters/ruby.rs +++ b/crates/pi-shell/src/minimizer/filters/ruby.rs @@ -3,6 +3,7 @@ use super::lint; use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn supports(program: &str, subcommand: Option<&str>) -> bool { match program { "rspec" | "rubocop" => true, @@ -17,6 +18,7 @@ fn is_def_scoped_subcommand(subcommand: Option<&str>) -> bool { matches!(subcommand, Some("db:migrate" | "db:rollback" | "routes")) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let text = match ruby_tool(ctx.program, ctx.subcommand) { @@ -412,7 +414,7 @@ fn first_json_string<'a>( fn first_json_u64(map: &serde_json::Map, keys: &[&str]) -> Option { keys .iter() - .find_map(|key| map.get(*key).and_then(|value| value.as_u64())) + .find_map(|key| map.get(*key).and_then(serde_json::Value::as_u64)) } fn filter_minitest(input: &str, exit_code: i32) -> String { @@ -671,6 +673,8 @@ fn has_content(text: &str) -> bool { #[cfg(test)] mod tests { + use std::fmt::Write as _; + use super::*; use crate::minimizer::MinimizerConfig; @@ -880,10 +884,11 @@ mod tests { fn rspec_caps_failure_blocks_at_five() { let mut input = String::from("Failures:\n\n"); for i in 1..=7 { - input.push_str(&format!( + let _ = write!( + input, " {i}) Example number {i} fails\n Failure/Error: expect(true).to eq(false)\n \ # ./spec/a_spec.rb:{i}\n\n" - )); + ); } input.push_str("7 examples, 7 failures\n"); let out = filter_rspec(&input, 1); @@ -1118,7 +1123,7 @@ mod tests { }; let mut input = String::from("rake aborted!\nNameError: undefined local variable\n"); for i in 0..20 { - input.push_str(&format!("/app/lib/task_{i}.rb:{i}:in `block'\n")); + let _ = writeln!(input, "/app/lib/task_{i}.rb:{i}:in `block'"); } input.push_str("\nTasks: TOP => db:seed\n"); let out = filter(&context, &input, 1); diff --git a/crates/pi-shell/src/minimizer/filters/rust_tools.rs b/crates/pi-shell/src/minimizer/filters/rust_tools.rs index 0c469f14a..498fb5184 100644 --- a/crates/pi-shell/src/minimizer/filters/rust_tools.rs +++ b/crates/pi-shell/src/minimizer/filters/rust_tools.rs @@ -11,10 +11,12 @@ use std::fmt::Write; use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn supports(program: &str, _subcommand: Option<&str>) -> bool { matches!(program, "rustfmt") } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { // Kill-switch parity (M2): legacy_filters_active=true skips this // filter so callers can rollback without recompile. @@ -112,7 +114,7 @@ mod tests { let cfg = MinimizerConfig { enabled: true, ..Default::default() }; let mut input = String::new(); for i in 0..50 { - input.push_str(&format!("Diff in src/file_{i}.rs at line 10:\n")); + let _ = writeln!(input, "Diff in src/file_{i}.rs at line 10:"); for _ in 0..8 { input.push_str("- old line\n"); input.push_str("+ new line\n"); @@ -147,7 +149,7 @@ mod tests { // Simulate a long usage/error dump with no `Diff in ` headers. let mut input = String::new(); for i in 0..400 { - input.push_str(&format!("error: usage line {i}\n")); + let _ = writeln!(input, "error: usage line {i}"); } let out = filter(&context, &input, 1); assert!(out.changed); @@ -158,9 +160,8 @@ mod tests { #[test] fn rustfmt_legacy_filters_active_passes_through() { // Kill-switch parity (M2). - let mut cfg = MinimizerConfig::default(); - cfg.enabled = true; - cfg.legacy_filters_active = true; + let cfg = + MinimizerConfig { enabled: true, legacy_filters_active: true, ..Default::default() }; let context = ctx("rustfmt", "rustfmt --check src/", &cfg); let input = "Diff in src/a.rs at line 1:\n-old\n+new\n"; let out = filter(&context, input, 1); diff --git a/crates/pi-shell/src/minimizer/filters/system.rs b/crates/pi-shell/src/minimizer/filters/system.rs index 672fc3bd7..c2ffb1c3f 100644 --- a/crates/pi-shell/src/minimizer/filters/system.rs +++ b/crates/pi-shell/src/minimizer/filters/system.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use super::git; use crate::minimizer::{MinimizerCtx, MinimizerOutput, primitives}; +#[must_use] pub fn supports(program: &str) -> bool { matches!( program, @@ -23,6 +24,7 @@ pub fn supports(program: &str) -> bool { ) } +#[must_use] pub fn filter(ctx: &MinimizerCtx<'_>, input: &str, exit_code: i32) -> MinimizerOutput { let cleaned = primitives::strip_ansi(input); let command = ctx.program; diff --git a/crates/pi-shell/src/minimizer/pipeline.rs b/crates/pi-shell/src/minimizer/pipeline.rs index 694627870..6aff600fb 100644 --- a/crates/pi-shell/src/minimizer/pipeline.rs +++ b/crates/pi-shell/src/minimizer/pipeline.rs @@ -235,6 +235,7 @@ pub fn compile(name: String, def: PipelineDef) -> Result) -> bool { if !self.match_command.is_match(program) { return false; @@ -249,6 +250,7 @@ impl CompiledPipeline { } /// Whether this pipeline is gated off for the supplied exit code. + #[must_use] pub fn skipped_by_exit(&self, exit_code: i32) -> bool { if !self.only_on_exit.is_empty() && !self.only_on_exit.contains(&exit_code) { return true; @@ -260,6 +262,7 @@ impl CompiledPipeline { } /// Apply the full 9-stage pipeline to `input`. + #[must_use] pub fn apply<'a>(&self, input: &'a str) -> Cow<'a, str> { // Stage 1: strip_ansi let stage1: Cow<'_, str> = if self.strip_ansi { @@ -380,6 +383,7 @@ pub struct PipelineRegistry { impl PipelineRegistry { /// Find the first pipeline that claims this `(program, subcommand)` pair. + #[must_use] pub fn find(&self, program: &str, subcommand: Option<&str>) -> Option<&CompiledPipeline> { self .pipelines @@ -432,6 +436,7 @@ pub struct TestOutcome { /// Run every inline test in `registry` and return the outcomes. #[allow(dead_code, reason = "test-only API surface")] +#[must_use] pub fn run_tests(registry: &PipelineRegistry) -> Vec { let mut out = Vec::new(); for (filter_name, tests) in ®istry.tests { diff --git a/crates/pi-shell/src/minimizer/plan.rs b/crates/pi-shell/src/minimizer/plan.rs index 870fa4818..6508a11f5 100644 --- a/crates/pi-shell/src/minimizer/plan.rs +++ b/crates/pi-shell/src/minimizer/plan.rs @@ -64,6 +64,7 @@ pub enum CommandPlan { } /// Parse `command` with `brush-parser` and classify its structure. +#[must_use] pub fn analyze(command: &str) -> CommandPlan { let trimmed = command.trim(); if trimmed.is_empty() { diff --git a/crates/pi-shell/src/minimizer/primitives.rs b/crates/pi-shell/src/minimizer/primitives.rs index 16bdff921..6e494b4ed 100644 --- a/crates/pi-shell/src/minimizer/primitives.rs +++ b/crates/pi-shell/src/minimizer/primitives.rs @@ -11,6 +11,7 @@ pub enum CapClass { } impl CapClass { + #[must_use] pub const fn lines(self) -> usize { match self { Self::Errors => 160, @@ -21,12 +22,14 @@ impl CapClass { } } +#[must_use] pub const fn reduced(cap: usize, by: usize) -> usize { let reduced = cap.saturating_sub(by); if reduced == 0 && cap > 0 { 1 } else { reduced } } /// Remove ANSI CSI escape sequences while preserving line endings verbatim. +#[must_use] pub fn strip_ansi(input: &str) -> String { let mut out = String::with_capacity(input.len()); let mut chars = input.chars().peekable(); @@ -46,6 +49,7 @@ pub fn strip_ansi(input: &str) -> String { } /// Collapse consecutive identical lines as `line (×N)`. +#[must_use] pub fn dedup_consecutive_lines(input: &str) -> String { let mut out = String::new(); let mut previous: Option<&str> = None; @@ -77,6 +81,7 @@ fn flush_repeated(out: &mut String, line: Option<&str>, count: usize) { } /// Keep the first `head` and last `tail` lines with an omission marker. +#[must_use] pub fn head_tail_lines(input: &str, head: usize, tail: usize) -> String { let lines: Vec<&str> = input.lines().collect(); if lines.len() <= head + tail { @@ -99,6 +104,7 @@ pub fn head_tail_lines(input: &str, head: usize, tail: usize) -> String { } /// Keep head/tail lines using a named cap class. +#[must_use] pub fn head_tail_cap(input: &str, class: CapClass) -> String { let cap = class.lines(); let head = reduced(cap, cap / 3); @@ -120,6 +126,7 @@ pub fn strip_lines(input: &str, predicates: &[fn(&str) -> bool]) -> String { } /// Group `file:line:message` style diagnostics by file. +#[must_use] pub fn group_by_file(input: &str, max_per_file: usize) -> String { let mut grouped: BTreeMap> = BTreeMap::new(); let mut ungrouped = Vec::new(); @@ -169,6 +176,7 @@ fn split_file_line(line: &str) -> Option<(&str, &str)> { Some((file, rest)) } +#[must_use] pub fn command_has_ordered_tokens(command: &str, first: &str, second: &str) -> bool { let mut saw_first = false; for part in command.split_whitespace() { @@ -182,6 +190,7 @@ pub fn command_has_ordered_tokens(command: &str, first: &str, second: &str) -> b false } +#[must_use] pub fn command_has_any_token(command: &str, tokens: &[&str]) -> bool { command.split_whitespace().any(|part| { tokens.iter().any(|token| { @@ -194,14 +203,17 @@ pub fn command_has_any_token(command: &str, tokens: &[&str]) -> bool { } /// Dedup consecutive lines then apply a 120-head / 80-tail cap. +#[must_use] pub fn head_tail_dedup(input: &str) -> String { head_tail_lines(&dedup_consecutive_lines(input), 120, 80) } +#[must_use] pub fn is_markdown_badge_or_image(line: &str) -> bool { line.starts_with("![") || line.starts_with("[![") || line.contains("img.shields.io") } +#[must_use] pub fn is_horizontal_rule(line: &str) -> bool { line.len() >= 3 && line.chars().all(|ch| matches!(ch, '-' | '*' | '_' | ' ')) @@ -209,6 +221,7 @@ pub fn is_horizontal_rule(line: &str) -> bool { } /// Compact a long plain listing to head/tail form. +#[must_use] pub fn compact_listing(input: &str, max_lines: usize) -> String { let lines: Vec<&str> = input .lines() @@ -244,6 +257,7 @@ pub fn compact_listing(input: &str, max_lines: usize) -> String { /// /// `max_chars == 0` is treated as "drop the line"; no marker is emitted in /// that case since the caller asked for an empty result. +#[must_use] pub fn truncate_line(line: &str, max_chars: usize) -> String { if max_chars == 0 { return String::new(); @@ -266,6 +280,7 @@ pub fn truncate_line(line: &str, max_chars: usize) -> String { } /// Keep only the first `head` lines; append a summary marker when truncated. +#[must_use] pub fn head_lines_only(input: &str, head: usize) -> String { let lines: Vec<&str> = input.lines().collect(); if lines.len() <= head { @@ -284,6 +299,7 @@ pub fn head_lines_only(input: &str, head: usize) -> String { } /// Keep only the last `tail` lines; prepend a summary marker when truncated. +#[must_use] pub fn tail_lines_only(input: &str, tail: usize) -> String { let lines: Vec<&str> = input.lines().collect(); if lines.len() <= tail { @@ -302,6 +318,7 @@ pub fn tail_lines_only(input: &str, tail: usize) -> String { } /// Hard cap: keep at most `max` lines, append a truncation marker otherwise. +#[must_use] pub fn max_lines(input: &str, max: usize) -> String { let lines: Vec<&str> = input.lines().collect(); if lines.len() <= max { @@ -325,6 +342,7 @@ pub fn max_lines(input: &str, max: usize) -> String { /// match the strip set (when present) — i.e. keep is `K AND NOT S`. An /// absent set imposes no constraint, so pure strip and pure keep filtering /// are the degenerate single-set cases. +#[must_use] pub fn filter_lines_regex( input: &str, strip: Option<®ex::RegexSet>, diff --git a/crates/pi-shell/src/process.rs b/crates/pi-shell/src/process.rs index 8899d8134..9f7c3be8a 100644 --- a/crates/pi-shell/src/process.rs +++ b/crates/pi-shell/src/process.rs @@ -1259,16 +1259,19 @@ impl Process { } /// Operating-system process identifier for this process reference. + #[must_use] pub const fn pid(&self) -> i32 { self.inner.pid() } /// Parent process id for this process, when available. + #[must_use] pub fn ppid(&self) -> Option { self.inner.parent_pid() } /// Launch arguments for this process. + #[must_use] pub fn args(&self) -> Vec { self.inner.args() } @@ -1279,11 +1282,13 @@ impl Process { /// signal abstraction, so the `signal` argument is ignored and the entire /// tree is hard-killed via `TerminateProcess`. Defaults to the POSIX /// hard-kill signal. + #[must_use] pub fn kill_tree(&self, signal: Option) -> u32 { self.signal_tree(signal.unwrap_or(KILL_SIGNAL)) } /// Process group id for this process, when supported by the platform. + #[must_use] pub fn group_id(&self) -> Option { self.inner.group_id() } @@ -1299,6 +1304,7 @@ impl Process { } /// Current status of this process reference. + #[must_use] pub fn status(&self) -> ProcessStatus { self.inner.status() } @@ -1463,6 +1469,7 @@ async fn wait_for_exit( /// Send `signal` to the process group `pgid`. /// Returns false when process groups are unsupported on the platform. #[allow(clippy::missing_const_for_fn, reason = "Dispatches to platform-specific implementation")] +#[must_use] pub fn kill_process_group(pgid: i32, signal: i32) -> bool { // Defense in depth: refuse to deliver a signal to the harness's own // process group. Doing so terminates the harness along with the targets. @@ -1510,6 +1517,7 @@ pub struct TerminationTargets { impl TerminationTargets { /// Create an empty target set. + #[must_use] pub fn new() -> Self { Self::default() } @@ -1533,6 +1541,7 @@ impl TerminationTargets { } /// True when no targets have been recorded. + #[must_use] pub const fn is_empty(&self) -> bool { self.pgids.is_empty() && self.processes.is_empty() } diff --git a/crates/pi-shell/tests/minimizer_fixtures.rs b/crates/pi-shell/tests/minimizer_fixtures.rs index 43ba35f08..b2905b780 100644 --- a/crates/pi-shell/tests/minimizer_fixtures.rs +++ b/crates/pi-shell/tests/minimizer_fixtures.rs @@ -23,7 +23,7 @@ //! All fixture failures are collected before the harness panics so a single run //! reports every regression, not just the first. -use std::{fs, path::Path}; +use std::{fmt::Write as _, fs, path::Path}; use pi_shell::minimizer::{self, MinimizerConfig}; @@ -158,12 +158,13 @@ fn diff_excerpt(expected: &str, actual: &str) -> String { let mut excerpt = String::new(); for i in start..(start + 3).min(max) { - excerpt.push_str(&format!( + let _ = write!( + excerpt, " line {}:\n expected {:?}\n actual {:?}\n", i + 1, expected_lines.get(i).copied().unwrap_or(""), actual_lines.get(i).copied().unwrap_or("") - )); + ); } excerpt.trim_end().to_string() }