use ripvec_core::repo_map::{self, Definition};
use streaming_iterator::StreamingIterator as _;
fn parse_and_extract_lang(source: &str, ext: &str) -> Vec<Definition> {
let lang_config = ripvec_core::languages::config_for_extension(ext).expect("lang config");
let call_config = ripvec_core::languages::call_query_for_extension(ext).expect("call config");
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&lang_config.language)
.expect("set lang");
let tree = parser.parse(source, None).expect("parse source");
let mut defs = Vec::new();
let mut cursor = tree_sitter::QueryCursor::new();
let mut matches = cursor.matches(&lang_config.query, tree.root_node(), source.as_bytes());
while let Some(m) = matches.next() {
let mut name = String::new();
let mut def_node = None;
for cap in m.captures {
let cap_name = &lang_config.query.capture_names()[cap.index as usize];
if *cap_name == "name" {
name = source[cap.node.start_byte()..cap.node.end_byte()].to_string();
} else if *cap_name == "def" {
def_node = Some(cap.node);
}
}
if let Some(node) = def_node {
defs.push(Definition {
name,
kind: node.kind().to_string(),
start_line: node.start_position().row as u32 + 1,
end_line: node.end_position().row as u32 + 1,
scope: String::new(),
signature: None,
start_byte: node.start_byte() as u32,
end_byte: node.end_byte() as u32,
calls: vec![],
decorator: None,
lsp_kind_hint: None,
});
}
}
repo_map::extract_calls_pub(source, &call_config, &mut defs);
defs
}
fn parse_and_extract(source: &str) -> Vec<Definition> {
let lang_config = ripvec_core::languages::config_for_extension("rs").expect("rs lang config");
let call_config =
ripvec_core::languages::call_query_for_extension("rs").expect("rs call config");
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&lang_config.language)
.expect("set rs lang");
let tree = parser.parse(source, None).expect("parse source");
let mut defs = Vec::new();
let mut cursor = tree_sitter::QueryCursor::new();
let mut matches = cursor.matches(&lang_config.query, tree.root_node(), source.as_bytes());
while let Some(m) = matches.next() {
let mut name = String::new();
let mut def_node = None;
for cap in m.captures {
let cap_name = &lang_config.query.capture_names()[cap.index as usize];
if *cap_name == "name" {
name = source[cap.node.start_byte()..cap.node.end_byte()].to_string();
} else if *cap_name == "def" {
def_node = Some(cap.node);
}
}
if let Some(node) = def_node {
defs.push(Definition {
name,
kind: node.kind().to_string(),
start_line: node.start_position().row as u32 + 1,
end_line: node.end_position().row as u32 + 1,
scope: String::new(),
signature: None,
start_byte: node.start_byte() as u32,
end_byte: node.end_byte() as u32,
calls: vec![],
decorator: None,
lsp_kind_hint: None,
});
}
}
repo_map::extract_calls_pub(source, &call_config, &mut defs);
defs
}
fn dump(defs: &[Definition], label: &str) {
eprintln!("--- {} ---", label);
for d in defs {
eprintln!(
" DEF name={:?} kind={} bytes={}..{} calls={:?}",
d.name,
d.kind,
d.start_byte,
d.end_byte,
d.calls
.iter()
.map(|c| (&c.name, c.byte_offset))
.collect::<Vec<_>>()
);
}
}
#[test]
fn probe_a_simple_closure() {
let source = r#"
fn outer() {
let items = vec![1u32, 2, 3];
items.iter().for_each(|x| {
target(*x);
});
}
fn target(x: u32) {}
"#;
let defs = parse_and_extract(source);
dump(&defs, "simple closure");
let outer = defs.iter().find(|d| d.name == "outer").unwrap();
assert!(outer.calls.iter().any(|c| c.name == "target"), "PROBE A");
}
#[test]
fn probe_b_chained_destructured_closure_mimicking_build_graph() {
let source = r#"
pub fn build_graph(root: &str) -> Vec<u32> {
let files: Vec<u32> = vec![1, 2, 3];
let sources: Vec<u32> = vec![10, 20, 30];
files
.iter()
.zip(sources.iter())
.for_each(|(file, source)| {
if let Some(config) = Some(*file) {
if is_go_language(config) {
enrich_go_method_def_scopes(*source);
}
}
});
vec![]
}
fn is_go_language(c: u32) -> bool { c > 0 }
fn enrich_go_method_def_scopes(x: u32) {}
"#;
let defs = parse_and_extract(source);
dump(&defs, "chained destructured closure (build_graph shape)");
let bg = defs.iter().find(|d| d.name == "build_graph").unwrap();
let names: Vec<&str> = bg.calls.iter().map(|c| c.name.as_str()).collect();
eprintln!("build_graph.calls names: {:?}", names);
assert!(
bg.calls
.iter()
.any(|c| c.name == "enrich_go_method_def_scopes"),
"PROBE B FAIL: build_graph should record call to enrich_go_method_def_scopes; got {:?}",
names
);
}
#[test]
fn probe_c_par_iter_mut_zip_for_each_exact() {
let source = r#"
pub fn build_graph() {
let mut files: Vec<u32> = vec![1, 2, 3];
let raw_sources: Vec<u32> = vec![10, 20, 30];
files
.iter_mut()
.zip(raw_sources.iter())
.for_each(|(file, source)| {
if let Some(config) = Some(file) {
if true {
*config = *source;
extract_definitions(*source);
if true {
enrich_go_method_def_scopes(*source);
}
}
}
});
}
fn extract_definitions(s: u32) {}
fn enrich_go_method_def_scopes(s: u32) {}
"#;
let defs = parse_and_extract(source);
dump(
&defs,
"par_iter_mut + zip + for_each + nested if-let (mirror)",
);
let bg = defs.iter().find(|d| d.name == "build_graph").unwrap();
let names: Vec<&str> = bg.calls.iter().map(|c| c.name.as_str()).collect();
eprintln!("build_graph.calls names: {:?}", names);
assert!(
bg.calls.iter().any(|c| c.name == "extract_definitions"),
"PROBE C: should have extract_definitions; got {:?}",
names
);
assert!(
bg.calls
.iter()
.any(|c| c.name == "enrich_go_method_def_scopes"),
"PROBE C: should have enrich_go_method_def_scopes; got {:?}",
names
);
}
#[test]
fn probe_d_cross_file_qualified_call_via_build_graph() {
use std::fs;
use tempfile::tempdir;
let dir = tempdir().unwrap();
let src_dir = dir.path().join("src");
let tests_dir = dir.path().join("tests");
fs::create_dir_all(&src_dir).unwrap();
fs::create_dir_all(&tests_dir).unwrap();
fs::write(src_dir.join("lib.rs"), "pub mod repo_map;\n").unwrap();
fs::write(
src_dir.join("repo_map.rs"),
r#"
pub fn build_graph(root: &str) -> u32 {
enrich_helper(root);
0
}
fn enrich_helper(_: &str) {}
"#,
)
.unwrap();
fs::write(
tests_dir.join("integration.rs"),
r#"
#[test]
fn calls_build_graph() {
let _ = my_crate::repo_map::build_graph("x");
}
"#,
)
.unwrap();
fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"my_crate\"\nversion=\"0.0.1\"\nedition=\"2021\"\n[lib]\npath=\"src/lib.rs\"\n",
)
.unwrap();
let graph = repo_map::build_graph(dir.path()).expect("build_graph");
let bg_did = graph
.files
.iter()
.enumerate()
.find_map(|(fi, f)| {
f.defs
.iter()
.enumerate()
.find(|(_, d)| d.name == "build_graph")
.map(|(di, _)| (fi as u32, di as u32))
})
.expect("build_graph def must exist");
let test_did = graph
.files
.iter()
.enumerate()
.find_map(|(fi, f)| {
f.defs
.iter()
.enumerate()
.find(|(_, d)| d.name == "calls_build_graph")
.map(|(di, _)| (fi as u32, di as u32))
})
.expect("calls_build_graph def must exist");
eprintln!("build_graph def_id: file={} def={}", bg_did.0, bg_did.1);
eprintln!(
"calls_build_graph def_id: file={} def={}",
test_did.0, test_did.1
);
let test_flat = graph.def_offsets[test_did.0 as usize] + test_did.1 as usize;
let bg_flat = graph.def_offsets[bg_did.0 as usize] + bg_did.1 as usize;
let callees = &graph.def_callees[test_flat];
eprintln!("calls_build_graph.def_callees: {:?}", callees);
eprintln!(
"Looking for bg_flat={} (file={}, def={})",
bg_flat, bg_did.0, bg_did.1
);
let has_edge = callees.iter().any(|did| {
let cf = graph.def_offsets[did.0 as usize] + did.1 as usize;
cf == bg_flat
});
assert!(
has_edge,
"PROBE D: cross-file qualified call calls_build_graph → build_graph not in def_callees. \
The def edge that lsp_incoming_calls would walk is missing from the BFS graph. \
This is the actual I#57 cause: cross-file qualified-path call resolution gap."
);
}
#[test]
#[ignore = "diagnostic — runs on real ripvec workspace; useful for I#57 root-cause"]
fn i57_diagnostic_real_corpus() {
use ripvec_core::entry_points::{EntryPointDetector, EntryPointKind, RustEntryDetector};
use std::path::Path;
let ws_root = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap();
eprintln!("[diag] workspace root: {}", ws_root.display());
let graph = repo_map::build_graph(ws_root).expect("build_graph workspace");
eprintln!("[diag] graph.files.len() = {}", graph.files.len());
let mut bg: Option<(usize, usize)> = None;
let mut enrich: Option<(usize, usize)> = None;
for (fi, f) in graph.files.iter().enumerate() {
if f.path.ends_with("crates/ripvec-core/src/repo_map.rs")
|| f.path.ends_with("ripvec-core/src/repo_map.rs")
{
for (di, d) in f.defs.iter().enumerate() {
if d.name == "build_graph" && bg.is_none() {
bg = Some((fi, di));
}
if d.name == "enrich_go_method_def_scopes" && enrich.is_none() {
enrich = Some((fi, di));
}
}
}
}
let (bg_fi, bg_di) = bg.expect("build_graph must be present in repo_map.rs");
let (en_fi, en_di) = enrich.expect("enrich_go_method_def_scopes must be present");
eprintln!(
"[diag] build_graph file_idx={} def_idx={} path={}",
bg_fi, bg_di, graph.files[bg_fi].path
);
eprintln!(
"[diag] enrich_go_method_def_scopes file_idx={} def_idx={} path={}",
en_fi, en_di, graph.files[en_fi].path
);
let bg_flat = graph.def_offsets[bg_fi] + bg_di;
let en_flat = graph.def_offsets[en_fi] + en_di;
let bg_callees = &graph.def_callees[bg_flat];
eprintln!(
"[diag] build_graph.def_callees.len() = {}",
bg_callees.len()
);
let names_called: Vec<&str> = bg_callees
.iter()
.filter_map(|did| {
let f = graph.files.get(did.0 as usize)?;
let d = f.defs.get(did.1 as usize)?;
Some(d.name.as_str())
})
.collect();
eprintln!("[diag] build_graph called names = {:?}", names_called);
let bg_calls_enrich = bg_callees.iter().any(|did| {
let cflat = graph.def_offsets[did.0 as usize] + did.1 as usize;
cflat == en_flat
});
eprintln!(
"[diag] build_graph -> enrich_go_method_def_scopes edge: {}",
bg_calls_enrich
);
let en_callers = &graph.def_callers[en_flat];
eprintln!(
"[diag] enrich_go_method_def_scopes.def_callers.len() = {}",
en_callers.len()
);
for did in en_callers {
let f = &graph.files[did.0 as usize];
let d = &f.defs[did.1 as usize];
eprintln!(
"[diag] caller: {}::{} (file_idx={} def_idx={})",
f.path, d.name, did.0, did.1
);
}
let det = RustEntryDetector;
let test_rel = "crates/ripvec-core/tests/repo_map_extractor.rs";
let abs = ws_root.join(test_rel);
let src = std::fs::read_to_string(&abs).expect("read repo_map_extractor.rs");
let eps = det.detect(&src, &abs);
let test_eps: Vec<_> = eps
.iter()
.filter(|e| matches!(e.kind, EntryPointKind::Test))
.collect();
eprintln!(
"[diag] repo_map_extractor.rs: total entries={} Test entries={}",
eps.len(),
test_eps.len()
);
for ep in test_eps.iter().take(5) {
eprintln!("[diag] Test entry: {} @ line {}", ep.name, ep.line);
}
let test_fi = graph
.files
.iter()
.position(|f| f.path.ends_with("tests/repo_map_extractor.rs"));
eprintln!(
"[diag] graph file index for repo_map_extractor.rs: {:?}",
test_fi
);
if let Some(test_fi) = test_fi {
let path_in_graph = &graph.files[test_fi].path;
eprintln!("[diag] graph path: {:?}", path_in_graph);
let file = &graph.files[test_fi];
let mut matched = 0;
for ep in &test_eps {
for d in &file.defs {
if d.start_line == ep.line || d.name == ep.name {
matched += 1;
break;
}
}
}
eprintln!(
"[diag] matched {} of {} Test entries to defs",
matched,
test_eps.len()
);
for d in &file.defs {
if d.name == "test_build_graph_links_impl_method_to_trait_method" {
let flat = graph.def_offsets[test_fi]
+ file.defs.iter().position(|x| std::ptr::eq(x, d)).unwrap();
let callees = &graph.def_callees[flat];
eprintln!(
"[diag] test_build_graph_links_impl_method_to_trait_method def_callees.len={}",
callees.len()
);
let names: Vec<&str> = callees
.iter()
.filter_map(|did| {
let f = graph.files.get(did.0 as usize)?;
let de = f.defs.get(did.1 as usize)?;
Some(de.name.as_str())
})
.collect();
eprintln!(
"[diag] test_build_graph_links_impl_method_to_trait_method callee names: {:?}",
names
);
let calls_bg = callees.iter().any(|did| {
let f = graph.def_offsets[did.0 as usize] + did.1 as usize;
f == bg_flat
});
eprintln!(
"[diag] test_build_graph...links->build_graph edge present: {}",
calls_bg
);
break;
}
}
}
let tools_fi = graph
.files
.iter()
.position(|f| f.path.ends_with("ripvec-mcp/src/tools.rs"));
eprintln!("[diag] graph file index for tools.rs: {:?}", tools_fi);
if let Some(tools_fi) = tools_fi {
let tf = &graph.files[tools_fi];
let mut any_caller_in_tools = false;
for (di, _d) in tf.defs.iter().enumerate() {
let flat = graph.def_offsets[tools_fi] + di;
let callees = &graph.def_callees[flat];
if callees.iter().any(|did| {
let f = graph.def_offsets[did.0 as usize] + did.1 as usize;
f == bg_flat
}) {
any_caller_in_tools = true;
eprintln!(
"[diag] tools.rs caller of build_graph: {}",
tf.defs[di].name
);
}
}
eprintln!(
"[diag] any tools.rs def has edge to build_graph: {}",
any_caller_in_tools
);
}
}
#[test]
fn i55_c_struct_literal_edges_propagate_to_def_edges() {
use std::fs;
use tempfile::tempdir;
let dir = tempdir().unwrap();
fs::write(
dir.path().join("commands.c"),
r#"
typedef int redisCommandProc(int *c);
struct redisCommand {
const char *name;
redisCommandProc *proc;
int arity;
};
int getCommand(int *c) { return 0; }
int setCommand(int *c) { return 0; }
int delCommand(int *c) { return 0; }
struct redisCommand redisCommandTable[] = {
{"get", getCommand, 2},
{"set", setCommand, -3},
{"del", delCommand, -2},
};
"#,
)
.unwrap();
let graph = repo_map::build_graph(dir.path()).expect("build_graph");
let mut tbl: Option<(u32, u32)> = None;
let mut targets: std::collections::HashMap<&str, (u32, u32)> = std::collections::HashMap::new();
for (fi, f) in graph.files.iter().enumerate() {
for (di, d) in f.defs.iter().enumerate() {
let did = (fi as u32, di as u32);
if d.name == "redisCommandTable" {
tbl = Some(did);
}
for name in ["getCommand", "setCommand", "delCommand"] {
if d.name == name && d.kind == "function_definition" {
targets.insert(name, did);
}
}
}
}
let (tbl_fi, tbl_di) = tbl.expect("redisCommandTable def must be present (I#55)");
let tbl_flat = graph.def_offsets[tbl_fi as usize] + tbl_di as usize;
for name in ["getCommand", "setCommand", "delCommand"] {
let (t_fi, t_di) = *targets
.get(name)
.unwrap_or_else(|| panic!("{name} function_definition def must be present"));
let target_flat = graph.def_offsets[t_fi as usize] + t_di as usize;
let callees = &graph.def_callees[tbl_flat];
let has_edge = callees
.iter()
.any(|did| graph.def_offsets[did.0 as usize] + did.1 as usize == target_flat);
assert!(
has_edge,
"I#55: redisCommandTable.def_callees must include {name} (table → command \
implementation edge). Without this edge the BFS in find_dead_code treats {name} \
as unreachable, producing the redis command-collapse pathology."
);
}
}
#[test]
fn js_use_callback_closure_call_attributed_to_enclosing() {
let source = r#"
function Component() {
const handler = useCallback(() => {
dispatch({type: 'X'});
}, []);
return handler;
}
"#;
let defs = parse_and_extract_lang(source, "js");
dump(
&defs,
"js_use_callback_closure_call_attributed_to_enclosing",
);
let comp = defs
.iter()
.find(|d| d.name == "Component")
.expect("Component def");
let names: Vec<&str> = comp.calls.iter().map(|c| c.name.as_str()).collect();
eprintln!("Component.calls: {:?}", names);
assert!(
comp.calls.iter().any(|c| c.name == "useCallback"),
"Component must record call to useCallback; got {:?}",
names
);
assert!(
comp.calls.iter().any(|c| c.name == "dispatch"),
"Component must record call to dispatch (closure attribution); got {:?}",
names
);
}
#[test]
fn js_set_timeout_closure_call_attributed() {
let source = r#"
function init() {
setTimeout(() => {
startup();
}, 0);
}
"#;
let defs = parse_and_extract_lang(source, "js");
dump(&defs, "js_set_timeout_closure_call_attributed");
let init = defs.iter().find(|d| d.name == "init").expect("init def");
let names: Vec<&str> = init.calls.iter().map(|c| c.name.as_str()).collect();
eprintln!("init.calls: {:?}", names);
assert!(
init.calls.iter().any(|c| c.name == "setTimeout"),
"init must record call to setTimeout; got {:?}",
names
);
assert!(
init.calls.iter().any(|c| c.name == "startup"),
"init must record call to startup (closure attribution); got {:?}",
names
);
}
#[test]
fn js_array_map_closure_call_attributed() {
let source = r#"
function process(xs) {
return xs.map(x => transform(x));
}
"#;
let defs = parse_and_extract_lang(source, "js");
dump(&defs, "js_array_map_closure_call_attributed");
let proc = defs
.iter()
.find(|d| d.name == "process")
.expect("process def");
let names: Vec<&str> = proc.calls.iter().map(|c| c.name.as_str()).collect();
eprintln!("process.calls: {:?}", names);
assert!(
proc.calls.iter().any(|c| c.name == "map"),
"process must record call to map; got {:?}",
names
);
assert!(
proc.calls.iter().any(|c| c.name == "transform"),
"process must record call to transform (closure attribution); got {:?}",
names
);
}
#[test]
fn js_express_app_use_closure_attributed() {
let source = r#"
function setupApp() {
app.use((req, res, next) => {
authenticate(req);
next();
});
}
"#;
let defs = parse_and_extract_lang(source, "js");
dump(&defs, "js_express_app_use_closure_attributed");
let setup = defs
.iter()
.find(|d| d.name == "setupApp")
.expect("setupApp def");
let names: Vec<&str> = setup.calls.iter().map(|c| c.name.as_str()).collect();
eprintln!("setupApp.calls: {:?}", names);
assert!(
setup.calls.iter().any(|c| c.name == "authenticate"),
"setupApp must record call to authenticate (closure attribution); got {:?}",
names
);
assert!(
setup.calls.iter().any(|c| c.name == "next"),
"setupApp must record call to next (closure attribution); got {:?}",
names
);
}
#[test]
fn js_top_level_const_arrow_fn_kept_separate() {
let source = r#"
const handler = () => doThing();
"#;
let defs = parse_and_extract_lang(source, "js");
dump(&defs, "js_top_level_const_arrow_fn_kept_separate");
let h = defs
.iter()
.find(|d| d.name == "handler")
.expect("handler def");
let names: Vec<&str> = h.calls.iter().map(|c| c.name.as_str()).collect();
eprintln!("handler.calls: {:?}", names);
assert!(
h.calls.iter().any(|c| c.name == "doThing"),
"handler must record call to doThing (named const arrow fn owns its calls); got {:?}",
names
);
}
#[test]
fn js_nested_closure_attributes_to_named_ancestor() {
let source = r#"
function outer() {
items.forEach(item => {
items.filter(x => predicate(x));
});
}
"#;
let defs = parse_and_extract_lang(source, "js");
dump(&defs, "js_nested_closure_attributes_to_named_ancestor");
let outer = defs.iter().find(|d| d.name == "outer").expect("outer def");
let names: Vec<&str> = outer.calls.iter().map(|c| c.name.as_str()).collect();
eprintln!("outer.calls: {:?}", names);
assert!(
outer.calls.iter().any(|c| c.name == "predicate"),
"outer must record call to predicate (deep closure attribution); got {:?}",
names
);
}
#[test]
fn i55_c_designated_init_edges_propagate_to_def_edges() {
use std::fs;
use tempfile::tempdir;
let dir = tempdir().unwrap();
fs::write(
dir.path().join("driver.c"),
r#"
struct file_operations {
int (*open)(int);
int (*release)(int);
long (*read)(int);
};
static int my_open(int x) { return 0; }
static int my_release(int x) { return 0; }
static long my_read(int x) { return 0; }
static const struct file_operations my_fops = {
.open = my_open,
.release = my_release,
.read = my_read,
};
"#,
)
.unwrap();
let graph = repo_map::build_graph(dir.path()).expect("build_graph");
let mut fops: Option<(u32, u32)> = None;
let mut targets: std::collections::HashMap<&str, (u32, u32)> = std::collections::HashMap::new();
for (fi, f) in graph.files.iter().enumerate() {
for (di, d) in f.defs.iter().enumerate() {
let did = (fi as u32, di as u32);
if d.name == "my_fops" {
fops = Some(did);
}
for name in ["my_open", "my_release", "my_read"] {
if d.name == name && d.kind == "function_definition" {
targets.insert(name, did);
}
}
}
}
let (fops_fi, fops_di) = fops.expect("my_fops def must be present");
let fops_flat = graph.def_offsets[fops_fi as usize] + fops_di as usize;
for name in ["my_open", "my_release", "my_read"] {
let (t_fi, t_di) = *targets.get(name).expect(name);
let target_flat = graph.def_offsets[t_fi as usize] + t_di as usize;
let callees = &graph.def_callees[fops_flat];
let has_edge = callees
.iter()
.any(|did| graph.def_offsets[did.0 as usize] + did.1 as usize == target_flat);
assert!(
has_edge,
"I#55: my_fops.def_callees must include {name} (designated-init edge)"
);
}
}
fn bfs_reachable(
graph: &repo_map::RepoGraph,
seed_flat: usize,
) -> std::collections::HashSet<usize> {
let mut visited = std::collections::HashSet::new();
let mut stack = vec![seed_flat];
visited.insert(seed_flat);
while let Some(cur) = stack.pop() {
for &(fi, di) in &graph.def_callees[cur] {
let f = graph.def_offsets[fi as usize] + di as usize;
if visited.insert(f) {
stack.push(f);
}
}
}
visited
}
fn find_def_flat(graph: &repo_map::RepoGraph, name: &str) -> Option<usize> {
for (fi, f) in graph.files.iter().enumerate() {
for (di, d) in f.defs.iter().enumerate() {
if d.name == name {
return Some(graph.def_offsets[fi] + di);
}
}
}
None
}
#[test]
fn bfs_python_main_reaches_called_functions_via_build_graph() {
use std::fs;
use tempfile::tempdir;
let dir = tempdir().unwrap();
fs::write(
dir.path().join("lib.py"),
r#"
def helper():
return 1
def process(x):
y = helper()
return x + y
"#,
)
.unwrap();
fs::write(
dir.path().join("cli.py"),
r#"
from lib import process
def main():
args = parse_args()
result = process(args)
write(result)
def parse_args():
return 42
def write(r):
return r
"#,
)
.unwrap();
let graph = repo_map::build_graph(dir.path()).expect("build_graph");
let main_flat = find_def_flat(&graph, "main").expect("main def must exist");
let process_flat = find_def_flat(&graph, "process").expect("process def must exist");
let helper_flat = find_def_flat(&graph, "helper").expect("helper def must exist");
let parse_args_flat = find_def_flat(&graph, "parse_args").expect("parse_args def must exist");
let write_flat = find_def_flat(&graph, "write").expect("write def must exist");
let reachable = bfs_reachable(&graph, main_flat);
assert!(
reachable.contains(&parse_args_flat),
"C10 W1: BFS from `main` must reach `parse_args` (same-file `args = parse_args()`); \
reachable size = {}",
reachable.len()
);
assert!(
reachable.contains(&process_flat),
"C10 W1: BFS from `main` must reach `process` (cross-file `result = process(args)`); \
reachable size = {}",
reachable.len()
);
assert!(
reachable.contains(&write_flat),
"C10 W1: BFS from `main` must reach `write` (bare statement `write(result)`)"
);
assert!(
reachable.contains(&helper_flat),
"C10 W1: BFS from `main` must transitively reach `helper` via `process`; \
this is the 2-hop reachability that the pre-fix baseline could not achieve \
because `process`'s body-internal `y = helper()` was siphoned into a \
nested assignment def named `y`"
);
}
#[test]
fn bfs_js_main_reaches_called_functions_via_build_graph() {
use std::fs;
use tempfile::tempdir;
let dir = tempdir().unwrap();
fs::write(
dir.path().join("lib.js"),
r#"
function helper() { return 1; }
function process(x) {
const y = helper();
return x + y;
}
module.exports = { process: process };
"#,
)
.unwrap();
fs::write(
dir.path().join("cli.js"),
r#"
const lib = require('./lib');
function parseArgs() { return 42; }
function write(r) { return r; }
function main() {
const args = parseArgs();
const result = lib.process(args);
write(result);
}
"#,
)
.unwrap();
let graph = repo_map::build_graph(dir.path()).expect("build_graph");
let main_flat = find_def_flat(&graph, "main").expect("main def must exist");
let parse_args_flat = find_def_flat(&graph, "parseArgs").expect("parseArgs def must exist");
let write_flat = find_def_flat(&graph, "write").expect("write def must exist");
let reachable = bfs_reachable(&graph, main_flat);
assert!(
reachable.contains(&parse_args_flat),
"C10 W1: BFS from `main` must reach `parseArgs` (same-file `const args = parseArgs()`); \
reachable size = {}",
reachable.len()
);
assert!(
reachable.contains(&write_flat),
"C10 W1: BFS from `main` must reach `write` (bare statement `write(result)`)"
);
}
fn flat_callers(graph: &repo_map::RepoGraph, target_flat: usize) -> Vec<usize> {
graph.def_callers[target_flat]
.iter()
.map(|did| graph.def_offsets[did.0 as usize] + did.1 as usize)
.collect()
}
#[test]
fn i77_python_from_import_as_rewrites_call_edge_to_canonical_target() {
use std::fs;
use tempfile::tempdir;
let dir = tempdir().unwrap();
fs::write(
dir.path().join("mod.py"),
r#"
def bar():
return 1
"#,
)
.unwrap();
fs::write(
dir.path().join("caller.py"),
r#"
from mod import bar as baz
def use():
baz()
"#,
)
.unwrap();
let graph = repo_map::build_graph(dir.path()).expect("build_graph");
let bar_flat = find_def_flat(&graph, "bar").expect("bar def must exist");
let use_flat = find_def_flat(&graph, "use").expect("use def must exist");
let bar_callers = flat_callers(&graph, bar_flat);
assert!(
bar_callers.contains(&use_flat),
"I#77: `mod::bar` must list `caller::use` as a caller after \
alias-rewriting `baz()` → `mod::bar` (callers = {:?}, expected to contain {})",
bar_callers,
use_flat
);
}
#[test]
fn i77_python_import_as_dotted_call_resolves_through_alias() {
use std::fs;
use tempfile::tempdir;
let dir = tempdir().unwrap();
fs::write(
dir.path().join("mod2.py"),
r#"
def fn():
return 1
"#,
)
.unwrap();
fs::write(
dir.path().join("caller2.py"),
r#"
import mod2 as m2
def use2():
m2.fn()
"#,
)
.unwrap();
let graph = repo_map::build_graph(dir.path()).expect("build_graph");
let fn_flat = find_def_flat(&graph, "fn").expect("fn def must exist");
let use2_flat = find_def_flat(&graph, "use2").expect("use2 def must exist");
let fn_callers = flat_callers(&graph, fn_flat);
assert!(
fn_callers.contains(&use2_flat),
"I#77: `mod2::fn` must list `caller2::use2` as a caller after \
alias-rewriting `m2.fn()` → `mod2::fn` (callers = {:?}, expected to contain {})",
fn_callers,
use2_flat
);
}
#[test]
fn i77_python_function_local_alias_does_not_leak_to_sibling() {
use std::fs;
use tempfile::tempdir;
let dir = tempdir().unwrap();
fs::write(
dir.path().join("mod.py"),
r#"
def bar():
return 1
"#,
)
.unwrap();
fs::write(
dir.path().join("scoped.py"),
r#"
def inner_use():
from mod import bar as baz
baz()
def sibling():
baz()
"#,
)
.unwrap();
let graph = repo_map::build_graph(dir.path()).expect("build_graph");
let bar_flat = find_def_flat(&graph, "bar").expect("bar def must exist");
let inner_use_flat = find_def_flat(&graph, "inner_use").expect("inner_use def must exist");
let sibling_flat = find_def_flat(&graph, "sibling").expect("sibling def must exist");
let bar_callers = flat_callers(&graph, bar_flat);
assert!(
bar_callers.contains(&inner_use_flat),
"I#77 scope-locality: `inner_use` (which has the local alias) MUST \
reach `mod::bar` via `baz()` (callers = {:?}, expected to contain {})",
bar_callers,
inner_use_flat
);
assert!(
!bar_callers.contains(&sibling_flat),
"I#77 scope-locality: `sibling` (no local alias, no module-level \
alias either) must NOT reach `mod::bar` — function-local alias \
must not leak to siblings (callers = {:?}, expected NOT to contain {})",
bar_callers,
sibling_flat
);
}