import path from "node:path"; import { createRequire } from "node:module"; const [tsc, count, ...args] = process.argv.slice(2); const ts = createRequire(import.meta.url)(path.resolve(path.dirname(tsc), "../lib/typescript.js")); const parsed = ts.parseCommandLine(args); const witnesses = parsed.fileNames.slice(0, Number(count)); const shared = parsed.fileNames.slice(Number(count)); const host = ts.createCompilerHost(parsed.options, true); // These programs only discover source closures; they do not bind or check. // Sharing immutable parsed source files avoids reading/parsing the same libs // for every witness. Only the final combined program performs typechecking. const sources = new Map(); const getSourceFile = host.getSourceFile; host.getSourceFile = (filename, ...options) => { if (!sources.has(filename)) sources.set(filename, getSourceFile(filename, ...options)); return sources.get(filename); }; for (const method of ["fileExists", "directoryExists", "getDirectories", "readFile", "realpath"]) { if (!host[method]) continue; const original = host[method], cache = new Map(); host[method] = (filename) => { if (!cache.has(filename)) cache.set(filename, original(filename)); return cache.get(filename); }; } const closures = witnesses.map( (witness) => new Set(ts.createProgram([witness, ...shared], parsed.options, host).getSourceFiles()), ); const common = new Set([...closures[0]].filter((source) => closures.every((closure) => closure.has(source)))); const ambient = (node) => ts.isModuleDeclaration(node) || ts.isNamespaceExportDeclaration(node) || ts.forEachChild(node, ambient); // A file that is visible to just some witnesses must not contribute globals // or ambient module declarations to the others. Otherwise a combined success // could hide an independent failure. Script files are conservatively unsafe. if ( closures.some((closure) => [...closure].some((source) => !common.has(source) && (!ts.isExternalModule(source) || ambient(source))), ) ) process.exit(2); const program = ts.createProgram(parsed.fileNames, parsed.options, host); const diagnostics = [...parsed.errors, ...ts.getPreEmitDiagnostics(program)]; if (diagnostics.length) { console.error(ts.formatDiagnosticsWithColorAndContext(diagnostics, host)); process.exit(1); }