Compare commits

...

4 Commits

Author SHA1 Message Date
Claude Bot
c3c02d39cb Remove timers from tests - make fast and non-flaky 2025-11-08 01:51:21 +00:00
Claude Bot
f61c7e15f9 Improve test coverage with nested arrow functions 2025-11-08 01:44:46 +00:00
Claude Bot
749552a271 Simplify test case to minimal repro 2025-11-08 01:35:47 +00:00
Claude Bot
d2cf5603bf 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>
2025-11-08 01:14:57 +00:00
2 changed files with 82 additions and 0 deletions

View File

@@ -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();

View File

@@ -0,0 +1,71 @@
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 {
A: 'a';
}
() => {};
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);
});
test("declare global with multiple type annotations and nested arrow functions", async () => {
using dir = tempDir("declare-global-multi", {
"test.ts": `
declare global {
TIMER: NodeJS.Timeout;
FOO: string;
BAR: number;
BAZ: () => void;
}
// Test nested arrow functions to ensure scope handling is correct
const fn = () => {
const nested = () => {
const deeplyNested = () => "nested";
return deeplyNested();
};
return nested();
};
console.log(fn());
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);
});