From 99bd6de1f6a89e63a0e70c97fde8b36541b41773 Mon Sep 17 00:00:00 2001 From: kirillDevPro <113171057+kirillDevPro@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:03:34 +0100 Subject: [PATCH] fix(xtask): match no-op builder parameters as whole identifiers Filed by the fix lane. Fixes #138 function_body_uses_param matched the parameter as a substring, so a builder taking `_id` and calling `with_identity()` read as using it and escaped the noop_public_api_markers count. It now counts a mention only when neither neighbouring character is `_` or ASCII alphanumeric. Test: function_body_usage_matches_whole_identifiers_only in xtask/src/component_audit/source_metrics/tests.rs failed before with `assertion failed: !function_body_uses_param(&unused, 0, "_id")` and passes now. Re-run: cargo test -p xtask function_body_usage cargo xtask component-audit --check-baseline still passes (no builder newly counted). Reviewer: Codex (PASS, no objections). --- xtask/src/component_audit/source_metrics.rs | 16 ++++++++++++++-- .../src/component_audit/source_metrics/tests.rs | 12 ++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/xtask/src/component_audit/source_metrics.rs b/xtask/src/component_audit/source_metrics.rs index b8431be..7fbd8ad 100644 --- a/xtask/src/component_audit/source_metrics.rs +++ b/xtask/src/component_audit/source_metrics.rs @@ -242,12 +242,12 @@ fn underscore_self_param(signature: &str) -> Option { fn function_body_uses_param(lines: &[&str], signature_ix: usize, param: &str) -> bool { let mut depth = brace_delta(lines[signature_ix]); let after_open = lines[signature_ix].split_once('{').map(|(_, body)| body); - if after_open.is_some_and(|body| body.contains(param)) { + if after_open.is_some_and(|body| contains_identifier(body, param)) { return true; } for line in lines.iter().skip(signature_ix + 1) { - if line.contains(param) { + if contains_identifier(line, param) { return true; } depth += brace_delta(line); @@ -259,6 +259,18 @@ fn function_body_uses_param(lines: &[&str], signature_ix: usize, param: &str) -> false } +/// Return whether `text` mentions `ident` as a whole identifier, so `_id` is not found inside +/// `with_identity`: a match counts only when neither neighbouring character is `_` or ASCII +/// alphanumeric. +fn contains_identifier(text: &str, ident: &str) -> bool { + let is_ident_char = |ch: char| ch == '_' || ch.is_ascii_alphanumeric(); + text.match_indices(ident).any(|(start, _)| { + let before = text[..start].chars().next_back(); + let after = text[start + ident.len()..].chars().next(); + !before.is_some_and(is_ident_char) && !after.is_some_and(is_ident_char) + }) +} + /// Return the net curly-brace depth change contributed by one source line. fn brace_delta(line: &str) -> i32 { line.chars().fold(0, |depth, ch| match ch { diff --git a/xtask/src/component_audit/source_metrics/tests.rs b/xtask/src/component_audit/source_metrics/tests.rs index 18ebbea..98d0886 100644 --- a/xtask/src/component_audit/source_metrics/tests.rs +++ b/xtask/src/component_audit/source_metrics/tests.rs @@ -36,6 +36,18 @@ fn function_body_usage_distinguishes_real_noop_from_used_arg() { assert!(!function_body_uses_param(&unused, 0, "_id")); } +/// Catches matching a builder parameter as a substring, which hides a no-op builder whose body +/// mentions a longer identifier that starts with the parameter's name. +#[test] +fn function_body_usage_matches_whole_identifiers_only() { + let unused = [ + "pub fn id(self, _id: usize) -> Self {", + " self.with_identity()", + "}", + ]; + assert!(!function_body_uses_param(&unused, 0, "_id")); +} + /// Catches `source_metrics.rs:scan_raw_hex_in_moon` exempting token sources by file name instead /// of by path, which would let any component module named `primitives.rs` paint raw hex without /// moving the `raw_hex_in_moon_runtime` metric, or dropping the exemption, which fails the audit