#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn format_single_file_output(
single_file: &Path,
results: &QualityGateResults,
violations: &[QualityViolation],
format: QualityGateOutputFormat,
) -> Result<String> {
match format {
QualityGateOutputFormat::Json => Ok(serde_json::to_string_pretty(&json!({
"file": single_file,
"passed": results.passed,
"results": results,
"violations": violations,
}))?),
QualityGateOutputFormat::Summary
| QualityGateOutputFormat::Markdown
| QualityGateOutputFormat::Detailed
| QualityGateOutputFormat::Human
| QualityGateOutputFormat::Junit => {
Ok(format_single_file_summary(single_file, results, violations))
}
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn format_single_file_summary(
file_path: &Path,
results: &QualityGateResults,
violations: &[QualityViolation],
) -> String {
let mut output = String::new();
output.push_str(&format!(
"# Quality Gate Report: {}\n\n",
file_path.display()
));
if results.passed {
output.push_str("✅ **Quality Gate: PASSED**\n\n");
} else {
output.push_str("❌ **Quality Gate: FAILED**\n\n");
}
add_summary_section(&mut output, results);
if !violations.is_empty() {
add_violations_section(&mut output, violations);
}
output
}
fn add_summary_section(output: &mut String, results: &QualityGateResults) {
output.push_str("## Summary\n\n");
output.push_str(&format!(
"- Total Violations: {}\n",
results.total_violations
));
output.push_str(&format!(
"- Complexity Issues: {}\n",
results.complexity_violations
));
output.push_str(&format!("- Dead Code: {}\n", results.dead_code_violations));
output.push_str(&format!(
"- Technical Debt (SATD): {}\n",
results.satd_violations
));
output.push_str(&format!(
"- Security Issues: {}\n",
results.security_violations
));
}
fn add_violations_section(output: &mut String, violations: &[QualityViolation]) {
output.push_str("\n## Violations\n\n");
let mut by_type: HashMap<String, Vec<&QualityViolation>> = HashMap::new();
for violation in violations {
by_type
.entry(violation.check_type.clone())
.or_default()
.push(violation);
}
for (check_type, type_violations) in by_type {
output.push_str(&format!(
"### {} ({})\n\n",
check_type.to_uppercase(),
type_violations.len()
));
for violation in type_violations {
add_violation_entry(output, violation);
}
output.push('\n');
}
}
fn add_violation_entry(output: &mut String, violation: &QualityViolation) {
let severity_icon = get_severity_icon(&violation.severity);
let file_display = if violation.file.is_empty() {
String::new()
} else {
let path = std::path::Path::new(&violation.file);
let short_path = path
.file_name()
.map(|f| f.to_string_lossy().to_string())
.unwrap_or_else(|| violation.file.clone());
format!(" {}", short_path)
};
if let Some(line) = violation.line {
output.push_str(&format!(
"- {}{}:{}: {}\n",
severity_icon, file_display, line, violation.message
));
} else if !violation.file.is_empty() {
output.push_str(&format!(
"- {}{}: {}\n",
severity_icon, file_display, violation.message
));
} else {
output.push_str(&format!("- {} {}\n", severity_icon, violation.message));
}
}
fn get_severity_icon(severity: &str) -> &'static str {
match severity {
"error" => "🔴",
"warning" => "🟡",
_ => "🟢",
}
}