diff --git a/benchmarks/aft-search/slice-descriptors/land-list-envelope-item-names.json b/benchmarks/aft-search/slice-descriptors/land-list-envelope-item-names.json new file mode 100644 index 0000000000..ebd574711e --- /dev/null +++ b/benchmarks/aft-search/slice-descriptors/land-list-envelope-item-names.json @@ -0,0 +1,8 @@ +{ + "targeted_mechanism": "none", + "kind": "harness", + "slice_class": "non_ranking", + "fixtures": [ + "harness-goldens" + ] +} diff --git a/crates/aft/src/list_surfaces.rs b/crates/aft/src/list_surfaces.rs index a2fd135fb9..d41af730d7 100644 --- a/crates/aft/src/list_surfaces.rs +++ b/crates/aft/src/list_surfaces.rs @@ -196,7 +196,7 @@ pub static LIST_SURFACES: &[SurfaceEntry] = &[ ReasonEntry { reason: Reason::Cap, kind: ReasonKind::Selecting, - predicate_name: "SearchTrailer::shared_envelope_projection, StopState::S1MoreAtDepth, StopState::S2Exhausted, more_available, handle_external_semantic_or_hybrid_search, handle_external_grep_search, handle_semantic_or_hybrid_search, handle_grep_search, from_matches, run_engine_ranking, blast_radius_annotation_for_result, enrich_snippets_from_source_reference, enrich_snippets_from_source_with_context, read_bounded_snippet_line, truncate_chars, split_semantic_results, handle_split_search, rank_hits", + predicate_name: "SearchTrailer::shared_envelope_projection, StopState::S1MoreAtDepth, StopState::S2Exhausted, more_available, handle_external_semantic_or_hybrid_search, handle_external_grep_search, handle_semantic_or_hybrid_search, handle_grep_search, from_matches, run_engine_ranking, blast_radius_annotation_for_result, enrich_snippets_from_source_reference, enrich_snippets_from_source_with_context, read_bounded_snippet_line, truncate_chars, split_semantic_results, handle_split_search, reply", }, ], }, @@ -1026,7 +1026,7 @@ pub static EXCLUSIONS: &[ExclusionEntry] = &[ // results page is the search surface's, registered under its cap reason. ExclusionEntry { file: "commands/semantic_search/external_exact.rs", - enclosing_item: "files_with_hits", + enclosing_item: "sweep", location_or_primitive: "commands::semantic_search::external_exact::sweep skipped_foreign_mounts", reason: "counts mount points of other filesystems that the walk does not enter; no list is cut here, and a walk the deadline stops is reported through the sweep's enumeration_stopped and the reply's coverage line", }, @@ -1044,9 +1044,9 @@ pub static EXCLUSIONS: &[ExclusionEntry] = &[ }, ExclusionEntry { file: "commands/semantic_search/external_pattern.rs", - enclosing_item: "semantic_results, semantic_index", + enclosing_item: "semantic_results", location_or_primitive: "commands::semantic_search::external_pattern::Corpus semantic enumeration", - reason: "enumerates at most SEMANTIC_ENUMERATION_LIMIT prose candidates before ranking, like the external query-only route; more_available reports the remaining candidates and the engine envelopes the results page. Discovery currently attributes the restricted-visibility method to its preceding semantic_index helper", + reason: "enumerates at most SEMANTIC_ENUMERATION_LIMIT prose candidates before ranking, like the external query-only route; more_available reports the remaining candidates and the engine envelopes the results page", }, ExclusionEntry { file: "commands/inspect.rs", diff --git a/crates/aft/tests/helpers/list_cut_item.rs b/crates/aft/tests/helpers/list_cut_item.rs new file mode 100644 index 0000000000..67c2275924 --- /dev/null +++ b/crates/aft/tests/helpers/list_cut_item.rs @@ -0,0 +1,76 @@ +pub(crate) fn extract_item_name(line: &str) -> Option { + let trimmed = line.trim(); + let declaration = if let Some(restricted) = trimmed.strip_prefix("pub(") { + restricted.split_once(')')?.1.trim_start() + } else { + trimmed.strip_prefix("pub ").unwrap_or(trimmed).trim_start() + }; + + // Strip function qualifiers separately so `const LIMIT` remains a const + // item, while `const unsafe extern "C" fn run` is named `run`, not `unsafe`. + let mut function = declaration; + while let Some(rest) = ["async ", "const ", "unsafe ", "extern \"C\" "] + .iter() + .find_map(|prefix| function.strip_prefix(prefix)) + { + function = rest.trim_start(); + } + let rest = function.strip_prefix("fn ").or_else(|| { + ["const ", "static ", "struct ", "enum "] + .iter() + .find_map(|prefix| declaration.strip_prefix(prefix)) + })?; + let name = rest + .trim_start() + .chars() + .take_while(|character| character.is_alphanumeric() || *character == '_') + .collect::(); + (!name.is_empty()).then_some(name) +} + +#[test] +fn visibility_and_qualifiers_preserve_item_names() { + for visibility in [ + "", + "pub ", + "pub(crate) ", + "pub(super) ", + "pub(self) ", + "pub(in crate::x) ", + ] { + for (declaration, expected) in [ + ("fn plain() {}", "plain"), + ("async fn asynchronous() {}", "asynchronous"), + ("const fn constant() {}", "constant"), + ("unsafe fn unchecked() {}", "unchecked"), + ("extern \"C\" fn foreign() {}", "foreign"), + ("async unsafe fn combined() {}", "combined"), + ("const unsafe extern \"C\" fn qualified() {}", "qualified"), + ("const LIMIT: usize = 1;", "LIMIT"), + ("static VALUES: &[usize] = &[];", "VALUES"), + ("struct Record {}", "Record"), + ("enum Choice {}", "Choice"), + ] { + let line = format!(" {visibility}{declaration}"); + assert_eq!( + extract_item_name(&line).as_deref(), + Some(expected), + "{line}" + ); + } + } +} + +#[test] +fn non_items_have_no_item_name() { + for line in [ + "", + "// pub(super) fn commented() {}", + "let item = 1;", + "impl Record {", + "pub(in crate::x fn unclosed() {}", + "pub(super) fn () {}", + ] { + assert_eq!(extract_item_name(line), None, "{line}"); + } +} diff --git a/crates/aft/tests/list_envelope/main.rs b/crates/aft/tests/list_envelope/main.rs index cd8080da26..c5bf74b32b 100644 --- a/crates/aft/tests/list_envelope/main.rs +++ b/crates/aft/tests/list_envelope/main.rs @@ -1,6 +1,9 @@ #[path = "../helpers/context_storage.rs"] mod context_storage; +#[path = "../helpers/list_cut_item.rs"] +mod list_cut_item; + #[path = "../list_envelope_bash.rs"] mod list_envelope_bash; #[path = "../list_envelope_callgraph.rs"] diff --git a/crates/aft/tests/list_envelope_conformance/conformance_test.rs b/crates/aft/tests/list_envelope_conformance/conformance_test.rs index 948c87c279..b6fab9b82b 100644 --- a/crates/aft/tests/list_envelope_conformance/conformance_test.rs +++ b/crates/aft/tests/list_envelope_conformance/conformance_test.rs @@ -344,6 +344,42 @@ fn renderer_and_measurement_callers_are_closed() { ); } +#[test] +fn qualified_visibility_cut_fixture_attribution() { + let source = r#" +fn plain() { + values.iter().take(1); +} +pub(super) fn restricted() { + values.iter().take(2); +} +pub(in crate::x) fn scoped() { + values.iter().take(3); +} +"#; + let lines = source.lines().collect::>(); + let cuts = lines + .iter() + .enumerate() + .filter(|(_, line)| line.contains(".take(")) + .map(|(index, _)| index) + .collect::>(); + let conformance = cuts + .iter() + .map(|&index| super::support::enclosing_item(&lines, index)) + .collect::>(); + let drift_guard = cuts + .iter() + .map(|&index| crate::list_envelope_drift_guard::find_enclosing_item(&lines, index)) + .collect::>(); + let expected = ["plain", "restricted", "scoped"].map(String::from).to_vec(); + assert_eq!( + (conformance, drift_guard), + (expected.clone(), expected), + "both scanners must attribute each cut to its own function" + ); +} + #[test] fn registry_free_discovery_resolves_every_cut() { let discovered = discover_list_cutting_sites(); diff --git a/crates/aft/tests/list_envelope_conformance/support.rs b/crates/aft/tests/list_envelope_conformance/support.rs index 46401b2b9d..901bf31ce0 100644 --- a/crates/aft/tests/list_envelope_conformance/support.rs +++ b/crates/aft/tests/list_envelope_conformance/support.rs @@ -31,6 +31,8 @@ use aft::protocol::Response; use aft::subc_format::{format_response_with_context, FormatContext, OutlineMode}; use serde_json::{json, Map, Value}; +use crate::list_cut_item::extract_item_name; + fn fixture_search_envelope( shown: usize, more_available: bool, @@ -2323,38 +2325,7 @@ pub struct DiscoveredCut { pub snippet: String, } -fn extract_item_name(line: &str) -> Option { - let trimmed = line.trim(); - let rest = [ - "pub(crate) fn ", - "pub fn ", - "fn ", - "async fn ", - "pub async fn ", - "pub(crate) async fn ", - "pub(crate) const ", - "pub const ", - "const ", - "pub(crate) static ", - "pub static ", - "static ", - "pub(crate) struct ", - "pub struct ", - "struct ", - "pub(crate) enum ", - "pub enum ", - "enum ", - ] - .iter() - .find_map(|prefix| trimmed.strip_prefix(prefix))?; - let name = rest - .chars() - .take_while(|character| character.is_alphanumeric() || *character == '_') - .collect::(); - (!name.is_empty()).then_some(name) -} - -fn enclosing_item(lines: &[&str], target: usize) -> String { +pub(super) fn enclosing_item(lines: &[&str], target: usize) -> String { (0..=target) .rev() .find_map(|index| extract_item_name(lines[index])) diff --git a/crates/aft/tests/list_envelope_drift_guard.rs b/crates/aft/tests/list_envelope_drift_guard.rs index 05e2c13cc8..623a44eebc 100644 --- a/crates/aft/tests/list_envelope_drift_guard.rs +++ b/crates/aft/tests/list_envelope_drift_guard.rs @@ -5,6 +5,8 @@ use aft::list_envelope::{render_trailer, ListEnvelope, Reason, Total, Unit}; use aft::list_surfaces::{ExclusionEntry, EXCLUSIONS, LIST_SURFACES}; use serde_json::Value; +use crate::list_cut_item::extract_item_name; + /// Returns the path to `crates/aft/src`. fn aft_src_dir() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("src") @@ -149,70 +151,7 @@ pub struct DiscoveredCut { pub snippet: String, } -fn extract_item_name(line: &str) -> Option { - let trimmed = line.trim(); - - // Check fn - if let Some(rest) = trimmed - .strip_prefix("pub(crate) fn ") - .or_else(|| trimmed.strip_prefix("pub fn ")) - .or_else(|| trimmed.strip_prefix("fn ")) - .or_else(|| trimmed.strip_prefix("async fn ")) - .or_else(|| trimmed.strip_prefix("pub async fn ")) - .or_else(|| trimmed.strip_prefix("pub(crate) async fn ")) - { - let name: String = rest - .chars() - .take_while(|c| c.is_alphanumeric() || *c == '_') - .collect(); - if !name.is_empty() { - return Some(name); - } - } - - // Check const / static - if let Some(rest) = trimmed - .strip_prefix("pub(crate) const ") - .or_else(|| trimmed.strip_prefix("pub const ")) - .or_else(|| trimmed.strip_prefix("const ")) - .or_else(|| { - trimmed - .strip_prefix("pub(crate) static ") - .or_else(|| trimmed.strip_prefix("pub static ")) - .or_else(|| trimmed.strip_prefix("static ")) - }) - { - let name: String = rest - .chars() - .take_while(|c| c.is_alphanumeric() || *c == '_') - .collect(); - if !name.is_empty() { - return Some(name); - } - } - - // Check struct / enum - if let Some(rest) = trimmed - .strip_prefix("pub(crate) struct ") - .or_else(|| trimmed.strip_prefix("pub struct ")) - .or_else(|| trimmed.strip_prefix("struct ")) - .or_else(|| trimmed.strip_prefix("pub(crate) enum ")) - .or_else(|| trimmed.strip_prefix("pub enum ")) - .or_else(|| trimmed.strip_prefix("enum ")) - { - let name: String = rest - .chars() - .take_while(|c| c.is_alphanumeric() || *c == '_') - .collect(); - if !name.is_empty() { - return Some(name); - } - } - - None -} - -fn find_enclosing_item(lines: &[&str], target_idx: usize) -> String { +pub(crate) fn find_enclosing_item(lines: &[&str], target_idx: usize) -> String { for i in (0..=target_idx).rev() { if let Some(name) = extract_item_name(lines[i]) { return name;