Files
scriptc/packages/compiler/test/process-builtin-values.test.ts
T
Chris Tate 7a7fd8ce1d Match Node's node:path behavior from Node 24's path tests
path.posix, path.win32 and the bare path module are one module object
each, the handle process.getBuiltinModule answers, so require('path/posix'),
path.posix and the target platform's bare path compare as Node's do, a
namespace beside other values in one slot is untyped, and path[family]
reads that object. Functions read through them take checked arguments:
each string parameter passes Node's validateString under its own name
(basename's suffix first), resolve validates paths[i] only as far as its
last-first walk reads them, parse answers an untyped { root, dir, base,
ext, name }, format follows _format over any object, and
toNamespacedPath answers a non-string as it is. A JavaScript call whose
arguments are not strings, or omit one, goes through the same checks.
Typed slots keep the typed function values.

path.win32.relative lowercases both resolved paths with
String.prototype.toLowerCase and walks them by UTF-16 unit, comparing
segments instead when lowercasing changed a length, as Node does. It
folded ASCII only, so a path with İ or Ⱥ in a different case gave a
longer relative path. The function moves into its own runtime unit,
scr_path_win32_relative.c, which links with the regex unit whose case
mapping it uses. scr_path.c stays free of it, so library and
cross-target builds that link the path functions without the regex unit
still link.

A JavaScript binding whose inferred type holds a union of functions and
arrays (a test table of [fn, cases] rows, or one destructured row) takes
the checked-dynamic kind that dynFallbackType gives the unmappable
spellings of such tables. Its rows keep their functions' names, and
for-of, forEach and destructuring read the items as untyped values.
TypeScript bindings keep their typed layout.
2026-10-28 19:45:36 -05:00

49 lines
2.1 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { expect, test } from "vitest";
import { compile } from "../src/index.js";
test.each(["llvm"] as const)(
"native builtin values refuse unimplemented modules and exports (%s)",
async (backend) => {
const dir = mkdtempSync("/tmp/scriptc-builtin-values-");
try {
const entry = join(dir, "main.js");
writeFileSync(
entry,
`
const load = process.getBuiltinModule;
try { load("node:fs"); } catch (error) { console.log(error.code, error.message.includes("native builtin 'fs'")); }
const path = load("path");
console.log(JSON.stringify(path.parse("/a")));
const worker = load("worker_threads");
try { console.log(worker.Worker); } catch (error) { console.log(error.code, error.message.includes("worker_threads.Worker")); }
console.log(path.posix.join("still", "works"), worker.isMainThread);
var scope = globalThis;
try { console.log(scope.process); } catch (error) { console.log(error.code, error.message.includes("globalThis.process")); }
const segmenter = new Intl.Segmenter();
try { segmenter.resolvedOptions(); } catch (error) { console.log(error.message.includes("Intl.Segmenter locale negotiation")); }
try { console.log(segmenter.segment); } catch (error) { console.log(error.message.includes("not supported")); }
`,
);
const result = await compile(entry, {
backend,
sanitize: process.env["SCRIPTC_SAN"] === "1",
outDir: dir,
outPath: join(dir, "program"),
});
expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true);
if (!result.ok) return;
const child = spawnSync(result.binaryPath, [], { encoding: "utf8" });
expect(child.status, child.stderr).toBe(0);
expect(child.stderr).toBe("");
expect(child.stdout).toBe(
'SC2020 true\n{"root":"/","dir":"/","base":"a","ext":"","name":"a"}\nSC2020 true\nstill/works true\nSC2020 true\ntrue\ntrue\n',
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
},
);