async fn handle_analyze_comprehensive_with_config(config: ComprehensiveConfig) -> Result<()> {
let start_time = Instant::now();
info!("🔍 Starting comprehensive analysis");
let (analysis_path, single_file_mode, target_files) = determine_analysis_mode(&config)?;
let enabled_analyses = get_enabled_analyses(&config);
info!("📊 Enabled analyses: {}", enabled_analyses.join(", "));
let service = DefectReportService::new();
let mut report = service.generate_report(&analysis_path).await?;
report = DefectReportService::filter_by_pattern(
&report,
config.include.clone(),
config.exclude.clone(),
config.min_lines,
);
let filtered_defects = filter_defects(
&report.defects,
single_file_mode,
&target_files,
config.confidence_threshold,
);
info!("📈 Total defects found: {}", report.defects.len());
info!(
"📉 After confidence filter (>={:.0}%): {}",
config.confidence_threshold * 100.0,
filtered_defects.len()
);
let formatted_output = format_report(&service, &report, filtered_defects, &config)?;
write_output(&config.output, &formatted_output).await?;
let elapsed = start_time.elapsed();
if config.perf {
print_performance_metrics(elapsed, &report);
}
info!("\n📊 Defects by Category:");
for (category, count) in &report.summary.by_category {
info!(" {}: {}", category, count);
}
warn_ignored_parameters(&config);
Ok(())
}
fn determine_analysis_mode(config: &ComprehensiveConfig) -> Result<(PathBuf, bool, Vec<PathBuf>)> {
let analysis_path = if let Some(ref file) = config.file {
find_project_root(file)?
} else {
config.project_path.clone()
};
let single_file_mode = config.file.is_some();
let target_files = if !config.files.is_empty() {
config.files.clone()
} else if let Some(ref file) = config.file {
vec![file.clone()]
} else {
vec![]
};
Ok((analysis_path, single_file_mode, target_files))
}
fn get_enabled_analyses(config: &ComprehensiveConfig) -> Vec<String> {
let mut analyses = Vec::new();
if config.include_complexity {
analyses.push("Complexity".to_string());
}
if config.include_tdg {
analyses.push("TDG".to_string());
}
if config.include_defects {
analyses.push("Defects".to_string());
}
if config.include_dead_code {
analyses.push("Dead Code".to_string());
}
if config.include_duplicates {
analyses.push("Duplicates".to_string());
}
analyses
}
fn filter_defects(
defects: &[crate::models::defect_report::Defect],
single_file_mode: bool,
target_files: &[PathBuf],
confidence_threshold: f32,
) -> Vec<crate::models::defect_report::Defect> {
defects
.iter()
.filter(|defect| {
let confidence = defect.metrics.get("confidence").copied().unwrap_or(1.0) as f32;
if confidence < confidence_threshold {
return false;
}
if single_file_mode && !target_files.is_empty() {
return target_files.contains(&defect.file_path);
}
true
})
.cloned()
.collect()
}
fn format_report(
service: &DefectReportService,
report: &crate::models::defect_report::DefectReport,
filtered_defects: Vec<crate::models::defect_report::Defect>,
config: &ComprehensiveConfig,
) -> Result<String> {
let format = match config.format {
ComprehensiveOutputFormat::Json => ReportFormat::Json,
ComprehensiveOutputFormat::Summary => ReportFormat::Markdown,
ComprehensiveOutputFormat::Detailed => ReportFormat::Markdown,
ComprehensiveOutputFormat::Markdown => ReportFormat::Markdown,
ComprehensiveOutputFormat::Sarif => ReportFormat::Json, };
let mut filtered_report = report.clone();
filtered_report.defects = filtered_defects;
match format {
ReportFormat::Json => serde_json::to_string_pretty(&filtered_report)
.context("Failed to serialize report to JSON"),
ReportFormat::Markdown | ReportFormat::Text => service
.format_text(&filtered_report)
.context("Failed to format report as text"),
ReportFormat::Csv => {
serde_json::to_string_pretty(&filtered_report).context("Failed to serialize report")
}
}
}
async fn write_output(output: &Option<PathBuf>, content: &str) -> Result<()> {
if let Some(output_path) = output {
tokio::fs::write(output_path, content)
.await
.context("Failed to write output file")?;
info!("📝 Report written to: {}", output_path.display());
} else {
println!("{content}");
}
Ok(())
}
fn print_performance_metrics(
elapsed: std::time::Duration,
report: &crate::models::defect_report::DefectReport,
) {
info!("\n⏱️ Performance Metrics:");
info!(" Total time: {:.2}s", elapsed.as_secs_f64());
info!(" Hotspot files: {}", report.summary.hotspot_files.len());
info!(" Defects found: {}", report.summary.total_defects);
let defects_per_second = report.summary.total_defects as f64 / elapsed.as_secs_f64();
info!(" Defects/second: {:.2}", defects_per_second);
}
fn warn_ignored_parameters(_config: &ComprehensiveConfig) {
}
fn find_project_root(start_path: &Path) -> Result<PathBuf> {
let start_dir = if start_path.is_file() {
start_path
.parent()
.context("File has no parent directory")?
} else {
start_path
};
walk_up_to_cargo_toml(start_dir)
.ok_or_else(|| anyhow::anyhow!("No Cargo.toml found"))
.or_else(|_| Ok(start_dir.to_path_buf()))
}
fn walk_up_to_cargo_toml(start: &Path) -> Option<PathBuf> {
let mut current = start;
loop {
if current.join("Cargo.toml").exists() {
return Some(current.to_path_buf());
}
let parent = current.parent()?;
if is_system_root(parent) {
return None;
}
current = parent;
}
}
fn is_system_root(path: &Path) -> bool {
path == Path::new("/tmp") || path == Path::new("/") || path == Path::new("/home")
}