Skip to content

Commit

Permalink
fix #4018: fix for async as boolean edge case
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jan 7, 2025
1 parent 59950c9 commit df815ac
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 2 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@

Due to a copy+paste typo, the binary published to `@esbuild/netbsd-arm64` was not actually for `arm64`, and didn't run in that environment. This release should fix running esbuild in that environment (NetBSD on 64-bit ARM). Sorry about the mistake.

* Fix esbuild incorrectly rejecting valid TypeScript edge case ([#4027](https://github.com/evanw/esbuild/issues/4027))

The following TypeScript code is valid:

```ts
export function open(async?: boolean): void {
console.log(async as boolean)
}
```

Before this version, esbuild would fail to parse this with a syntax error as it expected the token sequence `async as ...` to be the start of an async arrow function expression `async as => ...`. This edge case should be parsed correctly by esbuild starting with this release.

## 2024

All esbuild versions published in the year 2024 (versions 0.19.12 through 0.24.2) can be found in [CHANGELOG-2024.md](./CHANGELOG-2024.md).
Expand Down
15 changes: 14 additions & 1 deletion internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2879,9 +2879,10 @@ func (p *parser) parseAsyncPrefixExpr(asyncRange logger.Range, level js_ast.L, f
// "async x => {}"
case js_lexer.TIdentifier:
if level <= js_ast.LAssign {
// See https://github.com/tc39/ecma262/issues/2034 for details
isArrowFn := true
if (flags&exprFlagForLoopInit) != 0 && p.lexer.Identifier.String == "of" {
// See https://github.com/tc39/ecma262/issues/2034 for details

// "for (async of" is only an arrow function if the next token is "=>"
isArrowFn = p.checkForArrowAfterTheCurrentToken()

Expand All @@ -2891,6 +2892,18 @@ func (p *parser) parseAsyncPrefixExpr(asyncRange logger.Range, level js_ast.L, f
p.log.AddError(&p.tracker, r, "For loop initializers cannot start with \"async of\"")
panic(js_lexer.LexerPanic{})
}
} else if p.options.ts.Parse && p.lexer.Token == js_lexer.TIdentifier {
// Make sure we can parse the following TypeScript code:
//
// export function open(async?: boolean): void {
// console.log(async as boolean)
// }
//
// TypeScript solves this by using a two-token lookahead to check for
// "=>" after an identifier after the "async". This is done in
// "isUnParenthesizedAsyncArrowFunctionWorker" which was introduced
// here: https://github.com/microsoft/TypeScript/pull/8444
isArrowFn = p.checkForArrowAfterTheCurrentToken()
}

if isArrowFn {
Expand Down
7 changes: 6 additions & 1 deletion internal/js_parser/ts_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2307,7 +2307,7 @@ func TestTSArrow(t *testing.T) {

expectPrintedTS(t, "async (): void => {}", "async () => {\n};\n")
expectPrintedTS(t, "async (a): void => {}", "async (a) => {\n};\n")
expectParseErrorTS(t, "async x: void => {}", "<stdin>: ERROR: Expected \"=>\" but found \":\"\n")
expectParseErrorTS(t, "async x: void => {}", "<stdin>: ERROR: Expected \";\" but found \"x\"\n")

expectPrintedTS(t, "function foo(x: boolean): asserts x", "")
expectPrintedTS(t, "function foo(x: boolean): asserts<T>", "")
Expand All @@ -2331,6 +2331,11 @@ func TestTSArrow(t *testing.T) {
expectParseErrorTargetTS(t, 5, "return check ? (hover = 2, bar) : baz()", "")
expectParseErrorTargetTS(t, 5, "return check ? (hover = 2, bar) => 0 : baz()",
"<stdin>: ERROR: Transforming default arguments to the configured target environment is not supported yet\n")

// https://github.com/evanw/esbuild/issues/4027
expectPrintedTS(t, "function f(async?) { g(async in x) }", "function f(async) {\n g(async in x);\n}\n")
expectPrintedTS(t, "function f(async?) { g(async as boolean) }", "function f(async) {\n g(async);\n}\n")
expectPrintedTS(t, "function f() { g(async as => boolean) }", "function f() {\n g(async (as) => boolean);\n}\n")
}

func TestTSSuperCall(t *testing.T) {
Expand Down

0 comments on commit df815ac

Please sign in to comment.