Skip to content

Commit

Permalink
Propagate forInit down to arrow functions
Browse files Browse the repository at this point in the history
FIX: Fix an issue where arrow function bodies in for loop context would inappropriately
consume `in` operators.

Issue #1058
Issue #838
Issue #922
  • Loading branch information
marijnh committed Aug 24, 2021
1 parent 06b9c8c commit f6f4250
Show file tree
Hide file tree
Showing 3 changed files with 288 additions and 34 deletions.
62 changes: 31 additions & 31 deletions acorn/src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ pp.parseMaybeConditional = function(forInit, refDestructuringErrors) {

pp.parseExprOps = function(forInit, refDestructuringErrors) {
let startPos = this.start, startLoc = this.startLoc
let expr = this.parseMaybeUnary(refDestructuringErrors, false)
let expr = this.parseMaybeUnary(refDestructuringErrors, false, false, forInit)
if (this.checkExpressionErrors(refDestructuringErrors)) return expr
return expr.start === startPos && expr.type === "ArrowFunctionExpression" ? expr : this.parseExprOp(expr, startPos, startLoc, -1, forInit)
}
Expand All @@ -202,7 +202,7 @@ pp.parseExprOp = function(left, leftStartPos, leftStartLoc, minPrec, forInit) {
let op = this.value
this.next()
let startPos = this.start, startLoc = this.startLoc
let right = this.parseExprOp(this.parseMaybeUnary(null, false), startPos, startLoc, prec, forInit)
let right = this.parseExprOp(this.parseMaybeUnary(null, false, false, forInit), startPos, startLoc, prec, forInit)
let node = this.buildBinary(leftStartPos, leftStartLoc, left, right, op, logical || coalesce)
if ((logical && this.type === tt.coalesce) || (coalesce && (this.type === tt.logicalOR || this.type === tt.logicalAND))) {
this.raiseRecoverable(this.start, "Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses")
Expand All @@ -223,17 +223,17 @@ pp.buildBinary = function(startPos, startLoc, left, right, op, logical) {

// Parse unary operators, both prefix and postfix.

pp.parseMaybeUnary = function(refDestructuringErrors, sawUnary, incDec) {
pp.parseMaybeUnary = function(refDestructuringErrors, sawUnary, incDec, forInit) {
let startPos = this.start, startLoc = this.startLoc, expr
if (this.isContextual("await") && this.canAwait) {
expr = this.parseAwait()
expr = this.parseAwait(forInit)
sawUnary = true
} else if (this.type.prefix) {
let node = this.startNode(), update = this.type === tt.incDec
node.operator = this.value
node.prefix = true
this.next()
node.argument = this.parseMaybeUnary(null, true, update)
node.argument = this.parseMaybeUnary(null, true, update, forInit)
this.checkExpressionErrors(refDestructuringErrors, true)
if (update) this.checkLValSimple(node.argument)
else if (this.strict && node.operator === "delete" &&
Expand All @@ -244,7 +244,7 @@ pp.parseMaybeUnary = function(refDestructuringErrors, sawUnary, incDec) {
else sawUnary = true
expr = this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression")
} else {
expr = this.parseExprSubscripts(refDestructuringErrors)
expr = this.parseExprSubscripts(refDestructuringErrors, forInit)
if (this.checkExpressionErrors(refDestructuringErrors)) return expr
while (this.type.postfix && !this.canInsertSemicolon()) {
let node = this.startNodeAt(startPos, startLoc)
Expand All @@ -261,7 +261,7 @@ pp.parseMaybeUnary = function(refDestructuringErrors, sawUnary, incDec) {
if (sawUnary)
this.unexpected(this.lastTokStart)
else
return this.buildBinary(startPos, startLoc, expr, this.parseMaybeUnary(null, false), "**", false)
return this.buildBinary(startPos, startLoc, expr, this.parseMaybeUnary(null, false, false, forInit), "**", false)
} else {
return expr
}
Expand All @@ -276,12 +276,12 @@ function isPrivateFieldAccess(node) {

// Parse call, dot, and `[]`-subscript expressions.

pp.parseExprSubscripts = function(refDestructuringErrors) {
pp.parseExprSubscripts = function(refDestructuringErrors, forInit) {
let startPos = this.start, startLoc = this.startLoc
let expr = this.parseExprAtom(refDestructuringErrors)
let expr = this.parseExprAtom(refDestructuringErrors, forInit)
if (expr.type === "ArrowFunctionExpression" && this.input.slice(this.lastTokStart, this.lastTokEnd) !== ")")
return expr
let result = this.parseSubscripts(expr, startPos, startLoc)
let result = this.parseSubscripts(expr, startPos, startLoc, false, forInit)
if (refDestructuringErrors && result.type === "MemberExpression") {
if (refDestructuringErrors.parenthesizedAssign >= result.start) refDestructuringErrors.parenthesizedAssign = -1
if (refDestructuringErrors.parenthesizedBind >= result.start) refDestructuringErrors.parenthesizedBind = -1
Expand All @@ -290,14 +290,14 @@ pp.parseExprSubscripts = function(refDestructuringErrors) {
return result
}

pp.parseSubscripts = function(base, startPos, startLoc, noCalls) {
pp.parseSubscripts = function(base, startPos, startLoc, noCalls, forInit) {
let maybeAsyncArrow = this.options.ecmaVersion >= 8 && base.type === "Identifier" && base.name === "async" &&
this.lastTokEnd === base.end && !this.canInsertSemicolon() && base.end - base.start === 5 &&
this.potentialArrowAt === base.start
let optionalChained = false

while (true) {
let element = this.parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow, optionalChained)
let element = this.parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow, optionalChained, forInit)

if (element.optional) optionalChained = true
if (element === base || element.type === "ArrowFunctionExpression") {
Expand All @@ -313,7 +313,7 @@ pp.parseSubscripts = function(base, startPos, startLoc, noCalls) {
}
}

pp.parseSubscript = function(base, startPos, startLoc, noCalls, maybeAsyncArrow, optionalChained) {
pp.parseSubscript = function(base, startPos, startLoc, noCalls, maybeAsyncArrow, optionalChained, forInit) {
let optionalSupported = this.options.ecmaVersion >= 11
let optional = optionalSupported && this.eat(tt.questionDot)
if (noCalls && optional) this.raise(this.lastTokStart, "Optional chaining cannot appear in the callee of new expressions")
Expand Down Expand Up @@ -349,7 +349,7 @@ pp.parseSubscript = function(base, startPos, startLoc, noCalls, maybeAsyncArrow,
this.yieldPos = oldYieldPos
this.awaitPos = oldAwaitPos
this.awaitIdentPos = oldAwaitIdentPos
return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList, true)
return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList, true, forInit)
}
this.checkExpressionErrors(refDestructuringErrors, true)
this.yieldPos = oldYieldPos || this.yieldPos
Expand Down Expand Up @@ -379,7 +379,7 @@ pp.parseSubscript = function(base, startPos, startLoc, noCalls, maybeAsyncArrow,
// `new`, or an expression wrapped in punctuation like `()`, `[]`,
// or `{}`.

pp.parseExprAtom = function(refDestructuringErrors) {
pp.parseExprAtom = function(refDestructuringErrors, forInit) {
// If a division operator appears in an expression position, the
// tokenizer got confused, and we force it to read a regexp instead.
if (this.type === tt.slash) this.readRegexp()
Expand Down Expand Up @@ -412,16 +412,16 @@ pp.parseExprAtom = function(refDestructuringErrors) {
let startPos = this.start, startLoc = this.startLoc, containsEsc = this.containsEsc
let id = this.parseIdent(false)
if (this.options.ecmaVersion >= 8 && !containsEsc && id.name === "async" && !this.canInsertSemicolon() && this.eat(tt._function))
return this.parseFunction(this.startNodeAt(startPos, startLoc), 0, false, true)
return this.parseFunction(this.startNodeAt(startPos, startLoc), 0, false, true, forInit)
if (canBeArrow && !this.canInsertSemicolon()) {
if (this.eat(tt.arrow))
return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id], false)
return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id], false, forInit)
if (this.options.ecmaVersion >= 8 && id.name === "async" && this.type === tt.name && !containsEsc &&
(!this.potentialArrowInForAwait || this.value !== "of" || this.containsEsc)) {
id = this.parseIdent(false)
if (this.canInsertSemicolon() || !this.eat(tt.arrow))
this.unexpected()
return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id], true)
return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id], true, forInit)
}
}
return id
Expand All @@ -443,7 +443,7 @@ pp.parseExprAtom = function(refDestructuringErrors) {
return this.finishNode(node, "Literal")

case tt.parenL:
let start = this.start, expr = this.parseParenAndDistinguishExpression(canBeArrow)
let start = this.start, expr = this.parseParenAndDistinguishExpression(canBeArrow, forInit)
if (refDestructuringErrors) {
if (refDestructuringErrors.parenthesizedAssign < 0 && !this.isSimpleAssignTarget(expr))
refDestructuringErrors.parenthesizedAssign = start
Expand Down Expand Up @@ -557,7 +557,7 @@ pp.parseParenExpression = function() {
return val
}

pp.parseParenAndDistinguishExpression = function(canBeArrow) {
pp.parseParenAndDistinguishExpression = function(canBeArrow, forInit) {
let startPos = this.start, startLoc = this.startLoc, val, allowTrailingComma = this.options.ecmaVersion >= 8
if (this.options.ecmaVersion >= 6) {
this.next()
Expand Down Expand Up @@ -590,7 +590,7 @@ pp.parseParenAndDistinguishExpression = function(canBeArrow) {
this.checkYieldAwaitInDefaultParams()
this.yieldPos = oldYieldPos
this.awaitPos = oldAwaitPos
return this.parseParenArrowList(startPos, startLoc, exprList)
return this.parseParenArrowList(startPos, startLoc, exprList, forInit)
}

if (!exprList.length || lastIsComma) this.unexpected(this.lastTokStart)
Expand Down Expand Up @@ -623,8 +623,8 @@ pp.parseParenItem = function(item) {
return item
}

pp.parseParenArrowList = function(startPos, startLoc, exprList) {
return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList)
pp.parseParenArrowList = function(startPos, startLoc, exprList, forInit) {
return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList, forInit)
}

// New's precedence is slightly tricky. It must allow its argument to
Expand Down Expand Up @@ -652,7 +652,7 @@ pp.parseNew = function() {
return this.finishNode(node, "MetaProperty")
}
let startPos = this.start, startLoc = this.startLoc, isImport = this.type === tt._import
node.callee = this.parseSubscripts(this.parseExprAtom(), startPos, startLoc, true)
node.callee = this.parseSubscripts(this.parseExprAtom(), startPos, startLoc, true, false)
if (isImport && node.callee.type === "ImportExpression") {
this.raise(startPos, "Cannot use new with import()")
}
Expand Down Expand Up @@ -868,7 +868,7 @@ pp.parseMethod = function(isGenerator, isAsync, allowDirectSuper) {
this.expect(tt.parenL)
node.params = this.parseBindingList(tt.parenR, false, this.options.ecmaVersion >= 8)
this.checkYieldAwaitInDefaultParams()
this.parseFunctionBody(node, false, true)
this.parseFunctionBody(node, false, true, false)

this.yieldPos = oldYieldPos
this.awaitPos = oldAwaitPos
Expand All @@ -878,7 +878,7 @@ pp.parseMethod = function(isGenerator, isAsync, allowDirectSuper) {

// Parse arrow function expression with given parameters.

pp.parseArrowExpression = function(node, params, isAsync) {
pp.parseArrowExpression = function(node, params, isAsync, forInit) {
let oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldAwaitIdentPos = this.awaitIdentPos

this.enterScope(functionFlags(isAsync, false) | SCOPE_ARROW)
Expand All @@ -890,7 +890,7 @@ pp.parseArrowExpression = function(node, params, isAsync) {
this.awaitIdentPos = 0

node.params = this.toAssignableList(params, true)
this.parseFunctionBody(node, true, false)
this.parseFunctionBody(node, true, false, forInit)

this.yieldPos = oldYieldPos
this.awaitPos = oldAwaitPos
Expand All @@ -900,12 +900,12 @@ pp.parseArrowExpression = function(node, params, isAsync) {

// Parse function body and check parameters.

pp.parseFunctionBody = function(node, isArrowFunction, isMethod) {
pp.parseFunctionBody = function(node, isArrowFunction, isMethod, forInit) {
let isExpression = isArrowFunction && this.type !== tt.braceL
let oldStrict = this.strict, useStrict = false

if (isExpression) {
node.body = this.parseMaybeAssign()
node.body = this.parseMaybeAssign(forInit)
node.expression = true
this.checkParams(node, false)
} else {
Expand Down Expand Up @@ -1069,11 +1069,11 @@ pp.parseYield = function(forInit) {
return this.finishNode(node, "YieldExpression")
}

pp.parseAwait = function() {
pp.parseAwait = function(forInit) {
if (!this.awaitPos) this.awaitPos = this.start

let node = this.startNode()
this.next()
node.argument = this.parseMaybeUnary(null, true)
node.argument = this.parseMaybeUnary(null, true, false, forInit)
return this.finishNode(node, "AwaitExpression")
}
6 changes: 3 additions & 3 deletions acorn/src/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ const FUNC_STATEMENT = 1, FUNC_HANGING_STATEMENT = 2, FUNC_NULLABLE_ID = 4
// `statement & FUNC_STATEMENT`).

// Remove `allowExpressionBody` for 7.0.0, as it is only called with false
pp.parseFunction = function(node, statement, allowExpressionBody, isAsync) {
pp.parseFunction = function(node, statement, allowExpressionBody, isAsync, forInit) {
this.initFunction(node)
if (this.options.ecmaVersion >= 9 || this.options.ecmaVersion >= 6 && !isAsync) {
if (this.type === tt.star && (statement & FUNC_HANGING_STATEMENT))
Expand Down Expand Up @@ -551,7 +551,7 @@ pp.parseFunction = function(node, statement, allowExpressionBody, isAsync) {
node.id = this.type === tt.name ? this.parseIdent() : null

this.parseFunctionParams(node)
this.parseFunctionBody(node, allowExpressionBody, false)
this.parseFunctionBody(node, allowExpressionBody, false, forInit)

this.yieldPos = oldYieldPos
this.awaitPos = oldAwaitPos
Expand Down Expand Up @@ -751,7 +751,7 @@ pp.parseClassId = function(node, isStatement) {
}

pp.parseClassSuper = function(node) {
node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null
node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts(false) : null
}

pp.enterClassBody = function() {
Expand Down
Loading

0 comments on commit f6f4250

Please sign in to comment.