Skip to content

Commit

Permalink
Enhance delayed destructuring error tracking to catch more cases.
Browse files Browse the repository at this point in the history
  • Loading branch information
RReverser committed Nov 4, 2015
1 parent c848999 commit c9f83aa
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 135 deletions.
91 changes: 47 additions & 44 deletions src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ pp.checkPropClash = function(prop, propHash) {
// and object pattern might appear (so it's possible to raise
// delayed syntax error at correct position).

pp.parseExpression = function(noIn, refShorthandDefaultPos) {
pp.parseExpression = function(noIn, refDestructuringErrors) {
let startPos = this.start, startLoc = this.startLoc
let expr = this.parseMaybeAssign(noIn, refShorthandDefaultPos)
let expr = this.parseMaybeAssign(noIn, refDestructuringErrors)
if (this.type === tt.comma) {
let node = this.startNodeAt(startPos, startLoc)
node.expressions = [expr]
while (this.eat(tt.comma)) node.expressions.push(this.parseMaybeAssign(noIn, refShorthandDefaultPos))
while (this.eat(tt.comma)) node.expressions.push(this.parseMaybeAssign(noIn, refDestructuringErrors))
return this.finishNode(node, "SequenceExpression")
}
return expr
Expand All @@ -89,42 +89,41 @@ pp.parseExpression = function(noIn, refShorthandDefaultPos) {
// Parse an assignment expression. This includes applications of
// operators like `+=`.

pp.parseMaybeAssign = function(noIn, refShorthandDefaultPos, afterLeftParse) {
pp.parseMaybeAssign = function(noIn, refDestructuringErrors, afterLeftParse) {
if (this.type == tt._yield && this.inGenerator) return this.parseYield()

let failOnShorthandAssign
if (!refShorthandDefaultPos) {
refShorthandDefaultPos = {start: 0}
failOnShorthandAssign = true
} else {
failOnShorthandAssign = false
let validateDestructuring = false
if (!refDestructuringErrors) {
refDestructuringErrors = {shorthandAssign: 0, trailingComma: 0}
validateDestructuring = true
}
let startPos = this.start, startLoc = this.startLoc
if (this.type == tt.parenL || this.type == tt.name)
this.potentialArrowAt = this.start
let left = this.parseMaybeConditional(noIn, refShorthandDefaultPos)
let left = this.parseMaybeConditional(noIn, refDestructuringErrors)
if (afterLeftParse) left = afterLeftParse.call(this, left, startPos, startLoc)
if (this.type.isAssign) {
if (validateDestructuring) this.checkPatternErrors(refDestructuringErrors, true)
let node = this.startNodeAt(startPos, startLoc)
node.operator = this.value
node.left = this.type === tt.eq ? this.toAssignable(left) : left
refShorthandDefaultPos.start = 0 // reset because shorthand default was used correctly
refDestructuringErrors.shorthandAssign = 0 // reset because shorthand default was used correctly
this.checkLVal(left)
this.next()
node.right = this.parseMaybeAssign(noIn)
return this.finishNode(node, "AssignmentExpression")
} else if (failOnShorthandAssign && refShorthandDefaultPos.start) {
this.unexpected(refShorthandDefaultPos.start)
} else {
if (validateDestructuring) this.checkExpressionErrors(refDestructuringErrors, true)
}
return left
}

// Parse a ternary conditional (`?:`) operator.

pp.parseMaybeConditional = function(noIn, refShorthandDefaultPos) {
pp.parseMaybeConditional = function(noIn, refDestructuringErrors) {
let startPos = this.start, startLoc = this.startLoc
let expr = this.parseExprOps(noIn, refShorthandDefaultPos)
if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr
let expr = this.parseExprOps(noIn, refDestructuringErrors)
if (this.checkExpressionErrors(refDestructuringErrors)) return expr
if (this.eat(tt.question)) {
let node = this.startNodeAt(startPos, startLoc)
node.test = expr
Expand All @@ -138,10 +137,10 @@ pp.parseMaybeConditional = function(noIn, refShorthandDefaultPos) {

// Start the precedence parser.

pp.parseExprOps = function(noIn, refShorthandDefaultPos) {
pp.parseExprOps = function(noIn, refDestructuringErrors) {
let startPos = this.start, startLoc = this.startLoc
let expr = this.parseMaybeUnary(refShorthandDefaultPos)
if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr
let expr = this.parseMaybeUnary(refDestructuringErrors)
if (this.checkExpressionErrors(refDestructuringErrors)) return expr
return this.parseExprOp(expr, startPos, startLoc, -1, noIn)
}

Expand Down Expand Up @@ -171,23 +170,23 @@ pp.parseExprOp = function(left, leftStartPos, leftStartLoc, minPrec, noIn) {

// Parse unary operators, both prefix and postfix.

pp.parseMaybeUnary = function(refShorthandDefaultPos) {
pp.parseMaybeUnary = function(refDestructuringErrors) {
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()
if (refShorthandDefaultPos && refShorthandDefaultPos.start) this.unexpected(refShorthandDefaultPos.start)
this.checkExpressionErrors(refDestructuringErrors, true)
if (update) this.checkLVal(node.argument)
else if (this.strict && node.operator === "delete" &&
node.argument.type === "Identifier")
this.raise(node.start, "Deleting local variable in strict mode")
return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression")
}
let startPos = this.start, startLoc = this.startLoc
let expr = this.parseExprSubscripts(refShorthandDefaultPos)
if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr
let expr = this.parseExprSubscripts(refDestructuringErrors)
if (this.checkExpressionErrors(refDestructuringErrors)) return expr
while (this.type.postfix && !this.canInsertSemicolon()) {
let node = this.startNodeAt(startPos, startLoc)
node.operator = this.value
Expand All @@ -202,11 +201,11 @@ pp.parseMaybeUnary = function(refShorthandDefaultPos) {

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

pp.parseExprSubscripts = function(refShorthandDefaultPos) {
pp.parseExprSubscripts = function(refDestructuringErrors) {
let startPos = this.start, startLoc = this.startLoc
let expr = this.parseExprAtom(refShorthandDefaultPos)
let expr = this.parseExprAtom(refDestructuringErrors)
let skipArrowSubscripts = expr.type === "ArrowFunctionExpression" && this.input.slice(this.lastTokStart, this.lastTokEnd) !== ")";
if ((refShorthandDefaultPos && refShorthandDefaultPos.start) || skipArrowSubscripts) return expr
if (this.checkExpressionErrors(refDestructuringErrors) || skipArrowSubscripts) return expr
return this.parseSubscripts(expr, startPos, startLoc)
}

Expand Down Expand Up @@ -246,7 +245,7 @@ pp.parseSubscripts = function(base, startPos, startLoc, noCalls) {
// `new`, or an expression wrapped in punctuation like `()`, `[]`,
// or `{}`.

pp.parseExprAtom = function(refShorthandDefaultPos) {
pp.parseExprAtom = function(refDestructuringErrors) {
let node, canBeArrow = this.potentialArrowAt == this.start
switch (this.type) {
case tt._super:
Expand Down Expand Up @@ -294,11 +293,11 @@ pp.parseExprAtom = function(refShorthandDefaultPos) {
if (this.options.ecmaVersion >= 7 && this.type === tt._for) {
return this.parseComprehension(node, false)
}
node.elements = this.parseExprList(tt.bracketR, true, true, refShorthandDefaultPos)
node.elements = this.parseExprList(tt.bracketR, true, true, refDestructuringErrors)
return this.finishNode(node, "ArrayExpression")

case tt.braceL:
return this.parseObj(false, refShorthandDefaultPos)
return this.parseObj(false, refDestructuringErrors)

case tt._function:
node = this.startNode()
Expand Down Expand Up @@ -345,7 +344,7 @@ pp.parseParenAndDistinguishExpression = function(canBeArrow) {

let innerStartPos = this.start, innerStartLoc = this.startLoc
let exprList = [], first = true
let refShorthandDefaultPos = {start: 0}, spreadStart, innerParenStart
let refDestructuringErrors = {shorthandAssign: 0, trailingComma: 0}, spreadStart, innerParenStart
while (this.type !== tt.parenR) {
first ? first = false : this.expect(tt.comma)
if (this.type === tt.ellipsis) {
Expand All @@ -356,20 +355,21 @@ pp.parseParenAndDistinguishExpression = function(canBeArrow) {
if (this.type === tt.parenL && !innerParenStart) {
innerParenStart = this.start
}
exprList.push(this.parseMaybeAssign(false, refShorthandDefaultPos, this.parseParenItem))
exprList.push(this.parseMaybeAssign(false, refDestructuringErrors, this.parseParenItem))
}
}
let innerEndPos = this.start, innerEndLoc = this.startLoc
this.expect(tt.parenR)

if (canBeArrow && !this.canInsertSemicolon() && this.eat(tt.arrow)) {
this.checkPatternErrors(refDestructuringErrors, true)
if (innerParenStart) this.unexpected(innerParenStart)
return this.parseParenArrowList(startPos, startLoc, exprList)
}

if (!exprList.length) this.unexpected(this.lastTokStart)
if (spreadStart) this.unexpected(spreadStart)
if (refShorthandDefaultPos.start) this.unexpected(refShorthandDefaultPos.start)
this.checkExpressionErrors(refDestructuringErrors, true)

if (exprList.length > 1) {
val = this.startNodeAt(innerStartPos, innerStartLoc)
Expand Down Expand Up @@ -455,7 +455,7 @@ pp.parseTemplate = function() {

// Parse an object literal or binding pattern.

pp.parseObj = function(isPattern, refShorthandDefaultPos) {
pp.parseObj = function(isPattern, refDestructuringErrors) {
let node = this.startNode(), first = true, propHash = {}
node.properties = []
this.next()
Expand All @@ -469,24 +469,24 @@ pp.parseObj = function(isPattern, refShorthandDefaultPos) {
if (this.options.ecmaVersion >= 6) {
prop.method = false
prop.shorthand = false
if (isPattern || refShorthandDefaultPos) {
if (isPattern || refDestructuringErrors) {
startPos = this.start
startLoc = this.startLoc
}
if (!isPattern)
isGenerator = this.eat(tt.star)
}
this.parsePropertyName(prop)
this.parsePropertyValue(prop, isPattern, isGenerator, startPos, startLoc, refShorthandDefaultPos)
this.parsePropertyValue(prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors)
this.checkPropClash(prop, propHash)
node.properties.push(this.finishNode(prop, "Property"))
}
return this.finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression")
}

pp.parsePropertyValue = function(prop, isPattern, isGenerator, startPos, startLoc, refShorthandDefaultPos) {
pp.parsePropertyValue = function(prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors) {
if (this.eat(tt.colon)) {
prop.value = isPattern ? this.parseMaybeDefault(this.start, this.startLoc) : this.parseMaybeAssign(false, refShorthandDefaultPos)
prop.value = isPattern ? this.parseMaybeDefault(this.start, this.startLoc) : this.parseMaybeAssign(false, refDestructuringErrors)
prop.kind = "init"
} else if (this.options.ecmaVersion >= 6 && this.type === tt.parenL) {
if (isPattern) this.unexpected()
Expand Down Expand Up @@ -515,9 +515,9 @@ pp.parsePropertyValue = function(prop, isPattern, isGenerator, startPos, startLo
(this.strict ? this.reservedWordsStrictBind : this.reservedWords).test(prop.key.name))
this.raise(prop.key.start, "Binding " + prop.key.name)
prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key)
} else if (this.type === tt.eq && refShorthandDefaultPos) {
if (!refShorthandDefaultPos.start)
refShorthandDefaultPos.start = this.start
} else if (this.type === tt.eq && refDestructuringErrors) {
if (!refDestructuringErrors.shorthandAssign)
refDestructuringErrors.shorthandAssign = this.start
prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key)
} else {
prop.value = prop.key
Expand Down Expand Up @@ -620,21 +620,24 @@ pp.checkParams = function(node) {
// nothing in between them to be parsed as `null` (which is needed
// for array literals).

pp.parseExprList = function(close, allowTrailingComma, allowEmpty, refShorthandDefaultPos) {
pp.parseExprList = function(close, allowTrailingComma, allowEmpty, refDestructuringErrors) {
let elts = [], first = true
while (!this.eat(close)) {
if (!first) {
this.expect(tt.comma)
if (this.type === close && refDestructuringErrors && !refDestructuringErrors.trailingComma) {
refDestructuringErrors.trailingComma = this.lastTokStart
}
if (allowTrailingComma && this.afterTrailingComma(close)) break
} else first = false

let elt
if (allowEmpty && this.type === tt.comma)
elt = null
else if (this.type === tt.ellipsis)
elt = this.parseSpread(refShorthandDefaultPos)
elt = this.parseSpread(refDestructuringErrors)
else
elt = this.parseMaybeAssign(false, refShorthandDefaultPos)
elt = this.parseMaybeAssign(false, refDestructuringErrors)
elts.push(elt)
}
return elts
Expand Down
4 changes: 2 additions & 2 deletions src/lval.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ pp.toAssignableList = function(exprList, isBinding) {

// Parses spread element.

pp.parseSpread = function(refShorthandDefaultPos) {
pp.parseSpread = function(refDestructuringErrors) {
let node = this.startNode()
this.next()
node.argument = this.parseMaybeAssign(refShorthandDefaultPos)
node.argument = this.parseMaybeAssign(refDestructuringErrors)
return this.finishNode(node, "SpreadElement")
}

Expand Down
12 changes: 12 additions & 0 deletions src/parseutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,15 @@ pp.expect = function(type) {
pp.unexpected = function(pos) {
this.raise(pos != null ? pos : this.start, "Unexpected token")
}

pp.checkPatternErrors = function(refDestructuringErrors, andThrow) {
let pos = refDestructuringErrors && refDestructuringErrors.trailingComma
if (!andThrow) return !!pos
if (pos) this.raise(pos, "Trailing comma is not permitted in destructuring patterns")
}

pp.checkExpressionErrors = function(refDestructuringErrors, andThrow) {
let pos = refDestructuringErrors && refDestructuringErrors.shorthandAssign
if (!andThrow) return !!pos
if (pos) this.raise(pos, "Shorthand property assignments are valid only in destructuring patterns")
}
9 changes: 5 additions & 4 deletions src/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,15 @@ pp.parseForStatement = function(node) {
return this.parseForIn(node, init)
return this.parseFor(node, init)
}
let refShorthandDefaultPos = {start: 0}
let init = this.parseExpression(true, refShorthandDefaultPos)
let refDestructuringErrors = {shorthandAssign: 0, trailingComma: 0}
let init = this.parseExpression(true, refDestructuringErrors)
if (this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of"))) {
this.checkPatternErrors(refDestructuringErrors, true)
this.toAssignable(init)
this.checkLVal(init)
return this.parseForIn(node, init)
} else if (refShorthandDefaultPos.start) {
this.unexpected(refShorthandDefaultPos.start)
} else {
this.checkExpressionErrors(refDestructuringErrors, true)
}
return this.parseFor(node, init)
}
Expand Down
Loading

0 comments on commit c9f83aa

Please sign in to comment.