Files
bun.sh/test/regression/issue/22929-module-extensions-asi.test.ts
Dylan Conway d292dcad26 fix(parser): typescript module parsing bug (#23284)
### What does this PR do?
A bug in our typescript parser was causing `module.foo = foo` to parse
as a typescript namespace. If it didn't end with a semicolon and there's
a statement on the next line it would cause a syntax error. Example:

```ts
module.foo = foo
foo.foo = foo
```

fixes #22929 
fixes #22883

### How did you verify your code works?
Added a regression test
2025-10-06 00:37:29 -07:00

116 lines
3.0 KiB
TypeScript

import { expect, test } from "bun:test";
import { mkdtempSync, writeFileSync } from "fs";
import { bunEnv, bunExe } from "harness";
import { tmpdir } from "os";
import { join } from "path";
test("Module._extensions should not break ASI (automatic semicolon insertion)", async () => {
const dir = mkdtempSync(join(tmpdir(), "bun-module-extensions-asi-"));
// Create a module without semicolons that relies on ASI
const moduleWithoutSemi = join(dir, "module-no-semi.js");
writeFileSync(
moduleWithoutSemi,
`function f() {}
module.exports = f
f.f = f`,
);
// Create a test file that hooks Module._extensions
const testFile = join(dir, "test.js");
writeFileSync(
testFile,
`
const Module = require("module");
const orig = Module._extensions[".js"];
// Hook Module._extensions[".js"] - commonly done by transpiler libraries
Module._extensions[".js"] = (m, f) => {
return orig(m, f);
};
// This should work without parse errors
const result = require("./module-no-semi.js");
if (typeof result !== 'function') {
throw new Error('Expected function but got ' + typeof result);
}
if (result.f !== result) {
throw new Error('Expected result.f === result');
}
console.log('SUCCESS');
`,
);
// Run the test
const proc = Bun.spawn({
cmd: [bunExe(), testFile],
cwd: dir,
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should not have parse errors
expect(stderr).not.toContain("Expected '{'");
expect(stderr).not.toContain("Unexpected end of file");
expect(exitCode).toBe(0);
expect(stdout.trim()).toBe("SUCCESS");
});
test("Module._extensions works with modules that have semicolons", async () => {
const dir = mkdtempSync(join(tmpdir(), "bun-module-extensions-semi-"));
// Create a module with semicolons
const moduleWithSemi = join(dir, "module-with-semi.js");
writeFileSync(
moduleWithSemi,
`function g() { return 42; }
module.exports = g;
g.g = g;`,
);
// Create a test file that hooks Module._extensions
const testFile = join(dir, "test.js");
writeFileSync(
testFile,
`
const Module = require("module");
const orig = Module._extensions[".js"];
Module._extensions[".js"] = (m, f) => {
return orig(m, f);
};
// This should also work with semicolons
const result = require("./module-with-semi.js");
if (typeof result !== 'function') {
throw new Error('Expected function but got ' + typeof result);
}
if (result() !== 42) {
throw new Error('Expected result() === 42');
}
if (result.g !== result) {
throw new Error('Expected result.g === result');
}
console.log('SUCCESS');
`,
);
// Run the test
const proc = Bun.spawn({
cmd: [bunExe(), testFile],
cwd: dir,
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should work correctly
expect(exitCode).toBe(0);
expect(stdout.trim()).toBe("SUCCESS");
});