mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-21 05:50:55 +08:00
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks. - Preserve compiler import boundaries and protected fixtures while cleaning up lint findings. - Standardize source formatting and validate import, fixture, and embedded-source protections.
118 lines
4.1 KiB
TypeScript
118 lines
4.1 KiB
TypeScript
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, expect, test } from "vitest";
|
|
import { compile } from "../src/index.js";
|
|
|
|
const dirs: string[] = [];
|
|
afterEach(() => {
|
|
for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
function fixture() {
|
|
const dir = mkdtempSync(join(tmpdir(), "scriptc-native-addon-"));
|
|
dirs.push(dir);
|
|
mkdirSync(join(dir, "native"));
|
|
writeFileSync(
|
|
join(dir, "native", "addon.node"),
|
|
"fixture only; never execute native addons during compilation\n",
|
|
);
|
|
writeFileSync(join(dir, "native", "package.json"), JSON.stringify({ main: "addon.node" }));
|
|
return dir;
|
|
}
|
|
|
|
for (const dynamic of [false, true]) {
|
|
test.for(["relative", "extensionless", "directory", "absolute", "inline"] as const)(
|
|
`createRequire explains native addon migration: %s (dynamic=${dynamic})`,
|
|
async (form) => {
|
|
const dir = fixture();
|
|
const spec =
|
|
form === "absolute"
|
|
? join(dir, "native", "addon.node")
|
|
: form === "extensionless"
|
|
? "./native/addon"
|
|
: form === "directory"
|
|
? "./native"
|
|
: "./native/addon.node";
|
|
const call =
|
|
form === "inline"
|
|
? `createRequire(import.meta.url)(${JSON.stringify(spec)})`
|
|
: `require(${JSON.stringify(spec)})`;
|
|
const source = [
|
|
'import { createRequire } from "node:module";',
|
|
"const require = createRequire(import.meta.url);",
|
|
`const native = ${call} as { value: number };`,
|
|
"console.log(native.value);",
|
|
].join("\n");
|
|
const entry = join(dir, "main.mts");
|
|
writeFileSync(entry, source);
|
|
const result = await compile(entry, {
|
|
dynamic,
|
|
outputKind: "ir",
|
|
outDir: dir,
|
|
outPath: join(dir, "out.json"),
|
|
});
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) return;
|
|
const diagnostic = result.diagnostics.find(
|
|
(diag) => diag.code === "SC2020" && diag.message.includes("native addon"),
|
|
);
|
|
expect(diagnostic, JSON.stringify(result.diagnostics)).toBeDefined();
|
|
expect(diagnostic?.message).toContain(spec);
|
|
expect(diagnostic?.hint).toContain("--ffi");
|
|
expect(diagnostic?.hint).toContain("C ABI");
|
|
expect(diagnostic?.hint).toContain("https://scriptc.dev/ffi#replacing-a-node-api-addon");
|
|
expect(source.slice(diagnostic?.loc.start, diagnostic?.loc.end)).toBe(call);
|
|
},
|
|
);
|
|
|
|
test(`a JavaScript directory named .node is still a program module (dynamic=${dynamic})`, async () => {
|
|
const dir = fixture();
|
|
mkdirSync(join(dir, "javascript.node"));
|
|
writeFileSync(join(dir, "javascript.node", "index.js"), "exports.value = 42;\n");
|
|
const entry = join(dir, "main.mts");
|
|
writeFileSync(
|
|
entry,
|
|
[
|
|
'import { createRequire } from "node:module";',
|
|
"const require = createRequire(import.meta.url);",
|
|
'const javascript = require("./javascript.node") as { value: number };',
|
|
"console.log(javascript.value);",
|
|
].join("\n"),
|
|
);
|
|
const result = await compile(entry, {
|
|
dynamic,
|
|
outputKind: "ir",
|
|
outDir: dir,
|
|
outPath: join(dir, "out.json"),
|
|
});
|
|
expect(result.ok, result.ok ? "" : JSON.stringify(result.diagnostics)).toBe(true);
|
|
});
|
|
}
|
|
|
|
test("a missing .node path keeps the unresolved-module diagnostic", async () => {
|
|
const dir = fixture();
|
|
const entry = join(dir, "main.mts");
|
|
writeFileSync(
|
|
entry,
|
|
[
|
|
'import { createRequire } from "node:module";',
|
|
"const require = createRequire(import.meta.url);",
|
|
'require("./missing.node");',
|
|
].join("\n"),
|
|
);
|
|
const result = await compile(entry, {
|
|
outputKind: "ir",
|
|
outDir: dir,
|
|
outPath: join(dir, "out.json"),
|
|
});
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) return;
|
|
expect(
|
|
result.diagnostics.some(
|
|
(diag) => diag.code === "SC2020" && diag.message.includes("./missing.node"),
|
|
),
|
|
).toBe(true);
|
|
expect(result.diagnostics.some((diag) => diag.message.includes("native addon"))).toBe(false);
|
|
});
|