use crate::adapters::analyzers::dry::dead_code::*;
use crate::adapters::analyzers::dry::{
has_allow_dead_code, has_cfg_test, has_test_attr, qualify_name,
};
use crate::adapters::shared::declared_function::DeclaredFunction;
use crate::adapters::shared::file_visitor::FileVisitor;
use crate::config::Config;
use std::collections::HashSet;
use syn::visit::Visit;
fn parse(code: &str) -> Vec<(String, String, syn::File)> {
let syntax = syn::parse_file(code).expect("parse failed");
vec![("test.rs".to_string(), code.to_string(), syntax)]
}
fn parse2(
parent_path: &str,
parent_code: &str,
child_path: &str,
child_code: &str,
) -> Vec<(String, String, syn::File)> {
vec![
(
parent_path.to_string(),
parent_code.to_string(),
syn::parse_file(parent_code).expect("parse parent"),
),
(
child_path.to_string(),
child_code.to_string(),
syn::parse_file(child_code).expect("parse child"),
),
]
}
fn collected_calls(code: &str) -> (HashSet<String>, HashSet<String>) {
let parsed = parse(code);
let cfg_test_files =
crate::adapters::shared::cfg_test_files::collect_cfg_test_file_paths(&parsed);
collect_all_calls(&parsed, &cfg_test_files)
}
fn dead_code_warnings(parsed: &[(String, String, syn::File)]) -> Vec<DeadCodeWarning> {
let cfg_test_files =
crate::adapters::shared::cfg_test_files::collect_cfg_test_file_paths(parsed);
detect_dead_code(
parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&cfg_test_files,
)
}
#[test]
fn test_detect_dead_code_empty() {
let parsed = parse("");
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(warnings.is_empty());
}
#[test]
fn test_uncalled_function_detected() {
let code = r#"
fn called_fn() { let x = 1; }
fn caller() { called_fn(); }
fn never_called() { let y = 2; }
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
let uncalled: Vec<_> = warnings
.iter()
.filter(|w| w.kind == DeadCodeKind::Uncalled)
.collect();
assert!(
uncalled.iter().any(|w| w.function_name == "never_called"),
"never_called should be flagged as uncalled"
);
assert!(
!uncalled.iter().any(|w| w.function_name == "called_fn"),
"called_fn should not be flagged"
);
}
#[test]
fn test_called_function_not_flagged() {
let code = r#"
fn helper() { let x = 1; }
fn main() { helper(); }
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "helper"),
"called function should not be flagged"
);
}
#[test]
fn test_main_excluded_from_dead_code() {
let code = "fn main() {}";
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "main"),
"main should never be flagged"
);
}
#[test]
fn test_test_function_excluded() {
let code = r#"
#[test]
fn test_something() { let x = 1; }
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "test_something"),
"test functions should be excluded"
);
}
#[test]
fn test_trait_impl_excluded() {
let code = r#"
trait Foo { fn bar(&self); }
struct S;
impl Foo for S {
fn bar(&self) {}
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "bar"),
"trait impl methods should be excluded"
);
}
#[test]
fn test_allow_dead_code_excluded() {
let code = r#"
#[allow(dead_code)]
fn intentionally_unused() { let x = 1; }
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings
.iter()
.any(|w| w.function_name == "intentionally_unused"),
"Functions with #[allow(dead_code)] should be excluded"
);
}
#[test]
fn test_test_only_function_detected() {
let code = r#"
fn helper() { let x = 1; }
fn production() { let y = 2; }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_it() { helper(); }
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
let test_only: Vec<_> = warnings
.iter()
.filter(|w| w.kind == DeadCodeKind::TestOnly)
.collect();
assert!(
test_only.iter().any(|w| w.function_name == "helper"),
"helper called only from tests should be flagged as test-only"
);
}
#[test]
fn testonly_suggestion_mentions_qual_api_and_test_helper() {
let code = r#"
pub fn helper() { let x = 1; }
#[cfg(test)]
mod tests {
use super::helper;
#[test]
fn t() { helper(); }
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
let testonly = warnings
.iter()
.find(|w| w.kind == DeadCodeKind::TestOnly)
.expect("helper should be flagged test-only");
assert!(
testonly.suggestion.contains("qual:api")
&& testonly.suggestion.contains("qual:test_helper"),
"testonly suggestion should mention both escape hatches, got: {:?}",
testonly.suggestion
);
}
#[test]
fn test_dead_code_always_runs_when_called_directly() {
let code = r#"
fn never_called() { let x = 1; }
"#;
let parsed = parse(code);
let mut config = Config::default();
config.duplicates.detect_dead_code = false;
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.is_empty(),
"detect_dead_code runs regardless — pipeline guards the config flag"
);
}
#[test]
fn test_method_call_detected() {
let code = r#"
struct S;
impl S {
fn helper(&self) { let x = 1; }
fn caller(&self) { self.helper(); }
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "helper"),
"Method called via self.helper() should not be flagged"
);
}
#[test]
fn test_function_reference_as_call_argument() {
let code = r#"
fn some_fn(x: i32) -> i32 { x + 1 }
fn caller() {
let items = vec![1, 2, 3];
let _: Vec<_> = items.into_iter().map(some_fn).collect();
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "some_fn"),
"Function passed as argument to map() should be detected as called"
);
}
#[test]
fn test_function_reference_as_method_argument() {
let code = r#"
fn process(x: i32) { let _ = x; }
fn caller() {
let items = vec![1, 2, 3];
items.iter().for_each(process);
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "process"),
"Function passed as argument to for_each() should be detected as called"
);
}
#[test]
fn test_qualified_function_reference_as_argument() {
let code = r#"
mod report {
pub fn print_item(x: &i32) { let _ = x; }
}
fn caller() {
let items = vec![1, 2, 3];
items.iter().for_each(report::print_item);
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "print_item"),
"Qualified function reference (module::fn) should be detected as called"
);
}
#[test]
fn test_qualified_call_detected() {
let code = r#"
struct Config;
impl Config {
fn load() -> Self { Config }
}
fn main() { let c = Config::load(); }
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "load"),
"Config::load() should be detected as called"
);
}
#[test]
fn test_pub_use_reexport_not_dead_code() {
let code = r#"
mod foo { pub fn do_work() { let x = 1; } }
pub use foo::do_work;
fn main() {}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "do_work"),
"pub use re-exported function should not be flagged as dead code"
);
}
#[test]
fn test_pub_use_rename_not_dead_code() {
let code = r#"
mod foo { pub fn do_work() { let x = 1; } }
pub use foo::do_work as perform_work;
fn main() {}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "do_work"),
"pub use rename re-export should record original name, not alias"
);
}
#[test]
fn test_pub_use_group_reexport_not_dead_code() {
let code = r#"
mod foo {
pub fn bar() { let x = 1; }
pub fn baz() { let y = 2; }
}
pub use foo::{bar, baz};
fn main() {}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "bar"),
"grouped pub use re-export: bar should not be flagged"
);
assert!(
!warnings.iter().any(|w| w.function_name == "baz"),
"grouped pub use re-export: baz should not be flagged"
);
}
#[test]
fn test_private_use_does_not_count_as_reexport() {
let code = r#"
mod foo { pub fn helper() { let x = 1; } }
use foo::helper;
fn main() {}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
warnings.iter().any(|w| w.function_name == "helper"),
"private use import (no call) should still be flagged as uncalled"
);
}
#[test]
fn integration_test_entry_point_in_crate_tests_dir_not_dead_code() {
let code = r#"
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn five_oh_two_twice_then_success_under_default_policy() { let x = 1; }
"#;
let parsed = vec![(
"crates/sv-utility-retry/tests/integration.rs".to_string(),
code.to_string(),
syn::parse_file(code).expect("parse failed"),
)];
let cfg_test_files =
crate::adapters::shared::cfg_test_files::collect_cfg_test_file_paths(&parsed);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&cfg_test_files,
);
assert!(
warnings.is_empty(),
"integration-test entry point must not be flagged as dead code: {warnings:?}"
);
}
#[test]
fn cfg_test_mod_child_files_not_flagged() {
let cases: &[(&str, &str, &str, &str, &str, &str)] = &[
(
"flat helpers.rs child",
"src/mod.rs",
r#"
fn production_fn() { let x = 1; }
#[cfg(test)]
mod helpers;
"#,
"src/helpers.rs",
"pub fn test_helper() { let x = 1; }",
"test_helper",
),
(
"dir helpers/mod.rs child",
"src/foo/mod.rs",
r#"
fn prod() { let x = 1; }
#[cfg(test)]
mod helpers;
"#,
"src/foo/helpers/mod.rs",
"pub fn test_util() { let x = 1; }",
"test_util",
),
(
"non-mod parent foo.rs → foo/",
"src/foo.rs",
r#"
fn prod() { let x = 1; }
#[cfg(test)]
mod test_utils;
"#,
"src/foo/test_utils.rs",
"pub fn helper() { let x = 1; }",
"helper",
),
];
for (label, parent_path, parent_code, child_path, child_code, fn_name) in cases {
let warnings =
dead_code_warnings(&parse2(parent_path, parent_code, child_path, child_code));
assert!(
!warnings.iter().any(|w| w.function_name == *fn_name),
"case {label}: {fn_name} in a #[cfg(test)] mod child must not be flagged; got {warnings:?}"
);
}
}
#[test]
fn test_cfg_test_mod_calls_classified_as_test() {
let parent_code = r#"
fn used_by_test_helpers() { let x = 1; }
fn used_by_production() { let y = 2; }
fn caller() { used_by_production(); }
#[cfg(test)]
mod helpers;
"#;
let child_code = r#"
pub fn test_helper() { super::used_by_test_helpers(); }
"#;
let parent_ast = syn::parse_file(parent_code).expect("parse parent");
let child_ast = syn::parse_file(child_code).expect("parse child");
let parsed = vec![
(
"src/lib.rs".to_string(),
parent_code.to_string(),
parent_ast,
),
(
"src/helpers.rs".to_string(),
child_code.to_string(),
child_ast,
),
];
let cfg_test_files =
crate::adapters::shared::cfg_test_files::collect_cfg_test_file_paths(&parsed);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&cfg_test_files,
);
let test_only: Vec<_> = warnings
.iter()
.filter(|w| w.kind == DeadCodeKind::TestOnly)
.collect();
assert!(
test_only
.iter()
.any(|w| w.function_name == "used_by_test_helpers"),
"Function called only from cfg(test) file should be flagged as test-only"
);
assert!(
!warnings
.iter()
.any(|w| w.function_name == "used_by_production"),
"Function called from production should not be flagged"
);
}
#[test]
fn test_serde_deserialize_with_not_dead_code() {
let code = r#"
fn custom_de<'de, D: serde::Deserializer<'de>>(d: D) -> Result<i32, D::Error> {
let v: i32 = serde::Deserialize::deserialize(d)?;
Ok(v)
}
#[derive(serde::Deserialize)]
struct Foo {
#[serde(deserialize_with = "custom_de")]
value: i32,
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "custom_de"),
"Function referenced by #[serde(deserialize_with)] should not be flagged"
);
}
#[test]
fn test_serde_serialize_with_not_dead_code() {
let code = r#"
fn custom_ser<S: serde::Serializer>(v: &i32, s: S) -> Result<S::Ok, S::Error> {
s.serialize_i32(*v)
}
#[derive(serde::Serialize)]
struct Foo {
#[serde(serialize_with = "custom_ser")]
value: i32,
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "custom_ser"),
"Function referenced by #[serde(serialize_with)] should not be flagged"
);
}
#[test]
fn test_serde_default_fn_not_dead_code() {
let code = r#"
fn default_val() -> i32 { 42 }
#[derive(serde::Deserialize)]
struct Foo {
#[serde(default = "default_val")]
value: i32,
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "default_val"),
"Function referenced by #[serde(default = \"fn\")] should not be flagged"
);
}
#[test]
fn test_serde_qualified_path_not_dead_code() {
let code = r#"
mod helpers {
pub fn custom_de<'de, D: serde::Deserializer<'de>>(d: D) -> Result<i32, D::Error> {
let v: i32 = serde::Deserialize::deserialize(d)?;
Ok(v)
}
}
#[derive(serde::Deserialize)]
struct Foo {
#[serde(deserialize_with = "helpers::custom_de")]
value: i32,
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "custom_de"),
"Qualified serde fn ref (helpers::custom_de) should not be flagged"
);
}
#[test]
fn test_serde_with_module_not_dead_code() {
let code = r#"
mod my_format {
pub fn serialize<S: serde::Serializer>(_v: &i32, s: S) -> Result<S::Ok, S::Error> {
s.serialize_i32(0)
}
pub fn deserialize<'de, D: serde::Deserializer<'de>>(d: D) -> Result<i32, D::Error> {
let v: i32 = serde::Deserialize::deserialize(d)?;
Ok(v)
}
}
struct Foo {
#[serde(with = "my_format")]
value: i32,
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "serialize"),
"Function referenced via #[serde(with)] should not be flagged"
);
}
#[test]
fn test_serde_default_without_value_ignored() {
let code = r#"
fn unused_fn() { let x = 1; }
#[derive(serde::Deserialize)]
struct Foo {
#[serde(default)]
value: i32,
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
warnings.iter().any(|w| w.function_name == "unused_fn"),
"Unrelated unused function should still be flagged"
);
}
#[test]
fn test_serde_default_fn_cross_file_not_dead_code() {
let code_a = r#"
pub fn default_true() -> bool { true }
pub fn default_adx_period() -> u32 { 14 }
"#;
let code_b = r#"
#[derive(serde::Deserialize)]
struct Config {
#[serde(default = "default_true")]
enabled: bool,
#[serde(default = "default_adx_period")]
adx_period: u32,
}
"#;
let ast_a = syn::parse_file(code_a).expect("parse code_a");
let ast_b = syn::parse_file(code_b).expect("parse code_b");
let parsed = vec![
(
"src/config_defaults.rs".to_string(),
code_a.to_string(),
ast_a,
),
("src/config.rs".to_string(), code_b.to_string(), ast_b),
];
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "default_true"),
"default_true referenced via #[serde(default)] in another file should not be flagged"
);
assert!(
!warnings
.iter()
.any(|w| w.function_name == "default_adx_period"),
"default_adx_period referenced via #[serde(default)] in another file should not be flagged"
);
}
#[test]
fn test_serde_default_fn_realistic_pattern() {
let code = r#"
fn default_true() -> bool { true }
fn default_false() -> bool { false }
fn default_period() -> u32 { 14 }
fn default_threshold() -> f64 { 0.5 }
#[derive(serde::Deserialize)]
struct IndicatorConfig {
#[serde(default = "default_true")]
enabled: bool,
#[serde(default = "default_false")]
verbose: bool,
#[serde(default = "default_period")]
period: u32,
#[serde(default = "default_threshold")]
threshold: f64,
}
"#;
let parsed = parse(code);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
let flagged: Vec<&str> = warnings.iter().map(|w| w.function_name.as_str()).collect();
assert!(
!flagged.contains(&"default_true"),
"default_true should not be flagged, got: {flagged:?}"
);
assert!(
!flagged.contains(&"default_false"),
"default_false should not be flagged, got: {flagged:?}"
);
assert!(
!flagged.contains(&"default_period"),
"default_period should not be flagged, got: {flagged:?}"
);
assert!(
!flagged.contains(&"default_threshold"),
"default_threshold should not be flagged, got: {flagged:?}"
);
}
const CALL_FORM_CASES: &[(&str, &str, bool, &str)] = &[
(
"call inside assert!() in a #[test]",
r#"
fn helper() -> bool { true }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_it() {
assert!(helper());
}
}
"#,
true,
"helper",
),
(
"call inside assert_eq!() in a #[test]",
r#"
fn compute() -> usize { 42 }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_it() {
assert_eq!(compute(), 42);
}
}
"#,
true,
"compute",
),
(
"&fn passed as a closure argument",
r#"
fn format_item(s: &str) -> String { s.to_string() }
fn process(items: &[&str], transform: &dyn Fn(&str) -> String) {
items.iter().for_each(|i| { transform(i); });
}
fn run() {
process(&["a"], &format_item);
}
"#,
false,
"format_item",
),
(
"bare fn name in a struct field",
r#"
struct Config { handler: fn() -> i32 }
fn my_handler() -> i32 { 42 }
fn setup() -> Config {
Config { handler: my_handler }
}
"#,
false,
"my_handler",
),
];
#[test]
fn collect_all_calls_recognizes_call_forms() {
for (label, code, in_test_scope, callee) in CALL_FORM_CASES {
let (prod_calls, test_calls) = collected_calls(code);
let set = if *in_test_scope {
&test_calls
} else {
&prod_calls
};
assert!(
set.contains(*callee),
"case {label}: callee `{callee}` not found in {set:?}"
);
}
}
#[test]
fn test_collect_cfg_test_file_paths_inline_mod_ignored() {
let code = r#"
#[cfg(test)]
mod tests {
fn helper() {}
}
"#;
let ast = syn::parse_file(code).unwrap();
let parsed = vec![("src/lib.rs".to_string(), code.to_string(), ast)];
let result = collect_cfg_test_file_paths(&parsed);
assert!(
result.is_empty(),
"Inline cfg(test) mod should not produce cfg-test file entries"
);
}
#[test]
fn test_api_function_excluded_from_dead_code() {
let code = r#"
// qual:api
pub fn public_api() { let x = 1; }
// spacer to move internal_unused outside annotation window
// another spacer line
fn internal_unused() { let y = 2; }
"#;
let parsed = parse(code);
let mut api_lines = std::collections::HashMap::new();
api_lines.insert(
"test.rs".to_string(),
[2usize]
.into_iter()
.collect::<std::collections::HashSet<_>>(),
);
let warnings = detect_dead_code(
&parsed,
&api_lines,
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
let names: Vec<&str> = warnings.iter().map(|w| w.function_name.as_str()).collect();
assert!(
!names.contains(&"public_api"),
"API-marked function should be excluded"
);
assert!(
names.contains(&"internal_unused"),
"Non-API function should still be flagged"
);
}
#[test]
fn test_api_does_not_count_as_suppression() {
assert!(crate::findings::parse_suppression(1, "// qual:api").is_none());
}
#[test]
fn test_helper_marker_suppresses_testonly_dead_code() {
let code = r#"
// qual:test_helper
pub fn shared_asserter(x: i32) {
let _ = x + 1;
}
#[cfg(test)]
mod tests {
use super::shared_asserter;
#[test]
fn t1() { shared_asserter(1); }
}
"#;
let parsed = parse(code);
let mut test_helper_lines = std::collections::HashMap::new();
test_helper_lines.insert(
"test.rs".to_string(),
[2usize]
.into_iter()
.collect::<std::collections::HashSet<_>>(),
);
let cfg_test_files = collect_cfg_test_file_paths(&parsed);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&test_helper_lines,
&cfg_test_files,
);
let names: Vec<&str> = warnings.iter().map(|w| w.function_name.as_str()).collect();
assert!(
!names.contains(&"shared_asserter"),
"test-helper-marked function should be excluded from dead code, got {names:?}"
);
}
#[test]
fn test_helper_marker_does_not_suppress_uncalled() {
let code = r#"
// qual:test_helper
pub fn marked_but_not_called() { let _ = 1; }
// spacer to move unmarked out of annotation window
// another spacer line
fn unmarked_and_uncalled() { let _ = 2; }
"#;
let parsed = parse(code);
let mut test_helper_lines = std::collections::HashMap::new();
test_helper_lines.insert(
"test.rs".to_string(),
[2usize]
.into_iter()
.collect::<std::collections::HashSet<_>>(),
);
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&test_helper_lines,
&std::collections::HashSet::new(),
);
let uncalled: Vec<&str> = warnings
.iter()
.filter(|w| w.kind == DeadCodeKind::Uncalled)
.map(|w| w.function_name.as_str())
.collect();
assert!(
uncalled.contains(&"marked_but_not_called"),
"test_helper on a function with no callers must still flag as Uncalled, got {uncalled:?}"
);
assert!(
uncalled.contains(&"unmarked_and_uncalled"),
"unmarked uncalled function must still flag, got {uncalled:?}"
);
}
fn blanket_dispatch_parsed() -> Vec<(String, String, syn::File)> {
let pf = |path: &str, src: &str| {
(
path.to_string(),
String::new(),
syn::parse_file(src).expect("parse fixture"),
)
};
vec![
pf(
"src/ports/reporter.rs",
r#"
pub trait ReporterImpl {
type Output;
fn publish(&self) -> Self::Output;
}
pub trait Reporter {
type Output;
fn render(&self) -> Self::Output;
}
impl<T: ReporterImpl> Reporter for T {
type Output = T::Output;
fn render(&self) -> Self::Output { self.publish() }
}
"#,
),
pf(
"src/adapters/report/sarif/rules.rs",
r#"
pub(super) fn sarif_rules() -> Vec<String> {
vec![String::from("rule")]
}
"#,
),
pf(
"src/adapters/report/sarif/mod.rs",
r#"
use super::rules::sarif_rules;
pub struct SarifReporter;
impl ReporterImpl for SarifReporter {
type Output = String;
fn publish(&self) -> Self::Output {
let _r = sarif_rules();
String::new()
}
}
pub fn build_sarif_string() -> String {
let r = SarifReporter;
r.render()
}
#[cfg(test)]
mod tests {
use super::build_sarif_string;
#[test]
fn it() { let _ = build_sarif_string(); }
}
"#,
),
]
}
#[test]
fn helper_reached_via_trait_blanket_dispatch_is_not_dead_code() {
let parsed = blanket_dispatch_parsed();
let warnings = detect_dead_code(
&parsed,
&std::collections::HashMap::new(),
&std::collections::HashMap::new(),
&std::collections::HashSet::new(),
);
assert!(
!warnings.iter().any(|w| w.function_name == "sarif_rules"),
"sarif_rules is reached via trait-blanket dispatch and must not be flagged dead-code, got: {:?}",
warnings.iter().map(|w| (&w.function_name, &w.kind)).collect::<Vec<_>>()
);
}
#[test]
fn fn_called_only_in_repeat_macro_arg_not_dead_code() {
let code = r#"
fn helper() -> i32 { 42 }
fn caller() -> Vec<i32> { vec![helper(); 3] }
#[cfg(test)]
mod tests {
#[test]
fn t() { let _ = super::caller(); }
}
"#;
let parsed = parse(code);
let warnings = dead_code_warnings(&parsed);
assert!(
!warnings.iter().any(|w| w.function_name == "helper"),
"helper() called inside a vec![_; n] repeat macro must not be flagged dead, got {warnings:?}"
);
}
#[test]
fn component_referenced_only_in_dsl_macro_not_dead_code() {
let code = r#"
fn LiveLogViewer() { let _ = 1; }
fn app() { rsx! { div { class: "log", LiveLogViewer {} } }; }
fn main() { app(); }
"#;
let parsed = parse(code);
let warnings = dead_code_warnings(&parsed);
assert!(
!warnings.iter().any(|w| w.function_name == "LiveLogViewer"),
"component referenced only inside an rsx! DSL macro must not be dead, got {warnings:?}"
);
}
#[test]
fn fn_named_like_a_lowercase_dsl_tag_is_still_dead_code() {
let code = r#"
fn view() { let _ = 1; }
fn app() { rsx! { view { class: "x", Widget {} } }; }
fn main() { app(); }
"#;
let parsed = parse(code);
let warnings = dead_code_warnings(&parsed);
assert!(
warnings.iter().any(|w| w.function_name == "view"),
"a fn matching only a lowercase DSL tag must still be flagged dead, got {warnings:?}"
);
}
#[test]
fn fn_named_like_a_dsl_prop_key_is_still_dead_code() {
let code = r#"
fn render_label() { let _ = 1; }
fn app() { rsx! { Widget { render_label: "x" } }; }
fn main() { app(); }
"#;
let parsed = parse(code);
let warnings = dead_code_warnings(&parsed);
assert!(
warnings.iter().any(|w| w.function_name == "render_label"),
"a fn matching only a DSL prop key must still be flagged dead, got {warnings:?}"
);
}
#[test]
fn fn_called_via_bound_local_not_dead_code_control() {
let code = r#"
fn helper() -> i32 { 42 }
fn caller() -> Vec<i32> { let h = helper(); vec![h] }
#[cfg(test)]
mod tests {
#[test]
fn t() { let _ = super::caller(); }
}
"#;
let parsed = parse(code);
let warnings = dead_code_warnings(&parsed);
assert!(
!warnings.iter().any(|w| w.function_name == "helper"),
"helper() via a bound local must not be flagged dead, got {warnings:?}"
);
}
#[test]
fn test_only_called_fn_is_not_uncalled() {
let parsed =
parse("fn helper() { let _ = 1; } #[cfg(test)] mod tests { #[test] fn t() { helper(); } }");
let warnings = dead_code_warnings(&parsed);
assert!(
!warnings
.iter()
.any(|w| w.function_name == "helper" && matches!(w.kind, DeadCodeKind::Uncalled)),
"a test-only fn must not be reported as Uncalled, got {warnings:?}"
);
}