Skip to content

Commit

Permalink
forbid "let" starting a for-of loop initializer
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Feb 18, 2021
1 parent 73f0e70 commit 4cf1446
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

* Code of the form `new async () => {}` must not be allowed. Previously this was incorrectly allowed since the speculative parsing of asynchronous arrow functions did not check the precedence level.

* It's not valid to start an initializer expression in a for-of loop with the token `let` such as `for (let.foo of bar) {}`. This is now forbidden. In addition, the code generator now respects this rule so `for ((let.foo) of bar) {}` is now printed as `for ((let).foo of bar) {}`.

## 0.8.47

* Release native binaries for the Apple M1 chip ([#550](https://github.com/evanw/esbuild/issues/550))
Expand Down
8 changes: 8 additions & 0 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5502,6 +5502,10 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
// "in" expressions aren't allowed here
p.allowIn = false

var badLetRange logger.Range
if p.lexer.IsContextualKeyword("let") {
badLetRange = p.lexer.Range()
}
decls := []js_ast.Decl{}
initLoc := p.lexer.Loc()
isVar := false
Expand All @@ -5525,6 +5529,7 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {
var stmt js_ast.Stmt
expr, stmt, decls = p.parseExprOrLetStmt(parseStmtOpts{lexicalDecl: lexicalDeclAllowAll})
if stmt.Data != nil {
badLetRange = logger.Range{}
init = &stmt
} else {
init = &js_ast.Stmt{Loc: initLoc, Data: &js_ast.SExpr{Value: expr}}
Expand All @@ -5536,6 +5541,9 @@ func (p *parser) parseStmt(opts parseStmtOpts) js_ast.Stmt {

// Detect for-of loops
if p.lexer.IsContextualKeyword("of") || isForAwait {
if badLetRange.Len > 0 {
p.log.AddRangeError(&p.source, badLetRange, "\"let\" must be wrapped in parentheses to be used as an expression here")
}
if isForAwait && !p.lexer.IsContextualKeyword("of") {
if init != nil {
p.lexer.ExpectedString("\"of\"")
Expand Down
9 changes: 9 additions & 0 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,15 @@ func TestFor(t *testing.T) {
expectParseError(t, "for (let a, b of b) ;", "<stdin>: error: for-of loops must have a single declaration\n")
expectParseError(t, "for (const a, b of b) ;", "<stdin>: error: for-of loops must have a single declaration\n")

// Avoid the initializer starting with "let" token
expectPrinted(t, "for ((let) of bar);", "for ((let) of bar)\n ;\n")
expectPrinted(t, "for ((let).foo of bar);", "for ((let).foo of bar)\n ;\n")
expectPrinted(t, "for ((let.foo) of bar);", "for ((let).foo of bar)\n ;\n")
expectPrinted(t, "for ((let``.foo) of bar);", "for ((let)``.foo of bar)\n ;\n")
expectParseError(t, "for (let.foo of bar);", "<stdin>: error: \"let\" must be wrapped in parentheses to be used as an expression here\n")
expectParseError(t, "for (let().foo of bar);", "<stdin>: error: \"let\" must be wrapped in parentheses to be used as an expression here\n")
expectParseError(t, "for (let``.foo of bar);", "<stdin>: error: \"let\" must be wrapped in parentheses to be used as an expression here\n")

expectPrinted(t, "for (var x = 0 in y) ;", "x = 0;\nfor (var x in y)\n ;\n") // This is a weird special-case
expectParseError(t, "for (let x = 0 in y) ;", "<stdin>: error: for-in loop variables cannot have an initializer\n")
expectParseError(t, "for (const x = 0 in y) ;", "<stdin>: error: for-in loop variables cannot have an initializer\n")
Expand Down
16 changes: 15 additions & 1 deletion internal/js_printer/js_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ type printer struct {
stmtStart int
exportDefaultStart int
arrowExprStart int
forOfInitStart int
prevOp js_ast.OpCode
prevOpEnd int
prevNumEnd int
Expand Down Expand Up @@ -1904,8 +1905,19 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags int) {
}

case *js_ast.EIdentifier:
name := p.renamer.NameForSymbol(e.Ref)
wrap := len(p.js) == p.forOfInitStart && name == "let"

if wrap {
p.print("(")
}

p.printSpaceBeforeIdentifier()
p.printSymbol(e.Ref)
p.printIdentifier(name)

if wrap {
p.print(")")
}

case *js_ast.EImportIdentifier:
// Potentially use a property access instead of an identifier
Expand Down Expand Up @@ -2711,6 +2723,7 @@ func (p *printer) printStmt(stmt js_ast.Stmt) {
}
p.printSpace()
p.print("(")
p.forOfInitStart = len(p.js)
p.printForLoopInit(s.Init)
p.printSpace()
p.printSpaceBeforeIdentifier()
Expand Down Expand Up @@ -3057,6 +3070,7 @@ func Print(tree js_ast.AST, symbols js_ast.SymbolMap, r renamer.Renamer, options
stmtStart: -1,
exportDefaultStart: -1,
arrowExprStart: -1,
forOfInitStart: -1,
prevOpEnd: -1,
prevNumEnd: -1,
prevRegExpEnd: -1,
Expand Down

0 comments on commit 4cf1446

Please sign in to comment.