#![cfg_attr(coverage_nightly, coverage(off))]
use crate::cli::ComprehensiveOutputFormat;
use crate::services::facades::analysis_orchestrator::ComprehensiveAnalysisResult;
use anyhow::Result;
use std::path::PathBuf;
pub(super) 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(())
}
pub(super) 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_text(&result, true),
ComprehensiveOutputFormat::Detailed => format_as_text(&result, false),
}
}
pub(super) fn format_as_json(result: &ComprehensiveAnalysisResult) -> Result<String> {
serde_json::to_string_pretty(result).map_err(Into::into)
}
pub(super) 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)
}
pub(super) 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(())
}
pub(super) 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(())
}
pub(super) 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(())
}
pub(super) 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(())
}
pub(super) fn format_as_text(
result: &ComprehensiveAnalysisResult,
executive_summary: bool,
) -> Result<String> {
use crate::cli::colors as c;
use std::fmt::Write;
let mut output = String::new();
writeln!(
&mut output,
"{}",
c::header("Comprehensive Code Analysis Report")
)?;
writeln!(&mut output)?;
if executive_summary {
writeln!(&mut output, "{}\n", c::subheader("Executive Summary"))?;
writeln!(
&mut output,
" Project analysis completed with {} total files analyzed.\n",
c::number(&result.summary.total_files.to_string())
)?;
writeln!(
&mut output,
" Quality Score: {}",
c::pct(result.summary.quality_score, 80.0, 50.0)
)?;
writeln!(
&mut output,
" Total Files: {}",
c::number(&result.summary.total_files.to_string())
)?;
writeln!(
&mut output,
" Total Issues: {}",
c::number(&result.summary.total_issues.to_string())
)?;
writeln!(
&mut output,
" Critical: {}",
if result.summary.critical_issues > 0 {
format!(
"{}{}{}",
c::BOLD_RED,
result.summary.critical_issues,
c::RESET
)
} else {
c::number(&result.summary.critical_issues.to_string())
}
)?;
writeln!(&mut output)?;
if !result.summary.recommendations.is_empty() {
writeln!(
&mut output,
" {}Key Recommendations{}\n",
c::BOLD,
c::RESET
)?;
for rec in &result.summary.recommendations {
writeln!(&mut output, " {} {rec}", c::dim("-"))?;
}
writeln!(&mut output)?;
}
}
if let Some(complexity) = &result.complexity {
writeln!(&mut output, "{}\n", c::subheader("Complexity Analysis"))?;
writeln!(
&mut output,
" Files Analyzed: {}",
c::number(&complexity.total_files.to_string())
)?;
writeln!(
&mut output,
" Average Complexity: {}",
c::number(&format!("{:.1}", complexity.average_complexity))
)?;
writeln!(
&mut output,
" Max Complexity: {}",
c::number(&complexity.max_complexity.to_string())
)?;
writeln!(
&mut output,
" Violations: {}",
if complexity.violations.is_empty() {
c::number("0")
} else {
format!("{}{}{}", c::YELLOW, complexity.violations.len(), c::RESET)
}
)?;
if !complexity.violations.is_empty() {
writeln!(
&mut output,
"\n {}Top Complexity Violations{}\n",
c::BOLD,
c::RESET
)?;
for (i, v) in complexity.violations.iter().take(5).enumerate() {
writeln!(
&mut output,
" {}. {} - {} (complexity: {})",
c::number(&(i + 1).to_string()),
c::path(&v.file_path),
c::label(&v.function_name),
c::number(&v.complexity.to_string())
)?;
}
}
writeln!(&mut output)?;
}
if let Some(dead_code) = &result.dead_code {
writeln!(&mut output, "{}\n", c::subheader("Dead Code Analysis"))?;
writeln!(
&mut output,
" Files Analyzed: {}",
c::number(&dead_code.total_files.to_string())
)?;
writeln!(
&mut output,
" Dead Items: {}",
c::number(&dead_code.dead_items.len().to_string())
)?;
writeln!(
&mut output,
" Dead Code: {}",
c::pct(dead_code.dead_percentage, 5.0, 15.0)
)?;
if !dead_code.dead_items.is_empty() {
writeln!(&mut output, "\n {}Dead Code Items{}\n", c::BOLD, c::RESET)?;
for (i, item) in dead_code.dead_items.iter().take(5).enumerate() {
writeln!(
&mut output,
" {}. {} - {} ({:?})",
c::number(&(i + 1).to_string()),
c::path(&item.file_path),
c::label(&item.item_name),
item.item_type
)?;
}
}
writeln!(&mut output)?;
}
if let Some(satd) = &result.satd {
writeln!(
&mut output,
"{}\n",
c::subheader("Technical Debt (SATD) Analysis")
)?;
writeln!(
&mut output,
" Files Analyzed: {}",
c::number(&satd.total_files.to_string())
)?;
writeln!(
&mut output,
" Violations: {}",
if satd.violations.is_empty() {
c::number("0")
} else {
format!("{}{}{}", c::YELLOW, satd.violations.len(), c::RESET)
}
)?;
if !satd.violations.is_empty() {
writeln!(&mut output, "\n {}SATD Violations{}\n", c::BOLD, c::RESET)?;
for (i, v) in satd.violations.iter().take(5).enumerate() {
writeln!(
&mut output,
" {}. {}:{} - {} ({:?})",
c::number(&(i + 1).to_string()),
c::path(&v.file_path),
c::number(&v.line_number.to_string()),
v.violation_type,
v.severity
)?;
}
}
writeln!(&mut output)?;
}
Ok(output)
}
pub(super) 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)
}