Skip to content

Commit

Permalink
disallow the "await x ** 2" edge case
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jan 22, 2021
1 parent 6e01a8e commit 39c4cc1
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 2 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

* Fix a commonly-missed corner case with `await` inside `**`

I recently discovered an interesting discussion about JavaScript syntax entitled ["Most implementations seem to have missed that `await x ** 2` is not legal"](https://github.com/tc39/ecma262/issues/2197). Indeed esbuild has missed this, but this is not surprising because V8 has missed this as well and I usually test esbuild against V8 to test if esbuild is conformant with the JavaScript standard. Regardless, it sounds like the result of the discussion is that the specification should stay the same and implementations should be fixed. This release fixes this bug in esbuild's parser. The syntax `await x ** 2` is no longer allowed and parentheses are now preserved for the syntax `(await x) ** 2`.

## 0.8.34

* Fix a parser bug about suffix expressions after an arrow function body ([#701](https://github.com/evanw/esbuild/issues/701))
Expand Down
6 changes: 5 additions & 1 deletion internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2375,7 +2375,11 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF
if p.fnOrArrowDataParse.arrowArgErrors != nil {
p.fnOrArrowDataParse.arrowArgErrors.invalidExprAwait = nameRange
}
return js_ast.Expr{Loc: loc, Data: &js_ast.EAwait{Value: p.parseExpr(js_ast.LPrefix)}}
value := p.parseExpr(js_ast.LPrefix)
if p.lexer.Token == js_lexer.TAsteriskAsterisk {
p.lexer.Unexpected()
}
return js_ast.Expr{Loc: loc, Data: &js_ast.EAwait{Value: value}}
}
}

Expand Down
43 changes: 43 additions & 0 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,49 @@ func TestExponentiation(t *testing.T) {
expectParseError(t, "void x.y() ** 2", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "delete x.y() ** 2", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "typeof x.y() ** 2", "<stdin>: error: Unexpected \"**\"\n")

// https://github.com/tc39/ecma262/issues/2197
expectParseError(t, "delete x ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "delete x.prop ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "delete x[0] ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "delete x?.prop ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "void x ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "typeof x ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "+x ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "-x ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "~x ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "!x ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "await x ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectParseError(t, "await -x ** 0", "<stdin>: error: Unexpected \"**\"\n")
expectPrinted(t, "(delete x) ** 0", "(delete x) ** 0;\n")
expectPrinted(t, "(delete x.prop) ** 0", "(delete x.prop) ** 0;\n")
expectPrinted(t, "(delete x[0]) ** 0", "(delete x[0]) ** 0;\n")
expectPrinted(t, "(delete x?.prop) ** 0", "(delete x?.prop) ** 0;\n")
expectPrinted(t, "(void x) ** 0", "(void x) ** 0;\n")
expectPrinted(t, "(typeof x) ** 0", "(typeof x) ** 0;\n")
expectPrinted(t, "(+x) ** 0", "(+x) ** 0;\n")
expectPrinted(t, "(-x) ** 0", "(-x) ** 0;\n")
expectPrinted(t, "(~x) ** 0", "(~x) ** 0;\n")
expectPrinted(t, "(!x) ** 0", "(!x) ** 0;\n")
expectPrinted(t, "(await x) ** 0", "(await x) ** 0;\n")
expectPrinted(t, "(await -x) ** 0", "(await -x) ** 0;\n")
}

func TestAwait(t *testing.T) {
expectPrinted(t, "await x", "await x;\n")
expectPrinted(t, "await +x", "await +x;\n")
expectPrinted(t, "await -x", "await -x;\n")
expectPrinted(t, "await ~x", "await ~x;\n")
expectPrinted(t, "await !x", "await !x;\n")
expectPrinted(t, "await --x", "await --x;\n")
expectPrinted(t, "await ++x", "await ++x;\n")
expectPrinted(t, "await x--", "await x--;\n")
expectPrinted(t, "await x++", "await x++;\n")
expectPrinted(t, "await void x", "await void x;\n")
expectPrinted(t, "await delete x", "await delete x;\n")
expectPrinted(t, "await typeof x", "await typeof x;\n")
expectPrinted(t, "await (x * y)", "await (x * y);\n")
expectPrinted(t, "await (x ** y)", "await (x ** y);\n")
}

func TestRegExp(t *testing.T) {
Expand Down
6 changes: 6 additions & 0 deletions internal/js_parser/ts_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,12 @@ func TestTSNew(t *testing.T) {
expectParseError(t, "new Foo!()", "<stdin>: error: Unexpected \"!\"\n")
}

func TestTSExponentiation(t *testing.T) {
// More info: https://github.com/microsoft/TypeScript/issues/41755
expectParseErrorTS(t, "await x! ** 2", "<stdin>: error: Unexpected \"**\"\n")
expectPrintedTS(t, "await x as any ** 2", "(await x) ** 2;\n")
}

func TestTSImport(t *testing.T) {
expectPrintedTS(t, "import {x} from 'foo'", "")
expectPrintedTS(t, "import {x} from 'foo'; log(x)", "import {x} from \"foo\";\nlog(x);\n")
Expand Down
4 changes: 3 additions & 1 deletion internal/js_printer/js_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -1939,7 +1939,7 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags int) {
p.printSpaceBeforeIdentifier()
p.print("await")
p.printSpace()
p.printExpr(e.Value, js_ast.LPrefix, 0)
p.printExpr(e.Value, js_ast.LPrefix-1, 0)

if wrap {
p.print(")")
Expand Down Expand Up @@ -2038,6 +2038,8 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags int) {
// "**" can't contain certain unary expressions
if left, ok := e.Left.Data.(*js_ast.EUnary); ok && left.Op.UnaryAssignTarget() == js_ast.AssignTargetNone {
leftLevel = js_ast.LCall
} else if _, ok := e.Left.Data.(*js_ast.EAwait); ok {
leftLevel = js_ast.LCall
} else if _, ok := e.Left.Data.(*js_ast.EUndefined); ok {
// Undefined is printed as "void 0"
leftLevel = js_ast.LCall
Expand Down

0 comments on commit 39c4cc1

Please sign in to comment.