// Shrinks a failing input while TypeScript still parses it cleanly and the judge still fails it the same way. // Usage: node minimize.mjs file [kind] kind: threw (default) | chunked | ast | errors // Deletes line ranges, then token-ish character runs (ddmin style) and prints the result as a JSON string. import fs from "node:fs"; 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 kind = process.argv[3] ?? "threw"; const SK = ts.SyntaxKind; 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)) out.push(JSON.stringify(n.text)); ts.forEachChild(n, visit); out.push(-1); })(sf); return out.join(","); } function* parts(src, n) { for (let i = 0; i < src.length; i += n) yield src.slice(i, i + n); } function chunks(src, n) { let out = ""; for (const piece of transformSync(parts(src, n))) out += piece; return out; } function fails(src) { const sf = ts.createSourceFile("input.ts", src, ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS); if (sf.parseDiagnostics.length) return false; const we = []; let want; try { want = blankSourceFile(sf, (n) => we.push(n.getStart(sf))); } catch { return false; } const ge = []; let got; try { got = transformSync(src, { onError: (e) => ge.push(e.start) }); } catch { return kind === "threw"; } if (kind === "threw") return false; if (kind === "ast") return jsAst(want) !== jsAst(got); if (kind === "errors") return jsAst(want) === jsAst(got) && we.join() !== ge.join(); for (const n of [1, 2, 3, 5, 7, 16]) { try { if (chunks(src, n) !== got) return true; } catch { return true; } } return false; } let src = fs.readFileSync(process.argv[2], "utf8"); if (!fails(src)) { console.log("input does not fail as", kind); process.exit(1); } function shrink(parts, join) { let n = 2; while (parts.length >= 2) { const size = Math.ceil(parts.length / n); let reduced = false; for (let i = 0; i < parts.length; i += size) { const cand = parts.slice(0, i).concat(parts.slice(i + size)); if (fails(cand.join(join))) { parts = cand; n = Math.max(n - 1, 2); reduced = true; break; } } if (!reduced) { if (n >= parts.length) break; n = Math.min(n * 2, parts.length); } } return parts; } src = shrink(src.split("\n"), "\n").join("\n"); src = shrink(src.match(/\s+|\w+|./gs) ?? [], "").join(""); console.log(JSON.stringify(src));