v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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<string, ShrinkResult>,
|
||||
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');
|
||||
}
|
||||
Reference in New Issue
Block a user