mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 19:08:50 +00:00
Fix panic when parsing declare global with type annotations
When parsing TypeScript code with `declare global { ... }` blocks,
identifiers followed by colons (like `TIMER: NodeJS.Timeout;`) were
incorrectly treated as labeled statements instead of type annotations.
This caused the parser to push a `.label` scope during the parse pass,
but when visiting the AST later, arrow functions expected `.function_args`
scopes, resulting in a "Scope mismatch while visiting" panic.
The fix checks if we're in a TypeScript declare context before treating
an identifier followed by a colon as a labeled statement. If we are in
a declare context, we skip the type annotation and return a TypeScript
node instead of creating a label scope.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1185,6 +1185,17 @@ pub fn ParseStmt(
|
||||
switch (expr.data) {
|
||||
.e_identifier => |ident| {
|
||||
if (p.lexer.token == .t_colon and !opts.hasDecorators()) {
|
||||
// In TypeScript declare contexts, "identifier: Type" is a type annotation, not a label
|
||||
if (comptime is_typescript_enabled) {
|
||||
if (opts.is_typescript_declare) {
|
||||
// Skip the colon and type annotation
|
||||
try p.lexer.next();
|
||||
try p.skipTypeScriptType(.lowest);
|
||||
try p.lexer.expectOrInsertSemicolon();
|
||||
return p.s(S.TypeScript{}, loc);
|
||||
}
|
||||
}
|
||||
|
||||
_ = try p.pushScopeForParsePass(.label, loc);
|
||||
defer p.popScope();
|
||||
|
||||
|
||||
65
test/js/bun/transpiler/declare-global.test.ts
Normal file
65
test/js/bun/transpiler/declare-global.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
|
||||
test("declare global with type annotation should not crash", async () => {
|
||||
using dir = tempDir("declare-global-test", {
|
||||
"test.ts": `
|
||||
declare global {
|
||||
TIMER: NodeJS.Timeout;
|
||||
}
|
||||
|
||||
if (globalThis.TIMER) clearInterval(globalThis.TIMER);
|
||||
globalThis.TIMER = setInterval(() => console.log("Started"), 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(globalThis.TIMER);
|
||||
console.log("SUCCESS");
|
||||
process.exit(0);
|
||||
}, 100);
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.ts"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).not.toContain("panic");
|
||||
expect(stderr).not.toContain("Scope mismatch");
|
||||
expect(stdout).toContain("SUCCESS");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("declare global with multiple type annotations", async () => {
|
||||
using dir = tempDir("declare-global-multi", {
|
||||
"test.ts": `
|
||||
declare global {
|
||||
FOO: string;
|
||||
BAR: number;
|
||||
BAZ: () => void;
|
||||
}
|
||||
|
||||
console.log("SUCCESS");
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.ts"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).not.toContain("panic");
|
||||
expect(stderr).not.toContain("Scope mismatch");
|
||||
expect(stdout).toContain("SUCCESS");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user