Files
Imhotep/packages/imhotep-reporter/src/human.ts
T

121 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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');
}