Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136// A copy of test/erase.mjs that never aborts: throws are tallied per input, with examples per class.// Usage: node judge.mjs <dir|file.jsonl|substring> [--list=path] (TYPESTRIP_ERASE_WASM selects the engine)import fs from "node:fs";import path from "node:path";import { createRequire } from "node:module";const root = new URL("../..", import.meta.url).pathname.replace(/\/$/, "");const ts = createRequire(root + "/package.json")("typescript");process.env.TYPESTRIP_ERASE_WASM ??= "build/erase.wasm";const { blankSourceFile } = await import(root + "/vendor/ts-blank-space/out/index.js");const { transformSync } = await import(root + "/src/index.js");const { cases } = await import(root + "/test/corpus.mjs");
const arg = process.argv[2];const listPath = process.argv.find((a) => a.startsWith("--list="))?.slice(7);const SK = ts.SyntaxKind;const ALLOWED = new Set([" ", ";", "(", ")"]);
function* walk(dir) { let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } for (const e of entries) { if (e.name.startsWith(".")) continue; const full = path.join(dir, e.name); if (e.isDirectory()) yield* walk(full); else if (/\.[cm]?ts$/.test(e.name) && !e.name.endsWith(".d.ts.map")) yield full; }}
function* inputs() { if (arg && fs.existsSync(arg) && fs.statSync(arg).isDirectory()) { const seen = new Set(); for (const f of walk(arg)) { let src; try { src = fs.readFileSync(f, "utf8"); } catch { continue; } if (seen.has(src)) continue; seen.add(src); const sf = ts.createSourceFile("input.ts", src, ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS); if (sf.parseDiagnostics.length === 0) yield { name: f, src, sf }; } } else if (arg?.endsWith(".jsonl")) { const lines = fs.readFileSync(arg, "utf8").split("\n").filter(Boolean); for (const [i, line] of lines.entries()) yield { name: `${path.basename(arg)}/${i}`, src: JSON.parse(line) }; } else { for (const c of cases({ only: arg })) yield c; }}
function jsAst(code) { const sf = ts.createSourceFile("out.js", code, ts.ScriptTarget.ESNext, false, ts.ScriptKind.JS); const out = []; (function visit(n) { if (n.kind === SK.EmptyStatement || n.kind === SK.SemicolonClassElement) return; out.push(n.kind); if (ts.isIdentifier(n) || ts.isPrivateIdentifier(n) || ts.isLiteralExpression(n) || ts.isTemplateLiteralToken?.(n)) out.push(JSON.stringify(n.text)); ts.forEachChild(n, visit); out.push(-1); })(sf); return { ok: sf.parseDiagnostics.length === 0, ast: out.join(",") };}
function positional(src, out) { if (out.length !== src.length) return `length ${out.length} != input ${src.length}`; for (let i = 0; i < src.length; i++) { const a = src[i], b = out[i]; if (a === b) continue; if (a === "\n" || a === "\r") return `line terminator changed at ${i}`; if (!ALLOWED.has(b)) return `invented ${JSON.stringify(b)} at ${i}`; } return null;}
function* parts(src) { let seed = src.length; for (let i = 0; i < src.length; ) { seed = (seed * 1103515245 + 12345) & 0x7fffffff; const n = 1 + (seed % 4096); yield src.slice(i, i + n); i += n; }}function chunked(src) { let out = ""; for (const piece of transformSync(parts(src))) out += piece; return out;}
let pass = 0, n = 0;const failures = [];const tally = {};const SHOW = Number(process.env.SHOW ?? 3);const t0 = performance.now();for (const { name, src, sf: parsed } of inputs()) { n++; const sf = parsed ?? ts.createSourceFile("input.ts", src, ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS); const wantErrors = []; let want; try { want = blankSourceFile(sf, (x) => wantErrors.push(x.getStart(sf))); } catch (e) { continue; } const gotErrors = []; let got; let problem = null; try { got = transformSync(src, { onError: (e) => gotErrors.push(e.start) }); } catch (e) { problem = "threw whole"; } if (!problem) { problem = positional(src, got); if (!problem) { const w = jsAst(want), g = jsAst(got); if (w.ok && !g.ok) problem = "JS parse failed where ts-blank-space's output parses"; else if (w.ast !== g.ast) problem = "JS AST differs from ts-blank-space's output"; else if (wantErrors.join() !== gotErrors.join()) problem = `errors want [${wantErrors}] got [${gotErrors}]`; else { try { if (chunked(src) !== got) problem = "streamed output differs from whole-input output"; } catch (e) { problem = "threw chunked"; } } } } if (!problem) { pass++; continue; } const kind = problem.startsWith("errors") ? "errors differ" : problem.split(" ").slice(0, 3).join(" "); tally[kind] = (tally[kind] ?? 0) + 1; let detail = ""; if (got !== undefined) { let i = 0; while (i < want.length && got[i] === want[i]) i++; detail = `near ${i}\n src : ${JSON.stringify(src.slice(Math.max(0, i - 50), i + 50))}\n want: ${JSON.stringify(want.slice(Math.max(0, i - 50), i + 50))}\n got : ${JSON.stringify(got.slice(Math.max(0, i - 50), i + 50))}`; } failures.push({ name, kind, problem, detail }); if ((tally[kind] ?? 0) <= SHOW) console.log(`FAIL ${name}: ${problem}\n ${detail}\n`);}console.log(`judge: ${pass} pass, ${failures.length} fail of ${n} in ${((performance.now() - t0) / 1000).toFixed(0)}s`, JSON.stringify(tally));if (listPath) fs.writeFileSync(listPath, failures.map((f) => `${f.kind}\t${f.name}\t${f.problem}`).join("\n") + "\n");