#[must_use]
#[allow(dead_code)] pub(crate) fn compute_reachability(
entry_points: &HashSet<String>,
function_calls: &HashMap<String, HashSet<String>>,
) -> HashSet<String> {
let mut reachable: HashSet<String> = entry_points.clone();
let mut changed = true;
while changed {
changed = false;
let current_reachable = reachable.clone();
for reachable_func in ¤t_reachable {
if let Some(callees) = function_calls.get(reachable_func) {
for callee in callees {
if !reachable.contains(callee) {
reachable.insert(callee.clone());
changed = true;
}
}
}
}
}
reachable
}
#[must_use]
#[allow(dead_code)]
fn find_containing_function(
file_path: &str,
line_number: usize,
all_functions: &HashMap<String, (String, u32)>,
) -> Option<String> {
let mut current_function = None;
for (qualified_name, (func_file, func_line)) in all_functions {
if qualified_name.starts_with(file_path)
&& func_file == file_path
&& line_number >= *func_line as usize
{
current_function = Some(qualified_name.clone());
}
}
current_function
}
#[must_use]
#[allow(dead_code)]
fn find_calls_in_line(
line: &str,
caller: &str,
all_functions: &HashMap<String, (String, u32)>,
) -> Vec<String> {
let mut calls = Vec::new();
for callee_qualified in all_functions.keys() {
let callee_name = callee_qualified.split("::").last().unwrap_or("");
if !callee_name.is_empty()
&& line.contains(&format!("{callee_name}("))
&& !line.contains(&format!("fn {callee_name}"))
&& caller != callee_qualified
{
calls.push(callee_qualified.clone());
}
}
calls
}
#[must_use]
#[allow(dead_code)] pub(crate) fn detect_function_calls_in_lines(
file_path: &str,
lines: &[&str],
all_functions: &HashMap<String, (String, u32)>,
) -> HashMap<String, HashSet<String>> {
let mut function_calls: HashMap<String, HashSet<String>> = HashMap::new();
for (i, line) in lines.iter().enumerate() {
let line_number = i + 1;
if let Some(caller) = find_containing_function(file_path, line_number, all_functions) {
for callee in find_calls_in_line(line, &caller, all_functions) {
function_calls
.entry(caller.clone())
.or_default()
.insert(callee);
}
}
}
function_calls
}
#[must_use]
#[allow(dead_code)] pub(crate) fn classify_dead_functions_pure(
all_functions: &HashMap<String, (String, u32)>,
reachable: &HashSet<String>,
) -> Vec<(String, String, u32)> {
let mut dead_functions = Vec::new();
for (qualified_name, (file_path, line)) in all_functions {
if !reachable.contains(qualified_name) {
let function_name = qualified_name.split("::").last().unwrap_or("").to_string();
dead_functions.push((function_name, file_path.clone(), *line));
}
}
dead_functions
}
#[must_use]
#[allow(dead_code)] pub(crate) fn collect_functions_from_context(
files: &[crate::services::context::FileContext],
) -> (HashMap<String, (String, u32)>, HashSet<String>) {
use crate::services::context::AstItem;
let mut all_functions: HashMap<String, (String, u32)> = HashMap::new();
let mut entry_points: HashSet<String> = HashSet::new();
for file in files {
for item in &file.items {
if let AstItem::Function { name, line, .. } = item {
let qualified_name = format!("{}::{}", file.path, name);
all_functions.insert(qualified_name.clone(), (file.path.clone(), *line as u32));
if name == "main" || name.starts_with("pub ") {
entry_points.insert(qualified_name);
}
}
}
}
(all_functions, entry_points)
}
#[must_use]
#[allow(dead_code)] pub(crate) fn calculate_dead_percentage(total_functions: usize, dead_count: usize) -> f32 {
if total_functions > 0 {
(dead_count as f32 / total_functions as f32) * 100.0
} else {
0.0
}
}