#![cfg_attr(coverage_nightly, coverage(off))]
use super::types::*;
use crate::cli::LintHotspotOutputFormat;
use anyhow::{Context, Result};
use serde::Serialize;
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub(crate) fn format_output(
result: &LintHotspotResult,
format: LintHotspotOutputFormat,
perf: bool,
elapsed: std::time::Duration,
top_files: usize,
) -> Result<String> {
match format {
LintHotspotOutputFormat::Summary => format_summary(result, perf, elapsed, top_files),
LintHotspotOutputFormat::Detailed => format_detailed(result, perf, elapsed, top_files),
LintHotspotOutputFormat::Json => format_json(result, false),
LintHotspotOutputFormat::EnforcementJson => format_json(result, true),
LintHotspotOutputFormat::Sarif => format_sarif(result),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn format_summary(
result: &LintHotspotResult,
perf: bool,
elapsed: std::time::Duration,
_top_files: usize,
) -> Result<String> {
let mut output = String::new();
output.push_str("# Lint Hotspot Analysis (EXTREME Quality Mode)\n\n");
output.push_str(&format!(
"**Total Project Violations**: {}\n",
result.total_project_violations
));
output.push_str(&format!(
"**Files with Issues**: {}\n\n",
result.summary_by_file.len()
));
output.push_str("## Top Files with Lint Issues\n\n");
let sorted_files = sort_files_by_density(result);
let files_to_show = if _top_files == 0 {
sorted_files.len()
} else {
_top_files
};
if sorted_files.len() > files_to_show {
output.push_str(&format!(
"_Showing {} of {} files with issues (--top-files {})._\n\n",
files_to_show,
sorted_files.len(),
files_to_show
));
}
for (i, (file, summary)) in sorted_files.iter().take(files_to_show).enumerate() {
let filename = file.file_name().unwrap_or_default().to_string_lossy();
if summary.sloc == 0 {
output.push_str(&format!(
"{}. `{}` - {} violations, SLOC not measured (density unavailable)\n",
i + 1,
filename,
summary.total_violations
));
} else {
output.push_str(&format!(
"{}. `{}` - {:.2} violations/SLOC ({} violations, {} SLOC)\n",
i + 1,
filename,
summary.defect_density,
summary.total_violations,
summary.sloc
));
}
}
output.push('\n');
output.push_str("## Hottest File Details\n");
output.push_str(&format!("**File**: {}\n", result.hotspot.file.display()));
output.push_str(&format!(
"**Defect Density**: {:.2} violations/SLOC\n",
result.hotspot.defect_density
));
output.push_str(&format!(
"**Total Violations**: {}\n",
result.hotspot.total_violations
));
output.push_str(&format!("**Lines of Code**: {}\n\n", result.hotspot.sloc));
output.push_str("## Severity Distribution\n");
output.push_str(&format!(
"- Errors: {}\n",
result.hotspot.severity_distribution.error
));
output.push_str(&format!(
"- Warnings: {}\n",
result.hotspot.severity_distribution.warning
));
output.push_str(&format!(
"- Suggestions: {}\n\n",
result.hotspot.severity_distribution.suggestion
));
output.push_str("## Top Violations\n");
for (lint, count) in result.hotspot.top_lints.iter().take(5) {
output.push_str(&format!("- {lint}: {count} occurrences\n"));
}
if let Some(enforcement) = &result.enforcement {
output.push_str("\n## Enforcement Metadata\n");
output.push_str(&format!(
"- Score: {:.1}/10\n",
enforcement.enforcement_score
));
output.push_str(&format!(
"- Priority: {}\n",
enforcement.enforcement_priority
));
output.push_str(&format!(
"- Estimated Fix Time: {} minutes\n",
enforcement.estimated_fix_time / 60
));
output.push_str(&format!(
"- Automation Confidence: {:.0}%\n",
enforcement.automation_confidence * 100.0
));
}
if !result.quality_gate.passed {
output.push_str("\n## ❌ Quality Gate Failed\n");
for violation in &result.quality_gate.violations {
output.push_str(&format!(
"- {} exceeded: {:.2} > {:.2}\n",
violation.rule, violation.actual, violation.threshold
));
}
}
if perf {
output.push_str(&format!(
"\n⏱️ Analysis completed in {:.2}s\n",
elapsed.as_secs_f64()
));
}
Ok(output)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub(crate) fn format_detailed(
result: &LintHotspotResult,
perf: bool,
elapsed: std::time::Duration,
top_files: usize,
) -> Result<String> {
let mut output = format_summary(result, perf, elapsed, top_files)?;
output.push_str("\n## Detailed Violations in Hotspot File\n");
for violation in &result.hotspot.detailed_violations {
output.push_str(&format!(
"- **{}:{}:{}** [{}] {}\n",
violation.file.display(),
violation.line,
violation.column,
violation.lint_name,
violation.message
));
if let Some(suggestion) = &violation.suggestion {
output.push_str(&format!(" Suggestion: {suggestion}\n"));
}
}
output.push_str("\n## Top Files by Violations\n");
let mut sorted_files: Vec<_> = result.summary_by_file.iter().collect();
sorted_files.sort_by(|a, b| {
b.1.total_violations
.cmp(&a.1.total_violations)
.then_with(|| a.0.cmp(b.0))
});
let files_to_show = if top_files == 0 {
sorted_files.len()
} else {
top_files
};
if sorted_files.len() > files_to_show {
output.push_str(&format!(
"_Showing {} of {} files with issues (--top-files {})._\n",
files_to_show,
sorted_files.len(),
files_to_show
));
}
for (file, summary) in sorted_files.iter().take(files_to_show) {
output.push_str(&format!(
"- {}: {} violations ({} errors, {} warnings, density: {:.2})\n",
file.display(),
summary.total_violations,
summary.errors,
summary.warnings,
summary.defect_density
));
}
if let Some(chain) = &result.refactor_chain {
output.push_str("\n## Refactor Chain\n");
output.push_str(&format!("ID: {}\n", chain.id));
output.push_str(&format!(
"Estimated Reduction: {} violations\n",
chain.estimated_reduction
));
output.push_str(&format!(
"Automation Confidence: {:.0}%\n\n",
chain.automation_confidence * 100.0
));
output.push_str("### Steps\n");
for (i, step) in chain.steps.iter().enumerate() {
output.push_str(&format!(
"{}. {} - {} (confidence: {:.0}%, impact: {})\n",
i + 1,
step.description,
step.lint,
step.confidence * 100.0,
step.impact
));
}
}
Ok(output)
}
fn sort_files_by_density(result: &LintHotspotResult) -> Vec<(&std::path::PathBuf, &FileSummary)> {
let mut sorted: Vec<_> = result.summary_by_file.iter().collect();
sorted.sort_by(|a, b| {
b.1.defect_density
.partial_cmp(&a.1.defect_density)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| a.0.cmp(b.0))
});
sorted
}
pub(crate) fn format_clean_result(format: &LintHotspotOutputFormat) -> Result<String> {
let empty_gate = serde_json::json!({
"passed": true,
"violations": [],
"blocking": false
});
match format {
LintHotspotOutputFormat::Json | LintHotspotOutputFormat::EnforcementJson => {
serde_json::to_string_pretty(&serde_json::json!({
"hotspot": serde_json::Value::Null,
"all_violations": [],
"summary_by_file": {},
"total_project_violations": 0,
"enforcement": serde_json::Value::Null,
"refactor_chain": serde_json::Value::Null,
"quality_gate": empty_gate,
}))
.context("Failed to serialize clean result to JSON")
}
LintHotspotOutputFormat::Sarif => serde_json::to_string_pretty(&serde_json::json!({
"version": "2.1.0",
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"runs": [{
"tool": { "driver": {
"name": "pmat-lint-hotspot",
"version": env!("CARGO_PKG_VERSION"),
"informationUri": "https://github.com/paiml/paiml-mcp-agent-toolkit"
}},
"results": []
}]
}))
.context("Failed to serialize clean SARIF"),
LintHotspotOutputFormat::Summary | LintHotspotOutputFormat::Detailed => Ok(concat!(
"# Lint Hotspot Analysis (EXTREME Quality Mode)\n\n",
"**Total Project Violations**: 0\n",
"**Files with Issues**: 0\n\n",
"`cargo clippy` ran to completion and reported no violations. ",
"There is no hotspot file.\n"
)
.to_string()),
}
}
fn format_json(result: &LintHotspotResult, enforcement: bool) -> Result<String> {
if enforcement {
serde_json::to_string_pretty(result).context("Failed to serialize to JSON")
} else {
#[derive(Serialize)]
struct SimpleResult<'a> {
hotspot: &'a LintHotspot,
quality_gate: &'a QualityGateStatus,
}
let simple = SimpleResult {
hotspot: &result.hotspot,
quality_gate: &result.quality_gate,
};
serde_json::to_string_pretty(&simple).context("Failed to serialize to JSON")
}
}
fn format_sarif(result: &LintHotspotResult) -> Result<String> {
let results = result
.quality_gate
.violations
.iter()
.map(|v| {
serde_json::json!({
"ruleId": v.rule,
"level": if v.severity == "blocking" { "error" } else { "warning" },
"message": {
"text": format!("{} exceeded: {:.2} > {:.2}", v.rule, v.actual, v.threshold)
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": result.hotspot.file.to_string_lossy()
}
}
}]
})
})
.collect::<Vec<_>>();
serde_json::to_string_pretty(&sarif_envelope(results)).context("Failed to serialize to SARIF")
}
fn sarif_envelope(results: Vec<serde_json::Value>) -> serde_json::Value {
serde_json::json!({
"version": "2.1.0",
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"runs": [{
"tool": {
"driver": {
"name": "pmat-lint-hotspot",
"version": env!("CARGO_PKG_VERSION"),
"informationUri": "https://github.com/paiml/paiml-mcp-agent-toolkit"
}
},
"results": results
}]
})
}
pub(crate) fn format_clean_output(
format: &LintHotspotOutputFormat,
perf: bool,
elapsed: std::time::Duration,
) -> Result<String> {
let doc = match format {
LintHotspotOutputFormat::Summary => return Ok(clean_human_report(false, perf, elapsed)),
LintHotspotOutputFormat::Detailed => return Ok(clean_human_report(true, perf, elapsed)),
LintHotspotOutputFormat::Json => serde_json::json!({
"hotspot": serde_json::Value::Null,
"quality_gate": clean_quality_gate(),
}),
LintHotspotOutputFormat::EnforcementJson => serde_json::json!({
"hotspot": serde_json::Value::Null,
"all_violations": [],
"summary_by_file": {},
"total_project_violations": 0,
"enforcement": serde_json::Value::Null,
"refactor_chain": serde_json::Value::Null,
"quality_gate": clean_quality_gate(),
}),
LintHotspotOutputFormat::Sarif => sarif_envelope(vec![]),
};
serde_json::to_string_pretty(&doc).context("Failed to serialize clean lint-hotspot result")
}
fn clean_quality_gate() -> serde_json::Value {
serde_json::json!({
"passed": true,
"violations": [],
"blocking": false,
})
}
fn clean_human_report(detailed: bool, perf: bool, elapsed: std::time::Duration) -> String {
let mut output = String::new();
output.push_str("# Lint Hotspot Analysis (EXTREME Quality Mode)\n\n");
output.push_str("**Total Project Violations**: 0\n");
output.push_str("**Files with Issues**: 0\n\n");
output.push_str("## Top Files with Lint Issues\n\n");
output.push_str("_No lint violations found — project is clean._\n\n");
output.push_str("## Hottest File Details\n");
output.push_str("**File**: none (no file has any violation)\n");
if detailed {
output.push_str("\n## Detailed Violations in Hotspot File\n");
output.push_str("_None._\n");
output.push_str("\n## Top Files by Violations\n");
output.push_str("_None._\n");
}
if perf {
output.push_str(&format!(
"\n⏱️ Analysis completed in {:.2}s\n",
elapsed.as_secs_f64()
));
}
output
}
#[cfg(test)]
mod lint_hotspot_output_tests {
use super::*;
fn empty_result() -> LintHotspotResult {
LintHotspotResult {
hotspot: LintHotspot {
file: "src/a.rs".into(),
defect_density: 0.0,
total_violations: 0,
sloc: 100,
severity_distribution: SeverityDistribution::default(),
top_lints: vec![],
detailed_violations: vec![],
},
all_violations: vec![],
summary_by_file: std::collections::HashMap::new(),
total_project_violations: 0,
enforcement: None,
refactor_chain: None,
quality_gate: QualityGateStatus {
passed: true,
violations: vec![],
blocking: false,
},
}
}
#[test]
fn test_format_output_json_returns_valid_json() {
let out = format_output(
&empty_result(),
LintHotspotOutputFormat::Json,
false,
std::time::Duration::from_millis(10),
5,
)
.unwrap();
let _: serde_json::Value = serde_json::from_str(&out).unwrap();
}
#[test]
fn test_format_output_enforcement_json_dispatch() {
let out = format_output(
&empty_result(),
LintHotspotOutputFormat::EnforcementJson,
false,
std::time::Duration::from_millis(10),
5,
)
.unwrap();
let _: serde_json::Value = serde_json::from_str(&out).unwrap();
}
#[test]
fn test_format_output_sarif_returns_sarif_envelope() {
let out = format_output(
&empty_result(),
LintHotspotOutputFormat::Sarif,
false,
std::time::Duration::from_millis(10),
5,
)
.unwrap();
let parsed: serde_json::Value = serde_json::from_str(&out).unwrap();
assert!(parsed.is_object());
let obj = parsed.as_object().unwrap();
assert!(
obj.contains_key("version") || obj.contains_key("runs"),
"SARIF envelope missing"
);
}
#[test]
fn test_format_output_summary_dispatch() {
let out = format_output(
&empty_result(),
LintHotspotOutputFormat::Summary,
false,
std::time::Duration::from_millis(10),
5,
)
.unwrap();
assert!(!out.is_empty());
}
#[test]
fn test_format_output_detailed_dispatch() {
let out = format_output(
&empty_result(),
LintHotspotOutputFormat::Detailed,
false,
std::time::Duration::from_millis(10),
5,
)
.unwrap();
assert!(!out.is_empty());
}
#[test]
fn test_format_json_enforcement_vs_non_enforcement_both_valid() {
let non_enf = format_json(&empty_result(), false).unwrap();
let enf = format_json(&empty_result(), true).unwrap();
let _: serde_json::Value = serde_json::from_str(&non_enf).unwrap();
let _: serde_json::Value = serde_json::from_str(&enf).unwrap();
}
fn clean(format: LintHotspotOutputFormat) -> String {
format_clean_output(&format, false, std::time::Duration::from_millis(7))
.unwrap_or_else(|e| panic!("{format} must render a clean report: {e}"))
}
#[test]
fn test_clean_output_no_format_is_empty() {
for format in [
LintHotspotOutputFormat::Summary,
LintHotspotOutputFormat::Detailed,
LintHotspotOutputFormat::Json,
LintHotspotOutputFormat::EnforcementJson,
LintHotspotOutputFormat::Sarif,
] {
let out = clean(format.clone());
assert!(!out.trim().is_empty(), "{format} emitted zero bytes");
}
}
#[test]
fn test_clean_output_formats_are_pairwise_distinct() {
let formats = [
LintHotspotOutputFormat::Summary,
LintHotspotOutputFormat::Detailed,
LintHotspotOutputFormat::Json,
LintHotspotOutputFormat::EnforcementJson,
LintHotspotOutputFormat::Sarif,
];
for (i, a) in formats.iter().enumerate() {
for b in formats.iter().skip(i + 1) {
assert_ne!(
clean(a.clone()),
clean(b.clone()),
"--format {a} and --format {b} produced identical bytes"
);
}
}
}
#[test]
fn test_clean_json_key_sets_match_populated_key_sets() {
let populated_simple: serde_json::Value =
serde_json::from_str(&format_json(&empty_result(), false).unwrap()).unwrap();
let clean_simple: serde_json::Value =
serde_json::from_str(&clean(LintHotspotOutputFormat::Json)).unwrap();
let mut a: Vec<_> = populated_simple.as_object().unwrap().keys().collect();
let mut b: Vec<_> = clean_simple.as_object().unwrap().keys().collect();
a.sort();
b.sort();
assert_eq!(a, b, "json clean/populated key sets diverge");
let populated_full: serde_json::Value =
serde_json::from_str(&format_json(&empty_result(), true).unwrap()).unwrap();
let clean_full: serde_json::Value =
serde_json::from_str(&clean(LintHotspotOutputFormat::EnforcementJson)).unwrap();
let mut a: Vec<_> = populated_full.as_object().unwrap().keys().collect();
let mut b: Vec<_> = clean_full.as_object().unwrap().keys().collect();
a.sort();
b.sort();
assert_eq!(a, b, "enforcement-json clean/populated key sets diverge");
}
#[test]
fn test_clean_json_reports_hotspot_as_absent_not_zero() {
for format in [
LintHotspotOutputFormat::Json,
LintHotspotOutputFormat::EnforcementJson,
] {
let doc: serde_json::Value = serde_json::from_str(&clean(format.clone())).unwrap();
assert!(doc["hotspot"].is_null(), "{format} fabricated a hotspot");
}
}
#[test]
fn test_output_is_deterministic_across_five_runs() {
for format in [
LintHotspotOutputFormat::Summary,
LintHotspotOutputFormat::Detailed,
LintHotspotOutputFormat::Json,
LintHotspotOutputFormat::EnforcementJson,
LintHotspotOutputFormat::Sarif,
] {
let mut baseline: Option<String> = None;
for run in 0..5 {
let out = format_output(
&multi_file_result(),
format.clone(),
false,
std::time::Duration::from_millis(1),
10,
)
.unwrap();
match &baseline {
None => baseline = Some(out),
Some(first) => assert_eq!(
first, &out,
"--format {format} differed on run {run} for identical input"
),
}
}
}
}
fn multi_file_result() -> LintHotspotResult {
let mut summary_by_file = std::collections::HashMap::new();
let mut all_violations = Vec::new();
for name in [
"src/alpha.rs",
"src/beta.rs",
"src/gamma.rs",
"src/delta.rs",
"src/epsilon.rs",
"src/zeta.rs",
"src/eta.rs",
"src/theta.rs",
] {
summary_by_file.insert(
std::path::PathBuf::from(name),
FileSummary {
total_violations: 4,
errors: 1,
warnings: 3,
sloc: 100,
defect_density: 0.04,
},
);
all_violations.push(ViolationDetail {
file: std::path::PathBuf::from(name),
line: 1,
column: 1,
end_line: 1,
end_column: 2,
lint_name: "clippy::needless_range_loop".to_string(),
message: "m".to_string(),
severity: "warning".to_string(),
suggestion: None,
machine_applicable: false,
});
}
let mut result = empty_result();
result.summary_by_file = summary_by_file;
result.all_violations = all_violations;
result.total_project_violations = 32;
result
}
#[test]
fn test_format_sarif_empty_result_valid_json() {
let out = format_sarif(&empty_result()).unwrap();
let _: serde_json::Value = serde_json::from_str(&out).unwrap();
}
#[test]
fn test_clean_result_is_non_empty_in_every_declared_format() {
for format in [
LintHotspotOutputFormat::Json,
LintHotspotOutputFormat::EnforcementJson,
LintHotspotOutputFormat::Sarif,
LintHotspotOutputFormat::Summary,
LintHotspotOutputFormat::Detailed,
] {
let out = format_clean_result(&format).unwrap();
assert!(!out.trim().is_empty(), "empty output for {format:?}");
}
}
#[test]
fn test_clean_json_says_hotspot_null_not_a_zeroed_hotspot() {
let out = format_clean_result(&LintHotspotOutputFormat::Json).unwrap();
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert!(v["hotspot"].is_null());
assert_eq!(v["total_project_violations"], 0);
assert!(v["quality_gate"]["passed"].as_bool().unwrap());
}
#[test]
fn test_clean_sarif_is_a_valid_envelope_with_no_results() {
let out = format_clean_result(&LintHotspotOutputFormat::Sarif).unwrap();
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["version"], "2.1.0");
assert_eq!(v["runs"][0]["results"].as_array().unwrap().len(), 0);
}
#[test]
fn test_summary_names_both_numbers_when_top_files_truncates() {
let mut result = empty_result();
for i in 0..7 {
result.summary_by_file.insert(
std::path::PathBuf::from(format!("src/f{i}.rs")),
FileSummary {
total_violations: i + 1,
errors: 0,
warnings: i + 1,
sloc: 100,
defect_density: (i + 1) as f64 / 100.0,
},
);
}
let out = format_summary(&result, false, std::time::Duration::from_secs(0), 3).unwrap();
assert!(out.contains("Showing 3 of 7 files"), "{out}");
}
#[test]
fn test_top_files_zero_means_all_as_documented() {
let mut result = empty_result();
for i in 0..12 {
result.summary_by_file.insert(
std::path::PathBuf::from(format!("src/f{i:02}.rs")),
FileSummary {
total_violations: i + 1,
errors: 0,
warnings: i + 1,
sloc: 100,
defect_density: (i + 1) as f64 / 100.0,
},
);
}
let out = format_summary(&result, false, std::time::Duration::from_secs(0), 0).unwrap();
let listed = out
.lines()
.filter(|l| l.contains("violations/SLOC") && l.contains("`f"))
.count();
assert_eq!(listed, 12, "--top-files 0 must list every file:\n{out}");
assert!(
!out.contains("Showing"),
"no truncation notice when nothing is cut"
);
}
#[test]
fn test_sloc_zero_is_reported_as_unmeasured_not_as_zero_density() {
let mut result = empty_result();
result.summary_by_file.insert(
std::path::PathBuf::from("src/ghost.rs"),
FileSummary {
total_violations: 7,
errors: 0,
warnings: 7,
sloc: 0,
defect_density: 0.0,
},
);
let out = format_summary(&result, false, std::time::Duration::from_secs(0), 10).unwrap();
assert!(out.contains("SLOC not measured"), "{out}");
assert!(
!out.contains("`ghost.rs` - 0.00 violations/SLOC"),
"an unmeasurable density was rendered as 0.00:\n{out}"
);
}
#[test]
fn test_summary_is_byte_identical_across_repeated_renders() {
let mut result = empty_result();
for i in 0..12 {
result.summary_by_file.insert(
std::path::PathBuf::from(format!("src/tie{i:02}.rs")),
FileSummary {
total_violations: 2,
errors: 0,
warnings: 2,
sloc: 100,
defect_density: 0.02,
},
);
}
let renders: Vec<String> = (0..8)
.map(|_| format_summary(&result, false, std::time::Duration::from_secs(0), 10).unwrap())
.collect();
if let Some(i) = (1..renders.len()).find(|&i| renders[i] != renders[i - 1]) {
panic!("summary render differs between runs {}/{i}", i - 1);
}
}
#[test]
fn test_format_output_perf_flag_toggle() {
let out_off = format_output(
&empty_result(),
LintHotspotOutputFormat::Summary,
false,
std::time::Duration::from_millis(5),
3,
)
.unwrap();
let out_on = format_output(
&empty_result(),
LintHotspotOutputFormat::Summary,
true,
std::time::Duration::from_millis(5),
3,
)
.unwrap();
assert!(!out_off.is_empty());
assert!(!out_on.is_empty());
}
}