mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-10 08:52:12 +03:00
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.
92 lines
3.2 KiB
JavaScript
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 };
|
|
}
|