50 lines
2.3 KiB
JavaScript
50 lines
2.3 KiB
JavaScript
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);
|
||
|
|
}
|