Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions xtask/src/component_audit/source_metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,12 @@ fn underscore_self_param(signature: &str) -> Option<String> {
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);
Expand All @@ -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 {
Expand Down
12 changes: 12 additions & 0 deletions xtask/src/component_audit/source_metrics/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading