use std::collections::{BTreeSet, HashMap, HashSet};
use std::path::{Path, PathBuf};
use cargo_metadata::MetadataCommand;
use proc_macro2::Span;
use syn::visit::{self, Visit};
use crate::deep::{DeepContext, DeepError, FileId};
use crate::finding::{EvidenceClass, Finding, Location, OneBasedLine, Origin, Severity};
use crate::functions::{type_name, walk_functions};
use crate::ingest::{SourceFile, Workspace};
use crate::rules::boundaries::module_path_for_file;
pub const UNUSED_PUB_WORKSPACE_RULE: &str = "unused-pub-workspace";
pub const UNUSED_PUB_WORKSPACE_RULE_REVISION: u32 = 1;
pub const UNUSED_PUB_API_RULE: &str = "unused-pub-api";
pub const UNUSED_PUB_API_RULE_REVISION: u32 = 1;
pub const DEAD_ENUM_VARIANT_RULE: &str = "dead-enum-variant";
pub const DEAD_ENUM_VARIANT_RULE_REVISION: u32 = 1;
pub const TEST_ONLY_PUB_RULE: &str = "test-only-pub";
pub const TEST_ONLY_PUB_RULE_REVISION: u32 = 1;
pub const UNREACHABLE_FROM_ENTRY_RULE: &str = "unreachable-from-entry";
pub const UNREACHABLE_FROM_ENTRY_RULE_REVISION: u32 = 1;
pub const CRATE_COUPLING_RULE: &str = "crate-coupling";
pub const CRATE_COUPLING_RULE_REVISION: u32 = 1;
pub const MODULE_COUPLING_RULE: &str = "module-coupling";
pub const MODULE_COUPLING_RULE_REVISION: u32 = 1;
#[derive(Debug)]
pub enum DeadCodeError {
Deep(DeepError),
Io(PathBuf, std::io::Error),
Parse(PathBuf, syn::Error),
Metadata(cargo_metadata::Error),
}
impl std::fmt::Display for DeadCodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Deep(err) => write!(f, "{err}"),
Self::Io(path, err) => fmt_io_error(f, path, err),
Self::Parse(path, err) => fmt_parse_error(f, path, err),
Self::Metadata(err) => write!(f, "failed to read cargo metadata: {err}"),
}
}
}
impl std::error::Error for DeadCodeError {}
pub(crate) fn fmt_io_error(
f: &mut std::fmt::Formatter<'_>,
path: &Path,
err: &std::io::Error,
) -> std::fmt::Result {
write!(f, "{}: failed to read file: {err}", path.display())
}
pub(crate) fn fmt_parse_error(
f: &mut std::fmt::Formatter<'_>,
path: &Path,
err: &syn::Error,
) -> std::fmt::Result {
write!(f, "{}: failed to parse: {err}", path.display())
}
#[derive(Debug, Default)]
pub struct WorkspaceDeadCode {
pub findings: Vec<Finding>,
pub errors: Vec<DeadCodeError>,
pub checked: usize,
}
pub(crate) struct TypeItemSite<'ast> {
pub(crate) qualified_name: String,
pub(crate) ident_span: Span,
pub(crate) vis: &'ast syn::Visibility,
}
macro_rules! visit_emitted_type_item {
($method:ident, $node:ty, $traverse:ident) => {
fn $method(&mut self, node: &'ast $node) {
self.emit(&node.ident.to_string(), node.ident.span(), &node.vis);
visit::$traverse(self, node);
}
};
}
macro_rules! visit_item_mod_tracking_path {
() => {
fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
if node.content.is_some() {
self.path.push(node.ident.to_string());
visit::visit_item_mod(self, node);
self.path.pop();
} else {
visit::visit_item_mod(self, node);
}
}
};
}
fn joined_qualified_name(path: &[String], name: &str) -> String {
if path.is_empty() {
name.to_string()
} else {
format!("{}::{name}", path.join("::"))
}
}
pub(crate) fn walk_type_items<'ast>(
file: &'ast syn::File,
on_item: impl FnMut(TypeItemSite<'ast>),
) {
struct Walker<F> {
path: Vec<String>,
on_item: F,
}
impl<F> Walker<F> {
fn qualified_name(&self, name: &str) -> String {
joined_qualified_name(&self.path, name)
}
}
impl<'ast, F: FnMut(TypeItemSite<'ast>)> Walker<F> {
fn emit(&mut self, name: &str, ident_span: Span, vis: &'ast syn::Visibility) {
if name == "_" {
return;
}
let qualified_name = self.qualified_name(name);
(self.on_item)(TypeItemSite {
qualified_name,
ident_span,
vis,
});
}
}
impl<'ast, F: FnMut(TypeItemSite<'ast>)> Visit<'ast> for Walker<F> {
visit_item_mod_tracking_path!();
visit_emitted_type_item!(visit_item_struct, syn::ItemStruct, visit_item_struct);
visit_emitted_type_item!(visit_item_enum, syn::ItemEnum, visit_item_enum);
fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
self.emit(&node.ident.to_string(), node.ident.span(), &node.vis);
self.path.push(node.ident.to_string());
visit::visit_item_trait(self, node);
self.path.pop();
}
fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
self.path.push(type_name(&node.self_ty));
visit::visit_item_impl(self, node);
self.path.pop();
}
visit_emitted_type_item!(visit_item_const, syn::ItemConst, visit_item_const);
visit_emitted_type_item!(visit_item_static, syn::ItemStatic, visit_item_static);
visit_emitted_type_item!(
visit_impl_item_const,
syn::ImplItemConst,
visit_impl_item_const
);
visit_emitted_type_item!(
visit_impl_item_type,
syn::ImplItemType,
visit_impl_item_type
);
}
let mut walker = Walker {
path: Vec::new(),
on_item,
};
walker.visit_file(file);
}
struct EnumVariantSite<'ast> {
qualified_name: String,
variant_name: String,
ident_span: Span,
vis: &'ast syn::Visibility,
}
fn walk_enum_variants<'ast>(file: &'ast syn::File, on_variant: impl FnMut(EnumVariantSite<'ast>)) {
struct Walker<F> {
path: Vec<String>,
on_variant: F,
}
impl<F> Walker<F> {
fn qualified_name(&self, name: &str) -> String {
joined_qualified_name(&self.path, name)
}
}
impl<'ast, F: FnMut(EnumVariantSite<'ast>)> Visit<'ast> for Walker<F> {
visit_item_mod_tracking_path!();
fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
let enum_qualified_name = self.qualified_name(&node.ident.to_string());
for variant in &node.variants {
let variant_name = variant.ident.to_string();
(self.on_variant)(EnumVariantSite {
qualified_name: format!("{enum_qualified_name}::{variant_name}"),
variant_name,
ident_span: variant.ident.span(),
vis: &node.vis,
});
}
visit::visit_item_enum(self, node);
}
}
let mut walker = Walker {
path: Vec::new(),
on_variant,
};
walker.visit_file(file);
}
fn file_constructs_variant(ast: &syn::File, variant_name: &str) -> bool {
struct ConstructionVisitor<'a> {
variant_name: &'a str,
found: bool,
in_pattern: bool,
}
fn path_ends_with(path: &syn::Path, name: &str) -> bool {
path.segments
.last()
.is_some_and(|segment| segment.ident == name)
}
impl<'a> ConstructionVisitor<'a> {
fn mark_if_constructs_variant(&mut self, path: &syn::Path) {
if !self.in_pattern && path_ends_with(path, self.variant_name) {
self.found = true;
}
}
}
impl<'a, 'ast> Visit<'ast> for ConstructionVisitor<'a> {
fn visit_pat(&mut self, node: &'ast syn::Pat) {
let previously_in_pattern = self.in_pattern;
self.in_pattern = true;
visit::visit_pat(self, node);
self.in_pattern = previously_in_pattern;
}
fn visit_expr(&mut self, node: &'ast syn::Expr) {
let previously_in_pattern = self.in_pattern;
self.in_pattern = false;
visit::visit_expr(self, node);
self.in_pattern = previously_in_pattern;
}
fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
self.mark_if_constructs_variant(&node.path);
visit::visit_expr_path(self, node);
}
fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
self.mark_if_constructs_variant(&node.path);
visit::visit_expr_struct(self, node);
}
}
let mut visitor = ConstructionVisitor {
variant_name,
found: false,
in_pattern: false,
};
visitor.visit_file(ast);
visitor.found
}
fn load_workspace_metadata(
workspace_root: &Path,
) -> Result<cargo_metadata::Metadata, cargo_metadata::Error> {
MetadataCommand::new()
.manifest_path(workspace_root.join("Cargo.toml"))
.exec()
}
fn proc_macro_exposed_crates(
workspace_root: &Path,
) -> Result<HashSet<String>, cargo_metadata::Error> {
let metadata = load_workspace_metadata(workspace_root)?;
let proc_macro_packages: HashSet<&cargo_metadata::PackageId> = metadata
.packages
.iter()
.filter(|package| {
package
.targets
.iter()
.any(cargo_metadata::Target::is_proc_macro)
})
.map(|package| &package.id)
.collect();
let Some(resolve) = &metadata.resolve else {
return Ok(HashSet::new());
};
let mut exposed = HashSet::new();
for member_id in &metadata.workspace_members {
let Some(node) = resolve.nodes.iter().find(|node| &node.id == member_id) else {
continue;
};
let has_direct_proc_macro_dep = node.deps.iter().any(|dep| {
proc_macro_packages.contains(&dep.pkg)
&& dep.dep_kinds.iter().any(|dep_kind| {
matches!(
dep_kind.kind,
cargo_metadata::DependencyKind::Normal
| cargo_metadata::DependencyKind::Build
)
})
});
if has_direct_proc_macro_dep
&& let Some(package) = metadata.packages.iter().find(|pkg| &pkg.id == member_id)
{
exposed.insert(package.name.clone());
}
}
Ok(exposed)
}
fn publishable_crates(workspace_root: &Path) -> Result<HashSet<String>, cargo_metadata::Error> {
let metadata = load_workspace_metadata(workspace_root)?;
Ok(metadata
.packages
.iter()
.filter(|package| metadata.workspace_members.contains(&package.id))
.filter(|package| {
package
.publish
.as_ref()
.is_none_or(|registries| !registries.is_empty())
})
.map(|package| package.name.clone())
.collect())
}
fn top_level_module_bucket(
crate_root: &Path,
file_path: &Path,
krate_name: &str,
) -> Option<String> {
let module_path = module_path_for_file(crate_root, file_path)?;
if module_path.is_empty() {
return Some(format!("{krate_name}::<root>"));
}
let top_level_segment = module_path.split("::").next().unwrap_or(&module_path);
Some(format!("{krate_name}::{top_level_segment}"))
}
pub(crate) fn offset_and_line(span: Span) -> (u32, usize) {
(span.byte_range().start as u32, span.start().line)
}
pub(crate) fn read_and_parse_file(path: &Path) -> Result<syn::File, DeadCodeError> {
let source =
std::fs::read_to_string(path).map_err(|err| DeadCodeError::Io(path.to_path_buf(), err))?;
syn::parse_file(&source).map_err(|err| DeadCodeError::Parse(path.to_path_buf(), err))
}
fn parsed_file_or_report(report: &mut WorkspaceDeadCode, path: &Path) -> Option<syn::File> {
match read_and_parse_file(path) {
Ok(ast) => Some(ast),
Err(err) => {
report.errors.push(err);
None
}
}
}
pub(crate) fn for_each_parsed_file(
workspace: &Workspace,
errors: &mut Vec<DeadCodeError>,
mut on_file: impl FnMut(&SourceFile, &syn::File),
) {
for krate in &workspace.crates {
for file in &krate.source_files {
if !file.kind.is_locally_reportable() {
continue;
}
match read_and_parse_file(&file.path) {
Ok(ast) => on_file(file, &ast),
Err(err) => errors.push(err),
}
}
}
}
fn checked_position(
report: &mut WorkspaceDeadCode,
file_id: FileId,
offset: u32,
) -> ra_ap_ide::FilePosition {
report.checked += 1;
ra_ap_ide::FilePosition {
file_id,
offset: offset.into(),
}
}
fn referencing_files_or_bail(
analysis: &ra_ap_ide::Analysis,
position: ra_ap_ide::FilePosition,
include_tests: bool,
report: &mut WorkspaceDeadCode,
) -> Option<HashSet<FileId>> {
match crate::deep::referencing_files(analysis, position, include_tests) {
Ok(referencing) => Some(referencing),
Err(err) => {
report.errors.push(DeadCodeError::Deep(err));
None
}
}
}
#[allow(clippy::too_many_arguments)]
fn dead_code_finding(
rule_id: &str,
severity: Severity,
evidence_class: EvidenceClass,
file: &SourceFile,
line: usize,
qualified_name: &str,
evidence: serde_json::Value,
limitations: Option<Vec<String>>,
) -> Finding {
Finding {
id: format!("{rule_id}:{}:{qualified_name}", file.path.display()).into(),
rule: rule_id.into(),
severity,
location: Location {
file: file.path.clone(),
line: OneBasedLine::new(line).expect("source line numbers are 1-based"),
item_path: qualified_name.to_string(),
},
evidence_class,
origin: Origin::Code,
evidence: Some(evidence),
limitations,
caused_by: Vec::new(),
causes: Vec::new(),
}
}
fn accumulate_coupling_edges(
referencing: &HashSet<FileId>,
file_id: FileId,
crate_of_file: &HashMap<FileId, &str>,
module_of_file: &HashMap<FileId, String>,
krate_name: &str,
edge_counts: &mut HashMap<(String, String), u32>,
module_edge_counts: &mut HashMap<(String, String), u32>,
) {
for referencing_file in referencing {
if let Some(&referencing_crate) = crate_of_file.get(referencing_file)
&& referencing_crate != krate_name
{
*edge_counts
.entry((krate_name.to_string(), referencing_crate.to_string()))
.or_insert(0) += 1;
}
if let (Some(owner_module), Some(referencing_module)) = (
module_of_file.get(&file_id),
module_of_file.get(referencing_file),
) && owner_module != referencing_module
{
*module_edge_counts
.entry((owner_module.clone(), referencing_module.clone()))
.or_insert(0) += 1;
}
}
}
#[allow(clippy::too_many_arguments)]
fn check_item(
analysis: &ra_ap_ide::Analysis,
crate_of_file: &HashMap<FileId, &str>,
module_of_file: &HashMap<FileId, String>,
entry_keys: &std::collections::HashSet<(FileId, u32)>,
proc_macro_exposed: &HashSet<String>,
file: &SourceFile,
file_id: FileId,
krate_name: &str,
qualified_name: &str,
offset: u32,
line: usize,
include_tests: bool,
rule_id: &str,
severity: Severity,
evidence_class: EvidenceClass,
reason: &str,
edge_counts: &mut HashMap<(String, String), u32>,
module_edge_counts: &mut HashMap<(String, String), u32>,
report: &mut WorkspaceDeadCode,
) {
let position = checked_position(report, file_id, offset);
let Some(referencing) = referencing_files_or_bail(analysis, position, include_tests, report)
else {
return;
};
accumulate_coupling_edges(
&referencing,
file_id,
crate_of_file,
module_of_file,
krate_name,
edge_counts,
module_edge_counts,
);
if is_used_externally(&referencing, crate_of_file, krate_name) {
return;
}
let evidence = serde_json::json!({
"tier": "deep",
"searched_crates": searched_crates_count(crate_of_file),
"references_found": referencing.len(),
"root_set_size": entry_keys.len(),
"reason": reason,
});
push_finding_if_unreachable(
analysis,
entry_keys,
position,
include_tests,
proc_macro_exposed,
krate_name,
rule_id,
severity,
evidence_class,
file,
line,
qualified_name,
evidence,
report,
);
}
fn is_used_externally(
referencing: &HashSet<FileId>,
crate_of_file: &HashMap<FileId, &str>,
krate_name: &str,
) -> bool {
referencing.iter().any(|referencing_file| {
crate_of_file
.get(referencing_file)
.is_some_and(|owner| *owner != krate_name)
})
}
fn searched_crates_count(crate_of_file: &HashMap<FileId, &str>) -> usize {
crate_of_file
.values()
.copied()
.collect::<HashSet<&str>>()
.len()
}
#[allow(clippy::too_many_arguments)]
fn push_finding_if_unreachable(
analysis: &ra_ap_ide::Analysis,
entry_keys: &std::collections::HashSet<(FileId, u32)>,
position: ra_ap_ide::FilePosition,
include_tests: bool,
proc_macro_exposed: &HashSet<String>,
krate_name: &str,
rule_id: &str,
severity: Severity,
evidence_class: EvidenceClass,
file: &SourceFile,
line: usize,
qualified_name: &str,
evidence: serde_json::Value,
report: &mut WorkspaceDeadCode,
) {
match crate::reachability::is_reachable_from_entry(
analysis,
entry_keys,
position,
include_tests,
) {
Ok(true) => {}
Ok(false) => {
let limitations = proc_macro_exposed
.contains(krate_name)
.then(|| vec!["proc_macro_expansion_disabled".to_string()]);
report.findings.push(dead_code_finding(
rule_id,
severity,
evidence_class,
file,
line,
qualified_name,
evidence,
limitations,
));
}
Err(err) => report.errors.push(reachability_error(err)),
}
}
const UNUSED_PUB_WORKSPACE_REASON: &str = "no reference from another workspace crate and \
unreachable from any recognized entry point (fn main in a [[bin]] or [[example]] target)";
const UNUSED_PUB_API_REASON: &str = "no reference found within the examined workspace; this \
crate is published, so external ecosystem usage is not inferable and expected";
pub(crate) fn reachability_error(err: crate::reachability::ReachabilityError) -> DeadCodeError {
use crate::reachability::ReachabilityError;
match err {
ReachabilityError::Deep(deep_err) => DeadCodeError::Deep(deep_err),
ReachabilityError::Io(path, io_err) => DeadCodeError::Io(path, io_err),
ReachabilityError::Parse(path, parse_err) => DeadCodeError::Parse(path, parse_err),
ReachabilityError::UnknownItem(item) => {
DeadCodeError::Deep(DeepError::Cancelled(format!("unknown item: {item}")))
}
ReachabilityError::AmbiguousItem(item, _) => {
DeadCodeError::Deep(DeepError::Cancelled(format!("ambiguous item: {item}")))
}
}
}
#[allow(clippy::too_many_arguments)]
fn check_enum_variant(
analysis: &ra_ap_ide::Analysis,
file_path_by_id: &HashMap<FileId, PathBuf>,
file: &SourceFile,
file_id: FileId,
qualified_name: &str,
variant_name: &str,
offset: u32,
line: usize,
include_tests: bool,
report: &mut WorkspaceDeadCode,
) {
let position = checked_position(report, file_id, offset);
let Some(referencing) = referencing_files_or_bail(analysis, position, include_tests, report)
else {
return;
};
let mut construction_found = false;
for referencing_file_id in &referencing {
let Some(path) = file_path_by_id.get(referencing_file_id) else {
continue;
};
let Some(ast) = parsed_file_or_report(report, path) else {
continue;
};
if file_constructs_variant(&ast, variant_name) {
construction_found = true;
break;
}
}
if construction_found {
return;
}
let evidence = serde_json::json!({
"tier": "deep",
"referencing_files": referencing.len(),
"reason": "no construction site found in the examined workspace view",
});
report.findings.push(dead_code_finding(
DEAD_ENUM_VARIANT_RULE,
Severity::Warn,
EvidenceClass::BoundedSemantic,
file,
line,
qualified_name,
evidence,
None,
));
}
fn reachable_or_bail(
analysis: &ra_ap_ide::Analysis,
entry_keys: &std::collections::HashSet<(FileId, u32)>,
position: ra_ap_ide::FilePosition,
include_tests: bool,
report: &mut WorkspaceDeadCode,
) -> Option<bool> {
match crate::reachability::is_reachable_from_entry(
analysis,
entry_keys,
position,
include_tests,
) {
Ok(reachable) => Some(reachable),
Err(err) => {
report.errors.push(reachability_error(err));
None
}
}
}
#[allow(clippy::too_many_arguments)]
fn check_test_only_pub(
analysis: &ra_ap_ide::Analysis,
crate_of_file: &HashMap<FileId, &str>,
entry_keys_production: &std::collections::HashSet<(FileId, u32)>,
entry_keys_all: &std::collections::HashSet<(FileId, u32)>,
file: &SourceFile,
file_id: FileId,
krate_name: &str,
qualified_name: &str,
offset: u32,
line: usize,
report: &mut WorkspaceDeadCode,
) {
let position = checked_position(report, file_id, offset);
let Some(referencing) = referencing_files_or_bail(analysis, position, true, report) else {
return;
};
if is_used_externally(&referencing, crate_of_file, krate_name) {
return;
}
let Some(production_reachable) =
reachable_or_bail(analysis, entry_keys_production, position, false, report)
else {
return;
};
if production_reachable {
return;
}
let Some(all_reachable) = reachable_or_bail(analysis, entry_keys_all, position, true, report)
else {
return;
};
if !all_reachable {
return;
}
let evidence = serde_json::json!({
"tier": "deep",
"searched_crates": searched_crates_count(crate_of_file),
"references_found": referencing.len(),
"root_set_size_production": entry_keys_production.len(),
"root_set_size_all": entry_keys_all.len(),
"reason": "reachable only through #[cfg(test)]/test-target code in the examined \
workspace view",
});
report.findings.push(dead_code_finding(
TEST_ONLY_PUB_RULE,
Severity::Warn,
EvidenceClass::BoundedSemantic,
file,
line,
qualified_name,
evidence,
None,
));
}
const UNREACHABLE_FROM_ENTRY_REASON: &str = "not reachable from any recognized entry point \
(fn main in a [[bin]] or [[example]] target, a #[test]/#[bench] function, or an \
FFI/wasm-bindgen export) in the examined reachability view";
#[allow(clippy::too_many_arguments)]
fn check_unreachable_from_entry(
analysis: &ra_ap_ide::Analysis,
entry_keys: &std::collections::HashSet<(FileId, u32)>,
proc_macro_exposed: &HashSet<String>,
file: &SourceFile,
file_id: FileId,
krate_name: &str,
qualified_name: &str,
offset: u32,
line: usize,
include_tests: bool,
report: &mut WorkspaceDeadCode,
) {
let position = checked_position(report, file_id, offset);
let evidence = serde_json::json!({
"tier": "deep",
"root_set_size": entry_keys.len(),
"reason": UNREACHABLE_FROM_ENTRY_REASON,
});
push_finding_if_unreachable(
analysis,
entry_keys,
position,
include_tests,
proc_macro_exposed,
krate_name,
UNREACHABLE_FROM_ENTRY_RULE,
Severity::Warn,
EvidenceClass::BoundedSemantic,
file,
line,
qualified_name,
evidence,
report,
);
}
fn build_file_maps<'a>(
workspace: &'a Workspace,
ctx: &DeepContext,
) -> Result<
(
HashMap<FileId, &'a str>,
HashMap<FileId, PathBuf>,
HashMap<FileId, String>,
),
DeadCodeError,
> {
let mut crate_of_file: HashMap<FileId, &str> = HashMap::new();
let mut file_path_by_id: HashMap<FileId, PathBuf> = HashMap::new();
let mut module_of_file: HashMap<FileId, String> = HashMap::new();
for krate in &workspace.crates {
for file in &krate.source_files {
if let Some(file_id) = ctx.file_id(&file.path).map_err(DeadCodeError::Deep)? {
crate_of_file.insert(file_id, krate.name.as_str());
file_path_by_id.insert(file_id, file.path.clone());
if let Some(bucket) = top_level_module_bucket(&krate.root, &file.path, &krate.name)
{
module_of_file.insert(file_id, bucket);
}
}
}
}
Ok((crate_of_file, file_path_by_id, module_of_file))
}
fn compute_entry_key_sets(
workspace: &Workspace,
ctx: &DeepContext,
) -> Result<
(
std::collections::HashSet<(FileId, u32)>,
std::collections::HashSet<(FileId, u32)>,
),
DeadCodeError,
> {
let entries_production = crate::reachability::entry_point_positions(workspace, ctx, false)
.map_err(reachability_error)?;
let entry_keys_production = crate::reachability::entry_keys_from(&entries_production);
let entries_all = crate::reachability::entry_point_positions(workspace, ctx, true)
.map_err(reachability_error)?;
let entry_keys_all = crate::reachability::entry_keys_from(&entries_all);
Ok((entry_keys_production, entry_keys_all))
}
fn load_proc_macro_and_publishable_sets(
workspace_root: &Path,
report: &mut WorkspaceDeadCode,
) -> (HashSet<String>, HashSet<String>) {
let proc_macro_exposed = match proc_macro_exposed_crates(workspace_root) {
Ok(exposed) => exposed,
Err(err) => {
report.errors.push(DeadCodeError::Metadata(err));
HashSet::new()
}
};
let publishable = match publishable_crates(workspace_root) {
Ok(publishable) => publishable,
Err(err) => {
report.errors.push(DeadCodeError::Metadata(err));
HashSet::new()
}
};
(proc_macro_exposed, publishable)
}
#[allow(clippy::too_many_arguments)]
fn check_pub_site(
analysis: &ra_ap_ide::Analysis,
crate_of_file: &HashMap<FileId, &str>,
module_of_file: &HashMap<FileId, String>,
entry_keys: &std::collections::HashSet<(FileId, u32)>,
entry_keys_production: &std::collections::HashSet<(FileId, u32)>,
entry_keys_all: &std::collections::HashSet<(FileId, u32)>,
proc_macro_exposed: &HashSet<String>,
file: &SourceFile,
file_id: FileId,
krate_name: &str,
qualified_name: &str,
offset: u32,
line: usize,
include_tests: bool,
rule_id: &str,
severity: Severity,
evidence_class: EvidenceClass,
reason: &str,
edge_counts: &mut HashMap<(String, String), u32>,
module_edge_counts: &mut HashMap<(String, String), u32>,
report: &mut WorkspaceDeadCode,
) {
check_item(
analysis,
crate_of_file,
module_of_file,
entry_keys,
proc_macro_exposed,
file,
file_id,
krate_name,
qualified_name,
offset,
line,
include_tests,
rule_id,
severity,
evidence_class,
reason,
edge_counts,
module_edge_counts,
report,
);
check_test_only_pub(
analysis,
crate_of_file,
entry_keys_production,
entry_keys_all,
file,
file_id,
krate_name,
qualified_name,
offset,
line,
report,
);
}
#[allow(clippy::too_many_arguments)]
fn scan_file_for_dead_code(
analysis: &ra_ap_ide::Analysis,
crate_of_file: &HashMap<FileId, &str>,
module_of_file: &HashMap<FileId, String>,
file_path_by_id: &HashMap<FileId, PathBuf>,
entry_keys: &std::collections::HashSet<(FileId, u32)>,
entry_keys_production: &std::collections::HashSet<(FileId, u32)>,
entry_keys_all: &std::collections::HashSet<(FileId, u32)>,
proc_macro_exposed: &HashSet<String>,
ast: &syn::File,
file: &SourceFile,
file_id: FileId,
krate_name: &str,
include_tests: bool,
rule_id: &str,
severity: Severity,
evidence_class: EvidenceClass,
reason: &str,
edge_counts: &mut HashMap<(String, String), u32>,
module_edge_counts: &mut HashMap<(String, String), u32>,
report: &mut WorkspaceDeadCode,
) {
walk_functions(ast, |site| {
let (offset, line) = offset_and_line(site.ident_span);
if let Some(syn::Visibility::Public(_)) = site.vis {
check_pub_site(
analysis,
crate_of_file,
module_of_file,
entry_keys,
entry_keys_production,
entry_keys_all,
proc_macro_exposed,
file,
file_id,
krate_name,
&site.qualified_name,
offset,
line,
include_tests,
rule_id,
severity,
evidence_class,
reason,
edge_counts,
module_edge_counts,
report,
);
return;
}
if let Some(syn::Visibility::Inherited | syn::Visibility::Restricted(_)) = site.vis {
check_unreachable_from_entry(
analysis,
entry_keys,
proc_macro_exposed,
file,
file_id,
krate_name,
&site.qualified_name,
offset,
line,
include_tests,
report,
);
}
});
walk_type_items(ast, |site| {
let (offset, line) = offset_and_line(site.ident_span);
if matches!(site.vis, syn::Visibility::Public(_)) {
check_pub_site(
analysis,
crate_of_file,
module_of_file,
entry_keys,
entry_keys_production,
entry_keys_all,
proc_macro_exposed,
file,
file_id,
krate_name,
&site.qualified_name,
offset,
line,
include_tests,
rule_id,
severity,
evidence_class,
reason,
edge_counts,
module_edge_counts,
report,
);
return;
}
check_unreachable_from_entry(
analysis,
entry_keys,
proc_macro_exposed,
file,
file_id,
krate_name,
&site.qualified_name,
offset,
line,
include_tests,
report,
);
});
walk_enum_variants(ast, |site| {
if !matches!(site.vis, syn::Visibility::Public(_)) {
return;
}
check_enum_variant(
analysis,
file_path_by_id,
file,
file_id,
&site.qualified_name,
&site.variant_name,
site.ident_span.byte_range().start as u32,
site.ident_span.start().line,
include_tests,
report,
);
});
}
pub fn analyze_workspace(
workspace: &Workspace,
include_tests: bool,
) -> Result<WorkspaceDeadCode, DeadCodeError> {
let ctx = DeepContext::load(&workspace.root).map_err(DeadCodeError::Deep)?;
let analysis = ctx.analysis();
let (crate_of_file, file_path_by_id, module_of_file) = build_file_maps(workspace, &ctx)?;
let (entry_keys_production, entry_keys_all) = compute_entry_key_sets(workspace, &ctx)?;
let entry_keys = if include_tests {
&entry_keys_all
} else {
&entry_keys_production
};
let mut report = WorkspaceDeadCode::default();
let mut edge_counts: HashMap<(String, String), u32> = HashMap::new();
let mut module_edge_counts: HashMap<(String, String), u32> = HashMap::new();
let (proc_macro_exposed, publishable) =
load_proc_macro_and_publishable_sets(&workspace.root, &mut report);
for krate in &workspace.crates {
let (rule_id, severity, evidence_class, reason) = if publishable.contains(&krate.name) {
(
UNUSED_PUB_API_RULE,
Severity::Info,
EvidenceClass::Heuristic,
UNUSED_PUB_API_REASON,
)
} else {
(
UNUSED_PUB_WORKSPACE_RULE,
Severity::Warn,
EvidenceClass::BoundedSemantic,
UNUSED_PUB_WORKSPACE_REASON,
)
};
for file in &krate.source_files {
if !file.kind.is_locally_reportable() {
continue;
}
let Some(file_id) = ctx.file_id(&file.path).map_err(DeadCodeError::Deep)? else {
continue;
};
let Some(ast) = parsed_file_or_report(&mut report, &file.path) else {
continue;
};
scan_file_for_dead_code(
&analysis,
&crate_of_file,
&module_of_file,
&file_path_by_id,
entry_keys,
&entry_keys_production,
&entry_keys_all,
&proc_macro_exposed,
&ast,
file,
file_id,
&krate.name,
include_tests,
rule_id,
severity,
evidence_class,
reason,
&mut edge_counts,
&mut module_edge_counts,
&mut report,
);
}
}
report
.findings
.extend(crate_coupling_findings(workspace, &edge_counts));
report
.findings
.extend(module_coupling_findings(workspace, &module_edge_counts));
Ok(report)
}
type CouplingSets<'a> = HashMap<&'a str, BTreeSet<&'a str>>;
type CouplingMetrics<'a> = (usize, usize, f64, Vec<&'a str>, Vec<&'a str>);
fn afferent_efferent_sets(
edge_counts: &HashMap<(String, String), u32>,
) -> (CouplingSets<'_>, CouplingSets<'_>) {
let mut afferent: CouplingSets<'_> = HashMap::new();
let mut efferent: CouplingSets<'_> = HashMap::new();
for (owner, referencer) in edge_counts.keys() {
afferent
.entry(owner.as_str())
.or_default()
.insert(referencer.as_str());
efferent
.entry(referencer.as_str())
.or_default()
.insert(owner.as_str());
}
(afferent, efferent)
}
fn coupling_metrics<'a>(
key: &str,
afferent: &CouplingSets<'a>,
efferent: &CouplingSets<'a>,
) -> Option<CouplingMetrics<'a>> {
let afferent_set = afferent.get(key);
let efferent_set = efferent.get(key);
let ca = afferent_set.map_or(0, BTreeSet::len);
let ce = efferent_set.map_or(0, BTreeSet::len);
if ca + ce == 0 {
return None;
}
let instability = ce as f64 / (ca + ce) as f64;
let efferent_list = efferent_set
.map(|set| set.iter().copied().collect::<Vec<_>>())
.unwrap_or_default();
let afferent_list = afferent_set
.map(|set| set.iter().copied().collect::<Vec<_>>())
.unwrap_or_default();
Some((ca, ce, instability, efferent_list, afferent_list))
}
fn coupling_finding(
id: String,
rule_id: &str,
file: PathBuf,
item_path: String,
evidence: serde_json::Value,
) -> Finding {
Finding {
id: id.into(),
rule: rule_id.into(),
severity: Severity::Info,
location: Location {
file,
line: OneBasedLine::FIRST,
item_path,
},
evidence_class: EvidenceClass::Heuristic,
origin: Origin::Code,
evidence: Some(evidence),
limitations: None,
caused_by: Vec::new(),
causes: Vec::new(),
}
}
fn crate_coupling_findings(
workspace: &Workspace,
edge_counts: &HashMap<(String, String), u32>,
) -> Vec<Finding> {
let (afferent, efferent) = afferent_efferent_sets(edge_counts);
let mut findings = Vec::new();
for krate in &workspace.crates {
let Some((ca, ce, instability, efferent_crates, afferent_crates)) =
coupling_metrics(krate.name.as_str(), &afferent, &efferent)
else {
continue;
};
findings.push(coupling_finding(
format!("{CRATE_COUPLING_RULE}:{}", krate.name),
CRATE_COUPLING_RULE,
krate.manifest_path.clone(),
krate.name.clone(),
serde_json::json!({
"tier": "deep",
"krate": krate.name,
"efferent_coupling": ce,
"afferent_coupling": ca,
"instability": instability,
"efferent_crates": efferent_crates,
"afferent_crates": afferent_crates,
}),
));
}
findings
}
fn module_coupling_findings(
workspace: &Workspace,
edge_counts: &HashMap<(String, String), u32>,
) -> Vec<Finding> {
let (afferent, efferent) = afferent_efferent_sets(edge_counts);
let mut modules: BTreeSet<&str> = BTreeSet::new();
for (owner, referencer) in edge_counts.keys() {
modules.insert(owner.as_str());
modules.insert(referencer.as_str());
}
let manifest_path_by_crate: HashMap<&str, &Path> = workspace
.crates
.iter()
.map(|krate| (krate.name.as_str(), krate.manifest_path.as_path()))
.collect();
let mut findings = Vec::new();
for module in modules {
let Some((ca, ce, instability, efferent_modules, afferent_modules)) =
coupling_metrics(module, &afferent, &efferent)
else {
continue;
};
let krate_name = module.split("::").next().unwrap_or(module);
let manifest_path = manifest_path_by_crate
.get(krate_name)
.copied()
.unwrap_or_else(|| Path::new(""));
findings.push(coupling_finding(
format!("{MODULE_COUPLING_RULE}:{module}"),
MODULE_COUPLING_RULE,
manifest_path.to_path_buf(),
module.to_string(),
serde_json::json!({
"tier": "deep",
"module": module,
"efferent_coupling": ce,
"afferent_coupling": ca,
"instability": instability,
"efferent_modules": efferent_modules,
"afferent_modules": afferent_modules,
}),
));
}
findings
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
use crate::test_util::TempDir;
fn load_single_crate_workspace(dir: &TempDir, lib_source: &str) -> Workspace {
load_single_crate_workspace_with_edition(dir, "2021", lib_source)
}
fn load_single_crate_workspace_with_edition(
dir: &TempDir,
edition: &str,
lib_source: &str,
) -> Workspace {
std::fs::write(
dir.join("Cargo.toml"),
format!(
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "{edition}"
"#
),
)
.unwrap();
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(dir.join("src/lib.rs"), lib_source).unwrap();
crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap()
}
fn write_crate(dir: &TempDir, name: &str, deps: &[(&str, &str)], lib_source: &str) {
std::fs::create_dir_all(dir.join(name).join("src")).unwrap();
let mut manifest =
format!("[package]\nname = \"{name}\"\nversion = \"0.1.0\"\nedition = \"2021\"\n");
if !deps.is_empty() {
manifest.push_str("\n[dependencies]\n");
for (dep_name, rel_path) in deps {
manifest.push_str(&format!("{dep_name} = {{ path = \"{rel_path}\" }}\n"));
}
}
std::fs::write(dir.join(name).join("Cargo.toml"), manifest).unwrap();
std::fs::write(dir.join(name).join("src/lib.rs"), lib_source).unwrap();
}
fn write_workspace_manifest(dir: &TempDir, members: &[&str]) {
let members_toml = members
.iter()
.map(|m| format!("\"{m}\""))
.collect::<Vec<_>>()
.join(", ");
std::fs::write(
dir.join("Cargo.toml"),
format!("[workspace]\nmembers = [{members_toml}]\nresolver = \"2\"\n"),
)
.unwrap();
}
#[test]
fn a_pub_fn_called_from_another_workspace_crate_is_not_flagged() {
let dir = TempDir::new("dead-code-cross-crate");
write_crate(
&dir,
"core",
&[],
r#"pub fn used_by_consumer() -> i32 {
1
}
pub fn never_called() -> i32 {
2
}
"#,
);
write_crate(
&dir,
"consumer",
&[("core", "../core")],
r#"pub fn run() -> i32 {
core::used_by_consumer()
}
"#,
);
write_workspace_manifest(&dir, &["core", "consumer"]);
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let names: Vec<_> = report
.findings
.iter()
.map(|f| f.location.item_path.as_str())
.collect();
assert!(
!names.contains(&"used_by_consumer"),
"called from `consumer`, a different workspace crate — must not be flagged"
);
assert!(
names.contains(&"never_called"),
"never referenced anywhere — must be flagged"
);
}
#[test]
fn a_completely_unused_private_fn_is_flagged_unreachable_from_entry() {
let dir = TempDir::new("dead-code-private-fn");
let workspace = load_single_crate_workspace(
&dir,
r#"fn private_and_unused() -> i32 {
1
}
"#,
);
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(report.checked, 1);
assert_eq!(report.findings.len(), 1, "{:?}", report.findings);
let finding = &report.findings[0];
assert_eq!(finding.rule, UNREACHABLE_FROM_ENTRY_RULE);
assert_eq!(finding.severity, Severity::Warn);
assert_eq!(finding.evidence_class, EvidenceClass::BoundedSemantic);
assert_eq!(finding.location.item_path, "private_and_unused");
let evidence = finding.evidence.as_ref().expect("evidence must be present");
assert_eq!(evidence["tier"], "deep");
assert!(evidence["reason"].is_string());
}
#[test]
fn a_private_fn_called_from_main_is_not_flagged_unreachable_from_entry() {
let dir = TempDir::new("dead-code-private-fn-reachable-from-main");
std::fs::create_dir_all(dir.join("src/bin")).unwrap();
std::fs::write(
dir.join("Cargo.toml"),
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "2021"
"#,
)
.unwrap();
std::fs::write(
dir.join("src/lib.rs"),
r#"fn private_helper() -> i32 {
1
}
pub fn call_helper() -> i32 {
private_helper()
}
"#,
)
.unwrap();
std::fs::write(
dir.join("src/bin/tool.rs"),
r#"fn main() {
dead_code_fixture::call_helper();
}
"#,
)
.unwrap();
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert!(
!report
.findings
.iter()
.any(|f| f.location.item_path == "private_helper"),
"private_helper is transitively reachable from main via call_helper — must not be \
flagged: {:?}",
report.findings
);
}
#[test]
fn a_private_fn_called_from_a_test_is_not_flagged_unreachable_from_entry() {
let dir = TempDir::new("dead-code-private-fn-reachable-from-test");
let workspace = load_single_crate_workspace(
&dir,
r#"fn private_helper() -> i32 {
1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_test() {
assert_eq!(private_helper(), 1);
}
}
"#,
);
let report = analyze_workspace(&workspace, true).unwrap();
assert!(
!report
.findings
.iter()
.any(|f| f.location.item_path == "private_helper"),
"private_helper is reachable from the #[test] fn a_test — must not be flagged: {:?}",
report.findings
);
}
#[test]
fn a_test_fn_itself_is_not_flagged_unreachable_from_entry() {
let dir = TempDir::new("dead-code-test-fn-itself");
let workspace = load_single_crate_workspace(
&dir,
r#"#[cfg(test)]
mod tests {
#[test]
fn a_test() {
assert_eq!(1, 1);
}
}
"#,
);
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(report.checked, 1, "a_test itself must still be queried");
assert!(
report.findings.is_empty(),
"a_test is itself a recognized entry point — it is trivially reachable from itself, \
not \"unreachable from entry\": {:?}",
report.findings
);
}
#[test]
fn a_pub_item_genuinely_unreachable_is_not_flagged_unreachable_from_entry() {
let dir = TempDir::new("dead-code-pub-item-not-unreachable-from-entry");
let workspace = load_single_crate_workspace(
&dir,
r#"pub fn never_called() -> i32 {
1
}
"#,
);
let report = analyze_workspace(&workspace, true).unwrap();
let rules: HashSet<&str> = report
.findings
.iter()
.filter(|f| f.location.item_path == "never_called")
.map(|f| f.rule.as_str())
.collect();
assert!(
!rules.contains(UNREACHABLE_FROM_ENTRY_RULE),
"a pub item's unreachability is unused-pub-workspace/unused-pub-api's territory, not \
unreachable-from-entry's: {:?}",
report.findings
);
assert!(
rules.contains(UNUSED_PUB_WORKSPACE_RULE) || rules.contains(UNUSED_PUB_API_RULE),
"control: never_called must still be flagged by the pub-item rule family: {:?}",
report.findings
);
}
#[test]
fn finding_shape_matches_the_documented_contract() {
let dir = TempDir::new("dead-code-finding-shape");
std::fs::write(
dir.join("Cargo.toml"),
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "2021"
publish = false
"#,
)
.unwrap();
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(
dir.join("src/lib.rs"),
r#"pub fn never_called() -> i32 {
1
}
"#,
)
.unwrap();
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(report.findings.len(), 1);
let finding = &report.findings[0];
assert_eq!(finding.rule, UNUSED_PUB_WORKSPACE_RULE);
assert_eq!(finding.severity, Severity::Warn);
assert_eq!(finding.origin, Origin::Code);
assert_eq!(finding.evidence_class, EvidenceClass::BoundedSemantic);
assert_eq!(finding.location.item_path, "never_called");
let evidence = finding.evidence.as_ref().expect("evidence must be present");
assert_eq!(evidence["tier"], "deep");
assert_eq!(evidence["searched_crates"], 1);
assert_eq!(evidence["references_found"], 0);
assert_eq!(evidence["root_set_size"], 0);
assert!(evidence["reason"].is_string());
}
#[test]
fn structs_enums_traits_and_consts_are_checked_the_same_way_as_functions() {
let dir = TempDir::new("dead-code-type-items");
write_crate(
&dir,
"core",
&[],
r#"pub struct UsedStruct;
pub struct DeadStruct;
pub enum UsedEnum {
A,
}
pub enum DeadEnum {
A,
}
pub trait UsedTrait {}
pub trait DeadTrait {}
pub const USED_CONST: i32 = 1;
pub const DEAD_CONST: i32 = 2;
"#,
);
write_crate(
&dir,
"consumer",
&[("core", "../core")],
r#"struct Local;
impl core::UsedTrait for Local {}
pub fn run() -> i32 {
let _ = core::UsedStruct;
let _ = core::UsedEnum::A;
core::USED_CONST
}
"#,
);
write_workspace_manifest(&dir, &["core", "consumer"]);
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let names: HashSet<&str> = report
.findings
.iter()
.map(|f| f.location.item_path.as_str())
.collect();
for used in ["UsedStruct", "UsedEnum", "UsedTrait", "USED_CONST"] {
assert!(
!names.contains(used),
"{used} is referenced from `consumer` and must not be flagged"
);
}
for dead in ["DeadStruct", "DeadEnum", "DeadTrait", "DEAD_CONST"] {
assert!(
names.contains(dead),
"{dead} is never referenced and must be flagged"
);
}
}
#[test]
fn associated_consts_types_and_statics_are_checked_the_same_way_as_functions() {
let dir = TempDir::new("dead-code-assoc-items-and-statics");
write_crate(
&dir,
"core",
&[],
r#"pub struct Widget;
impl Widget {
pub const USED_ASSOC_CONST: i32 = 1;
pub const DEAD_ASSOC_CONST: i32 = 2;
}
pub trait Converter {
type Output;
}
impl Converter for Widget {
type Output = i32;
}
pub static USED_STATIC: i32 = 1;
pub static DEAD_STATIC: i32 = 2;
"#,
);
write_crate(
&dir,
"consumer",
&[("core", "../core")],
r#"pub fn run() -> i32 {
core::USED_STATIC + core::Widget::USED_ASSOC_CONST
}
"#,
);
write_workspace_manifest(&dir, &["core", "consumer"]);
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let names: HashSet<&str> = report
.findings
.iter()
.map(|f| f.location.item_path.as_str())
.collect();
for used in ["Widget::USED_ASSOC_CONST", "USED_STATIC"] {
assert!(
!names.contains(used),
"{used} is referenced from `consumer` and must not be flagged"
);
}
for dead in ["Widget::DEAD_ASSOC_CONST", "DEAD_STATIC"] {
assert!(
names.contains(dead),
"{dead} is never referenced and must be flagged"
);
}
}
#[test]
fn a_private_struct_never_referenced_is_flagged_unreachable_from_entry() {
let dir = TempDir::new("dead-code-private-struct");
let workspace = load_single_crate_workspace(&dir, "struct PrivateStruct;\n");
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(report.checked, 1);
assert_eq!(report.findings.len(), 1, "{:?}", report.findings);
assert_eq!(report.findings[0].rule, UNREACHABLE_FROM_ENTRY_RULE);
assert_eq!(report.findings[0].location.item_path, "PrivateStruct");
}
#[test]
fn a_single_crate_workspace_does_not_flag_items_reachable_from_its_own_main() {
let dir = TempDir::new("dead-code-single-crate-entry");
std::fs::write(
dir.join("Cargo.toml"),
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "2021"
"#,
)
.unwrap();
std::fs::create_dir_all(dir.join("src/bin")).unwrap();
std::fs::write(
dir.join("src/lib.rs"),
r#"pub fn used_by_main() -> i32 {
1
}
pub fn truly_dead() -> i32 {
2
}
"#,
)
.unwrap();
std::fs::write(
dir.join("src/bin/tool.rs"),
r#"fn main() {
dead_code_fixture::used_by_main();
}
"#,
)
.unwrap();
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let names: HashSet<&str> = report
.findings
.iter()
.map(|f| f.location.item_path.as_str())
.collect();
assert!(
!names.contains("used_by_main"),
"reachable from this crate's own `fn main` — must not be flagged even with no cross-crate reference"
);
assert!(
names.contains("truly_dead"),
"never referenced anywhere — must be flagged"
);
}
#[test]
fn an_item_only_used_by_a_no_mangle_export_is_not_flagged() {
let dir = TempDir::new("dead-code-no-mangle-entry");
let workspace = load_single_crate_workspace(
&dir,
r#"pub fn used_by_export() -> i32 {
1
}
pub fn truly_dead() -> i32 {
2
}
#[no_mangle]
pub extern "C" fn exported() -> i32 {
used_by_export()
}
"#,
);
let report = analyze_workspace(&workspace, false).unwrap();
let names: HashSet<&str> = report
.findings
.iter()
.map(|f| f.location.item_path.as_str())
.collect();
assert!(
!names.contains("used_by_export"),
"reachable from a #[no_mangle] export — must not be flagged even in production-only mode"
);
assert!(
names.contains("truly_dead"),
"never referenced anywhere — must be flagged"
);
}
#[test]
fn an_item_only_used_by_an_unsafe_wrapped_export_is_not_flagged() {
let dir = TempDir::new("dead-code-unsafe-attr-entry");
let workspace = load_single_crate_workspace_with_edition(
&dir,
"2024",
r#"pub fn used_by_no_mangle_export() -> i32 {
1
}
pub fn used_by_export_name_export() -> i32 {
2
}
pub fn truly_dead() -> i32 {
3
}
#[unsafe(no_mangle)]
pub extern "C" fn exported_a() -> i32 {
used_by_no_mangle_export()
}
#[unsafe(export_name = "exported_b_symbol")]
pub extern "C" fn exported_b() -> i32 {
used_by_export_name_export()
}
"#,
);
let report = analyze_workspace(&workspace, false).unwrap();
let names: HashSet<&str> = report
.findings
.iter()
.map(|f| f.location.item_path.as_str())
.collect();
assert!(
!names.contains("used_by_no_mangle_export"),
"reachable from a #[unsafe(no_mangle)] export — must not be flagged"
);
assert!(
!names.contains("used_by_export_name_export"),
"reachable from a #[unsafe(export_name = ...)] export — must not be flagged"
);
assert!(
names.contains("truly_dead"),
"never referenced anywhere — must be flagged"
);
}
#[test]
fn an_unresolvable_position_is_a_collected_error_not_a_finding() {
let dir = TempDir::new("dead-code-unresolvable-position");
let lib_source = "pub fn item() -> i32 {\n 1\n}\n";
let workspace = load_single_crate_workspace(&dir, lib_source);
let ctx = DeepContext::load(&workspace.root).unwrap();
let analysis = ctx.analysis();
let krate = &workspace.crates[0];
let file = krate
.source_files
.iter()
.find(|file| file.path.ends_with("src/lib.rs"))
.unwrap();
let file_id = ctx.file_id(&file.path).unwrap().unwrap();
let crate_of_file = HashMap::from([(file_id, krate.name.as_str())]);
let module_of_file = HashMap::new();
let entry_keys = std::collections::HashSet::new();
let offset = lib_source.find(" 1").unwrap() as u32 + 1;
let mut report = WorkspaceDeadCode::default();
let proc_macro_exposed = HashSet::new();
let mut edge_counts = HashMap::new();
let mut module_edge_counts = HashMap::new();
check_item(
&analysis,
&crate_of_file,
&module_of_file,
&entry_keys,
&proc_macro_exposed,
file,
file_id,
&krate.name,
"not_a_symbol",
offset,
2,
true,
UNUSED_PUB_WORKSPACE_RULE,
Severity::Warn,
EvidenceClass::BoundedSemantic,
UNUSED_PUB_WORKSPACE_REASON,
&mut edge_counts,
&mut module_edge_counts,
&mut report,
);
assert!(
report.findings.is_empty(),
"an unresolvable position must never become a dead-code finding: {:?}",
report.findings
);
assert!(
report
.errors
.iter()
.any(|err| matches!(err, DeadCodeError::Deep(DeepError::UnresolvedSymbol(_)))),
"the failed resolution must be collected as an analyzer error: {:?}",
report.errors
);
}
#[test]
fn a_pub_fn_reachable_only_through_an_unexpanded_proc_macro_derive_is_falsely_flagged_dead() {
let dir = TempDir::new("dead-code-proc-macro-blind-spot");
std::fs::create_dir_all(dir.join("macros/src")).unwrap();
std::fs::write(
dir.join("macros/Cargo.toml"),
r#"[package]
name = "macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
"#,
)
.unwrap();
std::fs::write(
dir.join("macros/src/lib.rs"),
r#"use proc_macro::TokenStream;
/// Would-be expansion (never actually run — the Deep Tier loads with no
/// proc-macro server): a call to `helper()` the analysis never sees.
#[proc_macro_derive(CallsHelper)]
pub fn calls_helper(_input: TokenStream) -> TokenStream {
"fn __generated_caller() { crate::helper(); }".parse().unwrap()
}
"#,
)
.unwrap();
write_crate(
&dir,
"core",
&[("macros", "../macros")],
r#"#[derive(macros::CallsHelper)]
pub struct Widget;
pub fn helper() -> i32 {
1
}
pub fn truly_dead() -> i32 {
2
}
"#,
);
write_workspace_manifest(&dir, &["macros", "core"]);
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let names: HashSet<&str> = report
.findings
.iter()
.map(|f| f.location.item_path.as_str())
.collect();
assert!(
report.errors.is_empty(),
"no analyzer error is raised for this case today — `helper` resolves fine, only its \
caller is invisible: {:?}",
report.errors
);
assert!(
names.contains("helper"),
"documents today's actual (policy-violating) behavior: `helper` is only reachable \
through the derive's unexpanded generated code, so it is flagged dead instead of \
producing analysis_incomplete — see this test's doc comment"
);
assert!(
names.contains("truly_dead"),
"genuinely dead regardless — control for the fixture"
);
let helper_finding = report
.findings
.iter()
.find(|f| f.location.item_path == "helper")
.expect("helper must be flagged, per the assertion above");
assert_eq!(
helper_finding.limitations,
Some(vec!["proc_macro_expansion_disabled".to_string()]),
"`core` has a direct proc-macro dependency (`macros`), so the finding must disclose \
that proc-macro expansion was disabled instead of presenting `helper` as an \
unqualified dead-code finding: {:?}",
helper_finding.limitations
);
}
#[test]
fn a_pub_item_in_a_proc_macro_exposed_crate_discloses_the_limitation() {
let dir = TempDir::new("dead-code-proc-macro-exposed-limitation");
std::fs::create_dir_all(dir.join("macros/src")).unwrap();
std::fs::write(
dir.join("macros/Cargo.toml"),
r#"[package]
name = "macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
"#,
)
.unwrap();
std::fs::write(
dir.join("macros/src/lib.rs"),
r#"use proc_macro::TokenStream;
#[proc_macro]
pub fn noop(_input: TokenStream) -> TokenStream {
TokenStream::new()
}
"#,
)
.unwrap();
write_crate(
&dir,
"core",
&[("macros", "../macros")],
r#"pub fn never_called() -> i32 {
1
}
"#,
);
write_workspace_manifest(&dir, &["macros", "core"]);
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let finding = report
.findings
.iter()
.find(|f| f.location.item_path == "never_called")
.expect("never_called must be flagged dead");
assert_eq!(
finding.limitations,
Some(vec!["proc_macro_expansion_disabled".to_string()]),
"`core` directly depends on the proc-macro crate `macros`, so the finding must \
disclose that proc-macro expansion was disabled, even though this particular dead \
item is unrelated to the derive: {:?}",
finding.limitations
);
}
#[test]
fn a_pub_item_in_a_crate_without_any_proc_macro_dependency_has_no_limitations() {
let dir = TempDir::new("dead-code-no-proc-macro-dependency");
let workspace = load_single_crate_workspace(
&dir,
r#"pub fn never_called() -> i32 {
1
}
"#,
);
let report = analyze_workspace(&workspace, true).unwrap();
let finding = report
.findings
.iter()
.find(|f| f.location.item_path == "never_called")
.expect("never_called must be flagged dead");
assert!(
finding.limitations.is_none(),
"no proc-macro dependency anywhere in this workspace — the finding must not carry a \
`limitations` field: {:?}",
finding.limitations
);
}
#[test]
fn a_pub_fn_reachable_only_through_a_non_default_cargo_feature_is_not_flagged_dead() {
let dir = TempDir::new("dead-code-feature-gate-blind-spot");
std::fs::write(
dir.join("Cargo.toml"),
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "2021"
[features]
extra = []
"#,
)
.unwrap();
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(
dir.join("src/lib.rs"),
r#"pub fn helper() -> i32 {
1
}
#[cfg(feature = "extra")]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn calls_helper() {
assert_eq!(helper(), 1);
}
}
"#,
)
.unwrap();
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let names: HashSet<&str> = report
.findings
.iter()
.filter(|f| f.rule == UNUSED_PUB_WORKSPACE_RULE || f.rule == UNUSED_PUB_API_RULE)
.map(|f| f.location.item_path.as_str())
.collect();
assert!(
report.errors.is_empty(),
"no analyzer error is raised for this case today: {:?}",
report.errors
);
assert!(
!names.contains("helper"),
"`helper` is called from `calls_helper`, a #[test] fn only active with the \
non-default `extra` feature — now that the Deep Tier loads with all features, the \
test is a live entry point and `helper` must not be flagged dead"
);
}
#[test]
fn a_dead_pub_item_in_a_publishable_crate_is_flagged_unused_pub_api() {
let dir = TempDir::new("dead-code-unused-pub-api");
let workspace = load_single_crate_workspace(
&dir,
r#"pub fn never_called() -> i32 {
1
}
"#,
);
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(report.findings.len(), 1);
let finding = &report.findings[0];
assert_eq!(finding.rule, UNUSED_PUB_API_RULE);
assert_eq!(finding.severity, Severity::Info);
assert_eq!(finding.evidence_class, EvidenceClass::Heuristic);
assert_eq!(finding.location.item_path, "never_called");
let evidence = finding.evidence.as_ref().expect("evidence must be present");
assert_eq!(evidence["reason"], serde_json::json!(UNUSED_PUB_API_REASON));
}
#[test]
fn a_dead_pub_item_in_a_publish_false_crate_stays_unused_pub_workspace() {
let dir = TempDir::new("dead-code-publish-false");
std::fs::write(
dir.join("Cargo.toml"),
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "2021"
publish = false
"#,
)
.unwrap();
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(
dir.join("src/lib.rs"),
r#"pub fn never_called() -> i32 {
1
}
"#,
)
.unwrap();
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(report.findings.len(), 1);
assert_eq!(report.findings[0].rule, UNUSED_PUB_WORKSPACE_RULE);
assert_eq!(report.findings[0].severity, Severity::Warn);
assert_eq!(
report.findings[0].evidence_class,
EvidenceClass::BoundedSemantic
);
}
#[test]
fn a_dead_pub_item_in_a_crate_restricted_to_a_registry_is_still_flagged_unused_pub_api() {
let dir = TempDir::new("dead-code-restricted-registry");
std::fs::write(
dir.join("Cargo.toml"),
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "2021"
publish = ["some-internal-registry"]
"#,
)
.unwrap();
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(
dir.join("src/lib.rs"),
r#"pub fn never_called() -> i32 {
1
}
"#,
)
.unwrap();
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(report.findings.len(), 1);
assert_eq!(report.findings[0].rule, UNUSED_PUB_API_RULE);
}
#[test]
fn an_enum_variant_never_constructed_is_flagged_dead_enum_variant() {
let dir = TempDir::new("dead-code-enum-variant-dead");
let workspace = load_single_crate_workspace(
&dir,
r#"pub enum Status {
Active,
Retired,
}
pub fn describe(status: Status) -> &'static str {
match status {
Status::Active => "active",
Status::Retired => "retired",
}
}
pub fn make() -> Status {
Status::Active
}
"#,
);
let report = analyze_workspace(&workspace, true).unwrap();
let dead_variants: Vec<&Finding> = report
.findings
.iter()
.filter(|f| f.rule == DEAD_ENUM_VARIANT_RULE)
.collect();
assert_eq!(dead_variants.len(), 1, "{dead_variants:?}");
let finding = dead_variants[0];
assert_eq!(finding.location.item_path, "Status::Retired");
assert_eq!(finding.severity, Severity::Warn);
assert_eq!(finding.evidence_class, EvidenceClass::BoundedSemantic);
assert_eq!(
finding.evidence.as_ref().unwrap()["reason"],
serde_json::json!("no construction site found in the examined workspace view")
);
}
#[test]
fn an_enum_variant_constructed_only_in_another_workspace_crate_is_not_flagged() {
let dir = TempDir::new("dead-code-enum-variant-cross-crate");
write_crate(
&dir,
"core",
&[],
r#"pub enum Status {
Active,
}
"#,
);
write_crate(
&dir,
"consumer",
&[("core", "../core")],
r#"pub fn make() -> core::Status {
core::Status::Active
}
"#,
);
write_workspace_manifest(&dir, &["core", "consumer"]);
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let dead_variants: Vec<&str> = report
.findings
.iter()
.filter(|f| f.rule == DEAD_ENUM_VARIANT_RULE)
.map(|f| f.location.item_path.as_str())
.collect();
assert!(
dead_variants.is_empty(),
"Status::Active is constructed from `consumer`, a different workspace crate: \
{dead_variants:?}"
);
}
#[test]
fn a_variant_of_a_private_enum_is_not_checked() {
let dir = TempDir::new("dead-code-enum-variant-private");
let workspace = load_single_crate_workspace(
&dir,
r#"enum Status {
Active,
Retired,
}
"#,
);
let report = analyze_workspace(&workspace, true).unwrap();
assert!(
!report
.findings
.iter()
.any(|f| f.rule == DEAD_ENUM_VARIANT_RULE),
"a private enum's variants are rustc's own dead_code lint's job, not this rule's"
);
}
#[test]
fn a_pub_fn_reachable_only_from_a_test_is_flagged_test_only_pub() {
let dir = TempDir::new("dead-code-test-only-pub");
std::fs::write(
dir.join("Cargo.toml"),
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "2021"
publish = false
"#,
)
.unwrap();
std::fs::create_dir_all(dir.join("src/bin")).unwrap();
std::fs::write(
dir.join("src/lib.rs"),
r#"pub fn test_only_helper() -> i32 {
1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_test() {
assert_eq!(test_only_helper(), 1);
}
}
"#,
)
.unwrap();
std::fs::write(dir.join("src/bin/tool.rs"), "fn main() {}\n").unwrap();
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let test_only_pub_findings: Vec<&Finding> = report
.findings
.iter()
.filter(|f| f.rule == TEST_ONLY_PUB_RULE)
.collect();
assert_eq!(test_only_pub_findings.len(), 1, "{:?}", report.findings);
let finding = test_only_pub_findings[0];
assert_eq!(finding.location.item_path, "test_only_helper");
assert_eq!(finding.severity, Severity::Warn);
assert_eq!(finding.evidence_class, EvidenceClass::BoundedSemantic);
assert!(
!report.findings.iter().any(|f| f.location.item_path
== "test_only_helper"
&& f.rule != TEST_ONLY_PUB_RULE),
"test_only_helper is reachable via the test entry point in \"all\" mode, so \
unused-pub-workspace/unused-pub-api must not also fire for it: {:?}",
report.findings
);
}
#[test]
fn a_pub_fn_reachable_from_main_is_not_flagged_test_only_pub() {
let dir = TempDir::new("dead-code-test-only-pub-negative-main");
std::fs::create_dir_all(dir.join("src/bin")).unwrap();
std::fs::write(
dir.join("Cargo.toml"),
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "2021"
"#,
)
.unwrap();
std::fs::write(
dir.join("src/lib.rs"),
r#"pub fn used_by_main() -> i32 {
1
}
"#,
)
.unwrap();
std::fs::write(
dir.join("src/bin/tool.rs"),
r#"fn main() {
dead_code_fixture::used_by_main();
}
"#,
)
.unwrap();
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert!(
!report.findings.iter().any(|f| f.rule == TEST_ONLY_PUB_RULE),
"used_by_main is reachable from main in production too — must not be flagged \
test-only-pub: {:?}",
report.findings
);
}
#[test]
fn a_pub_fn_used_by_another_workspace_crate_is_not_flagged_test_only_pub_even_if_also_tested() {
let dir = TempDir::new("dead-code-test-only-pub-cross-crate");
write_crate(
&dir,
"core",
&[],
r#"pub fn shared() -> i32 {
1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_test() {
assert_eq!(shared(), 1);
}
}
"#,
);
write_crate(
&dir,
"consumer",
&[("core", "../core")],
r#"pub fn run() -> i32 {
core::shared()
}
"#,
);
write_workspace_manifest(&dir, &["core", "consumer"]);
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert!(
!report
.findings
.iter()
.any(|f| f.rule == TEST_ONLY_PUB_RULE && f.location.item_path == "shared"),
"`shared` is referenced from `consumer`, a different workspace crate — must not be \
flagged test-only-pub even though it also has a #[cfg(test)] caller: {:?}",
report.findings
);
}
#[test]
fn file_constructs_variant_does_not_count_a_bare_pattern_match_as_construction() {
let ast: syn::File = syn::parse_str(
r#"
pub enum Status {
Active,
Retired,
}
pub fn describe(status: Status) -> &'static str {
match status {
Status::Active => "active",
Status::Retired => "retired",
}
}
pub fn make() -> Status {
Status::Active
}
"#,
)
.unwrap();
assert!(
file_constructs_variant(&ast, "Active"),
"Active is constructed in `make`"
);
assert!(
!file_constructs_variant(&ast, "Retired"),
"Retired only ever appears as a match-arm pattern, never constructed"
);
}
#[cfg(feature = "deep")]
#[test]
fn unused_pub_workspace_registry_example_still_triggers_the_rule() {
let example = crate::rule_registry::lookup(UNUSED_PUB_WORKSPACE_RULE)
.expect("unused-pub-workspace has a registry entry")
.example
.expect("unused-pub-workspace has a curated example")
.before;
let dir = TempDir::new("dead-code-unused-pub-workspace-registry-example");
std::fs::write(
dir.join("Cargo.toml"),
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "2021"
publish = false
"#,
)
.unwrap();
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(dir.join("src/lib.rs"), example).unwrap();
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(
report
.findings
.iter()
.filter(|f| f.rule == UNUSED_PUB_WORKSPACE_RULE)
.count(),
1,
"{:?}",
report.findings
);
}
#[cfg(feature = "deep")]
#[test]
fn unused_pub_api_registry_example_still_triggers_the_rule() {
let example = crate::rule_registry::lookup(UNUSED_PUB_API_RULE)
.expect("unused-pub-api has a registry entry")
.example
.expect("unused-pub-api has a curated example")
.before;
let dir = TempDir::new("dead-code-unused-pub-api-registry-example");
let workspace = load_single_crate_workspace(&dir, example);
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(
report
.findings
.iter()
.filter(|f| f.rule == UNUSED_PUB_API_RULE)
.count(),
1,
"{:?}",
report.findings
);
}
#[cfg(feature = "deep")]
#[test]
fn dead_enum_variant_registry_example_still_triggers_the_rule() {
let example = crate::rule_registry::lookup(DEAD_ENUM_VARIANT_RULE)
.expect("dead-enum-variant has a registry entry")
.example
.expect("dead-enum-variant has a curated example")
.before;
let dir = TempDir::new("dead-code-dead-enum-variant-registry-example");
let workspace = load_single_crate_workspace(&dir, example);
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(
report
.findings
.iter()
.filter(|f| f.rule == DEAD_ENUM_VARIANT_RULE)
.count(),
1,
"{:?}",
report.findings
);
}
#[cfg(feature = "deep")]
#[test]
fn test_only_pub_registry_example_still_triggers_the_rule() {
let example = crate::rule_registry::lookup(TEST_ONLY_PUB_RULE)
.expect("test-only-pub has a registry entry")
.example
.expect("test-only-pub has a curated example")
.before;
let dir = TempDir::new("dead-code-test-only-pub-registry-example");
std::fs::write(
dir.join("Cargo.toml"),
r#"
[package]
name = "dead-code-fixture"
version = "0.1.0"
edition = "2021"
publish = false
"#,
)
.unwrap();
std::fs::create_dir_all(dir.join("src/bin")).unwrap();
std::fs::write(dir.join("src/lib.rs"), example).unwrap();
std::fs::write(dir.join("src/bin/tool.rs"), "fn main() {}\n").unwrap();
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(
report
.findings
.iter()
.filter(|f| f.rule == TEST_ONLY_PUB_RULE)
.count(),
1,
"{:?}",
report.findings
);
}
#[cfg(feature = "deep")]
#[test]
fn unreachable_from_entry_registry_example_still_triggers_the_rule() {
let example = crate::rule_registry::lookup(UNREACHABLE_FROM_ENTRY_RULE)
.expect("unreachable-from-entry has a registry entry")
.example
.expect("unreachable-from-entry has a curated example")
.before;
let dir = TempDir::new("dead-code-unreachable-from-entry-registry-example");
let workspace = load_single_crate_workspace(&dir, example);
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(
report
.findings
.iter()
.filter(|f| f.rule == UNREACHABLE_FROM_ENTRY_RULE)
.count(),
1,
"{:?}",
report.findings
);
}
#[cfg(feature = "deep")]
#[test]
fn crate_coupling_reports_ca_ce_for_a_shared_core_crate() {
let dir = TempDir::new("dead-code-crate-coupling-core");
write_crate(
&dir,
"core",
&[],
r#"pub fn shared_helper() -> i32 {
1
}
"#,
);
write_crate(
&dir,
"app",
&[("core", "../core")],
r#"pub fn run() -> i32 {
core::shared_helper()
}
"#,
);
write_crate(
&dir,
"plugin",
&[("core", "../core")],
r#"pub fn run() -> i32 {
core::shared_helper()
}
"#,
);
write_workspace_manifest(&dir, &["core", "app", "plugin"]);
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let core_finding = report
.findings
.iter()
.find(|f| f.rule == CRATE_COUPLING_RULE && f.location.item_path == "core")
.unwrap_or_else(|| {
panic!(
"no crate-coupling finding for `core`: {:?}",
report.findings
)
});
let evidence = core_finding
.evidence
.as_ref()
.expect("evidence must be present");
assert_eq!(evidence["afferent_coupling"], serde_json::json!(2));
assert_eq!(evidence["efferent_coupling"], serde_json::json!(0));
assert_eq!(evidence["instability"], serde_json::json!(0.0));
assert_eq!(
evidence["afferent_crates"],
serde_json::json!(["app", "plugin"])
);
for consumer in ["app", "plugin"] {
let consumer_finding = report
.findings
.iter()
.find(|f| f.rule == CRATE_COUPLING_RULE && f.location.item_path == consumer)
.unwrap_or_else(|| {
panic!(
"no crate-coupling finding for `{consumer}`: {:?}",
report.findings
)
});
let evidence = consumer_finding
.evidence
.as_ref()
.expect("evidence must be present");
assert!(
evidence["efferent_coupling"].as_u64().unwrap() >= 1,
"`{consumer}` calls into `core` — must have Ce >= 1: {evidence:?}"
);
}
}
#[cfg(feature = "deep")]
#[test]
fn crate_coupling_skips_a_fully_isolated_crate() {
let dir = TempDir::new("dead-code-crate-coupling-isolated");
write_crate(
&dir,
"isolated",
&[],
r#"pub fn never_referenced_elsewhere() -> i32 {
1
}
"#,
);
write_workspace_manifest(&dir, &["isolated"]);
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert!(
!report
.findings
.iter()
.any(|f| f.rule == CRATE_COUPLING_RULE),
"a crate with no cross-crate coupling at all must not be flagged: {:?}",
report.findings
);
}
#[cfg(feature = "deep")]
fn split_marked_crates(source: &str) -> Vec<(&str, String)> {
let mut result: Vec<(&str, String)> = Vec::new();
for line in source.lines() {
if let Some(name) = line.strip_prefix("// crate: ") {
result.push((name, String::new()));
} else if let Some(entry) = result.last_mut() {
entry.1.push_str(line);
entry.1.push('\n');
}
}
result
}
#[cfg(feature = "deep")]
#[test]
fn crate_coupling_registry_example_still_triggers_the_rule() {
let example = crate::rule_registry::lookup(CRATE_COUPLING_RULE)
.expect("crate-coupling has a registry entry")
.example
.expect("crate-coupling has a curated example")
.before;
let crates = split_marked_crates(example);
assert_eq!(crates.len(), 3, "expected 3 marked crates: {crates:?}");
let crate0 = crates[0].0;
let crate1 = crates[1].0;
let crate2 = crates[2].0;
let dep = format!("../{crate0}");
let dir = TempDir::new("dead-code-crate-coupling-registry-example");
write_crate(&dir, crate0, &[], &crates[0].1);
write_crate(&dir, crate1, &[(crate0, &dep)], &crates[1].1);
write_crate(&dir, crate2, &[(crate0, &dep)], &crates[2].1);
write_workspace_manifest(&dir, &[crate0, crate1, crate2]);
let workspace = crate::ingest::load(Some(&dir.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(
report
.findings
.iter()
.filter(|f| f.rule == CRATE_COUPLING_RULE)
.count(),
3,
"{:?}",
report.findings
);
}
#[cfg(feature = "deep")]
fn write_single_crate_module(dir: &TempDir, relative_path: &str, source: &str) {
let path = dir.join("src").join(relative_path);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(path, source).unwrap();
}
#[cfg(feature = "deep")]
#[test]
fn module_coupling_reports_ca_ce_for_a_shared_core_module() {
let dir = TempDir::new("dead-code-module-coupling-core");
let workspace = load_single_crate_workspace(
&dir,
"pub mod core_mod;\npub mod consumer_a;\npub mod consumer_b;\n",
);
write_single_crate_module(
&dir,
"core_mod.rs",
r#"pub fn shared_helper() -> i32 {
1
}
"#,
);
write_single_crate_module(
&dir,
"consumer_a.rs",
r#"pub fn run() -> i32 {
crate::core_mod::shared_helper()
}
"#,
);
write_single_crate_module(
&dir,
"consumer_b.rs",
r#"pub fn run() -> i32 {
crate::core_mod::shared_helper()
}
"#,
);
let workspace = crate::ingest::load(Some(&workspace.root.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
let krate_name = &workspace.crates[0].name;
let core_module = format!("{krate_name}::core_mod");
let core_finding = report
.findings
.iter()
.find(|f| f.rule == MODULE_COUPLING_RULE && f.location.item_path == core_module)
.unwrap_or_else(|| {
panic!(
"no module-coupling finding for `{core_module}`: {:?}",
report.findings
)
});
let evidence = core_finding
.evidence
.as_ref()
.expect("evidence must be present");
assert_eq!(evidence["afferent_coupling"], serde_json::json!(2));
assert_eq!(evidence["efferent_coupling"], serde_json::json!(0));
assert_eq!(evidence["instability"], serde_json::json!(0.0));
assert_eq!(
evidence["afferent_modules"],
serde_json::json!([
format!("{krate_name}::consumer_a"),
format!("{krate_name}::consumer_b"),
])
);
for consumer_module in [
format!("{krate_name}::consumer_a"),
format!("{krate_name}::consumer_b"),
] {
let consumer_finding = report
.findings
.iter()
.find(|f| f.rule == MODULE_COUPLING_RULE && f.location.item_path == consumer_module)
.unwrap_or_else(|| {
panic!(
"no module-coupling finding for `{consumer_module}`: {:?}",
report.findings
)
});
let evidence = consumer_finding
.evidence
.as_ref()
.expect("evidence must be present");
assert!(
evidence["efferent_coupling"].as_u64().unwrap() >= 1,
"`{consumer_module}` calls into `core_mod` — must have Ce >= 1: {evidence:?}"
);
}
}
#[cfg(feature = "deep")]
#[test]
fn module_coupling_skips_a_module_with_no_cross_module_coupling() {
let dir = TempDir::new("dead-code-module-coupling-isolated");
let workspace = load_single_crate_workspace(&dir, "pub mod isolated_mod;\n");
write_single_crate_module(
&dir,
"isolated_mod.rs",
r#"pub fn never_referenced_elsewhere() -> i32 {
1
}
"#,
);
let workspace = crate::ingest::load(Some(&workspace.root.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert!(
!report
.findings
.iter()
.any(|f| f.rule == MODULE_COUPLING_RULE),
"a module with no cross-module coupling at all must not be flagged: {:?}",
report.findings
);
}
#[cfg(feature = "deep")]
fn split_marked_files(source: &str) -> Vec<(&str, String)> {
let mut result: Vec<(&str, String)> = Vec::new();
for line in source.lines() {
if let Some(name) = line.strip_prefix("// file: ") {
result.push((name, String::new()));
} else if let Some(entry) = result.last_mut() {
entry.1.push_str(line);
entry.1.push('\n');
}
}
result
}
#[cfg(feature = "deep")]
#[test]
fn module_coupling_registry_example_still_triggers_the_rule() {
let example = crate::rule_registry::lookup(MODULE_COUPLING_RULE)
.expect("module-coupling has a registry entry")
.example
.expect("module-coupling has a curated example")
.before;
let files = split_marked_files(example);
assert_eq!(files.len(), 3, "expected 3 marked files: {files:?}");
let dir = TempDir::new("dead-code-module-coupling-registry-example");
let mod_declarations: String = files
.iter()
.map(|(name, _)| {
let mod_name = name.strip_suffix(".rs").expect("marked file ends in .rs");
format!("pub mod {mod_name};\n")
})
.collect();
let workspace = load_single_crate_workspace(&dir, &mod_declarations);
for (name, source) in &files {
write_single_crate_module(&dir, name, source);
}
let workspace = crate::ingest::load(Some(&workspace.root.join("Cargo.toml"))).unwrap();
let report = analyze_workspace(&workspace, true).unwrap();
assert_eq!(
report
.findings
.iter()
.filter(|f| f.rule == MODULE_COUPLING_RULE)
.count(),
3,
"{:?}",
report.findings
);
}
}