use std::path::PathBuf;
use serde::Serialize;
use super::analyzer::{CognitiveLevel, FunctionCognitive};
use crate::report_helpers::{self, PerFunctionFile, PerFunctionRow};
pub struct FileCogcomMetrics {
pub path: PathBuf,
pub language: String,
pub function_count: usize,
pub avg_complexity: f64,
pub max_complexity: usize,
pub total_complexity: usize,
pub level: CognitiveLevel,
pub functions: Vec<FunctionCognitive>,
}
pub fn print_report(files: &[FileCogcomMetrics]) {
if files.is_empty() {
println!("No recognized source files found.");
return;
}
let max_path_len = report_helpers::max_path_width(files.iter().map(|f| f.path.as_path()), 4);
let header_width = max_path_len + 55;
let separator = report_helpers::separator(header_width.max(78));
println!("Cognitive Complexity");
println!("{separator}");
println!(
" {:<width$} {:>9} {:>5} {:>5} {:>7} Level",
"File",
"Functions",
"Avg",
"Max",
"Total",
width = max_path_len
);
println!("{separator}");
for f in files {
println!(
" {:<width$} {:>9} {:>5.1} {:>5} {:>7} {}",
f.path.display(),
f.function_count,
f.avg_complexity,
f.max_complexity,
f.total_complexity,
f.level.as_str(),
width = max_path_len
);
}
println!("{separator}");
let total_functions: usize = files.iter().map(|f| f.function_count).sum();
let total_complexity: usize = files.iter().map(|f| f.total_complexity).sum();
let max_complexity = files.iter().map(|f| f.max_complexity).max().unwrap_or(0);
let avg = if total_functions > 0 {
total_complexity as f64 / total_functions as f64
} else {
0.0
};
let total_label = format!(" Total ({} files)", files.len());
println!(
"{:<width$} {:>9} {:>5.1} {:>5} {:>7}",
total_label,
total_functions,
avg,
max_complexity,
total_complexity,
width = max_path_len + 1,
);
}
impl PerFunctionRow for FunctionCognitive {
fn name(&self) -> &str {
&self.name
}
fn complexity(&self) -> usize {
self.complexity
}
fn level_str(&self) -> &str {
self.level.as_str()
}
}
impl PerFunctionFile for FileCogcomMetrics {
type Row = FunctionCognitive;
fn path_str(&self) -> String {
self.path.display().to_string()
}
fn rows(&self) -> &[FunctionCognitive] {
&self.functions
}
}
pub fn print_per_function(files: &[FileCogcomMetrics]) {
report_helpers::print_per_function_breakdown("Cognitive Complexity (per function)", files);
}
#[derive(Serialize)]
struct JsonFunctionEntry {
name: String,
start_line: usize,
complexity: usize,
level: CognitiveLevel,
}
#[derive(Serialize)]
struct JsonFileEntry {
path: String,
language: String,
function_count: usize,
avg_complexity: f64,
max_complexity: usize,
total_complexity: usize,
level: CognitiveLevel,
functions: Vec<JsonFunctionEntry>,
}
pub fn print_json(files: &[FileCogcomMetrics]) -> Result<(), Box<dyn std::error::Error>> {
let entries: Vec<JsonFileEntry> = files
.iter()
.map(|f| JsonFileEntry {
path: f.path.display().to_string(),
language: f.language.clone(),
function_count: f.function_count,
avg_complexity: f.avg_complexity,
max_complexity: f.max_complexity,
total_complexity: f.total_complexity,
level: f.level,
functions: f
.functions
.iter()
.map(|func| JsonFunctionEntry {
name: func.name.clone(),
start_line: func.start_line,
complexity: func.complexity,
level: func.level,
})
.collect(),
})
.collect();
report_helpers::print_json_stdout(&entries)
}
pub fn print_github(files: &[FileCogcomMetrics], min_complexity: usize) {
for f in files {
let path = f.path.display().to_string();
for func in &f.functions {
if func.complexity >= min_complexity {
let message = format!(
"function '{}' has cognitive complexity {} (threshold: {})",
func.name, func.complexity, min_complexity
);
report_helpers::github_annotation(
"warning",
&path,
func.start_line,
"Cognitive Complexity",
&message,
);
}
}
}
}
#[cfg(test)]
#[path = "report_test.rs"]
mod tests;