#![cfg_attr(coverage_nightly, coverage(off))]
use crate::cli::colors as c;
use crate::cli::BigOOutputFormat;
use crate::models::complexity_bound::BigOClass;
use crate::services::big_o_analyzer::{BigOAnalysisReport, BigOAnalyzer};
use anyhow::Result;
use std::path::PathBuf;
use tracing::info;
pub(super) fn format_analysis_output(
analyzer: &BigOAnalyzer,
report: &BigOAnalysisReport,
format: BigOOutputFormat,
) -> Result<String> {
match format {
BigOOutputFormat::Json => analyzer.format_as_json(report),
BigOOutputFormat::Markdown => Ok(analyzer.format_as_markdown(report)),
BigOOutputFormat::Summary => Ok(format_big_o_summary(report)),
BigOOutputFormat::Detailed => Ok(format_big_o_detailed(report)),
}
}
pub(super) async fn write_analysis_output(content: &str, output: Option<PathBuf>) -> Result<()> {
if let Some(output_path) = output {
tokio::fs::write(&output_path, content).await?;
info!("📄 Big-O analysis saved to: {}", output_path.display());
} else {
println!("{content}");
}
Ok(())
}
#[must_use]
pub fn format_big_o_summary(report: &BigOAnalysisReport) -> String {
let mut output = String::with_capacity(1024);
output.push_str(&format!(
"{}\n",
c::header("Big-O Complexity Analysis Summary")
));
output.push('\n');
output.push_str(&format!(
" {}: {}\n",
c::label("Total Functions Analyzed"),
c::number(&report.analyzed_functions.to_string()),
));
let high_color = if report.high_complexity_functions.is_empty() {
c::GREEN
} else {
c::YELLOW
};
output.push_str(&format!(
" {}: {}{}{}\n\n",
c::label("High Complexity Functions"),
high_color,
report.high_complexity_functions.len(),
c::RESET,
));
output.push_str(&format!("{}\n", c::subheader("Complexity Distribution:")));
let dist = &report.complexity_distribution;
output.push_str(&format!(
" {}O(1){} : {} functions\n",
c::GREEN,
c::RESET,
c::number(&format!("{:>4}", dist.constant))
));
output.push_str(&format!(
" {}O(log n){} : {} functions\n",
c::GREEN,
c::RESET,
c::number(&format!("{:>4}", dist.logarithmic))
));
output.push_str(&format!(
" {}O(n){} : {} functions\n",
c::YELLOW,
c::RESET,
c::number(&format!("{:>4}", dist.linear))
));
output.push_str(&format!(
" {}O(n log n){} : {} functions\n",
c::YELLOW,
c::RESET,
c::number(&format!("{:>4}", dist.linearithmic))
));
output.push_str(&format!(
" {}O(n²){} : {} functions\n",
c::RED,
c::RESET,
c::number(&format!("{:>4}", dist.quadratic))
));
output.push_str(&format!(
" {}O(n³){} : {} functions\n",
c::RED,
c::RESET,
c::number(&format!("{:>4}", dist.cubic))
));
output.push_str(&format!(
" {}O(2^n){} : {} functions\n",
c::BOLD_RED,
c::RESET,
c::number(&format!("{:>4}", dist.exponential))
));
output.push_str(&format!(
" {}Unknown{} : {} functions\n",
c::DIM,
c::RESET,
c::number(&format!("{:>4}", dist.unknown))
));
if !report.recommendations.is_empty() {
output.push_str(&format!("\n{}\n", c::subheader("Recommendations:")));
for rec in &report.recommendations {
output.push_str(&format!(" {} {rec}\n", c::warn("")));
}
}
if !report.high_complexity_functions.is_empty() {
output.push_str(&format!("\n{}\n", c::subheader("Top Files by Complexity:")));
use std::collections::HashMap;
let mut file_scores: HashMap<&std::path::Path, f64> = HashMap::new();
let mut file_function_counts: HashMap<&std::path::Path, usize> = HashMap::new();
for func in &report.high_complexity_functions {
let score = match func.time_complexity.class {
BigOClass::Constant => 1.0,
BigOClass::Logarithmic => 2.0,
BigOClass::Linear => 3.0,
BigOClass::Linearithmic => 4.0,
BigOClass::Quadratic => 5.0,
BigOClass::Cubic => 6.0,
BigOClass::Exponential => 7.0,
BigOClass::Factorial => 8.0,
BigOClass::Unknown => 3.0,
};
*file_scores.entry(&func.file_path).or_insert(0.0) += score;
*file_function_counts.entry(&func.file_path).or_insert(0) += 1;
}
let mut sorted_files: Vec<_> = file_scores.into_iter().collect();
sorted_files.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
for (i, (file_path, score)) in sorted_files.iter().take(10).enumerate() {
let filename = file_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(file_path.to_str().unwrap_or("unknown"));
let function_count = file_function_counts.get(file_path).unwrap_or(&0);
let score_color = if *score > 20.0 {
c::RED
} else if *score > 10.0 {
c::YELLOW
} else {
c::GREEN
};
output.push_str(&format!(
" {}. {} - score: {}{:.1}{}, {} functions\n",
c::number(&(i + 1).to_string()),
c::path(filename),
score_color,
score,
c::RESET,
c::number(&function_count.to_string()),
));
}
}
output
}
pub(super) fn format_big_o_detailed(report: &BigOAnalysisReport) -> String {
let mut output = format_big_o_summary(report);
if !report.high_complexity_functions.is_empty() {
output.push_str(&format!("\n{}\n", c::header("High Complexity Functions:")));
for func in &report.high_complexity_functions {
output.push_str(&format!(
"\n{} ({}:{}{}{})\n",
c::label(&func.function_name),
c::path(&func.file_path.display().to_string()),
c::DIM,
func.line_number,
c::RESET,
));
output.push_str(&format!(
" {}: {} ({})\n",
c::label("Time Complexity"),
func.time_complexity.notation(),
c::pct(func.time_complexity.confidence as f64, 80.0, 50.0),
));
output.push_str(&format!(
" {}: {} ({})\n",
c::label("Space Complexity"),
func.space_complexity.notation(),
c::pct(func.space_complexity.confidence as f64, 80.0, 50.0),
));
if !func.notes.is_empty() {
output.push_str(&format!(" {}:\n", c::label("Notes")));
for note in &func.notes {
output.push_str(&format!(" {}─{} {note}\n", c::DIM, c::RESET));
}
}
}
}
if !report.pattern_matches.is_empty() {
output.push_str(&format!("\n{}\n", c::header("Pattern Matches:")));
for pattern in &report.pattern_matches {
output.push_str(&format!(
" {} : {} occurrences\n",
c::label(&pattern.pattern_name),
c::number(&pattern.occurrences.to_string()),
));
}
}
output
}