use debtmap::analyzers::rust::RustAnalyzer;
use debtmap::analyzers::rust_call_graph::extract_call_graph;
use debtmap::analyzers::Analyzer;
use debtmap::priority::call_graph::FunctionId;
use debtmap::priority::scoring::classification::{
classify_debt_type_with_exclusions, is_dead_code_with_exclusions,
};
use debtmap::priority::DebtType;
use im::HashSet;
use std::path::PathBuf;
#[test]
fn test_rust_method_with_same_name_as_function_not_false_positive() {
let rust_code = r#"
use std::collections::HashMap;
pub struct DependencyGraph {
modules: Vec<String>,
}
#[derive(Debug)]
pub struct ModuleDependency {
pub name: String,
pub dependency_count: usize,
}
// Standalone function (also exported)
pub fn calculate_coupling_metrics(modules: &[String]) -> Vec<ModuleDependency> {
modules.iter().map(|name| ModuleDependency {
name: name.clone(),
dependency_count: 0,
}).collect()
}
impl DependencyGraph {
pub fn new() -> Self {
Self {
modules: Vec::new(),
}
}
// Method with same name as the standalone function - this should NOT be flagged as dead code
pub fn calculate_coupling_metrics(&self) -> Vec<ModuleDependency> {
self.modules.iter().map(|module| {
ModuleDependency {
name: module.clone(),
dependency_count: self.get_dependencies(module).len(),
}
}).collect()
}
fn get_dependencies(&self, _module: &str) -> Vec<String> {
vec![]
}
}
// Usage in analysis utils
pub fn analyze_dependencies() -> Vec<ModuleDependency> {
let dep_graph = DependencyGraph::new();
// This call should prevent the method from being marked as dead code
dep_graph.calculate_coupling_metrics()
}
// Usage in tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coupling_analysis() {
let graph = DependencyGraph::new();
// Another call that should prevent dead code detection
let metrics = graph.calculate_coupling_metrics();
assert!(metrics.is_empty());
}
}
"#;
let analyzer = RustAnalyzer::new();
let path = PathBuf::from("dependency_graph.rs");
let ast = analyzer.parse(rust_code, path.clone()).unwrap();
let metrics = analyzer.analyze(&ast);
let syntax_tree = syn::parse_file(rust_code).unwrap();
let call_graph = extract_call_graph(&syntax_tree, &path);
let method_func = metrics
.complexity
.functions
.iter()
.find(|f| {
f.name.contains("calculate_coupling_metrics") && f.name.contains("DependencyGraph::")
})
.expect("Should find DependencyGraph::calculate_coupling_metrics method");
let func_id = FunctionId::new(
path.clone(),
"DependencyGraph::calculate_coupling_metrics".to_string(),
method_func.line,
);
let framework_exclusions_im = HashSet::new();
let framework_exclusions_std: std::collections::HashSet<FunctionId> =
framework_exclusions_im.clone().into_iter().collect();
let is_dead = is_dead_code_with_exclusions(
method_func,
&call_graph,
&func_id,
&framework_exclusions_std,
None,
);
assert!(
!is_dead,
"DependencyGraph::calculate_coupling_metrics should NOT be marked as dead code because it has 2 callers: analyze_dependencies() and tests::test_coupling_analysis()"
);
let debt_types = classify_debt_type_with_exclusions(
method_func,
&call_graph,
&func_id,
&framework_exclusions_std,
None,
None, );
for debt_type in debt_types {
match debt_type {
DebtType::DeadCode { .. } => {
panic!("DependencyGraph::calculate_coupling_metrics should not be classified as DeadCode! This is the false positive we're testing for.");
}
_ => {
}
}
}
}
#[test]
fn test_rust_function_vs_method_distinction() {
let rust_code = r#"
pub struct Calculator {
value: i32,
}
// Standalone function - this one IS dead code
pub fn calculate(x: i32) -> i32 {
x * 2
}
impl Calculator {
// Method with same name - this one is NOT dead code
pub fn calculate(&self) -> i32 {
self.value * 3
}
}
pub fn use_calculator() -> i32 {
let calc = Calculator { value: 10 };
calc.calculate() // Only the method is called, not the standalone function
}
"#;
let analyzer = RustAnalyzer::new();
let path = PathBuf::from("calculator.rs");
let ast = analyzer.parse(rust_code, path.clone()).unwrap();
let metrics = analyzer.analyze(&ast);
let syntax_tree = syn::parse_file(rust_code).unwrap();
let call_graph = extract_call_graph(&syntax_tree, &path);
let standalone_func = metrics
.complexity
.functions
.iter()
.find(|f| {
f.name.contains("calculate")
&& !f.name.contains("Calculator::") && !f.name.contains("use_calculator")
})
.expect("Should find standalone calculate function");
let method_func = metrics
.complexity
.functions
.iter()
.find(|f| {
f.name.contains("calculate") && f.name.contains("Calculator::") })
.expect("Should find Calculator::calculate method");
let all_functions = call_graph.find_all_functions();
let standalone_func_id = all_functions
.iter()
.find(|f| f.name == "calculate" && !f.name.contains("::"))
.expect("Should find standalone calculate function");
let standalone_excluded = standalone_func.name == "main"
|| standalone_func.name.starts_with("_start")
|| standalone_func.is_test
|| standalone_func.name.starts_with("test_")
|| standalone_func.file.to_string_lossy().contains("/tests/")
|| standalone_func.in_test_module
|| standalone_func.name.contains("<closure@")
|| standalone_func.name.contains("extern")
|| standalone_func.name.starts_with("__")
|| standalone_func.is_trait_method;
let standalone_has_callers = !call_graph.get_callers(standalone_func_id).is_empty();
let standalone_is_dead = if !standalone_excluded && !standalone_has_callers {
true } else {
false
};
let method_func_id = all_functions
.iter()
.find(|f| f.name == "Calculator::calculate")
.expect("Should find Calculator::calculate method");
let method_excluded = method_func.name == "main"
|| method_func.name.starts_with("_start")
|| method_func.is_test
|| method_func.name.starts_with("test_")
|| method_func.file.to_string_lossy().contains("/tests/")
|| method_func.in_test_module
|| method_func.name.contains("<closure@")
|| method_func.name.contains("extern")
|| method_func.name.starts_with("__")
|| method_func.is_trait_method;
let method_has_callers = !call_graph.get_callers(method_func_id).is_empty();
let method_is_dead = if !method_excluded && !method_has_callers {
true } else {
false
};
assert!(
standalone_is_dead,
"Standalone calculate function should be marked as dead code. Found: {}",
standalone_is_dead
);
assert!(
!method_is_dead,
"Calculator::calculate method should NOT be marked as dead code"
);
}