///|
/// Serialize the report with stable field and diagnostic ordering.
pub fn render_json(report : Report) -> String {
let diagnostics = report.diagnostics.copy()
sort_diagnostics(diagnostics)
let rendered = diagnostics.map(d => {
let paths = d.paths.copy()
paths.sort_by(ordinal_compare)
"{\"code\":" +
quote(d.code) +
",\"severity\":" +
quote(d.severity) +
",\"paths\":" +
json_strings(paths) +
",\"occurrences\":\{d.occurrences},\"message\":" +
quote(d.message) +
"}"
})
let s = report.summary
"{\"schema_version\":\{report.schema_version},\"profile\":" +
quote(report.profile) +
",\"source\":" +
quote(report.source) +
",\"complete\":\{report.complete}," +
"\"summary\":{\"entries\":\{s.entries},\"files\":\{s.files},\"directories\":\{s.directories}," +
"\"diagnostic_groups\":\{s.diagnostic_groups},\"scan_issues\":\{s.scan_issues}}," +
"\"diagnostics\":[" +
rendered.join(",") +
"],\"limitations\":" +
json_strings(report.limitations) +
"}"
}
///|
/// Human-readable output quotes paths so control characters remain data.
pub fn render_text(report : Report) -> String {
let status = if !report.complete {
"INCOMPLETE"
} else if report.diagnostics.is_empty() {
"PASS (covered rules only)"
} else {
"FINDINGS"
}
let lines = [
"MoonPortCheck — \{report.profile}",
"\{status}; source=\{report.source}; entries=\{report.summary.entries}; files=\{report.summary.files}; directories=\{report.summary.directories}",
]
let diagnostics = report.diagnostics.copy()
sort_diagnostics(diagnostics)
for d in diagnostics {
let paths = d.paths.copy()
paths.sort_by(ordinal_compare)
lines.push(
"[\{d.code}] " +
paths.map(quote).join(", ") +
" (occurrences=\{d.occurrences}): " +
d.message,
)
}
lines.push("Coverage limits:")
for limitation in report.limitations {
lines.push("- " + limitation)
}
lines.join("\n")
}
///|
fn quote(text : String) -> String {
text.to_json().stringify()
}
///|
fn json_strings(items : Array[String]) -> String {
"[" + items.map(quote).join(",") + "]"
}