diff --git a/packages/babel-parser/src/parser/error-message.js b/packages/babel-parser/src/parser/error-message.js index 77953092dc..f9e95d039e 100644 --- a/packages/babel-parser/src/parser/error-message.js +++ b/packages/babel-parser/src/parser/error-message.js @@ -80,6 +80,7 @@ export const ErrorMessages = Object.freeze({ LabelRedeclaration: "Label '%0' is already declared", LetInLexicalBinding: "'let' is not allowed to be used as a name in 'let' or 'const' declarations.", + LineTerminatorBeforeArrow: "No line break is allowed before '=>'", MalformedRegExpFlags: "Invalid regular expression flag", MissingClassName: "A class name is required", MissingEqInAssignment: diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 39df900050..26527c8300 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -90,7 +90,6 @@ export default class ExpressionParser extends LValParser { prop.type === "SpreadElement" || prop.type === "ObjectMethod" || prop.computed || - // $FlowIgnore prop.shorthand ) { return; @@ -121,6 +120,12 @@ export default class ExpressionParser extends LValParser { } } + shouldExitDescending(expr: N.Expression, potentialArrowAt: number): boolean { + return ( + expr.type === "ArrowFunctionExpression" && expr.start === potentialArrowAt + ); + } + // Convenience method to parse an Expression only getExpression(): N.Expression { let paramFlags = PARAM; @@ -160,6 +165,7 @@ export default class ExpressionParser extends LValParser { // and object pattern might appear (so it's possible to raise // delayed syntax error at correct position). + // https://tc39.es/ecma262/#prod-Expression parseExpression( noIn?: boolean, refExpressionErrors?: ExpressionErrors, @@ -182,6 +188,7 @@ export default class ExpressionParser extends LValParser { // Parse an assignment expression. This includes applications of // operators like `+=`. + // https://tc39.es/ecma262/#prod-AssignmentExpression parseMaybeAssign( noIn?: ?boolean, refExpressionErrors?: ?ExpressionErrors, @@ -253,6 +260,7 @@ export default class ExpressionParser extends LValParser { } // Parse a ternary conditional (`?:`) operator. + // https://tc39.es/ecma262/#prod-ConditionalExpression parseMaybeConditional( noIn: ?boolean, @@ -264,13 +272,9 @@ export default class ExpressionParser extends LValParser { const potentialArrowAt = this.state.potentialArrowAt; const expr = this.parseExprOps(noIn, refExpressionErrors); - if ( - expr.type === "ArrowFunctionExpression" && - expr.start === potentialArrowAt - ) { + if (this.shouldExitDescending(expr, potentialArrowAt)) { return expr; } - if (this.checkExpressionErrors(refExpressionErrors, false)) return expr; return this.parseConditional( expr, @@ -302,6 +306,7 @@ export default class ExpressionParser extends LValParser { } // Start the precedence parser. + // https://tc39.es/ecma262/#prod-ShortCircuitExpression parseExprOps( noIn: ?boolean, @@ -312,13 +317,7 @@ export default class ExpressionParser extends LValParser { const potentialArrowAt = this.state.potentialArrowAt; const expr = this.parseMaybeUnary(refExpressionErrors); - if ( - expr.type === "ArrowFunctionExpression" && - expr.start === potentialArrowAt - ) { - return expr; - } - if (this.checkExpressionErrors(refExpressionErrors, false)) { + if (this.shouldExitDescending(expr, potentialArrowAt)) { return expr; } @@ -341,15 +340,20 @@ export default class ExpressionParser extends LValParser { let prec = this.state.type.binop; if (prec != null && (!noIn || !this.match(tt._in))) { if (prec > minPrec) { - const operator = this.state.value; - if (operator === "|>" && this.state.inFSharpPipelineDirectBody) { - return left; + const op = this.state.type; + if (op === tt.pipeline) { + this.expectPlugin("pipelineOperator"); + if (this.state.inFSharpPipelineDirectBody) { + return left; + } + this.state.inPipeline = true; + this.checkPipelineAtInfixOperator(left, leftStartPos); } const node = this.startNodeAt(leftStartPos, leftStartLoc); node.left = left; - node.operator = operator; + node.operator = this.state.value; if ( - operator === "**" && + op === tt.exponent && left.type === "UnaryExpression" && (this.options.createParenthesizedExpressions || !(left.extra && left.extra.parenthesized)) @@ -360,15 +364,10 @@ export default class ExpressionParser extends LValParser { ); } - const op = this.state.type; const logical = op === tt.logicalOR || op === tt.logicalAND; const coalesce = op === tt.nullishCoalescing; - if (op === tt.pipeline) { - this.expectPlugin("pipelineOperator"); - this.state.inPipeline = true; - this.checkPipelineAtInfixOperator(left, leftStartPos); - } else if (coalesce) { + if (coalesce) { // Handle the precedence of `tt.coalesce` as equal to the range of logical expressions. // In other words, `node.right` shouldn't contain logical expressions in order to check the mixed error. prec = ((tt.logicalAND: any): { binop: number }).binop; @@ -476,28 +475,28 @@ export default class ExpressionParser extends LValParser { } // Parse unary operators, both prefix and postfix. - + // https://tc39.es/ecma262/#prod-UnaryExpression parseMaybeUnary(refExpressionErrors: ?ExpressionErrors): N.Expression { if (this.isContextual("await") && this.isAwaitAllowed()) { return this.parseAwait(); - } else if (this.state.type.prefix) { - const node = this.startNode(); - const update = this.match(tt.incDec); + } + const update = this.match(tt.incDec); + const node = this.startNode(); + if (this.state.type.prefix) { node.operator = this.state.value; node.prefix = true; - if (node.operator === "throw") { + if (this.match(tt._throw)) { this.expectPlugin("throwExpressions"); } + const isDelete = this.match(tt._delete); this.next(); node.argument = this.parseMaybeUnary(); this.checkExpressionErrors(refExpressionErrors, true); - if (update) { - this.checkLVal(node.argument, undefined, undefined, "prefix operation"); - } else if (this.state.strict && node.operator === "delete") { + if (this.state.strict && isDelete) { const arg = node.argument; if (arg.type === "Identifier") { @@ -511,10 +510,23 @@ export default class ExpressionParser extends LValParser { } } - return this.finishNode( - node, - update ? "UpdateExpression" : "UnaryExpression", - ); + if (!update) { + return this.finishNode(node, "UnaryExpression"); + } + } + + return this.parseUpdate(node, update, refExpressionErrors); + } + + // https://tc39.es/ecma262/#prod-UpdateExpression + parseUpdate( + node: N.Expression, + update: boolean, + refExpressionErrors: ?ExpressionErrors, + ): N.Expression { + if (update) { + this.checkLVal(node.argument, undefined, undefined, "prefix operation"); + return this.finishNode(node, "UpdateExpression"); } const startPos = this.state.start; @@ -534,17 +546,14 @@ export default class ExpressionParser extends LValParser { } // Parse call, dot, and `[]`-subscript expressions. - + // https://tc39.es/ecma262/#prod-LeftHandSideExpression parseExprSubscripts(refExpressionErrors: ?ExpressionErrors): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; const potentialArrowAt = this.state.potentialArrowAt; const expr = this.parseExprAtom(refExpressionErrors); - if ( - expr.type === "ArrowFunctionExpression" && - expr.start === potentialArrowAt - ) { + if (this.shouldExitDescending(expr, potentialArrowAt)) { return expr; } @@ -588,155 +597,198 @@ export default class ExpressionParser extends LValParser { state: N.ParseSubscriptState, ): N.Expression { if (!noCalls && this.eat(tt.doubleColon)) { - const node = this.startNodeAt(startPos, startLoc); - node.object = base; - node.callee = this.parseNoCallExpr(); - state.stop = true; - return this.parseSubscripts( - this.finishNode(node, "BindExpression"), + return this.parseBind(base, startPos, startLoc, noCalls, state); + } else if (this.match(tt.backQuote)) { + return this.parseTaggedTemplateExpression( + base, startPos, startLoc, - noCalls, + state, ); } + let optional = false; if (this.match(tt.questionDot)) { state.optionalChainMember = optional = true; if (noCalls && this.lookaheadCharCode() === charCodes.leftParenthesis) { + // stop at `?.` when parsing `new a?.()` state.stop = true; return base; } this.next(); } - const computed = this.eat(tt.bracketL); - if ( - (optional && !this.match(tt.parenL) && !this.match(tt.backQuote)) || - computed || - this.eat(tt.dot) - ) { - const node = this.startNodeAt(startPos, startLoc); - node.object = base; - node.property = computed - ? this.parseExpression() - : this.parseMaybePrivateName(true); - node.computed = computed; - if (node.property.type === "PrivateName") { - if (node.object.type === "Super") { - this.raise(startPos, Errors.SuperPrivateField); - } - this.classScope.usePrivateName( - node.property.id.name, - node.property.start, - ); - } - - if (computed) { - this.expect(tt.bracketR); - } - - if (state.optionalChainMember) { - node.optional = optional; - return this.finishNode(node, "OptionalMemberExpression"); - } else { - return this.finishNode(node, "MemberExpression"); - } - } else if (!noCalls && this.match(tt.parenL)) { - const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; - const oldYieldPos = this.state.yieldPos; - const oldAwaitPos = this.state.awaitPos; - this.state.maybeInArrowParameters = true; - this.state.yieldPos = -1; - this.state.awaitPos = -1; - - this.next(); - - let node = this.startNodeAt(startPos, startLoc); - node.callee = base; - - if (state.optionalChainMember) { - node.optional = optional; - } - if (optional) { - node.arguments = this.parseCallExpressionArguments(tt.parenR, false); - } else { - node.arguments = this.parseCallExpressionArguments( - tt.parenR, - state.maybeAsyncArrow, - base.type === "Import", - base.type !== "Super", - node, - ); - } - this.finishCallExpression(node, state.optionalChainMember); - - if (state.maybeAsyncArrow && this.shouldParseAsyncArrow() && !optional) { - state.stop = true; - - node = this.parseAsyncArrowFromCallExpression( - this.startNodeAt(startPos, startLoc), - node, - ); - this.checkYieldAwaitInDefaultParams(); - this.state.yieldPos = oldYieldPos; - this.state.awaitPos = oldAwaitPos; - } else { - this.toReferencedListDeep(node.arguments); - - // We keep the old value if it isn't null, for cases like - // (x = async(yield)) => {} - // - // Hi developer of the future :) If you are implementing generator - // arrow functions, please read the note below about "await" and - // verify if the same logic is needed for yield. - if (oldYieldPos !== -1) this.state.yieldPos = oldYieldPos; - - // Await is trickier than yield. When parsing a possible arrow function - // (e.g. something starting with `async(`) we don't know if its possible - // parameters will actually be inside an async arrow function or if it is - // a normal call expression. - // If it ended up being a call expression, if we are in a context where - // await expression are disallowed (and thus "await" is an identifier) - // we must be careful not to leak this.state.awaitPos to an even outer - // context, where "await" could not be an identifier. - // For example, this code is valid because "await" isn't directly inside - // an async function: - // - // async function a() { - // function b(param = async (await)) { - // } - // } - // - if ( - (!this.isAwaitAllowed() && !oldMaybeInArrowParameters) || - oldAwaitPos !== -1 - ) { - this.state.awaitPos = oldAwaitPos; - } - } - - this.state.maybeInArrowParameters = oldMaybeInArrowParameters; - - return node; - } else if (this.match(tt.backQuote)) { - return this.parseTaggedTemplateExpression( + if (!noCalls && this.match(tt.parenL)) { + return this.parseCoverCallAndAsyncArrowHead( + base, startPos, startLoc, - base, state, + optional, ); + } else if (optional || this.match(tt.bracketL) || this.eat(tt.dot)) { + return this.parseMember(base, startPos, startLoc, state, optional); } else { state.stop = true; return base; } } - parseTaggedTemplateExpression( + // base[?Yield, ?Await] [ Expression[+In, ?Yield, ?Await] ] + // base[?Yield, ?Await] . IdentifierName + // base[?Yield, ?Await] . PrivateIdentifier + // where `base` is one of CallExpression, MemberExpression and OptionalChain + parseMember( + base: N.Expression, + startPos: number, + startLoc: Position, + state: N.ParseSubscriptState, + optional: boolean, + ): N.OptionalMemberExpression | N.MemberExpression { + const node = this.startNodeAt(startPos, startLoc); + const computed = this.eat(tt.bracketL); + node.object = base; + node.computed = computed; + const property = computed + ? this.parseExpression() + : this.parseMaybePrivateName(true); + + if (property.type === "PrivateName") { + if (node.object.type === "Super") { + this.raise(startPos, Errors.SuperPrivateField); + } + this.classScope.usePrivateName(property.id.name, property.start); + } + node.property = property; + + if (computed) { + this.expect(tt.bracketR); + } + + if (state.optionalChainMember) { + node.optional = optional; + return this.finishNode(node, "OptionalMemberExpression"); + } else { + return this.finishNode(node, "MemberExpression"); + } + } + + // https://github.com/tc39/proposal-bind-operator#syntax + parseBind( + base: N.Expression, + startPos: number, + startLoc: Position, + noCalls: ?boolean, + state: N.ParseSubscriptState, + ): N.Expression { + const node = this.startNodeAt(startPos, startLoc); + node.object = base; + node.callee = this.parseNoCallExpr(); + state.stop = true; + return this.parseSubscripts( + this.finishNode(node, "BindExpression"), + startPos, + startLoc, + noCalls, + ); + } + + // https://tc39.es/ecma262/#prod-CoverCallExpressionAndAsyncArrowHead + // CoverCallExpressionAndAsyncArrowHead + // CallExpression[?Yield, ?Await] Arguments[?Yield, ?Await] + // OptionalChain[?Yield, ?Await] Arguments[?Yield, ?Await] + parseCoverCallAndAsyncArrowHead( + base: N.Expression, + startPos: number, + startLoc: Position, + state: N.ParseSubscriptState, + optional: boolean, + ): N.Expression { + const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; + const oldYieldPos = this.state.yieldPos; + const oldAwaitPos = this.state.awaitPos; + this.state.maybeInArrowParameters = true; + this.state.yieldPos = -1; + this.state.awaitPos = -1; + + this.next(); // eat `(` + + let node = this.startNodeAt(startPos, startLoc); + node.callee = base; + + if (state.optionalChainMember) { + node.optional = optional; + } + if (optional) { + node.arguments = this.parseCallExpressionArguments(tt.parenR, false); + } else { + node.arguments = this.parseCallExpressionArguments( + tt.parenR, + state.maybeAsyncArrow, + base.type === "Import", + base.type !== "Super", + node, + ); + } + this.finishCallExpression(node, state.optionalChainMember); + + if (state.maybeAsyncArrow && this.shouldParseAsyncArrow() && !optional) { + state.stop = true; + + node = this.parseAsyncArrowFromCallExpression( + this.startNodeAt(startPos, startLoc), + node, + ); + this.checkYieldAwaitInDefaultParams(); + this.state.yieldPos = oldYieldPos; + this.state.awaitPos = oldAwaitPos; + } else { + this.toReferencedListDeep(node.arguments); + + // We keep the old value if it isn't null, for cases like + // (x = async(yield)) => {} + // + // Hi developer of the future :) If you are implementing generator + // arrow functions, please read the note below about "await" and + // verify if the same logic is needed for yield. + if (oldYieldPos !== -1) this.state.yieldPos = oldYieldPos; + + // Await is trickier than yield. When parsing a possible arrow function + // (e.g. something starting with `async(`) we don't know if its possible + // parameters will actually be inside an async arrow function or if it is + // a normal call expression. + // If it ended up being a call expression, if we are in a context where + // await expression are disallowed (and thus "await" is an identifier) + // we must be careful not to leak this.state.awaitPos to an even outer + // context, where "await" could not be an identifier. + // For example, this code is valid because "await" isn't directly inside + // an async function: + // + // async function a() { + // function b(param = async (await)) { + // } + // } + // + if ( + (!this.isAwaitAllowed() && !oldMaybeInArrowParameters) || + oldAwaitPos !== -1 + ) { + this.state.awaitPos = oldAwaitPos; + } + } + + this.state.maybeInArrowParameters = oldMaybeInArrowParameters; + + return node; + } + + // MemberExpression [?Yield, ?Await] TemplateLiteral[?Yield, ?Await, +Tagged] + // CallExpression [?Yield, ?Await] TemplateLiteral[?Yield, ?Await, +Tagged] + parseTaggedTemplateExpression( + base: N.Expression, startPos: number, startLoc: Position, - base: N.Expression, state: N.ParseSubscriptState, - typeArguments?: ?N.TsTypeParameterInstantiation, ): N.TaggedTemplateExpression { const node: N.TaggedTemplateExpression = this.startNodeAt( startPos, @@ -744,7 +796,6 @@ export default class ExpressionParser extends LValParser { ); node.tag = base; node.quasi = this.parseTemplate(true); - if (typeArguments) node.typeParameters = typeArguments; if (state.optionalChainMember) { this.raise(startPos, Errors.OptionalChainingNoTemplate); } @@ -875,7 +926,7 @@ export default class ExpressionParser extends LValParser { } // Parse a no-call expression (like argument of `new` or `::` operators). - + // https://tc39.es/ecma262/#prod-MemberExpression parseNoCallExpr(): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; @@ -887,6 +938,13 @@ export default class ExpressionParser extends LValParser { // `new`, or an expression wrapped in punctuation like `()`, `[]`, // or `{}`. + // https://tc39.es/ecma262/#prod-PrimaryExpression + // https://tc39.es/ecma262/#prod-AsyncArrowFunction + // PrimaryExpression + // Super + // Import + // AsyncArrowFunction + 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. @@ -897,30 +955,7 @@ export default class ExpressionParser extends LValParser { switch (this.state.type) { case tt._super: - node = this.startNode(); - this.next(); - if ( - this.match(tt.parenL) && - !this.scope.allowDirectSuper && - !this.options.allowSuperOutsideMethod - ) { - this.raise(node.start, Errors.SuperNotAllowed); - } else if ( - !this.scope.allowSuper && - !this.options.allowSuperOutsideMethod - ) { - this.raise(node.start, Errors.UnexpectedSuper); - } - - if ( - !this.match(tt.parenL) && - !this.match(tt.bracketL) && - !this.match(tt.dot) - ) { - this.raise(node.start, Errors.UnsupportedSuper); - } - - return this.finishNode(node, "Super"); + return this.parseSuper(); case tt._import: node = this.startNode(); @@ -940,76 +975,49 @@ export default class ExpressionParser extends LValParser { return this.finishNode(node, "ThisExpression"); case tt.name: { - node = this.startNode(); const containsEsc = this.state.containsEsc; const id = this.parseIdentifier(); - if ( - !containsEsc && - id.name === "async" && - this.match(tt._function) && - !this.canInsertSemicolon() - ) { - const last = this.state.context.length - 1; - if (this.state.context[last] !== ct.functionStatement) { - // Since "async" is an identifier and normally identifiers - // can't be followed by expression, the tokenizer assumes - // that "function" starts a statement. - // Fixing it in the tokenizer would mean tracking not only the - // previous token ("async"), but also the one before to know - // its beforeExpr value. - // It's easier and more efficient to adjust the context here. - throw new Error("Internal error"); - } - this.state.context[last] = ct.functionExpression; + if (!containsEsc && id.name === "async" && !this.canInsertSemicolon()) { + if (this.match(tt._function)) { + const last = this.state.context.length - 1; + if (this.state.context[last] !== ct.functionStatement) { + // Since "async" is an identifier and normally identifiers + // can't be followed by expression, the tokenizer assumes + // that "function" starts a statement. + // Fixing it in the tokenizer would mean tracking not only the + // previous token ("async"), but also the one before to know + // its beforeExpr value. + // It's easier and more efficient to adjust the context here. + throw new Error("Internal error"); + } + this.state.context[last] = ct.functionExpression; - this.next(); - return this.parseFunction(node, undefined, true); - } else if ( - canBeArrow && - !containsEsc && - id.name === "async" && - this.match(tt.name) && - !this.canInsertSemicolon() - ) { - const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; - const oldMaybeInAsyncArrowHead = this.state.maybeInAsyncArrowHead; - const oldYieldPos = this.state.yieldPos; - const oldAwaitPos = this.state.awaitPos; - this.state.maybeInArrowParameters = true; - this.state.maybeInAsyncArrowHead = true; - this.state.yieldPos = -1; - this.state.awaitPos = -1; - const params = [this.parseIdentifier()]; - this.expect(tt.arrow); - this.checkYieldAwaitInDefaultParams(); - this.state.maybeInArrowParameters = oldMaybeInArrowParameters; - this.state.maybeInAsyncArrowHead = oldMaybeInAsyncArrowHead; - this.state.yieldPos = oldYieldPos; - this.state.awaitPos = oldAwaitPos; - // let foo = async bar => {}; - this.parseArrowExpression(node, params, true); - return node; + this.next(); + return this.parseFunction( + this.startNodeAtNode(id), + undefined, + true, + ); + } else if (this.match(tt.name)) { + return this.parseAsyncArrowUnaryFunction(id); + } } if (canBeArrow && this.match(tt.arrow) && !this.canInsertSemicolon()) { this.next(); - this.parseArrowExpression(node, [id], false); - return node; + return this.parseArrowExpression( + this.startNodeAtNode(id), + [id], + false, + ); } return id; } case tt._do: { - this.expectPlugin("doExpressions"); - const node = this.startNode(); - this.next(); - const oldLabels = this.state.labels; - this.state.labels = []; - node.body = this.parseBlock(); - this.state.labels = oldLabels; - return this.finishNode(node, "DoExpression"); + return this.parseDo(); } case tt.regexp: { @@ -1046,68 +1054,40 @@ export default class ExpressionParser extends LValParser { case tt.bracketBarL: case tt.bracketHashL: { - this.expectPlugin("recordAndTuple"); - const oldInFSharpPipelineDirectBody = this.state - .inFSharpPipelineDirectBody; - const close = - this.state.type === tt.bracketBarL ? tt.bracketBarR : tt.bracketR; - this.state.inFSharpPipelineDirectBody = false; - node = this.startNode(); - this.next(); - node.elements = this.parseExprList( - close, - false, + return this.parseArrayLike( + this.state.type === tt.bracketBarL ? tt.bracketBarR : tt.bracketR, + /* canBePattern */ false, + /* isTuple */ true, refExpressionErrors, - node, ); - this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; - return this.finishNode(node, "TupleExpression"); } case tt.bracketL: { - const oldInFSharpPipelineDirectBody = this.state - .inFSharpPipelineDirectBody; - this.state.inFSharpPipelineDirectBody = false; - node = this.startNode(); - this.next(); - node.elements = this.parseExprList( + return this.parseArrayLike( tt.bracketR, - true, + /* canBePattern */ true, + /* isTuple */ false, refExpressionErrors, - node, ); - if (!this.state.maybeInArrowParameters) { - // This could be an array pattern: - // ([a: string, b: string]) => {} - // In this case, we don't have to call toReferencedList. We will - // call it, if needed, when we are sure that it is a parenthesized - // expression by calling toReferencedListDeep. - this.toReferencedList(node.elements); - } - this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; - return this.finishNode(node, "ArrayExpression"); } case tt.braceBarL: case tt.braceHashL: { - this.expectPlugin("recordAndTuple"); - const oldInFSharpPipelineDirectBody = this.state - .inFSharpPipelineDirectBody; - const close = - this.state.type === tt.braceBarL ? tt.braceBarR : tt.braceR; - this.state.inFSharpPipelineDirectBody = false; - const ret = this.parseObj(close, false, true, refExpressionErrors); - this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; - return ret; + return this.parseObjectLike( + this.state.type === tt.braceBarL ? tt.braceBarR : tt.braceR, + /* isPattern */ false, + /* isRecord */ true, + refExpressionErrors, + ); } case tt.braceL: { - const oldInFSharpPipelineDirectBody = this.state - .inFSharpPipelineDirectBody; - this.state.inFSharpPipelineDirectBody = false; - const ret = this.parseObj(tt.braceR, false, false, refExpressionErrors); - this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; - return ret; + return this.parseObjectLike( + tt.braceR, + /* isPattern */ false, + /* isRecord */ false, + refExpressionErrors, + ); } case tt._function: - return this.parseFunctionExpression(); + return this.parseFunctionOrFunctionSent(); case tt.at: this.parseDecorators(); @@ -1118,11 +1098,13 @@ export default class ExpressionParser extends LValParser { return this.parseClass(node, false); case tt._new: - return this.parseNew(); + return this.parseNewOrNewTarget(); case tt.backQuote: return this.parseTemplate(false); + // BindExpression[Yield] + // :: MemberExpression[?Yield] case tt.doubleColon: { node = this.startNode(); this.next(); @@ -1155,6 +1137,9 @@ export default class ExpressionParser extends LValParser { return this.finishNode(node, "PipelinePrimaryTopicReference"); } + // https://tc39.es/proposal-private-fields-in-in + // RelationalExpression [In, Yield, Await] + // [+In] PrivateIdentifier in ShiftExpression[?Yield, ?Await] const nextCh = this.input.codePointAt(this.state.end); if (isIdentifierStart(nextCh) || nextCh === charCodes.backslash) { const start = this.state.start; @@ -1193,6 +1178,72 @@ export default class ExpressionParser extends LValParser { } } + // async [no LineTerminator here] AsyncArrowBindingIdentifier[?Yield] [no LineTerminator here] => AsyncConciseBody[?In] + parseAsyncArrowUnaryFunction(id: N.Expression): N.ArrowFunctionExpression { + const node = this.startNodeAtNode(id); + const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; + const oldMaybeInAsyncArrowHead = this.state.maybeInAsyncArrowHead; + const oldYieldPos = this.state.yieldPos; + const oldAwaitPos = this.state.awaitPos; + this.state.maybeInArrowParameters = true; + this.state.maybeInAsyncArrowHead = true; + this.state.yieldPos = -1; + this.state.awaitPos = -1; + const params = [this.parseIdentifier()]; + if (this.hasPrecedingLineBreak()) { + this.raise(this.state.pos, Errors.LineTerminatorBeforeArrow); + } + this.expect(tt.arrow); + this.checkYieldAwaitInDefaultParams(); + this.state.maybeInArrowParameters = oldMaybeInArrowParameters; + this.state.maybeInAsyncArrowHead = oldMaybeInAsyncArrowHead; + this.state.yieldPos = oldYieldPos; + this.state.awaitPos = oldAwaitPos; + // let foo = async bar => {}; + this.parseArrowExpression(node, params, true); + return node; + } + + // https://github.com/tc39/proposal-do-expressions + parseDo(): N.DoExpression { + this.expectPlugin("doExpressions"); + const node = this.startNode(); + this.next(); // eat `do` + const oldLabels = this.state.labels; + this.state.labels = []; + node.body = this.parseBlock(); + this.state.labels = oldLabels; + return this.finishNode(node, "DoExpression"); + } + + // Parse the `super` keyword + parseSuper(): N.Super { + const node = this.startNode(); + this.next(); // eat `super` + if ( + this.match(tt.parenL) && + !this.scope.allowDirectSuper && + !this.options.allowSuperOutsideMethod + ) { + this.raise(node.start, Errors.SuperNotAllowed); + } else if ( + !this.scope.allowSuper && + !this.options.allowSuperOutsideMethod + ) { + this.raise(node.start, Errors.UnexpectedSuper); + } + + if ( + !this.match(tt.parenL) && + !this.match(tt.bracketL) && + !this.match(tt.dot) + ) { + this.raise(node.start, Errors.UnsupportedSuper); + } + + return this.finishNode(node, "Super"); + } + parseBooleanLiteral(): N.BooleanLiteral { const node = this.startNode(); node.value = this.match(tt._true); @@ -1220,19 +1271,22 @@ export default class ExpressionParser extends LValParser { } } - parseFunctionExpression(): N.FunctionExpression | N.MetaProperty { + parseFunctionOrFunctionSent(): N.FunctionExpression | N.MetaProperty { const node = this.startNode(); - // We do not do parseIdentifier here because when parseFunctionExpression + // We do not do parseIdentifier here because when parseFunctionOrFunctionSent // is called we already know that the current token is a "name" with the value "function" // This will improve perf a tiny little bit as we do not do validation but more importantly // here is that parseIdentifier will remove an item from the expression stack // if "function" or "class" is parsed as identifier (in objects e.g.), which should not happen here. - let meta = this.startNode(); - this.next(); - meta = this.createIdentifier(meta, "function"); + this.next(); // eat `function` - if (this.prodParam.hasYield && this.eat(tt.dot)) { + if (this.prodParam.hasYield && this.match(tt.dot)) { + const meta = this.createIdentifier( + this.startNodeAtNode(node), + "function", + ); + this.next(); // eat `.` return this.parseMetaProperty(node, meta, "sent"); } return this.parseFunction(node); @@ -1246,6 +1300,7 @@ export default class ExpressionParser extends LValParser { node.meta = meta; if (meta.name === "function" && propertyName === "sent") { + // https://github.com/tc39/proposal-function.sent#syntax-1 if (this.isContextual(propertyName)) { this.expectPlugin("functionSent"); } else if (!this.hasPlugin("functionSent")) { @@ -1270,9 +1325,10 @@ export default class ExpressionParser extends LValParser { return this.finishNode(node, "MetaProperty"); } + // https://tc39.es/ecma262/#prod-ImportMeta parseImportMetaProperty(node: N.MetaProperty): N.MetaProperty { const id = this.createIdentifier(this.startNodeAtNode(node), "import"); - this.expect(tt.dot); + this.next(); // eat `.` if (this.isContextual("meta")) { if (!this.inModule) { @@ -1305,12 +1361,13 @@ export default class ExpressionParser extends LValParser { return this.finishNode(node, type); } + // https://tc39.es/ecma262/#prod-CoverParenthesizedExpressionAndArrowParameterList parseParenAndDistinguishExpression(canBeArrow: boolean): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; let val; - this.expect(tt.parenL); + this.next(); // eat `(` const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; const oldYieldPos = this.state.yieldPos; @@ -1449,20 +1506,13 @@ export default class ExpressionParser extends LValParser { return node; } - // New's precedence is slightly tricky. It must allow its argument to - // be a `[]` or dot subscript expression, but not a call — at least, - // not without wrapping it in parentheses. Thus, it uses the noCalls - // argument to parseSubscripts to prevent it from consuming the - // argument list. - - parseNew(): N.NewExpression | N.MetaProperty { + parseNewOrNewTarget(): N.NewExpression | N.MetaProperty { const node = this.startNode(); - - let meta = this.startNode(); this.next(); - meta = this.createIdentifier(meta, "new"); - - if (this.eat(tt.dot)) { + if (this.match(tt.dot)) { + // https://tc39.es/ecma262/#prod-NewTarget + const meta = this.createIdentifier(this.startNodeAtNode(node), "new"); + this.next(); const metaProp = this.parseMetaProperty(node, meta, "target"); if (!this.scope.inNonArrowFunction && !this.scope.inClass) { @@ -1480,6 +1530,16 @@ export default class ExpressionParser extends LValParser { return metaProp; } + return this.parseNew(node); + } + + // New's precedence is slightly tricky. It must allow its argument to + // be a `[]` or dot subscript expression, but not a call — at least, + // not without wrapping it in parentheses. Thus, it uses the noCalls + // argument to parseSubscripts to prevent it from consuming the + // argument list. + // https://tc39.es/ecma262/#prod-NewExpression + parseNew(node: N.Expression): N.NewExpression { node.callee = this.parseNoCallExpr(); if (node.callee.type === "Import") { @@ -1528,6 +1588,7 @@ export default class ExpressionParser extends LValParser { return this.finishNode(elem, "TemplateElement"); } + // https://tc39.es/ecma262/#prod-TemplateLiteral parseTemplate(isTagged: boolean): N.TemplateLiteral { const node = this.startNode(); this.next(); @@ -1546,12 +1607,17 @@ export default class ExpressionParser extends LValParser { // Parse an object literal, binding pattern, or record. - parseObj( + parseObjectLike( close: TokenType, isPattern: boolean, isRecord?: ?boolean, refExpressionErrors?: ?ExpressionErrors, ): T { + if (isRecord) { + this.expectPlugin("recordAndTuple"); + } + const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody; + this.state.inFSharpPipelineDirectBody = false; const propHash: any = Object.create(null); let first = true; const node = this.startNode(); @@ -1571,7 +1637,7 @@ export default class ExpressionParser extends LValParser { } } - const prop = this.parseObjectMember(isPattern, refExpressionErrors); + const prop = this.parsePropertyDefinition(isPattern, refExpressionErrors); if (!isPattern) { // $FlowIgnore RestElement will never be returned if !isPattern this.checkProto(prop, isRecord, propHash, refExpressionErrors); @@ -1593,6 +1659,7 @@ export default class ExpressionParser extends LValParser { node.properties.push(prop); } + this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; let type = "ObjectExpression"; if (isPattern) { type = "ObjectPattern"; @@ -1602,19 +1669,21 @@ export default class ExpressionParser extends LValParser { return this.finishNode(node, type); } - isAsyncProp(prop: N.ObjectProperty): boolean { + // Check grammar production: + // IdentifierName *_opt PropertyName + // It is used in `parsePropertyDefinition` to detect AsyncMethod and Accessors + maybeAsyncOrAccessorProp(prop: N.ObjectProperty): boolean { return ( !prop.computed && prop.key.type === "Identifier" && - prop.key.name === "async" && (this.isLiteralPropertyName() || this.match(tt.bracketL) || - this.match(tt.star)) && - !this.hasPrecedingLineBreak() + this.match(tt.star)) ); } - parseObjectMember( + // https://tc39.es/ecma262/#prod-PropertyDefinition + parsePropertyDefinition( isPattern: boolean, refExpressionErrors?: ?ExpressionErrors, ): N.ObjectMember | N.SpreadElement | N.RestElement { @@ -1634,6 +1703,7 @@ export default class ExpressionParser extends LValParser { const prop = this.startNode(); let isGenerator = false; let isAsync = false; + let isAccessor = false; let startPos; let startLoc; @@ -1669,12 +1739,27 @@ export default class ExpressionParser extends LValParser { const containsEsc = this.state.containsEsc; this.parsePropertyName(prop, /* isPrivateNameAllowed */ false); - if (!isPattern && !containsEsc && !isGenerator && this.isAsyncProp(prop)) { - isAsync = true; - isGenerator = this.eat(tt.star); - this.parsePropertyName(prop, /* isPrivateNameAllowed */ false); - } else { - isAsync = false; + if ( + !isPattern && + !isGenerator && + !containsEsc && + this.maybeAsyncOrAccessorProp(prop) + ) { + // https://tc39.es/ecma262/#prod-AsyncMethod + // https://tc39.es/ecma262/#prod-AsyncGeneratorMethod + if (prop.key.name === "async" && !this.hasPrecedingLineBreak()) { + isAsync = true; + isGenerator = this.eat(tt.star); + this.parsePropertyName(prop, /* isPrivateNameAllowed */ false); + } + // get PropertyName[?Yield, ?Await] () { FunctionBody[~Yield, ~Await] } + // set PropertyName[?Yield, ?Await] ( PropertySetParameterList ) { FunctionBody[~Yield, ~Await] } + if (prop.key.name === "get" || prop.key.name === "set") { + isAccessor = true; + isGenerator = this.eat(tt.star); // tt.star is allowed in `maybeAsyncOrAccessorProp`, we will throw in `parseObjectMethod` later + prop.kind = prop.key.name; + this.parsePropertyName(prop, /* isPrivateNameAllowed */ false); + } } this.parseObjPropValue( @@ -1684,24 +1769,13 @@ export default class ExpressionParser extends LValParser { isGenerator, isAsync, isPattern, + isAccessor, refExpressionErrors, - containsEsc, ); return prop; } - isGetterOrSetterMethod(prop: N.ObjectMethod, isPattern: boolean): boolean { - return ( - !isPattern && - !prop.computed && - prop.key.type === "Identifier" && - (prop.key.name === "get" || prop.key.name === "set") && - (this.isLiteralPropertyName() || // get foo() {} - this.match(tt.bracketL)) // get ["string"]() {} - ); - } - getGetterSetterExpectedParamCount( method: N.ObjectMethod | N.ClassMethod, ): number { @@ -1729,13 +1803,29 @@ export default class ExpressionParser extends LValParser { } } + // https://tc39.es/ecma262/#prod-MethodDefinition parseObjectMethod( prop: N.ObjectMethod, isGenerator: boolean, isAsync: boolean, isPattern: boolean, - containsEsc: boolean, + isAccessor: boolean, ): ?N.ObjectMethod { + if (isAccessor) { + // isAccessor implies isAsync: false, isPattern: false + if (isGenerator) this.unexpected(); + this.parseMethod( + prop, + /* isGenerator */ false, + /* isAsync */ false, + /* isConstructor */ false, + false, + "ObjectMethod", + ); + this.checkGetterSetterParams(prop); + return prop; + } + if (isAsync || isGenerator || this.match(tt.parenL)) { if (isPattern) this.unexpected(); prop.kind = "method"; @@ -1749,24 +1839,10 @@ export default class ExpressionParser extends LValParser { "ObjectMethod", ); } - - if (!containsEsc && this.isGetterOrSetterMethod(prop, isPattern)) { - if (isGenerator || isAsync) this.unexpected(); - prop.kind = prop.key.name; - this.parsePropertyName(prop, /* isPrivateNameAllowed */ false); - this.parseMethod( - prop, - /* isGenerator */ false, - /* isAsync */ false, - /* isConstructor */ false, - false, - "ObjectMethod", - ); - this.checkGetterSetterParams(prop); - return prop; - } } + // if `isPattern` is true, parse https://tc39.es/ecma262/#prod-BindingProperty + // else https://tc39.es/ecma262/#prod-PropertyDefinition parseObjectProperty( prop: N.ObjectProperty, startPos: ?number, @@ -1822,8 +1898,8 @@ export default class ExpressionParser extends LValParser { isGenerator: boolean, isAsync: boolean, isPattern: boolean, + isAccessor: boolean, refExpressionErrors?: ?ExpressionErrors, - containsEsc: boolean, ): void { const node = this.parseObjectMethod( @@ -1831,7 +1907,7 @@ export default class ExpressionParser extends LValParser { isGenerator, isAsync, isPattern, - containsEsc, + isAccessor, ) || this.parseObjectProperty( prop, @@ -1923,6 +1999,43 @@ export default class ExpressionParser extends LValParser { return node; } + // parse an array literal or tuple literal + // https://tc39.es/ecma262/#prod-ArrayLiteral + // https://tc39.es/proposal-record-tuple/#prod-TupleLiteral + parseArrayLike( + close: TokenType, + canBePattern: boolean, + isTuple: boolean, + refExpressionErrors: ?ExpressionErrors, + ): N.ArrayExpression | N.TupleExpression { + if (isTuple) { + this.expectPlugin("recordAndTuple"); + } + const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody; + this.state.inFSharpPipelineDirectBody = false; + const node = this.startNode(); + this.next(); + node.elements = this.parseExprList( + close, + /* allowEmpty */ !isTuple, + refExpressionErrors, + node, + ); + if (canBePattern && !this.state.maybeInArrowParameters) { + // This could be an array pattern: + // ([a: string, b: string]) => {} + // In this case, we don't have to call toReferencedList. We will + // call it, if needed, when we are sure that it is a parenthesized + // expression by calling toReferencedListDeep. + this.toReferencedList(node.elements); + } + this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; + return this.finishNode( + node, + isTuple ? "TupleExpression" : "ArrayExpression", + ); + } + // Parse arrow function expression. // If the parameters are provided, they will be converted to an // assignable list. diff --git a/packages/babel-parser/src/parser/lval.js b/packages/babel-parser/src/parser/lval.js index a952219dd4..e9b5e67589 100644 --- a/packages/babel-parser/src/parser/lval.js +++ b/packages/babel-parser/src/parser/lval.js @@ -235,15 +235,18 @@ export default class LValParser extends NodeUtils { return this.finishNode(node, "SpreadElement"); } + // https://tc39.es/ecma262/#prod-BindingRestProperty + // https://tc39.es/ecma262/#prod-BindingRestElement parseRestBinding(): RestElement { const node = this.startNode(); - this.next(); + this.next(); // eat `...` node.argument = this.parseBindingAtom(); return this.finishNode(node, "RestElement"); } // Parses lvalue (assignable) atom. parseBindingAtom(): Pattern { + // https://tc39.es/ecma262/#prod-BindingPattern switch (this.state.type) { case tt.bracketL: { const node = this.startNode(); @@ -257,12 +260,14 @@ export default class LValParser extends NodeUtils { } case tt.braceL: - return this.parseObj(tt.braceR, true); + return this.parseObjectLike(tt.braceR, true); } + // https://tc39.es/ecma262/#prod-BindingIdentifier return this.parseIdentifier(); } + // https://tc39.es/ecma262/#prod-BindingElementList parseBindingList( close: TokenType, closeCharCode: $Values, @@ -292,6 +297,7 @@ export default class LValParser extends NodeUtils { if (this.match(tt.at) && this.hasPlugin("decorators")) { this.raise(this.state.start, Errors.UnsupportedParameterDecorator); } + // invariant: hasPlugin("decorators-legacy") while (this.match(tt.at)) { decorators.push(this.parseDecorator()); } @@ -314,20 +320,22 @@ export default class LValParser extends NodeUtils { return elt; } + // Used by flow/typescript plugin to add type annotations to binding elements parseAssignableListItemTypes(param: Pattern): Pattern { return param; } // Parses assignment pattern around given atom if possible. - + // https://tc39.es/ecma262/#prod-BindingElement parseMaybeDefault( startPos?: ?number, startLoc?: ?Position, left?: ?Pattern, ): Pattern { - startLoc = startLoc || this.state.startLoc; - startPos = startPos || this.state.start; - left = left || this.parseBindingAtom(); + startLoc = startLoc ?? this.state.startLoc; + startPos = startPos ?? this.state.start; + // $FlowIgnore + left = left ?? this.parseBindingAtom(); if (!this.eat(tt.eq)) return left; const node = this.startNodeAt(startPos, startLoc); diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 224515cd76..8f584240d8 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -141,7 +141,9 @@ export default class StatementParser extends ExpressionParser { // regular expression literal. This is to handle cases like // `if (foo) /blah/.exec(foo)`, where looking at the previous token // does not help. - + // https://tc39.es/ecma262/#prod-Statement + // ImportDeclaration and ExportDeclaration are also handled here so we can throw recoverable errors + // when they are not at the top level parseStatement(context: ?string, topLevel?: boolean): N.Statement { if (this.match(tt.at)) { this.parseDecorators(true); @@ -216,21 +218,22 @@ export default class StatementParser extends ExpressionParser { return this.parseBlock(); case tt.semi: return this.parseEmptyStatement(node); - case tt._export: case tt._import: { const nextTokenCharCode = this.lookaheadCharCode(); if ( - nextTokenCharCode === charCodes.leftParenthesis || - nextTokenCharCode === charCodes.dot + nextTokenCharCode === charCodes.leftParenthesis || // import() + nextTokenCharCode === charCodes.dot // import.meta ) { break; } - + } + // fall through + case tt._export: { if (!this.options.allowImportExportEverywhere && !topLevel) { this.raise(this.state.start, Errors.UnexpectedImportExport); } - this.next(); + this.next(); // eat `import`/`export` let result; if (starttype === tt._import) { @@ -847,6 +850,8 @@ export default class StatementParser extends ExpressionParser { } // Undefined directives means that directives are not allowed. + // https://tc39.es/ecma262/#prod-Block + // https://tc39.es/ecma262/#prod-ModuleBody parseBlockOrModuleBlockBody( body: N.Statement[], directives: ?(N.Directive[]), @@ -1161,10 +1166,9 @@ export default class StatementParser extends ExpressionParser { this.parseClassId(node, isStatement, optionalId); this.parseClassSuper(node); + // this.state.strict is restored in parseClassBody node.body = this.parseClassBody(!!node.superClass, oldStrict); - this.state.strict = oldStrict; - return this.finishNode( node, isStatement ? "ClassDeclaration" : "ClassExpression", @@ -1188,6 +1192,7 @@ export default class StatementParser extends ExpressionParser { ); } + // https://tc39.es/ecma262/#prod-ClassBody parseClassBody( constructorAllowsSuper: boolean, oldStrict?: boolean, @@ -1238,11 +1243,9 @@ export default class StatementParser extends ExpressionParser { } }); - if (!oldStrict) { - this.state.strict = false; - } + this.state.strict = oldStrict; - this.next(); + this.next(); // eat `}` if (decorators.length) { throw this.raise(this.state.start, Errors.TrailingDecorator); @@ -1253,13 +1256,26 @@ export default class StatementParser extends ExpressionParser { return this.finishNode(classBody, "ClassBody"); } + // Check grammar production: + // IdentifierName *_opt ClassElementName + // It is used in `parsePropertyDefinition` to detect AsyncMethod and Accessors + maybeClassModifier(prop: N.ObjectProperty): boolean { + return ( + !prop.computed && + prop.key.type === "Identifier" && + (this.isLiteralPropertyName() || + this.match(tt.bracketL) || + this.match(tt.star) || + this.match(tt.hash)) + ); + } + // returns true if the current identifier is a method/field name, // false if it is a modifier parseClassMemberFromModifier( classBody: N.ClassBody, member: N.ClassMember, ): boolean { - const containsEsc = this.state.containsEsc; const key = this.parseIdentifier(true); // eats the modifier if (this.isClassMethod()) { @@ -1288,10 +1304,7 @@ export default class StatementParser extends ExpressionParser { prop.static = false; classBody.body.push(this.parseClassProperty(prop)); return true; - } else if (containsEsc) { - throw this.unexpected(); } - return false; } @@ -1337,7 +1350,7 @@ export default class StatementParser extends ExpressionParser { if (this.eat(tt.star)) { // a generator method.kind = "method"; - this.parseClassPropertyName(method); + this.parseClassElementName(method); if (method.key.type === "PrivateName") { // Private generator method @@ -1362,7 +1375,7 @@ export default class StatementParser extends ExpressionParser { } const containsEsc = this.state.containsEsc; - const key = this.parseClassPropertyName(member); + const key = this.parseClassElementName(member); const isPrivate = key.type === "PrivateName"; // Check the key is not a computed expression or string literal. const isSimple = key.type === "Identifier"; @@ -1421,7 +1434,7 @@ export default class StatementParser extends ExpressionParser { method.kind = "method"; // The so-called parsed name would have been "async": get the real name. - this.parseClassPropertyName(method); + this.parseClassElementName(method); this.parsePostMemberNameModifiers(publicMember); if (method.key.type === "PrivateName") { @@ -1456,7 +1469,7 @@ export default class StatementParser extends ExpressionParser { // a getter or setter method.kind = key.name; // The so-called parsed name would have been "get/set": get the real name. - this.parseClassPropertyName(publicMethod); + this.parseClassElementName(publicMethod); if (method.key.type === "PrivateName") { // private getter/setter @@ -1488,7 +1501,8 @@ export default class StatementParser extends ExpressionParser { } } - parseClassPropertyName(member: N.ClassMember): N.Expression | N.Identifier { + // https://tc39.es/proposal-class-fields/#prod-ClassElementName + parseClassElementName(member: N.ClassMember): N.Expression | N.Identifier { const key = this.parsePropertyName(member, /* isPrivateNameAllowed */ true); if ( @@ -1660,11 +1674,13 @@ export default class StatementParser extends ExpressionParser { } } + // https://tc39.es/ecma262/#prod-ClassHeritage parseClassSuper(node: N.Class): void { node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null; } // Parses module export declaration. + // https://tc39.es/ecma262/#prod-ExportDeclaration parseExport(node: N.Node): N.AnyExport { const hasDefault = this.maybeParseExportDefaultSpecifier(node); @@ -1839,7 +1855,7 @@ export default class StatementParser extends ExpressionParser { isExportDefaultSpecifier(): boolean { if (this.match(tt.name)) { const value = this.state.value; - if (value === "async" || value === "let") { + if ((value === "async" && !this.state.containsEsc) || value === "let") { return false; } if ( @@ -2068,6 +2084,7 @@ export default class StatementParser extends ExpressionParser { } // Parses import declaration. + // https://tc39.es/ecma262/#prod-ImportDeclaration parseImport(node: N.Node): N.AnyImport { // import '...' @@ -2232,6 +2249,7 @@ export default class StatementParser extends ExpressionParser { } } + // https://tc39.es/ecma262/#prod-ImportSpecifier parseImportSpecifier(node: N.ImportDeclaration): void { const specifier = this.startNode(); specifier.imported = this.parseIdentifier(true); diff --git a/packages/babel-parser/src/plugins/estree.js b/packages/babel-parser/src/plugins/estree.js index 86c5872d43..06d79f3835 100644 --- a/packages/babel-parser/src/plugins/estree.js +++ b/packages/babel-parser/src/plugins/estree.js @@ -313,14 +313,14 @@ export default (superClass: Class): Class => isGenerator: boolean, isAsync: boolean, isPattern: boolean, - containsEsc: boolean, + isAccessor: boolean, ): ?N.ObjectMethod { const node: N.EstreeProperty = (super.parseObjectMethod( prop, isGenerator, isAsync, isPattern, - containsEsc, + isAccessor, ): any); if (node) { diff --git a/packages/babel-parser/src/plugins/flow.js b/packages/babel-parser/src/plugins/flow.js index 9e052f4e80..a5e597cc79 100644 --- a/packages/babel-parser/src/plugins/flow.js +++ b/packages/babel-parser/src/plugins/flow.js @@ -2362,8 +2362,8 @@ export default (superClass: Class): Class => isGenerator: boolean, isAsync: boolean, isPattern: boolean, + isAccessor: boolean, refExpressionErrors: ?ExpressionErrors, - containsEsc: boolean, ): void { if ((prop: $FlowFixMe).variance) { this.unexpected((prop: $FlowFixMe).variance.start); @@ -2373,7 +2373,7 @@ export default (superClass: Class): Class => let typeParameters; // method shorthand - if (this.isRelational("<")) { + if (this.isRelational("<") && !isAccessor) { typeParameters = this.flowParseTypeParameterDeclaration(); if (!this.match(tt.parenL)) this.unexpected(); } @@ -2385,8 +2385,8 @@ export default (superClass: Class): Class => isGenerator, isAsync, isPattern, + isAccessor, refExpressionErrors, - containsEsc, ); // add typeParameters if we found them diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index bb3a5a10cc..a9b3bf2e8b 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -1823,13 +1823,14 @@ export default (superClass: Class): Class => node.typeParameters = typeArguments; return this.finishCallExpression(node, state.optionalChainMember); } else if (this.match(tt.backQuote)) { - return this.parseTaggedTemplateExpression( + const result = this.parseTaggedTemplateExpression( + base, startPos, startLoc, - base, state, - typeArguments, ); + result.typeParameters = typeArguments; + return result; } } diff --git a/packages/babel-parser/src/types.js b/packages/babel-parser/src/types.js index 3566baaafb..6d77c5c0d0 100644 --- a/packages/babel-parser/src/types.js +++ b/packages/babel-parser/src/types.js @@ -389,6 +389,11 @@ export type ArrayExpression = NodeBase & { elements: $ReadOnlyArray, }; +export type DoExpression = NodeBase & { + type: "DoExpression", + body: ?BlockStatement, +}; + export type TupleExpression = NodeBase & { type: "TupleExpression", elements: $ReadOnlyArray, diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-escape-export-async-function/options.json b/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-escape-export-async-function/options.json index f2ecbd9741..9d60866b80 100644 --- a/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-escape-export-async-function/options.json +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-escape-export-async-function/options.json @@ -1,3 +1,3 @@ { "throws": "Unexpected token, expected \"{\" (1:7)" -} +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-arrow/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-arrow/input.js new file mode 100644 index 0000000000..0b304c87df --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-arrow/input.js @@ -0,0 +1,2 @@ +async x +=> x diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-arrow/output.json b/packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-arrow/output.json new file mode 100644 index 0000000000..19ade021f4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-arrow/output.json @@ -0,0 +1,39 @@ +{ + "type": "File", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":4}}, + "errors": [ + "SyntaxError: No line break is allowed before '=>' (2:2)" + ], + "program": { + "type": "Program", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":4}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":4}}, + "expression": { + "type": "ArrowFunctionExpression", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":4}}, + "id": null, + "generator": false, + "async": true, + "params": [ + { + "type": "Identifier", + "start":6,"end":7,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":7},"identifierName":"x"}, + "name": "x" + } + ], + "body": { + "type": "Identifier", + "start":11,"end":12,"loc":{"start":{"line":2,"column":3},"end":{"line":2,"column":4},"identifierName":"x"}, + "name": "x" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/newline-arrow/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-binding/input.js similarity index 100% rename from packages/babel-parser/test/fixtures/es2017/async-functions/newline-arrow/input.js rename to packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-binding/input.js diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/newline-arrow/output.json b/packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-binding/output.json similarity index 100% rename from packages/babel-parser/test/fixtures/es2017/async-functions/newline-arrow/output.json rename to packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-binding/output.json diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/newline/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-function/input.js similarity index 100% rename from packages/babel-parser/test/fixtures/es2017/async-functions/newline/input.js rename to packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-function/input.js diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/newline/output.json b/packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-function/output.json similarity index 100% rename from packages/babel-parser/test/fixtures/es2017/async-functions/newline/output.json rename to packages/babel-parser/test/fixtures/es2017/async-functions/newline-before-function/output.json diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-and-ns/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-and-ns/options.json deleted file mode 100644 index fb17f33a28..0000000000 --- a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-and-ns/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["exportDefaultFrom"], - "sourceType": "module" -} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/options.json deleted file mode 100644 index 164d90422f..0000000000 --- a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default-asi/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "sourceType": "module", - "plugins": ["exportDefaultFrom"] -} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default/options.json deleted file mode 100644 index 164d90422f..0000000000 --- a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-default/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "sourceType": "module", - "plugins": ["exportDefaultFrom"] -} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-escaped/input.js b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-escaped/input.js new file mode 100644 index 0000000000..1b9fbd91fe --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-escaped/input.js @@ -0,0 +1 @@ +export asyn\u{63} from "async"; diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-escaped/output.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-escaped/output.json new file mode 100644 index 0000000000..33806b3628 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-escaped/output.json @@ -0,0 +1,37 @@ +{ + "type": "File", + "start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":31}}, + "program": { + "type": "Program", + "start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":31}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ExportNamedDeclaration", + "start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":31}}, + "specifiers": [ + { + "type": "ExportDefaultSpecifier", + "start":7,"end":17,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":17}}, + "exported": { + "type": "Identifier", + "start":7,"end":17,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":17},"identifierName":"async"}, + "name": "async" + } + } + ], + "source": { + "type": "StringLiteral", + "start":23,"end":30,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":30}}, + "extra": { + "rawValue": "async", + "raw": "\"async\"" + }, + "value": "async" + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/options.json deleted file mode 100644 index fb17f33a28..0000000000 --- a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-escaped/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["exportDefaultFrom"], - "sourceType": "module" -} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/options.json deleted file mode 100644 index fb17f33a28..0000000000 --- a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-from-identifier-parenthesized/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["exportDefaultFrom"], - "sourceType": "module" -} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-type-without-flow/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default-type-without-flow/options.json deleted file mode 100644 index fb17f33a28..0000000000 --- a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-type-without-flow/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["exportDefaultFrom"], - "sourceType": "module" -} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/default/options.json deleted file mode 100644 index fb17f33a28..0000000000 --- a/packages/babel-parser/test/fixtures/experimental/export-extensions/default/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["exportDefaultFrom"], - "sourceType": "module" -} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/options.json deleted file mode 100644 index fb17f33a28..0000000000 --- a/packages/babel-parser/test/fixtures/experimental/export-extensions/invalid-default-from-identifier/options.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["exportDefaultFrom"], - "sourceType": "module" -} diff --git a/packages/babel-parser/test/fixtures/experimental/export-extensions/default-and-named/options.json b/packages/babel-parser/test/fixtures/experimental/export-extensions/options.json similarity index 100% rename from packages/babel-parser/test/fixtures/experimental/export-extensions/default-and-named/options.json rename to packages/babel-parser/test/fixtures/experimental/export-extensions/options.json diff --git a/packages/babel-parser/test/fixtures/flow/type-annotations/20/output.json b/packages/babel-parser/test/fixtures/flow/type-annotations/20/output.json index 82b7ad2a96..32481b6a89 100644 --- a/packages/babel-parser/test/fixtures/flow/type-annotations/20/output.json +++ b/packages/babel-parser/test/fixtures/flow/type-annotations/20/output.json @@ -34,7 +34,6 @@ }, "computed": false, "kind": "set", - "variance": null, "id": null, "generator": false, "async": false, diff --git a/packages/babel-parser/test/fixtures/flow/type-annotations/21/output.json b/packages/babel-parser/test/fixtures/flow/type-annotations/21/output.json index 1ac23f768b..06c830a318 100644 --- a/packages/babel-parser/test/fixtures/flow/type-annotations/21/output.json +++ b/packages/babel-parser/test/fixtures/flow/type-annotations/21/output.json @@ -34,7 +34,6 @@ }, "computed": false, "kind": "set", - "variance": null, "id": null, "generator": false, "async": false, diff --git a/packages/babel-parser/test/fixtures/flow/type-annotations/22/output.json b/packages/babel-parser/test/fixtures/flow/type-annotations/22/output.json index d9e9cf0243..6c446c3120 100644 --- a/packages/babel-parser/test/fixtures/flow/type-annotations/22/output.json +++ b/packages/babel-parser/test/fixtures/flow/type-annotations/22/output.json @@ -34,7 +34,6 @@ }, "computed": false, "kind": "get", - "variance": null, "id": null, "generator": false, "async": false,