use debtmap::analyzers::rust_call_graph::extract_call_graph;
use std::path::PathBuf;
#[test]
fn test_method_call_does_not_match_standalone_function() {
let code = r#"
// File 1: Standalone function (DEAD CODE)
#[allow(dead_code)]
pub fn analyze_imports(path: &str) -> String {
format!("standalone: {}", path)
}
// File 2: Struct with method (USED CODE)
pub struct ImportResolver {
prefix: String,
}
impl ImportResolver {
pub fn analyze_imports(&self, path: &str) -> String {
format!("{}: {}", self.prefix, path)
}
}
// File 3: Regular function that calls the METHOD (not standalone)
pub fn test_import_analysis() {
let resolver = ImportResolver { prefix: "TEST".to_string() };
let result = resolver.analyze_imports("module.py");
assert_eq!(result, "TEST: module.py");
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("test.rs");
let call_graph = extract_call_graph(&parsed, &path);
let all_functions = call_graph.find_all_functions();
println!("All functions found:");
for func in &all_functions {
println!(" - {} at line {}", func.name, func.line);
}
let standalone_id = all_functions
.iter()
.find(|f| f.name == "analyze_imports" && !f.name.contains("::"))
.expect("Should find standalone analyze_imports function");
let method_id = all_functions
.iter()
.find(|f| f.name == "ImportResolver::analyze_imports")
.expect("Should find ImportResolver::analyze_imports method");
let test_id = all_functions
.iter()
.find(|f| f.name == "test_import_analysis")
.expect("Should find test_import_analysis function");
let standalone_callers = call_graph.get_callers(standalone_id);
assert_eq!(
standalone_callers.len(),
0,
"BUG REPRODUCED! Standalone `analyze_imports()` at line {} shows {} caller(s): {:?}, but should have 0! \
The test calls the METHOD `resolver.analyze_imports()`, not the standalone function.",
standalone_id.line,
standalone_callers.len(),
standalone_callers.iter().map(|c| format!("{}:{}", c.name, c.line)).collect::<Vec<_>>()
);
let method_callers = call_graph.get_callers(method_id);
assert_eq!(
method_callers.len(),
1,
"Method `ImportResolver::analyze_imports()` should have 1 caller (the test)"
);
assert_eq!(
method_callers[0].name, "test_import_analysis",
"The test should be calling the method"
);
let test_callees = call_graph.get_callees(test_id);
println!("\nTest callees:");
for callee in &test_callees {
println!(" - {} at line {}", callee.name, callee.line);
}
assert!(
test_callees
.iter()
.any(|c| c.name == "ImportResolver::analyze_imports"),
"Test should call ImportResolver::analyze_imports (the method). Called: {:?}",
test_callees.iter().map(|c| &c.name).collect::<Vec<_>>()
);
assert!(
!test_callees
.iter()
.any(|c| c.name == "analyze_imports" && !c.name.contains("::")),
"Test should NOT call standalone analyze_imports() function"
);
}
#[test]
fn test_simple_reproduction_method_vs_function() {
let code = r#"
fn process() {
println!("standalone");
}
struct Handler;
impl Handler {
fn process(&self) {
println!("method");
}
}
fn caller() {
let h = Handler;
h.process(); // Calls the METHOD, not the standalone function
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("simple.rs");
let call_graph = extract_call_graph(&parsed, &path);
let all_functions = call_graph.find_all_functions();
println!("\nSimple test - All functions:");
for func in &all_functions {
let callers = call_graph.get_callers(func);
println!(
" {} (line {}) - {} callers",
func.name,
func.line,
callers.len()
);
}
let standalone = all_functions
.iter()
.find(|f| f.name == "process" && !f.name.contains("::"))
.expect("Should find standalone process function");
let method = all_functions
.iter()
.find(|f| f.name == "Handler::process")
.expect("Should find Handler::process method");
let standalone_callers = call_graph.get_callers(standalone);
let method_callers = call_graph.get_callers(method);
assert_eq!(
standalone_callers.len(),
0,
"Standalone process() should have 0 callers, but has {}: {:?}",
standalone_callers.len(),
standalone_callers
.iter()
.map(|c| &c.name)
.collect::<Vec<_>>()
);
assert_eq!(
method_callers.len(),
1,
"Method Handler::process() should have 1 caller"
);
}