Files
scriptc/packages/compiler/test/native-class-descriptors.test.ts
Chris Tate a07998f667 Expand static three.js workflows and host integration (#621)
* Expand static three.js workflows and host integration

* Preserve JavaScript class state in static three.js workflows

* Preserve inferred JavaScript behavior across static workflows

* Preserve inferred callables and native compatibility boundaries

* Retain annotated JavaScript factory exports and bootstrap-safe call spreads

* Preserve Buffer and child callback identities in static packages

* Clarify JavaScript symbol spread support
2026-10-02 19:56:34 -05:00

99 lines
4.6 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 { analyze, compile } from "../src/index.js";
function fixture(source: string) {
const dir = mkdtempSync("/tmp/scriptc-class-descriptors-");
const entry = join(dir, "main.js");
writeFileSync(entry, source);
return { dir, entry };
}
test.each([
"Object.defineProperty(value, 'x', {value: 2, writable: false});",
"Object.defineProperties(value, {x: {value: 2}});",
])("keeps unsafe native descriptor changes fenced: %s", (operation) => {
const { dir, entry } = fixture(`class Value { constructor() { this.x = 1; } } const value = new Value(); ${operation}`);
try {
const { coverage } = analyze(entry, { dynamic: false });
expect([...coverage.diagnostics, ...(coverage.runtimeFences ?? [])].some((d) => d.code === "SC2020" && d.message.includes("Object.define"))).toBe(true);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("JS overrides with different return types match Node", async () => {
const { dir, entry } = fixture(`
class Base { copy(value = 1) { return value; } }
class Child extends Base {
/** @returns {string} */
// @ts-expect-error JavaScript permits a different return type at runtime.
copy(value) { console.log('child'); return String(value); }
}
const child = new Child();
console.log(child.copy(2), typeof child.copy(3));
console.log(new Base().copy());
`);
try {
const result = await compile(entry, { dynamic: false, outDir: dir, outPath: join(dir, "program"), sanitize: process.env["SCRIPTC_SAN"] === "1" });
if (!result.ok) throw new Error(JSON.stringify(result.diagnostics));
const run = spawnSync(result.binaryPath, { encoding: "utf8", timeout: 30_000 });
const oracle = spawnSync(process.execPath, [entry], { encoding: "utf8", timeout: 30_000 });
expect(run.error).toBeUndefined();
expect(oracle.status).toBe(0);
expect({ status: run.status, signal: run.signal, stdout: run.stdout, stderr: run.stderr }).toEqual({ status: oracle.status, signal: oracle.signal, stdout: oracle.stdout, stderr: oracle.stderr });
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("replaced instance constructors preserve argument evaluation and match Node", async () => {
const { dir, entry } = fixture(`
function argument() { console.log('argument'); return 2; }
class Value {
constructor(x = 1) { this.x = x; }
clone() { return new this.constructor(argument()); }
}
function replace(value) { value.constructor = Value; }
const value = new Value();
replace(value);
console.log(value.clone().x);
`);
try {
const result = await compile(entry, { dynamic: false, outDir: dir, outPath: join(dir, "program"), sanitize: process.env["SCRIPTC_SAN"] === "1" });
if (!result.ok) throw new Error(JSON.stringify(result.diagnostics));
const run = spawnSync(result.binaryPath, { encoding: "utf8", timeout: 30_000 });
const oracle = spawnSync(process.execPath, [entry], { encoding: "utf8", timeout: 30_000 });
expect(run.error).toBeUndefined();
expect(oracle.status).toBe(0);
expect({ status: run.status, signal: run.signal, stdout: run.stdout, stderr: run.stderr }).toEqual({ status: oracle.status, signal: oracle.signal, stdout: oracle.stdout, stderr: oracle.stderr });
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("derived constructor boundaries preserve initialization and refuse repeated super", async () => {
const { dir, entry } = fixture(`
class Base { constructor(value) { this.value = value; } }
class Before extends Base { constructor() {
// @ts-expect-error Exercise the runtime initialization guard.
this.value; super(1); } }
class Missing extends Base { constructor(flag) { if (flag) super(1); } }
class Repeated extends Base { constructor() { super(1); super(2); } }
try { new Before(); } catch (error) { console.log(error instanceof ReferenceError); }
try { new Missing(false); } catch (error) { console.log(error instanceof ReferenceError); }
try { new Repeated(); } catch (error) { console.log(error.code === 'SC2020'); }
`);
try {
const result = await compile(entry, { outDir: dir, outPath: join(dir, "program"), backend: "llvm", optimization: "dev" });
if (!result.ok) throw new Error(JSON.stringify(result.diagnostics));
const execution = spawnSync(result.binaryPath, [], { encoding: "utf8" });
expect(execution.status).toBe(0);
expect(execution.stdout).toBe("true\ntrue\ntrue\n");
expect(execution.stderr).toBe("");
} finally { rmSync(dir, { recursive: true, force: true }); }
});