use crate::visitor::{DefInfo, DefinitionVisitor, UsageVisitor};
use crate::UnusedItem;
use anyhow::Result;
use rayon::prelude::*;
use std::collections::HashSet;
use std::fs;
use std::path::PathBuf;
use syn::visit::Visit;
pub fn analyze(files: Vec<PathBuf>, verbose: bool) -> Result<(Vec<DefInfo>, HashSet<String>)> {
let results: Vec<Result<(Vec<DefInfo>, HashSet<String>)>> = files
.into_par_iter()
.map(|path| {
let content = fs::read_to_string(&path)?;
let file_path_str = path.to_string_lossy().to_string();
let ast = match syn::parse_file(&content) {
Ok(ast) => ast,
Err(e) => {
if verbose {
eprintln!("Failed to parse {}: {}", file_path_str, e);
}
return Ok((Vec::new(), HashSet::new()));
}
};
let mut def_visitor = DefinitionVisitor::new(file_path_str.clone());
def_visitor.visit_file(&ast);
let mut usage_visitor = UsageVisitor::default();
usage_visitor.visit_file(&ast);
Ok((def_visitor.definitions, usage_visitor.usages))
})
.collect();
let mut all_defs = Vec::new();
let mut all_usages = HashSet::new();
for res in results {
let (defs, usages) = res?;
all_defs.extend(defs);
all_usages.extend(usages);
}
Ok((all_defs, all_usages))
}
pub fn diff(
definitions: Vec<DefInfo>,
usages: HashSet<String>,
filters: Vec<String>,
) -> Vec<UnusedItem> {
let mut unused = Vec::new();
let filter_set: HashSet<String> = filters.into_iter().map(|s| s.to_lowercase()).collect();
for def in definitions {
if !filter_set.is_empty() && !filter_set.contains(&def.kind.to_lowercase()) {
continue;
}
if !usages.contains(&def.name) {
if def.name == "main" && def.kind == "fn" {
continue;
}
unused.push(UnusedItem {
name: def.name,
kind: def.kind,
location: def.location,
});
}
}
unused
}