#![cfg_attr(coverage_nightly, coverage(off))]
use super::{Analyzer, AnalyzerInfo, ProjectAnalyzer, ProjectConfig, ProjectInput};
use crate::services::dead_code_analyzer::{DeadCodeAnalyzer as OriginalAnalyzer, DeadCodeReport};
use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::path::Path;
pub struct DeadCodeAnalyzer {
#[allow(dead_code)]
inner: OriginalAnalyzer,
}
impl DeadCodeAnalyzer {
#[must_use]
pub fn new() -> Self {
Self {
inner: OriginalAnalyzer::new(Self::DEFAULT_CAPACITY),
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: OriginalAnalyzer::new(capacity),
}
}
const DEFAULT_CAPACITY: usize = 100_000;
}
impl Default for DeadCodeAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeadCodeConfig {
pub base: ProjectConfig,
pub include_unreachable: bool,
pub min_dead_lines: usize,
pub confidence_threshold: f64,
}
impl Default for DeadCodeConfig {
fn default() -> Self {
Self {
base: ProjectConfig::default(),
include_unreachable: true,
min_dead_lines: 5,
confidence_threshold: 0.7,
}
}
}
pub type DeadCodeOutput = DeadCodeReport;
#[async_trait]
impl Analyzer for DeadCodeAnalyzer {
type Input = ProjectInput;
type Output = DeadCodeOutput;
type Config = ProjectConfig;
async fn analyze(&self, input: Self::Input, config: Self::Config) -> Result<Self::Output> {
use crate::services::cargo_dead_code_analyzer::{CargoDeadCodeAnalyzer, DeadCodeKind};
let cargo_analyzer = if config.include_tests {
CargoDeadCodeAnalyzer::new(&input.project_path).include_tests()
} else {
CargoDeadCodeAnalyzer::new(&input.project_path)
};
let accurate_report = cargo_analyzer.analyze().await?;
let mut dead_functions = Vec::new();
let mut dead_classes = Vec::new();
let mut dead_variables = Vec::new();
use crate::models::unified_ast::NodeKey;
use crate::services::dead_code_analyzer::{DeadCodeItem, DeadCodeType};
let mut node_id = 0u32;
for file in &accurate_report.files_with_dead_code {
for item in &file.dead_items {
let dead_item = DeadCodeItem {
node_key: node_id as NodeKey,
name: item.name.clone(),
file_path: file.file_path.display().to_string(),
line_number: item.line as u32,
dead_type: match item.kind {
DeadCodeKind::Function | DeadCodeKind::Method => {
DeadCodeType::UnusedFunction
}
DeadCodeKind::Struct | DeadCodeKind::Enum => DeadCodeType::UnusedClass,
DeadCodeKind::Field | DeadCodeKind::Static | DeadCodeKind::Constant => {
DeadCodeType::UnusedVariable
}
_ => DeadCodeType::UnreachableCode,
},
confidence: 0.95, reason: item.message.clone(),
};
match dead_item.dead_type {
DeadCodeType::UnusedFunction => dead_functions.push(dead_item),
DeadCodeType::UnusedClass => dead_classes.push(dead_item),
DeadCodeType::UnusedVariable => dead_variables.push(dead_item),
_ => {}
}
node_id += 1;
}
}
Ok(DeadCodeReport {
dead_functions,
dead_classes,
dead_variables,
unreachable_code: Vec::new(),
summary: crate::services::dead_code_analyzer::DeadCodeSummary {
total_dead_code_lines: accurate_report.dead_lines,
percentage_dead: accurate_report.dead_code_percentage as f32,
dead_by_type: accurate_report.dead_by_type,
confidence_level: 0.95, },
})
}
fn name(&self) -> &'static str {
"dead_code"
}
}
#[async_trait]
impl ProjectAnalyzer for DeadCodeAnalyzer {
async fn analyze_project(&self, project_path: &Path) -> Result<Self::Output> {
let input = ProjectInput {
project_path: project_path.to_path_buf(),
};
let config = ProjectConfig::default();
self.analyze(input, config).await
}
}
impl AnalyzerInfo for DeadCodeAnalyzer {
fn name(&self) -> &'static str {
"dead_code"
}
fn version(&self) -> &'static str {
env!("CARGO_PKG_VERSION")
}
fn description(&self) -> &'static str {
"Analyzes code for unreachable and unused code patterns"
}
}
pub struct DeadCodeAnalyzerFactory;
impl DeadCodeAnalyzerFactory {
#[must_use]
pub fn create() -> DeadCodeAnalyzer {
DeadCodeAnalyzer::new()
}
#[must_use]
pub fn create_with_capacity(capacity: usize) -> DeadCodeAnalyzer {
DeadCodeAnalyzer::with_capacity(capacity)
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_dead_code_analyzer_creation() {
let analyzer = DeadCodeAnalyzer::new();
assert_eq!(Analyzer::name(&analyzer), "dead_code");
assert_eq!(Analyzer::version(&analyzer), env!("CARGO_PKG_VERSION"));
}
#[test]
fn test_dead_code_analyzer_new() {
let analyzer = DeadCodeAnalyzer::new();
assert_eq!(AnalyzerInfo::name(&analyzer), "dead_code");
}
#[test]
fn test_dead_code_analyzer_default() {
let analyzer = DeadCodeAnalyzer::default();
assert_eq!(AnalyzerInfo::name(&analyzer), "dead_code");
}
#[test]
fn test_dead_code_analyzer_with_capacity() {
let analyzer = DeadCodeAnalyzer::with_capacity(50000);
assert_eq!(AnalyzerInfo::name(&analyzer), "dead_code");
}
#[test]
fn test_dead_code_analyzer_with_large_capacity() {
let analyzer = DeadCodeAnalyzer::with_capacity(1_000_000);
assert_eq!(AnalyzerInfo::name(&analyzer), "dead_code");
}
#[test]
fn test_dead_code_analyzer_with_small_capacity() {
let analyzer = DeadCodeAnalyzer::with_capacity(100);
assert_eq!(AnalyzerInfo::name(&analyzer), "dead_code");
}
#[test]
fn test_dead_code_analyzer_default_capacity() {
assert_eq!(DeadCodeAnalyzer::DEFAULT_CAPACITY, 100_000);
}
#[tokio::test]
async fn test_dead_code_config_default() {
let config = DeadCodeConfig::default();
assert!(config.include_unreachable);
assert_eq!(config.min_dead_lines, 5);
assert_eq!(config.confidence_threshold, 0.7);
}
#[test]
fn test_dead_code_config_custom() {
let config = DeadCodeConfig {
base: ProjectConfig::default(),
include_unreachable: false,
min_dead_lines: 10,
confidence_threshold: 0.9,
};
assert!(!config.include_unreachable);
assert_eq!(config.min_dead_lines, 10);
assert!((config.confidence_threshold - 0.9).abs() < 0.001);
}
#[test]
fn test_dead_code_config_serialization() {
let config = DeadCodeConfig::default();
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains("include_unreachable"));
assert!(json.contains("min_dead_lines"));
assert!(json.contains("confidence_threshold"));
}
#[test]
fn test_dead_code_config_deserialization() {
let json = r#"{
"base": {"include_tests": false, "max_depth": null, "parallel": false},
"include_unreachable": false,
"min_dead_lines": 20,
"confidence_threshold": 0.85
}"#;
let config: DeadCodeConfig = serde_json::from_str(json).unwrap();
assert!(!config.include_unreachable);
assert_eq!(config.min_dead_lines, 20);
assert!((config.confidence_threshold - 0.85).abs() < 0.001);
}
#[test]
fn test_dead_code_config_clone() {
let config = DeadCodeConfig::default();
let cloned = config.clone();
assert_eq!(config.include_unreachable, cloned.include_unreachable);
assert_eq!(config.min_dead_lines, cloned.min_dead_lines);
assert_eq!(config.confidence_threshold, cloned.confidence_threshold);
}
#[test]
fn test_dead_code_config_debug() {
let config = DeadCodeConfig::default();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("DeadCodeConfig"));
assert!(debug_str.contains("include_unreachable"));
}
#[test]
fn test_dead_code_config_roundtrip() {
let config = DeadCodeConfig {
base: ProjectConfig {
include_tests: true,
max_depth: None,
parallel: false,
},
include_unreachable: true,
min_dead_lines: 15,
confidence_threshold: 0.8,
};
let json = serde_json::to_string(&config).unwrap();
let parsed: DeadCodeConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config.include_unreachable, parsed.include_unreachable);
assert_eq!(config.min_dead_lines, parsed.min_dead_lines);
}
#[tokio::test]
async fn test_analyzer_info() {
let analyzer = DeadCodeAnalyzer::new();
assert_eq!(Analyzer::name(&analyzer), "dead_code");
assert!(AnalyzerInfo::description(&analyzer).contains("unreachable"));
}
#[test]
fn test_analyzer_info_name() {
let analyzer = DeadCodeAnalyzer::new();
assert_eq!(AnalyzerInfo::name(&analyzer), "dead_code");
}
#[test]
fn test_analyzer_info_version() {
let analyzer = DeadCodeAnalyzer::new();
let version = AnalyzerInfo::version(&analyzer);
assert!(!version.is_empty());
assert!(version.contains('.'));
}
#[test]
fn test_analyzer_info_description() {
let analyzer = DeadCodeAnalyzer::new();
let description = AnalyzerInfo::description(&analyzer);
assert!(description.contains("unreachable"));
assert!(description.contains("unused"));
}
#[tokio::test]
async fn test_factory_creation() {
let analyzer = DeadCodeAnalyzerFactory::create();
assert_eq!(Analyzer::name(&analyzer), "dead_code");
let analyzer_with_capacity = DeadCodeAnalyzerFactory::create_with_capacity(50000);
assert_eq!(Analyzer::name(&analyzer_with_capacity), "dead_code");
}
#[test]
fn test_factory_create() {
let analyzer = DeadCodeAnalyzerFactory::create();
assert_eq!(AnalyzerInfo::name(&analyzer), "dead_code");
}
#[test]
fn test_factory_create_with_capacity() {
let analyzer = DeadCodeAnalyzerFactory::create_with_capacity(75000);
assert_eq!(AnalyzerInfo::name(&analyzer), "dead_code");
}
#[test]
fn test_factory_create_with_zero_capacity() {
let analyzer = DeadCodeAnalyzerFactory::create_with_capacity(0);
assert_eq!(AnalyzerInfo::name(&analyzer), "dead_code");
}
#[test]
fn test_factory_create_with_default_capacity() {
let analyzer =
DeadCodeAnalyzerFactory::create_with_capacity(DeadCodeAnalyzer::DEFAULT_CAPACITY);
assert_eq!(AnalyzerInfo::name(&analyzer), "dead_code");
}
#[test]
fn test_analyzer_trait_name() {
let analyzer = DeadCodeAnalyzer::new();
let name = <DeadCodeAnalyzer as Analyzer>::name(&analyzer);
assert_eq!(name, "dead_code");
}
#[test]
fn test_dead_code_config_with_project_config() {
let project_config = ProjectConfig {
include_tests: true,
max_depth: None,
parallel: false,
};
let config = DeadCodeConfig {
base: project_config,
include_unreachable: true,
min_dead_lines: 3,
confidence_threshold: 0.6,
};
assert!(config.base.include_tests);
assert!(config.include_unreachable);
}
#[test]
fn test_dead_code_config_confidence_bounds() {
let config_low = DeadCodeConfig {
base: ProjectConfig::default(),
include_unreachable: true,
min_dead_lines: 1,
confidence_threshold: 0.0,
};
assert!((config_low.confidence_threshold - 0.0).abs() < 0.001);
let config_high = DeadCodeConfig {
base: ProjectConfig::default(),
include_unreachable: true,
min_dead_lines: 1,
confidence_threshold: 1.0,
};
assert!((config_high.confidence_threshold - 1.0).abs() < 0.001);
}
#[test]
fn test_dead_code_config_min_lines_variants() {
let config_zero = DeadCodeConfig {
base: ProjectConfig::default(),
include_unreachable: true,
min_dead_lines: 0,
confidence_threshold: 0.7,
};
assert_eq!(config_zero.min_dead_lines, 0);
let config_large = DeadCodeConfig {
base: ProjectConfig::default(),
include_unreachable: true,
min_dead_lines: 1000,
confidence_threshold: 0.7,
};
assert_eq!(config_large.min_dead_lines, 1000);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}