/** * Human-readable reporter output. * * Turns diagnostics, traces, and shrink results into plain text * suitable for terminal reading. */ import type { Diagnostic } from './diagnostics.js'; import type { TraceEvent } from './traces.js'; import type { ShrinkResult } from './shrink.js'; /** * Options for human formatting. * Injected so callers control colors, verbosity, etc. */ export interface HumanFormatOptions { // show trace events after each diagnostic showTraces?: boolean; // show shrink summary when available showShrink?: boolean; // max related facts to print maxRelated?: number; } /** * Render a list of diagnostics into a human-readable string. */ export function renderHumanReport( diagnostics: Diagnostic[], traces: readonly TraceEvent[], shrinkResults: Map, opts: HumanFormatOptions = {}, ): string { const lines: string[] = []; for (const d of diagnostics) { lines.push(renderDiagnostic(d, opts)); if (opts.showShrink && d.clauseId && shrinkResults.has(d.clauseId)) { const shrink = shrinkResults.get(d.clauseId)!; lines.push(renderShrink(shrink)); } if (opts.showTraces && d.traceRef) { const relevant = traces.filter( (t) => t.traceEventId === d.traceRef || t.refs.diagnosticId === d.diagnosticId, ); if (relevant.length > 0) { lines.push(' trace:'); for (const t of relevant) { lines.push(` ${t.phase} at ${t.at}`); } } } } return lines.join('\n'); } /** * Render a single diagnostic in human form. */ export function renderDiagnostic( d: Diagnostic, opts: HumanFormatOptions = {}, ): string { const lines: string[] = []; const prefix = d.severity === 'error' ? '✖' : d.severity === 'warning' ? '⚠' : 'ℹ'; lines.push(`${prefix} ${d.message}`); lines.push(` ${d.code}`); if (d.position) { lines.push( ` at line ${d.position.start.line}, column ${d.position.start.column}`, ); } const maxRelated = opts.maxRelated ?? 5; if (d.related.length > 0) { lines.push(' related:'); for (const r of d.related.slice(0, maxRelated)) { lines.push(` • ${r.message}`); } if (d.related.length > maxRelated) { lines.push(` … and ${d.related.length - maxRelated} more`); } } if (d.fixHints.length > 0) { lines.push(' hints:'); for (const h of d.fixHints) { lines.push(` → ${h}`); } } if (d.suggestedFix) { lines.push(' suggested fix:'); lines.push(` action: ${d.suggestedFix.action}`); lines.push(` target: ${d.suggestedFix.target}`); lines.push(` value: ${d.suggestedFix.value}`); lines.push(` rationale: ${d.suggestedFix.rationale}`); } return lines.join('\n'); } /** * Render a shrink result summary. */ export function renderShrink(result: ShrinkResult): string { const lines: string[] = []; lines.push(' shrink:'); if (result.reduced) { lines.push(` reduced across: ${result.axes.join(', ')}`); lines.push(` steps: ${result.steps}`); } else { lines.push(' no reduction possible'); } return lines.join('\n'); }