use crate::data_flow::DataFlowGraph;
use crate::organization::calculate_file_cohesion;
use crate::priority::UnifiedDebtItem;
use crate::tui::theme::Theme;
use ratatui::text::Line;
use super::super::{
app::ResultsApp,
detail_page::DetailPage,
detail_pages::{
context, data_flow, dependencies, git_context, overview, patterns, responsibilities,
score_breakdown,
},
};
pub fn lines_to_plain_text(lines: &[Line]) -> String {
lines
.iter()
.map(|line| {
line.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>()
})
.collect::<Vec<_>>()
.join("\n")
}
pub fn extract_page_text(item: &UnifiedDebtItem, page: DetailPage, app: &ResultsApp) -> String {
match page {
DetailPage::Overview => extract_overview_text(item, app),
DetailPage::ScoreBreakdown => extract_score_breakdown_text(item),
DetailPage::Context => extract_context_text(item),
DetailPage::Dependencies => extract_dependencies_text(item, app),
DetailPage::GitContext => extract_git_context_text(item),
DetailPage::Patterns => extract_patterns_text(item),
DetailPage::DataFlow => extract_data_flow_text(item, &app.analysis().data_flow_graph),
DetailPage::Responsibilities => extract_responsibilities_text(item),
}
}
pub fn format_path_text(path: &std::path::Path) -> String {
path.to_string_lossy().to_string()
}
const TEXT_EXTRACTION_WIDTH: u16 = 80;
fn extract_overview_text(item: &UnifiedDebtItem, app: &ResultsApp) -> String {
let theme = Theme::default();
let width = TEXT_EXTRACTION_WIDTH;
let location_items = overview::get_items_at_location(app, item);
let cohesion = calculate_file_cohesion(&item.location.file, &app.analysis().call_graph);
let all_lines: Vec<Line<'static>> = [
overview::build_location_section(item, &theme, width),
overview::build_score_section(&location_items, item, &theme, width),
overview::build_god_object_section(item, &theme, width),
overview::build_complexity_section(item, &theme, width),
overview::build_coverage_section(item, &theme, width),
overview::build_cohesion_section(cohesion.as_ref(), &theme, width),
overview::build_debt_types_section(&location_items, item, &theme),
]
.into_iter()
.flatten()
.collect();
lines_to_plain_text(&all_lines)
}
fn extract_score_breakdown_text(item: &UnifiedDebtItem) -> String {
let theme = Theme::default();
let lines = score_breakdown::build_page_lines(item, &theme, TEXT_EXTRACTION_WIDTH);
lines_to_plain_text(&lines)
}
fn extract_context_text(item: &UnifiedDebtItem) -> String {
let theme = Theme::default();
let lines = context::build_page_lines(item, &theme, TEXT_EXTRACTION_WIDTH);
lines_to_plain_text(&lines)
}
fn extract_dependencies_text(item: &UnifiedDebtItem, _app: &ResultsApp) -> String {
let theme = Theme::default();
let lines = dependencies::build_page_lines(item, &theme, TEXT_EXTRACTION_WIDTH);
lines_to_plain_text(&lines)
}
fn extract_git_context_text(item: &UnifiedDebtItem) -> String {
let theme = Theme::default();
let lines = git_context::build_page_lines(item, &theme, TEXT_EXTRACTION_WIDTH);
lines_to_plain_text(&lines)
}
fn extract_patterns_text(item: &UnifiedDebtItem) -> String {
let theme = Theme::default();
let lines = patterns::build_page_lines(item, &theme, TEXT_EXTRACTION_WIDTH);
lines_to_plain_text(&lines)
}
fn extract_data_flow_text(item: &UnifiedDebtItem, data_flow_graph: &DataFlowGraph) -> String {
let theme = Theme::default();
let lines = data_flow::build_page_lines(item, data_flow_graph, &theme, TEXT_EXTRACTION_WIDTH);
lines_to_plain_text(&lines)
}
fn extract_responsibilities_text(item: &UnifiedDebtItem) -> String {
let theme = Theme::default();
let lines = responsibilities::build_page_lines(item, &theme, TEXT_EXTRACTION_WIDTH);
lines_to_plain_text(&lines)
}
pub use overview::format_debt_type_name;
pub fn extract_item_as_llm_markdown(item: &UnifiedDebtItem) -> String {
use crate::io::writers::llm_markdown::format;
use crate::output::unified::FunctionDebtItemOutput;
let output_item = FunctionDebtItemOutput::from_function_item(item, true);
let mut result = String::new();
result.push_str("# Debt Item\n\n");
result.push_str(&format::identification(
&output_item.location,
&output_item.category,
&output_item.debt_type,
));
result.push('\n');
result.push_str(&format::severity(output_item.score, &output_item.priority));
result.push('\n');
result.push_str(&format::metrics(
&output_item.metrics,
output_item.adjusted_complexity.as_ref(),
));
result.push('\n');
if let Some(cov) = format::coverage(&output_item.metrics) {
result.push_str(&cov);
result.push('\n');
}
result.push_str(&format::dependencies(&output_item.dependencies));
result.push('\n');
if let Some(pur) = format::purity(output_item.purity_analysis.as_ref()) {
result.push_str(&pur);
result.push('\n');
}
if let Some(pat) = format::pattern_analysis(
output_item.pattern_type.as_ref(),
output_item.pattern_confidence,
) {
result.push_str(&pat);
result.push('\n');
}
if let Some(scr) = format::scoring(
output_item.scoring_details.as_ref(),
&output_item.function_role,
) {
result.push_str(&scr);
result.push('\n');
}
if let Some(ctx) = format::context(output_item.context.as_ref()) {
result.push_str(&ctx);
result.push('\n');
}
if let Some(git) = format::git_history(output_item.git_history.as_ref()) {
result.push_str(&git);
result.push('\n');
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::priority::formatter_verbosity::git_history::classify_stability;
use ratatui::text::Span;
fn entropy_description(score: f64) -> &'static str {
if score < 0.3 {
"low (repetitive)"
} else if score < 0.5 {
"medium (typical)"
} else {
"high (chaotic)"
}
}
fn derive_coupling_classification(
afferent: usize,
efferent: usize,
instability: f64,
) -> String {
let total = afferent + efferent;
if total > 15 {
"Highly Coupled".to_string()
} else if total <= 2 {
"Isolated".to_string()
} else if instability < 0.3 && afferent > efferent {
"Stable Core".to_string()
} else if instability > 0.7 && efferent > afferent {
"Leaf Module".to_string()
} else {
"Utility Module".to_string()
}
}
fn get_clipboard_fix_suggestion(reasons: &[String]) -> Option<&'static str> {
if reasons.len() > 2 {
return None;
}
let first_reason = reasons.first()?.to_lowercase();
if first_reason.contains("i/o")
|| first_reason.contains("print")
|| first_reason.contains("log")
{
Some("Move logging to caller - function becomes pure")
} else if first_reason.contains("time") || first_reason.contains("now") {
Some("Pass time as parameter instead of calling now()")
} else if first_reason.contains("random") || first_reason.contains("rand") {
Some("Inject RNG as parameter for deterministic behavior")
} else if first_reason.contains("mutable param") || first_reason.contains("&mut") {
Some("Consider taking &self instead of &mut self")
} else {
None
}
}
#[test]
fn test_lines_to_plain_text_single_line() {
let lines = vec![Line::from("hello world")];
let result = lines_to_plain_text(&lines);
assert_eq!(result, "hello world");
}
#[test]
fn test_lines_to_plain_text_multiple_spans() {
let lines = vec![Line::from(vec![
Span::raw("label"),
Span::raw(" "),
Span::raw("value"),
])];
let result = lines_to_plain_text(&lines);
assert_eq!(result, "label value");
}
#[test]
fn test_lines_to_plain_text_multiple_lines() {
let lines = vec![
Line::from("first line"),
Line::from("second line"),
Line::from("third line"),
];
let result = lines_to_plain_text(&lines);
assert_eq!(result, "first line\nsecond line\nthird line");
}
#[test]
fn test_lines_to_plain_text_empty() {
let lines: Vec<Line> = vec![];
let result = lines_to_plain_text(&lines);
assert_eq!(result, "");
}
#[test]
fn test_format_path_text() {
use std::path::PathBuf;
let path = PathBuf::from("/tmp/test.rs");
let text = format_path_text(&path);
assert_eq!(text, "/tmp/test.rs");
}
#[test]
fn test_classify_stability() {
assert_eq!(classify_stability(0.5), "Stable");
assert_eq!(classify_stability(2.0), "Moderately Unstable");
assert_eq!(classify_stability(10.0), "Highly Unstable");
}
#[test]
fn test_entropy_description() {
assert_eq!(entropy_description(0.1), "low (repetitive)");
assert_eq!(entropy_description(0.4), "medium (typical)");
assert_eq!(entropy_description(0.7), "high (chaotic)");
}
#[test]
fn test_derive_coupling_classification() {
assert_eq!(derive_coupling_classification(20, 5, 0.5), "Highly Coupled");
assert_eq!(derive_coupling_classification(1, 1, 0.5), "Isolated");
assert_eq!(derive_coupling_classification(8, 2, 0.2), "Stable Core");
assert_eq!(derive_coupling_classification(2, 8, 0.8), "Leaf Module");
assert_eq!(derive_coupling_classification(5, 5, 0.5), "Utility Module");
}
#[test]
fn test_get_clipboard_fix_suggestion() {
let io_reasons = vec!["i/o operation".to_string()];
assert!(get_clipboard_fix_suggestion(&io_reasons).is_some());
let time_reasons = vec!["calls now()".to_string()];
assert!(get_clipboard_fix_suggestion(&time_reasons).is_some());
let many_reasons = vec!["a".to_string(), "b".to_string(), "c".to_string()];
assert!(get_clipboard_fix_suggestion(&many_reasons).is_none());
}
#[test]
fn test_format_debt_type_name() {
use crate::priority::DebtType;
let complexity = DebtType::ComplexityHotspot {
cyclomatic: 10,
cognitive: 20,
};
assert_eq!(format_debt_type_name(&complexity), "High Complexity");
let god_object = DebtType::GodObject {
methods: 50,
fields: Some(20),
responsibilities: 5,
lines: 1000,
god_object_score: 100.0,
};
assert_eq!(format_debt_type_name(&god_object), "God Object");
}
}