From 2e9b91b4005bedabe02853f3a23ee9bc05dfccfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Sat, 11 Jan 2020 16:34:46 -0500 Subject: [PATCH 1/4] refactor: replace refShorthandDefaultPos by refExpressionErrors --- .../babel-parser/src/parser/expression.js | 115 +++++++++--------- packages/babel-parser/src/parser/lval.js | 9 +- packages/babel-parser/src/parser/statement.js | 9 +- packages/babel-parser/src/parser/util.js | 22 ++++ packages/babel-parser/src/plugins/estree.js | 11 +- packages/babel-parser/src/plugins/flow.js | 13 +- .../babel-parser/src/plugins/jsx/index.js | 7 +- .../src/plugins/typescript/index.js | 5 +- 8 files changed, 110 insertions(+), 81 deletions(-) diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index d88bf86266d0..c841935a51f8 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -41,6 +41,7 @@ import { SCOPE_PROGRAM, SCOPE_ASYNC, } from "../util/scopeflags"; +import { ExpressionErrors } from "./util"; export default class ExpressionParser extends LValParser { // Forward-declaration: defined in statement.js @@ -127,17 +128,18 @@ export default class ExpressionParser extends LValParser { // and object pattern might appear (so it's possible to raise // delayed syntax error at correct position). - parseExpression(noIn?: boolean, refShorthandDefaultPos?: Pos): N.Expression { + parseExpression( + noIn?: boolean, + refExpressionErrors?: ExpressionErrors, + ): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; - const expr = this.parseMaybeAssign(noIn, refShorthandDefaultPos); + const expr = this.parseMaybeAssign(noIn, refExpressionErrors); if (this.match(tt.comma)) { const node = this.startNodeAt(startPos, startLoc); node.expressions = [expr]; while (this.eat(tt.comma)) { - node.expressions.push( - this.parseMaybeAssign(noIn, refShorthandDefaultPos), - ); + node.expressions.push(this.parseMaybeAssign(noIn, refExpressionErrors)); } this.toReferencedList(node.expressions); return this.finishNode(node, "SequenceExpression"); @@ -150,7 +152,7 @@ export default class ExpressionParser extends LValParser { parseMaybeAssign( noIn?: ?boolean, - refShorthandDefaultPos?: ?Pos, + refExpressionErrors?: ?ExpressionErrors, afterLeftParse?: Function, refNeedsArrowPos?: ?Pos, ): N.Expression { @@ -170,12 +172,12 @@ export default class ExpressionParser extends LValParser { } } - let failOnShorthandAssign; - if (refShorthandDefaultPos) { - failOnShorthandAssign = false; + let ownExpressionErrors; + if (refExpressionErrors) { + ownExpressionErrors = false; } else { - refShorthandDefaultPos = { start: 0 }; - failOnShorthandAssign = true; + refExpressionErrors = new ExpressionErrors(); + ownExpressionErrors = true; } if (this.match(tt.parenL) || this.match(tt.name)) { @@ -184,7 +186,7 @@ export default class ExpressionParser extends LValParser { let left = this.parseMaybeConditional( noIn, - refShorthandDefaultPos, + refExpressionErrors, refNeedsArrowPos, ); if (afterLeftParse) { @@ -205,8 +207,8 @@ export default class ExpressionParser extends LValParser { ? this.toAssignable(left, undefined, "assignment expression") : left; - if (refShorthandDefaultPos.start >= node.left.start) { - refShorthandDefaultPos.start = 0; // reset because shorthand default was used correctly + if (refExpressionErrors.shorthandAssign >= node.left.start) { + refExpressionErrors.shorthandAssign = -1; // reset because shorthand default was used correctly } this.checkLVal(left, undefined, undefined, "assignment expression"); @@ -214,8 +216,8 @@ export default class ExpressionParser extends LValParser { this.next(); node.right = this.parseMaybeAssign(noIn); return this.finishNode(node, "AssignmentExpression"); - } else if (failOnShorthandAssign && refShorthandDefaultPos.start) { - this.unexpected(refShorthandDefaultPos.start); + } else if (ownExpressionErrors) { + this.checkExpressionErrors(refExpressionErrors, true); } return left; @@ -225,13 +227,13 @@ export default class ExpressionParser extends LValParser { parseMaybeConditional( noIn: ?boolean, - refShorthandDefaultPos: Pos, + refExpressionErrors: ExpressionErrors, refNeedsArrowPos?: ?Pos, ): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; const potentialArrowAt = this.state.potentialArrowAt; - const expr = this.parseExprOps(noIn, refShorthandDefaultPos); + const expr = this.parseExprOps(noIn, refExpressionErrors); if ( expr.type === "ArrowFunctionExpression" && @@ -239,7 +241,7 @@ export default class ExpressionParser extends LValParser { ) { return expr; } - if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr; + if (this.checkExpressionErrors(refExpressionErrors, false)) return expr; return this.parseConditional( expr, @@ -272,11 +274,14 @@ export default class ExpressionParser extends LValParser { // Start the precedence parser. - parseExprOps(noIn: ?boolean, refShorthandDefaultPos: Pos): N.Expression { + parseExprOps( + noIn: ?boolean, + refExpressionErrors: ExpressionErrors, + ): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; const potentialArrowAt = this.state.potentialArrowAt; - const expr = this.parseMaybeUnary(refShorthandDefaultPos); + const expr = this.parseMaybeUnary(refExpressionErrors); if ( expr.type === "ArrowFunctionExpression" && @@ -284,7 +289,7 @@ export default class ExpressionParser extends LValParser { ) { return expr; } - if (refShorthandDefaultPos && refShorthandDefaultPos.start) { + if (this.checkExpressionErrors(refExpressionErrors, false)) { return expr; } @@ -463,7 +468,7 @@ export default class ExpressionParser extends LValParser { // Parse unary operators, both prefix and postfix. - parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression { + parseMaybeUnary(refExpressionErrors: ?ExpressionErrors): N.Expression { if (this.isContextual("await") && this.isAwaitAllowed()) { return this.parseAwait(); } else if (this.state.type.prefix) { @@ -479,9 +484,7 @@ export default class ExpressionParser extends LValParser { node.argument = this.parseMaybeUnary(); - if (refShorthandDefaultPos && refShorthandDefaultPos.start) { - this.unexpected(refShorthandDefaultPos.start); - } + this.checkExpressionErrors(refExpressionErrors, true); if (update) { this.checkLVal(node.argument, undefined, undefined, "prefix operation"); @@ -506,8 +509,8 @@ export default class ExpressionParser extends LValParser { const startPos = this.state.start; const startLoc = this.state.startLoc; - let expr = this.parseExprSubscripts(refShorthandDefaultPos); - if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr; + let expr = this.parseExprSubscripts(refExpressionErrors); + if (this.checkExpressionErrors(refExpressionErrors, false)) return expr; while (this.state.type.postfix && !this.canInsertSemicolon()) { const node = this.startNodeAt(startPos, startLoc); node.operator = this.state.value; @@ -522,11 +525,11 @@ export default class ExpressionParser extends LValParser { // Parse call, dot, and `[]`-subscript expressions. - parseExprSubscripts(refShorthandDefaultPos: ?Pos): N.Expression { + parseExprSubscripts(refExpressionErrors: ?ExpressionErrors): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; const potentialArrowAt = this.state.potentialArrowAt; - const expr = this.parseExprAtom(refShorthandDefaultPos); + const expr = this.parseExprAtom(refExpressionErrors); if ( expr.type === "ArrowFunctionExpression" && @@ -535,7 +538,7 @@ export default class ExpressionParser extends LValParser { return expr; } - if (refShorthandDefaultPos && refShorthandDefaultPos.start) { + if (this.checkExpressionErrors(refExpressionErrors, false)) { return expr; } @@ -816,7 +819,7 @@ export default class ExpressionParser extends LValParser { elts.push( this.parseExprListItem( false, - possibleAsyncArrow ? { start: 0 } : undefined, + possibleAsyncArrow ? new ExpressionErrors() : undefined, possibleAsyncArrow ? { start: 0 } : undefined, allowPlaceholder, ), @@ -864,7 +867,7 @@ export default class ExpressionParser extends LValParser { // `new`, or an expression wrapped in punctuation like `()`, `[]`, // or `{}`. - parseExprAtom(refShorthandDefaultPos?: ?Pos): N.Expression { + parseExprAtom(refExpressionErrors?: ?ExpressionErrors): N.Expression { // If a division operator appears in an expression position, the // tokenizer got confused, and we force it to read a regexp instead. if (this.state.type === tt.slash) this.readRegexp(); @@ -1038,7 +1041,7 @@ export default class ExpressionParser extends LValParser { node.elements = this.parseExprList( tt.bracketR, true, - refShorthandDefaultPos, + refExpressionErrors, node, ); if (!this.state.maybeInArrowParameters) { @@ -1056,7 +1059,7 @@ export default class ExpressionParser extends LValParser { const oldInFSharpPipelineDirectBody = this.state .inFSharpPipelineDirectBody; this.state.inFSharpPipelineDirectBody = false; - const ret = this.parseObj(false, refShorthandDefaultPos); + const ret = this.parseObj(false, refExpressionErrors); this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; return ret; } @@ -1263,7 +1266,7 @@ export default class ExpressionParser extends LValParser { const innerStartPos = this.state.start; const innerStartLoc = this.state.startLoc; const exprList = []; - const refShorthandDefaultPos = { start: 0 }; + const refExpressionErrors = new ExpressionErrors(); const refNeedsArrowPos = { start: 0 }; let first = true; let spreadStart; @@ -1299,7 +1302,7 @@ export default class ExpressionParser extends LValParser { exprList.push( this.parseMaybeAssign( false, - refShorthandDefaultPos, + refExpressionErrors, this.parseParenItem, refNeedsArrowPos, ), @@ -1343,9 +1346,7 @@ export default class ExpressionParser extends LValParser { } if (optionalCommaStart) this.unexpected(optionalCommaStart); if (spreadStart) this.unexpected(spreadStart); - if (refShorthandDefaultPos.start) { - this.unexpected(refShorthandDefaultPos.start); - } + this.checkExpressionErrors(refExpressionErrors, true); if (refNeedsArrowPos.start) this.unexpected(refNeedsArrowPos.start); this.toReferencedListDeep(exprList, /* isParenthesizedExpr */ true); @@ -1490,7 +1491,7 @@ export default class ExpressionParser extends LValParser { parseObj( isPattern: boolean, - refShorthandDefaultPos?: ?Pos, + refExpressionErrors?: ?ExpressionErrors, ): T { const propHash: any = Object.create(null); let first = true; @@ -1511,7 +1512,7 @@ export default class ExpressionParser extends LValParser { } } - const prop = this.parseObjectMember(isPattern, refShorthandDefaultPos); + const prop = this.parseObjectMember(isPattern, refExpressionErrors); // $FlowIgnore RestElement will never be returned if !isPattern if (!isPattern) this.checkDuplicatedProto(prop, propHash); @@ -1550,7 +1551,7 @@ export default class ExpressionParser extends LValParser { parseObjectMember( isPattern: boolean, - refShorthandDefaultPos: ?Pos, + refExpressionErrors?: ?ExpressionErrors, ): N.ObjectMember | N.SpreadElement | N.RestElement { let decorators = []; if (this.match(tt.at)) { @@ -1594,7 +1595,7 @@ export default class ExpressionParser extends LValParser { prop.method = false; - if (isPattern || refShorthandDefaultPos) { + if (isPattern || refExpressionErrors) { startPos = this.state.start; startLoc = this.state.startLoc; } @@ -1621,7 +1622,7 @@ export default class ExpressionParser extends LValParser { isGenerator, isAsync, isPattern, - refShorthandDefaultPos, + refExpressionErrors, containsEsc, ); @@ -1715,14 +1716,14 @@ export default class ExpressionParser extends LValParser { startPos: ?number, startLoc: ?Position, isPattern: boolean, - refShorthandDefaultPos: ?Pos, + refExpressionErrors: ?ExpressionErrors, ): ?N.ObjectProperty { prop.shorthand = false; if (this.eat(tt.colon)) { prop.value = isPattern ? this.parseMaybeDefault(this.state.start, this.state.startLoc) - : this.parseMaybeAssign(false, refShorthandDefaultPos); + : this.parseMaybeAssign(false, refExpressionErrors); return this.finishNode(prop, "ObjectProperty"); } @@ -1736,9 +1737,9 @@ export default class ExpressionParser extends LValParser { startLoc, prop.key.__clone(), ); - } else if (this.match(tt.eq) && refShorthandDefaultPos) { - if (!refShorthandDefaultPos.start) { - refShorthandDefaultPos.start = this.state.start; + } else if (this.match(tt.eq) && refExpressionErrors) { + if (refExpressionErrors.shorthandAssign === -1) { + refExpressionErrors.shorthandAssign = this.state.start; } prop.value = this.parseMaybeDefault( startPos, @@ -1761,7 +1762,7 @@ export default class ExpressionParser extends LValParser { isGenerator: boolean, isAsync: boolean, isPattern: boolean, - refShorthandDefaultPos: ?Pos, + refExpressionErrors?: ?ExpressionErrors, containsEsc: boolean, ): void { const node = @@ -1777,7 +1778,7 @@ export default class ExpressionParser extends LValParser { startPos, startLoc, isPattern, - refShorthandDefaultPos, + refExpressionErrors, ); if (!node) this.unexpected(); @@ -2019,7 +2020,7 @@ export default class ExpressionParser extends LValParser { parseExprList( close: TokenType, allowEmpty?: boolean, - refShorthandDefaultPos?: ?Pos, + refExpressionErrors?: ?ExpressionErrors, nodeForExtra?: ?N.Node, ): $ReadOnlyArray { const elts = []; @@ -2043,14 +2044,14 @@ export default class ExpressionParser extends LValParser { } } - elts.push(this.parseExprListItem(allowEmpty, refShorthandDefaultPos)); + elts.push(this.parseExprListItem(allowEmpty, refExpressionErrors)); } return elts; } parseExprListItem( allowEmpty: ?boolean, - refShorthandDefaultPos: ?Pos, + refExpressionErrors?: ?ExpressionErrors, refNeedsArrowPos: ?Pos, allowPlaceholder: ?boolean, ): ?N.Expression { @@ -2061,7 +2062,7 @@ export default class ExpressionParser extends LValParser { const spreadNodeStartPos = this.state.start; const spreadNodeStartLoc = this.state.startLoc; elt = this.parseParenItem( - this.parseSpread(refShorthandDefaultPos, refNeedsArrowPos), + this.parseSpread(refExpressionErrors, refNeedsArrowPos), spreadNodeStartPos, spreadNodeStartLoc, ); @@ -2076,7 +2077,7 @@ export default class ExpressionParser extends LValParser { } else { elt = this.parseMaybeAssign( false, - refShorthandDefaultPos, + refExpressionErrors, this.parseParenItem, refNeedsArrowPos, ); diff --git a/packages/babel-parser/src/parser/lval.js b/packages/babel-parser/src/parser/lval.js index 383b5f3edc1a..0bb6347ceaab 100644 --- a/packages/babel-parser/src/parser/lval.js +++ b/packages/babel-parser/src/parser/lval.js @@ -21,6 +21,7 @@ import { } from "../util/identifier"; import { NodeUtils } from "./node"; import { type BindingTypes, BIND_NONE } from "../util/scopeflags"; +import { ExpressionErrors } from "./util"; const unwrapParenthesizedExpression = (node: Node) => { return node.type === "ParenthesizedExpression" @@ -33,13 +34,13 @@ export default class LValParser extends NodeUtils { +parseIdentifier: (liberal?: boolean) => Identifier; +parseMaybeAssign: ( noIn?: ?boolean, - refShorthandDefaultPos?: ?Pos, + refExpressionErrors?: ?ExpressionErrors, afterLeftParse?: Function, refNeedsArrowPos?: ?Pos, ) => Expression; +parseObj: ( isPattern: boolean, - refShorthandDefaultPos?: ?Pos, + refExpressionErrors?: ?ExpressionErrors, ) => T; // Forward-declaration: defined in statement.js +parseDecorator: () => Decorator; @@ -241,14 +242,14 @@ export default class LValParser extends NodeUtils { // Parses spread element. parseSpread( - refShorthandDefaultPos: ?Pos, + refExpressionErrors: ?ExpressionErrors, refNeedsArrowPos?: ?Pos, ): SpreadElement { const node = this.startNode(); this.next(); node.argument = this.parseMaybeAssign( false, - refShorthandDefaultPos, + refExpressionErrors, undefined, refNeedsArrowPos, ); diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 85e177564676..a57a618c9748 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -27,6 +27,7 @@ import { CLASS_ELEMENT_STATIC_SETTER, type BindingTypes, } from "../util/scopeflags"; +import { ExpressionErrors } from "./util"; const loopLabel = { kind: "loop" }, switchLabel = { kind: "switch" }; @@ -533,8 +534,8 @@ export default class StatementParser extends ExpressionParser { return this.parseFor(node, init); } - const refShorthandDefaultPos = { start: 0 }; - const init = this.parseExpression(true, refShorthandDefaultPos); + const refExpressionErrors = new ExpressionErrors(); + const init = this.parseExpression(true, refExpressionErrors); if (this.match(tt._in) || this.isContextual("of")) { const description = this.isContextual("of") ? "for-of statement" @@ -542,8 +543,8 @@ export default class StatementParser extends ExpressionParser { this.toAssignable(init, undefined, description); this.checkLVal(init, undefined, undefined, description); return this.parseForIn(node, init, awaitAt); - } else if (refShorthandDefaultPos.start) { - this.unexpected(refShorthandDefaultPos.start); + } else { + this.checkExpressionErrors(refExpressionErrors, true); } if (awaitAt > -1) { this.unexpected(awaitAt); diff --git a/packages/babel-parser/src/parser/util.js b/packages/babel-parser/src/parser/util.js index e37becfa1a73..68e742365dc3 100644 --- a/packages/babel-parser/src/parser/util.js +++ b/packages/babel-parser/src/parser/util.js @@ -268,4 +268,26 @@ export default class UtilParser extends Tokenizer { throw error; } } + + checkExpressionErrors( + refExpressionErrors: ?ExpressionErrors, + andThrow: boolean, + ) { + if (!refExpressionErrors) return false; + const { shorthandAssign } = refExpressionErrors; + if (!andThrow) return shorthandAssign >= 0; + if (shorthandAssign >= 0) { + this.unexpected(shorthandAssign); + } + } +} + +/** + * The Expression Errors is a context struct used to track + * - **shorthandAssign**: track initializer `=` position when parsing ambiguous + * patterns. When we are sure the parsed pattern is a RHS, we will throw on + * this position for invalid assign syntax, otherwise reset to -1 + */ +export class ExpressionErrors { + shorthandAssign = -1; } diff --git a/packages/babel-parser/src/plugins/estree.js b/packages/babel-parser/src/plugins/estree.js index 5fd0ab11350c..78fddf6829d9 100644 --- a/packages/babel-parser/src/plugins/estree.js +++ b/packages/babel-parser/src/plugins/estree.js @@ -4,8 +4,9 @@ import { types as tt, TokenType } from "../tokenizer/types"; import type Parser from "../parser"; +import type { ExpressionErrors } from "../parser/util"; import * as N from "../types"; -import type { Pos, Position } from "../util/location"; +import type { Position } from "../util/location"; import { type BindingTypes, BIND_NONE } from "../util/scopeflags"; function isSimpleProperty(node: N.Node): boolean { @@ -234,7 +235,7 @@ export default (superClass: Class): Class => classBody.body.push(method); } - parseExprAtom(refShorthandDefaultPos?: ?Pos): N.Expression { + parseExprAtom(refExpressionErrors?: ?ExpressionErrors): N.Expression { switch (this.state.type) { case tt.num: case tt.string: @@ -256,7 +257,7 @@ export default (superClass: Class): Class => return this.estreeParseLiteral(false); default: - return super.parseExprAtom(refShorthandDefaultPos); + return super.parseExprAtom(refExpressionErrors); } } @@ -340,14 +341,14 @@ export default (superClass: Class): Class => startPos: ?number, startLoc: ?Position, isPattern: boolean, - refShorthandDefaultPos: ?Pos, + refExpressionErrors: ?ExpressionErrors, ): ?N.ObjectProperty { const node: N.EstreeProperty = (super.parseObjectProperty( prop, startPos, startLoc, isPattern, - refShorthandDefaultPos, + refExpressionErrors, ): any); if (node) { diff --git a/packages/babel-parser/src/plugins/flow.js b/packages/babel-parser/src/plugins/flow.js index 42f864a2069d..2ab7604514d1 100644 --- a/packages/babel-parser/src/plugins/flow.js +++ b/packages/babel-parser/src/plugins/flow.js @@ -21,6 +21,7 @@ import { SCOPE_ARROW, SCOPE_OTHER, } from "../util/scopeflags"; +import type { ExpressionErrors } from "../parser/util"; const reservedTypes = new Set([ "_", @@ -2283,7 +2284,7 @@ export default (superClass: Class): Class => isGenerator: boolean, isAsync: boolean, isPattern: boolean, - refShorthandDefaultPos: ?Pos, + refExpressionErrors: ?ExpressionErrors, containsEsc: boolean, ): void { if ((prop: $FlowFixMe).variance) { @@ -2306,7 +2307,7 @@ export default (superClass: Class): Class => isGenerator, isAsync, isPattern, - refShorthandDefaultPos, + refExpressionErrors, containsEsc, ); @@ -2559,7 +2560,7 @@ export default (superClass: Class): Class => // 3. This is neither. Just call the super method parseMaybeAssign( noIn?: ?boolean, - refShorthandDefaultPos?: ?Pos, + refExpressionErrors?: ?ExpressionErrors, afterLeftParse?: Function, refNeedsArrowPos?: ?Pos, ): N.Expression { @@ -2577,7 +2578,7 @@ export default (superClass: Class): Class => () => super.parseMaybeAssign( noIn, - refShorthandDefaultPos, + refExpressionErrors, afterLeftParse, refNeedsArrowPos, ), @@ -2611,7 +2612,7 @@ export default (superClass: Class): Class => () => super.parseMaybeAssign( noIn, - refShorthandDefaultPos, + refExpressionErrors, afterLeftParse, refNeedsArrowPos, ), @@ -2659,7 +2660,7 @@ export default (superClass: Class): Class => return super.parseMaybeAssign( noIn, - refShorthandDefaultPos, + refExpressionErrors, afterLeftParse, refNeedsArrowPos, ); diff --git a/packages/babel-parser/src/plugins/jsx/index.js b/packages/babel-parser/src/plugins/jsx/index.js index 2aba709267f2..fce057e48b74 100644 --- a/packages/babel-parser/src/plugins/jsx/index.js +++ b/packages/babel-parser/src/plugins/jsx/index.js @@ -4,11 +4,12 @@ import * as charCodes from "charcodes"; import XHTMLEntities from "./xhtml"; import type Parser from "../../parser"; +import type { ExpressionErrors } from "../../parser/util"; import { TokenType, types as tt } from "../../tokenizer/types"; import { TokContext, types as tc } from "../../tokenizer/context"; import * as N from "../../types"; import { isIdentifierChar, isIdentifierStart } from "../../util/identifier"; -import type { Pos, Position } from "../../util/location"; +import type { Position } from "../../util/location"; import { isNewLine } from "../../util/whitespace"; const HEX_NUMBER = /^[\da-fA-F]+$/; @@ -510,7 +511,7 @@ export default (superClass: Class): Class => // Overrides // ================================== - parseExprAtom(refShortHandDefaultPos: ?Pos): N.Expression { + parseExprAtom(refExpressionErrors: ?ExpressionErrors): N.Expression { if (this.match(tt.jsxText)) { return this.parseLiteral(this.state.value, "JSXText"); } else if (this.match(tt.jsxTagStart)) { @@ -524,7 +525,7 @@ export default (superClass: Class): Class => this.finishToken(tt.jsxTagStart); return this.jsxParseElement(); } else { - return super.parseExprAtom(refShortHandDefaultPos); + return super.parseExprAtom(refExpressionErrors); } } diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index b53dca72d61c..08691c5b90ba 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -25,6 +25,7 @@ import { } from "../../util/scopeflags"; import TypeScriptScopeHandler from "./scope"; import * as charCodes from "charcodes"; +import type { ExpressionErrors } from "../../parser/util"; type TsModifier = | "readonly" @@ -2340,11 +2341,11 @@ export default (superClass: Class): Class => } // Handle type assertions - parseMaybeUnary(refShorthandDefaultPos?: ?Pos): N.Expression { + parseMaybeUnary(refExpressionErrors?: ?ExpressionErrors): N.Expression { if (!this.hasPlugin("jsx") && this.isRelational("<")) { return this.tsParseTypeAssertion(); } else { - return super.parseMaybeUnary(refShorthandDefaultPos); + return super.parseMaybeUnary(refExpressionErrors); } } From 3ce326c32df64d19f5295a72b6a1c315aed660e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Sat, 11 Jan 2020 17:36:51 -0500 Subject: [PATCH 2/4] fix: duplicate __proto__ keys should be allowed in patterns --- .../babel-parser/src/parser/expression.js | 30 ++- packages/babel-parser/src/parser/util.js | 8 +- packages/babel-parser/src/plugins/estree.js | 11 +- .../input.js | 0 .../output.json | 0 .../destructuring/duplicate-proto-2/input.js | 1 + .../duplicate-proto-2/output.json | 241 ++++++++++++++++++ .../destructuring/duplicate-proto-3/input.js | 1 + .../duplicate-proto-3/output.json | 208 +++++++++++++++ 9 files changed, 483 insertions(+), 17 deletions(-) rename packages/babel-parser/test/fixtures/es2015/destructuring/{duplicate-proto => duplicate-proto-1}/input.js (100%) rename packages/babel-parser/test/fixtures/es2015/destructuring/{duplicate-proto => duplicate-proto-1}/output.json (100%) create mode 100644 packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-2/input.js create mode 100644 packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-2/output.json create mode 100644 packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-3/input.js create mode 100644 packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-3/output.json diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index c841935a51f8..86c4f0808d78 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -70,7 +70,8 @@ export default class ExpressionParser extends LValParser { checkDuplicatedProto( prop: N.ObjectMember | N.SpreadElement, - protoRef: { used: boolean, start?: number }, + protoRef: { used: boolean }, + refExpressionErrors: ?ExpressionErrors, ): void { if ( prop.type === "SpreadElement" || @@ -88,8 +89,12 @@ export default class ExpressionParser extends LValParser { if (name === "__proto__") { // Store the first redefinition's position - if (protoRef.used && !protoRef.start) { - protoRef.start = key.start; + if (protoRef.used) { + if (refExpressionErrors && refExpressionErrors.doubleProto === -1) { + refExpressionErrors.doubleProto = key.start; + } else { + this.raise(key.start, "Redefinition of __proto__ property"); + } } protoRef.used = true; @@ -203,9 +208,12 @@ export default class ExpressionParser extends LValParser { if (operator === "||=" || operator === "&&=") { this.expectPlugin("logicalAssignment"); } - node.left = this.match(tt.eq) - ? this.toAssignable(left, undefined, "assignment expression") - : left; + if (this.match(tt.eq)) { + node.left = this.toAssignable(left, undefined, "assignment expression"); + refExpressionErrors.doubleProto = -1; // reset because double __proto__ is valid in assignment expression + } else { + node.left = left; + } if (refExpressionErrors.shorthandAssign >= node.left.start) { refExpressionErrors.shorthandAssign = -1; // reset because shorthand default was used correctly @@ -1513,8 +1521,10 @@ export default class ExpressionParser extends LValParser { } const prop = this.parseObjectMember(isPattern, refExpressionErrors); - // $FlowIgnore RestElement will never be returned if !isPattern - if (!isPattern) this.checkDuplicatedProto(prop, propHash); + if (!isPattern) { + // $FlowIgnore RestElement will never be returned if !isPattern + this.checkDuplicatedProto(prop, propHash, refExpressionErrors); + } // $FlowIgnore if (prop.shorthand) { @@ -1524,10 +1534,6 @@ export default class ExpressionParser extends LValParser { node.properties.push(prop); } - if (!this.match(tt.eq) && propHash.start !== undefined) { - this.raise(propHash.start, "Redefinition of __proto__ property"); - } - return this.finishNode( node, isPattern ? "ObjectPattern" : "ObjectExpression", diff --git a/packages/babel-parser/src/parser/util.js b/packages/babel-parser/src/parser/util.js index 68e742365dc3..ee420b7ab96b 100644 --- a/packages/babel-parser/src/parser/util.js +++ b/packages/babel-parser/src/parser/util.js @@ -274,11 +274,14 @@ export default class UtilParser extends Tokenizer { andThrow: boolean, ) { if (!refExpressionErrors) return false; - const { shorthandAssign } = refExpressionErrors; - if (!andThrow) return shorthandAssign >= 0; + const { shorthandAssign, doubleProto } = refExpressionErrors; + if (!andThrow) return shorthandAssign >= 0 || doubleProto >= 0; if (shorthandAssign >= 0) { this.unexpected(shorthandAssign); } + if (doubleProto >= 0) { + this.raise(doubleProto, "Redefinition of __proto__ property"); + } } } @@ -290,4 +293,5 @@ export default class UtilParser extends Tokenizer { */ export class ExpressionErrors { shorthandAssign = -1; + doubleProto = -1; } diff --git a/packages/babel-parser/src/plugins/estree.js b/packages/babel-parser/src/plugins/estree.js index 78fddf6829d9..9cd825945015 100644 --- a/packages/babel-parser/src/plugins/estree.js +++ b/packages/babel-parser/src/plugins/estree.js @@ -149,7 +149,8 @@ export default (superClass: Class): Class => checkDuplicatedProto( prop: N.ObjectMember | N.SpreadElement, - protoRef: { used: boolean, start?: number }, + protoRef: { used: boolean }, + refExpressionErrors: ?ExpressionErrors, ): void { if ( prop.type === "SpreadElement" || @@ -167,8 +168,12 @@ export default (superClass: Class): Class => if (name === "__proto__" && prop.kind === "init") { // Store the first redefinition's position - if (protoRef.used && !protoRef.start) { - protoRef.start = key.start; + if (protoRef.used) { + if (refExpressionErrors && refExpressionErrors.doubleProto === -1) { + refExpressionErrors.doubleProto = key.start; + } else { + this.raise(key.start, "Redefinition of __proto__ property"); + } } protoRef.used = true; diff --git a/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto/input.js b/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-1/input.js similarity index 100% rename from packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto/input.js rename to packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-1/input.js diff --git a/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto/output.json b/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-1/output.json similarity index 100% rename from packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto/output.json rename to packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-1/output.json diff --git a/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-2/input.js b/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-2/input.js new file mode 100644 index 000000000000..73d46e315061 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-2/input.js @@ -0,0 +1 @@ +([{ __proto__: x, __proto__: y }] = [{}]); diff --git a/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-2/output.json b/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-2/output.json new file mode 100644 index 000000000000..3646ce22df38 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-2/output.json @@ -0,0 +1,241 @@ +{ + "type": "File", + "start": 0, + "end": 42, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 42 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 42, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 42 + } + }, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 42, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 42 + } + }, + "expression": { + "type": "AssignmentExpression", + "start": 1, + "end": 40, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 40 + } + }, + "operator": "=", + "left": { + "type": "ArrayPattern", + "start": 1, + "end": 33, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 33 + } + }, + "elements": [ + { + "type": "ObjectPattern", + "start": 2, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 32 + } + }, + "properties": [ + { + "type": "ObjectProperty", + "start": 4, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "method": false, + "key": { + "type": "Identifier", + "start": 4, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 13 + }, + "identifierName": "__proto__" + }, + "name": "__proto__" + }, + "computed": false, + "shorthand": false, + "value": { + "type": "Identifier", + "start": 15, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 16 + }, + "identifierName": "x" + }, + "name": "x" + } + }, + { + "type": "ObjectProperty", + "start": 18, + "end": 30, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 30 + } + }, + "method": false, + "key": { + "type": "Identifier", + "start": 18, + "end": 27, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 27 + }, + "identifierName": "__proto__" + }, + "name": "__proto__" + }, + "computed": false, + "shorthand": false, + "value": { + "type": "Identifier", + "start": 29, + "end": 30, + "loc": { + "start": { + "line": 1, + "column": 29 + }, + "end": { + "line": 1, + "column": 30 + }, + "identifierName": "y" + }, + "name": "y" + } + } + ] + } + ] + }, + "right": { + "type": "ArrayExpression", + "start": 36, + "end": 40, + "loc": { + "start": { + "line": 1, + "column": 36 + }, + "end": { + "line": 1, + "column": 40 + } + }, + "elements": [ + { + "type": "ObjectExpression", + "start": 37, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 37 + }, + "end": { + "line": 1, + "column": 39 + } + }, + "properties": [] + } + ] + }, + "extra": { + "parenthesized": true, + "parenStart": 0 + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-3/input.js b/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-3/input.js new file mode 100644 index 000000000000..478c33774b46 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-3/input.js @@ -0,0 +1 @@ +({ __proto__: x, __proto__: y }) => {}; diff --git a/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-3/output.json b/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-3/output.json new file mode 100644 index 000000000000..27df8bb4401e --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/destructuring/duplicate-proto-3/output.json @@ -0,0 +1,208 @@ +{ + "type": "File", + "start": 0, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 39 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 39 + } + }, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 39 + } + }, + "expression": { + "type": "ArrowFunctionExpression", + "start": 0, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 38 + } + }, + "id": null, + "generator": false, + "async": false, + "params": [ + { + "type": "ObjectPattern", + "start": 1, + "end": 31, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 31 + } + }, + "properties": [ + { + "type": "ObjectProperty", + "start": 3, + "end": 15, + "loc": { + "start": { + "line": 1, + "column": 3 + }, + "end": { + "line": 1, + "column": 15 + } + }, + "method": false, + "key": { + "type": "Identifier", + "start": 3, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 3 + }, + "end": { + "line": 1, + "column": 12 + }, + "identifierName": "__proto__" + }, + "name": "__proto__" + }, + "computed": false, + "shorthand": false, + "value": { + "type": "Identifier", + "start": 14, + "end": 15, + "loc": { + "start": { + "line": 1, + "column": 14 + }, + "end": { + "line": 1, + "column": 15 + }, + "identifierName": "x" + }, + "name": "x" + } + }, + { + "type": "ObjectProperty", + "start": 17, + "end": 29, + "loc": { + "start": { + "line": 1, + "column": 17 + }, + "end": { + "line": 1, + "column": 29 + } + }, + "method": false, + "key": { + "type": "Identifier", + "start": 17, + "end": 26, + "loc": { + "start": { + "line": 1, + "column": 17 + }, + "end": { + "line": 1, + "column": 26 + }, + "identifierName": "__proto__" + }, + "name": "__proto__" + }, + "computed": false, + "shorthand": false, + "value": { + "type": "Identifier", + "start": 28, + "end": 29, + "loc": { + "start": { + "line": 1, + "column": 28 + }, + "end": { + "line": 1, + "column": 29 + }, + "identifierName": "y" + }, + "name": "y" + } + } + ] + } + ], + "body": { + "type": "BlockStatement", + "start": 36, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 36 + }, + "end": { + "line": 1, + "column": 38 + } + }, + "body": [], + "directives": [] + } + } + } + ], + "directives": [] + } +} \ No newline at end of file From ef7733cad0c70db311878519f2d37347225ea6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Sat, 11 Jan 2020 17:56:07 -0500 Subject: [PATCH 3/4] docs: add comments for ExpressionErrors.doubleProto [ci-skip] --- packages/babel-parser/src/parser/util.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/babel-parser/src/parser/util.js b/packages/babel-parser/src/parser/util.js index ee420b7ab96b..ceb42ef5a410 100644 --- a/packages/babel-parser/src/parser/util.js +++ b/packages/babel-parser/src/parser/util.js @@ -286,10 +286,15 @@ export default class UtilParser extends Tokenizer { } /** - * The Expression Errors is a context struct used to track + * The ExpressionErrors is a context struct used to track * - **shorthandAssign**: track initializer `=` position when parsing ambiguous - * patterns. When we are sure the parsed pattern is a RHS, we will throw on - * this position for invalid assign syntax, otherwise reset to -1 + * patterns. When we are sure the parsed pattern is a RHS, which means it is + * not a pattern, we will throw on this position on invalid assign syntax, + * otherwise it will be reset to -1 + * - **doubleProto**: track the duplicate `__proto__` key position when parsing + * ambiguous object patterns. When we are sure the parsed pattern is a RHS, + * which means it is an object literal, we will throw on this position for + * __proto__ redefinition, otherwise it will be reset to -1 */ export class ExpressionErrors { shorthandAssign = -1; From 1e76984cf28db9078003073121b9166d80d73605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Tue, 14 Jan 2020 22:05:16 -0500 Subject: [PATCH 4/4] test: add more test for coverage --- .../in-new-expression/input.js | 1 + .../in-new-expression/output.json | 223 ++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 packages/babel-parser/test/fixtures/es2015/duplicate-proto/in-new-expression/input.js create mode 100644 packages/babel-parser/test/fixtures/es2015/duplicate-proto/in-new-expression/output.json diff --git a/packages/babel-parser/test/fixtures/es2015/duplicate-proto/in-new-expression/input.js b/packages/babel-parser/test/fixtures/es2015/duplicate-proto/in-new-expression/input.js new file mode 100644 index 000000000000..900d3841bc6c --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/duplicate-proto/in-new-expression/input.js @@ -0,0 +1 @@ +new {__proto__: Number, __proto__: Number}.__proto__; diff --git a/packages/babel-parser/test/fixtures/es2015/duplicate-proto/in-new-expression/output.json b/packages/babel-parser/test/fixtures/es2015/duplicate-proto/in-new-expression/output.json new file mode 100644 index 000000000000..8b1e9a9a780d --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/duplicate-proto/in-new-expression/output.json @@ -0,0 +1,223 @@ +{ + "type": "File", + "start": 0, + "end": 53, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 53 + } + }, + "errors": [ + "SyntaxError: Redefinition of __proto__ property (1:24)" + ], + "program": { + "type": "Program", + "start": 0, + "end": 53, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 53 + } + }, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 53, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 53 + } + }, + "expression": { + "type": "NewExpression", + "start": 0, + "end": 52, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 52 + } + }, + "callee": { + "type": "MemberExpression", + "start": 4, + "end": 52, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 52 + } + }, + "object": { + "type": "ObjectExpression", + "start": 4, + "end": 42, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 42 + } + }, + "properties": [ + { + "type": "ObjectProperty", + "start": 5, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 22 + } + }, + "method": false, + "key": { + "type": "Identifier", + "start": 5, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 14 + }, + "identifierName": "__proto__" + }, + "name": "__proto__" + }, + "computed": false, + "shorthand": false, + "value": { + "type": "Identifier", + "start": 16, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 22 + }, + "identifierName": "Number" + }, + "name": "Number" + } + }, + { + "type": "ObjectProperty", + "start": 24, + "end": 41, + "loc": { + "start": { + "line": 1, + "column": 24 + }, + "end": { + "line": 1, + "column": 41 + } + }, + "method": false, + "key": { + "type": "Identifier", + "start": 24, + "end": 33, + "loc": { + "start": { + "line": 1, + "column": 24 + }, + "end": { + "line": 1, + "column": 33 + }, + "identifierName": "__proto__" + }, + "name": "__proto__" + }, + "computed": false, + "shorthand": false, + "value": { + "type": "Identifier", + "start": 35, + "end": 41, + "loc": { + "start": { + "line": 1, + "column": 35 + }, + "end": { + "line": 1, + "column": 41 + }, + "identifierName": "Number" + }, + "name": "Number" + } + } + ] + }, + "property": { + "type": "Identifier", + "start": 43, + "end": 52, + "loc": { + "start": { + "line": 1, + "column": 43 + }, + "end": { + "line": 1, + "column": 52 + }, + "identifierName": "__proto__" + }, + "name": "__proto__" + }, + "computed": false + }, + "arguments": [] + } + } + ], + "directives": [] + } +} \ No newline at end of file