mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
### What does this PR do? Three things: - JSCommonJSExtensions.cpp `onAssign` was returning out of sync numbers instead of `BunLoaderTypeJS`/`BunLoaderTypeNAPI`/... - `bun.schema.api.Loader._none` was 255 instead of 254 like `BunLoaderTypeNone` - `Bun__transpileFile` used `bun.options.Loader.Optional` instead of `bun.schema.api.Loader`. `bun.options.Loader` does not have a type kept in sync in C++. ### How did you verify your code works? Added tests that make sure the correct loader is used for modules required with custom _extensions functions
457 lines
14 KiB
TypeScript
457 lines
14 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { bunEnv, bunExe, tempDir } from "harness";
|
|
|
|
test("Module._extensions override should not allow TypeScript syntax in .js files", async () => {
|
|
using dir = tempDir("module-extensions-ts-in-js", {
|
|
"index.js": `
|
|
const Module = require("module");
|
|
const orig = Module._extensions[".js"];
|
|
|
|
// Override the .js extension handler with a wrapper
|
|
Module._extensions[".js"] = (m, f) => {
|
|
return orig(m, f);
|
|
};
|
|
|
|
// This should error because it has TypeScript syntax in a .js file
|
|
try {
|
|
require("./typescript-syntax.js");
|
|
console.log("ERROR: Should have failed to parse TypeScript syntax in .js file");
|
|
process.exit(1);
|
|
} catch (err) {
|
|
console.log("SUCCESS: Correctly rejected TypeScript syntax in .js file");
|
|
process.exit(0);
|
|
}
|
|
`,
|
|
"typescript-syntax.js": `
|
|
const value: string = "hello";
|
|
export { value };
|
|
`,
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "index.js"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
stdout: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout).toContain("SUCCESS: Correctly rejected TypeScript syntax in .js file");
|
|
});
|
|
|
|
test("Module._extensions override should not allow JSX syntax in .js files", async () => {
|
|
using dir = tempDir("module-extensions-jsx-in-js", {
|
|
"index.js": `
|
|
const Module = require("module");
|
|
const orig = Module._extensions[".js"];
|
|
|
|
// Override the .js extension handler
|
|
Module._extensions[".js"] = (m, f) => {
|
|
console.log("Loading:", f);
|
|
return orig(m, f);
|
|
};
|
|
|
|
// This should error because it has JSX syntax in a .js file
|
|
try {
|
|
require("./jsx-syntax.js");
|
|
console.log("ERROR: Should have failed to parse JSX syntax in .js file");
|
|
process.exit(1);
|
|
} catch (err) {
|
|
console.log("SUCCESS: Correctly rejected JSX syntax in .js file");
|
|
process.exit(0);
|
|
}
|
|
`,
|
|
"jsx-syntax.js": `
|
|
const element = <div>Hello</div>;
|
|
export { element };
|
|
`,
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "index.js"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
stdout: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout).toContain("SUCCESS: Correctly rejected JSX syntax in .js file");
|
|
});
|
|
|
|
test("Module._extensions override should preserve loader type for each extension", async () => {
|
|
using dir = tempDir("module-extensions-loader-types", {
|
|
"index.js": `
|
|
const Module = require("module");
|
|
const results = [];
|
|
|
|
// Save original loaders
|
|
const origJS = Module._extensions[".js"];
|
|
const origTS = Module._extensions[".ts"];
|
|
const origJSON = Module._extensions[".json"];
|
|
|
|
// Override each with a wrapper
|
|
Module._extensions[".js"] = (m, f) => {
|
|
results.push(".js loader called");
|
|
return origJS(m, f);
|
|
};
|
|
|
|
Module._extensions[".ts"] = (m, f) => {
|
|
results.push(".ts loader called");
|
|
return origTS(m, f);
|
|
};
|
|
|
|
Module._extensions[".json"] = (m, f) => {
|
|
results.push(".json loader called");
|
|
return origJSON(m, f);
|
|
};
|
|
|
|
// Test .js file with JavaScript (should work)
|
|
try {
|
|
const js = require("./plain.js");
|
|
results.push("plain.js loaded: " + js.type);
|
|
} catch (err) {
|
|
results.push("ERROR loading plain.js: " + err.message);
|
|
}
|
|
|
|
// Test .ts file with TypeScript (should work)
|
|
try {
|
|
const ts = require("./typed.ts");
|
|
results.push("typed.ts loaded: " + ts.type);
|
|
} catch (err) {
|
|
results.push("ERROR loading typed.ts: " + err.message);
|
|
}
|
|
|
|
// Test .json file (should work)
|
|
try {
|
|
const json = require("./data.json");
|
|
results.push("data.json loaded: " + json.type);
|
|
} catch (err) {
|
|
results.push("ERROR loading data.json: " + err.message);
|
|
}
|
|
|
|
// Test .js file with TypeScript syntax (should fail)
|
|
try {
|
|
require("./typescript-in-js.js");
|
|
results.push("ERROR: typescript-in-js.js should have failed");
|
|
} catch (err) {
|
|
results.push("typescript-in-js.js correctly failed");
|
|
}
|
|
|
|
console.log(results.join("\\n"));
|
|
`,
|
|
"plain.js": `
|
|
module.exports = { type: "javascript" };
|
|
`,
|
|
"typed.ts": `
|
|
interface Data {
|
|
type: string;
|
|
}
|
|
const data: Data = { type: "typescript" };
|
|
module.exports = data;
|
|
`,
|
|
"data.json": `
|
|
{ "type": "json" }
|
|
`,
|
|
"typescript-in-js.js": `
|
|
const value: string = "should fail";
|
|
module.exports = value;
|
|
`,
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "index.js"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
stdout: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout).toContain(".js loader called");
|
|
expect(stdout).toContain(".ts loader called");
|
|
expect(stdout).toContain(".json loader called");
|
|
expect(stdout).toContain("plain.js loaded: javascript");
|
|
expect(stdout).toContain("typed.ts loaded: typescript");
|
|
expect(stdout).toContain("data.json loaded: json");
|
|
expect(stdout).toContain("typescript-in-js.js correctly failed");
|
|
});
|
|
|
|
test("Module._extensions override with custom function should not affect loader type", async () => {
|
|
using dir = tempDir("module-extensions-custom-function", {
|
|
"index.js": `
|
|
const Module = require("module");
|
|
const fs = require("fs");
|
|
|
|
// Override .js with a completely custom function (not wrapping the original)
|
|
Module._extensions[".js"] = function(module, filename) {
|
|
// Custom implementation that mimics the original
|
|
const content = fs.readFileSync(filename, 'utf8');
|
|
module._compile(content, filename);
|
|
};
|
|
|
|
// This should still fail with TypeScript syntax in .js
|
|
try {
|
|
require("./typescript.js");
|
|
console.log("ERROR: Should have failed with TypeScript in .js");
|
|
process.exit(1);
|
|
} catch (err) {
|
|
console.log("SUCCESS: Correctly failed with custom loader");
|
|
process.exit(0);
|
|
}
|
|
`,
|
|
"typescript.js": `
|
|
const x: number = 42;
|
|
module.exports = x;
|
|
`,
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "index.js"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
stdout: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout).toContain("SUCCESS: Correctly failed with custom loader");
|
|
});
|
|
|
|
test("Module._extensions override should handle cross-assignment correctly", async () => {
|
|
using dir = tempDir("module-extensions-cross-assign", {
|
|
"index.js": `
|
|
const Module = require("module");
|
|
|
|
// Save original loaders
|
|
const origJS = Module._extensions[".js"];
|
|
const origTS = Module._extensions[".ts"];
|
|
|
|
// Cross-assign: .js uses .ts loader, .ts uses .js loader
|
|
Module._extensions[".js"] = origTS;
|
|
Module._extensions[".ts"] = origJS;
|
|
|
|
// Now .js files should accept TypeScript syntax
|
|
try {
|
|
const jsWithTS = require("./typescript-syntax.js");
|
|
console.log("SUCCESS: .js with TS loader accepts TypeScript:", jsWithTS.value);
|
|
} catch (err) {
|
|
console.log("ERROR: .js with TS loader failed:", err.message);
|
|
process.exit(1);
|
|
}
|
|
|
|
// And .ts files should reject TypeScript syntax
|
|
try {
|
|
require("./typescript-syntax.ts");
|
|
console.log("ERROR: .ts with JS loader should reject TypeScript");
|
|
process.exit(1);
|
|
} catch (err) {
|
|
console.log("SUCCESS: .ts with JS loader correctly rejects TypeScript");
|
|
}
|
|
`,
|
|
"typescript-syntax.js": `
|
|
const value: string = "typescript";
|
|
module.exports = { value };
|
|
`,
|
|
"typescript-syntax.ts": `
|
|
const value: string = "typescript";
|
|
module.exports = { value };
|
|
`,
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "index.js"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
stdout: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout).toContain("SUCCESS: .js with TS loader accepts TypeScript");
|
|
expect(stdout).toContain("SUCCESS: .ts with JS loader correctly rejects TypeScript");
|
|
});
|
|
|
|
test("Module._extensions override chain should preserve correct loader", async () => {
|
|
using dir = tempDir("module-extensions-chain", {
|
|
"index.js": `
|
|
const Module = require("module");
|
|
const origJS = Module._extensions[".js"];
|
|
|
|
// Create a chain of wrappers
|
|
Module._extensions[".js"] = (m, f) => {
|
|
console.log("Wrapper 1");
|
|
return origJS(m, f);
|
|
};
|
|
|
|
const wrapper1 = Module._extensions[".js"];
|
|
Module._extensions[".js"] = (m, f) => {
|
|
console.log("Wrapper 2");
|
|
return wrapper1(m, f);
|
|
};
|
|
|
|
// Should still reject TypeScript in .js
|
|
try {
|
|
require("./typescript.js");
|
|
console.log("ERROR: Should reject TypeScript in .js");
|
|
process.exit(1);
|
|
} catch (err) {
|
|
console.log("SUCCESS: Correctly rejected TypeScript");
|
|
process.exit(0);
|
|
}
|
|
`,
|
|
"typescript.js": `
|
|
type Foo = string;
|
|
const x: Foo = "test";
|
|
module.exports = x;
|
|
`,
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "index.js"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
stdout: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout).toContain("Wrapper 2");
|
|
expect(stdout).toContain("Wrapper 1");
|
|
expect(stdout).toContain("SUCCESS: Correctly rejected TypeScript");
|
|
});
|
|
|
|
test("Module._extensions override should handle .mjs and .cjs correctly", async () => {
|
|
using dir = tempDir("module-extensions-mjs-cjs", {
|
|
"index.js": `
|
|
const Module = require("module");
|
|
|
|
// Override .mjs and .cjs
|
|
const origMJS = Module._extensions[".mjs"];
|
|
const origCJS = Module._extensions[".cjs"] || Module._extensions[".js"];
|
|
|
|
Module._extensions[".mjs"] = (m, f) => {
|
|
console.log("Loading .mjs:", f);
|
|
return origMJS(m, f);
|
|
};
|
|
|
|
Module._extensions[".cjs"] = (m, f) => {
|
|
console.log("Loading .cjs:", f);
|
|
return origCJS(m, f);
|
|
};
|
|
|
|
// .mjs with TypeScript should fail
|
|
try {
|
|
require("./typescript.mjs");
|
|
console.log("ERROR: .mjs should reject TypeScript");
|
|
} catch (err) {
|
|
console.log("SUCCESS: .mjs rejected TypeScript");
|
|
}
|
|
|
|
// .cjs with TypeScript should fail
|
|
try {
|
|
require("./typescript.cjs");
|
|
console.log("ERROR: .cjs should reject TypeScript");
|
|
} catch (err) {
|
|
console.log("SUCCESS: .cjs rejected TypeScript");
|
|
}
|
|
|
|
// Valid .mjs should work
|
|
try {
|
|
const mjs = require("./valid.mjs");
|
|
console.log("SUCCESS: .mjs loaded:", mjs.type);
|
|
} catch (err) {
|
|
console.log("ERROR: Valid .mjs failed:", err.message);
|
|
}
|
|
`,
|
|
"typescript.mjs": `
|
|
const value: string = "typescript";
|
|
export { value };
|
|
`,
|
|
"typescript.cjs": `
|
|
const value: string = "typescript";
|
|
module.exports = { value };
|
|
`,
|
|
"valid.mjs": `
|
|
export const type = "mjs";
|
|
`,
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "index.js"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
stdout: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout).toContain("SUCCESS: .mjs rejected TypeScript");
|
|
expect(stdout).toContain("SUCCESS: .cjs rejected TypeScript");
|
|
expect(stdout).toContain("SUCCESS: .mjs loaded");
|
|
});
|
|
|
|
test("Module._extensions override should not affect JSON parsing", async () => {
|
|
using dir = tempDir("module-extensions-json", {
|
|
"index.js": `
|
|
const Module = require("module");
|
|
const origJSON = Module._extensions[".json"];
|
|
|
|
Module._extensions[".json"] = (m, f) => {
|
|
console.log("Loading JSON:", f);
|
|
return origJSON(m, f);
|
|
};
|
|
|
|
// Should still parse as JSON, not JavaScript
|
|
try {
|
|
const data = require("./invalid-json.json");
|
|
console.log("ERROR: Should have failed to parse invalid JSON");
|
|
process.exit(1);
|
|
} catch (err) {
|
|
console.log("SUCCESS: Correctly failed on invalid JSON");
|
|
}
|
|
|
|
// Valid JSON should work
|
|
const valid = require("./valid.json");
|
|
console.log("Valid JSON loaded:", valid.test);
|
|
`,
|
|
"invalid-json.json": `
|
|
// This is a comment, which is invalid in JSON
|
|
{ "test": "value" }
|
|
`,
|
|
"valid.json": `
|
|
{ "test": "passed" }
|
|
`,
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "index.js"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
stdout: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stdout).toContain("SUCCESS: Correctly failed on invalid JSON");
|
|
expect(stdout).toContain("Valid JSON loaded: passed");
|
|
expect(stdout).toContain("Loading JSON:");
|
|
});
|