use crate::output::pattern_analysis::{
AlmostPureFunction, AsyncPatternSummary, DetectedPattern, ErrorHandlingSummary,
FrameworkPatternMetrics, PatternAnalysis, PurityMetrics, RustPatternMetrics,
};
pub struct PatternFormatter;
impl PatternFormatter {
pub fn format(analysis: &PatternAnalysis) -> String {
if !analysis.has_patterns() {
return String::new();
}
[
Self::format_purity(&analysis.purity),
Self::format_frameworks(&analysis.frameworks),
Self::format_rust_patterns(&analysis.rust_patterns),
]
.into_iter()
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("\n\n")
}
fn format_purity(metrics: &PurityMetrics) -> String {
if !metrics.has_functions() {
return String::new();
}
let mut sections = vec![format!(
"PURITY ANALYSIS:\n\
- Strictly Pure: {} functions ({:.0}%)\n\
- Locally Pure: {} functions\n\
- Read-Only: {} functions\n\
- Impure: {} functions",
metrics.strictly_pure,
metrics.purity_percentage() * 100.0,
metrics.locally_pure,
metrics.read_only,
metrics.impure
)];
let opportunities = Self::format_refactoring_opportunities(metrics);
if !opportunities.is_empty() {
sections.push(opportunities);
}
sections.join("\n\n")
}
fn format_refactoring_opportunities(metrics: &PurityMetrics) -> String {
let opportunities = metrics.top_refactoring_opportunities(5);
if opportunities.is_empty() {
return String::new();
}
let mut output = String::from("REFACTORING OPPORTUNITIES:");
for func in opportunities {
output.push_str(&Self::format_almost_pure_function(func));
}
output
}
fn format_almost_pure_function(func: &AlmostPureFunction) -> String {
let violation_count = func.violations.len();
let plural = if violation_count == 1 { "" } else { "s" };
let violation_desc = if !func.violations.is_empty() {
func.violations[0].description()
} else {
"Unknown violation".to_string()
};
format!(
"\n - {} ({} violation{}): {}\n → Suggestion: {}",
func.name, violation_count, plural, violation_desc, func.refactoring_suggestion
)
}
fn format_frameworks(metrics: &FrameworkPatternMetrics) -> String {
if !metrics.has_patterns() {
return String::new();
}
let mut output = String::from("FRAMEWORK PATTERNS DETECTED:");
for pattern in metrics.sorted_by_frequency() {
output.push_str(&Self::format_detected_pattern(pattern));
}
output
}
fn format_detected_pattern(pattern: &DetectedPattern) -> String {
let examples = if pattern.examples.is_empty() {
"(no examples)".to_string()
} else {
pattern.examples.join(", ")
};
format!(
"\n - {} {} ({}x detected)\n Examples: {}\n Recommendation: {}",
pattern.framework,
pattern.pattern_type,
pattern.count,
examples,
pattern.recommendation
)
}
fn format_rust_patterns(metrics: &RustPatternMetrics) -> String {
if !metrics.has_patterns() {
return String::new();
}
let sections = [
Self::format_trait_implementations(metrics),
Self::format_async_patterns(metrics),
Self::format_error_handling(metrics),
Self::format_builder_candidates(metrics),
];
let non_empty: Vec<_> = sections.into_iter().filter(|s| !s.is_empty()).collect();
if non_empty.is_empty() {
return String::new();
}
format!("RUST-SPECIFIC PATTERNS:\n{}", non_empty.join("\n"))
}
fn format_trait_implementations(metrics: &RustPatternMetrics) -> String {
if metrics.trait_impls.is_empty() {
return String::new();
}
let mut output = String::from(" Trait Implementations:");
for trait_impl in &metrics.trait_impls {
let types_display = if trait_impl.types.is_empty() {
"(no examples)".to_string()
} else {
trait_impl.types.join(", ")
};
output.push_str(&format!(
"\n - {}: {} implementations ({})",
trait_impl.trait_name, trait_impl.count, types_display
));
}
let repetitive = metrics.repetitive_traits();
if !repetitive.is_empty() {
output.push_str("\n → Consider using macros for repetitive implementations");
}
output
}
fn format_async_patterns(metrics: &RustPatternMetrics) -> String {
if !metrics.has_async_patterns() {
return String::new();
}
Self::format_async_summary(&metrics.async_patterns)
}
fn format_async_summary(async_pat: &AsyncPatternSummary) -> String {
let mut output = format!(
" Async/Concurrency:\n\
- Async functions: {}\n\
- Spawn calls: {}\n\
- Channels: {}\n\
- Mutex usage: {}",
async_pat.async_functions,
async_pat.spawn_calls,
if async_pat.channel_usage { "Yes" } else { "No" },
if async_pat.mutex_usage { "Yes" } else { "No" }
);
if async_pat.spawn_calls > 0 {
output.push_str("\n → Concurrency management detected - group spawn logic");
}
output
}
fn format_error_handling(metrics: &RustPatternMetrics) -> String {
if !metrics.has_error_handling() {
return String::new();
}
Self::format_error_summary(&metrics.error_handling)
}
fn format_error_summary(error_handling: &ErrorHandlingSummary) -> String {
let mut output = format!(
" Error Handling:\n - Average ? operators per function: {:.1}",
error_handling.question_mark_density
);
if error_handling.unwrap_count > 0 {
output.push_str(&format!(
"\n ⚠ {} unwrap() calls detected - replace with proper error handling",
error_handling.unwrap_count
));
}
output
}
fn format_builder_candidates(metrics: &RustPatternMetrics) -> String {
if !metrics.has_builder_candidates() {
return String::new();
}
format!(
" Builder Patterns:\n\
- Candidates: {}\n\
→ Extract builder logic into separate module",
metrics.builder_candidates.join(", ")
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_empty_analysis_produces_empty_string() {
let analysis = PatternAnalysis::default();
let output = PatternFormatter::format(&analysis);
assert!(output.is_empty());
}
#[test]
fn format_purity_with_functions() {
let metrics = PurityMetrics {
strictly_pure: 15,
locally_pure: 10,
read_only: 5,
impure: 20,
almost_pure: vec![],
};
let output = PatternFormatter::format_purity(&metrics);
assert!(output.contains("PURITY ANALYSIS"));
assert!(output.contains("Strictly Pure: 15"));
assert!(output.contains("50%")); }
#[test]
fn format_purity_empty_metrics() {
let metrics = PurityMetrics::default();
let output = PatternFormatter::format_purity(&metrics);
assert!(output.is_empty());
}
#[test]
fn format_handles_empty_examples() {
let metrics = FrameworkPatternMetrics {
patterns: vec![DetectedPattern {
framework: "Test".into(),
pattern_type: "Pattern".into(),
count: 5,
examples: vec![],
recommendation: "Do something".into(),
}],
};
let output = PatternFormatter::format_frameworks(&metrics);
assert!(output.contains("(no examples)"));
assert!(!output.contains("Examples: ,"));
}
}