pub mod boilerplate;
pub(crate) mod call_targets;
pub mod dead_code;
pub mod fragments;
pub mod functions;
pub mod match_patterns;
pub mod wildcards;
pub use boilerplate::BoilerplateFind;
pub use dead_code::{DeadCodeKind, DeadCodeWarning};
pub use fragments::FragmentGroup;
pub use functions::{DuplicateGroup, DuplicateKind};
use syn::visit::Visit;
use crate::adapters::shared::normalize::NormalizedToken;
pub(crate) trait FileVisitor {
fn reset_for_file(&mut self, file_path: &str);
}
pub(crate) fn visit_all_files<'a, V>(parsed: &'a [(String, String, syn::File)], visitor: &mut V)
where
V: FileVisitor + Visit<'a>,
{
parsed.iter().for_each(|(path, _, file)| {
visitor.reset_for_file(path);
syn::visit::visit_file(visitor, file);
});
}
pub struct FunctionHashEntry {
pub name: String,
pub qualified_name: String,
pub file: String,
pub line: usize,
pub hash: u64,
pub token_count: usize,
pub tokens: Vec<NormalizedToken>,
}
pub struct DeclaredFunction {
pub name: String,
pub qualified_name: String,
pub file: String,
pub line: usize,
pub is_test: bool,
pub is_main: bool,
pub is_trait_impl: bool,
pub has_allow_dead_code: bool,
pub is_api: bool,
}
pub(crate) fn collect_function_hashes(
parsed: &[(String, String, syn::File)],
config: &crate::config::sections::DuplicatesConfig,
) -> Vec<FunctionHashEntry> {
let mut collector = functions::FunctionCollector::new(config);
visit_all_files(parsed, &mut collector);
collector.entries
}
pub(crate) fn collect_declared_functions(
parsed: &[(String, String, syn::File)],
) -> Vec<DeclaredFunction> {
let mut collector = dead_code::DeclaredFnCollector::new();
visit_all_files(parsed, &mut collector);
collector.functions
}
pub(crate) use crate::adapters::shared::cfg_test::{has_cfg_test, has_test_attr};
fn has_allow_dead_code(attrs: &[syn::Attribute]) -> bool {
attrs
.iter()
.filter(|a| a.path().is_ident("allow"))
.any(allow_contains_dead_code)
}
fn allow_contains_dead_code(attr: &syn::Attribute) -> bool {
attr.parse_args_with(syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated)
.is_ok_and(|paths| paths.iter().any(|p| p.is_ident("dead_code")))
}
fn qualify_name(parent: &Option<String>, name: &str) -> String {
parent
.as_ref()
.map_or_else(|| name.to_string(), |p| [p.as_str(), "::", name].concat())
}
#[cfg(test)]
mod tests;