use crate::formatting::FormattingConfig;
use crate::output::unified::{classify_coupling, CouplingClassification};
use crate::priority::{UnifiedAnalysis, UnifiedDebtItem};
use crate::priority::formatter_verbosity as verbosity;
mod context;
mod dependencies;
mod helpers;
mod orchestrators;
pub mod pure;
mod recommendations;
mod sections;
pub mod summary;
pub mod writer;
#[derive(Debug, Clone, Copy)]
pub enum OutputFormat {
Default, Top(usize), Tail(usize), }
pub fn format_priorities(analysis: &UnifiedAnalysis, format: OutputFormat) -> String {
format_priorities_with_verbosity(analysis, format, 0)
}
pub fn format_priorities_with_verbosity(
analysis: &UnifiedAnalysis,
format: OutputFormat,
verbosity: u8,
) -> String {
format_priorities_with_config(analysis, format, verbosity, FormattingConfig::default())
}
pub fn format_priorities_with_config(
analysis: &UnifiedAnalysis,
format: OutputFormat,
verbosity: u8,
config: FormattingConfig,
) -> String {
match format {
OutputFormat::Default => {
orchestrators::format_default_with_config(analysis, 10, verbosity, config)
}
OutputFormat::Top(n) => {
orchestrators::format_default_with_config(analysis, n, verbosity, config)
}
OutputFormat::Tail(n) => {
orchestrators::format_tail_with_config(analysis, n, verbosity, config)
}
}
}
pub fn format_summary_terminal(analysis: &UnifiedAnalysis, limit: usize, verbosity: u8) -> String {
summary::format_summary_terminal(analysis, limit, verbosity)
}
pub fn format_priority_item(
output: &mut String,
rank: usize,
item: &UnifiedDebtItem,
has_coverage_data: bool,
) {
let formatted = pure::format_priority_item(
rank,
item,
0, FormattingConfig::default(),
has_coverage_data,
);
let mut buffer = Vec::new();
let _ = writer::write_priority_item(&mut buffer, &formatted);
if let Ok(result) = String::from_utf8(buffer) {
output.push_str(&result);
}
}
pub use helpers::{
extract_complexity_info, extract_dependency_info, format_debt_type, format_impact, format_role,
};
fn format_truncated_list(items: &[String], max: usize) -> String {
if items.is_empty() {
return String::new();
}
if items.len() <= max {
items.join(", ")
} else {
let shown: Vec<_> = items.iter().take(max).collect();
format!(
"{} (+{} more)",
shown
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", "),
items.len() - max
)
}
}
fn coupling_classification_label(classification: &CouplingClassification) -> &'static str {
match classification {
CouplingClassification::WellTestedCore => "well-tested core",
CouplingClassification::StableFoundation => "stable foundation",
CouplingClassification::UnstableHighCoupling => "unstable high coupling",
CouplingClassification::ArchitecturalHub => "architectural hub",
CouplingClassification::StableCore => "stable core",
CouplingClassification::UtilityModule => "utility",
CouplingClassification::LeafModule => "leaf",
CouplingClassification::Isolated => "isolated",
CouplingClassification::HighlyCoupled => "highly coupled",
}
}
pub fn format_file_priority_item_with_verbosity(
output: &mut String,
rank: usize,
item: &crate::priority::FileDebtItem,
_config: FormattingConfig,
_verbosity: u8,
) {
use colored::*;
use std::fmt::Write;
let severity = crate::priority::classification::Severity::from_score_100(item.score);
let (severity_label, severity_color) = (severity.as_str(), severity.color());
writeln!(
output,
"#{} {} [{}]",
rank,
format!("SCORE: {:.1}", item.score).bright_yellow(),
severity_label.color(severity_color).bold()
)
.unwrap();
writeln!(
output,
"{} {}",
"├─ LOCATION:".bright_blue(),
item.metrics.path.display()
)
.unwrap();
writeln!(
output,
"{} {}",
"├─ IMPACT:".bright_blue(),
format!(
"-{:.0} complexity, -{:.1} maintainability improvement",
item.impact.complexity_reduction, item.impact.maintainability_improvement
)
.bright_cyan()
)
.unwrap();
writeln!(
output,
"{} {} lines, {} functions, avg complexity: {:.1}",
"├─ METRICS:".bright_blue(),
item.metrics.total_lines,
item.metrics.function_count,
item.metrics.avg_complexity
)
.unwrap();
let total_coupling = item.metrics.afferent_coupling + item.metrics.efferent_coupling;
if total_coupling >= 2 {
let classification = classify_coupling(
item.metrics.afferent_coupling,
item.metrics.efferent_coupling,
);
let label = coupling_classification_label(&classification);
writeln!(
output,
"{} Ca={} ({}), Ce={}, I={:.2}",
"├─ COUPLING:".bright_blue(),
item.metrics.afferent_coupling,
label,
item.metrics.efferent_coupling,
item.metrics.instability
)
.unwrap();
if !item.metrics.dependents.is_empty() {
let display = format_truncated_list(&item.metrics.dependents, 3);
writeln!(output, " {} {}", "←".dimmed(), display).unwrap();
}
if !item.metrics.dependencies_list.is_empty() {
let display = format_truncated_list(&item.metrics.dependencies_list, 3);
writeln!(output, " {} {}", "→".dimmed(), display).unwrap();
}
}
if let Some(ref god_analysis) = item.metrics.god_object_analysis {
if god_analysis.is_god_object {
writeln!(
output,
"{} {} methods, {} fields, {} responsibilities (score: {:.1})",
"├─ GOD OBJECT:".bright_blue(),
god_analysis.method_count,
god_analysis.field_count,
god_analysis.responsibility_count,
god_analysis.god_object_score
)
.unwrap();
if !god_analysis.recommended_splits.is_empty() {
writeln!(
output,
" {} {} recommended module splits",
"Suggested:".dimmed(),
god_analysis.recommended_splits.len()
)
.unwrap();
}
}
}
writeln!(
output,
"{} {}",
"├─ ACTION:".bright_blue(),
item.recommendation.bright_yellow()
)
.unwrap();
let rationale = format_file_rationale(item);
writeln!(
output,
"{} {}",
"└─ WHY THIS MATTERS:".bright_blue(),
rationale
)
.unwrap();
}
fn format_file_rationale(item: &crate::priority::FileDebtItem) -> String {
if let Some(ref god_analysis) = item.metrics.god_object_analysis {
if god_analysis.is_god_object {
let responsibilities = god_analysis.responsibility_count;
let methods = god_analysis.method_count;
if responsibilities > 5 {
return format!(
"File has {} distinct responsibilities across {} methods. High coupling makes changes risky and testing difficult. Splitting by responsibility will improve maintainability and reduce change impact.",
responsibilities, methods
);
} else if methods > 50 {
return format!(
"File contains {} methods with {} responsibilities. Large interface makes it difficult to understand and maintain. Extracting cohesive modules will improve clarity.",
methods, responsibilities
);
} else {
return format!(
"File exhibits god object characteristics (score: {:.1}). Refactoring will improve separation of concerns and testability.",
god_analysis.god_object_score
);
}
}
}
if item.metrics.total_complexity > 500 {
format!(
"High total complexity ({}) across {} functions (avg: {:.1}). Breaking into smaller modules will reduce cognitive load and improve maintainability.",
item.metrics.total_complexity,
item.metrics.function_count,
item.metrics.avg_complexity
)
} else if item.metrics.total_lines > 1000 {
format!(
"Large file ({} lines) with {} functions. Size alone increases maintenance burden and makes navigation difficult.",
item.metrics.total_lines,
item.metrics.function_count
)
} else {
"File-level refactoring will improve overall code organization and maintainability."
.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_truncated_list_empty() {
let items: Vec<String> = vec![];
assert_eq!(format_truncated_list(&items, 3), "");
}
#[test]
fn test_format_truncated_list_under_limit() {
let items = vec!["a.rs".to_string(), "b.rs".to_string()];
assert_eq!(format_truncated_list(&items, 3), "a.rs, b.rs");
}
#[test]
fn test_format_truncated_list_at_limit() {
let items = vec!["a.rs".to_string(), "b.rs".to_string(), "c.rs".to_string()];
assert_eq!(format_truncated_list(&items, 3), "a.rs, b.rs, c.rs");
}
#[test]
fn test_format_truncated_list_over_limit() {
let items = vec![
"a.rs".to_string(),
"b.rs".to_string(),
"c.rs".to_string(),
"d.rs".to_string(),
"e.rs".to_string(),
];
assert_eq!(
format_truncated_list(&items, 3),
"a.rs, b.rs, c.rs (+2 more)"
);
}
#[test]
fn test_format_truncated_list_single_item() {
let items = vec!["only.rs".to_string()];
assert_eq!(format_truncated_list(&items, 3), "only.rs");
}
#[test]
fn test_coupling_classification_labels() {
assert_eq!(
coupling_classification_label(&CouplingClassification::StableCore),
"stable core"
);
assert_eq!(
coupling_classification_label(&CouplingClassification::UtilityModule),
"utility"
);
assert_eq!(
coupling_classification_label(&CouplingClassification::LeafModule),
"leaf"
);
assert_eq!(
coupling_classification_label(&CouplingClassification::Isolated),
"isolated"
);
assert_eq!(
coupling_classification_label(&CouplingClassification::HighlyCoupled),
"highly coupled"
);
}
fn make_test_god_analysis(
is_god_object: bool,
method_count: usize,
responsibility_count: usize,
god_object_score: f64,
) -> crate::organization::GodObjectAnalysis {
crate::organization::GodObjectAnalysis {
is_god_object,
method_count,
weighted_method_count: None,
field_count: 10,
responsibility_count,
lines_of_code: 500,
complexity_sum: 200,
god_object_score,
recommended_splits: Vec::new(),
confidence: crate::organization::GodObjectConfidence::Definite,
responsibilities: Vec::new(),
responsibility_method_counts: Default::default(),
purity_distribution: None,
module_structure: None,
detection_type: crate::organization::DetectionType::GodClass,
struct_name: None,
struct_line: None,
struct_location: None,
visibility_breakdown: None,
domain_count: 0,
domain_diversity: 0.0,
struct_ratio: 0.0,
analysis_method: crate::organization::SplitAnalysisMethod::default(),
cross_domain_severity: None,
domain_diversity_metrics: None,
aggregated_entropy: None,
aggregated_error_swallowing_count: None,
aggregated_error_swallowing_patterns: None,
layering_impact: None,
anti_pattern_report: None,
complexity_metrics: None,
trait_method_summary: None,
}
}
fn make_test_file_item(
god_analysis: Option<crate::organization::GodObjectAnalysis>,
total_complexity: u32,
total_lines: usize,
function_count: usize,
avg_complexity: f64,
) -> crate::priority::FileDebtItem {
crate::priority::FileDebtItem {
metrics: crate::priority::FileDebtMetrics {
god_object_analysis: god_analysis,
total_complexity,
total_lines,
function_count,
avg_complexity,
..Default::default()
},
score: 50.0,
priority_rank: 1,
recommendation: "Test recommendation".to_string(),
impact: Default::default(),
}
}
#[test]
fn test_format_file_rationale_god_object_high_responsibilities() {
let god_analysis = make_test_god_analysis(true, 30, 8, 75.0);
let item = make_test_file_item(Some(god_analysis), 100, 500, 30, 3.3);
let result = format_file_rationale(&item);
assert!(result.contains("8 distinct responsibilities"));
assert!(result.contains("30 methods"));
assert!(result.contains("Splitting by responsibility"));
}
#[test]
fn test_format_file_rationale_god_object_many_methods() {
let god_analysis = make_test_god_analysis(true, 60, 3, 80.0);
let item = make_test_file_item(Some(god_analysis), 200, 800, 60, 3.3);
let result = format_file_rationale(&item);
assert!(result.contains("60 methods"));
assert!(result.contains("3 responsibilities"));
assert!(result.contains("Extracting cohesive modules"));
}
#[test]
fn test_format_file_rationale_god_object_generic() {
let god_analysis = make_test_god_analysis(true, 20, 4, 65.5);
let item = make_test_file_item(Some(god_analysis), 100, 400, 20, 5.0);
let result = format_file_rationale(&item);
assert!(result.contains("god object characteristics"));
assert!(result.contains("score: 65.5"));
}
#[test]
fn test_format_file_rationale_not_god_object_skipped() {
let god_analysis = make_test_god_analysis(false, 10, 2, 20.0);
let item = make_test_file_item(Some(god_analysis), 100, 400, 10, 10.0);
let result = format_file_rationale(&item);
assert!(result.contains("File-level refactoring"));
}
#[test]
fn test_format_file_rationale_high_complexity() {
let item = make_test_file_item(None, 600, 800, 50, 12.0);
let result = format_file_rationale(&item);
assert!(result.contains("High total complexity (600)"));
assert!(result.contains("50 functions"));
assert!(result.contains("avg: 12.0"));
}
#[test]
fn test_format_file_rationale_large_file() {
let item = make_test_file_item(None, 300, 1500, 40, 7.5);
let result = format_file_rationale(&item);
assert!(result.contains("Large file (1500 lines)"));
assert!(result.contains("40 functions"));
}
#[test]
fn test_format_file_rationale_default() {
let item = make_test_file_item(None, 100, 500, 20, 5.0);
let result = format_file_rationale(&item);
assert_eq!(
result,
"File-level refactoring will improve overall code organization and maintainability."
);
}
}