fn format_incremental_coverage_lcov(report: &IncrementalCoverageReport) -> Result<String> {
let mut output = String::new();
for file in &report.files {
output.push_str("TN:\n");
output.push_str(&format!("SF:{}\n", file.path.display()));
for line in 1..=file.lines_added {
if line <= file.lines_covered {
output.push_str(&format!("DA:{line},1\n"));
} else {
output.push_str(&format!("DA:{line},0\n"));
}
}
output.push_str(&format!("LF:{}\n", file.lines_added));
output.push_str(&format!("LH:{}\n", file.lines_covered));
output.push_str("end_of_record\n");
}
Ok(output)
}
fn format_incremental_coverage_sarif(report: &IncrementalCoverageReport) -> Result<String> {
use serde_json::json;
let runs = vec![json!({
"tool": {
"driver": {
"name": "pmat-incremental-coverage",
"version": "2.13.3"
}
},
"results": report.files.iter().filter(|f| f.coverage_delta < 0.0).map(|file| {
json!({
"ruleId": "coverage-decrease",
"level": "warning",
"message": {
"text": format!("Coverage decreased by {:.1}% in {}",
file.coverage_delta.abs(), file.path.display())
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": file.path.to_string_lossy()
}
}
}]
})
}).collect::<Vec<_>>()
})];
let sarif = json!({
"version": "2.1.0",
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"runs": runs
});
Ok(serde_json::to_string_pretty(&sarif)?)
}
pub fn format_incremental_coverage_summary(
report: &IncrementalCoverageReport,
top_files: usize,
) -> Result<String> {
let mut output = String::new();
write_coverage_header(&mut output, report)?;
write_coverage_summary(&mut output, &report.summary)?;
write_coverage_file_details(&mut output, &report.files, top_files)?;
Ok(output)
}
fn write_coverage_header(output: &mut String, report: &IncrementalCoverageReport) -> Result<()> {
use std::fmt::Write;
writeln!(output, "# Incremental Coverage Analysis\n")?;
writeln!(output, "**Base Branch**: {}", report.base_branch)?;
writeln!(output, "**Target Branch**: {}", report.target_branch)?;
writeln!(
output,
"**Coverage Threshold**: {:.1}%",
report.coverage_threshold * 100.0
)?;
writeln!(
output,
"**Overall Delta**: {:+.1}%",
report.summary.overall_delta
)?;
writeln!(
output,
"**Meets Threshold**: {}\n",
if report.summary.meets_threshold {
"✅ Yes"
} else {
"❌ No"
}
)?;
Ok(())
}
fn write_coverage_summary(output: &mut String, summary: &CoverageSummary) -> Result<()> {
use std::fmt::Write;
writeln!(output, "## Summary\n")?;
writeln!(output, "- Files Changed: {}", summary.total_files_changed)?;
writeln!(output, "- Files Improved: {} 📈", summary.files_improved)?;
writeln!(output, "- Files Degraded: {} 📉\n", summary.files_degraded)?;
Ok(())
}
fn write_coverage_file_details(
output: &mut String,
files: &[FileCoverageMetrics],
top_files: usize,
) -> Result<()> {
use std::fmt::Write;
writeln!(output, "## Top Files by Coverage Change\n")?;
let mut sorted_files = files.to_vec();
sorted_files.sort_unstable_by(|a, b| {
b.coverage_delta
.abs()
.partial_cmp(&a.coverage_delta.abs())
.unwrap_or(std::cmp::Ordering::Equal)
});
let files_to_show = calculate_files_to_show(&sorted_files, top_files);
write_file_entries(output, &sorted_files, files_to_show)?;
Ok(())
}
pub fn calculate_files_to_show(files: &[FileCoverageMetrics], top_files: usize) -> usize {
if top_files == 0 {
files.len()
} else {
top_files.min(files.len())
}
}
fn write_file_entries(
output: &mut String,
files: &[FileCoverageMetrics],
files_to_show: usize,
) -> Result<()> {
use std::fmt::Write;
for (i, file) in files.iter().take(files_to_show).enumerate() {
let filename = extract_filename(&file.path);
let emoji = get_coverage_emoji(file.coverage_delta);
writeln!(
output,
"{}. `{}` - {:.1}% → {:.1}% ({:+.1}%) {}",
i + 1,
filename,
file.base_coverage,
file.target_coverage,
file.coverage_delta,
emoji
)?;
}
Ok(())
}
pub fn extract_filename(path: &std::path::Path) -> &str {
path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
}
pub fn get_coverage_emoji(delta: f64) -> &'static str {
if delta > 0.0 {
"📈"
} else {
"📉"
}
}
fn format_incremental_coverage_detailed(
report: &IncrementalCoverageReport,
top_files: usize,
) -> Result<String> {
format_incremental_coverage_summary(report, top_files) }
fn format_incremental_coverage_markdown(
report: &IncrementalCoverageReport,
top_files: usize,
) -> Result<String> {
format_incremental_coverage_summary(report, top_files) }
fn format_incremental_coverage_delta(
report: &IncrementalCoverageReport,
_top_files: usize,
) -> Result<String> {
use std::fmt::Write;
let mut output = String::new();
writeln!(&mut output, "Coverage Delta Report\n")?;
for file in &report.files {
let filename = file.path.display();
writeln!(&mut output, "{}: {:+.1}%", filename, file.coverage_delta)?;
}
Ok(output)
}