unused-pub 0.1.3

A tool to detect unused public items (structs, enums, functions, etc.) in a Rust codebase.
Documentation
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>)> {
    // We can process files in parallel to parse and return (defs, usages) per file.
    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();

            // Parse AST
            let ast = match syn::parse_file(&content) {
                Ok(ast) => ast,
                Err(e) => {
                    // If parse fails (e.g. macro expansion issues), we just warn and skip?
                    // Or maybe just log it.
                    // In a tool like this, robustly ignoring one bad file is better than crashing.
                    if verbose {
                        eprintln!("Failed to parse {}: {}", file_path_str, e);
                    }
                    return Ok((Vec::new(), HashSet::new()));
                }
            };

            // 1. Collect Definitions
            let mut def_visitor = DefinitionVisitor::new(file_path_str.clone());
            def_visitor.visit_file(&ast);

            // 2. Collect Usages
            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 {
        // Apply filter if any are specified
        if !filter_set.is_empty() && !filter_set.contains(&def.kind.to_lowercase()) {
            continue;
        }

        if !usages.contains(&def.name) {
            // Special check: If it's a "main" function, it's often not "used" in Rust code but invoked by runtime.
            if def.name == "main" && def.kind == "fn" {
                continue;
            }

            unused.push(UnusedItem {
                name: def.name,
                kind: def.kind,
                location: def.location,
            });
        }
    }

    unused
}