Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
20f9e57e3f fix(parser): reject import.meta in CommonJS files
`import.meta` is only valid in ES modules. When used in `.cjs`, `.cts`,
or files under `"type": "commonjs"`, Bun now reports a syntax error
matching Node.js behavior: "Cannot use 'import.meta' outside a module".

Previously, Bun had two bugs:
1. `import.meta` incorrectly set `esm_import_keyword`, causing `.cjs`
   files without other CJS features to be silently promoted to ESM.
2. `.cjs` files with CJS features (exports, module) would wrap
   `import.meta` via a `$Bun_import_meta` parameter instead of erroring.

Closes #27425

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-25 18:32:36 +00:00
4 changed files with 124 additions and 1 deletions

View File

@@ -154,6 +154,7 @@ pub fn NewParser_(
has_top_level_return: bool = false,
latest_return_had_semicolon: bool = false,
has_import_meta: bool = false,
import_meta_keyword: logger.Range = logger.Range.None,
has_es_module_syntax: bool = false,
top_level_await_keyword: logger.Range = logger.Range.None,
fn_or_arrow_data_parse: FnOrArrowDataParse = FnOrArrowDataParse{},

View File

@@ -1158,6 +1158,11 @@ pub const Parser = struct {
}
}
// Reject import.meta in CommonJS files (matches Node.js behavior)
if (p.has_import_meta and p.options.module_type == .cjs and p.options.features.commonjs_at_runtime) {
try p.log.addRangeError(p.source, p.import_meta_keyword, "Cannot use 'import.meta' outside a module");
}
// Handle dirname and filename at runtime.
//
// If we reach this point, it means:

View File

@@ -12,11 +12,12 @@ pub fn ParseImportExport(
pub fn parseImportExpr(noalias p: *P, loc: logger.Loc, level: Level) anyerror!Expr {
// Parse an "import.meta" expression
if (p.lexer.token == .t_dot) {
p.esm_import_keyword = js_lexer.rangeOfIdentifier(p.source, loc);
const r = js_lexer.rangeOfIdentifier(p.source, loc);
try p.lexer.next();
if (p.lexer.isContextualKeyword("meta")) {
try p.lexer.next();
p.has_import_meta = true;
p.import_meta_keyword = r;
return p.newExpr(E.ImportMeta{}, loc);
} else {
try p.lexer.expectedString("\"meta\"");

View File

@@ -0,0 +1,116 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
// https://github.com/oven-sh/bun/issues/27425
// import.meta in .cjs files should throw a SyntaxError, matching Node.js behavior
test("import.meta in .cjs file throws a SyntaxError", async () => {
using dir = tempDir("issue-27425-simple", {
"simple.cjs": `console.log(import.meta.url);`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "run", "simple.cjs"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain("Cannot use 'import.meta' outside a module");
expect(exitCode).not.toBe(0);
});
test("import.meta in .cjs file with CJS features throws a SyntaxError", async () => {
using dir = tempDir("issue-27425-cjs-features", {
"foo.cjs": `
Object.defineProperty(exports, "__esModule", { value: true });
const module_1 = require('module');
const require2 = (0, module_1.createRequire)(import.meta.url);
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "run", "foo.cjs"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain("Cannot use 'import.meta' outside a module");
expect(exitCode).not.toBe(0);
});
test("import.meta in .cjs file caught by require() throws a SyntaxError", async () => {
using dir = tempDir("issue-27425-require", {
"foo.cjs": `
Object.defineProperty(exports, "__esModule", { value: true });
const module_1 = require('module');
const require2 = (0, module_1.createRequire)(import.meta.url);
`,
"bar.cjs": `
try {
require('./foo.cjs');
console.log('no error');
} catch (err) {
console.log('got an error', err.constructor.name);
}
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "run", "bar.cjs"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("got an error");
expect(stdout).not.toContain("no error");
});
test("import.meta in .cts file throws a SyntaxError", async () => {
using dir = tempDir("issue-27425-cts", {
"simple.cts": `console.log(import.meta.url);`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "run", "simple.cts"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toContain("Cannot use 'import.meta' outside a module");
expect(exitCode).not.toBe(0);
});
test("import.meta in .mjs file still works", async () => {
using dir = tempDir("issue-27425-mjs", {
"simple.mjs": `console.log(typeof import.meta.url);`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "run", "simple.mjs"],
cwd: String(dir),
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout.trim()).toBe("string");
expect(exitCode).toBe(0);
});