Files
OmniRoute/scripts/check/typecheckBaseline.mjs
Diego Rodrigues de Sa e Souza 089e70cbc5 fix(quality): validate typecheck baseline schema (#12419)
The API-route and Open-SSE typecheck gates treated every top-level baseline value as a diagnostic map, so policy metadata such as _relax_velocity_2026_08_30 was iterated character by character and reported as fabricated numeric TypeScript improvements. Both gates now share one fail-closed baseline boundary: underscore-prefixed top-level keys are reserved for metadata and skipped, and real file entries must be plain objects.

Worth a follow-up: check-dashboard-typecheck still prints the same fabricated entries, so it looks like a third gate with the same defect that this PR's scope does not cover.

Validated in a combined worktree with the batch's ready set boarded onto the current tip: parse sweep clean on every changed TypeScript file, typecheck:core clean, check:dashboard-typecheck OK (207 pre-existing errors, all within baseline), check:cycles OK, check-file-size OK, 203/205 focused node tests and 94/94 vitest — the two failures belong to #12427, which is held back.
2026-09-02 10:01:51 -03:00

92 lines
3.2 KiB
JavaScript

// Shared parsing and frozen-baseline comparison for the scoped TypeScript gates.
const TSC_ERROR_LINE = /^(.+?)\((\d+),(\d+)\): error (TS\d+):/;
const TS_CODE = /^TS\d+$/;
const UNSAFE_PROPERTY_KEYS = new Set(["__proto__", "constructor", "prototype"]);
function isPlainObject(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
function normalizeDiagnosticCounts(value, label) {
if (!isPlainObject(value)) {
throw new TypeError(`${label} must be a plain object`);
}
const normalized = Object.create(null);
for (const [file, codes] of Object.entries(value)) {
if (UNSAFE_PROPERTY_KEYS.has(file)) {
throw new TypeError(`${label} contains unsupported property key "${file}"`);
}
if (file.startsWith("_")) continue;
if (!isPlainObject(codes)) {
throw new TypeError(`${label} entry "${file}" must be a plain object`);
}
const normalizedCodes = Object.create(null);
for (const [code, count] of Object.entries(codes)) {
if (!TS_CODE.test(code)) {
throw new TypeError(`${label} entry "${file}" has invalid TypeScript code "${code}"`);
}
if (!Number.isFinite(count) || !Number.isInteger(count) || count < 0) {
throw new TypeError(
`${label} entry "${file}" code "${code}" must be a finite nonnegative integer`
);
}
normalizedCodes[code] = count;
}
normalized[file] = normalizedCodes;
}
return normalized;
}
/** Parse `tsc --pretty false` output into per-file/per-code diagnostic counts. */
export function parseTscOutput(raw) {
const counts = {};
for (const line of String(raw).split("\n")) {
const match = TSC_ERROR_LINE.exec(line);
if (!match) continue;
const [, file, , , code] = match;
if (!counts[file]) counts[file] = {};
counts[file][code] = (counts[file][code] || 0) + 1;
}
return counts;
}
/**
* Compare live diagnostic counts with a frozen baseline.
*
* Underscore-prefixed top-level keys are reserved for baseline metadata and
* never participate in the diagnostic comparison.
*/
export function diffAgainstBaseline(live, baseline) {
const liveCounts = normalizeDiagnosticCounts(live, "live diagnostics");
const baselineCounts = normalizeDiagnosticCounts(baseline, "typecheck baseline");
const regressions = [];
const improvements = [];
for (const [file, codes] of Object.entries(liveCounts)) {
for (const [code, liveCount] of Object.entries(codes)) {
const baselineCount = baselineCounts[file]?.[code] ?? 0;
if (liveCount > baselineCount) {
regressions.push({ file, code, liveCount, baselineCount });
} else if (liveCount < baselineCount) {
improvements.push({ file, code, liveCount, baselineCount });
}
}
}
for (const [file, codes] of Object.entries(baselineCounts)) {
for (const [code, baselineCount] of Object.entries(codes)) {
const liveCodes = liveCounts[file];
if (!Object.hasOwn(liveCodes ?? {}, code) && baselineCount > 0) {
improvements.push({ file, code, liveCount: 0, baselineCount });
}
}
}
return { regressions, improvements };
}