mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
1270 lines
54 KiB
TypeScript
1270 lines
54 KiB
TypeScript
/* Project adoption: the tsconfig.json governing the ENTRY decides checker
|
||
* strictness (the adopted knobs), while lib/module/target stay forced —
|
||
* see the compiler-options split in frontend/program.ts and SEMANTICS.md.
|
||
* The strictness fixtures compile the SAME program under two tsconfigs and
|
||
* pin both directions; the null-floor fixture pins the strictNullChecks
|
||
* floor diagnostic; the node-types fixture (vendored, pinned @types/node —
|
||
* see its README) pins the @types/node adoption path: the supported
|
||
* process surface still lowers statically, and everything else @types/node
|
||
* declares fences with the SC2020-family wording that names it.
|
||
*/
|
||
import { execFile } from "node:child_process";
|
||
import { mkdtempSync, writeFileSync } from "node:fs";
|
||
import { tmpdir } from "node:os";
|
||
import { join } from "node:path";
|
||
import { promisify } from "node:util";
|
||
import { expect, test } from "vitest";
|
||
import { analyze, compile, renderDiagnostics } from "@scriptc/compiler";
|
||
|
||
/* Every compile below deliberately carries NO backend pin: this suite is
|
||
* the user-adoption path, so it must see exactly what a flagless
|
||
* `scriptc build` produces through the production LLVM backend. */
|
||
const execFileAsync = promisify(execFile);
|
||
const repoRoot = join(import.meta.dirname, "../..");
|
||
const fixture = (name: string) => join(repoRoot, "tests/fixtures/strictness", name);
|
||
const nodeTypesDir = join(repoRoot, "tests/fixtures/node-types");
|
||
const sanitize = process.env["SCRIPTC_SAN"] === "1";
|
||
|
||
function comparableStderr(text: string): string {
|
||
return sanitize
|
||
? text.replace(
|
||
/^==\d+==WARNING: ASan doesn't fully support makecontext\/swapcontext functions and may produce false positives in some cases!\n/gm,
|
||
"",
|
||
)
|
||
: text;
|
||
}
|
||
|
||
test("node-types: filesystem capacity supports typed number and BigInt results under @types/node", async () => {
|
||
const outDir = outDirFor("fs-statfs");
|
||
const entry = join(nodeTypesDir, "fs-statfs.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, process.platform === "win32" ? "fs-statfs.exe" : "fs-statfs"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: Headers, Request and Response members typed by undici-types run natively", async () => {
|
||
const outDir = outDirFor("fetch-handle-members");
|
||
const entry = join(nodeTypesDir, "fetch-handle-members.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(
|
||
outDir,
|
||
process.platform === "win32" ? "fetch-handle-members.exe" : "fetch-handle-members",
|
||
),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: fetch's string | URL | Request input and a custom fetch cast to typeof fetch", async () => {
|
||
const outDir = outDirFor("fetch-input-unions");
|
||
const entry = join(nodeTypesDir, "fetch-input-unions.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(
|
||
outDir,
|
||
process.platform === "win32" ? "fetch-input-unions.exe" : "fetch-input-unions",
|
||
),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
for (const [name, title] of [
|
||
[
|
||
"event-loop-metrics",
|
||
"eventLoopUtilization and monitorEventLoopDelay follow @types/node's types",
|
||
],
|
||
["timeout-type", "NodeJS.Timeout fields, ref/unref/hasRef and clearInterval under @types/node"],
|
||
] as const) {
|
||
test(`node-types: ${title}`, async () => {
|
||
const outDir = outDirFor(name);
|
||
const entry = join(nodeTypesDir, `${name}.ts`);
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, process.platform === "win32" ? `${name}.exe` : name),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
}
|
||
|
||
test("node-types: http header arrays and server.address() follow @types/node's types", async () => {
|
||
const outDir = outDirFor("http-headers-address");
|
||
const entry = join(nodeTypesDir, "http-headers-address.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(
|
||
outDir,
|
||
process.platform === "win32" ? "http-headers-address.exe" : "http-headers-address",
|
||
),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: filesystem links accept URL and Buffer paths and typed readlink results under @types/node", async () => {
|
||
const outDir = outDirFor("fs-links");
|
||
const entry = join(nodeTypesDir, "fs-links.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, process.platform === "win32" ? "fs-links.exe" : "fs-links"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: filesystem timestamps accept Date, Buffer, URL, and FileHandle inputs under @types/node", async () => {
|
||
const outDir = outDirFor("fs-timestamps");
|
||
const entry = join(nodeTypesDir, "fs-timestamps.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "fs-timestamps"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: ESM metadata supports computed and destructured reads under @types/node", async () => {
|
||
const outDir = outDirFor("esm-metadata");
|
||
const entry = join(nodeTypesDir, "esm-metadata.mts");
|
||
const result = await compile(entry, { outPath: join(outDir, "esm-metadata"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, [entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: util.types supports native brands and predicate narrowing under @types/node", async () => {
|
||
const outDir = outDirFor("util-types");
|
||
const entry = join(nodeTypesDir, "util-types.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "util-types"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: util.styleText supports stored calls and standard streams under @types/node", async () => {
|
||
const outDir = outDirFor("style-text");
|
||
const entry = join(nodeTypesDir, "style-text.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "style-text"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: dgram socket options under @types/node", async () => {
|
||
const outDir = outDirFor("dgram-options");
|
||
const entry = join(nodeTypesDir, "dgram-options.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, process.platform === "win32" ? "dgram-options.exe" : "dgram-options"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: events.once over typed EventEmitter listeners under @types/node", async () => {
|
||
const outDir = outDirFor("events-once");
|
||
const entry = join(nodeTypesDir, "events-once.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "events-once"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: events.once with a signal over streams, raced, under @types/node", async () => {
|
||
const outDir = outDirFor("events-once-signal-race");
|
||
const entry = join(nodeTypesDir, "events-once-signal-race.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "events-once-signal-race"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: socket.address and resetAndDestroy under @types/node", async () => {
|
||
const outDir = outDirFor("net-reset-address");
|
||
const entry = join(nodeTypesDir, "net-reset-address.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(
|
||
outDir,
|
||
process.platform === "win32" ? "net-reset-address.exe" : "net-reset-address",
|
||
),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: TLSSocket.isSessionReused and keyed ??= timings under @types/node", async () => {
|
||
const outDir = outDirFor("tls-session-timings");
|
||
const entry = join(nodeTypesDir, "tls-session-timings.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(
|
||
outDir,
|
||
process.platform === "win32" ? "tls-session-timings.exe" : "tls-session-timings",
|
||
),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: net.SocketAddress and net.BlockList under @types/node", async () => {
|
||
const outDir = outDirFor("net-blocklist");
|
||
const entry = join(nodeTypesDir, "net-blocklist.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, process.platform === "win32" ? "net-blocklist.exe" : "net-blocklist"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: timers/promises resolve values and setInterval iteration under @types/node", async () => {
|
||
const outDir = outDirFor("timers-promises");
|
||
const entry = join(nodeTypesDir, "timers-promises.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "timers-promises"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: readline/promises over stdin compiles under @types/node", async () => {
|
||
const outDir = outDirFor("readline-promises");
|
||
const entry = join(nodeTypesDir, "readline-promises.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "readline-promises"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
function outDirFor(name: string): string {
|
||
return mkdtempSync(join(tmpdir(), `scriptc-config-${name}-`));
|
||
}
|
||
|
||
test("indexed-loose: the project's own (less strict) knobs are adopted — compiles and runs", async () => {
|
||
const outDir = outDirFor("loose");
|
||
const result = await compile(join(fixture("indexed-loose"), "main.ts"), {
|
||
outPath: join(outDir, "main"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout } = await execFileAsync(result.binaryPath);
|
||
expect(stdout).toBe("21\n");
|
||
});
|
||
|
||
test("indexed-strict: the project's EXTRA strictness is honored — preflight fails", async () => {
|
||
const outDir = outDirFor("strict");
|
||
const result = await compile(join(fixture("indexed-strict"), "main.ts"), {
|
||
outPath: join(outDir, "main"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok).toBe(false);
|
||
if (result.ok) return;
|
||
// The SAME program compiles in indexed-loose; only the tsconfig differs.
|
||
expect(result.diagnostics.map((d) => d.code)).toContain("SC0001");
|
||
expect(result.diagnostics.some((d) => d.message.includes("possibly 'undefined'"))).toBe(true);
|
||
});
|
||
|
||
test("project paths: TS 7 checking and native module lowering share the configured aliases", async () => {
|
||
const outDir = outDirFor("project-paths");
|
||
const result = await compile(join(fixture("project-paths"), "main.ts"), {
|
||
outPath: join(outDir, "main"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout } = await execFileAsync(result.binaryPath);
|
||
expect(stdout).toBe("paths agree 42\n");
|
||
});
|
||
|
||
test("node-types: the supported process surface lowers statically under @types/node", async () => {
|
||
const outDir = outDirFor("node-types");
|
||
const result = await compile(join(nodeTypesDir, "argv-env.ts"), {
|
||
outPath: join(outDir, "argv-env"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout } = await execFileAsync(result.binaryPath, ["alpha", "beta"], {
|
||
env: { ...process.env, SCRIPTC_FIXTURE_GREETING: "hi from env" },
|
||
});
|
||
// argv[0] is the binary path (machine-dependent) — the program prints
|
||
// only the count and argv[1..], then exercises the raw stdout write.
|
||
expect(stdout).toBe("2\nalpha\nbeta\nhi from env\nwritten without newline <- flushed in order\n");
|
||
});
|
||
|
||
test("node-types: URL port and hash getters lower statically under @types/node", async () => {
|
||
const outDir = outDirFor("node-url-getters");
|
||
const entry = join(nodeTypesDir, "url-getters.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "url-getters"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, [entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: captured NodeJS.WritableStream values write through the procStream scalar", async () => {
|
||
const outDir = outDirFor("node-stream-capture");
|
||
const result = await compile(join(nodeTypesDir, "stream-capture.ts"), {
|
||
outPath: join(outDir, "stream-capture"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout, stderr } = await execFileAsync(result.binaryPath);
|
||
expect(stdout).toBe("[out] line\n[out] line\ndone\n");
|
||
expect(comparableStderr(stderr)).toBe("[err] line\n");
|
||
});
|
||
|
||
test("node-types: refined spawn returns expose writable child stdin", async () => {
|
||
const dir = outDirFor("node-child-stdin");
|
||
const entry = join(nodeTypesDir, "child-stdin.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(dir, "child-stdin"),
|
||
outDir: dir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout } = await execFileAsync(result.binaryPath);
|
||
expect(stdout).toBe("typed child stdin\n");
|
||
});
|
||
|
||
test("node-types: callback execFile uses the typed error-first overload", async () => {
|
||
const dir = outDirFor("node-child-execfile");
|
||
const entry = join(nodeTypesDir, "child-execfile.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(dir, "child-execfile"),
|
||
outDir: dir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout } = await execFileAsync(result.binaryPath);
|
||
expect(stdout).toBe("typed-execfile true\n");
|
||
});
|
||
|
||
test("node-types: promisified exec and execFile results under PromiseWithChild", async () => {
|
||
const dir = outDirFor("node-child-promisify-exec");
|
||
const entry = join(nodeTypesDir, "child-promisify-exec.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(dir, "child-promisify-exec"),
|
||
outDir: dir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout, stderr } = await execFileAsync(result.binaryPath);
|
||
expect(stdout).toBe('typed-exec err typed-execfile true\n"Command failed: exit 2\\n"\n');
|
||
expect(stderr).toBe("");
|
||
});
|
||
|
||
test("node-types: promisified fs callback functions lower as their fs/promises calls", async () => {
|
||
const outDir = outDirFor("promisify-fs");
|
||
const entry = join(nodeTypesDir, "promisify-fs.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "promisify-fs"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: util.promisify over typed callback-last functions settles like Node", async () => {
|
||
const outDir = outDirFor("promisify-callback-functions");
|
||
const entry = join(nodeTypesDir, "promisify-callback-functions.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "promisify-callback-functions"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: lookup-option unions and narrowed header-record entries under @types/node", async () => {
|
||
const outDir = outDirFor("lookup-and-header-entries");
|
||
const entry = join(nodeTypesDir, "lookup-and-header-entries.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "lookup-and-header-entries"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const native = await execFileAsync(result.binaryPath);
|
||
const node = await execFileAsync(process.execPath, [
|
||
"--disable-warning=MODULE_TYPELESS_PACKAGE_JSON",
|
||
entry,
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: http.request over a Unix socket with RequestOptions read at run time", async () => {
|
||
const outDir = outDirFor("http-socket-path");
|
||
const entry = join(nodeTypesDir, "http-socket-path.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "http-socket-path"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const native = await execFileAsync(result.binaryPath);
|
||
const node = await execFileAsync(process.execPath, [
|
||
"--disable-warning=MODULE_TYPELESS_PACKAGE_JSON",
|
||
entry,
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: RequestInit-derived options carry BodyInit and HeadersInit into fetch", async () => {
|
||
const outDir = outDirFor("fetch-init-members");
|
||
const entry = join(nodeTypesDir, "fetch-init-members.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "fetch-init-members"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const native = await execFileAsync(result.binaryPath);
|
||
const node = await execFileAsync(process.execPath, [
|
||
"--disable-warning=MODULE_TYPELESS_PACKAGE_JSON",
|
||
entry,
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: stream subclasses extend the @types/node stream classes", async () => {
|
||
const outDir = outDirFor("stream-subclass-hooks");
|
||
const entry = join(nodeTypesDir, "stream-subclass-hooks.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "stream-subclass-hooks"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: NodeJS stream interfaces and stream.Readable/Writable take every stream producer", async () => {
|
||
const outDir = outDirFor("node-stream-interfaces");
|
||
const entry = join(nodeTypesDir, "node-stream-interfaces.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "node-stream-interfaces"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: streams construct and child listeners take @types/node's parameter types", async () => {
|
||
const outDir = outDirFor("stream-construction");
|
||
const entry = join(nodeTypesDir, "stream-construction.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "stream-construction"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: promisified setTimeout and setImmediate lower as timers/promises", async () => {
|
||
const outDir = outDirFor("promisify-timers");
|
||
const entry = join(nodeTypesDir, "promisify-timers.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "promisify-timers"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: promisified zlib one-shots settle from the worker-pool codec", async () => {
|
||
const outDir = outDirFor("promisify-zlib");
|
||
const entry = join(nodeTypesDir, "promisify-zlib.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "promisify-zlib"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: promisified crypto callback functions settle with the callback value", async () => {
|
||
const outDir = outDirFor("promisify-crypto");
|
||
const entry = join(nodeTypesDir, "promisify-crypto.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "promisify-crypto"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: promisified dns lookups settle with the lookup objects", async () => {
|
||
const outDir = outDirFor("promisify-dns");
|
||
const entry = join(nodeTypesDir, "promisify-dns.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "promisify-dns"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: child stdin forms beyond write(string) compile through the general stream representation", async () => {
|
||
const dir = outDirFor("node-child-stdin-streams");
|
||
const entry = join(nodeTypesDir, "child-stdin-streams.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(dir, "child-stdin-streams"),
|
||
outDir: dir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: callback exec uses the typed ExecException overloads", async () => {
|
||
const dir = outDirFor("node-child-exec");
|
||
const entry = join(nodeTypesDir, "child-exec.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(dir, "child-exec"),
|
||
outDir: dir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout } = await execFileAsync(result.binaryPath);
|
||
expect(stdout).toBe('typed-exec err\n"Command failed: exit 2\\n"\n');
|
||
});
|
||
|
||
test("node-types: exec's numeric ExecException.code remains a named fence", async () => {
|
||
const dir = outDirFor("node-child-exec-code-fences");
|
||
const entry = join(nodeTypesDir, "child-exec-code-fences.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(dir, "child-exec-code-fences"),
|
||
outDir: dir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok).toBe(false);
|
||
if (result.ok) return;
|
||
expect(result.diagnostics.map((diagnostic) => diagnostic.code)).toEqual([
|
||
"SC2020",
|
||
"SC2020",
|
||
"SC2020",
|
||
]);
|
||
for (const diagnostic of result.diagnostics) {
|
||
expect(diagnostic.message).toContain("ExecException.code");
|
||
}
|
||
});
|
||
|
||
test("node-types: path and os lower statically under @types/node's shapes", async () => {
|
||
const outDir = outDirFor("node-path-os");
|
||
const result = await compile(join(nodeTypesDir, "path-os.ts"), {
|
||
outPath: join(outDir, "path-os"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout } = await execFileAsync(result.binaryPath);
|
||
expect(stdout).toBe("b/c.txt\n/x/y z .gz\ntrue /\ntrue true\ntrue true true true true\ntrue\n");
|
||
});
|
||
|
||
test("node-types: fetch AbortSignal and readable bodies lower statically", async () => {
|
||
const outDir = outDirFor("node-fetch-static");
|
||
const result = await compile(join(nodeTypesDir, "fetch-static.ts"), {
|
||
outPath: join(outDir, "fetch-static"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
});
|
||
|
||
test("node-types: imported TypeScript sources can use the RequestInfo global", async () => {
|
||
const entry = join(nodeTypesDir, "source-import/main.ts");
|
||
expect(analyze(entry).coverage.preflightFailed).toBe(false);
|
||
const outDir = outDirFor("request-info");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "request-info"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, [entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: global and node:util codec instances share the stored native representation", async () => {
|
||
const entry = join(nodeTypesDir, "text-codecs.mts");
|
||
const outDir = outDirFor("text-codecs");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "text-codecs"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, [entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: imported console methods share native output formatting", async () => {
|
||
const entry = join(nodeTypesDir, "console-imports.mts");
|
||
const outDir = outDirFor("console-imports");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "console-imports"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, [entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: crypto key derivation and SHA-2 match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-crypto-kdf");
|
||
const entry = join(nodeTypesDir, "crypto-kdf.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "crypto-kdf"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: URL mutation matches Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-url-mutation");
|
||
const entry = join(nodeTypesDir, "url-mutation.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "url-mutation"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: URL factories match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-url-factories");
|
||
const entry = join(nodeTypesDir, "url-factories.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "url-factories"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: the legacy url API matches Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-url-legacy");
|
||
const entry = join(nodeTypesDir, "url-legacy.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "url-legacy"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
// The DEP0169 report names each process's own pid.
|
||
const withoutPid = (text: string): string => text.replace(/^\(node:\d+\) /gm, "(node:pid) ");
|
||
expect(withoutPid(comparableStderr(native.stderr))).toBe(withoutPid(node.stderr));
|
||
expect(node.stderr).toContain("[DEP0169] DeprecationWarning");
|
||
});
|
||
|
||
test("node-types: no-error assertions match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-no-error-assertions");
|
||
const entry = join(nodeTypesDir, "no-error-assertions.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "no-error-assertions"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: text utilities match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-text-utilities");
|
||
const entry = join(nodeTypesDir, "text-utilities.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "text-utilities"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: system error utilities match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-system-errors");
|
||
const entry = join(nodeTypesDir, "system-errors.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "system-errors"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: deep equality matches Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-deep-equality");
|
||
const entry = join(nodeTypesDir, "deep-equality.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "deep-equality"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: file URL conversions match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-file-url-conversion");
|
||
const entry = join(nodeTypesDir, "file-url-conversion.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "file-url-conversion"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: environment-file APIs match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-env-files");
|
||
const entry = join(nodeTypesDir, "env-files.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "env-files"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: text encoding APIs match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-text-encoding");
|
||
const entry = join(nodeTypesDir, "text-encoding.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "text-encoding"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: Blob and File match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-blob-file");
|
||
const entry = join(nodeTypesDir, "blob-file.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "blob-file"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: fs.glob forms match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-fs-glob");
|
||
const entry = join(nodeTypesDir, "fs-glob.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "fs-glob"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: util function helpers match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-util-callables");
|
||
const entry = join(nodeTypesDir, "util-callables.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "util-callables"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: Cipheriv and Decipheriv match Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-cipher");
|
||
const entry = join(nodeTypesDir, "cipher.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "cipher"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: ECDH matches Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-ecdh");
|
||
const entry = join(nodeTypesDir, "ecdh.ts");
|
||
const result = await compile(entry, { outPath: join(outDir, "ecdh"), outDir, sanitize });
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: WebCrypto HMAC matches Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-webcrypto-hmac");
|
||
const entry = join(nodeTypesDir, "webcrypto-hmac.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "webcrypto-hmac"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: the navigator global's runtime probe matches Node under @types/node", async () => {
|
||
const outDir = outDirFor("node-navigator");
|
||
const entry = join(nodeTypesDir, "navigator.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "navigator"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: declared-but-not-lowered surface fences, naming @types/node", async () => {
|
||
const outDir = outDirFor("node-fenced");
|
||
const result = await compile(join(nodeTypesDir, "fenced.ts"), {
|
||
outPath: join(outDir, "fenced"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok).toBe(false);
|
||
if (result.ok) return;
|
||
const rendered = renderDiagnostics(result.diagnostics, result.sourceTexts, {
|
||
color: false,
|
||
}).replaceAll(nodeTypesDir + "/", "");
|
||
await expect(rendered).toMatchFileSnapshot("__snapshots__/node-types-fenced.txt");
|
||
});
|
||
|
||
test("json-any: any-typed JSON.parse reads pass preflight (the project's own tsc is the oracle) and LOWER now; the non-Error reject still fences", async () => {
|
||
const outDir = outDirFor("json-any");
|
||
const entry = join(fixture("json-any"), "main.ts");
|
||
// Analyzable: the program typechecks under its own tsconfig, so the
|
||
// override-manufactured errors (parse(): unknown, reject's pinned
|
||
// `(reason: Error) => void`) never fail preflight — coverage computes
|
||
// instead of stopping.
|
||
const { coverage } = analyze(entry);
|
||
expect(coverage.preflightFailed).toBe(false);
|
||
// The build still fails — at LOWERING, with the honest fence for the
|
||
// string reject (a bound reject called with an Error is the supported
|
||
// surface and lowers). The unknown READS lower now (the dyn keyed read,
|
||
// corpus 1544) — no SC1100 remains. Never a SC0001.
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "main"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok).toBe(false);
|
||
if (result.ok) return;
|
||
const codes = result.diagnostics.map((d) => d.code);
|
||
expect(codes).not.toContain("SC0001");
|
||
expect(codes).not.toContain("SC1100");
|
||
expect(
|
||
result.diagnostics.some((d) => d.message.includes("'string' values where 'Error' is expected")),
|
||
).toBe(true);
|
||
});
|
||
|
||
test("json-any-broken: genuine type errors report the project world's own tsc errors, not override-manufactured ones", async () => {
|
||
const outDir = outDirFor("json-any-broken");
|
||
const result = await compile(join(fixture("json-any-broken"), "main.ts"), {
|
||
outPath: join(outDir, "main"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok).toBe(false);
|
||
if (result.ok) return;
|
||
const tsc = result.diagnostics.filter((d) => d.code === "SC0001");
|
||
expect(tsc.some((d) => d.message.includes("is not assignable to type 'number'"))).toBe(true);
|
||
// The 'unknown' complaint exists only in the lowering world — the
|
||
// reported set is the project world's, reproducible with their own tsc.
|
||
expect(tsc.some((d) => d.message.includes("'unknown'"))).toBe(false);
|
||
});
|
||
|
||
test("null-floor: disabling strictNullChecks fails with the floor diagnostic", async () => {
|
||
const outDir = outDirFor("floor");
|
||
const result = await compile(join(fixture("null-floor"), "main.ts"), {
|
||
outPath: join(outDir, "main"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok).toBe(false);
|
||
if (result.ok) return;
|
||
expect(result.diagnostics.map((d) => d.code)).toContain("SC0002");
|
||
expect(result.diagnostics[0]!.message).toContain("strictNullChecks");
|
||
});
|
||
|
||
test("no-types-node: a bare project outside the repo compiles on the fallback declarations", async () => {
|
||
// The npm-install shape: a fresh directory whose package.json has no
|
||
// "type" and whose node_modules has no @types/node — the shipped
|
||
// fallback d.ts is the whole Node surface. Regression: tsgo treats a
|
||
// d.ts under a type:module package as ESM and bans its `export =`
|
||
// ambient-module blocks (9× SC0001 at 1:1); ambient/package.json pins
|
||
// the realm to commonjs so the fallback stays a legal global script.
|
||
const dir = mkdtempSync(join(tmpdir(), "scriptc-bare-project-"));
|
||
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "bare", version: "1.0.0" }));
|
||
writeFileSync(join(dir, "main.ts"), 'const who: string = "bare";\nconsole.log(`hi ${who}`);\n');
|
||
const outDir = outDirFor("bare");
|
||
const result = await compile(join(dir, "main.ts"), {
|
||
outPath: join(outDir, "main"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout } = await execFileAsync(result.binaryPath);
|
||
expect(stdout).toBe("hi bare\n");
|
||
});
|
||
|
||
test("dot-parent: bare '.' and '..' imports build and run (the TS project dialect)", async () => {
|
||
// The vercel-CLI spelling (`from '..'` for a parent directory's index):
|
||
// scriptc-only — Node refuses directory imports when running raw TS as
|
||
// ESM, but the compiled program follows the project's own bundler-
|
||
// resolution dialect (the SEMANTICS.md relative-specifier note).
|
||
const outDir = outDirFor("dot-parent");
|
||
const result = await compile(join(repoRoot, "tests/coverage-fixtures/dot-parent/main.ts"), {
|
||
outPath: join(outDir, "main"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const { stdout } = await execFileAsync(result.binaryPath);
|
||
expect(stdout).toBe("parent-banner:lib-index!\n");
|
||
});
|
||
|
||
test("node-types: terminal properties and filesystem error metadata match Node", async () => {
|
||
const outDir = outDirFor("terminal-error-fields");
|
||
const entry = join(nodeTypesDir, "terminal-error-fields.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "terminal-error-fields"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|
||
|
||
test("node-types: process.versions entries @types/node declares as strings read undefined when absent", async () => {
|
||
const outDir = outDirFor("process-versions-absent");
|
||
const entry = join(nodeTypesDir, "process-versions-absent.ts");
|
||
const result = await compile(entry, {
|
||
outPath: join(outDir, "process-versions-absent"),
|
||
outDir,
|
||
sanitize,
|
||
});
|
||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||
if (!result.ok) return;
|
||
const [native, node] = await Promise.all([
|
||
execFileAsync(result.binaryPath),
|
||
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
|
||
]);
|
||
expect(native.stdout).toBe(node.stdout);
|
||
expect(comparableStderr(native.stderr)).toBe(node.stderr);
|
||
});
|