use crate::cli::ComprehensiveOutputFormat;
use crate::services::facades::analysis_orchestrator::{
AnalysisOrchestrator, ComprehensiveAnalysisRequest, ComprehensiveAnalysisResult,
};
use crate::services::service_registry::ServiceRegistry;
use anyhow::Result;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct ComprehensiveAnalysisConfig {
pub project_path: PathBuf,
pub file: Option<PathBuf>,
pub files: Vec<PathBuf>,
pub format: ComprehensiveOutputFormat,
pub include_duplicates: bool,
pub include_dead_code: bool,
pub include_defects: bool,
pub include_complexity: bool,
pub include_tdg: bool,
pub confidence_threshold: f32,
pub min_lines: usize,
pub include: Option<String>,
pub exclude: Option<String>,
pub output: Option<PathBuf>,
pub perf: bool,
pub executive_summary: bool,
pub top_files: usize,
}
pub async fn handle_analyze_comprehensive(config: ComprehensiveAnalysisConfig) -> Result<()> {
eprintln!("🔍 Running comprehensive analysis...");
let start = init_timing(config.perf);
let analysis_path = determine_analysis_path(&config);
let result = run_orchestrated_analysis(analysis_path, &config).await?;
let enhanced_result = enhance_results_if_needed(result, &config).await?;
report_completion_and_performance(start, &config, &enhanced_result);
output_results(
enhanced_result,
config.format,
config.executive_summary,
config.output,
)
.await?;
Ok(())
}
fn init_timing(perf: bool) -> Option<std::time::Instant> {
if perf {
Some(std::time::Instant::now())
} else {
None
}
}
fn determine_analysis_path(config: &ComprehensiveAnalysisConfig) -> PathBuf {
if let Some(single_file) = &config.file {
single_file.clone()
} else if !config.files.is_empty() {
config.project_path.clone()
} else {
config.project_path.clone()
}
}
async fn run_orchestrated_analysis(
analysis_path: PathBuf,
config: &ComprehensiveAnalysisConfig,
) -> Result<ComprehensiveAnalysisResult> {
let registry = Arc::new(ServiceRegistry::new());
let orchestrator = AnalysisOrchestrator::new(registry);
let request = create_analysis_request(analysis_path, config);
orchestrator.analyze(request).await
}
fn create_analysis_request(
path: PathBuf,
config: &ComprehensiveAnalysisConfig,
) -> ComprehensiveAnalysisRequest {
ComprehensiveAnalysisRequest {
path,
include_complexity: config.include_complexity,
include_dead_code: config.include_dead_code,
include_satd: config.include_tdg, include_tests: false,
language: None, parallel: true, }
}
async fn enhance_results_if_needed(
result: ComprehensiveAnalysisResult,
config: &ComprehensiveAnalysisConfig,
) -> Result<ComprehensiveAnalysisResult> {
if config.include_duplicates || config.include_defects {
let additional_config = create_additional_config(config);
enhance_with_additional_analyses(result, additional_config).await
} else {
Ok(result)
}
}
fn create_additional_config(config: &ComprehensiveAnalysisConfig) -> AdditionalAnalysisConfig<'_> {
AdditionalAnalysisConfig {
project_path: &config.project_path,
include_duplicates: config.include_duplicates,
include_defects: config.include_defects,
confidence_threshold: config.confidence_threshold,
min_lines: config.min_lines,
include: &config.include,
exclude: &config.exclude,
top_files: config.top_files,
}
}
fn report_completion_and_performance(
start: Option<std::time::Instant>,
config: &ComprehensiveAnalysisConfig,
result: &ComprehensiveAnalysisResult,
) {
if let Some(start_time) = start {
let elapsed = start_time.elapsed();
eprintln!("✅ Comprehensive analysis completed in {elapsed:?}");
if config.perf {
print_performance_breakdown(result, elapsed.as_millis() as u64);
}
} else {
eprintln!("✅ Comprehensive analysis completed");
}
}
struct AdditionalAnalysisConfig<'a> {
project_path: &'a Path,
include_duplicates: bool,
include_defects: bool,
confidence_threshold: f32,
min_lines: usize,
include: &'a Option<String>,
exclude: &'a Option<String>,
top_files: usize,
}
async fn enhance_with_additional_analyses(
mut result: ComprehensiveAnalysisResult,
config: AdditionalAnalysisConfig<'_>,
) -> Result<ComprehensiveAnalysisResult> {
if config.include_duplicates {
eprintln!("👥 Detecting duplicates...");
result.summary.recommendations.push(
"Duplicate detection analysis requested - integrate with duplicate detector"
.to_string(),
);
}
if config.include_defects {
eprintln!("🐛 Predicting defects...");
use crate::services::facades::defect_prediction_facade::{
DefectPredictionFacade, DefectPredictionRequest,
};
use crate::services::service_registry::ServiceRegistry;
let registry = Arc::new(ServiceRegistry::new());
let facade = DefectPredictionFacade::new(registry);
let request = DefectPredictionRequest {
project_path: config.project_path.to_path_buf(),
confidence_threshold: config.confidence_threshold,
min_lines: config.min_lines,
include_low_confidence: false,
high_risk_only: false,
include_recommendations: true,
include: config.include.as_ref().map(|s| vec![s.clone()]),
exclude: config.exclude.as_ref().map(|s| vec![s.clone()]),
top_files: config.top_files,
};
if let Ok(defect_result) = facade.analyze_project(request).await {
result.summary.total_issues += defect_result.high_risk_files;
result.summary.critical_issues += defect_result.high_risk_files;
if defect_result.high_risk_files > 0 {
result.summary.recommendations.push(format!(
"Focus on {} high-risk files identified by defect prediction",
defect_result.high_risk_files
));
}
}
}
Ok(result)
}
fn print_performance_breakdown(result: &ComprehensiveAnalysisResult, total_ms: u64) {
eprintln!("\n⏱️ Performance Breakdown:");
eprintln!(" Total execution time: {total_ms}ms");
eprintln!(" Analysis duration: {}ms", result.duration_ms);
eprintln!(" Files analyzed: {}", result.summary.total_files);
eprintln!(" Issues found: {}", result.summary.total_issues);
if result.summary.total_files > 0 {
let ms_per_file = total_ms as f64 / result.summary.total_files as f64;
eprintln!(" Average time per file: {ms_per_file:.2}ms");
}
}
async fn output_results(
result: ComprehensiveAnalysisResult,
format: ComprehensiveOutputFormat,
executive_summary: bool,
output: Option<PathBuf>,
) -> Result<()> {
let content = format_result(result, format, executive_summary)?;
if let Some(output_path) = output {
tokio::fs::write(&output_path, &content).await?;
eprintln!("📄 Report written to: {}", output_path.display());
} else {
println!("{content}");
}
Ok(())
}
fn format_result(
result: ComprehensiveAnalysisResult,
format: ComprehensiveOutputFormat,
executive_summary: bool,
) -> Result<String> {
match format {
ComprehensiveOutputFormat::Json => format_as_json(&result),
ComprehensiveOutputFormat::Markdown => format_as_markdown(&result, executive_summary),
ComprehensiveOutputFormat::Sarif => format_as_sarif(&result),
ComprehensiveOutputFormat::Summary => format_as_markdown(&result, true), ComprehensiveOutputFormat::Detailed => format_as_markdown(&result, false), }
}
fn format_as_json(result: &ComprehensiveAnalysisResult) -> Result<String> {
serde_json::to_string_pretty(result).map_err(Into::into)
}
fn format_as_markdown(
result: &ComprehensiveAnalysisResult,
executive_summary: bool,
) -> Result<String> {
use std::fmt::Write;
let mut output = String::new();
writeln!(&mut output, "# Comprehensive Code Analysis Report\n")?;
if executive_summary {
format_executive_summary(&mut output, &result.summary)?;
}
if let Some(complexity) = &result.complexity {
format_complexity_section(&mut output, complexity)?;
}
if let Some(dead_code) = &result.dead_code {
format_dead_code_section(&mut output, dead_code)?;
}
if let Some(satd) = &result.satd {
format_satd_section(&mut output, satd)?;
}
Ok(output)
}
fn format_executive_summary(
output: &mut String,
summary: &crate::services::facades::analysis_orchestrator::AnalysisSummary,
) -> Result<()> {
use std::fmt::Write;
writeln!(output, "## Executive Summary\n")?;
writeln!(
output,
"Project analysis completed with {} total files analyzed.\n",
summary.total_files
)?;
writeln!(output, "- **Quality Score**: {:.1}%", summary.quality_score)?;
writeln!(output, "- **Total Files**: {}", summary.total_files)?;
writeln!(output, "- **Total Issues**: {}", summary.total_issues)?;
writeln!(output, "- **Critical Issues**: {}", summary.critical_issues)?;
writeln!(output)?;
if !summary.recommendations.is_empty() {
writeln!(output, "### Key Recommendations\n")?;
for rec in &summary.recommendations {
writeln!(output, "- {rec}")?;
}
writeln!(output)?;
}
Ok(())
}
fn format_complexity_section(
output: &mut String,
complexity: &crate::services::facades::complexity_facade::ComplexityAnalysisResult,
) -> Result<()> {
use std::fmt::Write;
writeln!(output, "## Complexity Analysis\n")?;
writeln!(output, "- **Files Analyzed**: {}", complexity.total_files)?;
writeln!(
output,
"- **Average Complexity**: {:.1}",
complexity.average_complexity
)?;
writeln!(
output,
"- **Max Complexity**: {}",
complexity.max_complexity
)?;
writeln!(output, "- **Violations**: {}", complexity.violations.len())?;
if !complexity.violations.is_empty() {
writeln!(output, "\n### Top Complexity Violations\n")?;
for (i, violation) in complexity.violations.iter().take(5).enumerate() {
writeln!(
output,
"{}. {} - {} (complexity: {})",
i + 1,
violation.file_path,
violation.function_name,
violation.complexity
)?;
}
}
writeln!(output)?;
Ok(())
}
fn format_dead_code_section(
output: &mut String,
dead_code: &crate::services::facades::dead_code_facade::DeadCodeAnalysisResult,
) -> Result<()> {
use std::fmt::Write;
writeln!(output, "## Dead Code Analysis\n")?;
writeln!(output, "- **Files Analyzed**: {}", dead_code.total_files)?;
writeln!(output, "- **Dead Items**: {}", dead_code.dead_items.len())?;
writeln!(
output,
"- **Dead Code %**: {:.1}%",
dead_code.dead_percentage
)?;
if !dead_code.dead_items.is_empty() {
writeln!(output, "\n### Dead Code Items\n")?;
for (i, item) in dead_code.dead_items.iter().take(5).enumerate() {
writeln!(
output,
"{}. {} - {} ({:?})",
i + 1,
item.file_path,
item.item_name,
item.item_type
)?;
}
}
writeln!(output)?;
Ok(())
}
fn format_satd_section(
output: &mut String,
satd: &crate::services::facades::satd_facade::SatdAnalysisResult,
) -> Result<()> {
use std::fmt::Write;
writeln!(output, "## Technical Debt (SATD) Analysis\n")?;
writeln!(output, "- **Files Analyzed**: {}", satd.total_files)?;
writeln!(output, "- **Violations**: {}", satd.violations.len())?;
if !satd.violations.is_empty() {
writeln!(output, "\n### SATD Violations\n")?;
for (i, violation) in satd.violations.iter().take(5).enumerate() {
writeln!(
output,
"{}. {}:{} - {} ({:?})",
i + 1,
violation.file_path,
violation.line_number,
violation.violation_type,
violation.severity
)?;
}
}
writeln!(output)?;
Ok(())
}
fn format_as_sarif(result: &ComprehensiveAnalysisResult) -> Result<String> {
let mut results = Vec::new();
if let Some(complexity) = &result.complexity {
for violation in &complexity.violations {
if violation.complexity > 20 {
results.push(serde_json::json!({
"ruleId": "high-complexity",
"level": if violation.complexity > 30 { "error" } else { "warning" },
"message": {
"text": format!("Function {} has complexity {}", violation.function_name, violation.complexity)
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": violation.file_path.clone()
},
"region": {
"startLine": violation.line_number
}
}
}]
}));
}
}
}
if let Some(satd) = &result.satd {
for violation in &satd.violations {
results.push(serde_json::json!({
"ruleId": "technical-debt",
"level": "warning",
"message": {
"text": format!("{}: {}", violation.violation_type, violation.message)
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": violation.file_path.clone()
},
"region": {
"startLine": violation.line_number
}
}
}]
}));
}
}
let sarif = serde_json::json!({
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "pmat-comprehensive",
"version": env!("CARGO_PKG_VERSION"),
"informationUri": "https://github.com/paiml/paiml-mcp-agent-toolkit"
}
},
"results": results
}]
});
serde_json::to_string_pretty(&sarif).map_err(Into::into)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_as_json() {
use crate::services::facades::analysis_orchestrator::AnalysisSummary;
let result = ComprehensiveAnalysisResult {
complexity: None,
dead_code: None,
satd: None,
summary: AnalysisSummary {
total_files: 10,
total_issues: 5,
critical_issues: 2,
quality_score: 85.0,
recommendations: vec!["Test recommendation".to_string()],
},
duration_ms: 1000,
};
let json = format_as_json(&result).unwrap();
assert!(json.contains("\"total_files\": 10"));
assert!(json.contains("\"quality_score\": 85.0"));
}
}
#[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);
}
}
}