Files
scriptc/tests/fixtures/self-hosting/frontend-program-cases.ts
Chris Tate 2a23ddc37d feat(compiler): run program preflight with native frontend services
- Compile production program loading across module graphs, packages, and configuration
- Preserve optional defaults and support runtime URL bases and regex split captures
- Isolate host execution services and initialize lowering tables outside module cycles
2026-09-28 17:31:11 -05:00

101 lines
4.2 KiB
TypeScript

import { readFileSync, realpathSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { loadProgram, checkPreflight } from "../../../packages/compiler/src/frontend/program.js";
import type { FrontendServices } from "../../../packages/compiler/src/frontend/services.js";
import type { ScrDiagnostic } from "../../../packages/compiler/src/diagnostics/diagnostic.js";
export interface FrontendProgramRequest {
root: string;
cases: {
name: string;
entry: string;
npmStatic: string[];
externalTypes: [string, string][];
edits: { path: string; source: string }[];
}[];
}
interface DiagnosticReport {
code: string;
message: string;
file: string;
start: number;
end: number;
}
interface ProgramReport {
name: string;
error: string;
diagnostics: DiagnosticReport[];
order: string[];
sources: { path: string; text: string }[];
external: { file: string; specifiers: string[] }[];
startup: { message: string; className: string; file: string; start: number; end: number } | null;
entryIdentity: boolean;
released: boolean;
}
function normalized(value: string, root: string): string {
return value.split("\\").join("/")
.split(realpathSync(root).split("\\").join("/") + "/").join("")
.split(root.split("\\").join("/") + "/").join("");
}
function diagnosticReport(diagnostic: ScrDiagnostic, root: string): DiagnosticReport {
return {
code: diagnostic.code,
message: normalized(diagnostic.message, root),
file: normalized(diagnostic.loc.file, root),
start: diagnostic.loc.start,
end: diagnostic.loc.end,
};
}
/** Run the production loader repeatedly through one service owner. Each load
* owns its program host; malformed inputs must not poison later projects. */
export function runFrontendPrograms(services: FrontendServices, input: string, output: string): void {
const request = JSON.parse(readFileSync(input, "utf8")) as FrontendProgramRequest;
const reports: ProgramReport[] = [];
for (const item of request.cases) {
for (const edit of item.edits) writeFileSync(join(request.root, edit.path), edit.source);
const report: ProgramReport = {
name: item.name, error: "", diagnostics: [], order: [], sources: [], external: [],
startup: null, entryIdentity: false, released: false,
};
try {
const entry = join(request.root, item.entry);
const load = loadProgram(entry, services, {
npmStatic: item.npmStatic,
externalTypes: item.externalTypes.map(([specifier, path]) => [specifier, join(request.root, path)]),
});
try {
report.entryIdentity = load.program.getSourceFile(entry) === load.entry;
report.diagnostics = checkPreflight(load).map((diagnostic) => diagnosticReport(diagnostic, request.root));
report.order = load.moduleOrder.map((source) => normalized(source.fileName, request.root));
report.sources = load.program.getSourceFiles()
.filter((source) => !source.isDeclarationFile)
.map((source) => ({ path: normalized(source.fileName, request.root), text: source.text }));
for (const [file, specifiers] of load.externalTypeSpecifiersByFile) {
report.external.push({ file: normalized(file, request.root), specifiers: [...specifiers] });
}
const crash = load.startupCrash;
if (crash !== undefined && crash !== null) report.startup = {
message: normalized(crash.message, request.root), className: crash.className,
file: normalized(crash.loc.file, request.root), start: crash.loc.start, end: crash.loc.end,
};
} finally { load.dispose(); }
report.released = load.program.analysis.nodeEsmFiles.size === 0 && load.program.analysis.createRequireReasons.size === 0;
} catch (error) {
report.error = normalized(error instanceof Error ? error.message : String(error), request.root);
}
reports.push(report);
// Retain completed cases when a later native run crashes or times out.
writeFileSync(output, JSON.stringify(reports));
}
services.close();
let refused = false;
try { services.createProgramHost(); } catch { refused = true; }
if (!refused) throw new Error("closed frontend services accepted a new program host");
services.close();
}