use std::collections::{BTreeMap, HashMap, HashSet};
use std::path::Path;
use crate::edit_check::{BaseSource, EditCheck, EditSite, ShapeChange};
use crate::models::{
BlastRadiusResult, CallingTest, ConstructionSite, ContextSlice, ImpactedSymbol, LabeledSite,
OverrideRow, ReferenceReport, ReferenceSite, Registration, SearchExplain, SiteLabel, Symbol,
SymbolSearchResult, VariantUse,
};
pub fn format_file_skeleton(
file_path: &str,
symbols: &[Symbol],
line_count: Option<usize>,
parse_errors: usize,
) -> String {
let mut out = String::new();
let lines_str = match line_count {
Some(c) => format!(" (Lines 1-{c})"),
None => String::new(),
};
out.push_str(&format!("// File: {file_path}{lines_str}\n\n"));
if parse_errors > 0 {
let noun = if parse_errors == 1 { "error" } else { "errors" };
out.push_str(&format!(
"// {parse_errors} parse {noun}: symbols may be incomplete\n\n"
));
}
if symbols.is_empty() {
out.push_str("// No exported symbols indexed.\n");
return out;
}
let in_file: HashSet<&str> = symbols.iter().map(|s| s.symbol_id.as_str()).collect();
let mut children_map: HashMap<Option<String>, Vec<&Symbol>> = HashMap::new();
for s in symbols {
let parent = s
.parent_symbol_id
.clone()
.filter(|parent| in_file.contains(parent.as_str()));
children_map.entry(parent).or_default().push(s);
}
if let Some(roots) = children_map.get(&None) {
for root in roots {
render_symbol_skeleton(&mut out, root, None, &children_map, 0);
}
} else {
for s in symbols {
render_symbol_skeleton(&mut out, s, None, &children_map, 0);
}
}
out
}
fn is_container_kind(kind: &str) -> bool {
let hides_its_body = matches!(
kind,
"function" | "method" | "constructor" | "destructor" | "operator"
);
!is_skippable_kind(kind) && !hides_its_body
}
pub(crate) fn display_kind(sym: &Symbol) -> &str {
match (sym.language.as_str(), sym.kind.as_str()) {
("html", "class") => "element",
("sql", "class") => "table",
("markdown", "module") => "section",
("markdown", "import") => "link",
(language, "property")
if language != "fsharp"
&& sym.signature.as_deref().is_some_and(|sig| {
["self.", "this.", "cls."]
.iter()
.any(|p| sig.starts_with(p))
}) =>
{
"attribute"
}
(_, kind) => kind,
}
}
fn is_skippable_kind(kind: &str) -> bool {
matches!(kind, "variable" | "parameter" | "import")
}
fn value_row_signature(sig: &str) -> std::borrow::Cow<'_, str> {
let sig = sig.trim_end().trim_end_matches(';').trim_end();
if !sig.contains('\n') && sig.chars().count() <= 120 {
return sig.into();
}
let joined = one_line(sig);
let Some(equals) = value_equals(&joined) else {
if joined.chars().count() <= 120 {
return joined.into();
}
let cut: String = joined.chars().take(119).collect();
return format!("{}…", cut.trim_end()).into();
};
let (declaration, value) = joined.split_at(equals);
let budget = 120usize.saturating_sub(declaration.chars().count()).max(40);
if value.chars().count() <= budget {
return joined.into();
}
let cut: String = value.chars().take(budget - 1).collect();
format!("{declaration}{}…", cut.trim_end()).into()
}
fn one_line(sig: &str) -> String {
let mut joined = sig
.lines()
.map(|line| {
let code = [" # ", " // "]
.iter()
.filter_map(|marker| line.find(marker))
.min()
.map_or(line, |at| &line[..at]);
code.trim().trim_end_matches('\\').trim_end()
})
.filter(|line| !line.is_empty())
.collect::<Vec<_>>()
.join(" ");
for (from, to) in [
("( ", "("),
("[ ", "["),
("{ ", "{"),
(", )", ")"),
(", ]", "]"),
(", }", "}"),
(" )", ")"),
(" ]", "]"),
(" }", "}"),
] {
joined = joined.replace(from, to);
}
joined
}
fn assigns_a_value(sig: &str) -> bool {
value_equals(sig).is_some()
}
fn value_equals(sig: &str) -> Option<usize> {
let bytes = sig.as_bytes();
let mut depth = 0usize;
for (i, &byte) in bytes.iter().enumerate() {
match byte {
b'(' | b'[' => depth += 1,
b')' | b']' => depth = depth.saturating_sub(1),
b'{' if depth == 0 => return None,
b'=' if depth == 0 => {
let before = i.checked_sub(1).map(|j| bytes[j]);
let after = bytes.get(i + 1).copied();
let operator_before = before.is_some_and(|b| b"<>!=^*~|$+-/%&:?".contains(&b));
let operator_after = after.is_some_and(|b| b == b'=' || b == b'>');
if !operator_before && !operator_after {
return Some(i);
}
}
_ => {}
}
}
None
}
fn sanitize_skeleton_sig<'a>(sig: &'a str, sym: &'a Symbol) -> std::borrow::Cow<'a, str> {
if matches!(
sym.kind.as_str(),
"variable" | "constant" | "property" | "field" | "enum_member"
) && assigns_a_value(sig)
{
return value_row_signature(sig);
}
container_signature(sig, sym)
}
fn container_signature<'a>(sig: &'a str, sym: &'a Symbol) -> std::borrow::Cow<'a, str> {
let expression_arrow = if sym.language == "csharp"
&& matches!(
sym.kind.as_str(),
"method" | "function" | "constructor" | "operator"
) {
sig.match_indices("=>")
.find_map(|(idx, _)| sig[..idx].trim_end().ends_with(')').then_some(idx))
} else {
None
};
let cutoff = sig
.find('{')
.into_iter()
.chain(expression_arrow)
.min()
.unwrap_or(sig.len());
let clean = sig[..cutoff].trim_end();
let trimmed = clean.trim_end_matches(';').trim_end();
if trimmed.is_empty() {
sym.name.as_str().into()
} else {
trimmed.into()
}
}
fn signature_without_duplicate_body<'a>(signature: &'a str, body: &str) -> &'a str {
let body = body.trim();
if body.is_empty() {
return signature;
}
let trimmed_signature = signature.trim_end();
trimmed_signature
.strip_suffix(body)
.map(str::trim_end)
.unwrap_or(signature)
}
fn unspelled_kind(sym: &Symbol) -> &'static str {
if sym.kind != "event" {
return "";
}
let spelled = sym.signature.as_deref().is_some_and(|sig| {
sig.split_whitespace()
.any(|word| word == "signal" || word == "event")
});
if spelled { "" } else { "event " }
}
fn assigning_method(
sym: &Symbol,
parent: Option<&Symbol>,
children_map: &HashMap<Option<String>, Vec<&Symbol>>,
) -> String {
if !sym
.signature
.as_deref()
.is_some_and(|sig| sig.starts_with("self."))
{
return String::new();
}
parent
.and_then(|parent| children_map.get(&Some(parent.symbol_id.clone())))
.and_then(|siblings| {
siblings.iter().find(|method| {
matches!(method.kind.as_str(), "method" | "function" | "constructor")
&& method.start_line <= sym.start_line
&& sym.end_line <= method.end_line
})
})
.map_or_else(String::new, |method| format!("in {}(), ", method.name))
}
fn is_shown(sym: &Symbol, parent: Option<&Symbol>) -> bool {
let python_declaration = sym.kind == "variable"
&& sym.language == "python"
&& parent.is_none_or(|parent| parent.kind == "class");
!is_skippable_kind(&sym.kind) || python_declaration
}
fn is_misparsed_c_function(child: &Symbol) -> bool {
matches!(child.language.as_str(), "c" | "cpp")
&& matches!(child.kind.as_str(), "function" | "method")
&& !child.is_test
&& !child.test_container
}
fn nested_definitions(children: &[&Symbol]) -> String {
let names: Vec<String> = children
.iter()
.filter(|child| {
matches!(
child.kind.as_str(),
"function" | "method" | "class" | "struct"
)
})
.filter(|child| {
!child
.signature
.as_deref()
.is_some_and(|sig| sig.starts_with("lambda"))
})
.filter(|child| !is_misparsed_c_function(child))
.map(|child| format!("`{}`", child.name))
.fold(Vec::new(), |mut names, name| {
if !names.contains(&name) {
names.push(name);
}
names
});
match names.len() {
0 => String::new(),
1..=5 => format!("; defines {}", names.join(", ")),
n => format!("; defines {}, +{} more", names[..5].join(", "), n - 5),
}
}
fn render_symbol_skeleton(
out: &mut String,
sym: &Symbol,
parent: Option<&Symbol>,
children_map: &HashMap<Option<String>, Vec<&Symbol>>,
indent_level: usize,
) {
if !is_shown(sym, parent) {
return;
}
let indent = " ".repeat(indent_level);
if let Some(ref doc) = sym.doc_comment {
let document = crate::queries::DOCUMENTATION_LANGUAGES.contains(&sym.language.as_str());
let lines: Vec<_> = if document {
doc.lines().collect()
} else {
doc.lines()
.map(doc_line_text)
.filter(|line| !line.is_empty())
.collect()
};
let cap = 3;
for line in lines.iter().take(cap) {
out.push_str(&format!("{indent}/// {line}\n"));
}
if lines.len() > cap {
out.push_str(&format!(
"{indent}/// ... ({} more lines)\n",
lines.len() - cap
));
}
}
let span_str = format!("L{}-{}", sym.start_line, sym.end_line);
let leaf_note = format!(
"{}{}{span_str}",
unspelled_kind(sym),
assigning_method(sym, parent, children_map)
);
let children = children_map.get(&Some(sym.symbol_id.clone()));
let spans_multiple_lines = sym.end_line > sym.start_line;
if is_container_kind(&sym.kind)
&& children.is_some_and(|list| list.iter().any(|child| is_shown(child, Some(sym))))
&& (spans_multiple_lines || sym.kind == "enum")
{
let raw_sig = sym.signature.as_deref().unwrap_or(&sym.name);
let sig = container_signature(raw_sig, sym);
out.push_str(&format!("{indent}{sig} {{\n"));
if let Some(child_list) = children {
for child in child_list {
render_symbol_skeleton(out, child, Some(sym), children_map, indent_level + 1);
}
}
out.push_str(&format!("{indent}}} // {span_str}\n\n"));
} else {
if let Some(count) = sym.hidden_body_line_count() {
let raw_sig = sym.signature.as_deref().unwrap_or(&sym.name);
let sig = if count > 1 {
container_signature(raw_sig, sym)
} else {
sanitize_skeleton_sig(raw_sig, sym)
};
let b_start = sym.body_start_line.unwrap_or(sym.start_line);
let b_end = sym.body_end_line.unwrap_or(sym.end_line);
if count > 1 {
let defines = nested_definitions(children.map(Vec::as_slice).unwrap_or_default());
out.push_str(&format!(
"{indent}{sig} {{ /* {count} lines hidden: L{b_start}-L{b_end}{defines} */ }}\n"
));
} else {
out.push_str(&format!("{indent}{sig}; // {leaf_note}\n"));
}
} else if let Some(ref raw_sig) = sym.signature {
let sig = sanitize_skeleton_sig(raw_sig, sym);
out.push_str(&format!("{indent}{sig}; // {leaf_note}\n"));
} else {
out.push_str(&format!(
"{indent}{} {sym_name}; // {span_str}\n",
display_kind(sym),
sym_name = sym.name
));
}
if sym.kind == "namespace"
&& let Some(child_list) = children
{
for child in child_list {
render_symbol_skeleton(out, child, Some(sym), children_map, indent_level);
}
}
}
}
#[derive(Default)]
pub struct OutlineNode {
pub files: BTreeMap<String, Vec<String>>, pub subdirs: BTreeMap<String, OutlineNode>,
pub hidden_files: usize,
pub plain_files: Vec<String>,
pub unsupported_files: usize,
}
fn outline_components(file_path: &str, norm_filter: &str) -> Option<Vec<String>> {
let normalized = file_path.replace('\\', "/");
let rel_path_str = if norm_filter.is_empty() {
normalized.as_str()
} else if normalized.eq_ignore_ascii_case(norm_filter) {
Path::new(&normalized)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(&normalized)
} else if normalized.len() > norm_filter.len()
&& normalized.as_bytes()[norm_filter.len()] == b'/'
&& normalized[..norm_filter.len()].eq_ignore_ascii_case(norm_filter)
{
&normalized[norm_filter.len() + 1..]
} else {
return None;
};
let path = Path::new(rel_path_str);
let components: Vec<String> = path
.components()
.map(|c| c.as_os_str().to_str().unwrap_or(""))
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
if components.is_empty() {
return None;
}
Some(components)
}
pub fn add_unsupported_to_outline(
root_node: &mut OutlineNode,
file_path: &str,
max_depth: usize,
norm_filter: &str,
) {
let Some(components) = outline_components(file_path, norm_filter) else {
return;
};
let mut curr = root_node;
for (i, comp) in components
.iter()
.take(components.len() - 1)
.take(max_depth)
.enumerate()
{
if i > 0 && !curr.subdirs.contains_key(comp) {
break;
}
curr = curr.subdirs.entry(comp.clone()).or_default();
}
curr.unsupported_files += 1;
}
pub fn add_path_to_outline(
root_node: &mut OutlineNode,
file_path: &str,
symbols_by_file: &HashMap<String, Vec<Symbol>>,
counts: &HashMap<String, crate::queries::OutlineCounts>,
max_depth: usize,
norm_filter: &str,
) {
let Some(components) = outline_components(file_path, norm_filter) else {
return;
};
let normalized = file_path.replace('\\', "/");
let mut curr = root_node;
let depth = components.len();
for (i, comp) in components.iter().enumerate() {
if i == depth - 1 {
if depth > max_depth {
curr.hidden_files += 1;
} else {
let mut sym_tags: Vec<String> = symbols_by_file
.get(&normalized)
.into_iter()
.flatten()
.filter(|s| !s.is_test && !s.test_container)
.take(5)
.map(|s| format!("{} {}", display_kind(s), s.name))
.collect();
let count = counts.get(&normalized).copied().unwrap_or_default();
if count.definitions > sym_tags.len() {
sym_tags.push(format!("+{} more", count.definitions - sym_tags.len()));
}
if count.tests > 0 {
sym_tags.push(format!("{} {}", count.tests, plural(count.tests, "test")));
}
if count.fixtures > 0 {
sym_tags.push(format!(
"{} {}",
count.fixtures,
plural(count.fixtures, "fixture")
));
}
if sym_tags.is_empty() {
curr.plain_files.push(comp.to_string());
} else {
curr.files.insert(comp.to_string(), sym_tags);
}
}
} else if i < max_depth {
curr = curr.subdirs.entry(comp.to_string()).or_default();
} else {
curr.hidden_files += 1;
break;
}
}
}
const FILES_LISTED_PER_FOLDER: usize = 40;
pub fn render_outline_tree(
out: &mut String,
node: &OutlineNode,
prefix: &str,
depth: usize,
max_depth: usize,
) {
if depth >= max_depth {
return;
}
let listed = if depth == 0 {
node.files.len()
} else {
node.files.len().min(FILES_LISTED_PER_FOLDER)
};
let more_files = node.files.len() - listed;
let total_items = node.subdirs.len()
+ listed
+ usize::from(more_files > 0)
+ usize::from(!node.plain_files.is_empty());
let mut index = 0;
for (name, sub) in &node.subdirs {
index += 1;
let is_last = index == total_items;
let branch = if is_last { "└── " } else { "├── " };
let next_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
let hidden = match (sub.hidden_files, sub.unsupported_files) {
(0, 0) => String::new(),
(0, m) => format!(" ({m} unsupported {})", plural(m, "file")),
(n, 0) => format!(" ({n} indexed {})", plural(n, "file")),
(n, m) => format!(" ({n} indexed {}, {m} unsupported)", plural(n, "file")),
};
out.push_str(&format!("{prefix}{branch}{name}/{hidden}\n"));
render_outline_tree(out, sub, &next_prefix, depth + 1, max_depth);
}
for (file_name, syms) in node.files.iter().take(listed) {
index += 1;
let is_last = index == total_items;
let branch = if is_last { "└── " } else { "├── " };
let sym_suffix = if !syms.is_empty() {
format!(" [{}]", syms.join(", "))
} else {
String::new()
};
out.push_str(&format!("{prefix}{branch}{file_name}{sym_suffix}\n"));
}
if more_files > 0 {
index += 1;
let branch = if index == total_items {
"└── "
} else {
"├── "
};
out.push_str(&format!(
"{prefix}{branch}(+{more_files} more {} with functions or classes; pass this folder as the path to list them)\n",
plural(more_files, "file")
));
}
if !node.plain_files.is_empty() {
let count = node.plain_files.len();
let names = if count <= 5 {
node.plain_files.join(", ")
} else {
format!("{}, +{} more", node.plain_files[..3].join(", "), count - 3)
};
out.push_str(&format!(
"{prefix}└── ({count} {} without functions or classes: {names})\n",
plural(count, "file")
));
}
}
pub fn format_symbol_body(symbol: &Symbol, source: &str) -> String {
let mut out = format!(
"// {}:{}-{} ({})\n{source}",
symbol.path, symbol.start_line, symbol.end_line, symbol.name
);
if !source.ends_with('\n') {
out.push('\n');
}
out
}
pub fn format_symbol_bodies(bodies: &[(Symbol, String)]) -> String {
let mut out = overloads_header(bodies.iter().map(|(symbol, _)| symbol));
let parts: Vec<String> = bodies
.iter()
.map(|(symbol, source)| format_symbol_body(symbol, source))
.collect();
out.push_str(&parts.join("\n"));
out
}
pub fn format_context_slices(slices: &[ContextSlice]) -> String {
let mut out = overloads_header(slices.iter().map(|slice| &slice.target_symbol));
let parts: Vec<String> = slices.iter().map(format_context_slice).collect();
out.push_str(&parts.join("\n"));
out
}
fn overloads_header<'a>(symbols: impl ExactSizeIterator<Item = &'a Symbol>) -> String {
let count = symbols.len();
let mut symbols = symbols;
match symbols.next() {
Some(first) if count > 1 => format!(
"`{}` has {count} overloads in {}; each one follows.\n\n",
first.name, first.path
),
_ => String::new(),
}
}
pub fn format_context_slice(slice: &ContextSlice) -> String {
let sym = &slice.target_symbol;
let mut out = String::new();
out.push_str(&format!(
"### Target: `{}` ({}:{}-{})\n\n",
sym.name, sym.path, sym.start_line, sym.end_line
));
if let Some(ref sig) = sym.signature {
let sig = signature_without_duplicate_body(sig, &slice.target_body);
if !sig.trim().is_empty() {
out.push_str(&format!("Signature: `{sig}`\n\n"));
}
}
out.push_str(&format!("```{}\n", sym.language));
out.push_str(&slice.target_body);
if !slice.target_body.ends_with('\n') {
out.push('\n');
}
out.push_str("```\n\n");
if !slice.callee_signatures.is_empty() {
out.push_str("### Dependencies (Signatures):\n");
for callee in &slice.callee_signatures {
out.push_str(&format!("- {callee}\n"));
}
if slice.callee_signatures.len() >= 10 {
out.push_str("[Showing 10 dependencies (limit reached)]\n");
}
out.push('\n');
}
if !slice.related_types.is_empty() {
out.push_str("### Types:\n");
for t in &slice.related_types {
out.push_str(&format!("- {t}\n"));
}
out.push('\n');
}
if !slice.related_tests.is_empty() {
out.push_str("### Related Tests:\n");
for test in &slice.related_tests {
out.push_str(&format!(
"- `{}` ({}:{})\n",
test.name, test.path, test.start_line
));
}
if slice.related_tests.len() >= 5 {
out.push_str("[Showing 5 tests (limit reached)]\n");
}
out.push_str("These tests call, use, or name this symbol; blast_radius also lists tests that can reach it through callers.\n\n");
} else {
out.push_str(
"### Related Tests:\nNo test calls, uses, or names this symbol; blast_radius lists tests that can reach it through callers.\n",
);
}
out
}
fn cap_notice(shown: usize, limit: usize) -> String {
let advice = if limit >= crate::queries::MAX_RESULT_LIMIT {
"narrow the query to see more"
} else {
"increase limit to see more"
};
format!("\n[Showing {shown} results (limit reached); {advice}.]\n")
}
fn match_line(snippet: &str, signature: &str) -> Option<String> {
let line = snippet
.lines()
.enumerate()
.map(|(i, text)| match i {
0 => text.trim(),
_ => text.trim().trim_start_matches(['/', '*', '!']).trim(),
})
.flat_map(str::split_whitespace)
.collect::<Vec<_>>()
.join(" ");
let plain = |text: &str| {
text.split_whitespace()
.collect::<String>()
.replace(['[', ']'], "")
};
let matched = plain(line.trim_start_matches("...").trim_end_matches("..."));
(!line.is_empty() && !plain(signature).contains(&matched)).then_some(line)
}
fn callee_name(name: &str) -> String {
let line = name.split_whitespace().collect::<Vec<_>>().join(" ");
if line.chars().count() <= 60 || !line.contains('(') {
return line;
}
match line.rsplit_once('.') {
Some((_, last)) if !last.is_empty() => format!("….{last}"),
_ => line,
}
}
pub fn format_import_summary(sites: &[(String, usize)]) -> String {
if sites.is_empty() {
return String::new();
}
let files: std::collections::BTreeSet<&str> =
sites.iter().map(|(path, _)| path.as_str()).collect();
let first: Vec<String> = sites
.iter()
.take(3)
.map(|(path, line)| format!("{path}:{line}"))
.collect();
let more = if sites.len() > 3 { ", …" } else { "" };
format!(
"Imported {} {} in {} {}: {}{more} (lookup_symbol with kind=\"import\" lists them)\n",
sites.len(),
plural(sites.len(), "time"),
files.len(),
plural(files.len(), "file"),
first.join(", ")
)
}
pub fn format_references(
target_name: &str,
refs: &[ReferenceSite],
direction: &str,
limit: usize,
) -> String {
let mut out = String::new();
let dir_label = if direction == "callers" {
"Callers of"
} else {
"Callees called by"
};
out.push_str(&format!(
"{dir_label} `{target_name}` ({} found):\n",
refs.len()
));
if refs.is_empty() {
out.push_str(" (none)\n");
return out;
}
for r in refs {
let line_info = match r.start_line {
Some(l) => format!(":{l}"),
None => String::new(),
};
let other = if direction == "callers" {
caller_text(r)
} else {
format!("`{}`", callee_name(&r.to_symbol_name))
};
let in_file = match r.occurrences {
Some(n) => format!(", {n} in file"),
None => String::new(),
};
let target = match &r.target {
Some(target) if direction != "callers" => format!(" → {target}"),
_ => String::new(),
};
out.push_str(&format!(
"- {other} [{}{line_info}] (kind: {}{in_file}){target}\n",
r.path, r.kind
));
}
if refs.len() >= limit {
out.push_str(&cap_notice(refs.len(), limit));
}
out
}
fn caller_text(site: &ReferenceSite) -> String {
if site.from_symbol_name.is_empty() && site.enclosing_symbol_name.is_none() {
"(no enclosing symbol)".to_string()
} else {
format!(
"`{}`",
displayed_symbol_name(
&site.from_symbol_name,
site.enclosing_symbol_name.as_deref()
)
)
}
}
const FOLD_FILES_NAMED: usize = 5;
const TEXT_ONLY_FILES_LISTED: usize = 10;
const TEXT_ONLY_LINES_PER_FILE: usize = 12;
pub fn format_reference_report(report: &ReferenceReport, limit: usize) -> String {
let target = &report.target;
let counts = &report.counts;
let mut out = format!(
"References to {} `{}` ({}:{})\nSites: {} · proven {} · likely {} · unresolved {} · excluded {}\n",
target.kind,
target.display_name,
target.path,
target.line,
counts.sites,
counts.proven,
counts.likely,
counts.unresolved,
counts.excluded,
);
out.push_str(&text_count_line(report));
let scan = &report.text_scan;
if !scan.complete && scan.skipped.is_none() {
out.push_str(&format!(
"Text scan stopped after {} s: {} of {} files read; counts cover those files.\n",
(scan.elapsed_ms + 500) / 1000,
scan.files_scanned,
scan.files_total
));
}
let mut body = site_groups(&report.sites, limit);
body.push_str(&override_sections(report, limit));
body.push_str(&construction_section(&report.constructions, limit));
body.push_str(&variant_section(&report.variant_uses, limit));
body.push_str(®istration_section(&report.registrations, limit));
body.push_str(&test_section(&report.tests, limit));
body.push_str(&text_only_section(report));
if !report.cannot_see.is_empty() {
body.push_str(&format!(
"Not visible to this index: {}.\n",
report.cannot_see.join("; ")
));
}
body.push_str(&format_import_summary(&report.imports));
if !body.is_empty() {
out.push('\n');
out.push_str(&body);
}
out
}
fn text_count_line(report: &ReferenceReport) -> String {
if let Some(class) = &report.text_scan.skipped {
return format!(
"Text: skipped for a constructor; its call sites spell the class name. Use find_references on `{class}`.\n"
);
}
let counts = &report.counts;
format!(
"Text: `{}` is on {} {}: {} {}, {} {}, {} {}, {} text only.\n",
report.target.name,
counts.text_lines,
plural(counts.text_lines, "line"),
counts.site_lines,
plural(counts.site_lines, "site"),
counts.definition_lines,
plural(counts.definition_lines, "definition"),
counts.import_lines,
plural(counts.import_lines, "import"),
counts.text_only_lines,
)
}
fn site_groups(sites: &[LabeledSite], limit: usize) -> String {
let mut out = String::new();
let mut listed = 0;
for label in SiteLabel::ALL {
let group: Vec<&LabeledSite> = sites.iter().filter(|site| site.label == label).collect();
if group.is_empty() {
continue;
}
let title = match label {
SiteLabel::Proven => "Proven",
SiteLabel::Likely => "Likely",
SiteLabel::Unresolved => "Unresolved",
SiteLabel::Excluded => "Excluded",
};
out.push_str(&format!("{title} ({}):\n", group.len()));
let shown = limit.saturating_sub(listed).min(group.len());
for labeled in &group[..shown] {
let site = &labeled.site;
let line = site.start_line.map(|l| format!(":{l}")).unwrap_or_default();
out.push_str(&format!(
"- {} [{}{line}] (kind: {}; {}{})\n",
caller_text(site),
site.path,
site.kind,
labeled.basis,
arg_count_text(labeled)
));
}
listed += shown;
if shown < group.len() {
out.push_str(&fold_line(label, &group[shown..]));
}
}
out
}
fn arg_count_text(labeled: &LabeledSite) -> String {
match labeled.arg_count {
Some(count) => {
let noun = if count == 1 { "arg" } else { "args" };
let text = format!("{count} {noun}");
if labeled.basis.starts_with(&text) {
String::new()
} else {
format!("; {text}")
}
}
None => String::new(),
}
}
fn fold_line(label: SiteLabel, hidden: &[&LabeledSite]) -> String {
let (files, named) = files_by_count(hidden.iter().map(|labeled| labeled.site.path.as_str()));
format!(
"… {} more {} {} in {files} {}: {named}\n",
hidden.len(),
label.as_str(),
plural(hidden.len(), "site"),
plural(files, "file"),
)
}
fn files_by_count<'a>(paths: impl Iterator<Item = &'a str>) -> (usize, String) {
let mut per_file: BTreeMap<&str, usize> = BTreeMap::new();
for path in paths {
*per_file.entry(path).or_default() += 1;
}
let mut files: Vec<(&str, usize)> = per_file.into_iter().collect();
files.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(b.0)));
let named: Vec<String> = files
.iter()
.take(FOLD_FILES_NAMED)
.map(|(path, count)| format!("{path} ({count})"))
.collect();
let more = if files.len() > FOLD_FILES_NAMED {
", …"
} else {
""
};
(files.len(), format!("{}{more}", named.join(", ")))
}
fn override_sections(report: &ReferenceReport, limit: usize) -> String {
let name = &report.target.display_name;
let mut out = method_section(
&format!(
"Overrides ({}): calls through `{name}` can reach these",
report.overrides.len()
),
&report.overrides,
limit,
);
out.push_str(&method_section(
&format!(
"Overrides of ({}): calls to these can reach `{name}`",
report.overridden.len()
),
&report.overridden,
limit,
));
out
}
fn construction_section(rows: &[ConstructionSite], limit: usize) -> String {
if rows.is_empty() {
return String::new();
}
let shown = limit.min(rows.len());
let mut per_file: Vec<(&str, Vec<String>)> = Vec::new();
for row in &rows[..shown] {
match per_file.last_mut() {
Some((path, lines)) if *path == row.path => lines.push(row.line.to_string()),
_ => per_file.push((&row.path, vec![row.line.to_string()])),
}
}
let listed: Vec<String> = per_file
.iter()
.map(|(path, lines)| format!("{path}: {}", lines.join(", ")))
.collect();
let mut out = format!("Constructions ({}):", rows.len());
if !listed.is_empty() {
out.push_str(&format!(" {}", listed.join(" · ")));
}
out.push('\n');
if shown < rows.len() {
out.push_str(§ion_fold(
rows[shown..].iter().map(|row| row.path.as_str()),
"construction",
));
}
out
}
fn variant_section(rows: &[VariantUse], limit: usize) -> String {
if rows.is_empty() {
return String::new();
}
let mut out = format!(
"Variant uses ({} {}): a match in them may need a new arm\n",
rows.len(),
plural(rows.len(), "function")
);
let shown = limit.min(rows.len());
for row in &rows[..shown] {
let caller = if row.caller.is_empty() {
"(no enclosing symbol)".to_string()
} else {
format!("`{}`", row.caller)
};
out.push_str(&format!(
"- {caller} [{}:{}] names {} of {} {}\n",
row.path,
row.line,
row.variants_used,
row.variants_total,
plural(row.variants_total, "variant"),
));
}
if shown < rows.len() {
out.push_str(§ion_fold(
rows[shown..].iter().map(|row| row.path.as_str()),
"function",
));
}
out
}
fn registration_section(rows: &[Registration], limit: usize) -> String {
if rows.is_empty() {
return String::new();
}
let mut out = format!("Registrations ({}):\n", rows.len());
let shown = limit.min(rows.len());
for row in &rows[..shown] {
out.push_str(&format!(
"- {} [{}:{}]{}\n",
registration_label(row),
row.path,
row.line,
if row.by_name { " (by name)" } else { "" }
));
}
if shown < rows.len() {
out.push_str(§ion_fold(
rows[shown..].iter().map(|row| row.path.as_str()),
"registration",
));
}
out
}
fn registration_label(row: &Registration) -> String {
if !row.pattern_id.contains(".route.") {
return row.pattern_id.clone();
}
let template = row.template.as_deref().unwrap_or(&row.pattern_id);
match row.verb.as_deref() {
Some(verb) => format!("route {verb} {template}"),
None => format!("route {template}"),
}
}
fn section_fold<'a>(hidden: impl ExactSizeIterator<Item = &'a str>, word: &str) -> String {
let count = hidden.len();
let (files, named) = files_by_count(hidden);
format!(
"… {count} more {} in {files} {}: {named}\n",
plural(count, word),
plural(files, "file"),
)
}
fn test_section(rows: &[CallingTest], limit: usize) -> String {
if rows.is_empty() {
return String::new();
}
let mut out = format!("Tests that call it ({}):\n", rows.len());
let shown = limit.min(rows.len());
for row in &rows[..shown] {
let run = row
.command
.as_ref()
.map(|command| format!(" run: {command}"))
.unwrap_or_default();
let name_only = if row.name_only { " (name only)" } else { "" };
out.push_str(&format!(
"- `{}` [{}:{}]{name_only}{run}\n",
on_one_line(&row.name),
row.path,
row.line
));
}
if shown < rows.len() {
out.push_str(§ion_fold(
rows[shown..].iter().map(|row| row.path.as_str()),
"test",
));
}
out
}
fn on_one_line(text: &str) -> String {
text.chars()
.map(|c| {
if c.is_control() || matches!(c, '\u{2028}' | '\u{2029}') {
c.escape_default().to_string()
} else {
c.to_string()
}
})
.collect()
}
fn method_section(title: &str, rows: &[OverrideRow], limit: usize) -> String {
if rows.is_empty() {
return String::new();
}
let mut out = format!("{title}\n");
let shown = limit.min(rows.len());
for row in &rows[..shown] {
out.push_str(&format!(
"- `{}.{}` ({}:{})\n",
row.owner, row.name, row.path, row.line
));
}
if shown < rows.len() {
out.push_str(§ion_fold(
rows[shown..].iter().map(|row| row.path.as_str()),
"method",
));
}
out
}
fn text_only_section(report: &ReferenceReport) -> String {
if report.text_only.is_empty() {
return String::new();
}
let mut out = format!(
"Text only ({}): docs, comments, strings, or code the index did not record\n",
report.counts.text_only_lines
);
let mut hidden_lines = 0;
let mut hidden_files = 0;
for (index, file) in report.text_only.iter().enumerate() {
let listed = if index < TEXT_ONLY_FILES_LISTED {
file.lines.len().min(TEXT_ONLY_LINES_PER_FILE)
} else {
0
};
if listed > 0 {
let lines: Vec<String> = file.lines[..listed].iter().map(usize::to_string).collect();
out.push_str(&format!(" {}: {}\n", file.path, lines.join(", ")));
}
if listed < file.lines.len() {
hidden_lines += file.lines.len() - listed;
hidden_files += 1;
}
}
if hidden_lines > 0 {
out.push_str(&format!(
" … {hidden_lines} more {} in {hidden_files} {}\n",
plural(hidden_lines, "line"),
plural(hidden_files, "file")
));
}
out
}
pub(crate) fn displayed_symbol_name(name: &str, enclosing_symbol_name: Option<&str>) -> String {
if let Some(enclosing_symbol_name) = enclosing_symbol_name {
format!("<lambda> in {enclosing_symbol_name}")
} else {
name.to_string()
}
}
fn other_names_line(query: &str, others: &[&Symbol]) -> String {
let mut groups: Vec<(String, &str, usize)> = Vec::new();
for s in others {
let kind = display_kind(s);
match groups
.iter_mut()
.find(|(name, k, _)| *name == s.name && *k == kind)
{
Some(group) => group.2 += 1,
None => groups.push((s.name.clone(), kind, 1)),
}
}
let listed: Vec<String> = groups
.iter()
.take(8)
.map(|(name, kind, count)| match count {
1 => format!("`{name}` ({kind})"),
n => format!("`{name}` ({kind}, {n}×)"),
})
.collect();
let more = match groups.len().saturating_sub(8) {
0 => String::new(),
n => format!(", +{n} more"),
};
format!(
"- {} other {} `{query}`, ignoring case: {}{more} (lookup_symbol with one of these names lists its rows)\n",
others.len(),
if others.len() == 1 {
"row starts with or contains"
} else {
"rows start with or contain"
},
listed.join(", ")
)
}
fn doc_line_text(line: &str) -> &str {
let mut text = line.trim();
for marker in [
"///", "//!", "//", "/**", "/*!", "/*", "##", "#'", "#", "--", "*", "'''", "'",
] {
if let Some(rest) = text.strip_prefix(marker) {
text = rest.trim_start();
break;
}
}
for tag in ["<summary>", "</summary>"] {
text = text.strip_prefix(tag).unwrap_or(text).trim_start();
}
for suffix in ["*/", "</summary>"] {
text = text.strip_suffix(suffix).unwrap_or(text).trim_end();
}
text
}
fn sentence_end(text: &str) -> Option<usize> {
let mut depth = 0usize;
for (index, c) in text.char_indices() {
match c {
'(' => depth += 1,
')' => depth = depth.saturating_sub(1),
'.' if depth == 0 => {
let rest = &text[index + 1..];
if rest.starts_with(' ') && !rest[1..].starts_with(|next: char| next.is_lowercase())
{
return Some(index);
}
}
_ => {}
}
}
None
}
fn doc_summary(doc: &str) -> Option<String> {
let paragraph = doc
.lines()
.map(doc_line_text)
.skip_while(|line| line.is_empty())
.take_while(|line| !line.is_empty())
.collect::<Vec<_>>()
.join(" ");
let summary = match sentence_end(¶graph) {
Some(end) => paragraph[..=end].to_string(),
None if paragraph.chars().count() > 200 => {
format!("{}…", paragraph.chars().take(200).collect::<String>())
}
None => paragraph,
};
(!summary.is_empty()).then_some(summary)
}
pub fn format_find_symbol_results(
query: &str,
name_rows: &[Symbol],
ranked_rows: &[SymbolSearchResult],
limit: usize,
) -> String {
let mut out = String::new();
if !name_rows.is_empty() {
out.push_str(&format_name_rows(query, name_rows, limit));
}
if !ranked_rows.is_empty() {
if !out.is_empty() {
out.push('\n');
}
out.push_str(&format_ranked_rows(query, ranked_rows, limit));
}
if out.is_empty() {
out.push_str(&format!("No symbols found matching \"{query}\".\n"));
}
out
}
fn format_name_rows(query: &str, exact_matches: &[Symbol], limit: usize) -> String {
let dotted = query.replace("::", ".");
let is_exact = |s: &Symbol| s.name == dotted || s.name.ends_with(&format!(".{dotted}"));
let named_exactly = exact_matches.iter().filter(|s| is_exact(s)).count();
let exact_note = if named_exactly < exact_matches.len() {
format!(" ({named_exactly} named exactly `{query}`; the rest start with or contain it)")
} else {
String::new()
};
let owner = query
.strip_suffix("::")
.or_else(|| query.strip_suffix('.'))
.filter(|owner| !owner.is_empty());
let mut out = match owner {
Some(owner) => format!("Found {} members of `{owner}`:\n\n", exact_matches.len()),
None => format!(
"Found {} symbols matching \"{query}\"{exact_note}:\n\n",
exact_matches.len()
),
};
let has_definition = exact_matches.iter().any(|s| s.kind != "import");
let folds = |s: &Symbol| crate::queries::folds_into_import_line(query, s, has_definition);
let imports: Vec<&Symbol> = exact_matches.iter().filter(|s| folds(s)).collect();
let (shown, others): (Vec<&Symbol>, Vec<&Symbol>) = exact_matches
.iter()
.filter(|s| !folds(s))
.partition(|s| named_exactly == 0 || is_exact(s));
for s in shown {
let sig = s.signature.as_deref().unwrap_or(&s.name);
out.push_str(&format!(
"- {} `{}` [{}:{}-{}] id={}\n",
display_kind(s),
s.name,
s.path,
s.start_line,
s.end_line,
s.symbol_id
));
out.push_str(&format!(" Signature: {sig}\n"));
if let Some(doc) = s.doc_comment.as_deref().and_then(doc_summary) {
out.push_str(&format!(" Doc: {doc}\n"));
}
}
if !imports.is_empty() {
let shown = imports
.iter()
.take(3)
.map(|s| format!("{}:{}", s.path, s.start_line))
.collect::<Vec<_>>()
.join(", ");
let more = if imports.len() > 3 { ", …" } else { "" };
let cut_inside_exact_names = exact_matches
.last()
.is_some_and(|s| s.name.eq_ignore_ascii_case(query) || folds(s));
let capped = if exact_matches.len() - imports.len() >= limit && cut_inside_exact_names {
"at least "
} else {
""
};
out.push_str(&format!(
"- {capped}{} {} of `{query}`: {shown}{more} (lookup_symbol with kind=\"import\" lists them)\n",
imports.len(),
plural(imports.len(), "import")
));
}
if !others.is_empty() {
out.push_str(&other_names_line(query, &others));
}
if exact_matches.len() - imports.len() >= limit {
out.push_str(&cap_notice(exact_matches.len() - imports.len(), limit));
}
out
}
fn format_ranked_rows(query: &str, fts_matches: &[SymbolSearchResult], limit: usize) -> String {
let mut out = format!(
"No exact name match; {} full-text matches for \"{query}\":\n\n",
fts_matches.len()
);
for r in fts_matches {
let s = &r.symbol;
let sig = s.signature.as_deref().unwrap_or(&s.name);
out.push_str(&format!(
"- {} `{}` [{}:{}-{}] (score: {:.2}) id={}\n",
display_kind(s),
s.name,
s.path,
s.start_line,
s.end_line,
r.score,
s.symbol_id
));
out.push_str(&format!(" Signature: {sig}\n"));
if let Some(line) = r.snippet.as_deref().and_then(|m| match_line(m, sig)) {
out.push_str(&format!(" Match: {line}\n"));
} else if let Some(doc) = s.doc_comment.as_deref().and_then(doc_summary) {
out.push_str(&format!(" Doc: {doc}\n"));
}
}
if fts_matches.len() >= limit {
out.push_str(&cap_notice(fts_matches.len(), limit));
}
out
}
pub fn no_facts_heading(category: &str, path_filter: Option<&str>) -> String {
match path_filter {
Some(path) => format!("No facts match '{category}' under `{path}`."),
None => format!("No facts match '{category}' in this repository."),
}
}
pub fn format_no_facts(
category: &str,
path_filter: Option<&str>,
categories: &[(String, usize)],
) -> String {
let mut out = format!("{}\n\n", no_facts_heading(category, path_filter));
if !crate::queries::is_category_alias(category) {
out.push_str(&format_fact_categories(categories));
return out;
}
if matches!(category.to_ascii_lowercase().as_str(), "import" | "imports") {
out.push_str("Most languages record imports as symbols: lookup_symbol with kind=\"import\" lists them.\n\n");
}
out.push_str(&alias_summary(categories));
out
}
fn alias_summary(categories: &[(String, usize)]) -> String {
let aliases = crate::queries::alias_fact_counts(categories);
if aliases.is_empty() {
return String::new();
}
let parts: Vec<String> = aliases
.iter()
.map(|(alias, patterns, facts)| {
format!(
"{alias} ({patterns} {}, {facts} {})",
plural(*patterns, "pattern"),
plural(*facts, "fact")
)
})
.collect();
format!("Aliases: {}\n\n", parts.join(", "))
}
pub fn format_fact_categories(categories: &[(String, usize)]) -> String {
if categories.is_empty() {
return "No structural facts or literals indexed in this repository.".to_string();
}
let mut out = String::new();
out.push_str(&alias_summary(categories));
let (patterns, literal_kinds): (Vec<_>, Vec<_>) = categories
.iter()
.filter(|(name, _)| !name.ends_with(".decorated_definition.v1"))
.partition(|(name, _)| name.contains('.'));
out.push_str(&format!(
"Available structural fact categories ({} found):\n\n",
patterns.len()
));
for (name, count) in &patterns {
out.push_str(&format!(
"- `{name}` ({count} {})\n",
plural(*count, "fact")
));
}
if !literal_kinds.is_empty() {
out.push_str("\nString literal kinds (the same command lists the literals of a kind):\n\n");
for (name, count) in &literal_kinds {
out.push_str(&format!(
"- `{name}` ({count} {})\n",
plural(*count, "literal")
));
}
}
out
}
fn plural(count: usize, word: &str) -> String {
if count == 1 {
word.to_string()
} else {
format!("{word}s")
}
}
pub fn format_structural_facts(
facts: &[crate::models::StructuralFact],
literals: &[crate::models::LiteralFact],
category: &str,
limit: usize,
) -> String {
let mut out = format!(
"Structural facts for '{category}' ({} found):\n",
facts.len()
);
for f in facts {
let label = f.key.as_deref().unwrap_or(&f.capture_name);
let details = format!("{}{}", qt_property_details(f), flask_route_details(f));
let api_style = f
.metadata
.as_ref()
.and_then(|m| m.get("api_style"))
.and_then(|v| v.as_str());
let place = match api_style {
Some("decorator_routing") => "handler",
Some("call_routing") => "registered in",
_ => "in",
};
let parent = f
.containing_symbol_name
.as_deref()
.map(|p| format!(", {place}: {p}"))
.unwrap_or_default();
out.push_str(&format!(
"- {label} [{}:{}] (pattern: {}{details}{parent})\n",
f.path, f.start_line, f.pattern_id
));
}
if limit > 0 && facts.len() >= limit {
out.push_str(&cap_notice(facts.len(), limit));
}
if facts
.iter()
.any(|f| f.pattern_id.starts_with("flask.route"))
{
out.push_str("Routes as declared in the source. By default Flask also answers HEAD for GET and OPTIONS for every rule, and serves `/static/<path:filename>`; the app or a rule can turn these off.\n");
}
if !literals.is_empty() {
out.push_str(&format!(
"\nMatching literals ({} found):\n",
literals.len()
));
for l in literals {
out.push_str(&format!(
"- \"{}\" [{}:{}] (kind: {})\n",
l.literal_text, l.path, l.start_line, l.kind
));
}
if limit > 0 && literals.len() >= limit {
out.push_str(&cap_notice(literals.len(), limit));
}
}
out
}
fn flask_route_details(fact: &crate::models::StructuralFact) -> String {
if !fact.pattern_id.starts_with("flask.route") {
return String::new();
}
let Some(metadata) = fact.metadata.as_ref() else {
return String::new();
};
let text = |key: &str| metadata.get(key).and_then(|v| v.as_str());
let mut out = String::new();
if let Some(endpoint) = text("endpoint") {
out.push_str(&format!(", endpoint: {endpoint}"));
}
if text("api_style") == Some("call_routing") {
match text("view_target") {
Some(view) => out.push_str(&format!(", view: {view}")),
None => out.push_str(", no view function"),
}
}
out
}
fn qt_property_details(fact: &crate::models::StructuralFact) -> String {
if fact.pattern_id != "cpp.qt_property.v1" {
return String::new();
}
let Some(metadata) = fact.metadata.as_ref() else {
return String::new();
};
[
"property_type",
"read",
"write",
"notify",
"designable",
"scriptable",
"stored",
"user",
"revision",
]
.into_iter()
.filter_map(|key| metadata.get(key).map(|value| (key, value)))
.map(|(key, value)| {
let value = value
.as_str()
.map(str::to_owned)
.unwrap_or_else(|| value.to_string());
format!(", {key}: {value}")
})
.collect()
}
pub fn format_search_results(query: &str, results: &[SymbolSearchResult], limit: usize) -> String {
if results.is_empty() {
return format!("No symbols found matching concept \"{query}\".");
}
let mut out = format!(
"Found {} symbols matching concept \"{query}\":\n",
results.len()
);
if let Some(explain) = results.first().and_then(|r| r.explain.as_ref()) {
out.push_str(&format!(
"rerank: {} candidates in {} µs",
explain.candidates, explain.rerank_us
));
if !explain.word_weights.is_empty() {
let words: Vec<String> = explain
.word_weights
.iter()
.map(|(word, weight)| format!("{word} {weight:.2}"))
.collect();
out.push_str(&format!("; words {}", words.join(", ")));
}
out.push('\n');
}
out.push('\n');
for r in results {
let s = &r.symbol;
let sig = s.signature.as_deref().unwrap_or(&s.name);
out.push_str(&format!(
"- {} `{}` [{}:{}-{}] (score: {:.2}) id={}\n",
display_kind(s),
s.name,
s.path,
s.start_line,
s.end_line,
r.score,
s.symbol_id
));
out.push_str(&format!(" Signature: {sig}\n"));
if let Some(line) = r.snippet.as_deref().and_then(|m| match_line(m, sig)) {
out.push_str(&format!(" Match: {line}\n"));
} else if let Some(doc) = s.doc_comment.as_deref().and_then(doc_summary) {
out.push_str(&format!(" Doc: {doc}\n"));
}
if let Some(explain) = &r.explain {
out.push_str(&format!(" explain: {}\n", explain_line(r.score, explain)));
}
}
if results.len() >= limit {
out.push_str(&cap_notice(results.len(), limit));
}
out
}
fn explain_line(score: f64, e: &SearchExplain) -> String {
let mut line = format!(
"score {score:.1} = terms {:.1} + name {}({}) {:.1} + kind {:.1} + path {:.1}",
e.term_score, e.name_tier, e.name_strength, e.name_bonus, e.kind_prior, e.path_role,
);
if e.field_diversity_bonus != 0.0 {
line.push_str(&format!(" + field coverage {:.1}", e.field_diversity_bonus));
}
if e.documentation != 0.0 {
line.push_str(&format!(" + doc {:.1}", e.documentation));
}
if e.test_intent != 0.0 {
line.push_str(&format!(" + test {:.1}", e.test_intent));
}
if e.nested != 0.0 {
line.push_str(&format!(" + nested {:.1}", e.nested));
}
line.push_str(&format!(" [{}]", e.branches.join(",")));
if let Some(bm25) = e.bm25 {
line.push_str(&format!(" bm25 {bm25:.2}"));
}
if !e.terms.is_empty() {
let terms: Vec<String> = e
.terms
.iter()
.map(|(term, field, credit)| format!("{term}={field}:{credit}"))
.collect();
line.push_str(&format!(" terms {}", terms.join(" ")));
}
line
}
const EDIT_SITES_LISTED: usize = 12;
const EDIT_TESTS_LISTED: usize = 8;
pub const STOP_SITES_LISTED: usize = 15;
pub fn format_edit_check(check: &EditCheck) -> String {
let source = match check.source {
BaseSource::Given => "given",
BaseSource::Session => "session base",
BaseSource::Head => "HEAD",
};
let mut out = String::new();
if check.changes.is_empty() {
out.push_str(&format!(
"check_edit: working tree against {} ({source}): no symbol changed shape.\n",
check.rev
));
push_edit_cuts(&mut out, check);
return out;
}
out.push_str(&format!(
"check_edit: working tree against {} ({source}), {} {} changed\n{} shape {}:\n",
check.rev,
check.files_changed,
plural(check.files_changed, "file"),
check.changes.len(),
plural(check.changes.len(), "change"),
));
for change in &check.changes {
push_shape_change(&mut out, change);
}
push_edit_cuts(&mut out, check);
if !check.tests.is_empty() {
let shown = check.tests.len().min(EDIT_TESTS_LISTED);
out.push_str(&format!(
"Tests to run: {}",
check.tests[..shown].join("; ")
));
if shown < check.tests.len() {
out.push_str(&format!("; … {} more", check.tests.len() - shown));
}
out.push('\n');
}
out.push_str("Not checked: calls inside macro arguments, functions passed as values.\n");
out
}
pub fn format_stop_reason(check: &EditCheck) -> Option<String> {
let rows = stop_rows(check);
if rows.is_empty() {
return None;
}
let mut names: Vec<String> = Vec::new();
for (_, change) in &rows {
let name = format!("`{}`", change.display);
if !names.contains(&name) {
names.push(name);
}
}
let mut out = format!(
"Before you finish: {} sites still use the old shape of {}.\n",
rows.len(),
names.join(", ")
);
for (site, change) in rows.iter().take(STOP_SITES_LISTED) {
out.push_str(&format!(
"{}:{} {}: {}\n",
site.path, site.line, change.display, change.change
));
}
out.push_str("Edit them or say why they need no change.");
Some(out)
}
fn stop_rows(check: &EditCheck) -> Vec<(&EditSite, &ShapeChange)> {
let mut rows: Vec<(&EditSite, &ShapeChange)> = Vec::new();
for change in check.changes.iter().filter(|change| blocks_a_stop(change)) {
rows.extend(change.proven.iter().map(|site| (site, change)));
}
rows.sort_by(|a, b| {
(&a.0.path, a.0.line, &a.1.display).cmp(&(&b.0.path, b.0.line, &b.1.display))
});
rows
}
pub fn stop_sites(check: &EditCheck) -> Vec<&EditSite> {
stop_rows(check).into_iter().map(|(site, _)| site).collect()
}
fn blocks_a_stop(change: &ShapeChange) -> bool {
let javascript = [".js", ".jsx", ".mjs", ".cjs"]
.iter()
.any(|extension| change.path.ends_with(extension));
!change.change.starts_with("signature changed")
&& !(javascript && change.change.starts_with("parameters"))
}
fn push_edit_cuts(out: &mut String, check: &EditCheck) {
for cut in &check.cuts {
out.push_str(&format!("Cut: {cut}\n"));
}
}
fn push_shape_change(out: &mut String, change: &ShapeChange) {
out.push_str(&format!(
" {} `{}` ({}): {}\n",
change.kind, change.display, change.path, change.change
));
if !change.in_diff.is_empty() {
let shown = change.in_diff.len().min(EDIT_SITES_LISTED);
let places: Vec<String> = change.in_diff[..shown]
.iter()
.map(|site| format!("{}:{}", site.path, site.line))
.collect();
out.push_str(&format!(
" In your diff ({}): {}",
change.in_diff.len(),
places.join(", ")
));
if shown < change.in_diff.len() {
out.push_str(&format!(", … {} more", change.in_diff.len() - shown));
}
out.push('\n');
}
for (label, sites) in [("proven", &change.proven), ("likely", &change.likely)] {
if sites.is_empty() {
continue;
}
out.push_str(&format!(
" Not in your diff, {label} ({}):\n",
sites.len()
));
for site in &sites[..sites.len().min(EDIT_SITES_LISTED)] {
out.push_str(&edit_site_row(site));
}
if sites.len() > EDIT_SITES_LISTED {
out.push_str(&format!(
" … {} more {label}\n",
sites.len() - EDIT_SITES_LISTED
));
}
}
if change.unresolved > 0 {
out.push_str(&format!(
" Unresolved outside the diff: {}\n",
change.unresolved
));
}
}
fn edit_site_row(site: &EditSite) -> String {
let caller = if site.caller.is_empty() {
"(no enclosing symbol)".to_string()
} else {
format!("`{}`", site.caller)
};
let note = site
.note
.as_deref()
.map(|note| format!(" {note}"))
.unwrap_or_default();
format!(" {}:{} {caller}{note}\n", site.path, site.line)
}
pub fn format_blast_radius(result: &BlastRadiusResult) -> String {
if result.seed_type == "none" {
return "No uncommitted changes detected in git working tree. Pass a 'symbol' or 'file' parameter to analyze blast radius.".to_string();
}
let mut out = String::new();
let seed_label = if result.seed_type == "file" {
format!("Files: {}", result.seeds.join(", "))
} else if result.seed_type == "symbol" {
format!("Symbol: {}", result.seeds.join(", "))
} else {
format!("Seeds: {}", result.seeds.join(", "))
};
out.push_str(&format!("## Blast Radius & Test Impact ({seed_label})\n\n"));
let more = if result.limit_at_maximum {
"limit is at its maximum, so narrow the target to a symbol or fewer files"
} else {
"raise limit to see the rest"
};
let shown_of = |shown: usize, found: usize, noun: &str| {
if found > shown {
format!("Showing {shown} of {found} {noun}; {more}.\n\n")
} else {
format!("Requested limit hid additional {noun}; {more}.\n\n")
}
};
if result.likely_tests_truncated {
out.push_str(&shown_of(
result.likely_tests.len(),
result.likely_tests_found,
"likely tests",
));
}
if result.impacted_symbols_truncated {
out.push_str(&shown_of(
result.impacted_symbols.len(),
result.impacted_symbols_found,
"impacted symbols",
));
}
if result.traversal_ceiling_reached {
out.push_str("Traversal stopped at the 200-row discovery ceiling; narrow the target because increasing limit cannot raise this ceiling.\n\n");
}
if !result.likely_tests.is_empty() {
let total = result.likely_tests.len();
out.push_str(&format!("### Likely Tests to Run ({} returned)\n", total));
let mut previous_path = None;
for t in &result.likely_tests {
if previous_path != Some(t.path.as_str()) {
out.push_str(&format!("{}:\n", t.path));
previous_path = Some(t.path.as_str());
}
out.push_str(&format!(
" - `{}` [line {}] ({})\n",
t.name, t.line, t.reason
));
}
out.push('\n');
} else if result.likely_tests_truncated || result.traversal_ceiling_reached {
out.push_str("### Likely Tests to Run (0 returned)\n\n");
} else if let Some(depth) = result.stopped_at_depth {
out.push_str(&format!(
"### Likely Tests to Run (0 returned)\nNo test reaches it within depth {depth}; a larger `depth` may find some. Otherwise run the full test suite.\n\n",
));
} else {
out.push_str(
"### Likely Tests to Run (0 returned)\nNo test reaches it through the index. Run the full test suite.\n\n",
);
}
if !result.impacted_symbols.is_empty() {
let total = result.impacted_symbols.len();
let mut visible = Vec::new();
let mut low_signal_count = 0;
for s in &result.impacted_symbols {
if matches!(s.kind.as_str(), "import" | "module" | "namespace") {
low_signal_count += 1;
} else {
visible.push(s);
}
}
out.push_str(&format!("### Downstream Impact ({} returned)\n", total));
if visible.is_empty() && low_signal_count > 0 {
let row_word = if low_signal_count == 1 {
"row (import/module)"
} else {
"rows (imports/modules)"
};
out.push_str(&format!(
"All impacted symbols are imports/modules; {low_signal_count} low-signal {row_word} hidden; available in CLI --json.\n"
));
} else {
let mut syms_by_file: std::collections::BTreeMap<&str, Vec<&ImpactedSymbol>> =
std::collections::BTreeMap::new();
let mut file_order = Vec::new();
for s in &visible {
if !syms_by_file.contains_key(s.path.as_str()) {
file_order.push(s.path.as_str());
}
syms_by_file.entry(s.path.as_str()).or_default().push(s);
}
for path in file_order {
out.push_str(&format!("{path}:\n"));
if let Some(syms) = syms_by_file.get(path) {
for s in syms {
let name =
displayed_symbol_name(&s.name, s.enclosing_symbol_name.as_deref());
out.push_str(&format!(
" - [depth {}] {} `{}` [line {}]\n",
s.depth, s.kind, name, s.line
));
}
}
}
if low_signal_count > 0 {
let row_word = if low_signal_count == 1 {
"row (import/module)"
} else {
"rows (imports/modules)"
};
out.push_str(&format!(
"... {low_signal_count} low-signal {row_word} hidden; available in CLI --json.\n"
));
}
}
} else if result.impacted_symbols_truncated || result.traversal_ceiling_reached {
out.push_str("### Downstream Impact (0 returned)\n");
} else {
out.push_str(
"### Downstream Impact (0 returned)\nNo downstream callers found within depth.\n",
);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{
ImpactedSymbol, LabeledSite, ReferenceCounts, ReferenceReport, ReferenceTarget, SiteLabel,
TestTarget, TextOnlyLines, TextScanSummary,
};
fn structural_fact(key: Option<&str>) -> crate::models::StructuralFact {
crate::models::StructuralFact {
structural_fact_id: "sf1".into(),
path: ".codex/config.toml".into(),
language: "toml".into(),
pattern_id: "toml.key_value.v1".into(),
capture_name: "key_value".into(),
node_kind: "table".into(),
key: key.map(str::to_string),
metadata: None,
containing_symbol_name: None,
start_line: 2,
end_line: 2,
confidence: 1.0,
}
}
#[test]
fn format_structural_facts_prints_key_and_falls_back_to_capture_name() {
let with_key = format_structural_facts(
&[structural_fact(Some("mcp_servers.code-kb.command"))],
&[],
"config",
30,
);
assert!(with_key.contains(
"- mcp_servers.code-kb.command [.codex/config.toml:2] (pattern: toml.key_value.v1)"
));
let without_key = format_structural_facts(&[structural_fact(None)], &[], "config", 30);
assert!(
without_key.contains("- key_value [.codex/config.toml:2] (pattern: toml.key_value.v1)")
);
}
#[test]
fn format_structural_facts_renders_selected_qt_property_metadata() {
let mut fact = structural_fact(Some("index"));
fact.pattern_id = "cpp.qt_property.v1".into();
fact.metadata = Some(serde_json::json!({
"property_type": "int",
"designable": false,
"scriptable": true,
"stored": false,
"user": true,
"revision": 2,
}));
let output = format_structural_facts(&[fact], &[], "property", 30);
assert!(output.contains(
"property_type: int, designable: false, scriptable: true, stored: false, user: true, revision: 2"
));
}
#[test]
fn format_structural_facts_names_the_endpoint_and_view_of_a_flask_route() {
let route = |metadata| crate::models::StructuralFact {
pattern_id: "flask.route.v1".into(),
metadata: Some(metadata),
..structural_fact(Some("GET /"))
};
let alias = route(serde_json::json!({"api_style": "call_routing", "endpoint": "index"}));
let rule = route(serde_json::json!({"api_style": "call_routing", "view_target": "ping"}));
let decorated = route(serde_json::json!({"api_style": "decorator_routing"}));
let output = format_structural_facts(&[alias, rule, decorated], &[], "route", 30);
assert!(
output.contains("(pattern: flask.route.v1, endpoint: index, no view function)"),
"{output}"
);
assert!(
output.contains("(pattern: flask.route.v1, view: ping)"),
"{output}"
);
assert!(output.contains("(pattern: flask.route.v1)"), "{output}");
}
#[test]
fn doc_summary_ends_at_the_first_sentence_of_the_first_paragraph() {
assert_eq!(
doc_summary(
"The actual WSGI application. This is not implemented in\n:meth:`__call__`."
)
.as_deref(),
Some("The actual WSGI application.")
);
assert_eq!(
doc_summary("\nCreate a runner\nfor tests\n\nMore.").as_deref(),
Some("Create a runner for tests")
);
assert_eq!(
doc_summary("Find facts by category (e.g. route, query). More.").as_deref(),
Some("Find facts by category (e.g. route, query).")
);
assert_eq!(
doc_summary("Use a name, e.g. foo. Then more.").as_deref(),
Some("Use a name, e.g. foo.")
);
assert_eq!(
doc_summary("/// <summary>\n/// Goes to previous page.\n/// </summary>").as_deref(),
Some("Goes to previous page.")
);
assert_eq!(
doc_summary("/**\n * Render the view.\n *\n * @param x\n */").as_deref(),
Some("Render the view.")
);
assert_eq!(
doc_summary("# Given a +hash+ returns the settings\n# for the environment.").as_deref(),
Some("Given a +hash+ returns the settings for the environment.")
);
assert_eq!(
doc_summary("#' A person record.").as_deref(),
Some("A person record.")
);
assert_eq!(doc_summary(" \n"), None);
assert!(doc_summary(&"word ".repeat(60)).unwrap().ends_with('…'));
}
#[test]
fn format_references_reports_the_occurrence_count_of_a_grouped_row() {
let site = |occurrences| ReferenceSite {
from_symbol_name: "Button".into(),
from_symbol_id: "s1".into(),
to_symbol_name: "background".into(),
kind: "member_access".into(),
path: "Ui/Button.qml".into(),
start_line: Some(5),
start_column: Some(4),
occurrences,
target: None,
enclosing_symbol_name: None,
};
let grouped = format_references("Color", &[site(Some(6))], "callers", 30);
assert!(
grouped.contains("- `Button` [Ui/Button.qml:5] (kind: member_access, 6 in file)"),
"{grouped}"
);
let single = format_references("Color", &[site(None)], "callers", 30);
assert!(
single.contains("- `Button` [Ui/Button.qml:5] (kind: member_access)"),
"{single}"
);
}
#[test]
fn a_match_snippet_joins_its_lines_and_is_dropped_when_it_repeats_the_signature() {
assert_eq!(
match_line("...path up to\n the [root] (the [root] excluded)", "fn f()"),
Some("...path up to the [root] (the [root] excluded)".into())
);
assert_eq!(
match_line("// [HelpFunc] returns the\n// [function] set", "fn f()"),
Some("// [HelpFunc] returns the [function] set".into())
);
assert_eq!(
match_line(
"fn [index]_facts(workspace_[root]: &Path) -> Option<[IndexFacts]>",
"fn index_facts(workspace_root: &Path) -> Option<IndexFacts>"
),
None
);
assert_eq!(
match_line(
"...[root]: &[PathBuf]) -> bool",
"fn f(root: &[PathBuf]) -> bool"
),
None
);
}
#[test]
fn a_long_unresolved_call_chain_shows_only_its_last_call() {
assert_eq!(
callee_name(
"[\"a\", \"b\"]\n .into_iter()\n .find_map(|key| args.get(key)).ok_or_else"
),
"….ok_or_else"
);
assert_eq!(
callee_name("workspace.resolve_path"),
"workspace.resolve_path"
);
let selector = "html.dash .header, html.dash .breadcrumbs, html.dash .navigation";
assert_eq!(callee_name(selector), selector);
}
#[test]
fn file_skeleton_reports_parse_errors() {
let two = format_file_skeleton("src/lib.rs", &[], Some(35), 2);
assert!(two.contains("// 2 parse errors: symbols may be incomplete"));
let one = format_file_skeleton("src/lib.rs", &[], Some(35), 1);
assert!(one.contains("// 1 parse error: symbols may be incomplete"));
let none = format_file_skeleton("src/lib.rs", &[], Some(35), 0);
assert!(!none.contains("parse error"));
}
#[test]
fn test_format_file_skeleton() {
let syms = vec![Symbol {
symbol_id: "s1".into(),
file_id: "f1".into(),
path: "src/lib.rs".into(),
language: "rust".into(),
name: "do_work".into(),
kind: "function".into(),
signature: Some("pub fn do_work() -> Result<()>".into()),
doc_comment: Some("Performs core work.".into()),
visibility: Some("pub".into()),
parent_symbol_id: None,
start_line: 10,
start_column: 0,
end_line: 30,
end_column: 1,
start_byte: 100,
end_byte: 300,
body_start_line: Some(11),
body_start_column: Some(0),
body_end_line: Some(29),
body_end_column: Some(1),
body_start_byte: Some(130),
body_end_byte: Some(298),
body_hash: None,
semantic_group: None,
is_test: false,
test_container: false,
}];
let skeleton = format_file_skeleton("src/lib.rs", &syms, Some(35), 0);
assert!(skeleton.contains("/// Performs core work."));
let mut commented = syms.clone();
commented[0].doc_comment = Some("/**\n * Performs core work.\n */".into());
let skeleton = format_file_skeleton("src/lib.rs", &commented, Some(35), 0);
assert!(skeleton.contains("/// Performs core work.\n"), "{skeleton}");
assert!(!skeleton.contains("/// /**"), "{skeleton}");
assert!(skeleton.contains("19 lines hidden: L11-L29"));
}
#[test]
fn format_symbol_body_prints_the_source_under_its_location() {
let mut symbol = sample_symbol("plain");
symbol.signature = Some("pub fn plain()".into());
assert_eq!(
format_symbol_body(&symbol, "pub fn plain() {\n 1\n}"),
"// src/lib.rs:1-10 (plain)\npub fn plain() {\n 1\n}\n"
);
}
#[test]
fn test_format_search_results() {
let results = vec![SymbolSearchResult {
symbol: Symbol {
symbol_id: "s1".into(),
file_id: "f1".into(),
path: "src/parser.rs".into(),
language: "rust".into(),
name: "parse_tokens".into(),
kind: "function".into(),
signature: Some("pub fn parse_tokens()".into()),
doc_comment: Some("Parses tokens from stream.".into()),
visibility: Some("pub".into()),
parent_symbol_id: None,
start_line: 15,
start_column: 0,
end_line: 25,
end_column: 1,
start_byte: 100,
end_byte: 250,
body_start_line: None,
body_start_column: None,
body_end_line: None,
body_end_column: None,
body_start_byte: None,
body_end_byte: None,
body_hash: None,
semantic_group: None,
is_test: false,
test_container: false,
},
score: -1.85,
snippet: Some("Parses [tokens] from stream.".into()),
explain: None,
}];
let formatted = format_search_results("tokens", &results, 20);
assert!(formatted.contains("Found 1 symbols matching concept \"tokens\":\n\n- "));
assert!(
formatted.contains("- function `parse_tokens` [src/parser.rs:15-25] (score: -1.85)")
);
assert!(formatted.contains("Match: Parses [tokens] from stream."));
assert!(!formatted.contains("explain"));
assert!(!formatted.contains("rerank"));
}
#[test]
fn exact_lookup_folds_import_rows_into_one_line_when_a_definition_exists() {
let class = Symbol {
kind: "class".into(),
path: "src/flask/app.py".into(),
..sample_symbol("Flask")
};
let import = |path: &str, line: usize| Symbol {
kind: "import".into(),
path: path.into(),
start_line: line,
symbol_id: format!("id_{path}"),
..sample_symbol("Flask")
};
let rows = vec![
class,
import("src/flask/cli.py", 34),
import("src/flask/ctx.py", 21),
import("tests/conftest.py", 6),
import("examples/app.py", 1),
];
let folded = format_find_symbol_results("Flask", &rows, &[], 20);
assert!(
folded.contains("- class `Flask` [src/flask/app.py:"),
"{folded}"
);
assert!(
folded.contains(
"- 4 imports of `Flask`: src/flask/cli.py:34, src/flask/ctx.py:21, tests/conftest.py:6, … (lookup_symbol with kind=\"import\" lists them)"
),
"{folded}"
);
assert!(!folded.contains("- import `Flask`"), "{folded}");
let capped = format_find_symbol_results("Flask", &rows, &[], 1);
assert!(
capped.contains("- at least 4 imports of `Flask`"),
"{capped}"
);
let mut with_prefix_rows = rows.clone();
with_prefix_rows.push(Symbol {
kind: "class".into(),
..sample_symbol("FlaskGroup")
});
let cut_after_exact = format_find_symbol_results("Flask", &with_prefix_rows, &[], 6);
assert!(
cut_after_exact.contains("- 4 imports of `Flask`"),
"{cut_after_exact}"
);
let imports_only = format_find_symbol_results("Flask", &rows[1..], &[], 20);
assert_eq!(imports_only.matches("- import `Flask`").count(), 4);
}
#[test]
fn skeleton_shows_python_class_and_module_variables_with_one_line_values() {
let leaf = |name: &str, kind: &str, line: usize, sig: &str| Symbol {
kind: kind.into(),
language: "python".into(),
path: "app.py".into(),
parent_symbol_id: Some("id_App".into()),
start_line: line,
end_line: line,
body_start_line: None,
body_end_line: None,
signature: Some(sig.into()),
..sample_symbol(name)
};
let class = Symbol {
kind: "class".into(),
language: "python".into(),
path: "app.py".into(),
start_line: 1,
end_line: 20,
body_start_line: None,
body_end_line: None,
signature: Some("class App".into()),
..sample_symbol("App")
};
let module_variable = Symbol {
kind: "variable".into(),
language: "python".into(),
path: "app.py".into(),
start_line: 30,
end_line: 30,
body_start_line: None,
body_end_line: None,
signature: Some("app = App()".into()),
..sample_symbol("app")
};
let symbols = vec![
class,
leaf(
"request_class",
"variable",
2,
"request_class: type[Request] = Request",
),
leaf(
"default_config",
"variable",
3,
"default_config = ImmutableDict(\n {\n \"DEBUG\": None,\n }\n)",
),
leaf(
"blueprints",
"property",
5,
"self.blueprints: dict[str, Blueprint] = {}",
),
leaf(
"error_handler_spec",
"property",
7,
"self.error_handler_spec: dict[\n ft.AppOrBlueprintKey,\n dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]],\n] = defaultdict(lambda: defaultdict(dict), default_factory_argument_that_is_long) # type: ignore",
),
module_variable,
];
let out = format_file_skeleton("app.py", &symbols, None, 0);
assert!(
out.contains(" request_class: type[Request] = Request; // L2-2"),
"{out}"
);
assert!(
out.contains(" default_config = ImmutableDict({\"DEBUG\": None}); // L3-3"),
"{out}"
);
assert!(
out.contains(" self.error_handler_spec: dict[ft.AppOrBlueprintKey, dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]]] = defaultdict(lambda: defaultdict(dict)…; // L7-7"),
"{out}"
);
assert!(
out.contains(" self.blueprints: dict[str, Blueprint] = {}; // L5-5"),
"{out}"
);
assert!(out.contains("app = App(); // L30-30"), "{out}");
}
#[test]
fn skeleton_names_the_method_that_assigns_an_attribute() {
let member = |name: &str, kind: &str, start: usize, end: usize, sig: &str| Symbol {
kind: kind.into(),
language: "python".into(),
parent_symbol_id: Some("id_Flask".into()),
start_line: start,
end_line: end,
body_start_line: None,
body_end_line: None,
signature: Some(sig.into()),
..sample_symbol(name)
};
let class = Symbol {
kind: "class".into(),
language: "python".into(),
start_line: 1,
end_line: 30,
body_start_line: None,
body_end_line: None,
signature: Some("class Flask(App)".into()),
..sample_symbol("Flask")
};
let symbols = vec![
class,
member("__init__", "method", 2, 10, "def __init__(self)"),
member("cli", "property", 3, 3, "self.cli = cli.AppGroup()"),
member("run", "method", 12, 20, "def run(self)"),
member("debug", "property", 14, 14, "self.debug = get_debug_flag()"),
];
let out = format_file_skeleton("app.py", &symbols, None, 0);
assert!(
out.contains("self.cli = cli.AppGroup(); // in __init__(), L3-3"),
"{out}"
);
assert!(
out.contains("self.debug = get_debug_flag(); // in run(), L14-14"),
"{out}"
);
}
#[test]
fn skeleton_hides_variables_that_are_not_python_class_attributes() {
let parent = |name: &str, kind: &str, language: &str| Symbol {
kind: kind.into(),
language: language.into(),
start_line: 1,
end_line: 20,
body_start_line: None,
body_end_line: None,
signature: Some(format!("{kind} {name}")),
..sample_symbol(name)
};
let child = |name: &str, parent: &str, language: &str, sig: &str| Symbol {
kind: "variable".into(),
language: language.into(),
parent_symbol_id: Some(format!("id_{parent}")),
start_line: 2,
end_line: 2,
body_start_line: None,
body_end_line: None,
signature: Some(sig.into()),
..sample_symbol(name)
};
let symbols = vec![
parent("Adapters", "class", "java"),
child(
"accessible",
"Adapters",
"java",
"boolean accessible = false",
),
parent("body", "class", "html"),
child(
"img",
"body",
"html",
"<img class=\"carat\" src=\"carat.png\" alt>",
),
parent("section", "module", "yaml"),
child("title", "section", "yaml", "title: Invoking jq"),
];
let out = format_file_skeleton("mixed", &symbols, None, 0);
for hidden in ["accessible", "<img", "title:"] {
assert!(!out.contains(hidden), "{out}");
}
}
#[test]
fn a_multi_line_macro_row_drops_its_line_continuations_and_is_cut_to_one_line() {
let short = "#define PAIR(a, b) \\\n a, \\\n b";
assert_eq!(value_row_signature(short), "#define PAIR(a, b) a, b");
let long = format!(
"#define WIDE(x) \\\n{}",
" x + x + x + x + x + x + x + x \\\n".repeat(8)
);
let row = value_row_signature(&long);
assert!(row.starts_with("#define WIDE(x) x + x"), "{row}");
assert!(row.ends_with('…'), "{row}");
assert!(!row.contains('\\'), "{row}");
assert!(row.chars().count() <= 120, "{row}");
}
#[test]
fn skeleton_keeps_values_only_for_real_assignments() {
let row =
|name: &str, kind: &str, line: usize, sig: &str, body: Option<(usize, usize)>| Symbol {
kind: kind.into(),
start_line: line,
end_line: body.map_or(line, |(_, end)| end),
body_start_line: body.map(|(start, _)| start),
body_end_line: body.map(|(_, end)| end),
signature: Some(sig.into()),
..sample_symbol(name)
};
let symbols = vec![
row(
"img",
"property",
1,
"[class^=rz-] img,[class^=rz-] svg { vertical-align:middle }",
None,
),
row(
"Coordinates",
"property",
2,
"[JsonProperty(ItemConverterType = typeof(IntToFloatConverter))] public int[,,] Coordinates { get; set; }",
None,
),
row(
"VERSION_CHECK",
"constant",
3,
"#define VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor)))\n",
None,
),
row(
"TABLE",
"constant",
4,
"TABLE = {\n '\"' => '%22',\n '\\r' => '%0D',\n}.freeze",
Some((4, 8)),
),
row(
"LIMIT",
"field",
10,
"private static final int LIMIT = 5;",
None,
),
];
let out = format_file_skeleton("mixed", &symbols, None, 0);
assert!(
out.contains("[class^=rz-] img,[class^=rz-] svg; // L1-1"),
"{out}"
);
assert!(out.contains("public int[,,] Coordinates; // L2-2"), "{out}");
assert!(
out.contains(
"#define VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))); // L3-3\n"
),
"{out}"
);
assert!(
out.contains("TABLE = { /* 5 lines hidden: L4-L8 */ }"),
"{out}"
);
assert!(
out.contains("private static final int LIMIT = 5; // L10-10"),
"{out}"
);
}
#[test]
fn skeleton_cuts_a_field_with_members_at_its_brace() {
let field = Symbol {
kind: "field".into(),
language: "java".into(),
start_line: 1,
end_line: 9,
body_start_line: None,
body_end_line: None,
signature: Some(
"public static final TypeAdapter<Class> CLASS = new TypeAdapter<Class>() {\n @Override\n}"
.into(),
),
..sample_symbol("CLASS")
};
let member = Symbol {
kind: "method".into(),
language: "java".into(),
parent_symbol_id: Some("id_CLASS".into()),
start_line: 2,
end_line: 4,
signature: Some("public void write(JsonWriter out, Class value)".into()),
..sample_symbol("write")
};
let out = format_file_skeleton("TypeAdapters.java", &[field, member], None, 0);
assert!(
out.contains(
"public static final TypeAdapter<Class> CLASS = new TypeAdapter<Class>() {\n"
),
"{out}"
);
assert!(!out.contains('…'), "{out}");
}
#[test]
fn import_summary_counts_sites_and_files_and_lists_three() {
let sites = vec![
("src/flask/__init__.py".to_string(), 2),
("src/flask/cli.py".to_string(), 34),
("src/flask/cli.py".to_string(), 45),
("tests/conftest.py".to_string(), 6),
];
assert_eq!(
format_import_summary(&sites),
"Imported 4 times in 3 files: src/flask/__init__.py:2, src/flask/cli.py:34, src/flask/cli.py:45, … (lookup_symbol with kind=\"import\" lists them)\n"
);
assert_eq!(format_import_summary(&[]), "");
}
#[test]
fn no_facts_for_an_alias_points_imports_at_lookup_and_skips_the_raw_list() {
let categories = vec![("flask.route.v1".to_string(), 3)];
let imports = format_no_facts("import", None, &categories);
let unknown = format_no_facts("widgets", None, &categories);
assert!(
imports.contains("lookup_symbol with kind=\"import\""),
"{imports}"
);
assert!(!imports.contains("`flask.route.v1`"), "{imports}");
assert!(unknown.contains("`flask.route.v1` (3 facts)"), "{unknown}");
}
#[test]
fn markup_and_data_rows_show_their_own_kind_words() {
let row = |language: &str, kind: &str| Symbol {
language: language.into(),
kind: kind.into(),
..sample_symbol("x")
};
assert_eq!(display_kind(&row("html", "class")), "element");
assert_eq!(display_kind(&row("sql", "class")), "table");
assert_eq!(display_kind(&row("markdown", "module")), "section");
assert_eq!(display_kind(&row("markdown", "import")), "link");
let attribute = Symbol {
kind: "property".into(),
signature: Some("self.extensions = {}".into()),
..sample_symbol("extensions")
};
assert_eq!(display_kind(&attribute), "attribute");
let fsharp_member = Symbol {
language: "fsharp".into(),
signature: Some("this.Total = decimal this.Qty * this.Price".into()),
..attribute
};
assert_eq!(display_kind(&fsharp_member), "property");
assert_eq!(display_kind(&row("python", "class")), "class");
}
#[test]
fn no_facts_heading_names_the_path_filter() {
assert_eq!(
no_facts_heading("config", Some("src/flask")),
"No facts match 'config' under `src/flask`."
);
assert_eq!(
no_facts_heading("config", None),
"No facts match 'config' in this repository."
);
}
#[test]
fn outline_says_how_many_definitions_it_left_out_and_counts_tests() {
let mut root = OutlineNode::default();
let symbol = |name: &str, is_test: bool| Symbol {
kind: "function".into(),
path: "tests/test_basic.py".into(),
is_test,
..sample_symbol(name)
};
let symbols = HashMap::from([(
"tests/test_basic.py".to_string(),
vec![symbol("helper", false), symbol("test_one", true)],
)]);
let counts = HashMap::from([(
"tests/test_basic.py".to_string(),
crate::queries::OutlineCounts {
definitions: 3,
tests: 90,
fixtures: 2,
},
)]);
add_path_to_outline(&mut root, "tests/test_basic.py", &symbols, &counts, 3, "");
let mut out = String::new();
render_outline_tree(&mut out, &root, "", 0, 3);
assert!(
out.contains("test_basic.py [function helper, +2 more, 90 tests, 2 fixtures]"),
"{out}"
);
}
#[test]
fn outline_counts_the_files_below_the_depth_limit() {
let mut root = OutlineNode::default();
let no_symbols = HashMap::new();
for path in [
"src/flask/app.py",
"src/flask/json/tag.py",
"src/flask/cli.py",
"tests/conftest.py",
] {
add_path_to_outline(&mut root, path, &no_symbols, &HashMap::new(), 2, "");
}
let mut out = String::new();
render_outline_tree(&mut out, &root, "", 0, 2);
assert!(out.contains("└── flask/ (3 indexed files)"), "{out}");
assert!(!out.contains("tests/ ("), "{out}");
}
#[test]
fn a_lookup_qualified_with_double_colons_names_the_member_exactly() {
let rows = vec![sample_symbol("Flask.handle_user_exception")];
let out = format_find_symbol_results("Flask::handle_user_exception", &rows, &[], 20);
assert!(!out.contains("named exactly"), "{out}");
}
#[test]
fn exact_lookup_folds_only_imports_named_exactly_like_the_query() {
let rows = vec![
Symbol {
kind: "function".into(),
..sample_symbol("read")
},
Symbol {
kind: "import".into(),
path: "src/flask/cli.py".into(),
start_line: 1034,
..sample_symbol("readline")
},
Symbol {
kind: "import".into(),
path: "src/Types.kt".into(),
start_line: 18,
..sample_symbol("com.example.internal.read")
},
];
let out = format_find_symbol_results("read", &rows, &[], 20);
assert!(
out.contains(
"- 1 other row starts with or contains `read`, ignoring case: `readline` (import) (lookup_symbol"
),
"{out}"
);
assert!(
out.contains(
"- 1 import of `read`: src/Types.kt:18 (lookup_symbol with kind=\"import\" lists them)"
),
"{out}"
);
}
#[test]
fn discovery_formatters_append_ids_to_metadata_rows() {
let exact = vec![sample_symbol("exact")];
let fallback = vec![SymbolSearchResult {
symbol: sample_symbol("fallback"),
score: 1.0,
snippet: None,
explain: None,
}];
let outputs = [
(
format_find_symbol_results("exact", &exact, &[], 20),
"id_exact",
),
(
format_find_symbol_results("fallback query", &[], &fallback, 20),
"id_fallback",
),
(
format_search_results("fallback", &fallback, 20),
"id_fallback",
),
];
for (formatted, id) in outputs {
assert!(
formatted
.lines()
.any(|line| line.starts_with("- ") && line.contains(&format!("id={id}"))),
"{formatted}"
);
assert!(!formatted.contains("\n id="), "{formatted}");
}
}
#[test]
fn lookup_lists_name_rows_before_the_ranked_rows_under_their_own_header() {
let names = vec![sample_symbol("retry_count")];
let ranked = vec![SymbolSearchResult {
symbol: sample_symbol("send_again"),
score: 7.5,
snippet: None,
explain: None,
}];
let out = format_find_symbol_results("retry", &names, &ranked, 20);
let name_header = out.find("Found 1 symbols matching \"retry\"").unwrap();
let ranked_header = out
.find("\nNo exact name match; 1 full-text matches for \"retry\":\n\n")
.unwrap();
assert!(name_header < out.find("`retry_count`").unwrap(), "{out}");
assert!(out.find("`retry_count`").unwrap() < ranked_header, "{out}");
assert!(ranked_header < out.find("`send_again`").unwrap(), "{out}");
}
#[test]
fn every_printed_part_of_the_explain_line_sums_to_the_score() {
let e = SearchExplain {
bm25: Some(-3.21),
branches: vec!["word".into()],
name_tier: "all".into(),
name_strength: 6,
term_score: 24.5,
field_diversity_bonus: 6.0,
name_bonus: 60.0,
kind_prior: 4.0,
path_role: -10.0,
documentation: -200.0,
test_intent: 5.0,
nested: 0.0,
terms: vec![("sha".into(), "name".into(), 3.0)],
word_weights: vec![("sha".into(), 2.6)],
candidates: 1,
rerank_us: 1,
};
let score = e.term_score
+ e.field_diversity_bonus
+ e.name_bonus
+ e.kind_prior
+ e.path_role
+ e.documentation
+ e.test_intent;
let line = explain_line(score, &e);
let parts: f64 = line
.split(" = ")
.nth(1)
.unwrap()
.split(" [")
.next()
.unwrap()
.split(" + ")
.map(|part| part.rsplit(' ').next().unwrap().parse::<f64>().unwrap())
.sum();
assert!(line.starts_with(&format!(
"score {score:.1} = terms 24.5 + name all(6) 60.0 "
)));
assert!(line.contains("+ field coverage 6.0"));
assert_eq!(format!("{parts:.1}"), format!("{score:.1}"));
}
#[test]
fn test_format_search_results_prints_the_explain_breakdown_when_present() {
let mut result = SymbolSearchResult {
symbol: sample_symbol("parseSha256Sidecar"),
score: 71.6,
snippet: Some("parse[Sha256]Sidecar".into()),
explain: Some(SearchExplain {
bm25: Some(-3.21),
branches: vec!["word".into(), "name".into()],
name_tier: "all".into(),
name_strength: 6,
terms: vec![
("sha".into(), "name".into(), 3.0),
("256".into(), "name".into(), 3.0),
],
term_score: 12.6,
field_diversity_bonus: 0.0,
name_bonus: 60.0,
kind_prior: 4.0,
path_role: -10.0,
documentation: 0.0,
test_intent: 5.0,
nested: 0.0,
word_weights: vec![("sha".into(), 2.6), ("256".into(), 0.97)],
candidates: 37,
rerank_us: 180,
}),
};
let formatted = format_search_results("sha256", std::slice::from_ref(&result), 20);
assert!(formatted.contains(
"Found 1 symbols matching concept \"sha256\":\nrerank: 37 candidates in 180 µs; words sha 2.60, 256 0.97\n\n- "
));
assert!(formatted.contains(
" explain: score 71.6 = terms 12.6 + name all(6) 60.0 + kind 4.0 + path -10.0 + test 5.0 [word,name] bm25 -3.21 terms sha=name:3 256=name:3\n"
));
result.explain = None;
let silent = format_search_results("sha256", std::slice::from_ref(&result), 20);
assert!(!silent.contains("explain"));
assert!(!silent.contains("rerank"));
}
#[test]
fn test_format_search_results_discloses_a_reached_limit() {
let results = vec![SymbolSearchResult {
symbol: sample_symbol("parse_tokens"),
score: 0.0,
snippet: None,
explain: None,
}];
let formatted = format_search_results("tokens", &results, 1);
assert!(
formatted.contains("[Showing 1 results (limit reached); increase limit to see more.]")
);
let at_ceiling =
format_search_results("tokens", &results, crate::queries::MAX_RESULT_LIMIT);
assert!(!at_ceiling.contains("limit reached"));
let full: Vec<SymbolSearchResult> = (0..crate::queries::MAX_RESULT_LIMIT)
.map(|_| SymbolSearchResult {
symbol: sample_symbol("parse_tokens"),
score: 0.0,
snippet: None,
explain: None,
})
.collect();
let capped = format_search_results("tokens", &full, crate::queries::MAX_RESULT_LIMIT);
assert!(capped.contains("(limit reached); narrow the query to see more.]"));
}
#[test]
fn test_format_blast_radius() {
let res = BlastRadiusResult {
seed_type: "symbol".into(),
seeds: vec!["do_work".into()],
likely_tests: vec![TestTarget {
name: "test_do_work".into(),
path: "tests/work_test.rs".into(),
line: 15,
reason: "direct caller".into(),
}],
impacted_symbols: vec![ImpactedSymbol {
name: "caller_fn".into(),
kind: "function".into(),
path: "src/caller.rs".into(),
line: 42,
depth: 1,
enclosing_symbol_name: None,
}],
traversal_ceiling_reached: false,
likely_tests_truncated: false,
impacted_symbols_truncated: false,
limit_at_maximum: false,
likely_tests_found: 0,
impacted_symbols_found: 0,
stopped_at_depth: None,
};
let formatted = format_blast_radius(&res);
assert!(formatted.contains("## Blast Radius & Test Impact (Symbol: do_work)"));
assert!(formatted.contains("### Likely Tests to Run (1 returned)"));
assert!(
formatted.contains("tests/work_test.rs:\n - `test_do_work` [line 15] (direct caller)")
);
assert!(formatted.contains("src/caller.rs:\n - [depth 1] function `caller_fn` [line 42]"));
}
#[test]
fn format_blast_radius_groups_every_returned_test_by_file() {
let mut likely_tests = Vec::new();
for i in 1..=25 {
likely_tests.push(TestTarget {
name: format!("test_{i}"),
path: format!("tests/test_{}.rs", (i % 3) + 1),
line: i * 10,
reason: "direct caller".into(),
});
}
let impacted_symbols = vec![
ImpactedSymbol {
name: "use_foo".into(),
kind: "import".into(),
path: "src/service.rs".into(),
line: 1,
depth: 1,
enclosing_symbol_name: None,
},
ImpactedSymbol {
name: "service_fn".into(),
kind: "function".into(),
path: "src/service.rs".into(),
line: 20,
depth: 1,
enclosing_symbol_name: None,
},
ImpactedSymbol {
name: "api_handler".into(),
kind: "function".into(),
path: "src/api.rs".into(),
line: 45,
depth: 2,
enclosing_symbol_name: None,
},
];
let res = BlastRadiusResult {
seed_type: "file".into(),
seeds: vec!["src/lib.rs".into()],
likely_tests,
impacted_symbols,
traversal_ceiling_reached: false,
likely_tests_truncated: false,
impacted_symbols_truncated: false,
limit_at_maximum: false,
likely_tests_found: 0,
impacted_symbols_found: 0,
stopped_at_depth: None,
};
let formatted = format_blast_radius(&res);
assert!(formatted.contains("### Likely Tests to Run (25 returned)"));
assert!(
formatted.contains(" - `test_25` [line 250]"),
"{formatted}"
);
assert!(!formatted.contains("hidden by the compact"));
assert!(formatted.contains("tests/test_1.rs:\n"));
assert!(formatted.contains(" - `test_"));
assert!(!formatted.contains("use_foo"));
assert!(
formatted
.contains("... 1 low-signal row (import/module) hidden; available in CLI --json.")
);
assert!(formatted.contains("src/service.rs:\n"));
assert!(formatted.contains(" - [depth 1] function `service_fn` [line 20]"));
}
fn sample_symbol(name: &str) -> Symbol {
Symbol {
symbol_id: format!("id_{name}"),
file_id: "f1".into(),
path: "src/lib.rs".into(),
language: "rust".into(),
name: name.into(),
kind: "function".into(),
signature: Some(format!("pub fn {name}()")),
doc_comment: None,
visibility: Some("pub".into()),
parent_symbol_id: None,
start_line: 1,
start_column: 0,
end_line: 10,
end_column: 1,
start_byte: 0,
end_byte: 100,
body_start_line: Some(2),
body_start_column: Some(0),
body_end_line: Some(9),
body_end_column: Some(1),
body_start_byte: Some(10),
body_end_byte: Some(99),
body_hash: None,
semantic_group: None,
is_test: false,
test_container: false,
}
}
fn sample_context_slice() -> ContextSlice {
ContextSlice {
target_symbol: sample_symbol("target_fn"),
target_body: " println!(\"hello\");\n".into(),
callee_signatures: Vec::new(),
related_types: Vec::new(),
related_tests: Vec::new(),
}
}
#[test]
fn format_context_slice_does_not_repeat_expression_already_in_signature() {
let body = "=>\n transport.SendAsync<Response>(\n HttpMethod.Get,\n $\"items/{id}\",\n cancellationToken: cancellationToken)";
let mut slice = sample_context_slice();
slice.target_symbol.language = "csharp".into();
slice.target_symbol.signature = Some(format!(
"public static Task<Response?> GetByIdAsync(\n int id,\n CancellationToken cancellationToken = default){body}"
));
slice.target_body = body.into();
let formatted = format_context_slice(&slice);
assert_eq!(
formatted.matches("transport.SendAsync").count(),
1,
"{formatted}"
);
}
#[test]
fn context_slice_leaves_out_a_signature_that_the_body_already_holds() {
let mut slice = sample_context_slice();
slice.target_symbol.signature = Some("type Person = { Name: string; Age: int }".into());
slice.target_body = "type Person = { Name: string; Age: int }\n".into();
let text = format_context_slice(&slice);
assert!(!text.contains("Signature:"), "{text}");
assert_eq!(text.matches("type Person").count(), 1, "{text}");
}
#[test]
fn context_slice_says_when_no_test_was_found() {
let text = format_context_slice(&sample_context_slice());
assert!(
text.ends_with("### Related Tests:\nNo test calls, uses, or names this symbol; blast_radius lists tests that can reach it through callers.\n"),
"{text}"
);
}
#[test]
fn test_context_slice_shows_truncation_notice_when_caps_hit() {
let mut slice = sample_context_slice();
slice.callee_signatures = (1..=10).map(|i| format!("fn callee_{i}()")).collect();
let text = format_context_slice(&slice);
assert!(text.contains("[Showing 10 dependencies (limit reached)]"));
let mut slice_tests = sample_context_slice();
slice_tests.related_tests = (1..=5)
.map(|i| {
let mut sym = sample_symbol(&format!("test_fn_{i}"));
sym.path = format!("tests/test_{i}.rs");
sym.is_test = true;
sym
})
.collect();
let text_tests = format_context_slice(&slice_tests);
assert!(text_tests.contains("[Showing 5 tests (limit reached)]"));
}
#[test]
fn test_blast_radius_shows_traversal_ceiling_at_200_symbols() {
let impacted_symbols = (1..=200)
.map(|i| ImpactedSymbol {
name: format!("sym_{i}"),
kind: "function".into(),
path: format!("src/mod_{}.rs", i % 10),
line: i,
depth: 1,
enclosing_symbol_name: None,
})
.collect();
let res = BlastRadiusResult {
seed_type: "symbol".into(),
seeds: vec!["root_fn".into()],
likely_tests: Vec::new(),
impacted_symbols,
traversal_ceiling_reached: true,
likely_tests_truncated: false,
impacted_symbols_truncated: false,
limit_at_maximum: false,
likely_tests_found: 0,
impacted_symbols_found: 0,
stopped_at_depth: None,
};
let formatted = format_blast_radius(&res);
assert!(formatted.contains("### Downstream Impact (200 returned)\n"));
}
#[test]
fn blast_radius_does_not_claim_no_results_after_the_traversal_ceiling() {
let res = BlastRadiusResult {
seed_type: "file".into(),
seeds: vec!["src/widget.rs".into()],
likely_tests: Vec::new(),
impacted_symbols: Vec::new(),
likely_tests_truncated: false,
impacted_symbols_truncated: false,
traversal_ceiling_reached: true,
limit_at_maximum: false,
likely_tests_found: 0,
impacted_symbols_found: 0,
stopped_at_depth: None,
};
let formatted = format_blast_radius(&res);
assert!(formatted.contains("Likely Tests to Run (0 returned)"));
assert!(formatted.contains("Downstream Impact (0 returned)"));
assert!(!formatted.contains("No test reaches it through the index"));
assert!(!formatted.contains("No downstream callers found within depth"));
let no_ceiling = BlastRadiusResult {
traversal_ceiling_reached: false,
..res
};
assert!(
format_blast_radius(&no_ceiling)
.contains("No test reaches it through the index. Run the full test suite.\n")
);
let cut_by_depth = BlastRadiusResult {
stopped_at_depth: Some(2),
..no_ceiling
};
let formatted = format_blast_radius(&cut_by_depth);
assert!(formatted.contains(
"No test reaches it within depth 2; a larger `depth` may find some. Otherwise run the full test suite.\n"
));
assert!(!formatted.contains("through the index"));
}
fn skeleton_row(
id: &str,
parent: Option<&str>,
kind: &str,
name: &str,
signature: &str,
lines: (usize, usize),
body: Option<(usize, usize)>,
) -> Symbol {
Symbol {
symbol_id: id.into(),
file_id: "f1".into(),
path: "src/lib.rs".into(),
language: "rust".into(),
name: name.into(),
kind: kind.into(),
signature: Some(signature.into()),
doc_comment: None,
visibility: None,
parent_symbol_id: parent.map(str::to_string),
start_line: lines.0,
start_column: 0,
end_line: lines.1,
end_column: 1,
start_byte: 0,
end_byte: 0,
body_start_line: body.map(|b| b.0),
body_start_column: None,
body_end_line: body.map(|b| b.1),
body_end_column: None,
body_start_byte: None,
body_end_byte: None,
body_hash: None,
semantic_group: None,
is_test: false,
test_container: false,
}
}
#[test]
fn skeleton_nests_an_object_under_the_field_that_declares_it() {
let syms = vec![
skeleton_row(
"root",
None,
"class",
"shell",
"extends ShellRoot",
(1, 8),
None,
),
skeleton_row(
"timer",
Some("root"),
"field",
"localPluginReloadTimer",
"localPluginReloadTimer: Timer",
(2, 7),
Some((2, 7)),
),
skeleton_row(
"interval",
Some("timer"),
"property",
"interval",
"interval: 150",
(3, 3),
None,
),
skeleton_row(
"fire",
Some("timer"),
"function",
"fire",
"function fire()",
(5, 7),
Some((6, 7)),
),
];
assert_eq!(
format_file_skeleton("shell/shell.qml", &syms, Some(8), 0),
"// File: shell/shell.qml (Lines 1-8)\n\
\n\
extends ShellRoot {\n\
\x20 localPluginReloadTimer: Timer {\n\
\x20 interval: 150; // L3-3\n\
\x20 function fire() { /* 2 lines hidden: L6-L7 */ }\n\
\x20 } // L2-7\n\
\n\
} // L1-8\n\
\n"
);
}
#[test]
fn skeleton_renders_a_single_line_symbol_with_children_as_a_leaf() {
let syms = vec![
skeleton_row(
"rusqlite",
None,
"field",
"rusqlite",
"rusqlite = { workspace = true }",
(15, 15),
None,
),
skeleton_row(
"workspace",
Some("rusqlite"),
"property",
"workspace",
"workspace = true",
(15, 15),
None,
),
];
assert_eq!(
format_file_skeleton("Cargo.toml", &syms, Some(15), 0),
"// File: Cargo.toml (Lines 1-15)\n\
\n\
rusqlite = { workspace = true }; // L15-15\n"
);
}
#[test]
fn skeleton_renders_members_of_a_single_line_enum() {
let mut syms = vec![
skeleton_row(
"mode",
None,
"enum",
"DispatchMode",
"public enum DispatchMode",
(21, 21),
None,
),
skeleton_row(
"provision",
Some("mode"),
"enum_member",
"Provision",
"Provision",
(21, 21),
None,
),
skeleton_row(
"deprovision",
Some("mode"),
"enum_member",
"Deprovision",
"Deprovision",
(21, 21),
None,
),
skeleton_row(
"extension",
Some("mode"),
"enum_member",
"Extension",
"Extension",
(21, 21),
None,
),
];
for symbol in &mut syms {
symbol.language = "csharp".into();
}
let skeleton = format_file_skeleton("DispatchContext.cs", &syms, Some(21), 0);
for member in ["Provision", "Deprovision", "Extension"] {
assert!(skeleton.contains(member), "{skeleton}");
}
}
#[test]
fn a_hidden_body_names_the_functions_defined_inside_it_but_not_lambdas() {
let mut syms = vec![
skeleton_row(
"factory",
None,
"function",
"create_app",
"def create_app()",
(1, 9),
Some((2, 9)),
),
skeleton_row(
"hello",
Some("factory"),
"function",
"hello",
"def hello()",
(4, 5),
Some((5, 5)),
),
skeleton_row(
"lambda",
Some("factory"),
"function",
"lambda_7",
"lambda v: v",
(7, 7),
None,
),
];
for symbol in &mut syms {
symbol.language = "python".into();
}
let skeleton = format_file_skeleton("app.py", &syms, Some(9), 0);
assert!(
skeleton.contains("def create_app() { /* 8 lines hidden: L2-L9; defines `hello` */ }"),
"{skeleton}"
);
for symbol in &mut syms {
symbol.language = "cpp".into();
}
let cpp = format_file_skeleton("app.cpp", &syms, Some(9), 0);
assert!(!cpp.contains("defines"), "{cpp}");
syms[1].test_container = true;
let cpp_section = format_file_skeleton("app.cpp", &syms, Some(9), 0);
assert!(cpp_section.contains("defines `hello`"), "{cpp_section}");
}
#[test]
fn skeleton_keeps_plain_fields_and_function_locals_unchanged() {
let syms = vec![
skeleton_row(
"cfg",
None,
"struct",
"Config",
"pub struct Config",
(1, 3),
None,
),
skeleton_row(
"retries",
Some("cfg"),
"field",
"retries",
"pub retries: u32",
(2, 2),
None,
),
skeleton_row(
"run",
None,
"function",
"run",
"pub fn run()",
(5, 9),
Some((6, 8)),
),
skeleton_row(
"tmp",
Some("run"),
"variable",
"tmp",
"let tmp",
(7, 7),
None,
),
];
assert_eq!(
format_file_skeleton("src/lib.rs", &syms, Some(9), 0),
"// File: src/lib.rs (Lines 1-9)\n\
\n\
pub struct Config {\n\
\x20 pub retries: u32; // L2-2\n\
} // L1-3\n\
\n\
pub fn run() { /* 3 lines hidden: L6-L8 */ }\n"
);
}
#[test]
fn skeleton_marks_an_event_row_whose_signature_does_not_spell_it() {
let syms = vec![
skeleton_row(
"cls",
None,
"class",
"ColumnViewAttached",
"class ColumnViewAttached : public QObject",
(11, 52),
None,
),
skeleton_row(
"sig",
Some("cls"),
"event",
"indexChanged",
"void indexChanged()",
(47, 47),
None,
),
skeleton_row(
"qml",
None,
"event",
"clicked",
"signal clicked()",
(60, 60),
None,
),
];
assert_eq!(
format_file_skeleton("src/columnview.h", &syms, Some(60), 0),
"// File: src/columnview.h (Lines 1-60)\n\
\n\
class ColumnViewAttached : public QObject {\n\
\x20 void indexChanged(); // event L47-47\n\
} // L11-52\n\
\n\
signal clicked(); // L60-60\n"
);
}
fn labeled_site(
label: SiteLabel,
path: &str,
line: usize,
caller: &str,
basis: &str,
) -> LabeledSite {
LabeledSite {
site: ReferenceSite {
from_symbol_name: caller.into(),
from_symbol_id: "c1".into(),
to_symbol_name: "target".into(),
kind: "calls".into(),
path: path.into(),
start_line: Some(line),
start_column: Some(4),
occurrences: None,
target: None,
enclosing_symbol_name: None,
},
label,
basis: basis.into(),
end_line: None,
arg_count: None,
}
}
fn report_of(sites: Vec<LabeledSite>) -> ReferenceReport {
let count = |label| sites.iter().filter(|site| site.label == label).count();
ReferenceReport {
target: ReferenceTarget {
symbol_id: "t1".into(),
name: "from_mapping".into(),
display_name: "Config.from_mapping".into(),
kind: "method".into(),
path: "src/flask/config.py".into(),
line: 304,
definitions_with_name: 1,
},
counts: ReferenceCounts {
sites: sites.len(),
proven: count(SiteLabel::Proven),
likely: count(SiteLabel::Likely),
unresolved: count(SiteLabel::Unresolved),
excluded: count(SiteLabel::Excluded),
text_lines: sites.len(),
site_lines: sites.len(),
..ReferenceCounts::default()
},
sites,
text_only: Vec::new(),
text_scan: TextScanSummary {
files_scanned: 12,
files_total: 12,
complete: true,
elapsed_ms: 40,
skipped: None,
},
cannot_see: Vec::new(),
imports: Vec::new(),
overrides: Vec::new(),
overridden: Vec::new(),
constructions: Vec::new(),
variant_uses: Vec::new(),
registrations: Vec::new(),
tests: Vec::new(),
}
}
#[test]
fn the_report_header_has_three_lines_with_plural_counts() {
let mut report = report_of(vec![
labeled_site(
SiteLabel::Proven,
"src/flask/config.py",
302,
"Config.from_file",
"same file",
),
labeled_site(
SiteLabel::Likely,
"tests/test_config.py",
112,
"test_config",
"name only",
),
]);
report.counts.text_lines = 17;
report.counts.site_lines = 2;
report.counts.definition_lines = 2;
report.counts.import_lines = 0;
report.counts.text_only_lines = 13;
let text = format_reference_report(&report, 20);
assert_eq!(
text.lines().take(3).collect::<Vec<_>>(),
[
"References to method `Config.from_mapping` (src/flask/config.py:304)",
"Sites: 2 · proven 1 · likely 1 · unresolved 0 · excluded 0",
"Text: `from_mapping` is on 17 lines: 2 sites, 2 definitions, 0 imports, 13 text only.",
]
);
}
#[test]
fn the_report_header_uses_singular_forms_for_a_count_of_one() {
let mut report = report_of(vec![labeled_site(
SiteLabel::Proven,
"a.py",
3,
"caller",
"same file",
)]);
report.counts.text_lines = 1;
report.counts.site_lines = 1;
report.counts.definition_lines = 1;
report.counts.import_lines = 1;
report.counts.text_only_lines = 0;
let text = format_reference_report(&report, 20);
assert_eq!(
text.lines().nth(2),
Some("Text: `from_mapping` is on 1 line: 1 site, 1 definition, 1 import, 0 text only.")
);
}
#[test]
fn the_report_lists_each_group_in_label_order_with_its_basis() {
let report = report_of(vec![
labeled_site(SiteLabel::Proven, "a.py", 2, "first", "same file"),
labeled_site(SiteLabel::Likely, "l.py", 6, "second", "name only"),
labeled_site(SiteLabel::Unresolved, "u.py", 4, "third", "3 definitions"),
labeled_site(SiteLabel::Excluded, "x.py", 9, "other", "other language"),
]);
let text = format_reference_report(&report, 20);
let body = text.split_once("\n\n").unwrap().1;
assert_eq!(
body,
"Proven (1):\n\
- `first` [a.py:2] (kind: calls; same file)\n\
Likely (1):\n\
- `second` [l.py:6] (kind: calls; name only)\n\
Unresolved (1):\n\
- `third` [u.py:4] (kind: calls; 3 definitions)\n\
Excluded (1):\n\
- `other` [x.py:9] (kind: calls; other language)\n"
);
}
#[test]
fn a_group_with_no_sites_is_left_out() {
let report = report_of(vec![labeled_site(
SiteLabel::Likely,
"l.py",
6,
"second",
"name only",
)]);
let text = format_reference_report(&report, 20);
assert!(!text.contains("Proven ("), "{text}");
assert!(!text.contains("Excluded ("), "{text}");
assert!(text.contains("Likely (1):\n"), "{text}");
}
#[test]
fn a_report_without_sites_has_only_the_header() {
let report = report_of(Vec::new());
let text = format_reference_report(&report, 20);
assert_eq!(
text,
"References to method `Config.from_mapping` (src/flask/config.py:304)\n\
Sites: 0 · proven 0 · likely 0 · unresolved 0 · excluded 0\n\
Text: `from_mapping` is on 0 lines: 0 sites, 0 definitions, 0 imports, 0 text only.\n"
);
}
#[test]
fn caller_names_follow_the_lambda_and_no_enclosing_symbol_rules() {
let mut lambda = labeled_site(SiteLabel::Proven, "a.py", 1, "lambda_7", "same file");
lambda.site.enclosing_symbol_name = Some("outer".into());
let callerless = labeled_site(SiteLabel::Proven, "a.py", 2, "", "same file");
let report = report_of(vec![lambda, callerless]);
let text = format_reference_report(&report, 20);
assert!(
text.contains("- `<lambda> in outer` [a.py:1] (kind: calls; same file)\n"),
"{text}"
);
assert!(
text.contains("- (no enclosing symbol) [a.py:2] (kind: calls; same file)\n"),
"{text}"
);
}
#[test]
fn rows_past_the_limit_fold_into_one_line_per_label_with_per_file_counts() {
let mut sites = vec![labeled_site(
SiteLabel::Proven,
"a.py",
1,
"p1",
"same file",
)];
for (index, path) in ["b.py", "b.py", "b.py", "c.py", "c.py", "d.py"]
.iter()
.enumerate()
{
sites.push(labeled_site(
SiteLabel::Likely,
path,
10 + index,
"caller",
"name only",
));
}
sites.push(labeled_site(
SiteLabel::Excluded,
"z.py",
1,
"other",
"other language",
));
let report = report_of(sites);
let text = format_reference_report(&report, 3);
let body = text.split_once("\n\n").unwrap().1;
assert_eq!(
body,
"Proven (1):\n\
- `p1` [a.py:1] (kind: calls; same file)\n\
Likely (6):\n\
- `caller` [b.py:10] (kind: calls; name only)\n\
- `caller` [b.py:11] (kind: calls; name only)\n\
… 4 more likely sites in 3 files: c.py (2), b.py (1), d.py (1)\n\
Excluded (1):\n\
… 1 more excluded site in 1 file: z.py (1)\n"
);
}
#[test]
fn a_fold_line_names_at_most_five_files() {
let sites = ["a", "b", "c", "d", "e", "f", "g"]
.iter()
.map(|stem| {
labeled_site(
SiteLabel::Likely,
&format!("{stem}.py"),
1,
"caller",
"name only",
)
})
.collect();
let report = report_of(sites);
let text = format_reference_report(&report, 1);
assert!(
text.contains(
"… 6 more likely sites in 6 files: b.py (1), c.py (1), d.py (1), e.py (1), f.py (1), …\n"
),
"{text}"
);
}
#[test]
fn text_only_lines_list_per_file_and_fold_past_ten_files_and_twelve_lines() {
let mut report = report_of(Vec::new());
report.text_only = (0..12)
.map(|index| TextOnlyLines {
path: format!("docs/f{index:02}.rst"),
lines: if index == 0 {
(1..=15).collect()
} else {
vec![index + 1, index + 2]
},
})
.collect();
report.counts.text_only_lines = report.text_only.iter().map(|f| f.lines.len()).sum();
let text = format_reference_report(&report, 20);
let section = text.split_once("Text only (").unwrap().1;
let mut lines = section.lines();
assert_eq!(
lines.next(),
Some("37): docs, comments, strings, or code the index did not record")
);
assert_eq!(
lines.next(),
Some(" docs/f00.rst: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12")
);
assert_eq!(lines.next(), Some(" docs/f01.rst: 2, 3"));
assert_eq!(lines.nth(7), Some(" docs/f09.rst: 10, 11"));
assert_eq!(lines.next(), Some(" … 7 more lines in 3 files"));
assert_eq!(lines.next(), None);
}
#[test]
fn a_text_scan_cut_by_its_budget_adds_the_stopped_line() {
let mut report = report_of(Vec::new());
report.text_scan = TextScanSummary {
files_scanned: 410,
files_total: 17334,
complete: false,
elapsed_ms: 3012,
skipped: None,
};
let text = format_reference_report(&report, 20);
assert_eq!(
text.lines().nth(3),
Some("Text scan stopped after 3 s: 410 of 17334 files read; counts cover those files.")
);
}
#[test]
fn a_registration_without_a_verb_or_template_or_route_pattern_prints_what_it_has() {
let registration =
|pattern_id: &str, verb: Option<&str>, template: Option<&str>| Registration {
pattern_id: pattern_id.into(),
verb: verb.map(Into::into),
template: template.map(Into::into),
path: "app.py".into(),
line: 3,
by_name: false,
};
let mut report = report_of(Vec::new());
report.registrations = vec![
registration("flask.route.v1", Some("GET"), Some("/a")),
registration("axum.route.v1", None, Some("/b")),
registration("hapi.route.v1", Some("PUT"), None),
registration("rocket.mount.v1", Some("GET"), Some("/c")),
];
let text = format_reference_report(&report, 20);
assert!(
text.contains(
"Registrations (4):\n\
- route GET /a [app.py:3]\n\
- route /b [app.py:3]\n\
- route PUT hapi.route.v1 [app.py:3]\n\
- rocket.mount.v1 [app.py:3]\n"
),
"{text}"
);
}
#[test]
fn a_constructor_report_replaces_the_text_line_with_the_skip_line() {
let mut report = report_of(Vec::new());
report.text_scan.skipped = Some("Flask".into());
let text = format_reference_report(&report, 20);
assert_eq!(
text.lines().nth(2),
Some(
"Text: skipped for a constructor; its call sites spell the class name. Use find_references on `Flask`."
)
);
assert!(!text.contains("is on"), "{text}");
assert!(!text.contains("Text scan stopped"), "{text}");
}
#[test]
fn the_report_ends_with_the_blind_spots_and_the_import_summary() {
let mut report = report_of(vec![labeled_site(
SiteLabel::Proven,
"a.py",
2,
"first",
"same file",
)]);
report.cannot_see = vec![
"calls through getattr() or names built at runtime".into(),
"calls inside macro bodies".into(),
];
report.imports = vec![("b.py".into(), 1), ("c.py".into(), 4)];
let text = format_reference_report(&report, 20);
assert!(
text.ends_with(
"Not visible to this index: calls through getattr() or names built at runtime; calls inside macro bodies.\n\
Imported 2 times in 2 files: b.py:1, c.py:4 (lookup_symbol with kind=\"import\" lists them)\n"
),
"{text}"
);
}
}