Compare commits

...

4 Commits

Author SHA1 Message Date
Claude Bot
b2e04754f5 Simplify test to use tempDirWithFiles helper
Use tempDirWithFiles instead of tempDir for cleaner test setup.
Keep async spawn pattern as it properly tests the CLI behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 22:29:27 +00:00
Claude Bot
b1f9e94d71 Use async Bun.spawn with await using pattern
Replace Bun.spawnSync with async Bun.spawn using await using construct
for deterministic process cleanup. Read stdout/stderr using Response
streams and await proc.exited for exit code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 22:25:31 +00:00
Claude Bot
1513f1dec6 Address code review feedback
- Add errdefer to pop function_args scope on error paths
- Rename test file to follow issue number convention (23712.test.ts)
- Remove negative assertions for "panic" and "unreachable"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 22:16:45 +00:00
Claude Bot
2a8d9a715e fix: parser assertion error on invalid async function syntax
Fixes a crash where the parser would hit an assertion failure when
parsing invalid async function declarations in error recovery mode.

The issue occurred when parsing code like:
```js
const object = {
  a(el) {
  } // missing comma
  b: async function(first) {
  }
}
```

Due to the missing comma, the parser would attempt to parse
`b: async function(first)` as a labeled statement. During error
recovery, `expect(T.t_identifier)` would consume the opening
paren token, causing the conditional scope push check to fail.
Later, when `parseFnBody` tried to push the function_body scope,
it would assert that the parent scope must be function_args,
triggering a panic.

The fix ensures that parseFnStmt always pushes the function_args
scope before calling parseFn, matching the behavior of parseFnExpr.
This maintains the required scope invariant even during error recovery.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 21:59:35 +00:00
2 changed files with 77 additions and 8 deletions

View File

@@ -61,14 +61,14 @@ pub fn ParseFn(
ifStmtScopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.block, loc);
}
var scopeIndex: usize = 0;
var pushedScopeForFunctionArgs = false;
// Push scope if the current lexer token is an open parenthesis token.
// That is, the parser is about parsing function arguments
if (p.lexer.token == .t_open_paren) {
scopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.function_args, p.lexer.loc());
pushedScopeForFunctionArgs = true;
}
// Always push function_args scope at the current lexer position.
// This must happen before parseFn() is called, and must use the same location
// that parseFn will capture as open_parens_loc (which is p.lexer.loc() before
// expect(t_open_paren)). Even in error recovery cases where we're not at the
// open paren, we must push this scope so that parseFnBody's assertion holds.
const scopeIndex = try p.pushScopeForParsePass(js_ast.Scope.Kind.function_args, p.lexer.loc());
const pushedScopeForFunctionArgs = true;
errdefer p.popScope();
var func = try p.parseFn(name, FnOrArrowDataParse{
.needs_async_loc = loc,

View File

@@ -0,0 +1,69 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
import { join } from "path";
test("parser should not crash with assertion error on invalid async function syntax", async () => {
// This used to cause: panic(main thread): reached unreachable code
// when parsing invalid syntax where async function appears after missing comma
const dir = tempDirWithFiles("parser-assertion", {
"input.js": `
const object = {
a(el) {
} // <-- no comma here
b: async function(first) {
}
}
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "build", join(dir, "input.js")],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
const output = stderr + stdout;
// Should report parse errors, not crash with assertion
expect(exitCode).not.toBe(0);
expect(output).toContain("error:");
});
test("parser should not crash with assertion error on labeled async function statement", async () => {
// Similar case: labeled statement with async function
const dir = tempDirWithFiles("parser-assertion-label", {
"input.js": `
b: async function(first) {
}
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "build", join(dir, "input.js")],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
const output = stderr + stdout;
// Should report parse errors, not crash
expect(exitCode).not.toBe(0);
expect(output).toContain("error:");
});