use debtmap::analyzers::rust_call_graph::extract_call_graph;
use debtmap::priority::call_graph::FunctionId;
use std::path::PathBuf;
#[test]
fn test_new_constructor_method_disambiguation() {
let code = r#"
// File 1: Standalone function (DEAD CODE)
pub fn analyze_imports() {
println!("standalone - never called");
}
// File 2: Struct with methods
pub struct EnhancedImportResolver {
data: String,
}
impl EnhancedImportResolver {
pub fn new() -> Self {
EnhancedImportResolver { data: String::new() }
}
pub fn analyze_imports(&mut self) {
println!("method - actually called");
}
}
// File 3: Test that uses the TYPE::new() pattern
#[cfg(test)]
mod tests {
use super::*;
fn test_direct_import() {
let mut resolver = EnhancedImportResolver::new();
resolver.analyze_imports(); // Should call the METHOD, NOT standalone function
}
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("test.rs");
let call_graph = extract_call_graph(&parsed, &path);
let module_path = "test".to_string();
let standalone_id = FunctionId::with_module_path(
path.clone(),
"analyze_imports".to_string(),
3,
module_path.clone(),
);
let method_id = FunctionId::with_module_path(
path.clone(),
"EnhancedImportResolver::analyze_imports".to_string(),
17,
module_path.clone(),
);
let test_id = FunctionId::with_module_path(
path.clone(),
"tests::test_direct_import".to_string(),
27,
module_path.clone(),
);
println!("\n=== All functions found ===");
for func in call_graph.find_all_functions() {
let callers = call_graph.get_callers(&func);
println!(
" {} (line {}, module_path: '{}') - {} callers",
func.name,
func.line,
func.module_path,
callers.len()
);
for caller in callers {
println!(" <- {}", caller.name);
}
}
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 `resolver.analyze_imports()` where resolver is created via `EnhancedImportResolver::new()`, \
so it should ONLY call the METHOD, not the standalone function.",
standalone_id.line,
standalone_callers.len(),
standalone_callers
.iter()
.map(|c| format!("`{}` at line {}", c.name, c.line))
.collect::<Vec<_>>()
);
let method_callers = call_graph.get_callers(&method_id);
assert_eq!(
method_callers.len(),
1,
"Method `EnhancedImportResolver::analyze_imports()` should have 1 caller (the test), but has {}",
method_callers.len()
);
if !method_callers.is_empty() {
assert_eq!(
method_callers[0].name, "tests::test_direct_import",
"The test should be calling the method"
);
}
let test_callees = call_graph.get_callees(&test_id);
println!("\n=== Test callees ===");
for callee in &test_callees {
println!(" -> {} at line {}", callee.name, callee.line);
}
assert!(
test_callees
.iter()
.any(|c| c.name == "EnhancedImportResolver::analyze_imports"),
"Test should call `EnhancedImportResolver::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.line == 3),
"Test should NOT call standalone `analyze_imports()` function at line 3"
);
}
#[test]
fn test_builder_pattern_method_disambiguation() {
let code = r#"
pub fn build() {
println!("standalone - never called");
}
pub struct Builder {
value: i32,
}
impl Builder {
pub fn new() -> Self {
Builder { value: 0 }
}
pub fn build(&self) -> i32 {
self.value
}
}
fn use_builder() {
let builder = Builder::new();
let result = builder.build(); // Should call METHOD, not standalone function
}
"#;
let parsed = syn::parse_file(code).unwrap();
let path = PathBuf::from("builder.rs");
let call_graph = extract_call_graph(&parsed, &path);
let module_path = "builder".to_string();
let standalone =
FunctionId::with_module_path(path.clone(), "build".to_string(), 2, module_path.clone());
let method = FunctionId::with_module_path(
path.clone(),
"Builder::build".to_string(),
15,
module_path.clone(),
);
println!("\n=== Builder pattern test ===");
for func in call_graph.find_all_functions() {
let callers = call_graph.get_callers(&func);
println!(
" {} (line {}) - {} callers",
func.name,
func.line,
callers.len()
);
}
let standalone_callers = call_graph.get_callers(&standalone);
let method_callers = call_graph.get_callers(&method);
assert_eq!(
standalone_callers.len(),
0,
"Standalone `build()` 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 `Builder::build()` should have 1 caller"
);
}