diff --git a/.eslintignore b/.eslintignore index 7980c46aa7..ab39f96045 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,4 +3,5 @@ build coverage lib node_modules -test +test/expressions +test/fixtures diff --git a/.eslintrc b/.eslintrc index abf5884e56..22749b4b7c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,12 @@ { "root": true, "extends": "babel", + "plugins": [ + "prettier" + ], + "rules": { + "prettier/prettier": ["error", { "trailingComma": "all" }] + }, "env": { "node": true } diff --git a/bin/babylon.js b/bin/babylon.js index 449ddfed00..440eef35ff 100755 --- a/bin/babylon.js +++ b/bin/babylon.js @@ -2,7 +2,7 @@ /* eslint no-var: 0 */ var babylon = require(".."); -var fs = require("fs"); +var fs = require("fs"); var filename = process.argv[2]; if (!filename) { @@ -11,6 +11,6 @@ if (!filename) { } var file = fs.readFileSync(filename, "utf8"); -var ast = babylon.parse(file); +var ast = babylon.parse(file); console.log(JSON.stringify(ast, null, " ")); diff --git a/bin/generate-identifier-regex.js b/bin/generate-identifier-regex.js index 9b0ae7d6b6..95641eafe0 100644 --- a/bin/generate-identifier-regex.js +++ b/bin/generate-identifier-regex.js @@ -3,20 +3,23 @@ // Which Unicode version should be used? const version = "9.0.0"; -const start = require("unicode-" + version + "/Binary_Property/ID_Start/code-points.js") - .filter(function(ch) { return ch > 0x7f; }); +const start = require("unicode-" + + version + + "/Binary_Property/ID_Start/code-points.js").filter(function(ch) { + return ch > 0x7f; +}); let last = -1; const cont = [0x200c, 0x200d].concat( - require("unicode-" + version + "/Binary_Property/ID_Continue/code-points.js") - .filter(function(ch) { - return ch > 0x7f && search(start, ch, last + 1) == -1; - }) - ); + require("unicode-" + + version + + "/Binary_Property/ID_Continue/code-points.js").filter(function(ch) { + return ch > 0x7f && search(start, ch, last + 1) == -1; + }), +); function search(arr, ch, starting) { for (let i = starting; arr[i] <= ch && i < arr.length; last = i++) - if (arr[i] === ch) - return i; + if (arr[i] === ch) return i; return -1; } @@ -56,7 +59,13 @@ function generate(chars) { const startData = generate(start); const contData = generate(cont); -console.log("let nonASCIIidentifierStartChars = \"" + startData.nonASCII + "\";"); -console.log("let nonASCIIidentifierChars = \"" + contData.nonASCII + "\";"); -console.log("const astralIdentifierStartCodes = " + JSON.stringify(startData.astral) + ";"); -console.log("const astralIdentifierCodes = " + JSON.stringify(contData.astral) + ";"); +console.log('let nonASCIIidentifierStartChars = "' + startData.nonASCII + '";'); +console.log('let nonASCIIidentifierChars = "' + contData.nonASCII + '";'); +console.log( + "const astralIdentifierStartCodes = " + + JSON.stringify(startData.astral) + + ";", +); +console.log( + "const astralIdentifierCodes = " + JSON.stringify(contData.astral) + ";", +); diff --git a/package.json b/package.json index 8f6b0b6220..2e2d043f5d 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,14 @@ "chalk": "^1.1.3", "cross-env": "^5.0.0", "eslint": "^4.0.0", - "eslint-config-babel": "^7.0.0", + "eslint-config-babel": "^7.0.1", "eslint-plugin-flowtype": "^2.34.0", + "eslint-plugin-prettier": "^2.1.2", "flow-bin": "^0.47.0", + "husky": "^0.14.1", + "lint-staged": "^4.0.0", "nyc": "^11.0.2", + "prettier": "^1.5.2", "rimraf": "^2.5.4", "rollup": "^0.42.0", "rollup-plugin-babel": "3.0.0-alpha.12", @@ -51,7 +55,7 @@ "changelog": "git log `git describe --tags --abbrev=0`..HEAD --pretty=format:' * %s (%an)' | grep -v 'Merge pull request'", "clean": "rimraf lib", "flow": "flow", - "lint": "eslint src bin", + "lint": "eslint src bin test", "prepublish": "cross-env BABEL_ENV=production yarn run build", "preversion": "yarn run test && npm run changelog", "test": "yarn run lint && yarn run flow && yarn run build -- -m && yarn run test-only", @@ -86,5 +90,11 @@ "Tag: New Feature": ":rocket: New Feature", "Tag: Polish": ":nail_care: Polish" } + }, + "lint-staged": { + "*.js": [ + "eslint --format=codeframe --fix", + "git add" + ] } } diff --git a/scripts/yarn-install.js b/scripts/yarn-install.js new file mode 100644 index 0000000000..079cfc83b5 --- /dev/null +++ b/scripts/yarn-install.js @@ -0,0 +1,13 @@ +"use strict"; + +const exec = require("child_process").exec; + +const runIfYarn = fn => { + exec("yarn -V", error => { + if (error === null) fn(); + }); +}; +runIfYarn(() => { + console.log("`package.json` was changed. Running yarn...🐈"); + exec("yarn"); +}); diff --git a/src/index.js b/src/index.js index d265f2c538..7a59f52e90 100755 --- a/src/index.js +++ b/src/index.js @@ -37,46 +37,54 @@ export function parseExpression(input: string, options?: Options): Expression { return parser.getExpression(); } - export { tokTypes }; function getParser(options: ?Options, input: string): Parser { - const cls = options && options.plugins ? getParserClass(options.plugins) : Parser; + const cls = + options && options.plugins ? getParserClass(options.plugins) : Parser; return new cls(options, input); } const parserClassCache: { [key: string]: Class } = {}; /** Get a Parser class with plugins applied. */ -function getParserClass(pluginsFromOptions: $ReadOnlyArray): Class { - - if (pluginsFromOptions.indexOf("decorators") >= 0 && pluginsFromOptions.indexOf("decorators2") >= 0) { +function getParserClass( + pluginsFromOptions: $ReadOnlyArray, +): Class { + if ( + pluginsFromOptions.indexOf("decorators") >= 0 && + pluginsFromOptions.indexOf("decorators2") >= 0 + ) { throw new Error("Cannot use decorators and decorators2 plugin together"); } // Filter out just the plugins that have an actual mixin associated with them. - let pluginList = pluginsFromOptions.filter((p) => - p === "estree" || p === "flow" || p === "jsx" || p === "typescript"); + let pluginList = pluginsFromOptions.filter( + p => p === "estree" || p === "flow" || p === "jsx" || p === "typescript", + ); if (pluginList.indexOf("flow") >= 0) { // ensure flow plugin loads last - pluginList = pluginList.filter((plugin) => plugin !== "flow"); + pluginList = pluginList.filter(plugin => plugin !== "flow"); pluginList.push("flow"); } - if (pluginList.indexOf("flow") >= 0 && pluginList.indexOf("typescript") >= 0) { + if ( + pluginList.indexOf("flow") >= 0 && + pluginList.indexOf("typescript") >= 0 + ) { throw new Error("Cannot combine flow and typescript plugins."); } if (pluginList.indexOf("typescript") >= 0) { // ensure typescript plugin loads last - pluginList = pluginList.filter((plugin) => plugin !== "typescript"); + pluginList = pluginList.filter(plugin => plugin !== "typescript"); pluginList.push("typescript"); } if (pluginList.indexOf("estree") >= 0) { // ensure estree plugin loads first - pluginList = pluginList.filter((plugin) => plugin !== "estree"); + pluginList = pluginList.filter(plugin => plugin !== "estree"); pluginList.unshift("estree"); } diff --git a/src/options.js b/src/options.js index f789cae388..74ff079aeb 100755 --- a/src/options.js +++ b/src/options.js @@ -4,16 +4,16 @@ // the parser process. These options are recognized: export type Options = { - sourceType: "script" | "module"; - sourceFilename?: string; - startLine: number; - allowReturnOutsideFunction: boolean; - allowImportExportEverywhere: boolean; - allowSuperOutsideMethod: boolean; - plugins: $ReadOnlyArray; - strictMode: ?boolean; - ranges: boolean; - tokens: boolean; + sourceType: "script" | "module", + sourceFilename?: string, + startLine: number, + allowReturnOutsideFunction: boolean, + allowImportExportEverywhere: boolean, + allowSuperOutsideMethod: boolean, + plugins: $ReadOnlyArray, + strictMode: ?boolean, + ranges: boolean, + tokens: boolean, }; export const defaultOptions: Options = { diff --git a/src/parser/comments.js b/src/parser/comments.js index 328c4d9abd..b3d4925927 100644 --- a/src/parser/comments.js +++ b/src/parser/comments.js @@ -66,7 +66,11 @@ export default class CommentsParser extends BaseParser { } } else { const lastInStack = last(stack); - if (stack.length > 0 && lastInStack.trailingComments && lastInStack.trailingComments[0].start >= node.end) { + if ( + stack.length > 0 && + lastInStack.trailingComments && + lastInStack.trailingComments[0].start >= node.end + ) { trailingComments = lastInStack.trailingComments; lastInStack.trailingComments = null; } @@ -93,7 +97,10 @@ export default class CommentsParser extends BaseParser { if (lastComment.start >= node.start) { if (this.state.commentPreviousNode) { for (j = 0; j < this.state.leadingComments.length; j++) { - if (this.state.leadingComments[j].end < this.state.commentPreviousNode.end) { + if ( + this.state.leadingComments[j].end < + this.state.commentPreviousNode.end + ) { this.state.leadingComments.splice(j, 1); j--; } @@ -105,10 +112,18 @@ export default class CommentsParser extends BaseParser { } } } - } else if (node.type === "CallExpression" && node.arguments && node.arguments.length) { + } else if ( + node.type === "CallExpression" && + node.arguments && + node.arguments.length + ) { const lastArg = last(node.arguments); - if (lastArg && lastComment.start >= lastArg.start && lastComment.end <= node.end) { + if ( + lastArg && + lastComment.start >= lastArg.start && + lastComment.end <= node.end + ) { if (this.state.commentPreviousNode) { if (this.state.leadingComments.length > 0) { lastArg.trailingComments = this.state.leadingComments; @@ -121,7 +136,10 @@ export default class CommentsParser extends BaseParser { if (lastChild) { if (lastChild.leadingComments) { - if (lastChild !== node && last(lastChild.leadingComments).end <= node.start) { + if ( + lastChild !== node && + last(lastChild.leadingComments).end <= node.start + ) { node.leadingComments = lastChild.leadingComments; lastChild.leadingComments = null; } else { @@ -140,7 +158,10 @@ export default class CommentsParser extends BaseParser { if (last(this.state.leadingComments).end <= node.start) { if (this.state.commentPreviousNode) { for (j = 0; j < this.state.leadingComments.length; j++) { - if (this.state.leadingComments[j].end < this.state.commentPreviousNode.end) { + if ( + this.state.leadingComments[j].end < + this.state.commentPreviousNode.end + ) { this.state.leadingComments.splice(j, 1); j--; } @@ -173,7 +194,8 @@ export default class CommentsParser extends BaseParser { // result in an empty array, and if so, the array must be // deleted. const leadingComments = this.state.leadingComments.slice(0, i); - node.leadingComments = leadingComments.length === 0 ? null : leadingComments; + node.leadingComments = + leadingComments.length === 0 ? null : leadingComments; // Similarly, trailing comments are attached later. The variable // must be reset to null if there are no trailing comments. @@ -187,7 +209,11 @@ export default class CommentsParser extends BaseParser { this.state.commentPreviousNode = node; if (trailingComments) { - if (trailingComments.length && trailingComments[0].start >= node.start && last(trailingComments).end <= node.end) { + if ( + trailingComments.length && + trailingComments[0].start >= node.start && + last(trailingComments).end <= node.end + ) { node.innerComments = trailingComments; } else { node.trailingComments = trailingComments; diff --git a/src/parser/expression.js b/src/parser/expression.js index 7a391b59a5..1b55fb265d 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -29,9 +29,19 @@ import type { Pos, Position } from "../util/location"; export default class ExpressionParser extends LValParser { // Forward-declaration: defined in statement.js +parseBlock: (allowDirectives?: boolean) => N.BlockStatement; - +parseClass: (node: N.Class, isStatement: boolean, optionalId?: boolean) => N.Class; + +parseClass: ( + node: N.Class, + isStatement: boolean, + optionalId?: boolean, + ) => N.Class; +parseDecorators: (allowExport?: boolean) => void; - +parseFunction: (node: T, isStatement: boolean, allowExpressionBody?: boolean, isAsync?: boolean, optionalId?: boolean) => T; + +parseFunction: ( + node: T, + isStatement: boolean, + allowExpressionBody?: boolean, + isAsync?: boolean, + optionalId?: boolean, + ) => T; +takeDecorators: (node: N.HasDecorators) => void; // Check if property name clashes with already added. @@ -39,7 +49,10 @@ export default class ExpressionParser extends LValParser { // either with each other or with an init property — and in // strict mode, init properties are also not allowed to be repeated. - checkPropClash(prop: N.ObjectMember, propHash: { [key: string]: boolean }): void { + checkPropClash( + prop: N.ObjectMember, + propHash: { [key: string]: boolean }, + ): void { if (prop.computed || prop.kind) return; const key = prop.key; @@ -47,7 +60,8 @@ export default class ExpressionParser extends LValParser { const name = key.type === "Identifier" ? key.name : String(key.value); if (name === "__proto__") { - if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property"); + if (propHash.proto) + this.raise(key.start, "Redefinition of __proto__ property"); propHash.proto = true; } } @@ -85,7 +99,9 @@ export default class ExpressionParser extends LValParser { 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, refShorthandDefaultPos), + ); } this.toReferencedList(node.expressions); return this.finishNode(node, "SequenceExpression"); @@ -96,12 +112,18 @@ export default class ExpressionParser extends LValParser { // Parse an assignment expression. This includes applications of // operators like `+=`. - parseMaybeAssign(noIn?: ?boolean, refShorthandDefaultPos?: ?Pos, afterLeftParse?: Function, refNeedsArrowPos?: ?Pos): N.Expression { + parseMaybeAssign( + noIn?: ?boolean, + refShorthandDefaultPos?: ?Pos, + afterLeftParse?: Function, + refNeedsArrowPos?: ?Pos, + ): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; if (this.match(tt._yield) && this.state.inGenerator) { let left = this.parseYield(); - if (afterLeftParse) left = afterLeftParse.call(this, left, startPos, startLoc); + if (afterLeftParse) + left = afterLeftParse.call(this, left, startPos, startLoc); return left; } @@ -117,12 +139,19 @@ export default class ExpressionParser extends LValParser { this.state.potentialArrowAt = this.state.start; } - let left = this.parseMaybeConditional(noIn, refShorthandDefaultPos, refNeedsArrowPos); - if (afterLeftParse) left = afterLeftParse.call(this, left, startPos, startLoc); + let left = this.parseMaybeConditional( + noIn, + refShorthandDefaultPos, + refNeedsArrowPos, + ); + if (afterLeftParse) + left = afterLeftParse.call(this, left, startPos, startLoc); if (this.state.type.isAssign) { const node = this.startNodeAt(startPos, startLoc); node.operator = this.state.value; - node.left = this.match(tt.eq) ? this.toAssignable(left, undefined, "assignment expression") : left; + node.left = this.match(tt.eq) + ? this.toAssignable(left, undefined, "assignment expression") + : left; refShorthandDefaultPos.start = 0; // reset because shorthand default was used correctly this.checkLVal(left, undefined, undefined, "assignment expression"); @@ -135,7 +164,10 @@ export default class ExpressionParser extends LValParser { errorMsg = "`([a]) = 0` use `([a] = 0)`"; } if (errorMsg) { - this.raise(left.start, `You're trying to assign to a parenthesized expression, eg. instead of ${errorMsg}`); + this.raise( + left.start, + `You're trying to assign to a parenthesized expression, eg. instead of ${errorMsg}`, + ); } } @@ -151,13 +183,23 @@ export default class ExpressionParser extends LValParser { // Parse a ternary conditional (`?:`) operator. - parseMaybeConditional(noIn: ?boolean, refShorthandDefaultPos: Pos, refNeedsArrowPos?: ?Pos): N.Expression { + parseMaybeConditional( + noIn: ?boolean, + refShorthandDefaultPos: Pos, + refNeedsArrowPos?: ?Pos, + ): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; const expr = this.parseExprOps(noIn, refShorthandDefaultPos); if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr; - return this.parseConditional(expr, noIn, startPos, startLoc, refNeedsArrowPos); + return this.parseConditional( + expr, + noIn, + startPos, + startLoc, + refNeedsArrowPos, + ); } parseConditional( @@ -167,7 +209,7 @@ export default class ExpressionParser extends LValParser { startLoc: Position, // FIXME: Disabling this for now since can't seem to get it to play nicely // eslint-disable-next-line no-unused-vars - refNeedsArrowPos?: ?Pos + refNeedsArrowPos?: ?Pos, ): N.Expression { if (this.eat(tt.question)) { const node = this.startNodeAt(startPos, startLoc); @@ -199,7 +241,13 @@ export default class ExpressionParser extends LValParser { // defer further parser to one of its callers when it encounters an // operator that has a lower precedence than the set it is parsing. - parseExprOp(left: N.Expression, leftStartPos: number, leftStartLoc: Position, minPrec: number, noIn: ?boolean): N.Expression { + parseExprOp( + left: N.Expression, + leftStartPos: number, + leftStartLoc: Position, + minPrec: number, + noIn: ?boolean, + ): N.Expression { const prec = this.state.type.binop; if (prec != null && (!noIn || !this.match(tt._in))) { if (prec > minPrec) { @@ -214,7 +262,10 @@ export default class ExpressionParser extends LValParser { !left.extra.parenthesizedArgument && !left.extra.parenthesized ) { - this.raise(left.argument.start, "Illegal expression. Wrap left hand side or entire exponentiation in parentheses."); + this.raise( + left.argument.start, + "Illegal expression. Wrap left hand side or entire exponentiation in parentheses.", + ); } const op = this.state.type; @@ -222,10 +273,27 @@ export default class ExpressionParser extends LValParser { const startPos = this.state.start; const startLoc = this.state.startLoc; - node.right = this.parseExprOp(this.parseMaybeUnary(), startPos, startLoc, op.rightAssociative ? prec - 1 : prec, noIn); + node.right = this.parseExprOp( + this.parseMaybeUnary(), + startPos, + startLoc, + op.rightAssociative ? prec - 1 : prec, + noIn, + ); - this.finishNode(node, (op === tt.logicalOR || op === tt.logicalAND) ? "LogicalExpression" : "BinaryExpression"); - return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn); + this.finishNode( + node, + op === tt.logicalOR || op === tt.logicalAND + ? "LogicalExpression" + : "BinaryExpression", + ); + return this.parseExprOp( + node, + leftStartPos, + leftStartLoc, + minPrec, + noIn, + ); } } return left; @@ -244,7 +312,12 @@ export default class ExpressionParser extends LValParser { const argType = this.state.type; node.argument = this.parseMaybeUnary(); - this.addExtra(node, "parenthesizedArgument", argType === tt.parenL && (!node.argument.extra || !node.argument.extra.parenthesized)); + this.addExtra( + node, + "parenthesizedArgument", + argType === tt.parenL && + (!node.argument.extra || !node.argument.extra.parenthesized), + ); if (refShorthandDefaultPos && refShorthandDefaultPos.start) { this.unexpected(refShorthandDefaultPos.start); @@ -258,13 +331,20 @@ export default class ExpressionParser extends LValParser { if (arg.type === "Identifier") { this.raise(node.start, "Deleting local variable in strict mode"); } else if (this.hasPlugin("classPrivateProperties")) { - if (arg.type === "PrivateName" || (arg.type === "MemberExpression" && arg.property.type === "PrivateName")) { + if ( + arg.type === "PrivateName" || + (arg.type === "MemberExpression" && + arg.property.type === "PrivateName") + ) { this.raise(node.start, "Deleting a private field is not allowed"); } } } - return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); + return this.finishNode( + node, + update ? "UpdateExpression" : "UnaryExpression", + ); } const startPos = this.state.start; @@ -291,7 +371,10 @@ export default class ExpressionParser extends LValParser { const potentialArrowAt = this.state.potentialArrowAt; const expr = this.parseExprAtom(refShorthandDefaultPos); - if (expr.type === "ArrowFunctionExpression" && expr.start === potentialArrowAt) { + if ( + expr.type === "ArrowFunctionExpression" && + expr.start === potentialArrowAt + ) { return expr; } @@ -302,7 +385,12 @@ export default class ExpressionParser extends LValParser { return this.parseSubscripts(expr, startPos, startLoc); } - parseSubscripts(base: N.Expression, startPos: number, startLoc: Position, noCalls?: ?boolean): N.Expression { + parseSubscripts( + base: N.Expression, + startPos: number, + startLoc: Position, + noCalls?: ?boolean, + ): N.Expression { const state = { stop: false }; do { base = this.parseSubscript(base, startPos, startLoc, noCalls, state); @@ -311,17 +399,30 @@ export default class ExpressionParser extends LValParser { } /** @param state Set 'state.stop = true' to indicate that we should stop parsing subscripts. */ - parseSubscript(base: N.Expression, startPos: number, startLoc: Position, noCalls: ?boolean, state: { stop: boolean }): N.Expression { + parseSubscript( + base: N.Expression, + startPos: number, + startLoc: Position, + noCalls: ?boolean, + state: { stop: boolean }, + ): 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"), startPos, startLoc, noCalls); - + return this.parseSubscripts( + this.finishNode(node, "BindExpression"), + startPos, + startLoc, + noCalls, + ); } else if (this.match(tt.questionDot)) { if (!this.hasPlugin("optionalChaining")) { - this.raise(startPos, "You can only use optional-chaining when the 'optionalChaining' plugin is enabled."); + this.raise( + startPos, + "You can only use optional-chaining when the 'optionalChaining' plugin is enabled.", + ); } if (noCalls && this.lookahead().type == tt.parenL) { @@ -343,7 +444,10 @@ export default class ExpressionParser extends LValParser { const possibleAsync = this.atPossibleAsync(base); node.callee = base; - node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync); + node.arguments = this.parseCallExpressionArguments( + tt.parenR, + possibleAsync, + ); node.optional = true; return this.finishNode(node, "CallExpression"); @@ -357,7 +461,9 @@ export default class ExpressionParser extends LValParser { } else if (this.eat(tt.dot)) { const node = this.startNodeAt(startPos, startLoc); node.object = base; - node.property = this.hasPlugin("classPrivateProperties") ? this.parseMaybePrivateName() : this.parseIdentifier(true); + node.property = this.hasPlugin("classPrivateProperties") + ? this.parseMaybePrivateName() + : this.parseIdentifier(true); node.computed = false; return this.finishNode(node, "MemberExpression"); } else if (this.eat(tt.bracketL)) { @@ -373,12 +479,18 @@ export default class ExpressionParser extends LValParser { const node = this.startNodeAt(startPos, startLoc); node.callee = base; - node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync); + node.arguments = this.parseCallExpressionArguments( + tt.parenR, + possibleAsync, + ); this.finishCallExpression(node); if (possibleAsync && this.shouldParseAsyncArrow()) { state.stop = true; - return this.parseAsyncArrowFromCallExpression(this.startNodeAt(startPos, startLoc), node); + return this.parseAsyncArrowFromCallExpression( + this.startNodeAt(startPos, startLoc), + node, + ); } else { this.toReferencedList(node.arguments); } @@ -395,7 +507,12 @@ export default class ExpressionParser extends LValParser { } atPossibleAsync(base: N.Expression): boolean { - return this.state.potentialArrowAt === base.start && base.type === "Identifier" && base.name === "async" && !this.canInsertSemicolon(); + return ( + this.state.potentialArrowAt === base.start && + base.type === "Identifier" && + base.name === "async" && + !this.canInsertSemicolon() + ); } finishCallExpression(node: N.CallExpression): N.CallExpression { @@ -412,7 +529,10 @@ export default class ExpressionParser extends LValParser { return this.finishNode(node, "CallExpression"); } - parseCallExpressionArguments(close: TokenType, possibleAsyncArrow: boolean): $ReadOnlyArray { + parseCallExpressionArguments( + close: TokenType, + possibleAsyncArrow: boolean, + ): $ReadOnlyArray { const elts = []; let innerParenStart; let first = true; @@ -430,7 +550,13 @@ export default class ExpressionParser extends LValParser { innerParenStart = this.state.start; } - elts.push(this.parseExprListItem(false, possibleAsyncArrow ? { start: 0 } : undefined, possibleAsyncArrow ? { start: 0 } : undefined)); + elts.push( + this.parseExprListItem( + false, + possibleAsyncArrow ? { start: 0 } : undefined, + possibleAsyncArrow ? { start: 0 } : undefined, + ), + ); } // we found an async arrow function so let's not allow any inner parens @@ -445,7 +571,10 @@ export default class ExpressionParser extends LValParser { return this.match(tt.arrow); } - parseAsyncArrowFromCallExpression(node: N.ArrowFunctionExpression, call: N.CallExpression): N.ArrowFunctionExpression { + parseAsyncArrowFromCallExpression( + node: N.ArrowFunctionExpression, + call: N.CallExpression, + ): N.ArrowFunctionExpression { this.expect(tt.arrow); return this.parseArrowExpression(node, call.arguments, true); } @@ -479,11 +608,22 @@ export default class ExpressionParser extends LValParser { node = this.startNode(); this.next(); - if (!this.match(tt.parenL) && !this.match(tt.bracketL) && !this.match(tt.dot)) { + if ( + !this.match(tt.parenL) && + !this.match(tt.bracketL) && + !this.match(tt.dot) + ) { this.unexpected(); } - if (this.match(tt.parenL) && this.state.inMethod !== "constructor" && !this.options.allowSuperOutsideMethod) { - this.raise(node.start, "super() is only valid inside a class constructor. Make sure the method name is spelled exactly as 'constructor'."); + if ( + this.match(tt.parenL) && + this.state.inMethod !== "constructor" && + !this.options.allowSuperOutsideMethod + ) { + this.raise( + node.start, + "super() is only valid inside a class constructor. Make sure the method name is spelled exactly as 'constructor'.", + ); } return this.finishNode(node, "Super"); @@ -519,7 +659,11 @@ export default class ExpressionParser extends LValParser { if (this.state.inAsync || this.inModule) { return this.parseAwait(node); } - } else if (id.name === "async" && this.match(tt._function) && !this.canInsertSemicolon()) { + } else if ( + id.name === "async" && + this.match(tt._function) && + !this.canInsertSemicolon() + ) { this.next(); return this.parseFunction(node, false, false, true); } else if (canBeArrow && id.name === "async" && this.match(tt.name)) { @@ -570,7 +714,8 @@ export default class ExpressionParser extends LValParser { this.next(); return this.finishNode(node, "NullLiteral"); - case tt._true: case tt._false: + case tt._true: + case tt._false: return this.parseBooleanLiteral(); case tt.parenL: @@ -579,7 +724,11 @@ export default class ExpressionParser extends LValParser { case tt.bracketL: node = this.startNode(); this.next(); - node.elements = this.parseExprList(tt.bracketR, true, refShorthandDefaultPos); + node.elements = this.parseExprList( + tt.bracketR, + true, + refShorthandDefaultPos, + ); this.toReferencedList(node.elements); return this.finishNode(node, "ArrayExpression"); @@ -614,11 +763,14 @@ export default class ExpressionParser extends LValParser { node = this.startNode(); this.next(); node.object = null; - const callee = node.callee = this.parseNoCallExpr(); + const callee = (node.callee = this.parseNoCallExpr()); if (callee.type === "MemberExpression") { return this.finishNode(node, "BindExpression"); } else { - throw this.raise(callee.start, "Binding should be performed on object property."); + throw this.raise( + callee.start, + "Binding should be performed on object property.", + ); } default: @@ -648,19 +800,30 @@ export default class ExpressionParser extends LValParser { parseFunctionExpression(): N.FunctionExpression | N.MetaProperty { const node = this.startNode(); const meta = this.parseIdentifier(true); - if (this.state.inGenerator && this.eat(tt.dot) && this.hasPlugin("functionSent")) { + if ( + this.state.inGenerator && + this.eat(tt.dot) && + this.hasPlugin("functionSent") + ) { return this.parseMetaProperty(node, meta, "sent"); } else { return this.parseFunction(node, false); } } - parseMetaProperty(node: N.MetaProperty, meta: N.Identifier, propertyName: string): N.MetaProperty { + parseMetaProperty( + node: N.MetaProperty, + meta: N.Identifier, + propertyName: string, + ): N.MetaProperty { node.meta = meta; node.property = this.parseIdentifier(true); if (node.property.name !== propertyName) { - this.raise(node.property.start, `The only valid meta property for ${meta.name} is ${meta.name}.${propertyName}`); + this.raise( + node.property.start, + `The only valid meta property for ${meta.name} is ${meta.name}.${propertyName}`, + ); } return this.finishNode(node, "MetaProperty"); @@ -671,12 +834,20 @@ export default class ExpressionParser extends LValParser { const id = this.parseIdentifier(true); this.expect(tt.dot); if (!this.inModule) { - this.raise(id.start, `import.meta may appear only with 'sourceType: "module"'`); + this.raise( + id.start, + `import.meta may appear only with 'sourceType: "module"'`, + ); } return this.parseMetaProperty(node, id, "meta"); } - parseLiteral(value: any, type: /*T["kind"]*/string, startPos?: number, startLoc?: Position): T { + parseLiteral( + value: any, + type: /*T["kind"]*/ string, + startPos?: number, + startLoc?: Position, + ): T { startPos = startPos || this.state.start; startLoc = startLoc || this.state.startLoc; @@ -726,10 +897,23 @@ export default class ExpressionParser extends LValParser { const spreadNodeStartPos = this.state.start; const spreadNodeStartLoc = this.state.startLoc; spreadStart = this.state.start; - exprList.push(this.parseParenItem(this.parseRest(), spreadNodeStartPos, spreadNodeStartLoc)); + exprList.push( + this.parseParenItem( + this.parseRest(), + spreadNodeStartPos, + spreadNodeStartLoc, + ), + ); break; } else { - exprList.push(this.parseMaybeAssign(false, refShorthandDefaultPos, this.parseParenItem, refNeedsArrowPos)); + exprList.push( + this.parseMaybeAssign( + false, + refShorthandDefaultPos, + this.parseParenItem, + refNeedsArrowPos, + ), + ); } } @@ -738,9 +922,14 @@ export default class ExpressionParser extends LValParser { this.expect(tt.parenR); let arrowNode = this.startNodeAt(startPos, startLoc); - if (canBeArrow && this.shouldParseArrow() && (arrowNode = this.parseArrow(arrowNode))) { + if ( + canBeArrow && + this.shouldParseArrow() && + (arrowNode = this.parseArrow(arrowNode)) + ) { for (const param of exprList) { - if (param.extra && param.extra.parenthesized) this.unexpected(param.extra.parenStart); + if (param.extra && param.extra.parenthesized) + this.unexpected(param.extra.parenStart); } return this.parseArrowExpression(arrowNode, exprList); @@ -751,7 +940,8 @@ export default class ExpressionParser extends LValParser { } if (optionalCommaStart) this.unexpected(optionalCommaStart); if (spreadStart) this.unexpected(spreadStart); - if (refShorthandDefaultPos.start) this.unexpected(refShorthandDefaultPos.start); + if (refShorthandDefaultPos.start) + this.unexpected(refShorthandDefaultPos.start); if (refNeedsArrowPos.start) this.unexpected(refNeedsArrowPos.start); if (exprList.length > 1) { @@ -763,7 +953,6 @@ export default class ExpressionParser extends LValParser { val = exprList[0]; } - this.addExtra(val, "parenthesized", true); this.addExtra(val, "parenStart", startPos); @@ -780,8 +969,12 @@ export default class ExpressionParser extends LValParser { } } - // eslint-disable-next-line no-unused-vars - parseParenItem(node: N.Expression, startPos: number, startLoc: Position): N.Expression { + parseParenItem( + node: N.Expression, + startPos: number, + // eslint-disable-next-line no-unused-vars + startLoc: Position, + ): N.Expression { return node; } @@ -797,7 +990,10 @@ export default class ExpressionParser extends LValParser { const metaProp = this.parseMetaProperty(node, meta, "target"); if (!this.state.inFunction) { - this.raise(metaProp.property.start, "new.target can only be used in functions"); + this.raise( + metaProp.property.start, + "new.target can only be used in functions", + ); } return metaProp; @@ -827,14 +1023,19 @@ export default class ExpressionParser extends LValParser { if (this.state.value === null) { if (!isTagged) { // TODO: fix this - this.raise(this.state.invalidTemplateEscapePosition || 0, "Invalid escape sequence in template"); + this.raise( + this.state.invalidTemplateEscapePosition || 0, + "Invalid escape sequence in template", + ); } else { this.state.invalidTemplateEscapePosition = null; } } elem.value = { - raw: this.input.slice(this.state.start, this.state.end).replace(/\r\n?/g, "\n"), - cooked: this.state.value + raw: this.input + .slice(this.state.start, this.state.end) + .replace(/\r\n?/g, "\n"), + cooked: this.state.value, }; this.next(); elem.tail = this.match(tt.backQuote); @@ -851,7 +1052,7 @@ export default class ExpressionParser extends LValParser { this.expect(tt.dollarBraceL); node.expressions.push(this.parseExpression()); this.expect(tt.braceR); - node.quasis.push(curElt = this.parseTemplateElement(isTagged)); + node.quasis.push((curElt = this.parseTemplateElement(isTagged))); } this.next(); return this.finishNode(node, "TemplateLiteral"); @@ -859,7 +1060,10 @@ export default class ExpressionParser extends LValParser { // Parse an object literal or binding pattern. - parseObj(isPattern: boolean, refShorthandDefaultPos?: ?Pos): T { + parseObj( + isPattern: boolean, + refShorthandDefaultPos?: ?Pos, + ): T { let decorators = []; const propHash = Object.create(null); let first = true; @@ -880,7 +1084,10 @@ export default class ExpressionParser extends LValParser { if (this.match(tt.at)) { if (this.hasPlugin("decorators2")) { - this.raise(this.state.start, "Stage 2 decorators disallow object literal property decorators"); + this.raise( + this.state.start, + "Stage 2 decorators disallow object literal property decorators", + ); } else { // we needn't check if decorators (stage 0) plugin is enabled since it's checked by // the call to this.parseDecorator @@ -890,7 +1097,11 @@ export default class ExpressionParser extends LValParser { } } - let prop = this.startNode(), isGenerator = false, isAsync = false, startPos, startLoc; + let prop = this.startNode(), + isGenerator = false, + isAsync = false, + startPos, + startLoc; if (decorators.length) { prop.decorators = decorators; decorators = []; @@ -904,11 +1115,20 @@ export default class ExpressionParser extends LValParser { if (isPattern) { const position = this.state.start; if (firstRestLocation !== null) { - this.unexpected(firstRestLocation, "Cannot have multiple rest elements when destructuring"); + this.unexpected( + firstRestLocation, + "Cannot have multiple rest elements when destructuring", + ); } else if (this.eat(tt.braceR)) { break; - } else if (this.match(tt.comma) && this.lookahead().type === tt.braceR) { - this.unexpected(position, "A trailing comma is not permitted after the rest element"); + } else if ( + this.match(tt.comma) && + this.lookahead().type === tt.braceR + ) { + this.unexpected( + position, + "A trailing comma is not permitted after the rest element", + ); } else { firstRestLocation = position; continue; @@ -933,19 +1153,34 @@ export default class ExpressionParser extends LValParser { if (isGenerator) this.unexpected(); const asyncId = this.parseIdentifier(); - if (this.match(tt.colon) || this.match(tt.parenL) || this.match(tt.braceR) || this.match(tt.eq) || this.match(tt.comma)) { + if ( + this.match(tt.colon) || + this.match(tt.parenL) || + this.match(tt.braceR) || + this.match(tt.eq) || + this.match(tt.comma) + ) { prop.key = asyncId; prop.computed = false; } else { isAsync = true; - if (this.hasPlugin("asyncGenerators")) isGenerator = this.eat(tt.star); + if (this.hasPlugin("asyncGenerators")) + isGenerator = this.eat(tt.star); this.parsePropertyName(prop); } } else { this.parsePropertyName(prop); } - this.parseObjPropValue(prop, startPos, startLoc, isGenerator, isAsync, isPattern, refShorthandDefaultPos); + this.parseObjPropValue( + prop, + startPos, + startLoc, + isGenerator, + isAsync, + isPattern, + refShorthandDefaultPos, + ); this.checkPropClash(prop, propHash); if (prop.shorthand) { @@ -956,28 +1191,37 @@ export default class ExpressionParser extends LValParser { } if (firstRestLocation !== null) { - this.unexpected(firstRestLocation, "The rest element has to be the last element when destructuring"); + this.unexpected( + firstRestLocation, + "The rest element has to be the last element when destructuring", + ); } if (decorators.length) { - this.raise(this.state.start, "You have trailing decorators with no property"); + this.raise( + this.state.start, + "You have trailing decorators with no property", + ); } - return this.finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression"); + return this.finishNode( + node, + isPattern ? "ObjectPattern" : "ObjectExpression", + ); } isGetterOrSetterMethod(prop: N.ObjectMethod, isPattern: boolean): boolean { - return !isPattern && + return ( + !isPattern && !prop.computed && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set") && - ( - this.match(tt.string) || // get "string"() {} - this.match(tt.num) || // get 1() {} - this.match(tt.bracketL) || // get ["string"]() {} - this.match(tt.name) || // get foo() {} - !!this.state.type.keyword // get debugger() {} - ); + (this.match(tt.string) || // get "string"() {} + this.match(tt.num) || // get 1() {} + this.match(tt.bracketL) || // get ["string"]() {} + this.match(tt.name) || // get foo() {} + !!this.state.type.keyword) // get debugger() {} + ); } // get methods aren't allowed to have any parameters @@ -994,29 +1238,54 @@ export default class ExpressionParser extends LValParser { } } - parseObjectMethod(prop: N.ObjectMethod, isGenerator: boolean, isAsync: boolean, isPattern: boolean): ?N.ObjectMethod { + parseObjectMethod( + prop: N.ObjectMethod, + isGenerator: boolean, + isAsync: boolean, + isPattern: boolean, + ): ?N.ObjectMethod { if (isAsync || isGenerator || this.match(tt.parenL)) { if (isPattern) this.unexpected(); prop.kind = "method"; prop.method = true; - return this.parseMethod(prop, isGenerator, isAsync, /* isConstructor */ false, "ObjectMethod"); + return this.parseMethod( + prop, + isGenerator, + isAsync, + /* isConstructor */ false, + "ObjectMethod", + ); } if (this.isGetterOrSetterMethod(prop, isPattern)) { if (isGenerator || isAsync) this.unexpected(); prop.kind = prop.key.name; this.parsePropertyName(prop); - this.parseMethod(prop, /* isGenerator */false, /* isAsync */ false, /* isConstructor */ false, "ObjectMethod"); + this.parseMethod( + prop, + /* isGenerator */ false, + /* isAsync */ false, + /* isConstructor */ false, + "ObjectMethod", + ); this.checkGetterSetterParamCount(prop); return prop; } } - parseObjectProperty(prop: N.ObjectProperty, startPos: ?number, startLoc: ?Position, isPattern: boolean, refShorthandDefaultPos: ?Pos): ?N.ObjectProperty { + parseObjectProperty( + prop: N.ObjectProperty, + startPos: ?number, + startLoc: ?Position, + isPattern: boolean, + refShorthandDefaultPos: ?Pos, + ): ?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); + prop.value = isPattern + ? this.parseMaybeDefault(this.state.start, this.state.startLoc) + : this.parseMaybeAssign(false, refShorthandDefaultPos); return this.finishNode(prop, "ObjectProperty"); } @@ -1025,12 +1294,20 @@ export default class ExpressionParser extends LValParser { this.checkReservedWord(prop.key.name, prop.key.start, true, true); if (isPattern) { - prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key.__clone()); + prop.value = this.parseMaybeDefault( + startPos, + startLoc, + prop.key.__clone(), + ); } else if (this.match(tt.eq) && refShorthandDefaultPos) { if (!refShorthandDefaultPos.start) { refShorthandDefaultPos.start = this.state.start; } - prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key.__clone()); + prop.value = this.parseMaybeDefault( + startPos, + startLoc, + prop.key.__clone(), + ); } else { prop.value = prop.key.__clone(); } @@ -1040,10 +1317,24 @@ export default class ExpressionParser extends LValParser { } } - parseObjPropValue(prop: any, startPos: ?number, startLoc: ?Position, isGenerator: boolean, isAsync: boolean, isPattern: boolean, refShorthandDefaultPos: ?Pos): void { + parseObjPropValue( + prop: any, + startPos: ?number, + startLoc: ?Position, + isGenerator: boolean, + isAsync: boolean, + isPattern: boolean, + refShorthandDefaultPos: ?Pos, + ): void { const node = this.parseObjectMethod(prop, isGenerator, isAsync, isPattern) || - this.parseObjectProperty(prop, startPos, startLoc, isPattern, refShorthandDefaultPos); + this.parseObjectProperty( + prop, + startPos, + startLoc, + isPattern, + refShorthandDefaultPos, + ); if (!node) this.unexpected(); @@ -1051,7 +1342,9 @@ export default class ExpressionParser extends LValParser { return node; } - parsePropertyName(prop: N.ObjectOrClassMember | N.TsNamedTypeElementBase): N.Expression { + parsePropertyName( + prop: N.ObjectOrClassMember | N.TsNamedTypeElementBase, + ): N.Expression { if (this.eat(tt.bracketL)) { prop.computed = true; prop.key = this.parseMaybeAssign(); @@ -1060,7 +1353,10 @@ export default class ExpressionParser extends LValParser { prop.computed = false; const oldInPropertyName = this.state.inPropertyName; this.state.inPropertyName = true; - prop.key = (this.match(tt.num) || this.match(tt.string)) ? this.parseExprAtom() : this.parseIdentifier(true); + prop.key = + this.match(tt.num) || this.match(tt.string) + ? this.parseExprAtom() + : this.parseIdentifier(true); this.state.inPropertyName = oldInPropertyName; } @@ -1078,13 +1374,23 @@ export default class ExpressionParser extends LValParser { // Parse object or class method. - parseMethod(node: T, isGenerator: boolean, isAsync: boolean, isConstructor: boolean, type: string): T { + parseMethod( + node: T, + isGenerator: boolean, + isAsync: boolean, + isConstructor: boolean, + type: string, + ): T { const oldInMethod = this.state.inMethod; this.state.inMethod = node.kind || true; this.initFunction(node, isAsync); this.expect(tt.parenL); const allowModifiers = isConstructor; // For TypeScript parameter properties - node.params = this.parseBindingList(tt.parenR, /* allowEmpty */ false, allowModifiers); + node.params = this.parseBindingList( + tt.parenR, + /* allowEmpty */ false, + allowModifiers, + ); node.generator = !!isGenerator; this.parseFunctionBodyAndFinish(node, type); this.state.inMethod = oldInMethod; @@ -1093,14 +1399,25 @@ export default class ExpressionParser extends LValParser { // Parse arrow function expression with given parameters. - parseArrowExpression(node: N.ArrowFunctionExpression, params: N.Expression[], isAsync?: boolean): N.ArrowFunctionExpression { + parseArrowExpression( + node: N.ArrowFunctionExpression, + params: N.Expression[], + isAsync?: boolean, + ): N.ArrowFunctionExpression { this.initFunction(node, isAsync); - node.params = this.toAssignableList(params, true, "arrow function parameters"); + node.params = this.toAssignableList( + params, + true, + "arrow function parameters", + ); this.parseFunctionBody(node, true); return this.finishNode(node, "ArrowFunctionExpression"); } - isStrictBody(node: { body: N.BlockStatement }, isExpression: ?boolean): boolean { + isStrictBody( + node: { body: N.BlockStatement }, + isExpression: ?boolean, + ): boolean { if (!isExpression && node.body.directives.length) { for (const directive of node.body.directives) { if (directive.value.value === "use strict") { @@ -1112,7 +1429,11 @@ export default class ExpressionParser extends LValParser { return false; } - parseFunctionBodyAndFinish(node: N.BodilessFunctionOrMethodBase, type: string, allowExpressionBody?: boolean): void { + parseFunctionBodyAndFinish( + node: N.BodilessFunctionOrMethodBase, + type: string, + allowExpressionBody?: boolean, + ): void { // $FlowIgnore (node is not bodiless if we get here) this.parseFunctionBody(node, allowExpressionBody); this.finishNode(node, type); @@ -1133,10 +1454,14 @@ export default class ExpressionParser extends LValParser { const oldInFunc = this.state.inFunction; const oldInGen = this.state.inGenerator; const oldLabels = this.state.labels; - this.state.inFunction = true; this.state.inGenerator = node.generator; this.state.labels = []; + this.state.inFunction = true; + this.state.inGenerator = node.generator; + this.state.labels = []; node.body = this.parseBlock(true); node.expression = false; - this.state.inFunction = oldInFunc; this.state.inGenerator = oldInGen; this.state.labels = oldLabels; + this.state.inFunction = oldInFunc; + this.state.inGenerator = oldInGen; + this.state.labels = oldLabels; } this.state.inAsync = oldInAsync; @@ -1147,7 +1472,12 @@ export default class ExpressionParser extends LValParser { // Also check when allowExpression === true for arrow functions const checkLVal = this.state.strict || allowExpression || isStrict; - if (isStrict && node.id && node.id.type === "Identifier" && node.id.name === "yield") { + if ( + isStrict && + node.id && + node.id.type === "Identifier" && + node.id.name === "yield" + ) { this.raise(node.id.start, "Binding yield in strict mode"); } @@ -1174,7 +1504,11 @@ export default class ExpressionParser extends LValParser { // nothing in between them to be parsed as `null` (which is needed // for array literals). - parseExprList(close: TokenType, allowEmpty?: boolean, refShorthandDefaultPos?: ?Pos): $ReadOnlyArray { + parseExprList( + close: TokenType, + allowEmpty?: boolean, + refShorthandDefaultPos?: ?Pos, + ): $ReadOnlyArray { const elts = []; let first = true; @@ -1191,14 +1525,23 @@ export default class ExpressionParser extends LValParser { return elts; } - parseExprListItem(allowEmpty: ?boolean, refShorthandDefaultPos: ?Pos, refNeedsArrowPos: ?Pos): ?N.Expression { + parseExprListItem( + allowEmpty: ?boolean, + refShorthandDefaultPos: ?Pos, + refNeedsArrowPos: ?Pos, + ): ?N.Expression { let elt; if (allowEmpty && this.match(tt.comma)) { elt = null; } else if (this.match(tt.ellipsis)) { elt = this.parseSpread(refShorthandDefaultPos); } else { - elt = this.parseMaybeAssign(false, refShorthandDefaultPos, this.parseParenItem, refNeedsArrowPos); + elt = this.parseMaybeAssign( + false, + refShorthandDefaultPos, + this.parseParenItem, + refNeedsArrowPos, + ); } return elt; } @@ -1217,7 +1560,12 @@ export default class ExpressionParser extends LValParser { parseIdentifierName(pos: number, liberal?: boolean): string { if (!liberal) { - this.checkReservedWord(this.state.value, this.state.start, !!this.state.type.keyword, false); + this.checkReservedWord( + this.state.value, + this.state.start, + !!this.state.type.keyword, + false, + ); } let name: string; @@ -1238,12 +1586,21 @@ export default class ExpressionParser extends LValParser { return name; } - checkReservedWord(word: string, startLoc: number, checkKeywords: boolean, isBinding: boolean): void { + checkReservedWord( + word: string, + startLoc: number, + checkKeywords: boolean, + isBinding: boolean, + ): void { if (this.isReservedWord(word) || (checkKeywords && this.isKeyword(word))) { this.raise(startLoc, word + " is a reserved word"); } - if (this.state.strict && (reservedWords.strict(word) || (isBinding && reservedWords.strictBind(word)))) { + if ( + this.state.strict && + (reservedWords.strict(word) || + (isBinding && reservedWords.strictBind(word))) + ) { this.raise(startLoc, word + " is a reserved word in strict mode"); } } @@ -1256,7 +1613,10 @@ export default class ExpressionParser extends LValParser { this.unexpected(); } if (this.match(tt.star)) { - this.raise(node.start, "await* has been removed from the async functions proposal. Use Promise.all() instead."); + this.raise( + node.start, + "await* has been removed from the async functions proposal. Use Promise.all() instead.", + ); } node.argument = this.parseMaybeUnary(); return this.finishNode(node, "AwaitExpression"); diff --git a/src/parser/index.js b/src/parser/index.js index 7a66b89a54..35525227c6 100644 --- a/src/parser/index.js +++ b/src/parser/index.js @@ -5,7 +5,9 @@ import type { File } from "../types"; import { getOptions } from "../options"; import StatementParser from "./statement"; -export const plugins: { [name: string]: (superClass: Class) => Class } = {}; +export const plugins: { + [name: string]: (superClass: Class) => Class, +} = {}; export default class Parser extends StatementParser { constructor(options: ?Options, input: string) { @@ -19,7 +21,11 @@ export default class Parser extends StatementParser { this.filename = options.sourceFilename; // If enabled, skip leading hashbang line. - if (this.state.pos === 0 && this.input[0] === "#" && this.input[1] === "!") { + if ( + this.state.pos === 0 && + this.input[0] === "#" && + this.input[1] === "!" + ) { this.skipLineComment(2); } } @@ -32,7 +38,9 @@ export default class Parser extends StatementParser { } } -function pluginsMap(pluginList: $ReadOnlyArray): { [key: string]: boolean } { +function pluginsMap( + pluginList: $ReadOnlyArray, +): { [key: string]: boolean } { const pluginMap = {}; for (const name of pluginList) { pluginMap[name] = true; diff --git a/src/parser/location.js b/src/parser/location.js index aed2760d62..a2a6039762 100644 --- a/src/parser/location.js +++ b/src/parser/location.js @@ -14,7 +14,9 @@ export default class LocationParser extends CommentsParser { const loc = getLineInfo(this.input, pos); message += ` (${loc.line}:${loc.column})`; // $FlowIgnore - const err: SyntaxError & { pos: number, loc: Position } = new SyntaxError(message); + const err: SyntaxError & { pos: number, loc: Position } = new SyntaxError( + message, + ); err.pos = pos; err.loc = loc; throw err; diff --git a/src/parser/lval.js b/src/parser/lval.js index 1345f53497..0d09a6a85f 100644 --- a/src/parser/lval.js +++ b/src/parser/lval.js @@ -1,28 +1,51 @@ // @flow import { types as tt, type TokenType } from "../tokenizer/types"; -import type { TSParameterProperty, Decorator, Expression, Identifier, Node, ObjectExpression, - ObjectPattern, Pattern, RestElement, SpreadElement } from "../types"; +import type { + TSParameterProperty, + Decorator, + Expression, + Identifier, + Node, + ObjectExpression, + ObjectPattern, + Pattern, + RestElement, + SpreadElement, +} from "../types"; import type { Pos, Position } from "../util/location"; import { NodeUtils } from "./node"; export default class LValParser extends NodeUtils { // Forward-declaration: defined in expression.js - +checkReservedWord: (word: string, startLoc: number, checkKeywords: boolean, isBinding: boolean) => void; + +checkReservedWord: ( + word: string, + startLoc: number, + checkKeywords: boolean, + isBinding: boolean, + ) => void; +parseIdentifier: (liberal?: boolean) => Identifier; +parseMaybeAssign: ( noIn?: ?boolean, refShorthandDefaultPos?: ?Pos, afterLeftParse?: Function, - refNeedsArrowPos?: ?Pos) => Expression; - +parseObj: (isPattern: boolean, refShorthandDefaultPos?: ?Pos) => T; + refNeedsArrowPos?: ?Pos, + ) => Expression; + +parseObj: ( + isPattern: boolean, + refShorthandDefaultPos?: ?Pos, + ) => T; // Forward-declaration: defined in statement.js +parseDecorator: () => Decorator; // Convert existing expression atom to assignable pattern // if possible. - toAssignable(node: Node, isBinding: ?boolean, contextDescription: string): Node { + toAssignable( + node: Node, + isBinding: ?boolean, + contextDescription: string, + ): Node { if (node) { switch (node.type) { case "Identifier": @@ -37,12 +60,22 @@ export default class LValParser extends NodeUtils { for (const prop of node.properties) { if (prop.type === "ObjectMethod") { if (prop.kind === "get" || prop.kind === "set") { - this.raise(prop.key.start, "Object pattern can't contain getter or setter"); + this.raise( + prop.key.start, + "Object pattern can't contain getter or setter", + ); } else { - this.raise(prop.key.start, "Object pattern can't contain methods"); + this.raise( + prop.key.start, + "Object pattern can't contain methods", + ); } } else { - this.toAssignable(prop, isBinding, "object destructuring pattern"); + this.toAssignable( + prop, + isBinding, + "object destructuring pattern", + ); } } break; @@ -67,7 +100,10 @@ export default class LValParser extends NodeUtils { node.type = "AssignmentPattern"; delete node.operator; } else { - this.raise(node.left.end, "Only '=' operator can be used for specifying default value."); + this.raise( + node.left.end, + "Only '=' operator can be used for specifying default value.", + ); } break; @@ -75,8 +111,11 @@ export default class LValParser extends NodeUtils { if (!isBinding) break; default: { - const message = "Invalid left-hand side" + - (contextDescription ? " in " + contextDescription : /* istanbul ignore next */ "expression"); + const message = + "Invalid left-hand side" + + (contextDescription + ? " in " + contextDescription + : /* istanbul ignore next */ "expression"); this.raise(node.start, message); } } @@ -87,7 +126,10 @@ export default class LValParser extends NodeUtils { // Convert list of expression atoms to binding list. toAssignableList( - exprList: Expression[], isBinding: ?boolean, contextDescription: string): $ReadOnlyArray { + exprList: Expression[], + isBinding: ?boolean, + contextDescription: string, + ): $ReadOnlyArray { let end = exprList.length; if (end) { const last = exprList[end - 1]; @@ -110,7 +152,10 @@ export default class LValParser extends NodeUtils { for (let i = 0; i < end; i++) { const elt = exprList[i]; if (elt && elt.type === "SpreadElement") - this.raise(elt.start, "The rest element has to be the last element when destructuring"); + this.raise( + elt.start, + "The rest element has to be the last element when destructuring", + ); if (elt) this.toAssignable(elt, isBinding, contextDescription); } return exprList; @@ -118,13 +163,15 @@ export default class LValParser extends NodeUtils { // Convert list of expression atoms to a list of - toReferencedList(exprList: $ReadOnlyArray): $ReadOnlyArray { + toReferencedList( + exprList: $ReadOnlyArray, + ): $ReadOnlyArray { return exprList; } // Parses spread element. - parseSpread(refShorthandDefaultPos: ?Pos): T { + parseSpread(refShorthandDefaultPos: ?Pos): T { const node = this.startNode(); this.next(); node.argument = this.parseMaybeAssign(false, refShorthandDefaultPos); @@ -139,7 +186,9 @@ export default class LValParser extends NodeUtils { } shouldAllowYieldIdentifier(): boolean { - return this.match(tt._yield) && !this.state.strict && !this.state.inGenerator; + return ( + this.match(tt._yield) && !this.state.strict && !this.state.inGenerator + ); } parseBindingIdentifier(): Identifier { @@ -170,8 +219,8 @@ export default class LValParser extends NodeUtils { parseBindingList( close: TokenType, allowEmpty?: boolean, - allowModifiers?: boolean - ): $ReadOnlyArray { + allowModifiers?: boolean, + ): $ReadOnlyArray { const elts: Array = []; let first = true; while (!this.eat(close)) { @@ -192,7 +241,10 @@ export default class LValParser extends NodeUtils { } else { const decorators = []; if (this.match(tt.at) && this.hasPlugin("decorators2")) { - this.raise(this.state.start, "Stage 2 decorators cannot be used to decorate parameters"); + this.raise( + this.state.start, + "Stage 2 decorators cannot be used to decorate parameters", + ); } while (this.match(tt.at)) { decorators.push(this.parseDecorator()); @@ -203,7 +255,10 @@ export default class LValParser extends NodeUtils { return elts; } - parseAssignableListItem(allowModifiers: ?boolean, decorators: Decorator[]): Pattern | TSParameterProperty { + parseAssignableListItem( + allowModifiers: ?boolean, + decorators: Decorator[], + ): Pattern | TSParameterProperty { const left = this.parseMaybeDefault(); this.parseAssignableListItemTypes(left); const elt = this.parseMaybeDefault(left.start, left.loc.start, left); @@ -219,7 +274,11 @@ export default class LValParser extends NodeUtils { // Parses assignment pattern around given atom if possible. - parseMaybeDefault(startPos?: ?number, startLoc?: ?Position, left?: ?Pattern): Pattern { + parseMaybeDefault( + startPos?: ?number, + startLoc?: ?Position, + left?: ?Pattern, + ): Pattern { startLoc = startLoc || this.state.startLoc; startPos = startPos || this.state.start; left = left || this.parseBindingAtom(); @@ -238,7 +297,8 @@ export default class LValParser extends NodeUtils { expr: Expression, isBinding: ?boolean, checkClashes: ?{ [key: string]: boolean }, - contextDescription: string): void { + contextDescription: string, + ): void { switch (expr.type) { case "PrivateName": case "Identifier": @@ -273,18 +333,34 @@ export default class LValParser extends NodeUtils { case "ObjectPattern": for (let prop of expr.properties) { if (prop.type === "ObjectProperty") prop = prop.value; - this.checkLVal(prop, isBinding, checkClashes, "object destructuring pattern"); + this.checkLVal( + prop, + isBinding, + checkClashes, + "object destructuring pattern", + ); } break; case "ArrayPattern": for (const elem of expr.elements) { - if (elem) this.checkLVal(elem, isBinding, checkClashes, "array destructuring pattern"); + if (elem) + this.checkLVal( + elem, + isBinding, + checkClashes, + "array destructuring pattern", + ); } break; case "AssignmentPattern": - this.checkLVal(expr.left, isBinding, checkClashes, "assignment pattern"); + this.checkLVal( + expr.left, + isBinding, + checkClashes, + "assignment pattern", + ); break; case "RestElement": @@ -292,9 +368,14 @@ export default class LValParser extends NodeUtils { break; default: { - const message = (isBinding ? /* istanbul ignore next */ "Binding invalid" : "Invalid") + + const message = + (isBinding + ? /* istanbul ignore next */ "Binding invalid" + : "Invalid") + " left-hand side" + - (contextDescription ? " in " + contextDescription : /* istanbul ignore next */ "expression"); + (contextDescription + ? " in " + contextDescription + : /* istanbul ignore next */ "expression"); this.raise(expr.start, message); } } diff --git a/src/parser/node.js b/src/parser/node.js index 586322e313..c5bfb2a82b 100644 --- a/src/parser/node.js +++ b/src/parser/node.js @@ -31,7 +31,7 @@ class Node implements NodeBase { __clone(): this { // $FlowIgnore - const node2: any = new Node; + const node2: any = new Node(); for (const key in this) { // Do not clone comments that are already attached to the node if (commentKeys.indexOf(key) < 0) { @@ -45,30 +45,40 @@ class Node implements NodeBase { } export class NodeUtils extends UtilParser { - startNode(): T { + startNode(): T { // $FlowIgnore return new Node(this, this.state.start, this.state.startLoc); } - startNodeAt(pos: number, loc: Position): T { + startNodeAt(pos: number, loc: Position): T { // $FlowIgnore return new Node(this, pos, loc); } /** Start a new node with a previous node's location. */ - startNodeAtNode(type: NodeType): T { + startNodeAtNode(type: NodeType): T { return this.startNodeAt(type.start, type.loc.start); } // Finish an AST node, adding `type` and `end` properties. - finishNode(node: T, type: string): T { - return this.finishNodeAt(node, type, this.state.lastTokEnd, this.state.lastTokEndLoc); + finishNode(node: T, type: string): T { + return this.finishNodeAt( + node, + type, + this.state.lastTokEnd, + this.state.lastTokEndLoc, + ); } // Finish node at given position - finishNodeAt(node: T, type: string, pos: number, loc: Position): T { + finishNodeAt( + node: T, + type: string, + pos: number, + loc: Position, + ): T { node.type = type; node.end = pos; node.loc.end = loc; diff --git a/src/parser/statement.js b/src/parser/statement.js index 6996d5c48c..53485072df 100644 --- a/src/parser/statement.js +++ b/src/parser/statement.js @@ -12,7 +12,8 @@ import { lineBreak } from "../util/whitespace"; const empty = []; -const loopLabel = { kind: "loop" }, switchLabel = { kind: "switch" }; +const loopLabel = { kind: "loop" }, + switchLabel = { kind: "switch" }; export default class StatementParser extends ExpressionParser { // ### Statement parsing @@ -41,15 +42,20 @@ export default class StatementParser extends ExpressionParser { const expr = stmt.expression; const directiveLiteral = this.startNodeAt(expr.start, expr.loc.start); - const directive = this.startNodeAt(stmt.start, stmt.loc.start); + const directive = this.startNodeAt(stmt.start, stmt.loc.start); const raw = this.input.slice(expr.start, expr.end); - const val = directiveLiteral.value = raw.slice(1, -1); // remove quotes + const val = (directiveLiteral.value = raw.slice(1, -1)); // remove quotes this.addExtra(directiveLiteral, "raw", raw); this.addExtra(directiveLiteral, "rawValue", val); - directive.value = this.finishNodeAt(directiveLiteral, "DirectiveLiteral", expr.end, expr.loc.end); + directive.value = this.finishNodeAt( + directiveLiteral, + "DirectiveLiteral", + expr.end, + expr.loc.end, + ); return this.finishNodeAt(directive, "Directive", stmt.end, stmt.loc.end); } @@ -77,11 +83,16 @@ export default class StatementParser extends ExpressionParser { // complexity. switch (starttype) { - // $FlowFixMe - case tt._break: case tt._continue: return this.parseBreakContinueStatement(node, starttype.keyword); - case tt._debugger: return this.parseDebuggerStatement(node); - case tt._do: return this.parseDoStatement(node); - case tt._for: return this.parseForStatement(node); + case tt._break: + case tt._continue: + // $FlowFixMe + return this.parseBreakContinueStatement(node, starttype.keyword); + case tt._debugger: + return this.parseDebuggerStatement(node); + case tt._do: + return this.parseDoStatement(node); + case tt._for: + return this.parseForStatement(node); case tt._function: if (!declaration) this.unexpected(); return this.parseFunctionStatement(node); @@ -90,11 +101,16 @@ export default class StatementParser extends ExpressionParser { if (!declaration) this.unexpected(); return this.parseClass(node, true); - case tt._if: return this.parseIfStatement(node); - case tt._return: return this.parseReturnStatement(node); - case tt._switch: return this.parseSwitchStatement(node); - case tt._throw: return this.parseThrowStatement(node); - case tt._try: return this.parseTryStatement(node); + case tt._if: + return this.parseIfStatement(node); + case tt._return: + return this.parseReturnStatement(node); + case tt._switch: + return this.parseSwitchStatement(node); + case tt._throw: + return this.parseThrowStatement(node); + case tt._try: + return this.parseTryStatement(node); case tt._let: case tt._const: @@ -103,22 +119,36 @@ export default class StatementParser extends ExpressionParser { case tt._var: return this.parseVarStatement(node, starttype); - case tt._while: return this.parseWhileStatement(node); - case tt._with: return this.parseWithStatement(node); - case tt.braceL: return this.parseBlock(); - case tt.semi: return this.parseEmptyStatement(node); + case tt._while: + return this.parseWhileStatement(node); + case tt._with: + return this.parseWithStatement(node); + case tt.braceL: + return this.parseBlock(); + case tt.semi: + return this.parseEmptyStatement(node); case tt._export: case tt._import: - if ((this.hasPlugin("dynamicImport") && this.lookahead().type === tt.parenL) || - (this.hasPlugin("importMeta") && this.lookahead().type === tt.dot)) break; + if ( + (this.hasPlugin("dynamicImport") && + this.lookahead().type === tt.parenL) || + (this.hasPlugin("importMeta") && this.lookahead().type === tt.dot) + ) + break; if (!this.options.allowImportExportEverywhere) { if (!topLevel) { - this.raise(this.state.start, "'import' and 'export' may only appear at the top level"); + this.raise( + this.state.start, + "'import' and 'export' may only appear at the top level", + ); } if (!this.inModule) { - this.raise(this.state.start, `'import' and 'export' may appear only with 'sourceType: "module"'`); + this.raise( + this.state.start, + `'import' and 'export' may appear only with 'sourceType: "module"'`, + ); } } @@ -151,7 +181,11 @@ export default class StatementParser extends ExpressionParser { const maybeName = this.state.value; const expr = this.parseExpression(); - if (starttype === tt.name && expr.type === "Identifier" && this.eat(tt.colon)) { + if ( + starttype === tt.name && + expr.type === "Identifier" && + this.eat(tt.colon) + ) { return this.parseLabeledStatement(node, maybeName, expr); } else { return this.parseExpressionStatement(node, expr); @@ -159,7 +193,9 @@ export default class StatementParser extends ExpressionParser { } takeDecorators(node: N.HasDecorators): void { - const decorators = this.state.decoratorStack[this.state.decoratorStack.length - 1]; + const decorators = this.state.decoratorStack[ + this.state.decoratorStack.length - 1 + ]; if (decorators.length) { node.decorators = decorators; if (this.hasPlugin("decorators2")) { @@ -174,7 +210,9 @@ export default class StatementParser extends ExpressionParser { allowExport = false; } - const currentContextDecorators = this.state.decoratorStack[this.state.decoratorStack.length - 1]; + const currentContextDecorators = this.state.decoratorStack[ + this.state.decoratorStack.length - 1 + ]; while (this.match(tt.at)) { const decorator = this.parseDecorator(); currentContextDecorators.push(decorator); @@ -184,12 +222,18 @@ export default class StatementParser extends ExpressionParser { if (allowExport) { return; } else { - this.raise(this.state.start, "Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class` instead"); + this.raise( + this.state.start, + "Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class` instead", + ); } } if (!this.match(tt._class)) { - this.raise(this.state.start, "Leading decorators must be attached to a class declaration"); + this.raise( + this.state.start, + "Leading decorators must be attached to a class declaration", + ); } } @@ -233,7 +277,10 @@ export default class StatementParser extends ExpressionParser { return this.finishNode(node, "Decorator"); } - parseBreakContinueStatement(node: N.BreakStatement | N.ContinueStatement, keyword: string): N.BreakStatement | N.ContinueStatement { + parseBreakContinueStatement( + node: N.BreakStatement | N.ContinueStatement, + keyword: string, + ): N.BreakStatement | N.ContinueStatement { const isBreak = keyword === "break"; this.next(); @@ -256,8 +303,12 @@ export default class StatementParser extends ExpressionParser { if (node.label && isBreak) break; } } - if (i === this.state.labels.length) this.raise(node.start, "Unsyntactic " + keyword); - return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); + if (i === this.state.labels.length) + this.raise(node.start, "Unsyntactic " + keyword); + return this.finishNode( + node, + isBreak ? "BreakStatement" : "ContinueStatement", + ); } parseDebuggerStatement(node: N.DebuggerStatement): N.DebuggerStatement { @@ -290,7 +341,11 @@ export default class StatementParser extends ExpressionParser { this.state.labels.push(loopLabel); let forAwait = false; - if (this.hasPlugin("asyncGenerators") && this.state.inAsync && this.isContextual("await")) { + if ( + this.hasPlugin("asyncGenerators") && + this.state.inAsync && + this.isContextual("await") + ) { forAwait = true; this.next(); } @@ -324,7 +379,9 @@ export default class StatementParser extends ExpressionParser { const refShorthandDefaultPos = { start: 0 }; const init = this.parseExpression(true, refShorthandDefaultPos); if (this.match(tt._in) || this.isContextual("of")) { - const description = this.isContextual("of") ? "for-of statement" : "for-in statement"; + const description = this.isContextual("of") + ? "for-of statement" + : "for-in statement"; this.toAssignable(init, undefined, description); this.checkLVal(init, undefined, undefined, description); return this.parseForIn(node, init, forAwait); @@ -374,7 +431,7 @@ export default class StatementParser extends ExpressionParser { parseSwitchStatement(node: N.SwitchStatement): N.SwitchStatement { this.next(); node.discriminant = this.parseParenExpression(); - const cases = node.cases = []; + const cases = (node.cases = []); this.expect(tt.braceL); this.state.labels.push(switchLabel); @@ -387,13 +444,14 @@ export default class StatementParser extends ExpressionParser { if (this.match(tt._case) || this.match(tt._default)) { const isCase = this.match(tt._case); if (cur) this.finishNode(cur, "SwitchCase"); - cases.push(cur = this.startNode()); + cases.push((cur = this.startNode())); cur.consequent = []; this.next(); if (isCase) { cur.test = this.parseExpression(); } else { - if (sawDefault) this.raise(this.state.lastTokStart, "Multiple default clauses"); + if (sawDefault) + this.raise(this.state.lastTokStart, "Multiple default clauses"); sawDefault = true; cur.test = null; } @@ -414,7 +472,9 @@ export default class StatementParser extends ExpressionParser { parseThrowStatement(node: N.ThrowStatement): N.ThrowStatement { this.next(); - if (lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start))) + if ( + lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start)) + ) this.raise(this.state.lastTokEnd, "Illegal newline after throw"); node.argument = this.parseExpression(); this.semicolon(); @@ -450,7 +510,10 @@ export default class StatementParser extends ExpressionParser { return this.finishNode(node, "TryStatement"); } - parseVarStatement(node: N.VariableDeclaration, kind: TokenType): N.VariableDeclaration { + parseVarStatement( + node: N.VariableDeclaration, + kind: TokenType, + ): N.VariableDeclaration { this.next(); this.parseVar(node, false, kind); this.semicolon(); @@ -467,7 +530,8 @@ export default class StatementParser extends ExpressionParser { } parseWithStatement(node: N.WithStatement): N.WithStatement { - if (this.state.strict) this.raise(this.state.start, "'with' in strict mode"); + if (this.state.strict) + this.raise(this.state.start, "'with' in strict mode"); this.next(); node.object = this.parseParenExpression(); node.body = this.parseStatement(false); @@ -479,14 +543,20 @@ export default class StatementParser extends ExpressionParser { return this.finishNode(node, "EmptyStatement"); } - parseLabeledStatement(node: N.LabeledStatement, maybeName: string, expr: N.Identifier): N.LabeledStatement { + parseLabeledStatement( + node: N.LabeledStatement, + maybeName: string, + expr: N.Identifier, + ): N.LabeledStatement { for (const label of this.state.labels) { if (label.name === maybeName) { this.raise(expr.start, `Label '${maybeName}' is already declared`); } } - const kind = this.state.type.isLoop ? "loop" : this.match(tt._switch) ? "switch" : null; + const kind = this.state.type.isLoop + ? "loop" + : this.match(tt._switch) ? "switch" : null; for (let i = this.state.labels.length - 1; i >= 0; i--) { const label = this.state.labels[i]; if (label.statementStart === node.start) { @@ -497,14 +567,21 @@ export default class StatementParser extends ExpressionParser { } } - this.state.labels.push({ name: maybeName, kind: kind, statementStart: this.state.start }); + this.state.labels.push({ + name: maybeName, + kind: kind, + statementStart: this.state.start, + }); node.body = this.parseStatement(true); this.state.labels.pop(); node.label = expr; return this.finishNode(node, "LabeledStatement"); } - parseExpressionStatement(node: N.ExpressionStatement, expr: N.Expression): N.ExpressionStatement { + parseExpressionStatement( + node: N.ExpressionStatement, + expr: N.Expression, + ): N.ExpressionStatement { node.expression = expr; this.semicolon(); return this.finishNode(node, "ExpressionStatement"); @@ -522,19 +599,36 @@ export default class StatementParser extends ExpressionParser { } isValidDirective(stmt: N.Statement): boolean { - return stmt.type === "ExpressionStatement" && + return ( + stmt.type === "ExpressionStatement" && stmt.expression.type === "StringLiteral" && - !stmt.expression.extra.parenthesized; + !stmt.expression.extra.parenthesized + ); } - parseBlockBody(node: N.BlockStatementLike, allowDirectives: ?boolean, topLevel: boolean, end: TokenType): void { - const body = node.body = []; - const directives = node.directives = []; - this.parseBlockOrModuleBlockBody(body, allowDirectives ? directives : undefined, topLevel, end); + parseBlockBody( + node: N.BlockStatementLike, + allowDirectives: ?boolean, + topLevel: boolean, + end: TokenType, + ): void { + const body = (node.body = []); + const directives = (node.directives = []); + this.parseBlockOrModuleBlockBody( + body, + allowDirectives ? directives : undefined, + topLevel, + end, + ); } // Undefined directives means that directives are not allowed. - parseBlockOrModuleBlockBody(body: N.Statement[], directives: ?N.Directive[], topLevel: boolean, end: TokenType): void { + parseBlockOrModuleBlockBody( + body: N.Statement[], + directives: ?(N.Directive[]), + topLevel: boolean, + end: TokenType, + ): void { let parsedNonDirective = false; let oldStrict; let octalPosition; @@ -575,7 +669,10 @@ export default class StatementParser extends ExpressionParser { // `parseStatement` will already have parsed the init statement or // expression. - parseFor(node: N.ForStatement, init: ?(N.VariableDeclaration | N.Expression)): N.ForStatement { + parseFor( + node: N.ForStatement, + init: ?(N.VariableDeclaration | N.Expression), + ): N.ForStatement { node.init = init; this.expect(tt.semi); node.test = this.match(tt.semi) ? null : this.parseExpression(); @@ -590,7 +687,11 @@ export default class StatementParser extends ExpressionParser { // Parse a `for`/`in` and `for`/`of` loop, which are almost // same from parser's perspective. - parseForIn(node: N.ForInOf, init: N.VariableDeclaration, forAwait: boolean): N.ForInOf { + parseForIn( + node: N.ForInOf, + init: N.VariableDeclaration, + forAwait: boolean, + ): N.ForInOf { const type = this.match(tt._in) ? "ForInStatement" : "ForOfStatement"; if (forAwait) { this.eatContextual("of"); @@ -610,8 +711,12 @@ export default class StatementParser extends ExpressionParser { // Parse a list of variable declarations. - parseVar(node: N.VariableDeclaration, isFor: boolean, kind: TokenType): N.VariableDeclaration { - const declarations = node.declarations = []; + parseVar( + node: N.VariableDeclaration, + isFor: boolean, + kind: TokenType, + ): N.VariableDeclaration { + const declarations = (node.declarations = []); // $FlowFixMe node.kind = kind.keyword; for (;;) { @@ -620,13 +725,22 @@ export default class StatementParser extends ExpressionParser { if (this.eat(tt.eq)) { decl.init = this.parseMaybeAssign(isFor); } else { - if (kind === tt._const && !(this.match(tt._in) || this.isContextual("of"))) { + if ( + kind === tt._const && + !(this.match(tt._in) || this.isContextual("of")) + ) { // `const` with no initializer is allowed in TypeScript. It could be a declaration `const x: number;`. if (!this.hasPlugin("typescript")) { this.unexpected(); } - } else if (decl.id.type !== "Identifier" && !(isFor && (this.match(tt._in) || this.isContextual("of")))) { - this.raise(this.state.lastTokEnd, "Complex binding patterns require an initialization value"); + } else if ( + decl.id.type !== "Identifier" && + !(isFor && (this.match(tt._in) || this.isContextual("of"))) + ) { + this.raise( + this.state.lastTokEnd, + "Complex binding patterns require an initialization value", + ); } decl.init = null; } @@ -644,7 +758,13 @@ export default class StatementParser extends ExpressionParser { // Parse a function declaration or literal (depending on the // `isStatement` parameter). - parseFunction(node: T, isStatement: boolean, allowExpressionBody?: boolean, isAsync?: boolean, optionalId?: boolean): T { + parseFunction( + node: T, + isStatement: boolean, + allowExpressionBody?: boolean, + isAsync?: boolean, + optionalId?: boolean, + ): T { const oldInMethod = this.state.inMethod; this.state.inMethod = false; @@ -659,7 +779,12 @@ export default class StatementParser extends ExpressionParser { } } - if (isStatement && !optionalId && !this.match(tt.name) && !this.match(tt._yield)) { + if ( + isStatement && + !optionalId && + !this.match(tt.name) && + !this.match(tt._yield) + ) { this.unexpected(); } @@ -668,7 +793,11 @@ export default class StatementParser extends ExpressionParser { } this.parseFunctionParams(node); - this.parseFunctionBodyAndFinish(node, isStatement ? "FunctionDeclaration" : "FunctionExpression", allowExpressionBody); + this.parseFunctionBodyAndFinish( + node, + isStatement ? "FunctionDeclaration" : "FunctionExpression", + allowExpressionBody, + ); this.state.inMethod = oldInMethod; return node; } @@ -681,13 +810,20 @@ export default class StatementParser extends ExpressionParser { // Parse a class declaration or literal (depending on the // `isStatement` parameter). - parseClass(node: T, isStatement: /* T === ClassDeclaration */boolean, optionalId?: boolean): T { + parseClass( + node: T, + isStatement: /* T === ClassDeclaration */ boolean, + optionalId?: boolean, + ): T { this.next(); this.takeDecorators(node); this.parseClassId(node, isStatement, optionalId); this.parseClassSuper(node); this.parseClassBody(node); - return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression"); + return this.finishNode( + node, + isStatement ? "ClassDeclaration" : "ClassExpression", + ); } isClassProperty(): boolean { @@ -699,9 +835,11 @@ export default class StatementParser extends ExpressionParser { } isNonstaticConstructor(method: N.ClassMethod | N.ClassProperty): boolean { - return !method.computed && !method.static && ( - (method.key.name === "constructor") || // Identifier - (method.key.value === "constructor") // Literal + return ( + !method.computed && + !method.static && + (method.key.name === "constructor" || // Identifier + method.key.value === "constructor") // Literal ); } @@ -722,7 +860,10 @@ export default class StatementParser extends ExpressionParser { while (!this.eat(tt.braceR)) { if (this.eat(tt.semi)) { if (decorators.length > 0) { - this.raise(this.state.lastTokEnd, "Decorators must not be followed by a semicolon"); + this.raise( + this.state.lastTokEnd, + "Decorators must not be followed by a semicolon", + ); } continue; } @@ -745,13 +886,24 @@ export default class StatementParser extends ExpressionParser { this.parseClassMember(classBody, member, state); - if (this.hasPlugin("decorators2") && member.kind != "method" && member.decorators && member.decorators.length > 0) { - this.raise(member.start, "Stage 2 decorators may only be used with a class or a class method"); + if ( + this.hasPlugin("decorators2") && + member.kind != "method" && + member.decorators && + member.decorators.length > 0 + ) { + this.raise( + member.start, + "Stage 2 decorators may only be used with a class or a class method", + ); } } if (decorators.length) { - this.raise(this.state.start, "You have trailing decorators with no method"); + this.raise( + this.state.start, + "You have trailing decorators with no method", + ); } node.body = this.finishNode(classBody, "ClassBody"); @@ -760,13 +912,18 @@ export default class StatementParser extends ExpressionParser { this.state.strict = oldStrict; } - parseClassMember(classBody: N.ClassBody, member: N.ClassMember, state: { hadConstructor: boolean }): void { + parseClassMember( + classBody: N.ClassBody, + member: N.ClassMember, + state: { hadConstructor: boolean }, + ): void { // Use the appropriate variable to represent `member` once a more specific type is known. const memberAny: any = member; const method: N.ClassMethod = memberAny; const prop: N.ClassProperty = memberAny; - if (this.hasPlugin("classPrivateProperties") && this.match(tt.hash)) { // Private property + if (this.hasPlugin("classPrivateProperties") && this.match(tt.hash)) { + // Private property this.next(); const privateProp: N.ClassPrivateProperty = memberAny; privateProp.key = this.parseIdentifier(true); @@ -783,7 +940,13 @@ export default class StatementParser extends ExpressionParser { method.computed = false; method.key = key; method.static = false; - this.parseClassMethod(classBody, method, false, false, /* isConstructor */ false); + this.parseClassMethod( + classBody, + method, + false, + false, + /* isConstructor */ false, + ); return; } else if (this.isClassProperty()) { // a property named 'static' @@ -800,7 +963,12 @@ export default class StatementParser extends ExpressionParser { this.parseClassMemberWithIsStatic(classBody, member, state, isStatic); } - parseClassMemberWithIsStatic(classBody: N.ClassBody, member: N.ClassMember, state: { hadConstructor: boolean }, isStatic: boolean) { + parseClassMemberWithIsStatic( + classBody: N.ClassBody, + member: N.ClassMember, + state: { hadConstructor: boolean }, + isStatic: boolean, + ) { const memberAny: any = member; const methodOrProp: N.ClassMethod | N.ClassProperty = memberAny; const method: N.ClassMethod = memberAny; @@ -815,10 +983,23 @@ export default class StatementParser extends ExpressionParser { if (this.isNonstaticConstructor(method)) { this.raise(method.key.start, "Constructor can't be a generator"); } - if (!method.computed && method.static && (method.key.name === "prototype" || method.key.value === "prototype")) { - this.raise(method.key.start, "Classes may not have static property named prototype"); + if ( + !method.computed && + method.static && + (method.key.name === "prototype" || method.key.value === "prototype") + ) { + this.raise( + method.key.start, + "Classes may not have static property named prototype", + ); } - this.parseClassMethod(classBody, method, true, false, /* isConstructor */ false); + this.parseClassMethod( + classBody, + method, + true, + false, + /* isConstructor */ false, + ); return; } @@ -827,7 +1008,6 @@ export default class StatementParser extends ExpressionParser { this.parsePostMemberNameModifiers(methodOrProp); - if (this.isClassMethod()) { // a normal method const isConstructor = this.isNonstaticConstructor(method); @@ -839,7 +1019,10 @@ export default class StatementParser extends ExpressionParser { if (isConstructor) { if (method.decorators) { - this.raise(method.start, "You can't attach decorators to a class constructor"); + this.raise( + method.start, + "You can't attach decorators to a class constructor", + ); } // TypeScript allows multiple overloaded constructor declarations. @@ -854,26 +1037,47 @@ export default class StatementParser extends ExpressionParser { this.pushClassProperty(classBody, prop); } else if (isSimple && key.name === "async" && !this.isLineTerminator()) { // an async method - const isGenerator = this.hasPlugin("asyncGenerators") && this.eat(tt.star); + const isGenerator = + this.hasPlugin("asyncGenerators") && this.eat(tt.star); method.kind = "method"; this.parsePropertyName(method); if (this.isNonstaticConstructor(method)) { this.raise(method.key.start, "Constructor can't be an async function"); } - this.parseClassMethod(classBody, method, isGenerator, true, /* isConstructor */ false); - } else if (isSimple && (key.name === "get" || key.name === "set") && !(this.isLineTerminator() && this.match(tt.star))) { // `get\n*` is an uninitialized property named 'get' followed by a generator. + this.parseClassMethod( + classBody, + method, + isGenerator, + true, + /* isConstructor */ false, + ); + } else if ( + isSimple && + (key.name === "get" || key.name === "set") && + !(this.isLineTerminator() && this.match(tt.star)) + ) { + // `get\n*` is an uninitialized property named 'get' followed by a generator. // a getter or setter method.kind = key.name; this.parsePropertyName(method); if (this.isNonstaticConstructor(method)) { this.raise(method.key.start, "Constructor can't have get/set modifier"); } - this.parseClassMethod(classBody, method, false, false, /* isConstructor */ false); + this.parseClassMethod( + classBody, + method, + false, + false, + /* isConstructor */ false, + ); this.checkGetterSetterParamCount(method); } else if (this.isLineTerminator()) { // an uninitialized class property (due to ASI, since we don't otherwise recognize the next token) if (this.isNonstaticConstructor(prop)) { - this.raise(prop.key.start, "Classes may not have a non-static field named 'constructor'"); + this.raise( + prop.key.start, + "Classes may not have a non-static field named 'constructor'", + ); } classBody.body.push(this.parseClassProperty(prop)); } else { @@ -881,31 +1085,48 @@ export default class StatementParser extends ExpressionParser { } } - parseClassPropertyName(methodOrProp: N.ClassMethod | N.ClassProperty): N.Expression { + parseClassPropertyName( + methodOrProp: N.ClassMethod | N.ClassProperty, + ): N.Expression { const key = this.parsePropertyName(methodOrProp); - if (!methodOrProp.computed && methodOrProp.static && (methodOrProp.key.name === "prototype" || methodOrProp.key.value === "prototype")) { - this.raise(methodOrProp.key.start, "Classes may not have static property named prototype"); + if ( + !methodOrProp.computed && + methodOrProp.static && + (methodOrProp.key.name === "prototype" || + methodOrProp.key.value === "prototype") + ) { + this.raise( + methodOrProp.key.start, + "Classes may not have static property named prototype", + ); } return key; } pushClassProperty(classBody: N.ClassBody, prop: N.ClassProperty) { if (this.isNonstaticConstructor(prop)) { - this.raise(prop.key.start, "Classes may not have a non-static field named 'constructor'"); + this.raise( + prop.key.start, + "Classes may not have a non-static field named 'constructor'", + ); } classBody.body.push(this.parseClassProperty(prop)); } // Overridden in typescript.js - // eslint-disable-next-line no-unused-vars - parsePostMemberNameModifiers(methodOrProp: N.ClassMethod | N.ClassProperty): void {} + parsePostMemberNameModifiers( + // eslint-disable-next-line no-unused-vars + methodOrProp: N.ClassMethod | N.ClassProperty, + ): void {} // Overridden in typescript.js parseAccessModifier(): ?N.Accessibility { return undefined; } - parsePrivateClassProperty(node: N.ClassPrivateProperty): N.ClassPrivateProperty { + parsePrivateClassProperty( + node: N.ClassPrivateProperty, + ): N.ClassPrivateProperty { this.state.inClassProperty = true; if (this.match(tt.eq)) { @@ -920,8 +1141,10 @@ export default class StatementParser extends ExpressionParser { } parseClassProperty(node: N.ClassProperty): N.ClassProperty { - const hasPlugin = this.hasPlugin("classProperties") || this.hasPlugin("typescript"); - const noPluginMsg = "You can only use Class Properties when the 'classProperties' plugin is enabled."; + const hasPlugin = + this.hasPlugin("classProperties") || this.hasPlugin("typescript"); + const noPluginMsg = + "You can only use Class Properties when the 'classProperties' plugin is enabled."; if (!node.typeAnnotation && !hasPlugin) { this.raise(node.start, noPluginMsg); } @@ -940,11 +1163,29 @@ export default class StatementParser extends ExpressionParser { return this.finishNode(node, "ClassProperty"); } - parseClassMethod(classBody: N.ClassBody, method: N.ClassMethod, isGenerator: boolean, isAsync: boolean, isConstructor: boolean): void { - classBody.body.push(this.parseMethod(method, isGenerator, isAsync, isConstructor, "ClassMethod")); + parseClassMethod( + classBody: N.ClassBody, + method: N.ClassMethod, + isGenerator: boolean, + isAsync: boolean, + isConstructor: boolean, + ): void { + classBody.body.push( + this.parseMethod( + method, + isGenerator, + isAsync, + isConstructor, + "ClassMethod", + ), + ); } - parseClassId(node: N.Class, isStatement: boolean, optionalId: ?boolean): void { + parseClassId( + node: N.Class, + isStatement: boolean, + optionalId: ?boolean, + ): void { if (this.match(tt.name)) { node.id = this.parseIdentifier(); } else { @@ -970,14 +1211,19 @@ export default class StatementParser extends ExpressionParser { this.next(); if (this.hasPlugin("exportExtensions") && this.eatContextual("as")) { specifier.exported = this.parseIdentifier(true); - node.specifiers = [this.finishNode(specifier, "ExportNamespaceSpecifier")]; + node.specifiers = [ + this.finishNode(specifier, "ExportNamespaceSpecifier"), + ]; this.parseExportSpecifiersMaybe(node); this.parseExportFrom(node, true); } else { this.parseExportFrom(node, true); return this.finishNode(node, "ExportAllDeclaration"); } - } else if (this.hasPlugin("exportExtensions") && this.isExportDefaultSpecifier()) { + } else if ( + this.hasPlugin("exportExtensions") && + this.isExportDefaultSpecifier() + ) { const specifier = this.startNode(); specifier.exported = this.parseIdentifier(true); const specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")]; @@ -993,7 +1239,8 @@ export default class StatementParser extends ExpressionParser { this.parseExportSpecifiersMaybe(node); } this.parseExportFrom(node, true); - } else if (this.eat(tt._default)) { // export default ... + } else if (this.eat(tt._default)) { + // export default ... let expr = this.startNode(); let needsSemi = false; if (this.eat(tt._function)) { @@ -1001,7 +1248,8 @@ export default class StatementParser extends ExpressionParser { } else if ( this.isContextual("async") && this.lookahead().type === tt._function - ) { // async function declaration + ) { + // async function declaration this.eatContextual("async"); this.eat(tt._function); expr = this.parseFunction(expr, true, false, true, true); @@ -1019,7 +1267,8 @@ export default class StatementParser extends ExpressionParser { node.specifiers = []; node.source = null; node.declaration = this.parseExportDeclaration(node); - } else { // export { x, y as z } [from '...'] + } else { + // export { x, y as z } [from '...'] node.declaration = null; node.specifiers = this.parseExportSpecifiers(); this.parseExportFrom(node); @@ -1043,7 +1292,10 @@ export default class StatementParser extends ExpressionParser { } const lookahead = this.lookahead(); - return lookahead.type === tt.comma || (lookahead.type === tt.name && lookahead.value === "from"); + return ( + lookahead.type === tt.comma || + (lookahead.type === tt.name && lookahead.value === "from") + ); } parseExportSpecifiersMaybe(node: N.ExportNamedDeclaration): void { @@ -1054,7 +1306,9 @@ export default class StatementParser extends ExpressionParser { parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void { if (this.eatContextual("from")) { - node.source = this.match(tt.string) ? this.parseExprAtom() : this.unexpected(); + node.source = this.match(tt.string) + ? this.parseExprAtom() + : this.unexpected(); this.checkExport(node); } else { if (expect) { @@ -1068,15 +1322,21 @@ export default class StatementParser extends ExpressionParser { } shouldParseExportDeclaration(): boolean { - return this.state.type.keyword === "var" - || this.state.type.keyword === "const" - || this.state.type.keyword === "let" - || this.state.type.keyword === "function" - || this.state.type.keyword === "class" - || this.isContextual("async"); + return ( + this.state.type.keyword === "var" || + this.state.type.keyword === "const" || + this.state.type.keyword === "let" || + this.state.type.keyword === "function" || + this.state.type.keyword === "class" || + this.isContextual("async") + ); } - checkExport(node: N.ExportNamedDeclaration, checkNames: ?boolean, isDefault?: boolean): void { + checkExport( + node: N.ExportNamedDeclaration, + checkNames: ?boolean, + isDefault?: boolean, + ): void { if (checkNames) { // Check for duplicate exports if (isDefault) { @@ -1089,7 +1349,10 @@ export default class StatementParser extends ExpressionParser { } } else if (node.declaration) { // Exported declarations - if (node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration") { + if ( + node.declaration.type === "FunctionDeclaration" || + node.declaration.type === "ClassDeclaration" + ) { this.checkDuplicateExports(node, node.declaration.id.name); } else if (node.declaration.type === "VariableDeclaration") { for (const declaration of node.declaration.declarations) { @@ -1099,11 +1362,19 @@ export default class StatementParser extends ExpressionParser { } } - const currentContextDecorators = this.state.decoratorStack[this.state.decoratorStack.length - 1]; + const currentContextDecorators = this.state.decoratorStack[ + this.state.decoratorStack.length - 1 + ]; if (currentContextDecorators.length) { - const isClass = node.declaration && (node.declaration.type === "ClassDeclaration" || node.declaration.type === "ClassExpression"); + const isClass = + node.declaration && + (node.declaration.type === "ClassDeclaration" || + node.declaration.type === "ClassExpression"); if (!node.declaration || !isClass) { - throw this.raise(node.start, "You can only use decorators on an export when exporting a class"); + throw this.raise( + node.start, + "You can only use decorators on an export when exporting a class", + ); } this.takeDecorators(node.declaration); } @@ -1130,17 +1401,25 @@ export default class StatementParser extends ExpressionParser { } } - checkDuplicateExports(node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier, name: string): void { + checkDuplicateExports( + node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier, + name: string, + ): void { if (this.state.exportedIdentifiers.indexOf(name) > -1) { this.raiseDuplicateExportError(node, name); } this.state.exportedIdentifiers.push(name); } - raiseDuplicateExportError(node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier, name: string): empty { - throw this.raise(node.start, name === "default" ? - "Only one default export allowed per module." : - `\`${name}\` has already been exported. Exported identifiers must be unique.` + raiseDuplicateExportError( + node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier, + name: string, + ): empty { + throw this.raise( + node.start, + name === "default" + ? "Only one default export allowed per module." + : `\`${name}\` has already been exported. Exported identifiers must be unique.`, ); } @@ -1167,7 +1446,9 @@ export default class StatementParser extends ExpressionParser { const node = this.startNode(); node.local = this.parseIdentifier(isDefault); - node.exported = this.eatContextual("as") ? this.parseIdentifier(true) : node.local.__clone(); + node.exported = this.eatContextual("as") + ? this.parseIdentifier(true) + : node.local.__clone(); nodes.push(this.finishNode(node, "ExportSpecifier")); } @@ -1190,7 +1471,9 @@ export default class StatementParser extends ExpressionParser { node.specifiers = []; this.parseImportSpecifiers(node); this.expectContextual("from"); - node.source = this.match(tt.string) ? this.parseExprAtom() : this.unexpected(); + node.source = this.match(tt.string) + ? this.parseExprAtom() + : this.unexpected(); } this.semicolon(); return this.finishNode(node, "ImportDeclaration"); @@ -1204,7 +1487,13 @@ export default class StatementParser extends ExpressionParser { // import defaultObj, { x, y as z } from '...' const startPos = this.state.start; const startLoc = this.state.startLoc; - node.specifiers.push(this.parseImportSpecifierDefault(this.parseIdentifier(), startPos, startLoc)); + node.specifiers.push( + this.parseImportSpecifierDefault( + this.parseIdentifier(), + startPos, + startLoc, + ), + ); if (!this.eat(tt.comma)) return; } @@ -1213,8 +1502,15 @@ export default class StatementParser extends ExpressionParser { this.next(); this.expectContextual("as"); specifier.local = this.parseIdentifier(); - this.checkLVal(specifier.local, true, undefined, "import namespace specifier"); - node.specifiers.push(this.finishNode(specifier, "ImportNamespaceSpecifier")); + this.checkLVal( + specifier.local, + true, + undefined, + "import namespace specifier", + ); + node.specifiers.push( + this.finishNode(specifier, "ImportNamespaceSpecifier"), + ); return; } @@ -1225,7 +1521,10 @@ export default class StatementParser extends ExpressionParser { } else { // Detect an attempt to deep destructure if (this.eat(tt.colon)) { - this.unexpected(null, "ES2015 named imports do not destructure. Use another statement for destructuring after the import."); + this.unexpected( + null, + "ES2015 named imports do not destructure. Use another statement for destructuring after the import.", + ); } this.expect(tt.comma); @@ -1242,14 +1541,23 @@ export default class StatementParser extends ExpressionParser { if (this.eatContextual("as")) { specifier.local = this.parseIdentifier(); } else { - this.checkReservedWord(specifier.imported.name, specifier.start, true, true); + this.checkReservedWord( + specifier.imported.name, + specifier.start, + true, + true, + ); specifier.local = specifier.imported.__clone(); } this.checkLVal(specifier.local, true, undefined, "import specifier"); node.specifiers.push(this.finishNode(specifier, "ImportSpecifier")); } - parseImportSpecifierDefault(id: N.Identifier, startPos: number, startLoc: Position): N.ImportDefaultSpecifier { + parseImportSpecifierDefault( + id: N.Identifier, + startPos: number, + startLoc: Position, + ): N.ImportDefaultSpecifier { const node = this.startNodeAt(startPos, startLoc); node.local = id; this.checkLVal(node.local, true, undefined, "default import specifier"); diff --git a/src/parser/util.js b/src/parser/util.js index 7f7531ac27..bc789ef993 100644 --- a/src/parser/util.js +++ b/src/parser/util.js @@ -13,7 +13,7 @@ export default class UtilParser extends Tokenizer { addExtra(node: Node, key: string, val: any): void { if (!node) return; - const extra = node.extra = node.extra || {}; + const extra = (node.extra = node.extra || {}); extra[key] = val; } @@ -64,13 +64,17 @@ export default class UtilParser extends Tokenizer { // Test whether a semicolon can be inserted at the current position. canInsertSemicolon(): boolean { - return this.match(tt.eof) || + return ( + this.match(tt.eof) || this.match(tt.braceR) || - this.hasPrecedingLineBreak(); + this.hasPrecedingLineBreak() + ); } hasPrecedingLineBreak(): boolean { - return lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start)); + return lineBreak.test( + this.input.slice(this.state.lastTokEnd, this.state.start), + ); } // TODO @@ -96,7 +100,10 @@ export default class UtilParser extends Tokenizer { // Raise an unexpected token error. Can take the expected token type // instead of a message string. - unexpected(pos: ?number, messageOrType: string | TokenType = "Unexpected token"): empty { + unexpected( + pos: ?number, + messageOrType: string | TokenType = "Unexpected token", + ): empty { if (typeof messageOrType !== "string") { messageOrType = `Unexpected token, expected ${messageOrType.label}`; } diff --git a/src/plugins/estree.js b/src/plugins/estree.js index 8a96ed83b4..86129d2731 100644 --- a/src/plugins/estree.js +++ b/src/plugins/estree.js @@ -6,283 +6,336 @@ import * as N from "../types"; import type { Pos, Position } from "../util/location"; function isSimpleProperty(node: N.Node): boolean { - return node != null && + return ( + node != null && node.type === "Property" && node.kind === "init" && - node.method === false; + node.method === false + ); } -export default (superClass: Class): Class => class extends superClass { - estreeParseRegExpLiteral({ pattern, flags }: N.RegExpLiteral): N.Node { - let regex = null; - try { - regex = new RegExp(pattern, flags); - } catch (e) { - // In environments that don't support these flags value will - // be null as the regex can't be represented natively. +export default (superClass: Class): Class => + class extends superClass { + estreeParseRegExpLiteral({ pattern, flags }: N.RegExpLiteral): N.Node { + let regex = null; + try { + regex = new RegExp(pattern, flags); + } catch (e) { + // In environments that don't support these flags value will + // be null as the regex can't be represented natively. + } + const node = this.estreeParseLiteral(regex); + node.regex = { pattern, flags }; + + return node; } - const node = this.estreeParseLiteral(regex); - node.regex = { pattern, flags }; - return node; - } - - estreeParseLiteral(value: any): N.Node { - return this.parseLiteral(value, "Literal"); - } - - directiveToStmt(directive: N.Directive): N.ExpressionStatement { - const directiveLiteral = directive.value; - - const stmt = this.startNodeAt(directive.start, directive.loc.start); - const expression = this.startNodeAt(directiveLiteral.start, directiveLiteral.loc.start); - - expression.value = directiveLiteral.value; - expression.raw = directiveLiteral.extra.raw; - - stmt.expression = this.finishNodeAt( - expression, "Literal", directiveLiteral.end, directiveLiteral.loc.end); - stmt.directive = directiveLiteral.extra.raw.slice(1, -1); - - return this.finishNodeAt(stmt, "ExpressionStatement", directive.end, directive.loc.end); - } - - // ================================== - // Overrides - // ================================== - - checkDeclaration(node: N.Pattern): void { - if (isSimpleProperty(node)) { - // $FlowFixMe - this.checkDeclaration(node.value); - } else { - super.checkDeclaration(node); + estreeParseLiteral(value: any): N.Node { + return this.parseLiteral(value, "Literal"); } - } - checkGetterSetterParamCount(prop: N.ObjectMethod | N.ClassMethod): void { - const paramCount = prop.kind === "get" ? 0 : 1; - // $FlowFixMe (prop.value present for ObjectMethod, but for ClassMethod should use prop.params?) - if (prop.value.params.length !== paramCount) { - const start = prop.start; - if (prop.kind === "get") { - this.raise(start, "getter should have no params"); + directiveToStmt(directive: N.Directive): N.ExpressionStatement { + const directiveLiteral = directive.value; + + const stmt = this.startNodeAt(directive.start, directive.loc.start); + const expression = this.startNodeAt( + directiveLiteral.start, + directiveLiteral.loc.start, + ); + + expression.value = directiveLiteral.value; + expression.raw = directiveLiteral.extra.raw; + + stmt.expression = this.finishNodeAt( + expression, + "Literal", + directiveLiteral.end, + directiveLiteral.loc.end, + ); + stmt.directive = directiveLiteral.extra.raw.slice(1, -1); + + return this.finishNodeAt( + stmt, + "ExpressionStatement", + directive.end, + directive.loc.end, + ); + } + + // ================================== + // Overrides + // ================================== + + checkDeclaration(node: N.Pattern): void { + if (isSimpleProperty(node)) { + // $FlowFixMe + this.checkDeclaration(node.value); } else { - this.raise(start, "setter should have exactly one param"); + super.checkDeclaration(node); } } - } - checkLVal( - expr: N.Expression, - isBinding: ?boolean, - checkClashes: ?{ [key: string]: boolean }, - contextDescription: string - ): void { - switch (expr.type) { - case "ObjectPattern": - expr.properties.forEach((prop) => { - this.checkLVal( - prop.type === "Property" ? prop.value : prop, - isBinding, - checkClashes, - "object destructuring pattern" - ); - }); - break; - default: - super.checkLVal(expr, isBinding, checkClashes, contextDescription); - } - } - - checkPropClash(prop: N.ObjectMember, propHash: { [key: string]: boolean }): void { - if (prop.computed || !isSimpleProperty(prop)) return; - - const key = prop.key; - // It is either an Identifier or a String/NumericLiteral - const name = key.type === "Identifier" ? key.name : String(key.value); - - if (name === "__proto__") { - if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property"); - propHash.proto = true; - } - } - - isStrictBody(node: { body: N.BlockStatement }, isExpression: ?boolean): boolean { - if (!isExpression && node.body.body.length > 0) { - for (const directive of node.body.body) { - if (directive.type === "ExpressionStatement" && directive.expression.type === "Literal") { - if (directive.expression.value === "use strict") return true; + checkGetterSetterParamCount(prop: N.ObjectMethod | N.ClassMethod): void { + const paramCount = prop.kind === "get" ? 0 : 1; + // $FlowFixMe (prop.value present for ObjectMethod, but for ClassMethod should use prop.params?) + if (prop.value.params.length !== paramCount) { + const start = prop.start; + if (prop.kind === "get") { + this.raise(start, "getter should have no params"); } else { - // Break for the first non literal expression + this.raise(start, "setter should have exactly one param"); + } + } + } + + checkLVal( + expr: N.Expression, + isBinding: ?boolean, + checkClashes: ?{ [key: string]: boolean }, + contextDescription: string, + ): void { + switch (expr.type) { + case "ObjectPattern": + expr.properties.forEach(prop => { + this.checkLVal( + prop.type === "Property" ? prop.value : prop, + isBinding, + checkClashes, + "object destructuring pattern", + ); + }); break; - } + default: + super.checkLVal(expr, isBinding, checkClashes, contextDescription); } } - return false; - } + checkPropClash( + prop: N.ObjectMember, + propHash: { [key: string]: boolean }, + ): void { + if (prop.computed || !isSimpleProperty(prop)) return; - isValidDirective(stmt: N.Statement): boolean { - return stmt.type === "ExpressionStatement" && - stmt.expression.type === "Literal" && - typeof stmt.expression.value === "string" && - (!stmt.expression.extra || !stmt.expression.extra.parenthesized); - } + const key = prop.key; + // It is either an Identifier or a String/NumericLiteral + const name = key.type === "Identifier" ? key.name : String(key.value); - stmtToDirective(stmt: N.Statement): N.Directive { - const directive = super.stmtToDirective(stmt); - const value = stmt.expression.value; + if (name === "__proto__") { + if (propHash.proto) + this.raise(key.start, "Redefinition of __proto__ property"); + propHash.proto = true; + } + } - // Reset value to the actual value as in estree mode we want - // the stmt to have the real value and not the raw value - directive.value.value = value; + isStrictBody( + node: { body: N.BlockStatement }, + isExpression: ?boolean, + ): boolean { + if (!isExpression && node.body.body.length > 0) { + for (const directive of node.body.body) { + if ( + directive.type === "ExpressionStatement" && + directive.expression.type === "Literal" + ) { + if (directive.expression.value === "use strict") return true; + } else { + // Break for the first non literal expression + break; + } + } + } - return directive; - } + return false; + } - parseBlockBody( - node: N.BlockStatementLike, - allowDirectives: ?boolean, - topLevel: boolean, - end: TokenType - ): void { - super.parseBlockBody(node, allowDirectives, topLevel, end); + isValidDirective(stmt: N.Statement): boolean { + return ( + stmt.type === "ExpressionStatement" && + stmt.expression.type === "Literal" && + typeof stmt.expression.value === "string" && + (!stmt.expression.extra || !stmt.expression.extra.parenthesized) + ); + } - const directiveStatements = node.directives.map((d) => this.directiveToStmt(d)); - node.body = directiveStatements.concat(node.body); - delete node.directives; - } + stmtToDirective(stmt: N.Statement): N.Directive { + const directive = super.stmtToDirective(stmt); + const value = stmt.expression.value; - parseClassMethod( - classBody: N.ClassBody, - method: N.ClassMethod, - isGenerator: boolean, - isAsync: boolean, - isConstructor: boolean - ): void { - this.parseMethod(method, isGenerator, isAsync, isConstructor, "MethodDefinition"); - if (method.typeParameters) { + // Reset value to the actual value as in estree mode we want + // the stmt to have the real value and not the raw value + directive.value.value = value; + + return directive; + } + + parseBlockBody( + node: N.BlockStatementLike, + allowDirectives: ?boolean, + topLevel: boolean, + end: TokenType, + ): void { + super.parseBlockBody(node, allowDirectives, topLevel, end); + + const directiveStatements = node.directives.map(d => + this.directiveToStmt(d), + ); + node.body = directiveStatements.concat(node.body); + delete node.directives; + } + + parseClassMethod( + classBody: N.ClassBody, + method: N.ClassMethod, + isGenerator: boolean, + isAsync: boolean, + isConstructor: boolean, + ): void { + this.parseMethod( + method, + isGenerator, + isAsync, + isConstructor, + "MethodDefinition", + ); + if (method.typeParameters) { + // $FlowIgnore + method.value.typeParameters = method.typeParameters; + delete method.typeParameters; + } + classBody.body.push(method); + } + + parseExprAtom(refShorthandDefaultPos?: ?Pos): N.Expression { + switch (this.state.type) { + case tt.regexp: + return this.estreeParseRegExpLiteral(this.state.value); + + case tt.num: + case tt.string: + return this.estreeParseLiteral(this.state.value); + + case tt._null: + return this.estreeParseLiteral(null); + + case tt._true: + return this.estreeParseLiteral(true); + + case tt._false: + return this.estreeParseLiteral(false); + + default: + return super.parseExprAtom(refShorthandDefaultPos); + } + } + + parseLiteral( + value: any, + type: /*T["kind"]*/ string, + startPos?: number, + startLoc?: Position, + ): T { + const node = super.parseLiteral(value, type, startPos, startLoc); + node.raw = node.extra.raw; + delete node.extra; + + return node; + } + + parseMethod( + node: T, + isGenerator: boolean, + isAsync: boolean, + isConstructor: boolean, + type: string, + ): T { + let funcNode = this.startNode(); + funcNode.kind = node.kind; // provide kind, so super method correctly sets state + funcNode = super.parseMethod( + funcNode, + isGenerator, + isAsync, + isConstructor, + "FunctionExpression", + ); + delete funcNode.kind; // $FlowIgnore - method.value.typeParameters = method.typeParameters; - delete method.typeParameters; - } - classBody.body.push(method); - } + node.value = funcNode; - parseExprAtom(refShorthandDefaultPos?: ?Pos): N.Expression { - switch (this.state.type) { - case tt.regexp: - return this.estreeParseRegExpLiteral(this.state.value); - - case tt.num: - case tt.string: - return this.estreeParseLiteral(this.state.value); - - case tt._null: - return this.estreeParseLiteral(null); - - case tt._true: - return this.estreeParseLiteral(true); - - case tt._false: - return this.estreeParseLiteral(false); - - default: - return super.parseExprAtom(refShorthandDefaultPos); - } - } - - parseLiteral( - value: any, - type: /*T["kind"]*/string, - startPos?: number, - startLoc?: Position - ): T { - const node = super.parseLiteral(value, type, startPos, startLoc); - node.raw = node.extra.raw; - delete node.extra; - - return node; - } - - parseMethod( - node: T, - isGenerator: boolean, - isAsync: boolean, - isConstructor: boolean, - type: string, - ): T { - let funcNode = this.startNode(); - funcNode.kind = node.kind; // provide kind, so super method correctly sets state - funcNode = super.parseMethod(funcNode, isGenerator, isAsync, isConstructor, "FunctionExpression"); - delete funcNode.kind; - // $FlowIgnore - node.value = funcNode; - - return this.finishNode(node, type); - } - - parseObjectMethod( - prop: N.ObjectMethod, - isGenerator: boolean, - isAsync: boolean, - isPattern: boolean - ): ?N.ObjectMethod { - const node: N.EstreeProperty = (super.parseObjectMethod(prop, isGenerator, isAsync, isPattern): any); - - if (node) { - node.type = "Property"; - if (node.kind === "method") node.kind = "init"; - node.shorthand = false; + return this.finishNode(node, type); } - return (node: any); - } + parseObjectMethod( + prop: N.ObjectMethod, + isGenerator: boolean, + isAsync: boolean, + isPattern: boolean, + ): ?N.ObjectMethod { + const node: N.EstreeProperty = (super.parseObjectMethod( + prop, + isGenerator, + isAsync, + isPattern, + ): any); - parseObjectProperty( - prop: N.ObjectProperty, - startPos: ?number, - startLoc: ?Position, - isPattern: boolean, - refShorthandDefaultPos: ?Pos - ): ?N.ObjectProperty { - const node: N.EstreeProperty = ( - super.parseObjectProperty(prop, startPos, startLoc, isPattern, refShorthandDefaultPos): any - ); - - if (node) { - node.kind = "init"; - node.type = "Property"; - } - - return (node: any); - } - - toAssignable( - node: N.Node, - isBinding: ?boolean, - contextDescription: string - ): N.Node { - if (isSimpleProperty(node)) { - this.toAssignable(node.value, isBinding, contextDescription); - - return node; - } else if (node.type === "ObjectExpression") { - node.type = "ObjectPattern"; - for (const prop of node.properties) { - if (prop.kind === "get" || prop.kind === "set") { - this.raise(prop.key.start, "Object pattern can't contain getter or setter"); - } else if (prop.method) { - this.raise(prop.key.start, "Object pattern can't contain methods"); - } else { - this.toAssignable(prop, isBinding, "object destructuring pattern"); - } + if (node) { + node.type = "Property"; + if (node.kind === "method") node.kind = "init"; + node.shorthand = false; } - return node; + return (node: any); } - return super.toAssignable(node, isBinding, contextDescription); - } -}; + parseObjectProperty( + prop: N.ObjectProperty, + startPos: ?number, + startLoc: ?Position, + isPattern: boolean, + refShorthandDefaultPos: ?Pos, + ): ?N.ObjectProperty { + const node: N.EstreeProperty = (super.parseObjectProperty( + prop, + startPos, + startLoc, + isPattern, + refShorthandDefaultPos, + ): any); + + if (node) { + node.kind = "init"; + node.type = "Property"; + } + + return (node: any); + } + + toAssignable( + node: N.Node, + isBinding: ?boolean, + contextDescription: string, + ): N.Node { + if (isSimpleProperty(node)) { + this.toAssignable(node.value, isBinding, contextDescription); + + return node; + } else if (node.type === "ObjectExpression") { + node.type = "ObjectPattern"; + for (const prop of node.properties) { + if (prop.kind === "get" || prop.kind === "set") { + this.raise( + prop.key.start, + "Object pattern can't contain getter or setter", + ); + } else if (prop.method) { + this.raise(prop.key.start, "Object pattern can't contain methods"); + } else { + this.toAssignable(prop, isBinding, "object destructuring pattern"); + } + } + + return node; + } + + return super.toAssignable(node, isBinding, contextDescription); + } + }; diff --git a/src/plugins/flow.js b/src/plugins/flow.js index a6edbf369c..b3faa7c350 100644 --- a/src/plugins/flow.js +++ b/src/plugins/flow.js @@ -16,18 +16,17 @@ const primitiveTypes = [ "number", "string", "void", - "null" + "null", ]; function isEsModuleType(bodyElement: N.Node): boolean { - return bodyElement.type === "DeclareExportAllDeclaration" || - ( - bodyElement.type === "DeclareExportDeclaration" && - ( - !bodyElement.declaration || - (bodyElement.declaration.type !== "TypeAlias" && bodyElement.declaration.type !== "InterfaceDeclaration") - ) - ); + return ( + bodyElement.type === "DeclareExportAllDeclaration" || + (bodyElement.type === "DeclareExportDeclaration" && + (!bodyElement.declaration || + (bodyElement.declaration.type !== "TypeAlias" && + bodyElement.declaration.type !== "InterfaceDeclaration"))) + ); } const exportSuggestions = { @@ -37,1543 +36,1810 @@ const exportSuggestions = { interface: "export interface", }; -export default (superClass: Class): Class => class extends superClass { - flowParseTypeInitialiser(tok?: TokenType): N.FlowType { - const oldInType = this.state.inType; - this.state.inType = true; - this.expect(tok || tt.colon); +export default (superClass: Class): Class => + class extends superClass { + flowParseTypeInitialiser(tok?: TokenType): N.FlowType { + const oldInType = this.state.inType; + this.state.inType = true; + this.expect(tok || tt.colon); - const type = this.flowParseType(); - this.state.inType = oldInType; - return type; - } - - flowParsePredicate() : N.FlowType { - const node = this.startNode(); - const moduloLoc = this.state.startLoc; - const moduloPos = this.state.start; - this.expect(tt.modulo); - const checksLoc = this.state.startLoc; - this.expectContextual("checks"); - // Force '%' and 'checks' to be adjacent - if (moduloLoc.line !== checksLoc.line || moduloLoc.column !== checksLoc.column - 1) { - this.raise(moduloPos, "Spaces between ´%´ and ´checks´ are not allowed here."); - } - if (this.eat(tt.parenL)) { - node.value = this.parseExpression(); - this.expect(tt.parenR); - return this.finishNode(node, "DeclaredPredicate"); - } else { - return this.finishNode(node, "InferredPredicate"); - } - } - - flowParseTypeAndPredicateInitialiser(): [?N.FlowType, ?N.FlowPredicate] { - const oldInType = this.state.inType; - this.state.inType = true; - this.expect(tt.colon); - let type = null; - let predicate = null; - if (this.match(tt.modulo)) { - this.state.inType = oldInType; - predicate = this.flowParsePredicate(); - } else { - type = this.flowParseType(); + const type = this.flowParseType(); this.state.inType = oldInType; + return type; + } + + flowParsePredicate(): N.FlowType { + const node = this.startNode(); + const moduloLoc = this.state.startLoc; + const moduloPos = this.state.start; + this.expect(tt.modulo); + const checksLoc = this.state.startLoc; + this.expectContextual("checks"); + // Force '%' and 'checks' to be adjacent + if ( + moduloLoc.line !== checksLoc.line || + moduloLoc.column !== checksLoc.column - 1 + ) { + this.raise( + moduloPos, + "Spaces between ´%´ and ´checks´ are not allowed here.", + ); + } + if (this.eat(tt.parenL)) { + node.value = this.parseExpression(); + this.expect(tt.parenR); + return this.finishNode(node, "DeclaredPredicate"); + } else { + return this.finishNode(node, "InferredPredicate"); + } + } + + flowParseTypeAndPredicateInitialiser(): [?N.FlowType, ?N.FlowPredicate] { + const oldInType = this.state.inType; + this.state.inType = true; + this.expect(tt.colon); + let type = null; + let predicate = null; if (this.match(tt.modulo)) { + this.state.inType = oldInType; predicate = this.flowParsePredicate(); - } - } - return [type, predicate]; - } - - flowParseDeclareClass(node: N.FlowDeclareClass): N.FlowDeclareClass { - this.next(); - this.flowParseInterfaceish(node); - return this.finishNode(node, "DeclareClass"); - } - - flowParseDeclareFunction(node: N.FlowDeclareFunction): N.FlowDeclareFunction { - this.next(); - - const id = node.id = this.parseIdentifier(); - - const typeNode = this.startNode(); - const typeContainer = this.startNode(); - - if (this.isRelational("<")) { - typeNode.typeParameters = this.flowParseTypeParameterDeclaration(); - } else { - typeNode.typeParameters = null; - } - - this.expect(tt.parenL); - const tmp = this.flowParseFunctionTypeParams(); - typeNode.params = tmp.params; - typeNode.rest = tmp.rest; - this.expect(tt.parenR); - - // $FlowFixMe (destructuring not supported yet) - [typeNode.returnType, node.predicate] = this.flowParseTypeAndPredicateInitialiser(); - typeContainer.typeAnnotation = this.finishNode(typeNode, "FunctionTypeAnnotation"); - - id.typeAnnotation = this.finishNode(typeContainer, "TypeAnnotation"); - - this.finishNode(id, id.type); - - this.semicolon(); - - return this.finishNode(node, "DeclareFunction"); - } - - flowParseDeclare(node: N.FlowDeclare, insideModule?: boolean): N.FlowDeclare { - if (this.match(tt._class)) { - return this.flowParseDeclareClass(node); - } else if (this.match(tt._function)) { - return this.flowParseDeclareFunction(node); - } else if (this.match(tt._var)) { - return this.flowParseDeclareVariable(node); - } else if (this.isContextual("module")) { - if (this.lookahead().type === tt.dot) { - return this.flowParseDeclareModuleExports(node); } else { - if (insideModule) this.unexpected(null, "`declare module` cannot be used inside another `declare module`"); - return this.flowParseDeclareModule(node); - } - } else if (this.isContextual("type")) { - return this.flowParseDeclareTypeAlias(node); - } else if (this.isContextual("interface")) { - return this.flowParseDeclareInterface(node); - } else if (this.match(tt._export)) { - return this.flowParseDeclareExportDeclaration(node, insideModule); - } else { - throw this.unexpected(); - } - } - - flowParseDeclareVariable(node: N.FlowDeclareVariable): N.FlowDeclareVariable { - this.next(); - node.id = this.flowParseTypeAnnotatableIdentifier(); - this.semicolon(); - return this.finishNode(node, "DeclareVariable"); - } - - flowParseDeclareModule(node: N.FlowDeclareModule): N.FlowDeclareModule { - this.next(); - - if (this.match(tt.string)) { - node.id = this.parseExprAtom(); - } else { - node.id = this.parseIdentifier(); - } - - const bodyNode = node.body = this.startNode(); - const body = bodyNode.body = []; - this.expect(tt.braceL); - while (!this.match(tt.braceR)) { - let bodyNode = this.startNode(); - - if (this.match(tt._import)) { - const lookahead = this.lookahead(); - if (lookahead.value !== "type" && lookahead.value !== "typeof") { - this.unexpected(null, "Imports within a `declare module` body must always be `import type` or `import typeof`"); + type = this.flowParseType(); + this.state.inType = oldInType; + if (this.match(tt.modulo)) { + predicate = this.flowParsePredicate(); } - this.next(); - this.parseImport(bodyNode); - } else { - this.expectContextual("declare", "Only declares and type imports are allowed inside declare module"); - - bodyNode = this.flowParseDeclare(bodyNode, true); } - - body.push(bodyNode); + return [type, predicate]; } - this.expect(tt.braceR); - this.finishNode(bodyNode, "BlockStatement"); + flowParseDeclareClass(node: N.FlowDeclareClass): N.FlowDeclareClass { + this.next(); + this.flowParseInterfaceish(node); + return this.finishNode(node, "DeclareClass"); + } - let kind = null; - let hasModuleExport = false; - const errorMessage = "Found both `declare module.exports` and `declare export` in the same module. Modules can only have 1 since they are either an ES module or they are a CommonJS module"; - body.forEach((bodyElement) => { - if (isEsModuleType(bodyElement)) { - if (kind === "CommonJS") this.unexpected(bodyElement.start, errorMessage); - kind = "ES"; - } else if (bodyElement.type === "DeclareModuleExports") { - if (hasModuleExport) this.unexpected(bodyElement.start, "Duplicate `declare module.exports` statement"); - if (kind === "ES") this.unexpected(bodyElement.start, errorMessage); - kind = "CommonJS"; - hasModuleExport = true; - } - }); + flowParseDeclareFunction( + node: N.FlowDeclareFunction, + ): N.FlowDeclareFunction { + this.next(); - node.kind = kind || "CommonJS"; - return this.finishNode(node, "DeclareModule"); - } + const id = (node.id = this.parseIdentifier()); - flowParseDeclareExportDeclaration(node: N.FlowDeclareExportDeclaration, insideModule: ?boolean): N.FlowDeclareExportDeclaration { - this.expect(tt._export); + const typeNode = this.startNode(); + const typeContainer = this.startNode(); - if (this.eat(tt._default)) { - if (this.match(tt._function) || this.match(tt._class)) { - // declare export default class ... - // declare export default function ... - node.declaration = this.flowParseDeclare(this.startNode()); + if (this.isRelational("<")) { + typeNode.typeParameters = this.flowParseTypeParameterDeclaration(); } else { - // declare export default [type]; - node.declaration = this.flowParseType(); - this.semicolon(); - } - node.default = true; - - return this.finishNode(node, "DeclareExportDeclaration"); - } else { - if ( - this.match(tt._const) || this.match(tt._let) || - ( - (this.isContextual("type") || this.isContextual("interface")) && - !insideModule - ) - ) { - const label = this.state.value; - const suggestion = exportSuggestions[label]; - this.unexpected(this.state.start, `\`declare export ${label}\` is not supported. Use \`${suggestion}\` instead`); + typeNode.typeParameters = null; } - if ( - this.match(tt._var) || // declare export var ... - this.match(tt._function) || // declare export function ... - this.match(tt._class) // declare export class ... - ) { - node.declaration = this.flowParseDeclare(this.startNode()); - node.default = false; + this.expect(tt.parenL); + const tmp = this.flowParseFunctionTypeParams(); + typeNode.params = tmp.params; + typeNode.rest = tmp.rest; + this.expect(tt.parenR); + + [ + // $FlowFixMe (destructuring not supported yet) + typeNode.returnType, + // $FlowFixMe (destructuring not supported yet) + node.predicate, + ] = this.flowParseTypeAndPredicateInitialiser(); + + typeContainer.typeAnnotation = this.finishNode( + typeNode, + "FunctionTypeAnnotation", + ); + + id.typeAnnotation = this.finishNode(typeContainer, "TypeAnnotation"); + + this.finishNode(id, id.type); + + this.semicolon(); + + return this.finishNode(node, "DeclareFunction"); + } + + flowParseDeclare( + node: N.FlowDeclare, + insideModule?: boolean, + ): N.FlowDeclare { + if (this.match(tt._class)) { + return this.flowParseDeclareClass(node); + } else if (this.match(tt._function)) { + return this.flowParseDeclareFunction(node); + } else if (this.match(tt._var)) { + return this.flowParseDeclareVariable(node); + } else if (this.isContextual("module")) { + if (this.lookahead().type === tt.dot) { + return this.flowParseDeclareModuleExports(node); + } else { + if (insideModule) + this.unexpected( + null, + "`declare module` cannot be used inside another `declare module`", + ); + return this.flowParseDeclareModule(node); + } + } else if (this.isContextual("type")) { + return this.flowParseDeclareTypeAlias(node); + } else if (this.isContextual("interface")) { + return this.flowParseDeclareInterface(node); + } else if (this.match(tt._export)) { + return this.flowParseDeclareExportDeclaration(node, insideModule); + } else { + throw this.unexpected(); + } + } + + flowParseDeclareVariable( + node: N.FlowDeclareVariable, + ): N.FlowDeclareVariable { + this.next(); + node.id = this.flowParseTypeAnnotatableIdentifier(); + this.semicolon(); + return this.finishNode(node, "DeclareVariable"); + } + + flowParseDeclareModule(node: N.FlowDeclareModule): N.FlowDeclareModule { + this.next(); + + if (this.match(tt.string)) { + node.id = this.parseExprAtom(); + } else { + node.id = this.parseIdentifier(); + } + + const bodyNode = (node.body = this.startNode()); + const body = (bodyNode.body = []); + this.expect(tt.braceL); + while (!this.match(tt.braceR)) { + let bodyNode = this.startNode(); + + if (this.match(tt._import)) { + const lookahead = this.lookahead(); + if (lookahead.value !== "type" && lookahead.value !== "typeof") { + this.unexpected( + null, + "Imports within a `declare module` body must always be `import type` or `import typeof`", + ); + } + this.next(); + this.parseImport(bodyNode); + } else { + this.expectContextual( + "declare", + "Only declares and type imports are allowed inside declare module", + ); + + bodyNode = this.flowParseDeclare(bodyNode, true); + } + + body.push(bodyNode); + } + this.expect(tt.braceR); + + this.finishNode(bodyNode, "BlockStatement"); + + let kind = null; + let hasModuleExport = false; + const errorMessage = + "Found both `declare module.exports` and `declare export` in the same module. Modules can only have 1 since they are either an ES module or they are a CommonJS module"; + body.forEach(bodyElement => { + if (isEsModuleType(bodyElement)) { + if (kind === "CommonJS") + this.unexpected(bodyElement.start, errorMessage); + kind = "ES"; + } else if (bodyElement.type === "DeclareModuleExports") { + if (hasModuleExport) + this.unexpected( + bodyElement.start, + "Duplicate `declare module.exports` statement", + ); + if (kind === "ES") this.unexpected(bodyElement.start, errorMessage); + kind = "CommonJS"; + hasModuleExport = true; + } + }); + + node.kind = kind || "CommonJS"; + return this.finishNode(node, "DeclareModule"); + } + + flowParseDeclareExportDeclaration( + node: N.FlowDeclareExportDeclaration, + insideModule: ?boolean, + ): N.FlowDeclareExportDeclaration { + this.expect(tt._export); + + if (this.eat(tt._default)) { + if (this.match(tt._function) || this.match(tt._class)) { + // declare export default class ... + // declare export default function ... + node.declaration = this.flowParseDeclare(this.startNode()); + } else { + // declare export default [type]; + node.declaration = this.flowParseType(); + this.semicolon(); + } + node.default = true; return this.finishNode(node, "DeclareExportDeclaration"); - } else if ( - this.match(tt.star) || // declare export * from '' - this.match(tt.braceL) || // declare export {} ... - this.isContextual("interface") || // declare export interface ... - this.isContextual("type") // declare export type ... - ) { - node = this.parseExport(node); - if (node.type === "ExportNamedDeclaration") { - // flow does not support the ExportNamedDeclaration - // $FlowIgnore - node.type = "ExportDeclaration"; - // $FlowFixMe - node.default = false; - delete node.exportKind; + } else { + if ( + this.match(tt._const) || + this.match(tt._let) || + ((this.isContextual("type") || this.isContextual("interface")) && + !insideModule) + ) { + const label = this.state.value; + const suggestion = exportSuggestions[label]; + this.unexpected( + this.state.start, + `\`declare export ${label}\` is not supported. Use \`${suggestion}\` instead`, + ); } - // $FlowIgnore - node.type = "Declare" + node.type; + if ( + this.match(tt._var) || // declare export var ... + this.match(tt._function) || // declare export function ... + this.match(tt._class) // declare export class ... + ) { + node.declaration = this.flowParseDeclare(this.startNode()); + node.default = false; - return node; + return this.finishNode(node, "DeclareExportDeclaration"); + } else if ( + this.match(tt.star) || // declare export * from '' + this.match(tt.braceL) || // declare export {} ... + this.isContextual("interface") || // declare export interface ... + this.isContextual("type") // declare export type ... + ) { + node = this.parseExport(node); + if (node.type === "ExportNamedDeclaration") { + // flow does not support the ExportNamedDeclaration + // $FlowIgnore + node.type = "ExportDeclaration"; + // $FlowFixMe + node.default = false; + delete node.exportKind; + } + + // $FlowIgnore + node.type = "Declare" + node.type; + + return node; + } } + + throw this.unexpected(); } - throw this.unexpected(); - } + flowParseDeclareModuleExports( + node: N.FlowDeclareModuleExports, + ): N.FlowDeclareModuleExports { + this.expectContextual("module"); + this.expect(tt.dot); + this.expectContextual("exports"); + node.typeAnnotation = this.flowParseTypeAnnotation(); + this.semicolon(); - flowParseDeclareModuleExports(node: N.FlowDeclareModuleExports): N.FlowDeclareModuleExports { - this.expectContextual("module"); - this.expect(tt.dot); - this.expectContextual("exports"); - node.typeAnnotation = this.flowParseTypeAnnotation(); - this.semicolon(); - - return this.finishNode(node, "DeclareModuleExports"); - } - - flowParseDeclareTypeAlias(node: N.FlowDeclareTypeAlias): N.FlowDeclareTypeAlias { - this.next(); - this.flowParseTypeAlias(node); - return this.finishNode(node, "DeclareTypeAlias"); - } - - flowParseDeclareInterface(node: N.FlowDeclareInterface): N.FlowDeclareInterface { - this.next(); - this.flowParseInterfaceish(node); - return this.finishNode(node, "DeclareInterface"); - } - - // Interfaces - - flowParseInterfaceish(node: N.FlowDeclare): void { - node.id = this.parseIdentifier(); - - if (this.isRelational("<")) { - node.typeParameters = this.flowParseTypeParameterDeclaration(); - } else { - node.typeParameters = null; + return this.finishNode(node, "DeclareModuleExports"); } - node.extends = []; - node.mixins = []; - - if (this.eat(tt._extends)) { - do { - node.extends.push(this.flowParseInterfaceExtends()); - } while (this.eat(tt.comma)); - } - - if (this.isContextual("mixins")) { + flowParseDeclareTypeAlias( + node: N.FlowDeclareTypeAlias, + ): N.FlowDeclareTypeAlias { this.next(); - do { - node.mixins.push(this.flowParseInterfaceExtends()); - } while (this.eat(tt.comma)); + this.flowParseTypeAlias(node); + return this.finishNode(node, "DeclareTypeAlias"); } - node.body = this.flowParseObjectType(true, false, false); - } - - flowParseInterfaceExtends(): N.FlowInterfaceExtends { - const node = this.startNode(); - - node.id = this.flowParseQualifiedTypeIdentifier(); - if (this.isRelational("<")) { - node.typeParameters = this.flowParseTypeParameterInstantiation(); - } else { - node.typeParameters = null; - } - - return this.finishNode(node, "InterfaceExtends"); - } - - flowParseInterface(node: N.FlowInterface): N.FlowInterface { - this.flowParseInterfaceish(node); - return this.finishNode(node, "InterfaceDeclaration"); - } - - flowParseRestrictedIdentifier(liberal?: boolean): N.Identifier { - if (primitiveTypes.indexOf(this.state.value) > -1) { - this.raise(this.state.start, `Cannot overwrite primitive type ${this.state.value}`); - } - - return this.parseIdentifier(liberal); - } - - // Type aliases - - flowParseTypeAlias(node: N.FlowTypeAlias): N.FlowTypeAlias { - node.id = this.flowParseRestrictedIdentifier(); - - if (this.isRelational("<")) { - node.typeParameters = this.flowParseTypeParameterDeclaration(); - } else { - node.typeParameters = null; - } - - node.right = this.flowParseTypeInitialiser(tt.eq); - this.semicolon(); - - return this.finishNode(node, "TypeAlias"); - } - - // Type annotations - - flowParseTypeParameter(): N.TypeParameter { - const node = this.startNode(); - - const variance = this.flowParseVariance(); - - const ident = this.flowParseTypeAnnotatableIdentifier(); - node.name = ident.name; - node.variance = variance; - node.bound = ident.typeAnnotation; - - if (this.match(tt.eq)) { - this.eat(tt.eq); - node.default = this.flowParseType(); - } - - return this.finishNode(node, "TypeParameter"); - } - - flowParseTypeParameterDeclaration(): N.TypeParameterDeclaration { - const oldInType = this.state.inType; - const node = this.startNode(); - node.params = []; - - this.state.inType = true; - - // istanbul ignore else: this condition is already checked at all call sites - if (this.isRelational("<") || this.match(tt.jsxTagStart)) { + flowParseDeclareInterface( + node: N.FlowDeclareInterface, + ): N.FlowDeclareInterface { this.next(); - } else { - this.unexpected(); + this.flowParseInterfaceish(node); + return this.finishNode(node, "DeclareInterface"); } - do { - node.params.push(this.flowParseTypeParameter()); - if (!this.isRelational(">")) { - this.expect(tt.comma); + // Interfaces + + flowParseInterfaceish(node: N.FlowDeclare): void { + node.id = this.parseIdentifier(); + + if (this.isRelational("<")) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } else { + node.typeParameters = null; } - } while (!this.isRelational(">")); - this.expectRelational(">"); - this.state.inType = oldInType; + node.extends = []; + node.mixins = []; - return this.finishNode(node, "TypeParameterDeclaration"); - } - - flowParseTypeParameterInstantiation(): N.TypeParameterInstantiation { - const node = this.startNode(); - const oldInType = this.state.inType; - node.params = []; - - this.state.inType = true; - - this.expectRelational("<"); - while (!this.isRelational(">")) { - node.params.push(this.flowParseType()); - if (!this.isRelational(">")) { - this.expect(tt.comma); + if (this.eat(tt._extends)) { + do { + node.extends.push(this.flowParseInterfaceExtends()); + } while (this.eat(tt.comma)); } - } - this.expectRelational(">"); - this.state.inType = oldInType; - - return this.finishNode(node, "TypeParameterInstantiation"); - } - - flowParseObjectPropertyKey(): N.Expression { - return (this.match(tt.num) || this.match(tt.string)) ? this.parseExprAtom() : this.parseIdentifier(true); - } - - flowParseObjectTypeIndexer(node: N.FlowObjectTypeIndexer, isStatic: boolean, variance: ?N.FlowVariance): N.FlowObjectTypeIndexer { - node.static = isStatic; - - this.expect(tt.bracketL); - if (this.lookahead().type === tt.colon) { - node.id = this.flowParseObjectPropertyKey(); - node.key = this.flowParseTypeInitialiser(); - } else { - node.id = null; - node.key = this.flowParseType(); - } - this.expect(tt.bracketR); - node.value = this.flowParseTypeInitialiser(); - node.variance = variance; - - return this.finishNode(node, "ObjectTypeIndexer"); - } - - flowParseObjectTypeMethodish(node: N.FlowFunctionTypeAnnotation): N.FlowFunctionTypeAnnotation { - node.params = []; - node.rest = null; - node.typeParameters = null; - - if (this.isRelational("<")) { - node.typeParameters = this.flowParseTypeParameterDeclaration(); - } - - this.expect(tt.parenL); - while (!this.match(tt.parenR) && !this.match(tt.ellipsis)) { - node.params.push(this.flowParseFunctionTypeParam()); - if (!this.match(tt.parenR)) { - this.expect(tt.comma); - } - } - - if (this.eat(tt.ellipsis)) { - node.rest = this.flowParseFunctionTypeParam(); - } - this.expect(tt.parenR); - node.returnType = this.flowParseTypeInitialiser(); - - return this.finishNode(node, "FunctionTypeAnnotation"); - } - - flowParseObjectTypeCallProperty(node: N.FlowObjectTypeCallProperty, isStatic: boolean): N.FlowObjectTypeCallProperty { - const valueNode = this.startNode(); - node.static = isStatic; - node.value = this.flowParseObjectTypeMethodish(valueNode); - return this.finishNode(node, "ObjectTypeCallProperty"); - } - - flowParseObjectType(allowStatic: boolean, allowExact: boolean, allowSpread: boolean): N.FlowObjectTypeAnnotation { - const oldInType = this.state.inType; - this.state.inType = true; - - const nodeStart = this.startNode(); - - nodeStart.callProperties = []; - nodeStart.properties = []; - nodeStart.indexers = []; - - let endDelim; - let exact; - if (allowExact && this.match(tt.braceBarL)) { - this.expect(tt.braceBarL); - endDelim = tt.braceBarR; - exact = true; - } else { - this.expect(tt.braceL); - endDelim = tt.braceR; - exact = false; - } - - nodeStart.exact = exact; - - while (!this.match(endDelim)) { - let isStatic = false; - const node = this.startNode(); - if (allowStatic && this.isContextual("static") && this.lookahead().type !== tt.colon) { + if (this.isContextual("mixins")) { this.next(); - isStatic = true; + do { + node.mixins.push(this.flowParseInterfaceExtends()); + } while (this.eat(tt.comma)); } + node.body = this.flowParseObjectType(true, false, false); + } + + flowParseInterfaceExtends(): N.FlowInterfaceExtends { + const node = this.startNode(); + + node.id = this.flowParseQualifiedTypeIdentifier(); + if (this.isRelational("<")) { + node.typeParameters = this.flowParseTypeParameterInstantiation(); + } else { + node.typeParameters = null; + } + + return this.finishNode(node, "InterfaceExtends"); + } + + flowParseInterface(node: N.FlowInterface): N.FlowInterface { + this.flowParseInterfaceish(node); + return this.finishNode(node, "InterfaceDeclaration"); + } + + flowParseRestrictedIdentifier(liberal?: boolean): N.Identifier { + if (primitiveTypes.indexOf(this.state.value) > -1) { + this.raise( + this.state.start, + `Cannot overwrite primitive type ${this.state.value}`, + ); + } + + return this.parseIdentifier(liberal); + } + + // Type aliases + + flowParseTypeAlias(node: N.FlowTypeAlias): N.FlowTypeAlias { + node.id = this.flowParseRestrictedIdentifier(); + + if (this.isRelational("<")) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } else { + node.typeParameters = null; + } + + node.right = this.flowParseTypeInitialiser(tt.eq); + this.semicolon(); + + return this.finishNode(node, "TypeAlias"); + } + + // Type annotations + + flowParseTypeParameter(): N.TypeParameter { + const node = this.startNode(); + const variance = this.flowParseVariance(); - if (this.match(tt.bracketL)) { - nodeStart.indexers.push(this.flowParseObjectTypeIndexer(node, isStatic, variance)); - } else if (this.match(tt.parenL) || this.isRelational("<")) { - if (variance) { - this.unexpected(variance.start); - } - nodeStart.callProperties.push(this.flowParseObjectTypeCallProperty(node, isStatic)); - } else { - let kind = "init"; + const ident = this.flowParseTypeAnnotatableIdentifier(); + node.name = ident.name; + node.variance = variance; + node.bound = ident.typeAnnotation; - if (this.isContextual("get") || this.isContextual("set")) { - const lookahead = this.lookahead(); - if ( - lookahead.type === tt.name || - lookahead.type === tt.string || - lookahead.type === tt.num - ) { - kind = this.state.value; - this.next(); - } - } - - nodeStart.properties.push(this.flowParseObjectTypeProperty(node, isStatic, variance, kind, allowSpread)); + if (this.match(tt.eq)) { + this.eat(tt.eq); + node.default = this.flowParseType(); } - this.flowObjectTypeSemicolon(); + return this.finishNode(node, "TypeParameter"); } - this.expect(endDelim); + flowParseTypeParameterDeclaration(): N.TypeParameterDeclaration { + const oldInType = this.state.inType; + const node = this.startNode(); + node.params = []; - const out = this.finishNode(nodeStart, "ObjectTypeAnnotation"); + this.state.inType = true; - this.state.inType = oldInType; - - return out; - } - - flowParseObjectTypeProperty( - node: N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty, - isStatic: boolean, - variance: ?N.FlowVariance, - kind: string, - allowSpread: boolean, - ): N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty { - if (this.match(tt.ellipsis)) { - if (!allowSpread) { - this.unexpected( - null, - "Spread operator cannot appear in class or interface definitions" - ); + // istanbul ignore else: this condition is already checked at all call sites + if (this.isRelational("<") || this.match(tt.jsxTagStart)) { + this.next(); + } else { + this.unexpected(); } - if (variance) { - this.unexpected(variance.start, "Spread properties cannot have variance"); - } - this.expect(tt.ellipsis); - node.argument = this.flowParseType(); - return this.finishNode(node, "ObjectTypeSpreadProperty"); - } else { - node.key = this.flowParseObjectPropertyKey(); + do { + node.params.push(this.flowParseTypeParameter()); + if (!this.isRelational(">")) { + this.expect(tt.comma); + } + } while (!this.isRelational(">")); + this.expectRelational(">"); + + this.state.inType = oldInType; + + return this.finishNode(node, "TypeParameterDeclaration"); + } + + flowParseTypeParameterInstantiation(): N.TypeParameterInstantiation { + const node = this.startNode(); + const oldInType = this.state.inType; + node.params = []; + + this.state.inType = true; + + this.expectRelational("<"); + while (!this.isRelational(">")) { + node.params.push(this.flowParseType()); + if (!this.isRelational(">")) { + this.expect(tt.comma); + } + } + this.expectRelational(">"); + + this.state.inType = oldInType; + + return this.finishNode(node, "TypeParameterInstantiation"); + } + + flowParseObjectPropertyKey(): N.Expression { + return this.match(tt.num) || this.match(tt.string) + ? this.parseExprAtom() + : this.parseIdentifier(true); + } + + flowParseObjectTypeIndexer( + node: N.FlowObjectTypeIndexer, + isStatic: boolean, + variance: ?N.FlowVariance, + ): N.FlowObjectTypeIndexer { node.static = isStatic; - node.kind = kind; - let optional = false; - if (this.isRelational("<") || this.match(tt.parenL)) { - // This is a method property - if (variance) { - this.unexpected(variance.start); + this.expect(tt.bracketL); + if (this.lookahead().type === tt.colon) { + node.id = this.flowParseObjectPropertyKey(); + node.key = this.flowParseTypeInitialiser(); + } else { + node.id = null; + node.key = this.flowParseType(); + } + this.expect(tt.bracketR); + node.value = this.flowParseTypeInitialiser(); + node.variance = variance; + + return this.finishNode(node, "ObjectTypeIndexer"); + } + + flowParseObjectTypeMethodish( + node: N.FlowFunctionTypeAnnotation, + ): N.FlowFunctionTypeAnnotation { + node.params = []; + node.rest = null; + node.typeParameters = null; + + if (this.isRelational("<")) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } + + this.expect(tt.parenL); + while (!this.match(tt.parenR) && !this.match(tt.ellipsis)) { + node.params.push(this.flowParseFunctionTypeParam()); + if (!this.match(tt.parenR)) { + this.expect(tt.comma); + } + } + + if (this.eat(tt.ellipsis)) { + node.rest = this.flowParseFunctionTypeParam(); + } + this.expect(tt.parenR); + node.returnType = this.flowParseTypeInitialiser(); + + return this.finishNode(node, "FunctionTypeAnnotation"); + } + + flowParseObjectTypeCallProperty( + node: N.FlowObjectTypeCallProperty, + isStatic: boolean, + ): N.FlowObjectTypeCallProperty { + const valueNode = this.startNode(); + node.static = isStatic; + node.value = this.flowParseObjectTypeMethodish(valueNode); + return this.finishNode(node, "ObjectTypeCallProperty"); + } + + flowParseObjectType( + allowStatic: boolean, + allowExact: boolean, + allowSpread: boolean, + ): N.FlowObjectTypeAnnotation { + const oldInType = this.state.inType; + this.state.inType = true; + + const nodeStart = this.startNode(); + + nodeStart.callProperties = []; + nodeStart.properties = []; + nodeStart.indexers = []; + + let endDelim; + let exact; + if (allowExact && this.match(tt.braceBarL)) { + this.expect(tt.braceBarL); + endDelim = tt.braceBarR; + exact = true; + } else { + this.expect(tt.braceL); + endDelim = tt.braceR; + exact = false; + } + + nodeStart.exact = exact; + + while (!this.match(endDelim)) { + let isStatic = false; + const node = this.startNode(); + if ( + allowStatic && + this.isContextual("static") && + this.lookahead().type !== tt.colon + ) { + this.next(); + isStatic = true; } - node.value = this.flowParseObjectTypeMethodish(this.startNodeAt(node.start, node.loc.start)); - if (kind === "get" || kind === "set") this.flowCheckGetterSetterParamCount(node); + const variance = this.flowParseVariance(); + + if (this.match(tt.bracketL)) { + nodeStart.indexers.push( + this.flowParseObjectTypeIndexer(node, isStatic, variance), + ); + } else if (this.match(tt.parenL) || this.isRelational("<")) { + if (variance) { + this.unexpected(variance.start); + } + nodeStart.callProperties.push( + this.flowParseObjectTypeCallProperty(node, isStatic), + ); + } else { + let kind = "init"; + + if (this.isContextual("get") || this.isContextual("set")) { + const lookahead = this.lookahead(); + if ( + lookahead.type === tt.name || + lookahead.type === tt.string || + lookahead.type === tt.num + ) { + kind = this.state.value; + this.next(); + } + } + + nodeStart.properties.push( + this.flowParseObjectTypeProperty( + node, + isStatic, + variance, + kind, + allowSpread, + ), + ); + } + + this.flowObjectTypeSemicolon(); + } + + this.expect(endDelim); + + const out = this.finishNode(nodeStart, "ObjectTypeAnnotation"); + + this.state.inType = oldInType; + + return out; + } + + flowParseObjectTypeProperty( + node: N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty, + isStatic: boolean, + variance: ?N.FlowVariance, + kind: string, + allowSpread: boolean, + ): N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty { + if (this.match(tt.ellipsis)) { + if (!allowSpread) { + this.unexpected( + null, + "Spread operator cannot appear in class or interface definitions", + ); + } + if (variance) { + this.unexpected( + variance.start, + "Spread properties cannot have variance", + ); + } + this.expect(tt.ellipsis); + node.argument = this.flowParseType(); + + return this.finishNode(node, "ObjectTypeSpreadProperty"); } else { - if (kind !== "init") this.unexpected(); + node.key = this.flowParseObjectPropertyKey(); + node.static = isStatic; + node.kind = kind; + + let optional = false; + if (this.isRelational("<") || this.match(tt.parenL)) { + // This is a method property + if (variance) { + this.unexpected(variance.start); + } + + node.value = this.flowParseObjectTypeMethodish( + this.startNodeAt(node.start, node.loc.start), + ); + if (kind === "get" || kind === "set") + this.flowCheckGetterSetterParamCount(node); + } else { + if (kind !== "init") this.unexpected(); + if (this.eat(tt.question)) { + optional = true; + } + node.value = this.flowParseTypeInitialiser(); + node.variance = variance; + } + + node.optional = optional; + + return this.finishNode(node, "ObjectTypeProperty"); + } + } + + // This is similar to checkGetterSetterParamCount, but as + // babylon uses non estree properties we cannot reuse it here + flowCheckGetterSetterParamCount( + property: N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty, + ): void { + const paramCount = property.kind === "get" ? 0 : 1; + if (property.value.params.length !== paramCount) { + const start = property.start; + if (property.kind === "get") { + this.raise(start, "getter should have no params"); + } else { + this.raise(start, "setter should have exactly one param"); + } + } + } + + flowObjectTypeSemicolon(): void { + if ( + !this.eat(tt.semi) && + !this.eat(tt.comma) && + !this.match(tt.braceR) && + !this.match(tt.braceBarR) + ) { + this.unexpected(); + } + } + + flowParseQualifiedTypeIdentifier( + startPos?: number, + startLoc?: Position, + id?: N.Identifier, + ): N.FlowQualifiedTypeIdentifier { + startPos = startPos || this.state.start; + startLoc = startLoc || this.state.startLoc; + let node = id || this.parseIdentifier(); + + while (this.eat(tt.dot)) { + const node2 = this.startNodeAt(startPos, startLoc); + node2.qualification = node; + node2.id = this.parseIdentifier(); + node = this.finishNode(node2, "QualifiedTypeIdentifier"); + } + + return node; + } + + flowParseGenericType( + startPos: number, + startLoc: Position, + id: N.Identifier, + ): N.FlowGenericTypeAnnotation { + const node = this.startNodeAt(startPos, startLoc); + + node.typeParameters = null; + node.id = this.flowParseQualifiedTypeIdentifier(startPos, startLoc, id); + + if (this.isRelational("<")) { + node.typeParameters = this.flowParseTypeParameterInstantiation(); + } + + return this.finishNode(node, "GenericTypeAnnotation"); + } + + flowParseTypeofType(): N.FlowTypeofTypeAnnotation { + const node = this.startNode(); + this.expect(tt._typeof); + node.argument = this.flowParsePrimaryType(); + return this.finishNode(node, "TypeofTypeAnnotation"); + } + + flowParseTupleType(): N.FlowTupleTypeAnnotation { + const node = this.startNode(); + node.types = []; + this.expect(tt.bracketL); + // We allow trailing commas + while (this.state.pos < this.input.length && !this.match(tt.bracketR)) { + node.types.push(this.flowParseType()); + if (this.match(tt.bracketR)) break; + this.expect(tt.comma); + } + this.expect(tt.bracketR); + return this.finishNode(node, "TupleTypeAnnotation"); + } + + flowParseFunctionTypeParam(): N.FlowFunctionTypeParam { + let name = null; + let optional = false; + let typeAnnotation = null; + const node = this.startNode(); + const lh = this.lookahead(); + if (lh.type === tt.colon || lh.type === tt.question) { + name = this.parseIdentifier(); if (this.eat(tt.question)) { optional = true; } - node.value = this.flowParseTypeInitialiser(); - node.variance = variance; - - } - - node.optional = optional; - - return this.finishNode(node, "ObjectTypeProperty"); - } - } - - // This is similar to checkGetterSetterParamCount, but as - // babylon uses non estree properties we cannot reuse it here - flowCheckGetterSetterParamCount(property: N.FlowObjectTypeProperty | N.FlowObjectTypeSpreadProperty): void { - const paramCount = property.kind === "get" ? 0 : 1; - if (property.value.params.length !== paramCount) { - const start = property.start; - if (property.kind === "get") { - this.raise(start, "getter should have no params"); + typeAnnotation = this.flowParseTypeInitialiser(); } else { - this.raise(start, "setter should have exactly one param"); + typeAnnotation = this.flowParseType(); + } + node.name = name; + node.optional = optional; + node.typeAnnotation = typeAnnotation; + return this.finishNode(node, "FunctionTypeParam"); + } + + reinterpretTypeAsFunctionTypeParam( + type: N.FlowType, + ): N.FlowFunctionTypeParam { + const node = this.startNodeAt(type.start, type.loc.start); + node.name = null; + node.optional = false; + node.typeAnnotation = type; + return this.finishNode(node, "FunctionTypeParam"); + } + + flowParseFunctionTypeParams( + params: N.FlowFunctionTypeParam[] = [], + ): { params: N.FlowFunctionTypeParam[], rest: ?N.FlowFunctionTypeParam } { + let rest: ?N.FlowFunctionTypeParam = null; + while (!this.match(tt.parenR) && !this.match(tt.ellipsis)) { + params.push(this.flowParseFunctionTypeParam()); + if (!this.match(tt.parenR)) { + this.expect(tt.comma); + } + } + if (this.eat(tt.ellipsis)) { + rest = this.flowParseFunctionTypeParam(); + } + return { params, rest }; + } + + flowIdentToTypeAnnotation( + startPos: number, + startLoc: Position, + node: N.FlowTypeAnnotation, + id: N.Identifier, + ): N.FlowTypeAnnotation { + switch (id.name) { + case "any": + return this.finishNode(node, "AnyTypeAnnotation"); + + case "void": + return this.finishNode(node, "VoidTypeAnnotation"); + + case "bool": + case "boolean": + return this.finishNode(node, "BooleanTypeAnnotation"); + + case "mixed": + return this.finishNode(node, "MixedTypeAnnotation"); + + case "empty": + return this.finishNode(node, "EmptyTypeAnnotation"); + + case "number": + return this.finishNode(node, "NumberTypeAnnotation"); + + case "string": + return this.finishNode(node, "StringTypeAnnotation"); + + default: + return this.flowParseGenericType(startPos, startLoc, id); } } - } - flowObjectTypeSemicolon(): void { - if (!this.eat(tt.semi) && !this.eat(tt.comma) && - !this.match(tt.braceR) && !this.match(tt.braceBarR)) { - this.unexpected(); - } - } + // The parsing of types roughly parallels the parsing of expressions, and + // primary types are kind of like primary expressions...they're the + // primitives with which other types are constructed. + flowParsePrimaryType(): N.FlowTypeAnnotation { + const startPos = this.state.start; + const startLoc = this.state.startLoc; + const node = this.startNode(); + let tmp; + let type; + let isGroupedType = false; + const oldNoAnonFunctionType = this.state.noAnonFunctionType; - flowParseQualifiedTypeIdentifier(startPos?: number, startLoc?: Position, id?: N.Identifier): N.FlowQualifiedTypeIdentifier { - startPos = startPos || this.state.start; - startLoc = startLoc || this.state.startLoc; - let node = id || this.parseIdentifier(); + switch (this.state.type) { + case tt.name: + return this.flowIdentToTypeAnnotation( + startPos, + startLoc, + node, + this.parseIdentifier(), + ); - while (this.eat(tt.dot)) { - const node2 = this.startNodeAt(startPos, startLoc); - node2.qualification = node; - node2.id = this.parseIdentifier(); - node = this.finishNode(node2, "QualifiedTypeIdentifier"); - } + case tt.braceL: + return this.flowParseObjectType(false, false, true); - return node; - } + case tt.braceBarL: + return this.flowParseObjectType(false, true, true); - flowParseGenericType(startPos: number, startLoc: Position, id: N.Identifier): N.FlowGenericTypeAnnotation { - const node = this.startNodeAt(startPos, startLoc); + case tt.bracketL: + return this.flowParseTupleType(); - node.typeParameters = null; - node.id = this.flowParseQualifiedTypeIdentifier(startPos, startLoc, id); + case tt.relational: + if (this.state.value === "<") { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + this.expect(tt.parenL); + tmp = this.flowParseFunctionTypeParams(); + node.params = tmp.params; + node.rest = tmp.rest; + this.expect(tt.parenR); - if (this.isRelational("<")) { - node.typeParameters = this.flowParseTypeParameterInstantiation(); - } + this.expect(tt.arrow); - return this.finishNode(node, "GenericTypeAnnotation"); - } + node.returnType = this.flowParseType(); - flowParseTypeofType(): N.FlowTypeofTypeAnnotation { - const node = this.startNode(); - this.expect(tt._typeof); - node.argument = this.flowParsePrimaryType(); - return this.finishNode(node, "TypeofTypeAnnotation"); - } + return this.finishNode(node, "FunctionTypeAnnotation"); + } + break; - flowParseTupleType(): N.FlowTupleTypeAnnotation { - const node = this.startNode(); - node.types = []; - this.expect(tt.bracketL); - // We allow trailing commas - while (this.state.pos < this.input.length && !this.match(tt.bracketR)) { - node.types.push(this.flowParseType()); - if (this.match(tt.bracketR)) break; - this.expect(tt.comma); - } - this.expect(tt.bracketR); - return this.finishNode(node, "TupleTypeAnnotation"); - } + case tt.parenL: + this.next(); - flowParseFunctionTypeParam(): N.FlowFunctionTypeParam { - let name = null; - let optional = false; - let typeAnnotation = null; - const node = this.startNode(); - const lh = this.lookahead(); - if (lh.type === tt.colon || - lh.type === tt.question) { - name = this.parseIdentifier(); - if (this.eat(tt.question)) { - optional = true; - } - typeAnnotation = this.flowParseTypeInitialiser(); - } else { - typeAnnotation = this.flowParseType(); - } - node.name = name; - node.optional = optional; - node.typeAnnotation = typeAnnotation; - return this.finishNode(node, "FunctionTypeParam"); - } + // Check to see if this is actually a grouped type + if (!this.match(tt.parenR) && !this.match(tt.ellipsis)) { + if (this.match(tt.name)) { + const token = this.lookahead().type; + isGroupedType = token !== tt.question && token !== tt.colon; + } else { + isGroupedType = true; + } + } - reinterpretTypeAsFunctionTypeParam(type: N.FlowType): N.FlowFunctionTypeParam { - const node = this.startNodeAt(type.start, type.loc.start); - node.name = null; - node.optional = false; - node.typeAnnotation = type; - return this.finishNode(node, "FunctionTypeParam"); - } + if (isGroupedType) { + this.state.noAnonFunctionType = false; + type = this.flowParseType(); + this.state.noAnonFunctionType = oldNoAnonFunctionType; - flowParseFunctionTypeParams(params: N.FlowFunctionTypeParam[] = []): { params: N.FlowFunctionTypeParam[], rest: ?N.FlowFunctionTypeParam } { - let rest: ?N.FlowFunctionTypeParam = null; - while (!this.match(tt.parenR) && !this.match(tt.ellipsis)) { - params.push(this.flowParseFunctionTypeParam()); - if (!this.match(tt.parenR)) { - this.expect(tt.comma); - } - } - if (this.eat(tt.ellipsis)) { - rest = this.flowParseFunctionTypeParam(); - } - return { params, rest }; - } + // A `,` or a `) =>` means this is an anonymous function type + if ( + this.state.noAnonFunctionType || + !( + this.match(tt.comma) || + (this.match(tt.parenR) && this.lookahead().type === tt.arrow) + ) + ) { + this.expect(tt.parenR); + return type; + } else { + // Eat a comma if there is one + this.eat(tt.comma); + } + } - flowIdentToTypeAnnotation(startPos: number, startLoc: Position, node: N.FlowTypeAnnotation, id: N.Identifier): N.FlowTypeAnnotation { - switch (id.name) { - case "any": - return this.finishNode(node, "AnyTypeAnnotation"); + if (type) { + tmp = this.flowParseFunctionTypeParams([ + this.reinterpretTypeAsFunctionTypeParam(type), + ]); + } else { + tmp = this.flowParseFunctionTypeParams(); + } - case "void": - return this.finishNode(node, "VoidTypeAnnotation"); - - case "bool": - case "boolean": - return this.finishNode(node, "BooleanTypeAnnotation"); - - case "mixed": - return this.finishNode(node, "MixedTypeAnnotation"); - - case "empty": - return this.finishNode(node, "EmptyTypeAnnotation"); - - case "number": - return this.finishNode(node, "NumberTypeAnnotation"); - - case "string": - return this.finishNode(node, "StringTypeAnnotation"); - - default: - return this.flowParseGenericType(startPos, startLoc, id); - } - } - - // The parsing of types roughly parallels the parsing of expressions, and - // primary types are kind of like primary expressions...they're the - // primitives with which other types are constructed. - flowParsePrimaryType(): N.FlowTypeAnnotation { - const startPos = this.state.start; - const startLoc = this.state.startLoc; - const node = this.startNode(); - let tmp; - let type; - let isGroupedType = false; - const oldNoAnonFunctionType = this.state.noAnonFunctionType; - - switch (this.state.type) { - case tt.name: - return this.flowIdentToTypeAnnotation(startPos, startLoc, node, this.parseIdentifier()); - - case tt.braceL: - return this.flowParseObjectType(false, false, true); - - case tt.braceBarL: - return this.flowParseObjectType(false, true, true); - - case tt.bracketL: - return this.flowParseTupleType(); - - case tt.relational: - if (this.state.value === "<") { - node.typeParameters = this.flowParseTypeParameterDeclaration(); - this.expect(tt.parenL); - tmp = this.flowParseFunctionTypeParams(); node.params = tmp.params; node.rest = tmp.rest; + this.expect(tt.parenR); this.expect(tt.arrow); node.returnType = this.flowParseType(); + node.typeParameters = null; + return this.finishNode(node, "FunctionTypeAnnotation"); - } - break; - case tt.parenL: - this.next(); - - // Check to see if this is actually a grouped type - if (!this.match(tt.parenR) && !this.match(tt.ellipsis)) { - if (this.match(tt.name)) { - const token = this.lookahead().type; - isGroupedType = token !== tt.question && token !== tt.colon; - } else { - isGroupedType = true; - } - } - - if (isGroupedType) { - this.state.noAnonFunctionType = false; - type = this.flowParseType(); - this.state.noAnonFunctionType = oldNoAnonFunctionType; - - // A `,` or a `) =>` means this is an anonymous function type - if (this.state.noAnonFunctionType || - !(this.match(tt.comma) || - (this.match(tt.parenR) && this.lookahead().type === tt.arrow))) { - this.expect(tt.parenR); - return type; - } else { - // Eat a comma if there is one - this.eat(tt.comma); - } - } - - if (type) { - tmp = this.flowParseFunctionTypeParams( - [this.reinterpretTypeAsFunctionTypeParam(type)], + case tt.string: + return this.parseLiteral( + this.state.value, + "StringLiteralTypeAnnotation", ); - } else { - tmp = this.flowParseFunctionTypeParams(); - } - node.params = tmp.params; - node.rest = tmp.rest; - - this.expect(tt.parenR); - - this.expect(tt.arrow); - - node.returnType = this.flowParseType(); - - node.typeParameters = null; - - return this.finishNode(node, "FunctionTypeAnnotation"); - - case tt.string: - return this.parseLiteral(this.state.value, "StringLiteralTypeAnnotation"); - - case tt._true: case tt._false: - node.value = this.match(tt._true); - this.next(); - return this.finishNode(node, "BooleanLiteralTypeAnnotation"); - - case tt.plusMin: - if (this.state.value === "-") { + case tt._true: + case tt._false: + node.value = this.match(tt._true); this.next(); - if (!this.match(tt.num)) this.unexpected(null, "Unexpected token, expected number"); + return this.finishNode(node, "BooleanLiteralTypeAnnotation"); - return this.parseLiteral(-this.state.value, "NumberLiteralTypeAnnotation", node.start, node.loc.start); - } + case tt.plusMin: + if (this.state.value === "-") { + this.next(); + if (!this.match(tt.num)) + this.unexpected(null, "Unexpected token, expected number"); - this.unexpected(); - case tt.num: - return this.parseLiteral(this.state.value, "NumberLiteralTypeAnnotation"); + return this.parseLiteral( + -this.state.value, + "NumberLiteralTypeAnnotation", + node.start, + node.loc.start, + ); + } - case tt._null: - node.value = this.match(tt._null); - this.next(); - return this.finishNode(node, "NullLiteralTypeAnnotation"); + this.unexpected(); + case tt.num: + return this.parseLiteral( + this.state.value, + "NumberLiteralTypeAnnotation", + ); - case tt._this: - node.value = this.match(tt._this); - this.next(); - return this.finishNode(node, "ThisTypeAnnotation"); + case tt._null: + node.value = this.match(tt._null); + this.next(); + return this.finishNode(node, "NullLiteralTypeAnnotation"); - case tt.star: - this.next(); - return this.finishNode(node, "ExistsTypeAnnotation"); + case tt._this: + node.value = this.match(tt._this); + this.next(); + return this.finishNode(node, "ThisTypeAnnotation"); - default: - if (this.state.type.keyword === "typeof") { - return this.flowParseTypeofType(); - } - } + case tt.star: + this.next(); + return this.finishNode(node, "ExistsTypeAnnotation"); - throw this.unexpected(); - } - - flowParsePostfixType(): N.FlowTypeAnnotation { - const startPos = this.state.start, startLoc = this.state.startLoc; - let type = this.flowParsePrimaryType(); - while (!this.canInsertSemicolon() && this.match(tt.bracketL)) { - const node = this.startNodeAt(startPos, startLoc); - node.elementType = type; - this.expect(tt.bracketL); - this.expect(tt.bracketR); - type = this.finishNode(node, "ArrayTypeAnnotation"); - } - return type; - } - - flowParsePrefixType(): N.FlowTypeAnnotation { - const node = this.startNode(); - if (this.eat(tt.question)) { - node.typeAnnotation = this.flowParsePrefixType(); - return this.finishNode(node, "NullableTypeAnnotation"); - } else { - return this.flowParsePostfixType(); - } - } - - flowParseAnonFunctionWithoutParens(): N.FlowTypeAnnotation { - const param = this.flowParsePrefixType(); - if (!this.state.noAnonFunctionType && this.eat(tt.arrow)) { - // TODO: This should be a type error. Passing in a SourceLocation, and it expects a Position. - const node = this.startNodeAt(param.start, param.loc.start); - node.params = [this.reinterpretTypeAsFunctionTypeParam(param)]; - node.rest = null; - node.returnType = this.flowParseType(); - node.typeParameters = null; - return this.finishNode(node, "FunctionTypeAnnotation"); - } - return param; - } - - flowParseIntersectionType(): N.FlowTypeAnnotation { - const node = this.startNode(); - this.eat(tt.bitwiseAND); - const type = this.flowParseAnonFunctionWithoutParens(); - node.types = [type]; - while (this.eat(tt.bitwiseAND)) { - node.types.push(this.flowParseAnonFunctionWithoutParens()); - } - return node.types.length === 1 ? type : this.finishNode(node, "IntersectionTypeAnnotation"); - } - - flowParseUnionType(): N.FlowTypeAnnotation { - const node = this.startNode(); - this.eat(tt.bitwiseOR); - const type = this.flowParseIntersectionType(); - node.types = [type]; - while (this.eat(tt.bitwiseOR)) { - node.types.push(this.flowParseIntersectionType()); - } - return node.types.length === 1 ? type : this.finishNode(node, "UnionTypeAnnotation"); - } - - flowParseType(): N.FlowTypeAnnotation { - const oldInType = this.state.inType; - this.state.inType = true; - const type = this.flowParseUnionType(); - this.state.inType = oldInType; - // noAnonFunctionType is true when parsing an arrow function - this.state.exprAllowed = this.state.noAnonFunctionType; - return type; - } - - flowParseTypeAnnotation(): N.FlowTypeAnnotation { - const node = this.startNode(); - node.typeAnnotation = this.flowParseTypeInitialiser(); - return this.finishNode(node, "TypeAnnotation"); - } - - flowParseTypeAnnotatableIdentifier(): N.Identifier { - const ident = this.flowParseRestrictedIdentifier(); - if (this.match(tt.colon)) { - ident.typeAnnotation = this.flowParseTypeAnnotation(); - this.finishNode(ident, ident.type); - } - return ident; - } - - typeCastToParameter(node: N.Node): N.Node { - node.expression.typeAnnotation = node.typeAnnotation; - - return this.finishNodeAt( - node.expression, - node.expression.type, - node.typeAnnotation.end, - node.typeAnnotation.loc.end - ); - } - - flowParseVariance(): ?N.FlowVariance { - let variance = null; - if (this.match(tt.plusMin)) { - variance = this.startNode(); - if (this.state.value === "+") { - variance.kind = "plus"; - } else { - variance.kind = "minus"; + default: + if (this.state.type.keyword === "typeof") { + return this.flowParseTypeofType(); + } } - this.next(); - this.finishNode(variance, "Variance"); - } - return variance; - } - // ================================== - // Overrides - // ================================== - - parseFunctionBodyAndFinish(node: N.BodilessFunctionOrMethodBase, type: string, allowExpressionBody?: boolean): void { - // For arrow functions, `parseArrow` handles the return type itself. - if (!allowExpressionBody && this.match(tt.colon)) { - const typeNode = this.startNode(); - // $FlowFixMe - [typeNode.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser(); - node.returnType = typeNode.typeAnnotation - ? this.finishNode(typeNode, "TypeAnnotation") - : null; + throw this.unexpected(); } - super.parseFunctionBodyAndFinish(node, type, allowExpressionBody); - } + flowParsePostfixType(): N.FlowTypeAnnotation { + const startPos = this.state.start, + startLoc = this.state.startLoc; + let type = this.flowParsePrimaryType(); + while (!this.canInsertSemicolon() && this.match(tt.bracketL)) { + const node = this.startNodeAt(startPos, startLoc); + node.elementType = type; + this.expect(tt.bracketL); + this.expect(tt.bracketR); + type = this.finishNode(node, "ArrayTypeAnnotation"); + } + return type; + } - // interfaces - parseStatement(declaration: boolean, topLevel?: boolean): N.Statement { - // strict mode handling of `interface` since it's a reserved word - if (this.state.strict && this.match(tt.name) && this.state.value === "interface") { + flowParsePrefixType(): N.FlowTypeAnnotation { const node = this.startNode(); - this.next(); - return this.flowParseInterface(node); - } else { - return super.parseStatement(declaration, topLevel); - } - } - - // declares, interfaces and type aliases - parseExpressionStatement(node: N.ExpressionStatement, expr: N.Expression): N.ExpressionStatement { - if (expr.type === "Identifier") { - if (expr.name === "declare") { - if (this.match(tt._class) || this.match(tt.name) || this.match(tt._function) || this.match(tt._var) || this.match(tt._export)) { - return this.flowParseDeclare(node); - } - } else if (this.match(tt.name)) { - if (expr.name === "interface") { - return this.flowParseInterface(node); - } else if (expr.name === "type") { - return this.flowParseTypeAlias(node); - } - } - } - - return super.parseExpressionStatement(node, expr); - } - - // export type - shouldParseExportDeclaration(): boolean { - return this.isContextual("type") - || this.isContextual("interface") - || super.shouldParseExportDeclaration(); - } - - isExportDefaultSpecifier(): boolean { - if ( - this.match(tt.name) && - (this.state.value === "type" || this.state.value === "interface") - ) { - return false; - } - - return super.isExportDefaultSpecifier(); - } - - parseConditional(expr: N.Expression, noIn: ?boolean, startPos: number, startLoc: Position, refNeedsArrowPos?: ?Pos): N.Expression { - // only do the expensive clone if there is a question mark - // and if we come from inside parens - if (refNeedsArrowPos && this.match(tt.question)) { - const state = this.state.clone(); - try { - return super.parseConditional(expr, noIn, startPos, startLoc); - } catch (err) { - if (err instanceof SyntaxError) { - this.state = state; - refNeedsArrowPos.start = err.pos || this.state.start; - return expr; - } else { - // istanbul ignore next: no such error is expected - throw err; - } - } - } - - return super.parseConditional(expr, noIn, startPos, startLoc); - } - - parseParenItem(node: N.Expression, startPos: number, startLoc: Position): N.Expression { - node = super.parseParenItem(node, startPos, startLoc); - if (this.eat(tt.question)) { - node.optional = true; - } - - if (this.match(tt.colon)) { - const typeCastNode = this.startNodeAt(startPos, startLoc); - typeCastNode.expression = node; - typeCastNode.typeAnnotation = this.flowParseTypeAnnotation(); - - return this.finishNode(typeCastNode, "TypeCastExpression"); - } - - return node; - } - - parseExport(node: N.ExportNamedDeclaration): N.ExportNamedDeclaration { - node = super.parseExport(node); - if (node.type === "ExportNamedDeclaration") { - node.exportKind = node.exportKind || "value"; - } - return node; - } - - parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration { - if (this.isContextual("type")) { - node.exportKind = "type"; - - const declarationNode = this.startNode(); - this.next(); - - if (this.match(tt.braceL)) { - // export type { foo, bar }; - node.specifiers = this.parseExportSpecifiers(); - this.parseExportFrom(node); - return null; + if (this.eat(tt.question)) { + node.typeAnnotation = this.flowParsePrefixType(); + return this.finishNode(node, "NullableTypeAnnotation"); } else { - // export type Foo = Bar; - return this.flowParseTypeAlias(declarationNode); - } - } else if (this.isContextual("interface")) { - node.exportKind = "type"; - const declarationNode = this.startNode(); - this.next(); - return this.flowParseInterface(declarationNode); - } else { - return super.parseExportDeclaration(node); - } - } - - parseClassId(node: N.Class, isStatement: boolean, optionalId: ?boolean) { - super.parseClassId(node, isStatement, optionalId); - if (this.isRelational("<")) { - node.typeParameters = this.flowParseTypeParameterDeclaration(); - } - } - - // don't consider `void` to be a keyword as then it'll use the void token type - // and set startExpr - isKeyword(name: string): boolean { - if (this.state.inType && name === "void") { - return false; - } else { - return super.isKeyword(name); - } - } - - // ensure that inside flow types, we bypass the jsx parser plugin - readToken(code: number): void { - if (this.state.inType && (code === 62 || code === 60)) { - return this.finishOp(tt.relational, 1); - } else { - return super.readToken(code); - } - } - - toAssignable(node: N.Node, isBinding: ?boolean, contextDescription: string): N.Node { - if (node.type === "TypeCastExpression") { - return super.toAssignable(this.typeCastToParameter(node), isBinding, contextDescription); - } else { - return super.toAssignable(node, isBinding, contextDescription); - } - } - - // turn type casts that we found in function parameter head into type annotated params - toAssignableList(exprList: N.Expression[], isBinding: ?boolean, contextDescription: string): $ReadOnlyArray { - for (let i = 0; i < exprList.length; i++) { - const expr = exprList[i]; - if (expr && expr.type === "TypeCastExpression") { - exprList[i] = this.typeCastToParameter(expr); - } - } - return super.toAssignableList(exprList, isBinding, contextDescription); - } - - // this is a list of nodes, from something like a call expression, we need to filter the - // type casts that we've found that are illegal in this context - toReferencedList(exprList: $ReadOnlyArray): $ReadOnlyArray { - for (let i = 0; i < exprList.length; i++) { - const expr = exprList[i]; - if (expr && expr._exprListItem && expr.type === "TypeCastExpression") { - this.raise(expr.start, "Unexpected type cast"); + return this.flowParsePostfixType(); } } - return exprList; - } + flowParseAnonFunctionWithoutParens(): N.FlowTypeAnnotation { + const param = this.flowParsePrefixType(); + if (!this.state.noAnonFunctionType && this.eat(tt.arrow)) { + // TODO: This should be a type error. Passing in a SourceLocation, and it expects a Position. + const node = this.startNodeAt(param.start, param.loc.start); + node.params = [this.reinterpretTypeAsFunctionTypeParam(param)]; + node.rest = null; + node.returnType = this.flowParseType(); + node.typeParameters = null; + return this.finishNode(node, "FunctionTypeAnnotation"); + } + return param; + } + + flowParseIntersectionType(): N.FlowTypeAnnotation { + const node = this.startNode(); + this.eat(tt.bitwiseAND); + const type = this.flowParseAnonFunctionWithoutParens(); + node.types = [type]; + while (this.eat(tt.bitwiseAND)) { + node.types.push(this.flowParseAnonFunctionWithoutParens()); + } + return node.types.length === 1 + ? type + : this.finishNode(node, "IntersectionTypeAnnotation"); + } + + flowParseUnionType(): N.FlowTypeAnnotation { + const node = this.startNode(); + this.eat(tt.bitwiseOR); + const type = this.flowParseIntersectionType(); + node.types = [type]; + while (this.eat(tt.bitwiseOR)) { + node.types.push(this.flowParseIntersectionType()); + } + return node.types.length === 1 + ? type + : this.finishNode(node, "UnionTypeAnnotation"); + } + + flowParseType(): N.FlowTypeAnnotation { + const oldInType = this.state.inType; + this.state.inType = true; + const type = this.flowParseUnionType(); + this.state.inType = oldInType; + // noAnonFunctionType is true when parsing an arrow function + this.state.exprAllowed = this.state.noAnonFunctionType; + return type; + } + + flowParseTypeAnnotation(): N.FlowTypeAnnotation { + const node = this.startNode(); + node.typeAnnotation = this.flowParseTypeInitialiser(); + return this.finishNode(node, "TypeAnnotation"); + } + + flowParseTypeAnnotatableIdentifier(): N.Identifier { + const ident = this.flowParseRestrictedIdentifier(); + if (this.match(tt.colon)) { + ident.typeAnnotation = this.flowParseTypeAnnotation(); + this.finishNode(ident, ident.type); + } + return ident; + } + + typeCastToParameter(node: N.Node): N.Node { + node.expression.typeAnnotation = node.typeAnnotation; + + return this.finishNodeAt( + node.expression, + node.expression.type, + node.typeAnnotation.end, + node.typeAnnotation.loc.end, + ); + } + + flowParseVariance(): ?N.FlowVariance { + let variance = null; + if (this.match(tt.plusMin)) { + variance = this.startNode(); + if (this.state.value === "+") { + variance.kind = "plus"; + } else { + variance.kind = "minus"; + } + this.next(); + this.finishNode(variance, "Variance"); + } + return variance; + } + + // ================================== + // Overrides + // ================================== + + parseFunctionBodyAndFinish( + node: N.BodilessFunctionOrMethodBase, + type: string, + allowExpressionBody?: boolean, + ): void { + // For arrow functions, `parseArrow` handles the return type itself. + if (!allowExpressionBody && this.match(tt.colon)) { + const typeNode = this.startNode(); + + [ + // $FlowFixMe (destructuring not supported yet) + typeNode.typeAnnotation, + // $FlowFixMe (destructuring not supported yet) + node.predicate, + ] = this.flowParseTypeAndPredicateInitialiser(); + + node.returnType = typeNode.typeAnnotation + ? this.finishNode(typeNode, "TypeAnnotation") + : null; + } + + super.parseFunctionBodyAndFinish(node, type, allowExpressionBody); + } + + // interfaces + parseStatement(declaration: boolean, topLevel?: boolean): N.Statement { + // strict mode handling of `interface` since it's a reserved word + if ( + this.state.strict && + this.match(tt.name) && + this.state.value === "interface" + ) { + const node = this.startNode(); + this.next(); + return this.flowParseInterface(node); + } else { + return super.parseStatement(declaration, topLevel); + } + } + + // declares, interfaces and type aliases + parseExpressionStatement( + node: N.ExpressionStatement, + expr: N.Expression, + ): N.ExpressionStatement { + if (expr.type === "Identifier") { + if (expr.name === "declare") { + if ( + this.match(tt._class) || + this.match(tt.name) || + this.match(tt._function) || + this.match(tt._var) || + this.match(tt._export) + ) { + return this.flowParseDeclare(node); + } + } else if (this.match(tt.name)) { + if (expr.name === "interface") { + return this.flowParseInterface(node); + } else if (expr.name === "type") { + return this.flowParseTypeAlias(node); + } + } + } + + return super.parseExpressionStatement(node, expr); + } + + // export type + shouldParseExportDeclaration(): boolean { + return ( + this.isContextual("type") || + this.isContextual("interface") || + super.shouldParseExportDeclaration() + ); + } + + isExportDefaultSpecifier(): boolean { + if ( + this.match(tt.name) && + (this.state.value === "type" || this.state.value === "interface") + ) { + return false; + } + + return super.isExportDefaultSpecifier(); + } + + parseConditional( + expr: N.Expression, + noIn: ?boolean, + startPos: number, + startLoc: Position, + refNeedsArrowPos?: ?Pos, + ): N.Expression { + // only do the expensive clone if there is a question mark + // and if we come from inside parens + if (refNeedsArrowPos && this.match(tt.question)) { + const state = this.state.clone(); + try { + return super.parseConditional(expr, noIn, startPos, startLoc); + } catch (err) { + if (err instanceof SyntaxError) { + this.state = state; + refNeedsArrowPos.start = err.pos || this.state.start; + return expr; + } else { + // istanbul ignore next: no such error is expected + throw err; + } + } + } + + return super.parseConditional(expr, noIn, startPos, startLoc); + } + + parseParenItem( + node: N.Expression, + startPos: number, + startLoc: Position, + ): N.Expression { + node = super.parseParenItem(node, startPos, startLoc); + if (this.eat(tt.question)) { + node.optional = true; + } + + if (this.match(tt.colon)) { + const typeCastNode = this.startNodeAt(startPos, startLoc); + typeCastNode.expression = node; + typeCastNode.typeAnnotation = this.flowParseTypeAnnotation(); + + return this.finishNode(typeCastNode, "TypeCastExpression"); + } - // parse an item inside a expression list eg. `(NODE, NODE)` where NODE represents - // the position where this function is called - parseExprListItem(allowEmpty: ?boolean, refShorthandDefaultPos: ?Pos, refNeedsArrowPos: ?Pos): ?N.Expression { - const container = this.startNode(); - const node = super.parseExprListItem(allowEmpty, refShorthandDefaultPos, refNeedsArrowPos); - if (this.match(tt.colon)) { - container._exprListItem = true; - container.expression = node; - container.typeAnnotation = this.flowParseTypeAnnotation(); - return this.finishNode(container, "TypeCastExpression"); - } else { return node; } - } - checkLVal( - expr: N.Expression, - isBinding: ?boolean, - checkClashes: ?{ [key: string]: boolean }, - contextDescription: string - ): void { - if (expr.type !== "TypeCastExpression") { - return super.checkLVal(expr, isBinding, checkClashes, contextDescription); - } - } - - // parse class property type annotations - parseClassProperty(node: N.ClassProperty): N.ClassProperty { - if (this.match(tt.colon)) { - node.typeAnnotation = this.flowParseTypeAnnotation(); - } - return super.parseClassProperty(node); - } - - // determine whether or not we're currently in the position where a class method would appear - isClassMethod(): boolean { - return this.isRelational("<") || super.isClassMethod(); - } - - // determine whether or not we're currently in the position where a class property would appear - isClassProperty(): boolean { - return this.match(tt.colon) || super.isClassProperty(); - } - - isNonstaticConstructor(method: N.ClassMethod | N.ClassProperty): boolean { - return !this.match(tt.colon) && super.isNonstaticConstructor(method); - } - - // parse type parameters for class methods - parseClassMethod( - classBody: N.ClassBody, - method: N.ClassMethod, - isGenerator: boolean, - isAsync: boolean, - isConstructor: boolean - ): void { - if (method.variance) { - this.unexpected(method.variance.start); - } - delete method.variance; - if (this.isRelational("<")) { - method.typeParameters = this.flowParseTypeParameterDeclaration(); - } - - super.parseClassMethod(classBody, method, isGenerator, isAsync, isConstructor); - } - - // parse a the super class type parameters and implements - parseClassSuper(node: N.Class): void { - super.parseClassSuper(node); - if (node.superClass && this.isRelational("<")) { - node.superTypeParameters = this.flowParseTypeParameterInstantiation(); - } - if (this.isContextual("implements")) { - this.next(); - const implemented: N.FlowClassImplements[] = node.implements = []; - do { - const node = this.startNode(); - node.id = this.parseIdentifier(); - if (this.isRelational("<")) { - node.typeParameters = this.flowParseTypeParameterInstantiation(); - } else { - node.typeParameters = null; - } - implemented.push(this.finishNode(node, "ClassImplements")); - } while (this.eat(tt.comma)); - } - } - - parsePropertyName(node: N.ObjectOrClassMember | N.TsNamedTypeElementBase): N.Identifier { - const variance = this.flowParseVariance(); - const key = super.parsePropertyName(node); - // $FlowIgnore ("variance" not defined on TsNamedTypeElementBase) - node.variance = variance; - return key; - } - - // parse type parameters for object method shorthand - parseObjPropValue( - prop: N.ObjectMember, - startPos: ?number, - startLoc: ?Position, - isGenerator: boolean, - isAsync: boolean, - isPattern: boolean, - refShorthandDefaultPos: ?Pos - ): void { - if (prop.variance) { - this.unexpected(prop.variance.start); - } - delete prop.variance; - - let typeParameters; - - // method shorthand - if (this.isRelational("<")) { - typeParameters = this.flowParseTypeParameterDeclaration(); - if (!this.match(tt.parenL)) this.unexpected(); - } - - super.parseObjPropValue( - prop, - startPos, - startLoc, - isGenerator, - isAsync, - isPattern, - refShorthandDefaultPos - ); - - // add typeParameters if we found them - if (typeParameters) { - // $FlowFixMe (trying to set '.typeParameters' on an expression) - (prop.value || prop).typeParameters = typeParameters; - } - } - - parseAssignableListItemTypes(param: N.Pattern): N.Pattern { - if (this.eat(tt.question)) { - if (param.type !== "Identifier") { - throw this.raise( - param.start, - "A binding pattern parameter cannot be optional in an implementation signature."); + parseExport(node: N.ExportNamedDeclaration): N.ExportNamedDeclaration { + node = super.parseExport(node); + if (node.type === "ExportNamedDeclaration") { + node.exportKind = node.exportKind || "value"; } - - param.optional = true; - } - if (this.match(tt.colon)) { - param.typeAnnotation = this.flowParseTypeAnnotation(); - } - this.finishNode(param, param.type); - return param; - } - - parseMaybeDefault(startPos?: ?number, startLoc?: ?Position, left?: ?N.Pattern): N.Pattern { - const node = super.parseMaybeDefault(startPos, startLoc, left); - - if (node.type === "AssignmentPattern" && node.typeAnnotation && node.right.start < node.typeAnnotation.start) { - this.raise(node.typeAnnotation.start, "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`"); + return node; } - return node; - } + parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration { + if (this.isContextual("type")) { + node.exportKind = "type"; - // parse typeof and type imports - parseImportSpecifiers(node: N.ImportDeclaration): void { - node.importKind = "value"; - - let kind = null; - if (this.match(tt._typeof)) { - kind = "typeof"; - } else if (this.isContextual("type")) { - kind = "type"; - } - if (kind) { - const lh = this.lookahead(); - if ((lh.type === tt.name && lh.value !== "from") || lh.type === tt.braceL || lh.type === tt.star) { + const declarationNode = this.startNode(); this.next(); - node.importKind = kind; - } - } - super.parseImportSpecifiers(node); - } - - // parse import-type/typeof shorthand - parseImportSpecifier(node: N.ImportDeclaration): void { - const specifier = this.startNode(); - const firstIdentLoc = this.state.start; - const firstIdent = this.parseIdentifier(true); - - let specifierTypeKind = null; - if (firstIdent.name === "type") { - specifierTypeKind = "type"; - } else if (firstIdent.name === "typeof") { - specifierTypeKind = "typeof"; - } - - let isBinding = false; - if (this.isContextual("as")) { - const as_ident = this.parseIdentifier(true); - if (specifierTypeKind !== null && !this.match(tt.name) && !this.state.type.keyword) { - // `import {type as ,` or `import {type as }` - specifier.imported = as_ident; - specifier.importKind = specifierTypeKind; - specifier.local = as_ident.__clone(); - } else { - // `import {type as foo` - specifier.imported = firstIdent; - specifier.importKind = null; - specifier.local = this.parseIdentifier(); - } - } else if (specifierTypeKind !== null && (this.match(tt.name) || this.state.type.keyword)) { - // `import {type foo` - specifier.imported = this.parseIdentifier(true); - specifier.importKind = specifierTypeKind; - if (this.eatContextual("as")) { - specifier.local = this.parseIdentifier(); - } else { - isBinding = true; - specifier.local = specifier.imported.__clone(); - } - } else { - isBinding = true; - specifier.imported = firstIdent; - specifier.importKind = null; - specifier.local = specifier.imported.__clone(); - } - - if ( - (node.importKind === "type" || node.importKind === "typeof") && - (specifier.importKind === "type" || specifier.importKind === "typeof") - ) { - this.raise(firstIdentLoc, "`The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. It cannot be used with `import type` or `import typeof` statements`"); - } - - if (isBinding) this.checkReservedWord(specifier.local.name, specifier.start, true, true); - - this.checkLVal(specifier.local, true, undefined, "import specifier"); - node.specifiers.push(this.finishNode(specifier, "ImportSpecifier")); - } - - // parse function type parameters - function foo() {} - parseFunctionParams(node: N.Function): void { - if (this.isRelational("<")) { - node.typeParameters = this.flowParseTypeParameterDeclaration(); - } - super.parseFunctionParams(node); - } - - // parse flow type annotations on variable declarator heads - let foo: string = bar - parseVarHead(decl: N.VariableDeclarator): void { - super.parseVarHead(decl); - if (this.match(tt.colon)) { - decl.id.typeAnnotation = this.flowParseTypeAnnotation(); - this.finishNode(decl.id, decl.id.type); - } - } - - // parse the return type of an async arrow function - let foo = (async (): number => {}); - parseAsyncArrowFromCallExpression(node: N.ArrowFunctionExpression, call: N.CallExpression): N.ArrowFunctionExpression { - if (this.match(tt.colon)) { - const oldNoAnonFunctionType = this.state.noAnonFunctionType; - this.state.noAnonFunctionType = true; - node.returnType = this.flowParseTypeAnnotation(); - this.state.noAnonFunctionType = oldNoAnonFunctionType; - } - - return super.parseAsyncArrowFromCallExpression(node, call); - } - - // todo description - shouldParseAsyncArrow(): boolean { - return this.match(tt.colon) || super.shouldParseAsyncArrow(); - } - - // We need to support type parameter declarations for arrow functions. This - // is tricky. There are three situations we need to handle - // - // 1. This is either JSX or an arrow function. We'll try JSX first. If that - // fails, we'll try an arrow function. If that fails, we'll throw the JSX - // error. - // 2. This is an arrow function. We'll parse the type parameter declaration, - // parse the rest, make sure the rest is an arrow function, and go from - // there - // 3. This is neither. Just call the super method - parseMaybeAssign(noIn?: ?boolean, refShorthandDefaultPos?: ?Pos, afterLeftParse?: Function, refNeedsArrowPos?: ?Pos): N.Expression { - let jsxError = null; - if (tt.jsxTagStart && this.match(tt.jsxTagStart)) { - const state = this.state.clone(); - try { - return super.parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos); - } catch (err) { - if (err instanceof SyntaxError) { - this.state = state; - - // Remove `tc.j_expr` and `tc.j_oTag` from context added - // by parsing `jsxTagStart` to stop the JSX plugin from - // messing with the tokens - this.state.context.length -= 2; - - jsxError = err; + if (this.match(tt.braceL)) { + // export type { foo, bar }; + node.specifiers = this.parseExportSpecifiers(); + this.parseExportFrom(node); + return null; } else { - // istanbul ignore next: no such error is expected - throw err; + // export type Foo = Bar; + return this.flowParseTypeAlias(declarationNode); + } + } else if (this.isContextual("interface")) { + node.exportKind = "type"; + const declarationNode = this.startNode(); + this.next(); + return this.flowParseInterface(declarationNode); + } else { + return super.parseExportDeclaration(node); + } + } + + parseClassId(node: N.Class, isStatement: boolean, optionalId: ?boolean) { + super.parseClassId(node, isStatement, optionalId); + if (this.isRelational("<")) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } + } + + // don't consider `void` to be a keyword as then it'll use the void token type + // and set startExpr + isKeyword(name: string): boolean { + if (this.state.inType && name === "void") { + return false; + } else { + return super.isKeyword(name); + } + } + + // ensure that inside flow types, we bypass the jsx parser plugin + readToken(code: number): void { + if (this.state.inType && (code === 62 || code === 60)) { + return this.finishOp(tt.relational, 1); + } else { + return super.readToken(code); + } + } + + toAssignable( + node: N.Node, + isBinding: ?boolean, + contextDescription: string, + ): N.Node { + if (node.type === "TypeCastExpression") { + return super.toAssignable( + this.typeCastToParameter(node), + isBinding, + contextDescription, + ); + } else { + return super.toAssignable(node, isBinding, contextDescription); + } + } + + // turn type casts that we found in function parameter head into type annotated params + toAssignableList( + exprList: N.Expression[], + isBinding: ?boolean, + contextDescription: string, + ): $ReadOnlyArray { + for (let i = 0; i < exprList.length; i++) { + const expr = exprList[i]; + if (expr && expr.type === "TypeCastExpression") { + exprList[i] = this.typeCastToParameter(expr); } } + return super.toAssignableList(exprList, isBinding, contextDescription); } - if (jsxError != null || this.isRelational("<")) { - let arrowExpression; - let typeParameters; - try { - typeParameters = this.flowParseTypeParameterDeclaration(); - - arrowExpression = super.parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos); - arrowExpression.typeParameters = typeParameters; - this.resetStartLocationFromNode(arrowExpression, typeParameters); - } catch (err) { - throw jsxError || err; + // this is a list of nodes, from something like a call expression, we need to filter the + // type casts that we've found that are illegal in this context + toReferencedList( + exprList: $ReadOnlyArray, + ): $ReadOnlyArray { + for (let i = 0; i < exprList.length; i++) { + const expr = exprList[i]; + if (expr && expr._exprListItem && expr.type === "TypeCastExpression") { + this.raise(expr.start, "Unexpected type cast"); + } } - if (arrowExpression.type === "ArrowFunctionExpression") { - return arrowExpression; - } else if (jsxError != null) { - throw jsxError; + return exprList; + } + + // parse an item inside a expression list eg. `(NODE, NODE)` where NODE represents + // the position where this function is called + parseExprListItem( + allowEmpty: ?boolean, + refShorthandDefaultPos: ?Pos, + refNeedsArrowPos: ?Pos, + ): ?N.Expression { + const container = this.startNode(); + const node = super.parseExprListItem( + allowEmpty, + refShorthandDefaultPos, + refNeedsArrowPos, + ); + if (this.match(tt.colon)) { + container._exprListItem = true; + container.expression = node; + container.typeAnnotation = this.flowParseTypeAnnotation(); + return this.finishNode(container, "TypeCastExpression"); } else { - this.raise( - typeParameters.start, - "Expected an arrow function after this type parameter declaration", + return node; + } + } + + checkLVal( + expr: N.Expression, + isBinding: ?boolean, + checkClashes: ?{ [key: string]: boolean }, + contextDescription: string, + ): void { + if (expr.type !== "TypeCastExpression") { + return super.checkLVal( + expr, + isBinding, + checkClashes, + contextDescription, ); } } - return super.parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos); - } + // parse class property type annotations + parseClassProperty(node: N.ClassProperty): N.ClassProperty { + if (this.match(tt.colon)) { + node.typeAnnotation = this.flowParseTypeAnnotation(); + } + return super.parseClassProperty(node); + } - // handle return types for arrow functions - parseArrow(node: N.ArrowFunctionExpression): ?N.ArrowFunctionExpression { - if (this.match(tt.colon)) { - const state = this.state.clone(); - try { - const oldNoAnonFunctionType = this.state.noAnonFunctionType; - this.state.noAnonFunctionType = true; + // determine whether or not we're currently in the position where a class method would appear + isClassMethod(): boolean { + return this.isRelational("<") || super.isClassMethod(); + } - const typeNode = this.startNode(); - // $FlowFixMe (destructuring not supported yet) - [typeNode.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser(); + // determine whether or not we're currently in the position where a class property would appear + isClassProperty(): boolean { + return this.match(tt.colon) || super.isClassProperty(); + } - this.state.noAnonFunctionType = oldNoAnonFunctionType; + isNonstaticConstructor(method: N.ClassMethod | N.ClassProperty): boolean { + return !this.match(tt.colon) && super.isNonstaticConstructor(method); + } - if (this.canInsertSemicolon()) this.unexpected(); - if (!this.match(tt.arrow)) this.unexpected(); + // parse type parameters for class methods + parseClassMethod( + classBody: N.ClassBody, + method: N.ClassMethod, + isGenerator: boolean, + isAsync: boolean, + isConstructor: boolean, + ): void { + if (method.variance) { + this.unexpected(method.variance.start); + } + delete method.variance; + if (this.isRelational("<")) { + method.typeParameters = this.flowParseTypeParameterDeclaration(); + } - // assign after it is clear it is an arrow - node.returnType = typeNode.typeAnnotation - ? this.finishNode(typeNode, "TypeAnnotation") - : null; - } catch (err) { - if (err instanceof SyntaxError) { - this.state = state; - } else { - // istanbul ignore next: no such error is expected - throw err; - } + super.parseClassMethod( + classBody, + method, + isGenerator, + isAsync, + isConstructor, + ); + } + + // parse a the super class type parameters and implements + parseClassSuper(node: N.Class): void { + super.parseClassSuper(node); + if (node.superClass && this.isRelational("<")) { + node.superTypeParameters = this.flowParseTypeParameterInstantiation(); + } + if (this.isContextual("implements")) { + this.next(); + const implemented: N.FlowClassImplements[] = (node.implements = []); + do { + const node = this.startNode(); + node.id = this.parseIdentifier(); + if (this.isRelational("<")) { + node.typeParameters = this.flowParseTypeParameterInstantiation(); + } else { + node.typeParameters = null; + } + implemented.push(this.finishNode(node, "ClassImplements")); + } while (this.eat(tt.comma)); } } - return super.parseArrow(node); - } + parsePropertyName( + node: N.ObjectOrClassMember | N.TsNamedTypeElementBase, + ): N.Identifier { + const variance = this.flowParseVariance(); + const key = super.parsePropertyName(node); + // $FlowIgnore ("variance" not defined on TsNamedTypeElementBase) + node.variance = variance; + return key; + } - shouldParseArrow(): boolean { - return this.match(tt.colon) || super.shouldParseArrow(); - } -}; + // parse type parameters for object method shorthand + parseObjPropValue( + prop: N.ObjectMember, + startPos: ?number, + startLoc: ?Position, + isGenerator: boolean, + isAsync: boolean, + isPattern: boolean, + refShorthandDefaultPos: ?Pos, + ): void { + if (prop.variance) { + this.unexpected(prop.variance.start); + } + delete prop.variance; + + let typeParameters; + + // method shorthand + if (this.isRelational("<")) { + typeParameters = this.flowParseTypeParameterDeclaration(); + if (!this.match(tt.parenL)) this.unexpected(); + } + + super.parseObjPropValue( + prop, + startPos, + startLoc, + isGenerator, + isAsync, + isPattern, + refShorthandDefaultPos, + ); + + // add typeParameters if we found them + if (typeParameters) { + // $FlowFixMe (trying to set '.typeParameters' on an expression) + (prop.value || prop).typeParameters = typeParameters; + } + } + + parseAssignableListItemTypes(param: N.Pattern): N.Pattern { + if (this.eat(tt.question)) { + if (param.type !== "Identifier") { + throw this.raise( + param.start, + "A binding pattern parameter cannot be optional in an implementation signature.", + ); + } + + param.optional = true; + } + if (this.match(tt.colon)) { + param.typeAnnotation = this.flowParseTypeAnnotation(); + } + this.finishNode(param, param.type); + return param; + } + + parseMaybeDefault( + startPos?: ?number, + startLoc?: ?Position, + left?: ?N.Pattern, + ): N.Pattern { + const node = super.parseMaybeDefault(startPos, startLoc, left); + + if ( + node.type === "AssignmentPattern" && + node.typeAnnotation && + node.right.start < node.typeAnnotation.start + ) { + this.raise( + node.typeAnnotation.start, + "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`", + ); + } + + return node; + } + + // parse typeof and type imports + parseImportSpecifiers(node: N.ImportDeclaration): void { + node.importKind = "value"; + + let kind = null; + if (this.match(tt._typeof)) { + kind = "typeof"; + } else if (this.isContextual("type")) { + kind = "type"; + } + if (kind) { + const lh = this.lookahead(); + if ( + (lh.type === tt.name && lh.value !== "from") || + lh.type === tt.braceL || + lh.type === tt.star + ) { + this.next(); + node.importKind = kind; + } + } + + super.parseImportSpecifiers(node); + } + + // parse import-type/typeof shorthand + parseImportSpecifier(node: N.ImportDeclaration): void { + const specifier = this.startNode(); + const firstIdentLoc = this.state.start; + const firstIdent = this.parseIdentifier(true); + + let specifierTypeKind = null; + if (firstIdent.name === "type") { + specifierTypeKind = "type"; + } else if (firstIdent.name === "typeof") { + specifierTypeKind = "typeof"; + } + + let isBinding = false; + if (this.isContextual("as")) { + const as_ident = this.parseIdentifier(true); + if ( + specifierTypeKind !== null && + !this.match(tt.name) && + !this.state.type.keyword + ) { + // `import {type as ,` or `import {type as }` + specifier.imported = as_ident; + specifier.importKind = specifierTypeKind; + specifier.local = as_ident.__clone(); + } else { + // `import {type as foo` + specifier.imported = firstIdent; + specifier.importKind = null; + specifier.local = this.parseIdentifier(); + } + } else if ( + specifierTypeKind !== null && + (this.match(tt.name) || this.state.type.keyword) + ) { + // `import {type foo` + specifier.imported = this.parseIdentifier(true); + specifier.importKind = specifierTypeKind; + if (this.eatContextual("as")) { + specifier.local = this.parseIdentifier(); + } else { + isBinding = true; + specifier.local = specifier.imported.__clone(); + } + } else { + isBinding = true; + specifier.imported = firstIdent; + specifier.importKind = null; + specifier.local = specifier.imported.__clone(); + } + + if ( + (node.importKind === "type" || node.importKind === "typeof") && + (specifier.importKind === "type" || specifier.importKind === "typeof") + ) { + this.raise( + firstIdentLoc, + "`The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. It cannot be used with `import type` or `import typeof` statements`", + ); + } + + if (isBinding) + this.checkReservedWord( + specifier.local.name, + specifier.start, + true, + true, + ); + + this.checkLVal(specifier.local, true, undefined, "import specifier"); + node.specifiers.push(this.finishNode(specifier, "ImportSpecifier")); + } + + // parse function type parameters - function foo() {} + parseFunctionParams(node: N.Function): void { + if (this.isRelational("<")) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } + super.parseFunctionParams(node); + } + + // parse flow type annotations on variable declarator heads - let foo: string = bar + parseVarHead(decl: N.VariableDeclarator): void { + super.parseVarHead(decl); + if (this.match(tt.colon)) { + decl.id.typeAnnotation = this.flowParseTypeAnnotation(); + this.finishNode(decl.id, decl.id.type); + } + } + + // parse the return type of an async arrow function - let foo = (async (): number => {}); + parseAsyncArrowFromCallExpression( + node: N.ArrowFunctionExpression, + call: N.CallExpression, + ): N.ArrowFunctionExpression { + if (this.match(tt.colon)) { + const oldNoAnonFunctionType = this.state.noAnonFunctionType; + this.state.noAnonFunctionType = true; + node.returnType = this.flowParseTypeAnnotation(); + this.state.noAnonFunctionType = oldNoAnonFunctionType; + } + + return super.parseAsyncArrowFromCallExpression(node, call); + } + + // todo description + shouldParseAsyncArrow(): boolean { + return this.match(tt.colon) || super.shouldParseAsyncArrow(); + } + + // We need to support type parameter declarations for arrow functions. This + // is tricky. There are three situations we need to handle + // + // 1. This is either JSX or an arrow function. We'll try JSX first. If that + // fails, we'll try an arrow function. If that fails, we'll throw the JSX + // error. + // 2. This is an arrow function. We'll parse the type parameter declaration, + // parse the rest, make sure the rest is an arrow function, and go from + // there + // 3. This is neither. Just call the super method + parseMaybeAssign( + noIn?: ?boolean, + refShorthandDefaultPos?: ?Pos, + afterLeftParse?: Function, + refNeedsArrowPos?: ?Pos, + ): N.Expression { + let jsxError = null; + if (tt.jsxTagStart && this.match(tt.jsxTagStart)) { + const state = this.state.clone(); + try { + return super.parseMaybeAssign( + noIn, + refShorthandDefaultPos, + afterLeftParse, + refNeedsArrowPos, + ); + } catch (err) { + if (err instanceof SyntaxError) { + this.state = state; + + // Remove `tc.j_expr` and `tc.j_oTag` from context added + // by parsing `jsxTagStart` to stop the JSX plugin from + // messing with the tokens + this.state.context.length -= 2; + + jsxError = err; + } else { + // istanbul ignore next: no such error is expected + throw err; + } + } + } + + if (jsxError != null || this.isRelational("<")) { + let arrowExpression; + let typeParameters; + try { + typeParameters = this.flowParseTypeParameterDeclaration(); + + arrowExpression = super.parseMaybeAssign( + noIn, + refShorthandDefaultPos, + afterLeftParse, + refNeedsArrowPos, + ); + arrowExpression.typeParameters = typeParameters; + this.resetStartLocationFromNode(arrowExpression, typeParameters); + } catch (err) { + throw jsxError || err; + } + + if (arrowExpression.type === "ArrowFunctionExpression") { + return arrowExpression; + } else if (jsxError != null) { + throw jsxError; + } else { + this.raise( + typeParameters.start, + "Expected an arrow function after this type parameter declaration", + ); + } + } + + return super.parseMaybeAssign( + noIn, + refShorthandDefaultPos, + afterLeftParse, + refNeedsArrowPos, + ); + } + + // handle return types for arrow functions + parseArrow(node: N.ArrowFunctionExpression): ?N.ArrowFunctionExpression { + if (this.match(tt.colon)) { + const state = this.state.clone(); + try { + const oldNoAnonFunctionType = this.state.noAnonFunctionType; + this.state.noAnonFunctionType = true; + + const typeNode = this.startNode(); + + [ + // $FlowFixMe (destructuring not supported yet) + typeNode.typeAnnotation, + // $FlowFixMe (destructuring not supported yet) + node.predicate, + ] = this.flowParseTypeAndPredicateInitialiser(); + + this.state.noAnonFunctionType = oldNoAnonFunctionType; + + if (this.canInsertSemicolon()) this.unexpected(); + if (!this.match(tt.arrow)) this.unexpected(); + + // assign after it is clear it is an arrow + node.returnType = typeNode.typeAnnotation + ? this.finishNode(typeNode, "TypeAnnotation") + : null; + } catch (err) { + if (err instanceof SyntaxError) { + this.state = state; + } else { + // istanbul ignore next: no such error is expected + throw err; + } + } + } + + return super.parseArrow(node); + } + + shouldParseArrow(): boolean { + return this.match(tt.colon) || super.shouldParseArrow(); + } + }; diff --git a/src/plugins/jsx/index.js b/src/plugins/jsx/index.js index 0d8e6b91a5..0cd591e5a5 100644 --- a/src/plugins/jsx/index.js +++ b/src/plugins/jsx/index.js @@ -29,7 +29,7 @@ tt.jsxTagStart.updateContext = function() { tt.jsxTagEnd.updateContext = function(prevType) { const out = this.state.context.pop(); - if (out === tc.j_oTag && prevType === tt.slash || out === tc.j_cTag) { + if ((out === tc.j_oTag && prevType === tt.slash) || out === tc.j_cTag) { this.state.context.pop(); this.state.exprAllowed = this.curContext() === tc.j_expr; } else { @@ -39,7 +39,9 @@ tt.jsxTagEnd.updateContext = function(prevType) { // Transforms JSX element name to string. -function getQualifiedJSXName(object: N.JSXIdentifier | N.JSXNamespacedName | N.JSXMemberExpression): string { +function getQualifiedJSXName( + object: N.JSXIdentifier | N.JSXNamespacedName | N.JSXMemberExpression, +): string { if (object.type === "JSXIdentifier") { return object.name; } @@ -49,423 +51,462 @@ function getQualifiedJSXName(object: N.JSXIdentifier | N.JSXNamespacedName | N.J } if (object.type === "JSXMemberExpression") { - return getQualifiedJSXName(object.object) + "." + getQualifiedJSXName(object.property); + return ( + getQualifiedJSXName(object.object) + + "." + + getQualifiedJSXName(object.property) + ); } // istanbul ignore next throw new Error("Node had unexpected type: " + object.type); } -export default (superClass: Class): Class => class extends superClass { - // Reads inline JSX contents token. +export default (superClass: Class): Class => + class extends superClass { + // Reads inline JSX contents token. - jsxReadToken(): void { - let out = ""; - let chunkStart = this.state.pos; - for (;;) { - if (this.state.pos >= this.input.length) { - this.raise(this.state.start, "Unterminated JSX contents"); - } + jsxReadToken(): void { + let out = ""; + let chunkStart = this.state.pos; + for (;;) { + if (this.state.pos >= this.input.length) { + this.raise(this.state.start, "Unterminated JSX contents"); + } - const ch = this.input.charCodeAt(this.state.pos); + const ch = this.input.charCodeAt(this.state.pos); - switch (ch) { - case 60: // "<" - case 123: // "{" - if (this.state.pos === this.state.start) { - if (ch === 60 && this.state.exprAllowed) { - ++this.state.pos; - return this.finishToken(tt.jsxTagStart); + switch (ch) { + case 60: // "<" + case 123: // "{" + if (this.state.pos === this.state.start) { + if (ch === 60 && this.state.exprAllowed) { + ++this.state.pos; + return this.finishToken(tt.jsxTagStart); + } + return this.getTokenFromCode(ch); } - return this.getTokenFromCode(ch); - } - out += this.input.slice(chunkStart, this.state.pos); - return this.finishToken(tt.jsxText, out); + out += this.input.slice(chunkStart, this.state.pos); + return this.finishToken(tt.jsxText, out); - case 38: // "&" + case 38: // "&" + out += this.input.slice(chunkStart, this.state.pos); + out += this.jsxReadEntity(); + chunkStart = this.state.pos; + break; + + default: + if (isNewLine(ch)) { + out += this.input.slice(chunkStart, this.state.pos); + out += this.jsxReadNewLine(true); + chunkStart = this.state.pos; + } else { + ++this.state.pos; + } + } + } + } + + jsxReadNewLine(normalizeCRLF: boolean): string { + const ch = this.input.charCodeAt(this.state.pos); + let out; + ++this.state.pos; + if (ch === 13 && this.input.charCodeAt(this.state.pos) === 10) { + ++this.state.pos; + out = normalizeCRLF ? "\n" : "\r\n"; + } else { + out = String.fromCharCode(ch); + } + ++this.state.curLine; + this.state.lineStart = this.state.pos; + + return out; + } + + jsxReadString(quote: number): void { + let out = ""; + let chunkStart = ++this.state.pos; + for (;;) { + if (this.state.pos >= this.input.length) { + this.raise(this.state.start, "Unterminated string constant"); + } + + const ch = this.input.charCodeAt(this.state.pos); + if (ch === quote) break; + if (ch === 38) { + // "&" out += this.input.slice(chunkStart, this.state.pos); out += this.jsxReadEntity(); chunkStart = this.state.pos; + } else if (isNewLine(ch)) { + out += this.input.slice(chunkStart, this.state.pos); + out += this.jsxReadNewLine(false); + chunkStart = this.state.pos; + } else { + ++this.state.pos; + } + } + out += this.input.slice(chunkStart, this.state.pos++); + return this.finishToken(tt.string, out); + } + + jsxReadEntity(): string { + let str = ""; + let count = 0; + let entity; + let ch = this.input[this.state.pos]; + + const startPos = ++this.state.pos; + while (this.state.pos < this.input.length && count++ < 10) { + ch = this.input[this.state.pos++]; + if (ch === ";") { + if (str[0] === "#") { + if (str[1] === "x") { + str = str.substr(2); + if (HEX_NUMBER.test(str)) + entity = String.fromCodePoint(parseInt(str, 16)); + } else { + str = str.substr(1); + if (DECIMAL_NUMBER.test(str)) + entity = String.fromCodePoint(parseInt(str, 10)); + } + } else { + entity = XHTMLEntities[str]; + } break; + } + str += ch; + } + if (!entity) { + this.state.pos = startPos; + return "&"; + } + return entity; + } + + // Read a JSX identifier (valid tag or attribute name). + // + // Optimized version since JSX identifiers can"t contain + // escape characters and so can be read as single slice. + // Also assumes that first character was already checked + // by isIdentifierStart in readToken. + + jsxReadWord(): void { + let ch; + const start = this.state.pos; + do { + ch = this.input.charCodeAt(++this.state.pos); + } while (isIdentifierChar(ch) || ch === 45); // "-" + return this.finishToken( + tt.jsxName, + this.input.slice(start, this.state.pos), + ); + } + + // Parse next token as JSX identifier + + jsxParseIdentifier(): N.JSXIdentifier { + const node = this.startNode(); + if (this.match(tt.jsxName)) { + node.name = this.state.value; + } else if (this.state.type.keyword) { + node.name = this.state.type.keyword; + } else { + this.unexpected(); + } + this.next(); + return this.finishNode(node, "JSXIdentifier"); + } + + // Parse namespaced identifier. + + jsxParseNamespacedName(): N.JSXNamespacedName { + const startPos = this.state.start; + const startLoc = this.state.startLoc; + const name = this.jsxParseIdentifier(); + if (!this.eat(tt.colon)) return name; + + const node = this.startNodeAt(startPos, startLoc); + node.namespace = name; + node.name = this.jsxParseIdentifier(); + return this.finishNode(node, "JSXNamespacedName"); + } + + // Parses element name in any form - namespaced, member + // or single identifier. + + jsxParseElementName(): N.JSXNamespacedName | N.JSXMemberExpression { + const startPos = this.state.start; + const startLoc = this.state.startLoc; + let node = this.jsxParseNamespacedName(); + while (this.eat(tt.dot)) { + const newNode = this.startNodeAt(startPos, startLoc); + newNode.object = node; + newNode.property = this.jsxParseIdentifier(); + node = this.finishNode(newNode, "JSXMemberExpression"); + } + return node; + } + + // Parses any type of JSX attribute value. + + jsxParseAttributeValue(): N.Expression { + let node; + switch (this.state.type) { + case tt.braceL: + node = this.jsxParseExpressionContainer(); + if (node.expression.type === "JSXEmptyExpression") { + throw this.raise( + node.start, + "JSX attributes must only be assigned a non-empty expression", + ); + } else { + return node; + } + + case tt.jsxTagStart: + case tt.string: + return this.parseExprAtom(); default: - if (isNewLine(ch)) { - out += this.input.slice(chunkStart, this.state.pos); - out += this.jsxReadNewLine(true); - chunkStart = this.state.pos; - } else { - ++this.state.pos; - } + throw this.raise( + this.state.start, + "JSX value should be either an expression or a quoted JSX text", + ); } } - } - jsxReadNewLine(normalizeCRLF: boolean): string { - const ch = this.input.charCodeAt(this.state.pos); - let out; - ++this.state.pos; - if (ch === 13 && this.input.charCodeAt(this.state.pos) === 10) { - ++this.state.pos; - out = normalizeCRLF ? "\n" : "\r\n"; - } else { - out = String.fromCharCode(ch); + // JSXEmptyExpression is unique type since it doesn't actually parse anything, + // and so it should start at the end of last read token (left brace) and finish + // at the beginning of the next one (right brace). + + jsxParseEmptyExpression(): N.JSXEmptyExpression { + const node = this.startNodeAt( + this.state.lastTokEnd, + this.state.lastTokEndLoc, + ); + return this.finishNodeAt( + node, + "JSXEmptyExpression", + this.state.start, + this.state.startLoc, + ); } - ++this.state.curLine; - this.state.lineStart = this.state.pos; - return out; - } + // Parse JSX spread child - jsxReadString(quote: number): void { - let out = ""; - let chunkStart = ++this.state.pos; - for (;;) { - if (this.state.pos >= this.input.length) { - this.raise(this.state.start, "Unterminated string constant"); - } - - const ch = this.input.charCodeAt(this.state.pos); - if (ch === quote) break; - if (ch === 38) { // "&" - out += this.input.slice(chunkStart, this.state.pos); - out += this.jsxReadEntity(); - chunkStart = this.state.pos; - } else if (isNewLine(ch)) { - out += this.input.slice(chunkStart, this.state.pos); - out += this.jsxReadNewLine(false); - chunkStart = this.state.pos; - } else { - ++this.state.pos; - } - } - out += this.input.slice(chunkStart, this.state.pos++); - return this.finishToken(tt.string, out); - } - - jsxReadEntity(): string { - let str = ""; - let count = 0; - let entity; - let ch = this.input[this.state.pos]; - - const startPos = ++this.state.pos; - while (this.state.pos < this.input.length && count++ < 10) { - ch = this.input[this.state.pos++]; - if (ch === ";") { - if (str[0] === "#") { - if (str[1] === "x") { - str = str.substr(2); - if (HEX_NUMBER.test(str)) - entity = String.fromCodePoint(parseInt(str, 16)); - } else { - str = str.substr(1); - if (DECIMAL_NUMBER.test(str)) - entity = String.fromCodePoint(parseInt(str, 10)); - } - } else { - entity = XHTMLEntities[str]; - } - break; - } - str += ch; - } - if (!entity) { - this.state.pos = startPos; - return "&"; - } - return entity; - } - - - // Read a JSX identifier (valid tag or attribute name). - // - // Optimized version since JSX identifiers can"t contain - // escape characters and so can be read as single slice. - // Also assumes that first character was already checked - // by isIdentifierStart in readToken. - - jsxReadWord(): void { - let ch; - const start = this.state.pos; - do { - ch = this.input.charCodeAt(++this.state.pos); - } while (isIdentifierChar(ch) || ch === 45); // "-" - return this.finishToken(tt.jsxName, this.input.slice(start, this.state.pos)); - } - - // Parse next token as JSX identifier - - jsxParseIdentifier(): N.JSXIdentifier { - const node = this.startNode(); - if (this.match(tt.jsxName)) { - node.name = this.state.value; - } else if (this.state.type.keyword) { - node.name = this.state.type.keyword; - } else { - this.unexpected(); - } - this.next(); - return this.finishNode(node, "JSXIdentifier"); - } - - // Parse namespaced identifier. - - jsxParseNamespacedName(): N.JSXNamespacedName { - const startPos = this.state.start; - const startLoc = this.state.startLoc; - const name = this.jsxParseIdentifier(); - if (!this.eat(tt.colon)) return name; - - const node = this.startNodeAt(startPos, startLoc); - node.namespace = name; - node.name = this.jsxParseIdentifier(); - return this.finishNode(node, "JSXNamespacedName"); - } - - // Parses element name in any form - namespaced, member - // or single identifier. - - jsxParseElementName(): N.JSXNamespacedName | N.JSXMemberExpression { - const startPos = this.state.start; - const startLoc = this.state.startLoc; - let node = this.jsxParseNamespacedName(); - while (this.eat(tt.dot)) { - const newNode = this.startNodeAt(startPos, startLoc); - newNode.object = node; - newNode.property = this.jsxParseIdentifier(); - node = this.finishNode(newNode, "JSXMemberExpression"); - } - return node; - } - - // Parses any type of JSX attribute value. - - jsxParseAttributeValue(): N.Expression { - let node; - switch (this.state.type) { - case tt.braceL: - node = this.jsxParseExpressionContainer(); - if (node.expression.type === "JSXEmptyExpression") { - throw this.raise(node.start, "JSX attributes must only be assigned a non-empty expression"); - } else { - return node; - } - - case tt.jsxTagStart: - case tt.string: - return this.parseExprAtom(); - - default: - throw this.raise(this.state.start, "JSX value should be either an expression or a quoted JSX text"); - } - } - - // JSXEmptyExpression is unique type since it doesn't actually parse anything, - // and so it should start at the end of last read token (left brace) and finish - // at the beginning of the next one (right brace). - - jsxParseEmptyExpression(): N.JSXEmptyExpression { - const node = this.startNodeAt(this.state.lastTokEnd, this.state.lastTokEndLoc); - return this.finishNodeAt(node, "JSXEmptyExpression", this.state.start, this.state.startLoc); - } - - // Parse JSX spread child - - jsxParseSpreadChild(): N.JSXSpreadChild { - const node = this.startNode(); - this.expect(tt.braceL); - this.expect(tt.ellipsis); - node.expression = this.parseExpression(); - this.expect(tt.braceR); - - return this.finishNode(node, "JSXSpreadChild"); - } - - // Parses JSX expression enclosed into curly brackets. - - - jsxParseExpressionContainer(): N.JSXExpressionContainer { - const node = this.startNode(); - this.next(); - if (this.match(tt.braceR)) { - node.expression = this.jsxParseEmptyExpression(); - } else { - node.expression = this.parseExpression(); - } - this.expect(tt.braceR); - return this.finishNode(node, "JSXExpressionContainer"); - } - - // Parses following JSX attribute name-value pair. - - jsxParseAttribute(): N.JSXAttribute { - const node = this.startNode(); - if (this.eat(tt.braceL)) { + jsxParseSpreadChild(): N.JSXSpreadChild { + const node = this.startNode(); + this.expect(tt.braceL); this.expect(tt.ellipsis); - node.argument = this.parseMaybeAssign(); + node.expression = this.parseExpression(); this.expect(tt.braceR); - return this.finishNode(node, "JSXSpreadAttribute"); + + return this.finishNode(node, "JSXSpreadChild"); } - node.name = this.jsxParseNamespacedName(); - node.value = this.eat(tt.eq) ? this.jsxParseAttributeValue() : null; - return this.finishNode(node, "JSXAttribute"); - } - // Parses JSX opening tag starting after "<". + // Parses JSX expression enclosed into curly brackets. - jsxParseOpeningElementAt(startPos: number, startLoc: Position): N.JSXOpeningElement { - const node = this.startNodeAt(startPos, startLoc); - node.attributes = []; - node.name = this.jsxParseElementName(); - while (!this.match(tt.slash) && !this.match(tt.jsxTagEnd)) { - node.attributes.push(this.jsxParseAttribute()); + jsxParseExpressionContainer(): N.JSXExpressionContainer { + const node = this.startNode(); + this.next(); + if (this.match(tt.braceR)) { + node.expression = this.jsxParseEmptyExpression(); + } else { + node.expression = this.parseExpression(); + } + this.expect(tt.braceR); + return this.finishNode(node, "JSXExpressionContainer"); } - node.selfClosing = this.eat(tt.slash); - this.expect(tt.jsxTagEnd); - return this.finishNode(node, "JSXOpeningElement"); - } - // Parses JSX closing tag starting after "", + ); } } - // $FlowIgnore - if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) { + node.openingElement = openingElement; + node.closingElement = closingElement; + node.children = children; + if (this.match(tt.relational) && this.state.value === "<") { this.raise( - // $FlowIgnore - closingElement.start, - "Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">" + this.state.start, + "Adjacent JSX elements must be wrapped in an enclosing tag", ); } + return this.finishNode(node, "JSXElement"); } - node.openingElement = openingElement; - node.closingElement = closingElement; - node.children = children; - if (this.match(tt.relational) && this.state.value === "<") { - this.raise(this.state.start, "Adjacent JSX elements must be wrapped in an enclosing tag"); - } - return this.finishNode(node, "JSXElement"); - } + // Parses entire JSX element from current position. - // Parses entire JSX element from current position. - - jsxParseElement(): N.JSXElement { - const startPos = this.state.start; - const startLoc = this.state.startLoc; - this.next(); - return this.jsxParseElementAt(startPos, startLoc); - } - - // ================================== - // Overrides - // ================================== - - parseExprAtom(refShortHandDefaultPos: ?Pos): N.Expression { - if (this.match(tt.jsxText)) { - return this.parseLiteral(this.state.value, "JSXText"); - } else if (this.match(tt.jsxTagStart)) { - return this.jsxParseElement(); - } else { - return super.parseExprAtom(refShortHandDefaultPos); - } - } - - readToken(code: number): void { - if (this.state.inPropertyName) return super.readToken(code); - - const context = this.curContext(); - - if (context === tc.j_expr) { - return this.jsxReadToken(); + jsxParseElement(): N.JSXElement { + const startPos = this.state.start; + const startLoc = this.state.startLoc; + this.next(); + return this.jsxParseElementAt(startPos, startLoc); } - if (context === tc.j_oTag || context === tc.j_cTag) { - if (isIdentifierStart(code)) { - return this.jsxReadWord(); - } + // ================================== + // Overrides + // ================================== - if (code === 62) { - ++this.state.pos; - return this.finishToken(tt.jsxTagEnd); - } - - if ((code === 34 || code === 39) && context === tc.j_oTag) { - return this.jsxReadString(code); - } - } - - if (code === 60 && this.state.exprAllowed) { - ++this.state.pos; - return this.finishToken(tt.jsxTagStart); - } - - return super.readToken(code); - } - - updateContext(prevType: TokenType): void { - if (this.match(tt.braceL)) { - const curContext = this.curContext(); - if (curContext === tc.j_oTag) { - this.state.context.push(tc.braceExpression); - } else if (curContext === tc.j_expr) { - this.state.context.push(tc.templateQuasi); + parseExprAtom(refShortHandDefaultPos: ?Pos): N.Expression { + if (this.match(tt.jsxText)) { + return this.parseLiteral(this.state.value, "JSXText"); + } else if (this.match(tt.jsxTagStart)) { + return this.jsxParseElement(); } else { - super.updateContext(prevType); + return super.parseExprAtom(refShortHandDefaultPos); } - this.state.exprAllowed = true; - } else if (this.match(tt.slash) && prevType === tt.jsxTagStart) { - this.state.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore - this.state.context.push(tc.j_cTag); // reconsider as closing tag context - this.state.exprAllowed = false; - } else { - return super.updateContext(prevType); } - } -}; + + readToken(code: number): void { + if (this.state.inPropertyName) return super.readToken(code); + + const context = this.curContext(); + + if (context === tc.j_expr) { + return this.jsxReadToken(); + } + + if (context === tc.j_oTag || context === tc.j_cTag) { + if (isIdentifierStart(code)) { + return this.jsxReadWord(); + } + + if (code === 62) { + ++this.state.pos; + return this.finishToken(tt.jsxTagEnd); + } + + if ((code === 34 || code === 39) && context === tc.j_oTag) { + return this.jsxReadString(code); + } + } + + if (code === 60 && this.state.exprAllowed) { + ++this.state.pos; + return this.finishToken(tt.jsxTagStart); + } + + return super.readToken(code); + } + + updateContext(prevType: TokenType): void { + if (this.match(tt.braceL)) { + const curContext = this.curContext(); + if (curContext === tc.j_oTag) { + this.state.context.push(tc.braceExpression); + } else if (curContext === tc.j_expr) { + this.state.context.push(tc.templateQuasi); + } else { + super.updateContext(prevType); + } + this.state.exprAllowed = true; + } else if (this.match(tt.slash) && prevType === tt.jsxTagStart) { + this.state.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore + this.state.context.push(tc.j_cTag); // reconsider as closing tag context + this.state.exprAllowed = false; + } else { + return super.updateContext(prevType); + } + } + }; diff --git a/src/plugins/jsx/xhtml.js b/src/plugins/jsx/xhtml.js index 022c4e94ac..101fdebfc7 100644 --- a/src/plugins/jsx/xhtml.js +++ b/src/plugins/jsx/xhtml.js @@ -225,7 +225,7 @@ const entities: { [name: string]: string } = { or: "\u2228", cap: "\u2229", cup: "\u222A", - "int": "\u222B", + int: "\u222B", there4: "\u2234", sim: "\u223C", cong: "\u2245", @@ -253,6 +253,6 @@ const entities: { [name: string]: string } = { spades: "\u2660", clubs: "\u2663", hearts: "\u2665", - diams: "\u2666" + diams: "\u2666", }; export default entities; diff --git a/src/plugins/typescript.js b/src/plugins/typescript.js index b185e6059c..5add49c8a2 100644 --- a/src/plugins/typescript.js +++ b/src/plugins/typescript.js @@ -7,7 +7,13 @@ import * as N from "../types"; import type { Pos, Position } from "../util/location"; import Parser from "../parser"; -type TsModifier = "readonly" | "abstract" | "static" | "public" | "private" | "protected"; +type TsModifier = + | "readonly" + | "abstract" + | "static" + | "public" + | "private" + | "protected"; function nonNull(x: ?T): T { if (x == null) { @@ -31,7 +37,9 @@ type ParsingContext = | "TypeParametersOrArguments"; // Doesn't handle "void" or "null" because those are keywords, not identifiers. -function keywordTypeFromName(value: string): N.TsKeywordTypeType | typeof undefined { +function keywordTypeFromName( + value: string, +): N.TsKeywordTypeType | typeof undefined { switch (value) { case "any": return "TSAnyKeyword"; @@ -54,1140 +62,1299 @@ function keywordTypeFromName(value: string): N.TsKeywordTypeType | typeof undefi } } -export default (superClass: Class): Class => class extends superClass { - tsIsIdentifier(): boolean { - // TODO: actually a bit more complex in TypeScript, but shouldn't matter. - // See https://github.com/Microsoft/TypeScript/issues/15008 - return this.match(tt.name); - } +export default (superClass: Class): Class => + class extends superClass { + tsIsIdentifier(): boolean { + // TODO: actually a bit more complex in TypeScript, but shouldn't matter. + // See https://github.com/Microsoft/TypeScript/issues/15008 + return this.match(tt.name); + } - tsNextTokenCanFollowModifier() { - // Note: TypeScript's implementation is much more complicated because - // more things are considered modifiers there. - // This implementation only handles modifiers not handled by babylon itself. And "static". - // TODO: Would be nice to avoid lookahead. Want a hasLineBreakUpNext() method... - this.next(); - return !this.hasPrecedingLineBreak() - && !this.match(tt.parenL) - && !this.match(tt.colon) - && !this.match(tt.eq) - && !this.match(tt.question); - } + tsNextTokenCanFollowModifier() { + // Note: TypeScript's implementation is much more complicated because + // more things are considered modifiers there. + // This implementation only handles modifiers not handled by babylon itself. And "static". + // TODO: Would be nice to avoid lookahead. Want a hasLineBreakUpNext() method... + this.next(); + return ( + !this.hasPrecedingLineBreak() && + !this.match(tt.parenL) && + !this.match(tt.colon) && + !this.match(tt.eq) && + !this.match(tt.question) + ); + } - /** Parses a modifier matching one the given modifier names. */ - tsParseModifier(allowedModifiers: T[]): ?T { - if (!this.match(tt.name)) { + /** Parses a modifier matching one the given modifier names. */ + tsParseModifier(allowedModifiers: T[]): ?T { + if (!this.match(tt.name)) { + return undefined; + } + + const modifier = this.state.value; + if ( + allowedModifiers.indexOf(modifier) !== -1 && + this.tsTryParse(this.tsNextTokenCanFollowModifier.bind(this)) + ) { + return modifier; + } return undefined; } - const modifier = this.state.value; - if (allowedModifiers.indexOf(modifier) !== -1 - && this.tsTryParse(this.tsNextTokenCanFollowModifier.bind(this))) { - return modifier; - } - return undefined; - } + tsIsListTerminator(kind: ParsingContext): boolean { + switch (kind) { + case "EnumMembers": + case "TypeMembers": + return this.match(tt.braceR); + case "HeritageClauseElement": + return this.match(tt.braceL); + case "TupleElementTypes": + return this.match(tt.bracketR); + case "TypeParametersOrArguments": + return this.isRelational(">"); + } - tsIsListTerminator(kind: ParsingContext): boolean { - switch (kind) { - case "EnumMembers": - case "TypeMembers": - return this.match(tt.braceR); - case "HeritageClauseElement": - return this.match(tt.braceL); - case "TupleElementTypes": - return this.match(tt.bracketR); - case "TypeParametersOrArguments": - return this.isRelational(">"); + throw new Error("Unreachable"); } - throw new Error("Unreachable"); - } - - tsParseList(kind: ParsingContext, parseElement: () => T): T[] { - const result: T[] = []; - while (!this.tsIsListTerminator(kind)) { - // Skipping "parseListElement" from the TS source since that's just for error handling. - result.push(parseElement()); + tsParseList(kind: ParsingContext, parseElement: () => T): T[] { + const result: T[] = []; + while (!this.tsIsListTerminator(kind)) { + // Skipping "parseListElement" from the TS source since that's just for error handling. + result.push(parseElement()); + } + return result; } - return result; - } - tsParseDelimitedList(kind: ParsingContext, parseElement: () => T): T[] { - return nonNull(this.tsParseDelimitedListWorker(kind, parseElement, /* expectSuccess */ true)); - } + tsParseDelimitedList( + kind: ParsingContext, + parseElement: () => T, + ): T[] { + return nonNull( + this.tsParseDelimitedListWorker( + kind, + parseElement, + /* expectSuccess */ true, + ), + ); + } - tsTryParseDelimitedList(kind: ParsingContext, parseElement: () => ?T): ?T[] { - return this.tsParseDelimitedListWorker(kind, parseElement, /* expectSuccess */ false); - } + tsTryParseDelimitedList( + kind: ParsingContext, + parseElement: () => ?T, + ): ?(T[]) { + return this.tsParseDelimitedListWorker( + kind, + parseElement, + /* expectSuccess */ false, + ); + } - /** + /** * If !expectSuccess, returns undefined instead of failing to parse. * If expectSuccess, parseElement should always return a defined value. */ - tsParseDelimitedListWorker( - kind: ParsingContext, - parseElement: () => ?T, - expectSuccess: boolean): ?T[] { + tsParseDelimitedListWorker( + kind: ParsingContext, + parseElement: () => ?T, + expectSuccess: boolean, + ): ?(T[]) { + const result = []; - const result = []; + while (true) { + if (this.tsIsListTerminator(kind)) { + break; + } - while (true) { - if (this.tsIsListTerminator(kind)) { - break; - } + const element = parseElement(); + if (element == null) { + return undefined; + } + result.push(element); - const element = parseElement(); - if (element == null) { + if (this.eat(tt.comma)) { + continue; + } + + if (this.tsIsListTerminator(kind)) { + break; + } + + if (expectSuccess) { + // This will fail with an error about a missing comma + this.expect(tt.comma); + } return undefined; } - result.push(element); - if (this.eat(tt.comma)) { - continue; - } - - if (this.tsIsListTerminator(kind)) { - break; - } - - if (expectSuccess) { - // This will fail with an error about a missing comma - this.expect(tt.comma); - } - return undefined; + return result; } - return result; - } + tsParseBracketedList( + kind: ParsingContext, + parseElement: () => T, + bracket: boolean, + skipFirstToken: boolean, + ): T[] { + if (!skipFirstToken) { + if (bracket) { + this.expect(tt.bracketL); + } else { + this.expectRelational("<"); + } + } - tsParseBracketedList( - kind: ParsingContext, - parseElement: () => T, - bracket: boolean, - skipFirstToken: boolean): T[] { + const result = this.tsParseDelimitedList(kind, parseElement); - if (!skipFirstToken) { if (bracket) { - this.expect(tt.bracketL); + this.expect(tt.bracketR); } else { - this.expectRelational("<"); + this.expectRelational(">"); } + + return result; } - const result = this.tsParseDelimitedList(kind, parseElement); - - if (bracket) { - this.expect(tt.bracketR); - } else { - this.expectRelational(">"); - } - - return result; - } - - tsParseEntityName(allowReservedWords: boolean): N.TsEntityName { - let entity: N.TsEntityName = this.parseIdentifier(); - while (this.eat(tt.dot)) { - const node: N.TsQualifiedName = this.startNodeAtNode(entity); - node.left = entity; - node.right = this.parseIdentifier(allowReservedWords); - entity = this.finishNode(node, "TSQualifiedName"); - } - return entity; - } - - tsParseTypeReference(): N.TsTypeReference { - const node: N.TsTypeReference = this.startNode(); - node.typeName = this.tsParseEntityName(/* allowReservedWords */ false); - if (!this.hasPrecedingLineBreak() && this.isRelational("<")) { - node.typeParameters = this.tsParseTypeArguments(); - } - return this.finishNode(node, "TSTypeReference"); - } - - tsParseThisTypePredicate(lhs: N.TsThisType): N.TsTypePredicate { - this.next(); - const node: N.TsTypePredicate = this.startNode(); - node.parameterName = lhs; - node.typeAnnotation = this.tsParseTypeAnnotation(/* eatColon */ false); - return this.finishNode(node, "TSTypePredicate"); - } - - tsParseThisTypeNode(): N.TsThisType { - const node: N.TsThisType = this.startNode(); - this.next(); - return this.finishNode(node, "TSThisType"); - } - - tsParseTypeQuery(): N.TsTypeQuery { - const node: N.TsTypeQuery = this.startNode(); - this.expect(tt._typeof); - node.exprName = this.tsParseEntityName(/* allowReservedWords */ true); - return this.finishNode(node, "TSTypeQuery"); - } - - tsParseTypeParameter(): N.TypeParameter { - const node: N.TypeParameter = this.startNode(); - node.name = this.parseIdentifierName(node.start); - if (this.eat(tt._extends)) { - node.constraint = this.tsParseType(); - } - - if (this.eat(tt.eq)) { - node.default = this.tsParseType(); - } - - return this.finishNode(node, "TypeParameter"); - } - - tsTryParseTypeParameters(): ?N.TypeParameterDeclaration { - if (this.eatRelational("<")) { - return this.tsParseTypeParameters(); - } - } - - tsParseTypeParameters(): N.TypeParameterDeclaration { - const node: N.TypeParameterDeclaration = this.startNode(); - node.params = this.tsParseBracketedList( - "TypeParametersOrArguments", - this.tsParseTypeParameter.bind(this), - /* bracket */ false, - /* skipFirstToken */ true); - return this.finishNode(node, "TypeParameterDeclaration"); - } - - // Note: In TypeScript implementation we must provide `yieldContext` and `awaitContext`, - // but here it's always false, because this is only used for types. - tsFillSignature(returnToken: TokenType, signature: N.TsSignatureDeclaration): void { - // Arrow fns *must* have return token (`=>`). Normal functions can omit it. - const returnTokenRequired = returnToken === tt.arrow; - signature.typeParameters = this.tsTryParseTypeParameters(); - this.expect(tt.parenL); - signature.parameters = this.tsParseBindingListForSignature(); - if (returnTokenRequired) { - signature.typeAnnotation = this.tsParseTypeOrTypePredicateAnnotation(returnToken); - } else if (this.match(returnToken)) { - signature.typeAnnotation = this.tsParseTypeOrTypePredicateAnnotation(returnToken); - } - } - - tsParseBindingListForSignature(): $ReadOnlyArray { - return this.parseBindingList(tt.parenR).map((pattern) => { - if (pattern.type !== "Identifier" && pattern.type !== "RestElement") { - throw this.unexpected(pattern.start, "Name in a signature must be an Identifier."); + tsParseEntityName(allowReservedWords: boolean): N.TsEntityName { + let entity: N.TsEntityName = this.parseIdentifier(); + while (this.eat(tt.dot)) { + const node: N.TsQualifiedName = this.startNodeAtNode(entity); + node.left = entity; + node.right = this.parseIdentifier(allowReservedWords); + entity = this.finishNode(node, "TSQualifiedName"); } - return pattern; - }); - } - - tsParseTypeMemberSemicolon(): void { - if (!this.eat(tt.comma)) { - this.semicolon(); - } - } - - tsParseSignatureMember( - kind: "TSCallSignatureDeclaration" | "TSConstructSignatureDeclaration", - ): N.TsCallSignatureDeclaration | N.TsConstructSignatureDeclaration { - const node: N.TsCallSignatureDeclaration | N.TsConstructSignatureDeclaration = this.startNode(); - if (kind === "TSConstructSignatureDeclaration") { - this.expect(tt._new); - } - this.tsFillSignature(tt.colon, node); - this.tsParseTypeMemberSemicolon(); - return this.finishNode(node, kind); - } - - tsIsUnambiguouslyIndexSignature() { - this.next(); // Skip '{' - return this.eat(tt.name) && this.match(tt.colon); - } - - tsTryParseIndexSignature(node: N.TsIndexSignature): ?N.TsIndexSignature { - if (!(this.match(tt.bracketL) && this.tsLookAhead(this.tsIsUnambiguouslyIndexSignature.bind(this)))) { - return undefined; + return entity; } - this.expect(tt.bracketL); - const id = this.parseIdentifier(); - this.expect(tt.colon); - id.typeAnnotation = this.tsParseTypeAnnotation(/* eatColon */ false); - this.expect(tt.bracketR); - node.parameters = [id]; - - const type = this.tsTryParseTypeAnnotation(); - if (type) node.typeAnnotation = type; - this.tsParseTypeMemberSemicolon(); - return this.finishNode(node, "TSIndexSignature"); - } - - tsParsePropertyOrMethodSignature( - node: N.TsPropertySignature | N.TsMethodSignature, - readonly: boolean): N.TsPropertySignature | N.TsMethodSignature { - - this.parsePropertyName(node); - if (this.eat(tt.question)) node.optional = true; - const nodeAny: any = node; - - if (!readonly && (this.match(tt.parenL) || this.isRelational("<"))) { - const method: N.TsMethodSignature = nodeAny; - this.tsFillSignature(tt.colon, method); - this.tsParseTypeMemberSemicolon(); - return this.finishNode(method, "TSMethodSignature"); - } else { - const property: N.TsPropertySignature = nodeAny; - if (readonly) property.readonly = true; - const type = this.tsTryParseTypeAnnotation(); - if (type) property.typeAnnotation = type; - this.tsParseTypeMemberSemicolon(); - return this.finishNode(property, "TSPropertySignature"); + tsParseTypeReference(): N.TsTypeReference { + const node: N.TsTypeReference = this.startNode(); + node.typeName = this.tsParseEntityName(/* allowReservedWords */ false); + if (!this.hasPrecedingLineBreak() && this.isRelational("<")) { + node.typeParameters = this.tsParseTypeArguments(); + } + return this.finishNode(node, "TSTypeReference"); } - } - tsParseTypeMember(): N.TsTypeElement { - if (this.match(tt.parenL) || this.isRelational("<")) { - return this.tsParseSignatureMember("TSCallSignatureDeclaration"); - } - if (this.match(tt._new) && this.tsLookAhead(this.tsIsStartOfConstructSignature.bind(this))) { - return this.tsParseSignatureMember("TSConstructSignatureDeclaration"); - } - // Instead of fullStart, we create a node here. - const node: any = this.startNode(); - const readonly = !!this.tsParseModifier(["readonly"]); - - const idx = this.tsTryParseIndexSignature(node); - if (idx) { - if (readonly) node.readonly = true; - return idx; - } - return this.tsParsePropertyOrMethodSignature(node, readonly); - } - - tsIsStartOfConstructSignature() { - this.next(); - return this.match(tt.parenL) || this.isRelational("<"); - } - - tsParseTypeLiteral(): N.TsTypeLiteral { - const node: N.TsTypeLiteral = this.startNode(); - node.members = this.tsParseObjectTypeMembers(); - return this.finishNode(node, "TSTypeLiteral"); - } - - tsParseObjectTypeMembers(): $ReadOnlyArray { - this.expect(tt.braceL); - const members = this.tsParseList("TypeMembers", this.tsParseTypeMember.bind(this)); - this.expect(tt.braceR); - return members; - } - - tsIsStartOfMappedType(): boolean { - this.next(); - if (this.isContextual("readonly")) { + tsParseThisTypePredicate(lhs: N.TsThisType): N.TsTypePredicate { this.next(); + const node: N.TsTypePredicate = this.startNode(); + node.parameterName = lhs; + node.typeAnnotation = this.tsParseTypeAnnotation(/* eatColon */ false); + return this.finishNode(node, "TSTypePredicate"); } - if (!this.match(tt.bracketL)) { - return false; + + tsParseThisTypeNode(): N.TsThisType { + const node: N.TsThisType = this.startNode(); + this.next(); + return this.finishNode(node, "TSThisType"); } - this.next(); - if (!this.tsIsIdentifier()) { - return false; + + tsParseTypeQuery(): N.TsTypeQuery { + const node: N.TsTypeQuery = this.startNode(); + this.expect(tt._typeof); + node.exprName = this.tsParseEntityName(/* allowReservedWords */ true); + return this.finishNode(node, "TSTypeQuery"); } - this.next(); - return this.match(tt._in); - } - tsParseMappedTypeParameter(): N.TypeParameter { - const node: N.TypeParameter = this.startNode(); - node.name = this.parseIdentifierName(node.start); - this.expect(tt._in); - node.constraint = this.tsParseType(); - return this.finishNode(node, "TypeParameter"); - } + tsParseTypeParameter(): N.TypeParameter { + const node: N.TypeParameter = this.startNode(); + node.name = this.parseIdentifierName(node.start); + if (this.eat(tt._extends)) { + node.constraint = this.tsParseType(); + } - tsParseMappedType(): N.TsMappedType { - const node: N.TsMappedType = this.startNode(); + if (this.eat(tt.eq)) { + node.default = this.tsParseType(); + } - this.expect(tt.braceL); - if (this.eatContextual("readonly")) { - node.readonly = true; + return this.finishNode(node, "TypeParameter"); } - this.expect(tt.bracketL); - node.typeParameter = this.tsParseMappedTypeParameter(); - this.expect(tt.bracketR); - if (this.eat(tt.question)) { - node.optional = true; + + tsTryParseTypeParameters(): ?N.TypeParameterDeclaration { + if (this.eatRelational("<")) { + return this.tsParseTypeParameters(); + } } - node.typeAnnotation = this.tsTryParseType(); - this.semicolon(); - this.expect(tt.braceR); - return this.finishNode(node, "TSMappedType"); - } - - tsParseTupleType(): N.TsTupleType { - const node: N.TsTupleType = this.startNode(); - node.elementTypes = this.tsParseBracketedList( - "TupleElementTypes", - this.tsParseType.bind(this), - /* bracket */true, - /* skipFirstToken */false); - return this.finishNode(node, "TSTupleType"); - } - - tsParseParenthesizedType(): N.TsParenthesizedType { - const node = this.startNode(); - this.expect(tt.parenL); - node.typeAnnotation = this.tsParseType(); - this.expect(tt.parenR); - return this.finishNode(node, "TSParenthesizedType"); - } - - tsParseFunctionOrConstructorType( - type: "TSFunctionType" | "TSConstructorType"): N.TsFunctionOrConstructorType { - - const node: N.TsFunctionOrConstructorType = this.startNode(); - if (type === "TSConstructorType") { - this.expect(tt._new); + tsParseTypeParameters(): N.TypeParameterDeclaration { + const node: N.TypeParameterDeclaration = this.startNode(); + node.params = this.tsParseBracketedList( + "TypeParametersOrArguments", + this.tsParseTypeParameter.bind(this), + /* bracket */ false, + /* skipFirstToken */ true, + ); + return this.finishNode(node, "TypeParameterDeclaration"); } - this.tsFillSignature(tt.arrow, node); - return this.finishNode(node, type); - } - tsParseLiteralTypeNode(): N.TsLiteralType { - const node: N.TsLiteralType = this.startNode(); - node.literal = (() => { + // Note: In TypeScript implementation we must provide `yieldContext` and `awaitContext`, + // but here it's always false, because this is only used for types. + tsFillSignature( + returnToken: TokenType, + signature: N.TsSignatureDeclaration, + ): void { + // Arrow fns *must* have return token (`=>`). Normal functions can omit it. + const returnTokenRequired = returnToken === tt.arrow; + signature.typeParameters = this.tsTryParseTypeParameters(); + this.expect(tt.parenL); + signature.parameters = this.tsParseBindingListForSignature(); + if (returnTokenRequired) { + signature.typeAnnotation = this.tsParseTypeOrTypePredicateAnnotation( + returnToken, + ); + } else if (this.match(returnToken)) { + signature.typeAnnotation = this.tsParseTypeOrTypePredicateAnnotation( + returnToken, + ); + } + } + + tsParseBindingListForSignature(): $ReadOnlyArray< + N.Identifier | N.RestElement, + > { + return this.parseBindingList(tt.parenR).map(pattern => { + if (pattern.type !== "Identifier" && pattern.type !== "RestElement") { + throw this.unexpected( + pattern.start, + "Name in a signature must be an Identifier.", + ); + } + return pattern; + }); + } + + tsParseTypeMemberSemicolon(): void { + if (!this.eat(tt.comma)) { + this.semicolon(); + } + } + + tsParseSignatureMember( + kind: "TSCallSignatureDeclaration" | "TSConstructSignatureDeclaration", + ): N.TsCallSignatureDeclaration | N.TsConstructSignatureDeclaration { + const node: + | N.TsCallSignatureDeclaration + | N.TsConstructSignatureDeclaration = this.startNode(); + if (kind === "TSConstructSignatureDeclaration") { + this.expect(tt._new); + } + this.tsFillSignature(tt.colon, node); + this.tsParseTypeMemberSemicolon(); + return this.finishNode(node, kind); + } + + tsIsUnambiguouslyIndexSignature() { + this.next(); // Skip '{' + return this.eat(tt.name) && this.match(tt.colon); + } + + tsTryParseIndexSignature(node: N.TsIndexSignature): ?N.TsIndexSignature { + if ( + !( + this.match(tt.bracketL) && + this.tsLookAhead(this.tsIsUnambiguouslyIndexSignature.bind(this)) + ) + ) { + return undefined; + } + + this.expect(tt.bracketL); + const id = this.parseIdentifier(); + this.expect(tt.colon); + id.typeAnnotation = this.tsParseTypeAnnotation(/* eatColon */ false); + this.expect(tt.bracketR); + node.parameters = [id]; + + const type = this.tsTryParseTypeAnnotation(); + if (type) node.typeAnnotation = type; + this.tsParseTypeMemberSemicolon(); + return this.finishNode(node, "TSIndexSignature"); + } + + tsParsePropertyOrMethodSignature( + node: N.TsPropertySignature | N.TsMethodSignature, + readonly: boolean, + ): N.TsPropertySignature | N.TsMethodSignature { + this.parsePropertyName(node); + if (this.eat(tt.question)) node.optional = true; + const nodeAny: any = node; + + if (!readonly && (this.match(tt.parenL) || this.isRelational("<"))) { + const method: N.TsMethodSignature = nodeAny; + this.tsFillSignature(tt.colon, method); + this.tsParseTypeMemberSemicolon(); + return this.finishNode(method, "TSMethodSignature"); + } else { + const property: N.TsPropertySignature = nodeAny; + if (readonly) property.readonly = true; + const type = this.tsTryParseTypeAnnotation(); + if (type) property.typeAnnotation = type; + this.tsParseTypeMemberSemicolon(); + return this.finishNode(property, "TSPropertySignature"); + } + } + + tsParseTypeMember(): N.TsTypeElement { + if (this.match(tt.parenL) || this.isRelational("<")) { + return this.tsParseSignatureMember("TSCallSignatureDeclaration"); + } + if ( + this.match(tt._new) && + this.tsLookAhead(this.tsIsStartOfConstructSignature.bind(this)) + ) { + return this.tsParseSignatureMember("TSConstructSignatureDeclaration"); + } + // Instead of fullStart, we create a node here. + const node: any = this.startNode(); + const readonly = !!this.tsParseModifier(["readonly"]); + + const idx = this.tsTryParseIndexSignature(node); + if (idx) { + if (readonly) node.readonly = true; + return idx; + } + return this.tsParsePropertyOrMethodSignature(node, readonly); + } + + tsIsStartOfConstructSignature() { + this.next(); + return this.match(tt.parenL) || this.isRelational("<"); + } + + tsParseTypeLiteral(): N.TsTypeLiteral { + const node: N.TsTypeLiteral = this.startNode(); + node.members = this.tsParseObjectTypeMembers(); + return this.finishNode(node, "TSTypeLiteral"); + } + + tsParseObjectTypeMembers(): $ReadOnlyArray { + this.expect(tt.braceL); + const members = this.tsParseList( + "TypeMembers", + this.tsParseTypeMember.bind(this), + ); + this.expect(tt.braceR); + return members; + } + + tsIsStartOfMappedType(): boolean { + this.next(); + if (this.isContextual("readonly")) { + this.next(); + } + if (!this.match(tt.bracketL)) { + return false; + } + this.next(); + if (!this.tsIsIdentifier()) { + return false; + } + this.next(); + return this.match(tt._in); + } + + tsParseMappedTypeParameter(): N.TypeParameter { + const node: N.TypeParameter = this.startNode(); + node.name = this.parseIdentifierName(node.start); + this.expect(tt._in); + node.constraint = this.tsParseType(); + return this.finishNode(node, "TypeParameter"); + } + + tsParseMappedType(): N.TsMappedType { + const node: N.TsMappedType = this.startNode(); + + this.expect(tt.braceL); + if (this.eatContextual("readonly")) { + node.readonly = true; + } + this.expect(tt.bracketL); + node.typeParameter = this.tsParseMappedTypeParameter(); + this.expect(tt.bracketR); + if (this.eat(tt.question)) { + node.optional = true; + } + node.typeAnnotation = this.tsTryParseType(); + this.semicolon(); + this.expect(tt.braceR); + + return this.finishNode(node, "TSMappedType"); + } + + tsParseTupleType(): N.TsTupleType { + const node: N.TsTupleType = this.startNode(); + node.elementTypes = this.tsParseBracketedList( + "TupleElementTypes", + this.tsParseType.bind(this), + /* bracket */ true, + /* skipFirstToken */ false, + ); + return this.finishNode(node, "TSTupleType"); + } + + tsParseParenthesizedType(): N.TsParenthesizedType { + const node = this.startNode(); + this.expect(tt.parenL); + node.typeAnnotation = this.tsParseType(); + this.expect(tt.parenR); + return this.finishNode(node, "TSParenthesizedType"); + } + + tsParseFunctionOrConstructorType( + type: "TSFunctionType" | "TSConstructorType", + ): N.TsFunctionOrConstructorType { + const node: N.TsFunctionOrConstructorType = this.startNode(); + if (type === "TSConstructorType") { + this.expect(tt._new); + } + this.tsFillSignature(tt.arrow, node); + return this.finishNode(node, type); + } + + tsParseLiteralTypeNode(): N.TsLiteralType { + const node: N.TsLiteralType = this.startNode(); + node.literal = (() => { + switch (this.state.type) { + case tt.num: + return this.parseLiteral(this.state.value, "NumericLiteral"); + case tt.string: + return this.parseLiteral(this.state.value, "StringLiteral"); + case tt._true: + case tt._false: + return this.parseBooleanLiteral(); + default: + throw this.unexpected(); + } + })(); + return this.finishNode(node, "TSLiteralType"); + } + + tsParseNonArrayType(): N.TsType { switch (this.state.type) { - case tt.num: - return this.parseLiteral(this.state.value, "NumericLiteral"); + case tt.name: + case tt._void: + case tt._null: + const type = this.match(tt._void) + ? "TSVoidKeyword" + : this.match(tt._null) + ? "TSNullKeyword" + : keywordTypeFromName(this.state.value); + if (type !== undefined && this.lookahead().type !== tt.dot) { + const node: N.TsKeywordType = this.startNode(); + this.next(); + return this.finishNode(node, type); + } + return this.tsParseTypeReference(); case tt.string: - return this.parseLiteral(this.state.value, "StringLiteral"); + case tt.num: case tt._true: case tt._false: - return this.parseBooleanLiteral(); - default: - throw this.unexpected(); - } - })(); - return this.finishNode(node, "TSLiteralType"); - } - - tsParseNonArrayType(): N.TsType { - switch (this.state.type) { - case tt.name: - case tt._void: - case tt._null: - const type = this.match(tt._void) - ? "TSVoidKeyword" - : this.match(tt._null) - ? "TSNullKeyword" - : keywordTypeFromName(this.state.value); - if (type !== undefined && this.lookahead().type !== tt.dot) { - const node: N.TsKeywordType = this.startNode(); - this.next(); - return this.finishNode(node, type); - } - return this.tsParseTypeReference(); - case tt.string: - case tt.num: - case tt._true: - case tt._false: - return this.tsParseLiteralTypeNode(); - case tt.plusMin: - if (this.state.value === "-") { - const node: N.TsLiteralType = this.startNode(); - this.next(); - if (!this.match(tt.num)) { - throw this.unexpected(); + return this.tsParseLiteralTypeNode(); + case tt.plusMin: + if (this.state.value === "-") { + const node: N.TsLiteralType = this.startNode(); + this.next(); + if (!this.match(tt.num)) { + throw this.unexpected(); + } + node.literal = this.parseLiteral( + -this.state.value, + "NumericLiteral", + node.start, + node.loc.start, + ); + return this.finishNode(node, "TSLiteralType"); } - node.literal = this.parseLiteral(-this.state.value, "NumericLiteral", node.start, node.loc.start); - return this.finishNode(node, "TSLiteralType"); - } - break; - case tt._this: - const thisKeyword = this.tsParseThisTypeNode(); - if (this.isContextual("is") && !this.hasPrecedingLineBreak()) { - return this.tsParseThisTypePredicate(thisKeyword); - } else { - return thisKeyword; - } - case tt._typeof: - return this.tsParseTypeQuery(); - case tt.braceL: - return this.tsLookAhead(this.tsIsStartOfMappedType.bind(this)) - ? this.tsParseMappedType() - : this.tsParseTypeLiteral(); - case tt.bracketL: - return this.tsParseTupleType(); - case tt.parenL: - return this.tsParseParenthesizedType(); - } - - throw this.unexpected(); - } - - tsParseArrayTypeOrHigher(): N.TsType { - let type = this.tsParseNonArrayType(); - while (!this.hasPrecedingLineBreak() && this.eat(tt.bracketL)) { - if (this.match(tt.bracketR)) { - const node: N.TsArrayType = this.startNodeAtNode(type); - node.elementType = type; - this.expect(tt.bracketR); - type = this.finishNode(node, "TSArrayType"); - } else { - const node: N.TsIndexedAccessType = this.startNodeAtNode(type); - node.objectType = type; - node.indexType = this.tsParseType(); - this.expect(tt.bracketR); - type = this.finishNode(node, "TSIndexedAccessType"); + break; + case tt._this: + const thisKeyword = this.tsParseThisTypeNode(); + if (this.isContextual("is") && !this.hasPrecedingLineBreak()) { + return this.tsParseThisTypePredicate(thisKeyword); + } else { + return thisKeyword; + } + case tt._typeof: + return this.tsParseTypeQuery(); + case tt.braceL: + return this.tsLookAhead(this.tsIsStartOfMappedType.bind(this)) + ? this.tsParseMappedType() + : this.tsParseTypeLiteral(); + case tt.bracketL: + return this.tsParseTupleType(); + case tt.parenL: + return this.tsParseParenthesizedType(); } - } - return type; - } - tsParseTypeOperator(operator: "keyof"): N.TsTypeOperator { - const node = this.startNode(); - this.expectContextual(operator); - node.operator = operator; - node.typeAnnotation = this.tsParseTypeOperatorOrHigher(); - return this.finishNode(node, "TSTypeOperator"); - } - - tsParseTypeOperatorOrHigher(): N.TsType { - if (this.isContextual("keyof")) { - return this.tsParseTypeOperator("keyof"); - } - return this.tsParseArrayTypeOrHigher(); - } - - tsParseUnionOrIntersectionType( - kind: "TSUnionType" | "TSIntersectionType", - parseConstituentType: () => N.TsType, - operator: TokenType): N.TsType { - - this.eat(operator); - let type = parseConstituentType(); - if (this.match(operator)) { - const types = [type]; - while (this.eat(operator)) { - types.push(parseConstituentType()); - } - const node: N.TsUnionType | N.TsIntersectionType = this.startNodeAtNode(type); - node.types = types; - type = this.finishNode(node, kind); - } - return type; - } - - tsParseIntersectionTypeOrHigher(): N.TsType { - return this.tsParseUnionOrIntersectionType( - "TSIntersectionType", - this.tsParseTypeOperatorOrHigher.bind(this), - tt.bitwiseAND); - } - - tsParseUnionTypeOrHigher() { - return this.tsParseUnionOrIntersectionType( - "TSUnionType", - this.tsParseIntersectionTypeOrHigher.bind(this), - tt.bitwiseOR); - } - - tsIsStartOfFunctionType() { - if (this.isRelational("<")) { - return true; - } - return this.match(tt.parenL) && this.tsLookAhead(this.tsIsUnambiguouslyStartOfFunctionType.bind(this)); - } - - tsSkipParameterStart(): boolean { - if (this.match(tt.name) || this.match(tt._this)) { - this.next(); - return true; - } - return false; - } - - tsIsUnambiguouslyStartOfFunctionType(): boolean { - this.next(); - if (this.match(tt.parenR) || this.match(tt.ellipsis)) { - // ( ) - // ( ... - return true; - } - if (this.tsSkipParameterStart()) { - if (this.match(tt.colon) || this.match(tt.comma) || this.match(tt.question) || this.match(tt.eq)) { - // ( xxx : - // ( xxx , - // ( xxx ? - // ( xxx = - return true; - } - if (this.match(tt.parenR)) { - this.next(); - if (this.match(tt.arrow)) { - // ( xxx ) => - return true; - } - } - } - return false; - } - - tsParseTypeOrTypePredicateAnnotation(returnToken: TokenType): N.TypeAnnotation { - const t: N.TypeAnnotation = this.startNode(); - this.expect(returnToken); - - const typePredicateVariable = this.tsIsIdentifier() - && this.tsTryParse(this.tsParseTypePredicatePrefix.bind(this)); - - if (!typePredicateVariable) { - return this.tsParseTypeAnnotation(/* eatColon */ false, t); - } - - const type = this.tsParseTypeAnnotation(/* eatColon */ false); - - const node: N.TsTypePredicate = this.startNodeAtNode(typePredicateVariable); - node.parameterName = typePredicateVariable; - node.typeAnnotation = type; - t.typeAnnotation = this.finishNode(node, "TSTypePredicate"); - return this.finishNode(t, "TypeAnnotation"); - } - - tsTryParseTypeOrTypePredicateAnnotation(): ?N.TypeAnnotation { - return this.match(tt.colon) ? this.tsParseTypeOrTypePredicateAnnotation(tt.colon) : undefined; - } - - tsTryParseTypeAnnotation(): ?N.TypeAnnotation { - return this.match(tt.colon) ? this.tsParseTypeAnnotation() : undefined; - } - - tsTryParseType(): ?N.TsType { - return this.eat(tt.colon) ? this.tsParseType() : undefined; - } - - tsParseTypePredicatePrefix(): ?N.Identifier { - const id = this.parseIdentifier(); - if (this.isContextual("is") && !this.hasPrecedingLineBreak()) { - this.next(); - return id; - } - } - - tsParseTypeAnnotation(eatColon = true, t: N.TypeAnnotation = this.startNode()): N.TypeAnnotation { - if (eatColon) this.expect(tt.colon); - t.typeAnnotation = this.tsParseType(); - return this.finishNode(t, "TypeAnnotation"); - } - - tsParseType(): N.TsType { - // Need to set `state.inType` so that we don't parse JSX in a type context. - const oldInType = this.state.inType; - this.state.inType = true; - try { - if (this.tsIsStartOfFunctionType()) { - return this.tsParseFunctionOrConstructorType("TSFunctionType"); - } - if (this.match(tt._new)) { // As in `new () => Date` - return this.tsParseFunctionOrConstructorType("TSConstructorType"); - } - return this.tsParseUnionTypeOrHigher(); - } finally { - this.state.inType = oldInType; - } - } - - tsParseTypeAssertion(): N.TsTypeAssertion { - const node: N.TsTypeAssertion = this.startNode(); - node.typeAnnotation = this.tsParseType(); - this.expectRelational(">"); - node.expression = this.parseMaybeUnary(); - return this.finishNode(node, "TSTypeAssertion"); - } - - tsTryParseTypeArgumentsInExpression(): ?N.TypeParameterInstantiation { - return this.tsTryParseAndCatch(() => { - const res: N.TypeParameterInstantiation = this.startNode(); - this.expectRelational("<"); - const typeArguments = this.tsParseDelimitedList( - "TypeParametersOrArguments", - this.tsParseType.bind(this)); - this.expectRelational(">"); - res.params = typeArguments; - this.finishNode(res, "TypeParameterInstantiation"); - this.expect(tt.parenL); - return res; - }); - } - - tsParseHeritageClause(): $ReadOnlyArray { - return this.tsParseDelimitedList( - "HeritageClauseElement", - this.tsParseExpressionWithTypeArguments.bind(this)); - } - - tsParseExpressionWithTypeArguments(): N.TsExpressionWithTypeArguments { - const node: N.TsExpressionWithTypeArguments = this.startNode(); - // Note: TS uses parseLeftHandSideExpressionOrHigher, - // then has grammar errors later if it's not an EntityName. - node.expression = this.tsParseEntityName(/* allowReservedWords */ false); - if (this.isRelational("<")) { - node.typeParameters = this.tsParseTypeArguments(); - } - - return this.finishNode(node, "TSExpressionWithTypeArguments"); - } - - tsParseInterfaceDeclaration(node: N.TsInterfaceDeclaration): N.TsInterfaceDeclaration { - node.id = this.parseIdentifier(); - node.typeParameters = this.tsTryParseTypeParameters(); - if (this.eat(tt._extends)) { - node.extends = this.tsParseHeritageClause(); - } - const body: N.TSInterfaceBody = this.startNode(); - body.body = this.tsParseObjectTypeMembers(); - node.body = this.finishNode(body, "TSInterfaceBody"); - return this.finishNode(node, "TSInterfaceDeclaration"); - } - - tsParseTypeAliasDeclaration(node: N.TsTypeAliasDeclaration): N.TsTypeAliasDeclaration { - node.id = this.parseIdentifier(); - node.typeParameters = this.tsTryParseTypeParameters(); - this.expect(tt.eq); - node.typeAnnotation = this.tsParseType(); - this.semicolon(); - return this.finishNode(node, "TSTypeAliasDeclaration"); - } - - tsParseEnumMember(): N.TsEnumMember { - const node: N.TsEnumMember = this.startNode(); - // Computed property names are grammar errors in an enum, so accept just string literal or identifier. - node.id = this.match(tt.string) - ? this.parseLiteral(this.state.value, "StringLiteral") - : this.parseIdentifier(/* liberal */ true); - if (this.eat(tt.eq)) { - node.initializer = this.parseMaybeAssign(); - } - return this.finishNode(node, "TSEnumMember"); - } - - tsParseEnumDeclaration(node: N.TsEnumDeclaration, isConst: boolean): N.TsEnumDeclaration { - if (isConst) node.const = true; - node.id = this.parseIdentifier(); - this.expect(tt.braceL); - node.members = this.tsParseDelimitedList("EnumMembers", this.tsParseEnumMember.bind(this)); - this.expect(tt.braceR); - return this.finishNode(node, "TSEnumDeclaration"); - } - - tsParseModuleBlock(): N.TsModuleBlock { - const node: N.TsModuleBlock = this.startNode(); - this.expect(tt.braceL); - // Inside of a module block is considered "top-level", meaning it can have imports and exports. - this.parseBlockOrModuleBlockBody( - node.body = [], /* directives */ undefined, /* topLevel */ true, /* end */ tt.braceR); - return this.finishNode(node, "TSModuleBlock"); - } - - tsParseModuleOrNamespaceDeclaration(node: N.TsModuleDeclaration): N.TsModuleDeclaration { - node.id = this.parseIdentifier(); - if (this.eat(tt.dot)) { - const inner = this.startNode(); - this.tsParseModuleOrNamespaceDeclaration(inner); - node.body = inner; - } else { - node.body = this.tsParseModuleBlock(); - } - return this.finishNode(node, "TSModuleDeclaration"); - } - - tsParseAmbientExternalModuleDeclaration(node: N.TsModuleDeclaration): N.TsModuleDeclaration { - if (this.isContextual("global")) { - node.global = true; - node.id = this.parseIdentifier(); - } else if (this.match(tt.string)) { - node.id = this.parseLiteral(this.state.value, "StringLiteral"); - } else { - this.unexpected(); - } - - if (this.match(tt.braceL)) { - node.body = this.tsParseModuleBlock(); - } else { - this.semicolon(); - } - - return this.finishNode(node, "TSModuleDeclaration"); - } - - tsParseImportEqualsDeclaration( - node: N.TsImportEqualsDeclaration, - isExport?: boolean): N.TsImportEqualsDeclaration { - - node.isExport = isExport || false; - node.id = this.parseIdentifier(); - this.expect(tt.eq); - node.moduleReference = this.tsParseModuleReference(); - this.semicolon(); - return this.finishNode(node, "TSImportEqualsDeclaration"); - } - - tsIsExternalModuleReference(): boolean { - return this.isContextual("require") && this.lookahead().type === tt.parenL; - } - - tsParseModuleReference(): N.TsModuleReference { - return this.tsIsExternalModuleReference() - ? this.tsParseExternalModuleReference() - : this.tsParseEntityName(/* allowReservedWords */ false); - } - - tsParseExternalModuleReference(): N.TsExternalModuleReference { - const node: N.TsExternalModuleReference = this.startNode(); - this.expectContextual("require"); - this.expect(tt.parenL); - if (!this.match(tt.string)) { throw this.unexpected(); } - node.expression = this.parseLiteral(this.state.value, "StringLiteral"); - this.expect(tt.parenR); - return this.finishNode(node, "TSExternalModuleReference"); - } - // Utilities + tsParseArrayTypeOrHigher(): N.TsType { + let type = this.tsParseNonArrayType(); + while (!this.hasPrecedingLineBreak() && this.eat(tt.bracketL)) { + if (this.match(tt.bracketR)) { + const node: N.TsArrayType = this.startNodeAtNode(type); + node.elementType = type; + this.expect(tt.bracketR); + type = this.finishNode(node, "TSArrayType"); + } else { + const node: N.TsIndexedAccessType = this.startNodeAtNode(type); + node.objectType = type; + node.indexType = this.tsParseType(); + this.expect(tt.bracketR); + type = this.finishNode(node, "TSIndexedAccessType"); + } + } + return type; + } - tsLookAhead(f: () => T): T { - const state = this.state.clone(); - const res = f(); - this.state = state; - return res; - } + tsParseTypeOperator(operator: "keyof"): N.TsTypeOperator { + const node = this.startNode(); + this.expectContextual(operator); + node.operator = operator; + node.typeAnnotation = this.tsParseTypeOperatorOrHigher(); + return this.finishNode(node, "TSTypeOperator"); + } - tsTryParseAndCatch(f: () => T): ?T { - const state = this.state.clone(); - try { - return f(); - } catch (e) { - if (e instanceof SyntaxError) { + tsParseTypeOperatorOrHigher(): N.TsType { + if (this.isContextual("keyof")) { + return this.tsParseTypeOperator("keyof"); + } + return this.tsParseArrayTypeOrHigher(); + } + + tsParseUnionOrIntersectionType( + kind: "TSUnionType" | "TSIntersectionType", + parseConstituentType: () => N.TsType, + operator: TokenType, + ): N.TsType { + this.eat(operator); + let type = parseConstituentType(); + if (this.match(operator)) { + const types = [type]; + while (this.eat(operator)) { + types.push(parseConstituentType()); + } + const node: N.TsUnionType | N.TsIntersectionType = this.startNodeAtNode( + type, + ); + node.types = types; + type = this.finishNode(node, kind); + } + return type; + } + + tsParseIntersectionTypeOrHigher(): N.TsType { + return this.tsParseUnionOrIntersectionType( + "TSIntersectionType", + this.tsParseTypeOperatorOrHigher.bind(this), + tt.bitwiseAND, + ); + } + + tsParseUnionTypeOrHigher() { + return this.tsParseUnionOrIntersectionType( + "TSUnionType", + this.tsParseIntersectionTypeOrHigher.bind(this), + tt.bitwiseOR, + ); + } + + tsIsStartOfFunctionType() { + if (this.isRelational("<")) { + return true; + } + return ( + this.match(tt.parenL) && + this.tsLookAhead(this.tsIsUnambiguouslyStartOfFunctionType.bind(this)) + ); + } + + tsSkipParameterStart(): boolean { + if (this.match(tt.name) || this.match(tt._this)) { + this.next(); + return true; + } + return false; + } + + tsIsUnambiguouslyStartOfFunctionType(): boolean { + this.next(); + if (this.match(tt.parenR) || this.match(tt.ellipsis)) { + // ( ) + // ( ... + return true; + } + if (this.tsSkipParameterStart()) { + if ( + this.match(tt.colon) || + this.match(tt.comma) || + this.match(tt.question) || + this.match(tt.eq) + ) { + // ( xxx : + // ( xxx , + // ( xxx ? + // ( xxx = + return true; + } + if (this.match(tt.parenR)) { + this.next(); + if (this.match(tt.arrow)) { + // ( xxx ) => + return true; + } + } + } + return false; + } + + tsParseTypeOrTypePredicateAnnotation( + returnToken: TokenType, + ): N.TypeAnnotation { + const t: N.TypeAnnotation = this.startNode(); + this.expect(returnToken); + + const typePredicateVariable = + this.tsIsIdentifier() && + this.tsTryParse(this.tsParseTypePredicatePrefix.bind(this)); + + if (!typePredicateVariable) { + return this.tsParseTypeAnnotation(/* eatColon */ false, t); + } + + const type = this.tsParseTypeAnnotation(/* eatColon */ false); + + const node: N.TsTypePredicate = this.startNodeAtNode( + typePredicateVariable, + ); + node.parameterName = typePredicateVariable; + node.typeAnnotation = type; + t.typeAnnotation = this.finishNode(node, "TSTypePredicate"); + return this.finishNode(t, "TypeAnnotation"); + } + + tsTryParseTypeOrTypePredicateAnnotation(): ?N.TypeAnnotation { + return this.match(tt.colon) + ? this.tsParseTypeOrTypePredicateAnnotation(tt.colon) + : undefined; + } + + tsTryParseTypeAnnotation(): ?N.TypeAnnotation { + return this.match(tt.colon) ? this.tsParseTypeAnnotation() : undefined; + } + + tsTryParseType(): ?N.TsType { + return this.eat(tt.colon) ? this.tsParseType() : undefined; + } + + tsParseTypePredicatePrefix(): ?N.Identifier { + const id = this.parseIdentifier(); + if (this.isContextual("is") && !this.hasPrecedingLineBreak()) { + this.next(); + return id; + } + } + + tsParseTypeAnnotation( + eatColon = true, + t: N.TypeAnnotation = this.startNode(), + ): N.TypeAnnotation { + if (eatColon) this.expect(tt.colon); + t.typeAnnotation = this.tsParseType(); + return this.finishNode(t, "TypeAnnotation"); + } + + tsParseType(): N.TsType { + // Need to set `state.inType` so that we don't parse JSX in a type context. + const oldInType = this.state.inType; + this.state.inType = true; + try { + if (this.tsIsStartOfFunctionType()) { + return this.tsParseFunctionOrConstructorType("TSFunctionType"); + } + if (this.match(tt._new)) { + // As in `new () => Date` + return this.tsParseFunctionOrConstructorType("TSConstructorType"); + } + return this.tsParseUnionTypeOrHigher(); + } finally { + this.state.inType = oldInType; + } + } + + tsParseTypeAssertion(): N.TsTypeAssertion { + const node: N.TsTypeAssertion = this.startNode(); + node.typeAnnotation = this.tsParseType(); + this.expectRelational(">"); + node.expression = this.parseMaybeUnary(); + return this.finishNode(node, "TSTypeAssertion"); + } + + tsTryParseTypeArgumentsInExpression(): ?N.TypeParameterInstantiation { + return this.tsTryParseAndCatch(() => { + const res: N.TypeParameterInstantiation = this.startNode(); + this.expectRelational("<"); + const typeArguments = this.tsParseDelimitedList( + "TypeParametersOrArguments", + this.tsParseType.bind(this), + ); + this.expectRelational(">"); + res.params = typeArguments; + this.finishNode(res, "TypeParameterInstantiation"); + this.expect(tt.parenL); + return res; + }); + } + + tsParseHeritageClause(): $ReadOnlyArray { + return this.tsParseDelimitedList( + "HeritageClauseElement", + this.tsParseExpressionWithTypeArguments.bind(this), + ); + } + + tsParseExpressionWithTypeArguments(): N.TsExpressionWithTypeArguments { + const node: N.TsExpressionWithTypeArguments = this.startNode(); + // Note: TS uses parseLeftHandSideExpressionOrHigher, + // then has grammar errors later if it's not an EntityName. + node.expression = this.tsParseEntityName(/* allowReservedWords */ false); + if (this.isRelational("<")) { + node.typeParameters = this.tsParseTypeArguments(); + } + + return this.finishNode(node, "TSExpressionWithTypeArguments"); + } + + tsParseInterfaceDeclaration( + node: N.TsInterfaceDeclaration, + ): N.TsInterfaceDeclaration { + node.id = this.parseIdentifier(); + node.typeParameters = this.tsTryParseTypeParameters(); + if (this.eat(tt._extends)) { + node.extends = this.tsParseHeritageClause(); + } + const body: N.TSInterfaceBody = this.startNode(); + body.body = this.tsParseObjectTypeMembers(); + node.body = this.finishNode(body, "TSInterfaceBody"); + return this.finishNode(node, "TSInterfaceDeclaration"); + } + + tsParseTypeAliasDeclaration( + node: N.TsTypeAliasDeclaration, + ): N.TsTypeAliasDeclaration { + node.id = this.parseIdentifier(); + node.typeParameters = this.tsTryParseTypeParameters(); + this.expect(tt.eq); + node.typeAnnotation = this.tsParseType(); + this.semicolon(); + return this.finishNode(node, "TSTypeAliasDeclaration"); + } + + tsParseEnumMember(): N.TsEnumMember { + const node: N.TsEnumMember = this.startNode(); + // Computed property names are grammar errors in an enum, so accept just string literal or identifier. + node.id = this.match(tt.string) + ? this.parseLiteral(this.state.value, "StringLiteral") + : this.parseIdentifier(/* liberal */ true); + if (this.eat(tt.eq)) { + node.initializer = this.parseMaybeAssign(); + } + return this.finishNode(node, "TSEnumMember"); + } + + tsParseEnumDeclaration( + node: N.TsEnumDeclaration, + isConst: boolean, + ): N.TsEnumDeclaration { + if (isConst) node.const = true; + node.id = this.parseIdentifier(); + this.expect(tt.braceL); + node.members = this.tsParseDelimitedList( + "EnumMembers", + this.tsParseEnumMember.bind(this), + ); + this.expect(tt.braceR); + return this.finishNode(node, "TSEnumDeclaration"); + } + + tsParseModuleBlock(): N.TsModuleBlock { + const node: N.TsModuleBlock = this.startNode(); + this.expect(tt.braceL); + // Inside of a module block is considered "top-level", meaning it can have imports and exports. + this.parseBlockOrModuleBlockBody( + (node.body = []), + /* directives */ undefined, + /* topLevel */ true, + /* end */ tt.braceR, + ); + return this.finishNode(node, "TSModuleBlock"); + } + + tsParseModuleOrNamespaceDeclaration( + node: N.TsModuleDeclaration, + ): N.TsModuleDeclaration { + node.id = this.parseIdentifier(); + if (this.eat(tt.dot)) { + const inner = this.startNode(); + this.tsParseModuleOrNamespaceDeclaration(inner); + node.body = inner; + } else { + node.body = this.tsParseModuleBlock(); + } + return this.finishNode(node, "TSModuleDeclaration"); + } + + tsParseAmbientExternalModuleDeclaration( + node: N.TsModuleDeclaration, + ): N.TsModuleDeclaration { + if (this.isContextual("global")) { + node.global = true; + node.id = this.parseIdentifier(); + } else if (this.match(tt.string)) { + node.id = this.parseLiteral(this.state.value, "StringLiteral"); + } else { + this.unexpected(); + } + + if (this.match(tt.braceL)) { + node.body = this.tsParseModuleBlock(); + } else { + this.semicolon(); + } + + return this.finishNode(node, "TSModuleDeclaration"); + } + + tsParseImportEqualsDeclaration( + node: N.TsImportEqualsDeclaration, + isExport?: boolean, + ): N.TsImportEqualsDeclaration { + node.isExport = isExport || false; + node.id = this.parseIdentifier(); + this.expect(tt.eq); + node.moduleReference = this.tsParseModuleReference(); + this.semicolon(); + return this.finishNode(node, "TSImportEqualsDeclaration"); + } + + tsIsExternalModuleReference(): boolean { + return ( + this.isContextual("require") && this.lookahead().type === tt.parenL + ); + } + + tsParseModuleReference(): N.TsModuleReference { + return this.tsIsExternalModuleReference() + ? this.tsParseExternalModuleReference() + : this.tsParseEntityName(/* allowReservedWords */ false); + } + + tsParseExternalModuleReference(): N.TsExternalModuleReference { + const node: N.TsExternalModuleReference = this.startNode(); + this.expectContextual("require"); + this.expect(tt.parenL); + if (!this.match(tt.string)) { + throw this.unexpected(); + } + node.expression = this.parseLiteral(this.state.value, "StringLiteral"); + this.expect(tt.parenR); + return this.finishNode(node, "TSExternalModuleReference"); + } + + // Utilities + + tsLookAhead(f: () => T): T { + const state = this.state.clone(); + const res = f(); + this.state = state; + return res; + } + + tsTryParseAndCatch(f: () => T): ?T { + const state = this.state.clone(); + try { + return f(); + } catch (e) { + if (e instanceof SyntaxError) { + this.state = state; + return undefined; + } + throw e; + } + } + + tsTryParse(f: () => ?T): ?T { + const state = this.state.clone(); + const result = f(); + if (result !== undefined && result !== false) { + return result; + } else { this.state = state; return undefined; } - throw e; } - } - tsTryParse(f: () => ?T): ?T { - const state = this.state.clone(); - const result = f(); - if (result !== undefined && result !== false) { - return result; - } else { - this.state = state; - return undefined; - } - } + nodeWithSamePosition(original: N.Node, type: string): T { + const node = this.startNodeAtNode(original); + node.type = type; + node.end = original.end; + node.loc.end = original.loc.end; - nodeWithSamePosition(original: N.Node, type: string): T { - const node = this.startNodeAtNode(original); - node.type = type; - node.end = original.end; - node.loc.end = original.loc.end; + if (original.leadingComments) + node.leadingComments = original.leadingComments; + if (original.trailingComments) + node.trailingComments = original.trailingComments; + if (original.innerComments) node.innerComments = original.innerComments; - if (original.leadingComments) node.leadingComments = original.leadingComments; - if (original.trailingComments) node.trailingComments = original.trailingComments; - if (original.innerComments) node.innerComments = original.innerComments; - - return node; - } - - tsTryParseDeclare(nany: any): ?N.Declaration { - switch (this.state.type) { - case tt._function: - this.next(); - return this.parseFunction(nany, /* isStatement */ true); - case tt._class: - return this.parseClass(nany, /* isStatement */ true, /* optionalId */ false); - case tt._const: - if (this.match(tt._const) && this.lookaheadIsContextual("enum")) { - // `const enum = 0;` not allowed because "enum" is a strict mode reserved word. - this.expect(tt._const); - this.expectContextual("enum"); - return this.tsParseEnumDeclaration(nany, /* isConst */ true); - } - // falls through - case tt._var: case tt._let: - return this.parseVarStatement(nany, this.state.type); - case tt.name: - const value = this.state.value; - if (value === "global") { - return this.tsParseAmbientExternalModuleDeclaration(nany); - } else { - return this.tsParseDeclaration(nany, value, /* next */ true); - } - } - } - - lookaheadIsContextual(name: string): boolean { - const l = this.lookahead(); - return l.type === tt.name && l.value === name; - } - - // Note: this won't be called unless the keyword is allowed in `shouldParseExportDeclaration`. - tsTryParseExportDeclaration(): ?N.Declaration { - return this.tsParseDeclaration(this.startNode(), this.state.value, /* next */ true); - } - - tsParseExpressionStatement(node: any, expr: N.Identifier): ?N.Declaration { - switch (expr.name) { - case "declare": - const declaration = this.tsTryParseDeclare(node); - if (declaration) { - declaration.declare = true; - return declaration; - } - break; - - case "global": - // `global { }` (with no `declare`) may appear inside an ambient module declaration. - // Would like to use tsParseAmbientExternalModuleDeclaration here, but already ran past "global". - if (this.match(tt.braceL)) { - const mod: N.TsModuleDeclaration = node; - mod.global = true; - mod.id = expr; - mod.body = this.tsParseModuleBlock(); - return this.finishNode(mod, "TSModuleDeclaration"); - } - break; - - default: - return this.tsParseDeclaration(node, expr.name, /* next */ false); - } - } - - // Common to tsTryParseDeclare, tsTryParseExportDeclaration, and tsParseExpressionStatement. - tsParseDeclaration(node: any, value: string, next: boolean): ?N.Declaration { - switch (value) { - case "abstract": - if (next || this.match(tt._class)) { - const cls: N.ClassDeclaration = node; - cls.abstract = true; - if (next) this.next(); - return this.parseClass(cls, /* isStatement */ true, /* optionalId */ false); - } - break; - - case "enum": - if (next || this.match(tt.name)) { - if (next) this.next(); - return this.tsParseEnumDeclaration(node, /* isConst */ false); - } - break; - - case "interface": - if (next || this.match(tt.name)) { - if (next) this.next(); - return this.tsParseInterfaceDeclaration(node); - } - break; - - case "module": - if (next) this.next(); - if (this.match(tt.string)) { - return this.tsParseAmbientExternalModuleDeclaration(node); - } else if (next || this.match(tt.name)) { - return this.tsParseModuleOrNamespaceDeclaration(node); - } - break; - - case "namespace": - if (next || this.match(tt.name)) { - if (next) this.next(); - return this.tsParseModuleOrNamespaceDeclaration(node); - } - break; - - case "type": - if (next || this.match(tt.name)) { - if (next) this.next(); - return this.tsParseTypeAliasDeclaration(node); - } - break; - } - } - - tsTryParseGenericAsyncArrowFunction(startPos: number, startLoc: Position): ?N.ArrowFunctionExpression { - const res: ?N.ArrowFunctionExpression = this.tsTryParseAndCatch(() => { - const node: N.ArrowFunctionExpression = this.startNodeAt(startPos, startLoc); - this.expectRelational("<"); - node.typeParameters = this.tsParseTypeParameters(); - // Don't use overloaded parseFunctionParams which would look for "<" again. - super.parseFunctionParams(node); - node.returnType = this.tsTryParseTypeOrTypePredicateAnnotation(); - this.expect(tt.arrow); return node; - }); - - if (!res) { - return undefined; } - res.id = null; - res.generator = false; - res.expression = true; // May be set again by parseFunctionBody. - res.async = true; - this.parseFunctionBody(res, true); - return this.finishNode(res, "ArrowFunctionExpression"); - } - - tsParseTypeArguments(): N.TypeParameterInstantiation { - const node = this.startNode(); - this.expectRelational("<"); - node.params = this.tsParseDelimitedList("TypeParametersOrArguments", this.tsParseType.bind(this)); - this.expectRelational(">"); - return this.finishNode(node, "TypeParameterInstantiation"); - } - - // ====================================================== - // OVERRIDES - // ====================================================== - - parseAssignableListItem( - allowModifiers: ?boolean, - decorators: N.Decorator[]): N.Pattern | N.TSParameterProperty { - let accessibility: ?N.Accessibility; - let readonly = false; - if (allowModifiers) { - accessibility = this.parseAccessModifier(); - readonly = !!this.tsParseModifier(["readonly"]); - } - - const left = this.parseMaybeDefault(); - this.parseAssignableListItemTypes(left); - const elt = this.parseMaybeDefault(left.start, left.loc.start, left); - if (accessibility || readonly) { - const pp: N.TSParameterProperty = this.startNodeAtNode(elt); - if (decorators.length) { - pp.decorators = decorators; + tsTryParseDeclare(nany: any): ?N.Declaration { + switch (this.state.type) { + case tt._function: + this.next(); + return this.parseFunction(nany, /* isStatement */ true); + case tt._class: + return this.parseClass( + nany, + /* isStatement */ true, + /* optionalId */ false, + ); + case tt._const: + if (this.match(tt._const) && this.lookaheadIsContextual("enum")) { + // `const enum = 0;` not allowed because "enum" is a strict mode reserved word. + this.expect(tt._const); + this.expectContextual("enum"); + return this.tsParseEnumDeclaration(nany, /* isConst */ true); + } + // falls through + case tt._var: + case tt._let: + return this.parseVarStatement(nany, this.state.type); + case tt.name: + const value = this.state.value; + if (value === "global") { + return this.tsParseAmbientExternalModuleDeclaration(nany); + } else { + return this.tsParseDeclaration(nany, value, /* next */ true); + } } - if (accessibility) pp.accessibility = accessibility; - if (readonly) pp.readonly = readonly; - if (elt.type !== "Identifier" && elt.type !== "AssignmentPattern") { - throw this.raise(pp.start, "A parameter property may not be declared using a binding pattern."); + } + + lookaheadIsContextual(name: string): boolean { + const l = this.lookahead(); + return l.type === tt.name && l.value === name; + } + + // Note: this won't be called unless the keyword is allowed in `shouldParseExportDeclaration`. + tsTryParseExportDeclaration(): ?N.Declaration { + return this.tsParseDeclaration( + this.startNode(), + this.state.value, + /* next */ true, + ); + } + + tsParseExpressionStatement(node: any, expr: N.Identifier): ?N.Declaration { + switch (expr.name) { + case "declare": + const declaration = this.tsTryParseDeclare(node); + if (declaration) { + declaration.declare = true; + return declaration; + } + break; + + case "global": + // `global { }` (with no `declare`) may appear inside an ambient module declaration. + // Would like to use tsParseAmbientExternalModuleDeclaration here, but already ran past "global". + if (this.match(tt.braceL)) { + const mod: N.TsModuleDeclaration = node; + mod.global = true; + mod.id = expr; + mod.body = this.tsParseModuleBlock(); + return this.finishNode(mod, "TSModuleDeclaration"); + } + break; + + default: + return this.tsParseDeclaration(node, expr.name, /* next */ false); } - pp.parameter = elt; - return this.finishNode(pp, "TSParameterProperty"); - } else { - if (decorators.length) { - left.decorators = decorators; + } + + // Common to tsTryParseDeclare, tsTryParseExportDeclaration, and tsParseExpressionStatement. + tsParseDeclaration( + node: any, + value: string, + next: boolean, + ): ?N.Declaration { + switch (value) { + case "abstract": + if (next || this.match(tt._class)) { + const cls: N.ClassDeclaration = node; + cls.abstract = true; + if (next) this.next(); + return this.parseClass( + cls, + /* isStatement */ true, + /* optionalId */ false, + ); + } + break; + + case "enum": + if (next || this.match(tt.name)) { + if (next) this.next(); + return this.tsParseEnumDeclaration(node, /* isConst */ false); + } + break; + + case "interface": + if (next || this.match(tt.name)) { + if (next) this.next(); + return this.tsParseInterfaceDeclaration(node); + } + break; + + case "module": + if (next) this.next(); + if (this.match(tt.string)) { + return this.tsParseAmbientExternalModuleDeclaration(node); + } else if (next || this.match(tt.name)) { + return this.tsParseModuleOrNamespaceDeclaration(node); + } + break; + + case "namespace": + if (next || this.match(tt.name)) { + if (next) this.next(); + return this.tsParseModuleOrNamespaceDeclaration(node); + } + break; + + case "type": + if (next || this.match(tt.name)) { + if (next) this.next(); + return this.tsParseTypeAliasDeclaration(node); + } + break; } - return elt; - } - } - - parseFunctionBodyAndFinish( - node: N.BodilessFunctionOrMethodBase, - type: string, - allowExpressionBody?: boolean): void { - // For arrow functions, `parseArrow` handles the return type itself. - if (!allowExpressionBody && this.match(tt.colon)) { - node.returnType = this.tsParseTypeOrTypePredicateAnnotation(tt.colon); } - const bodilessType = type === "FunctionDeclaration" - ? "TSDeclareFunction" - : type === "ClassMethod" - ? "TSDeclareMethod" - : undefined; - if (bodilessType && !this.match(tt.braceL) && this.isLineTerminator()) { - this.finishNode(node, bodilessType); - return; + tsTryParseGenericAsyncArrowFunction( + startPos: number, + startLoc: Position, + ): ?N.ArrowFunctionExpression { + const res: ?N.ArrowFunctionExpression = this.tsTryParseAndCatch(() => { + const node: N.ArrowFunctionExpression = this.startNodeAt( + startPos, + startLoc, + ); + this.expectRelational("<"); + node.typeParameters = this.tsParseTypeParameters(); + // Don't use overloaded parseFunctionParams which would look for "<" again. + super.parseFunctionParams(node); + node.returnType = this.tsTryParseTypeOrTypePredicateAnnotation(); + this.expect(tt.arrow); + return node; + }); + + if (!res) { + return undefined; + } + + res.id = null; + res.generator = false; + res.expression = true; // May be set again by parseFunctionBody. + res.async = true; + this.parseFunctionBody(res, true); + return this.finishNode(res, "ArrowFunctionExpression"); } - super.parseFunctionBodyAndFinish(node, type, allowExpressionBody); - } - - parseSubscript( - base: N.Expression, - startPos: number, - startLoc: Position, - noCalls: ?boolean, - state: { stop: boolean }): N.Expression { - - if (this.eat(tt.bang)) { - const nonNullExpression: N.TsNonNullExpression = this.startNodeAt(startPos, startLoc); - nonNullExpression.expression = base; - return this.finishNode(nonNullExpression, "TSNonNullExpression"); + tsParseTypeArguments(): N.TypeParameterInstantiation { + const node = this.startNode(); + this.expectRelational("<"); + node.params = this.tsParseDelimitedList( + "TypeParametersOrArguments", + this.tsParseType.bind(this), + ); + this.expectRelational(">"); + return this.finishNode(node, "TypeParameterInstantiation"); } - if (!noCalls && this.isRelational("<")) { - if (this.atPossibleAsync(base)) { - // Almost certainly this is a generic async function `async () => ... - // But it might be a call with a type argument `async();` - const asyncArrowFn = this.tsTryParseGenericAsyncArrowFunction(startPos, startLoc); - if (asyncArrowFn) { - return asyncArrowFn; + // ====================================================== + // OVERRIDES + // ====================================================== + + parseAssignableListItem( + allowModifiers: ?boolean, + decorators: N.Decorator[], + ): N.Pattern | N.TSParameterProperty { + let accessibility: ?N.Accessibility; + let readonly = false; + if (allowModifiers) { + accessibility = this.parseAccessModifier(); + readonly = !!this.tsParseModifier(["readonly"]); + } + + const left = this.parseMaybeDefault(); + this.parseAssignableListItemTypes(left); + const elt = this.parseMaybeDefault(left.start, left.loc.start, left); + if (accessibility || readonly) { + const pp: N.TSParameterProperty = this.startNodeAtNode(elt); + if (decorators.length) { + pp.decorators = decorators; + } + if (accessibility) pp.accessibility = accessibility; + if (readonly) pp.readonly = readonly; + if (elt.type !== "Identifier" && elt.type !== "AssignmentPattern") { + throw this.raise( + pp.start, + "A parameter property may not be declared using a binding pattern.", + ); + } + pp.parameter = elt; + return this.finishNode(pp, "TSParameterProperty"); + } else { + if (decorators.length) { + left.decorators = decorators; + } + return elt; + } + } + + parseFunctionBodyAndFinish( + node: N.BodilessFunctionOrMethodBase, + type: string, + allowExpressionBody?: boolean, + ): void { + // For arrow functions, `parseArrow` handles the return type itself. + if (!allowExpressionBody && this.match(tt.colon)) { + node.returnType = this.tsParseTypeOrTypePredicateAnnotation(tt.colon); + } + + const bodilessType = + type === "FunctionDeclaration" + ? "TSDeclareFunction" + : type === "ClassMethod" ? "TSDeclareMethod" : undefined; + if (bodilessType && !this.match(tt.braceL) && this.isLineTerminator()) { + this.finishNode(node, bodilessType); + return; + } + + super.parseFunctionBodyAndFinish(node, type, allowExpressionBody); + } + + parseSubscript( + base: N.Expression, + startPos: number, + startLoc: Position, + noCalls: ?boolean, + state: { stop: boolean }, + ): N.Expression { + if (this.eat(tt.bang)) { + const nonNullExpression: N.TsNonNullExpression = this.startNodeAt( + startPos, + startLoc, + ); + nonNullExpression.expression = base; + return this.finishNode(nonNullExpression, "TSNonNullExpression"); + } + + if (!noCalls && this.isRelational("<")) { + if (this.atPossibleAsync(base)) { + // Almost certainly this is a generic async function `async () => ... + // But it might be a call with a type argument `async();` + const asyncArrowFn = this.tsTryParseGenericAsyncArrowFunction( + startPos, + startLoc, + ); + if (asyncArrowFn) { + return asyncArrowFn; + } + } + + const node: N.CallExpression = this.startNodeAt(startPos, startLoc); + node.callee = base; + + // May be passing type arguments. But may just be the `<` operator. + const typeArguments = this.tsTryParseTypeArgumentsInExpression(); // Also eats the "(" + if (typeArguments) { + // possibleAsync always false here, because we would have handled it above. + // $FlowIgnore (won't be any undefined arguments) + node.arguments = this.parseCallExpressionArguments( + tt.parenR, + /* possibleAsync */ false, + ); + node.typeParameters = typeArguments; + return this.finishCallExpression(node); } } - const node: N.CallExpression = this.startNodeAt(startPos, startLoc); - node.callee = base; + return super.parseSubscript(base, startPos, startLoc, noCalls, state); + } - // May be passing type arguments. But may just be the `<` operator. - const typeArguments = this.tsTryParseTypeArgumentsInExpression(); // Also eats the "(" - if (typeArguments) { - // possibleAsync always false here, because we would have handled it above. - // $FlowIgnore (won't be any undefined arguments) - node.arguments = this.parseCallExpressionArguments(tt.parenR, /* possibleAsync */ false); - node.typeParameters = typeArguments; - return this.finishCallExpression(node); + parseNewArguments(node: N.NewExpression): void { + if (this.isRelational("<")) { + // tsTryParseAndCatch is expensive, so avoid if not necessary. + // 99% certain this is `new C();`. But may be `new C < T;`, which is also legal. + const typeParameters = this.tsTryParseAndCatch(() => { + const args = this.tsParseTypeArguments(); + if (!this.match(tt.parenL)) this.unexpected(); + return args; + }); + if (typeParameters) { + node.typeParameters = typeParameters; + } } + + super.parseNewArguments(node); } - return super.parseSubscript(base, startPos, startLoc, noCalls, state); - } - - parseNewArguments(node: N.NewExpression): void { - if (this.isRelational("<")) { // tsTryParseAndCatch is expensive, so avoid if not necessary. - // 99% certain this is `new C();`. But may be `new C < T;`, which is also legal. - const typeParameters = this.tsTryParseAndCatch(() => { - const args = this.tsParseTypeArguments(); - if (!this.match(tt.parenL)) this.unexpected(); - return args; - }); - if (typeParameters) { - node.typeParameters = typeParameters; + parseExprOp( + left: N.Expression, + leftStartPos: number, + leftStartLoc: Position, + minPrec: number, + noIn: ?boolean, + ) { + if ( + nonNull(tt._in.binop) > minPrec && + !this.hasPrecedingLineBreak() && + this.eatContextual("as") + ) { + const node: N.TsAsExpression = this.startNodeAt( + leftStartPos, + leftStartLoc, + ); + node.expression = left; + node.typeAnnotation = this.tsParseType(); + this.finishNode(node, "TSAsExpression"); + return this.parseExprOp( + node, + leftStartPos, + leftStartLoc, + minPrec, + noIn, + ); } + + return super.parseExprOp(left, leftStartPos, leftStartLoc, minPrec, noIn); } - super.parseNewArguments(node); - } - - parseExprOp( - left: N.Expression, - leftStartPos: number, - leftStartLoc: Position, - minPrec: number, - noIn: ?boolean) { - if (nonNull(tt._in.binop) > minPrec && !this.hasPrecedingLineBreak() && this.eatContextual("as")) { - const node: N.TsAsExpression = this.startNodeAt(leftStartPos, leftStartLoc); - node.expression = left; - node.typeAnnotation = this.tsParseType(); - this.finishNode(node, "TSAsExpression"); - return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn); + checkReservedWord( + word: string, + startLoc: number, + checkKeywords: boolean, + // eslint-disable-next-line no-unused-vars + isBinding: boolean, + ): void { + // Don't bother checking for TypeScript code. + // Strict mode words may be allowed as in `declare namespace N { const static: number; }`. + // And we have a type checker anyway, so don't bother having the parser do it. } - return super.parseExprOp(left, leftStartPos, leftStartLoc, minPrec, noIn); - } - - // eslint-disable-next-line no-unused-vars - checkReservedWord(word: string, startLoc: number, checkKeywords: boolean, isBinding: boolean): void { - // Don't bother checking for TypeScript code. - // Strict mode words may be allowed as in `declare namespace N { const static: number; }`. - // And we have a type checker anyway, so don't bother having the parser do it. - } - - /* + /* Don't bother doing this check in TypeScript code because: 1. We may have a nested export statement with the same name: export const x = 0; @@ -1196,293 +1363,183 @@ export default (superClass: Class): Class => class extends super } 2. We have a type checker to warn us about this sort of thing. */ - checkDuplicateExports() {} + checkDuplicateExports() {} - parseImport(node: N.Node): N.ImportDeclaration | N.TsImportEqualsDeclaration { - if (this.match(tt.name) && this.lookahead().type === tt.eq) { - return this.tsParseImportEqualsDeclaration(node); + parseImport( + node: N.Node, + ): N.ImportDeclaration | N.TsImportEqualsDeclaration { + if (this.match(tt.name) && this.lookahead().type === tt.eq) { + return this.tsParseImportEqualsDeclaration(node); + } + return super.parseImport(node); } - return super.parseImport(node); - } - parseExport(node: N.Node): N.Node { - if (this.match(tt._import)) { // `export import A = B;` - this.expect(tt._import); - return this.tsParseImportEqualsDeclaration(node, /* isExport */ true); - } else if (this.eat(tt.eq)) { // `export = x;` - const assign: N.TsExportAssignment = node; - assign.expression = this.parseExpression(); - this.semicolon(); - return this.finishNode(assign, "TSExportAssignment"); - } else if (this.eatContextual("as")) { // `export as namespace A;` - const decl: N.TsNamespaceExportDeclaration = node; - // See `parseNamespaceExportDeclaration` in TypeScript's own parser - this.expectContextual("namespace"); - decl.id = this.parseIdentifier(); - this.semicolon(); - return this.finishNode(decl, "TSNamespaceExportDeclaration"); - } else { - return super.parseExport(node); - } - } - - parseStatementContent(declaration: boolean, topLevel: ?boolean): N.Statement { - if (this.state.type === tt._const) { - const ahead = this.lookahead(); - if (ahead.type === tt.name && ahead.value === "enum") { - const node: N.TsEnumDeclaration = this.startNode(); - this.expect(tt._const); - this.expectContextual("enum"); - return this.tsParseEnumDeclaration(node, /* isConst */ true); + parseExport(node: N.Node): N.Node { + if (this.match(tt._import)) { + // `export import A = B;` + this.expect(tt._import); + return this.tsParseImportEqualsDeclaration(node, /* isExport */ true); + } else if (this.eat(tt.eq)) { + // `export = x;` + const assign: N.TsExportAssignment = node; + assign.expression = this.parseExpression(); + this.semicolon(); + return this.finishNode(assign, "TSExportAssignment"); + } else if (this.eatContextual("as")) { + // `export as namespace A;` + const decl: N.TsNamespaceExportDeclaration = node; + // See `parseNamespaceExportDeclaration` in TypeScript's own parser + this.expectContextual("namespace"); + decl.id = this.parseIdentifier(); + this.semicolon(); + return this.finishNode(decl, "TSNamespaceExportDeclaration"); + } else { + return super.parseExport(node); } } - return super.parseStatementContent(declaration, topLevel); - } - parseAccessModifier(): ?N.Accessibility { - return this.tsParseModifier(["public", "protected", "private"]); - } - - parseClassMember(classBody: N.ClassBody, member: any, state: { hadConstructor: boolean }): void { - const accessibility = this.parseAccessModifier(); - if (accessibility) member.accessibility = accessibility; - - super.parseClassMember(classBody, member, state); - } - - parseClassMemberWithIsStatic( - classBody: N.ClassBody, - member: any, - state: { hadConstructor: boolean }, - isStatic: boolean): void { - const methodOrProp: N.ClassMethod | N.ClassProperty = member; - const prop: N.ClassProperty = member; - const propOrIdx: N.ClassProperty | N.TsIndexSignature = member; - - let abstract = false, readonly = false; - - const mod = this.tsParseModifier(["abstract", "readonly"]); - switch (mod) { - case "readonly": - readonly = true; - abstract = !!this.tsParseModifier(["abstract"]); - break; - case "abstract": - abstract = true; - readonly = !!this.tsParseModifier(["readonly"]); - break; + parseStatementContent( + declaration: boolean, + topLevel: ?boolean, + ): N.Statement { + if (this.state.type === tt._const) { + const ahead = this.lookahead(); + if (ahead.type === tt.name && ahead.value === "enum") { + const node: N.TsEnumDeclaration = this.startNode(); + this.expect(tt._const); + this.expectContextual("enum"); + return this.tsParseEnumDeclaration(node, /* isConst */ true); + } + } + return super.parseStatementContent(declaration, topLevel); } - if (abstract) methodOrProp.abstract = true; - if (readonly) propOrIdx.readonly = true; + parseAccessModifier(): ?N.Accessibility { + return this.tsParseModifier(["public", "protected", "private"]); + } - if (!abstract && !isStatic && !methodOrProp.accessibility) { - const idx = this.tsTryParseIndexSignature(member); - if (idx) { - classBody.body.push(idx); + parseClassMember( + classBody: N.ClassBody, + member: any, + state: { hadConstructor: boolean }, + ): void { + const accessibility = this.parseAccessModifier(); + if (accessibility) member.accessibility = accessibility; + + super.parseClassMember(classBody, member, state); + } + + parseClassMemberWithIsStatic( + classBody: N.ClassBody, + member: any, + state: { hadConstructor: boolean }, + isStatic: boolean, + ): void { + const methodOrProp: N.ClassMethod | N.ClassProperty = member; + const prop: N.ClassProperty = member; + const propOrIdx: N.ClassProperty | N.TsIndexSignature = member; + + let abstract = false, + readonly = false; + + const mod = this.tsParseModifier(["abstract", "readonly"]); + switch (mod) { + case "readonly": + readonly = true; + abstract = !!this.tsParseModifier(["abstract"]); + break; + case "abstract": + abstract = true; + readonly = !!this.tsParseModifier(["readonly"]); + break; + } + + if (abstract) methodOrProp.abstract = true; + if (readonly) propOrIdx.readonly = true; + + if (!abstract && !isStatic && !methodOrProp.accessibility) { + const idx = this.tsTryParseIndexSignature(member); + if (idx) { + classBody.body.push(idx); + return; + } + } + + if (readonly) { + // Must be a property (if not an index signature). + methodOrProp.static = isStatic; + this.parseClassPropertyName(prop); + this.parsePostMemberNameModifiers(methodOrProp); + this.pushClassProperty(classBody, prop); return; } + + super.parseClassMemberWithIsStatic(classBody, member, state, isStatic); } - if (readonly) { - // Must be a property (if not an index signature). - methodOrProp.static = isStatic; - this.parseClassPropertyName(prop); - this.parsePostMemberNameModifiers(methodOrProp); - this.pushClassProperty(classBody, prop); - return; + parsePostMemberNameModifiers( + methodOrProp: N.ClassMethod | N.ClassProperty, + ): void { + const optional = this.eat(tt.question); + if (optional) methodOrProp.optional = true; } - super.parseClassMemberWithIsStatic(classBody, member, state, isStatic); - } + // Note: The reason we do this in `parseExpressionStatement` and not `parseStatement` + // is that e.g. `type()` is valid JS, so we must try parsing that first. + // If it's really a type, we will parse `type` as the statement, and can correct it here + // by parsing the rest. + parseExpressionStatement( + node: N.ExpressionStatement, + expr: N.Expression, + ): N.Statement { + const decl = + expr.type === "Identifier" + ? this.tsParseExpressionStatement(node, expr) + : undefined; + return decl || super.parseExpressionStatement(node, expr); + } - parsePostMemberNameModifiers(methodOrProp: N.ClassMethod | N.ClassProperty): void { - const optional = this.eat(tt.question); - if (optional) methodOrProp.optional = true; - } - - // Note: The reason we do this in `parseExpressionStatement` and not `parseStatement` - // is that e.g. `type()` is valid JS, so we must try parsing that first. - // If it's really a type, we will parse `type` as the statement, and can correct it here - // by parsing the rest. - parseExpressionStatement(node: N.ExpressionStatement, expr: N.Expression): N.Statement { - const decl = expr.type === "Identifier" ? this.tsParseExpressionStatement(node, expr) : undefined; - return decl || super.parseExpressionStatement(node, expr); - } - - // export type - // Should be true for anything parsed by `tsTryParseExportDeclaration`. - shouldParseExportDeclaration(): boolean { - if (this.match(tt.name)) { - switch (this.state.value) { - case "abstract": - case "declare": - case "enum": - case "interface": - case "module": - case "namespace": - case "type": - return true; + // export type + // Should be true for anything parsed by `tsTryParseExportDeclaration`. + shouldParseExportDeclaration(): boolean { + if (this.match(tt.name)) { + switch (this.state.value) { + case "abstract": + case "declare": + case "enum": + case "interface": + case "module": + case "namespace": + case "type": + return true; + } } - } - return super.shouldParseExportDeclaration(); - } - - // An apparent conditional expression could actually be an optional parameter in an arrow function. - parseConditional( - expr: N.Expression, - noIn: ?boolean, - startPos: number, - startLoc: Position, - refNeedsArrowPos?: ?Pos): N.Expression { - - // only do the expensive clone if there is a question mark - // and if we come from inside parens - if (!refNeedsArrowPos || !this.match(tt.question)) { - return super.parseConditional(expr, noIn, startPos, startLoc, refNeedsArrowPos); + return super.shouldParseExportDeclaration(); } - const state = this.state.clone(); - try { - return super.parseConditional(expr, noIn, startPos, startLoc); - } catch (err) { - if (!(err instanceof SyntaxError)) { - // istanbul ignore next: no such error is expected - throw err; + // An apparent conditional expression could actually be an optional parameter in an arrow function. + parseConditional( + expr: N.Expression, + noIn: ?boolean, + startPos: number, + startLoc: Position, + refNeedsArrowPos?: ?Pos, + ): N.Expression { + // only do the expensive clone if there is a question mark + // and if we come from inside parens + if (!refNeedsArrowPos || !this.match(tt.question)) { + return super.parseConditional( + expr, + noIn, + startPos, + startLoc, + refNeedsArrowPos, + ); } - this.state = state; - refNeedsArrowPos.start = err.pos || this.state.start; - return expr; - } - } - - // Note: These "type casts" are *not* valid TS expressions. - // But we parse them here and change them when completing the arrow function. - parseParenItem(node: N.Expression, startPos: number, startLoc: Position): N.Expression { - node = super.parseParenItem(node, startPos, startLoc); - if (this.eat(tt.question)) { - node.optional = true; - } - - if (this.match(tt.colon)) { - const typeCastNode: N.TypeCastExpression = this.startNodeAt(startPos, startLoc); - typeCastNode.expression = node; - typeCastNode.typeAnnotation = this.tsParseTypeAnnotation(); - - return this.finishNode(typeCastNode, "TypeCastExpression"); - } - - return node; - } - - parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration { - // "export declare" is equivalent to just "export". - const isDeclare = this.eatContextual("declare"); - - let declaration: ?N.Declaration; - if (this.match(tt.name)) { - declaration = this.tsTryParseExportDeclaration(); - } - if (!declaration) { - declaration = super.parseExportDeclaration(node); - } - - if (declaration && isDeclare) { - declaration.declare = true; - } - - return declaration; - } - - parseClassId(node: N.Class, isStatement: boolean, optionalId: ?boolean): void { - if ((!isStatement || optionalId) && this.isContextual("implements")) { - return; - } - - super.parseClassId(...arguments); - const typeParameters = this.tsTryParseTypeParameters(); - if (typeParameters) node.typeParameters = typeParameters; - } - - parseClassProperty(node: N.ClassProperty): N.ClassProperty { - const type = this.tsTryParseTypeAnnotation(); - if (type) node.typeAnnotation = type; - return super.parseClassProperty(node); - } - - parseClassMethod( - classBody: N.ClassBody, - method: N.ClassMethod, - isGenerator: boolean, - isAsync: boolean, - isConstructor: boolean): void { - - const typeParameters = this.tsTryParseTypeParameters(); - if (typeParameters) method.typeParameters = typeParameters; - super.parseClassMethod(classBody, method, isGenerator, isAsync, isConstructor); - } - - parseClassSuper(node: N.Class): void { - super.parseClassSuper(node); - if (node.superClass && this.isRelational("<")) { - node.superTypeParameters = this.tsParseTypeArguments(); - } - if (this.eatContextual("implements")) { - node.implements = this.tsParseHeritageClause(); - } - } - - parseObjPropValue(prop: N.ObjectMember, ...args): void { - if (this.isRelational("<")) { - throw new Error("TODO"); - } - - super.parseObjPropValue(prop, ...args); - } - - parseFunctionParams(node: N.Function): void { - const typeParameters = this.tsTryParseTypeParameters(); - if (typeParameters) node.typeParameters = typeParameters; - super.parseFunctionParams(node); - } - - // `let x: number;` - parseVarHead(decl: N.VariableDeclarator): void { - super.parseVarHead(decl); - const type = this.tsTryParseTypeAnnotation(); - if (type) { - decl.id.typeAnnotation = type; - this.finishNode(decl.id, decl.id.type); // set end position to end of type - } - } - - // parse the return type of an async arrow function - let foo = (async (): number => {}); - parseAsyncArrowFromCallExpression( - node: N.ArrowFunctionExpression, - call: N.CallExpression): N.ArrowFunctionExpression { - - if (this.match(tt.colon)) { - node.returnType = this.tsParseTypeAnnotation(); - } - return super.parseAsyncArrowFromCallExpression(node, call); - } - - parseMaybeAssign(...args): N.Expression { - // Note: When the JSX plugin is on, type assertions (` x`) aren't valid syntax. - - let jsxError: ?SyntaxError; - - if (this.match(tt.jsxTagStart)) { - const context = this.curContext(); - assert(context === ct.j_oTag); - // Only time j_oTag is pushed is right after j_expr. - assert(this.state.context[this.state.context.length - 2] === ct.j_expr); - - // Prefer to parse JSX if possible. But may be an arrow fn. const state = this.state.clone(); try { - return super.parseMaybeAssign(...args); + return super.parseConditional(expr, noIn, startPos, startLoc); } catch (err) { if (!(err instanceof SyntaxError)) { // istanbul ignore next: no such error is expected @@ -1490,228 +1547,408 @@ export default (superClass: Class): Class => class extends super } this.state = state; - // Pop the context added by the jsxTagStart. - assert(this.curContext() === ct.j_oTag); - this.state.context.pop(); - assert(this.curContext() === ct.j_expr); - this.state.context.pop(); - jsxError = err; + refNeedsArrowPos.start = err.pos || this.state.start; + return expr; } } - if (jsxError === undefined && !this.isRelational("<")) { - return super.parseMaybeAssign(...args); - } - - // Either way, we're looking at a '<': tt.jsxTagStart or relational. - - let arrowExpression; - let typeParameters: N.TypeParameterDeclaration; - const state = this.state.clone(); - this.next(); // skip the jsx start - try { - // This is similar to TypeScript's `tryParseParenthesizedArrowFunctionExpression`. - typeParameters = this.tsParseTypeParameters(); - arrowExpression = super.parseMaybeAssign(...args); - if (arrowExpression.type !== "ArrowFunctionExpression") { - this.unexpected(); // Go to the catch block (needs a SyntaxError). - } - } catch (err) { - if (!(err instanceof SyntaxError)) { - // istanbul ignore next: no such error is expected - throw err; + // Note: These "type casts" are *not* valid TS expressions. + // But we parse them here and change them when completing the arrow function. + parseParenItem( + node: N.Expression, + startPos: number, + startLoc: Position, + ): N.Expression { + node = super.parseParenItem(node, startPos, startLoc); + if (this.eat(tt.question)) { + node.optional = true; } - if (jsxError) { - throw jsxError; + if (this.match(tt.colon)) { + const typeCastNode: N.TypeCastExpression = this.startNodeAt( + startPos, + startLoc, + ); + typeCastNode.expression = node; + typeCastNode.typeAnnotation = this.tsParseTypeAnnotation(); + + return this.finishNode(typeCastNode, "TypeCastExpression"); } - // Try parsing a type cast instead of an arrow function. - // This will never happen outside of JSX. - // (Because in JSX the '<' should be a jsxTagStart and not a relational. - assert(!this.hasPlugin("jsx")); - // Parsing an arrow function failed, so try a type cast. - this.state = state; - // This will start with a type assertion (via parseMaybeUnary). - // But don't directly call `this.tsParseTypeAssertion` because we want to handle any binary after it. - return super.parseMaybeAssign(...args); + return node; } - // Correct TypeScript code should have at least 1 type parameter, but don't crash on bad code. - if (typeParameters && typeParameters.params.length !== 0) { - this.resetStartLocationFromNode(arrowExpression, typeParameters.params[0]); - } - arrowExpression.typeParameters = typeParameters; - return arrowExpression; - } + parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration { + // "export declare" is equivalent to just "export". + const isDeclare = this.eatContextual("declare"); - // Handle type assertions - parseMaybeUnary(refShorthandDefaultPos?: ?Pos): N.Expression { - if (!this.hasPlugin("jsx") && this.eatRelational("<")) { - return this.tsParseTypeAssertion(); - } else { - return super.parseMaybeUnary(refShorthandDefaultPos); - } - } + let declaration: ?N.Declaration; + if (this.match(tt.name)) { + declaration = this.tsTryParseExportDeclaration(); + } + if (!declaration) { + declaration = super.parseExportDeclaration(node); + } + + if (declaration && isDeclare) { + declaration.declare = true; + } + + return declaration; + } + + parseClassId( + node: N.Class, + isStatement: boolean, + optionalId: ?boolean, + ): void { + if ((!isStatement || optionalId) && this.isContextual("implements")) { + return; + } + + super.parseClassId(...arguments); + const typeParameters = this.tsTryParseTypeParameters(); + if (typeParameters) node.typeParameters = typeParameters; + } + + parseClassProperty(node: N.ClassProperty): N.ClassProperty { + const type = this.tsTryParseTypeAnnotation(); + if (type) node.typeAnnotation = type; + return super.parseClassProperty(node); + } + + parseClassMethod( + classBody: N.ClassBody, + method: N.ClassMethod, + isGenerator: boolean, + isAsync: boolean, + isConstructor: boolean, + ): void { + const typeParameters = this.tsTryParseTypeParameters(); + if (typeParameters) method.typeParameters = typeParameters; + super.parseClassMethod( + classBody, + method, + isGenerator, + isAsync, + isConstructor, + ); + } + + parseClassSuper(node: N.Class): void { + super.parseClassSuper(node); + if (node.superClass && this.isRelational("<")) { + node.superTypeParameters = this.tsParseTypeArguments(); + } + if (this.eatContextual("implements")) { + node.implements = this.tsParseHeritageClause(); + } + } + + parseObjPropValue(prop: N.ObjectMember, ...args): void { + if (this.isRelational("<")) { + throw new Error("TODO"); + } + + super.parseObjPropValue(prop, ...args); + } + + parseFunctionParams(node: N.Function): void { + const typeParameters = this.tsTryParseTypeParameters(); + if (typeParameters) node.typeParameters = typeParameters; + super.parseFunctionParams(node); + } + + // `let x: number;` + parseVarHead(decl: N.VariableDeclarator): void { + super.parseVarHead(decl); + const type = this.tsTryParseTypeAnnotation(); + if (type) { + decl.id.typeAnnotation = type; + this.finishNode(decl.id, decl.id.type); // set end position to end of type + } + } + + // parse the return type of an async arrow function - let foo = (async (): number => {}); + parseAsyncArrowFromCallExpression( + node: N.ArrowFunctionExpression, + call: N.CallExpression, + ): N.ArrowFunctionExpression { + if (this.match(tt.colon)) { + node.returnType = this.tsParseTypeAnnotation(); + } + return super.parseAsyncArrowFromCallExpression(node, call); + } + + parseMaybeAssign(...args): N.Expression { + // Note: When the JSX plugin is on, type assertions (` x`) aren't valid syntax. + + let jsxError: ?SyntaxError; + + if (this.match(tt.jsxTagStart)) { + const context = this.curContext(); + assert(context === ct.j_oTag); + // Only time j_oTag is pushed is right after j_expr. + assert(this.state.context[this.state.context.length - 2] === ct.j_expr); + + // Prefer to parse JSX if possible. But may be an arrow fn. + const state = this.state.clone(); + try { + return super.parseMaybeAssign(...args); + } catch (err) { + if (!(err instanceof SyntaxError)) { + // istanbul ignore next: no such error is expected + throw err; + } - parseArrow(node: N.ArrowFunctionExpression): ?N.ArrowFunctionExpression { - if (this.match(tt.colon)) { - // This is different from how the TS parser does it. - // TS uses lookahead. Babylon parses it as a parenthesized expression and converts. - const state = this.state.clone(); - try { - const returnType = this.tsParseTypeOrTypePredicateAnnotation(tt.colon); - if (this.canInsertSemicolon()) this.unexpected(); - if (!this.match(tt.arrow)) this.unexpected(); - node.returnType = returnType; - } catch (err) { - if (err instanceof SyntaxError) { this.state = state; - } else { + // Pop the context added by the jsxTagStart. + assert(this.curContext() === ct.j_oTag); + this.state.context.pop(); + assert(this.curContext() === ct.j_expr); + this.state.context.pop(); + jsxError = err; + } + } + + if (jsxError === undefined && !this.isRelational("<")) { + return super.parseMaybeAssign(...args); + } + + // Either way, we're looking at a '<': tt.jsxTagStart or relational. + + let arrowExpression; + let typeParameters: N.TypeParameterDeclaration; + const state = this.state.clone(); + this.next(); // skip the jsx start + try { + // This is similar to TypeScript's `tryParseParenthesizedArrowFunctionExpression`. + typeParameters = this.tsParseTypeParameters(); + arrowExpression = super.parseMaybeAssign(...args); + if (arrowExpression.type !== "ArrowFunctionExpression") { + this.unexpected(); // Go to the catch block (needs a SyntaxError). + } + } catch (err) { + if (!(err instanceof SyntaxError)) { // istanbul ignore next: no such error is expected throw err; } + + if (jsxError) { + throw jsxError; + } + + // Try parsing a type cast instead of an arrow function. + // This will never happen outside of JSX. + // (Because in JSX the '<' should be a jsxTagStart and not a relational. + assert(!this.hasPlugin("jsx")); + // Parsing an arrow function failed, so try a type cast. + this.state = state; + // This will start with a type assertion (via parseMaybeUnary). + // But don't directly call `this.tsParseTypeAssertion` because we want to handle any binary after it. + return super.parseMaybeAssign(...args); + } + + // Correct TypeScript code should have at least 1 type parameter, but don't crash on bad code. + if (typeParameters && typeParameters.params.length !== 0) { + this.resetStartLocationFromNode( + arrowExpression, + typeParameters.params[0], + ); + } + arrowExpression.typeParameters = typeParameters; + return arrowExpression; + } + + // Handle type assertions + parseMaybeUnary(refShorthandDefaultPos?: ?Pos): N.Expression { + if (!this.hasPlugin("jsx") && this.eatRelational("<")) { + return this.tsParseTypeAssertion(); + } else { + return super.parseMaybeUnary(refShorthandDefaultPos); } } - return super.parseArrow(node); - } - - // Allow type annotations inside of a parameter list. - parseAssignableListItemTypes(param: N.Pattern) { - if (this.eat(tt.question)) { - if (param.type !== "Identifier") { - throw this.raise( - param.start, - "A binding pattern parameter cannot be optional in an implementation signature."); + parseArrow(node: N.ArrowFunctionExpression): ?N.ArrowFunctionExpression { + if (this.match(tt.colon)) { + // This is different from how the TS parser does it. + // TS uses lookahead. Babylon parses it as a parenthesized expression and converts. + const state = this.state.clone(); + try { + const returnType = this.tsParseTypeOrTypePredicateAnnotation( + tt.colon, + ); + if (this.canInsertSemicolon()) this.unexpected(); + if (!this.match(tt.arrow)) this.unexpected(); + node.returnType = returnType; + } catch (err) { + if (err instanceof SyntaxError) { + this.state = state; + } else { + // istanbul ignore next: no such error is expected + throw err; + } + } } - param.optional = true; - } - const type = this.tsTryParseTypeAnnotation(); - if (type) param.typeAnnotation = type; - return this.finishNode(param, param.type); - } - - toAssignable(node: N.Node, isBinding: ?boolean, contextDescription: string): N.Node { - switch (node.type) { - case "TypeCastExpression": - return super.toAssignable(this.typeCastToParameter(node), isBinding, contextDescription); - case "TSParameterProperty": - return super.toAssignable(node, isBinding, contextDescription); - default: - return super.toAssignable(node, isBinding, contextDescription); - } - } - - checkLVal( - expr: N.Expression, - isBinding: ?boolean, - checkClashes: ?{ [key: string]: boolean }, - contextDescription: string): void { - switch (expr.type) { - case "TypeCastExpression": - // Allow "typecasts" to appear on the left of assignment expressions, - // because it may be in an arrow function. - // e.g. `const f = (foo: number = 0) => foo;` - return; - case "TSParameterProperty": - this.checkLVal(expr.parameter, isBinding, checkClashes, "parameter property"); - return; - default: - super.checkLVal(expr, isBinding, checkClashes, contextDescription); - return; - } - } - - parseBindingAtom(): N.Pattern { - switch (this.state.type) { - case tt._this: - // "this" may be the name of a parameter, so allow it. - return this.parseIdentifier(/* liberal */ true); - default: - return super.parseBindingAtom(); - } - } - - // === === === === === === === === === === === === === === === === - // Note: All below methods are duplicates of something in flow.js. - // Not sure what the best way to combine these is. - // === === === === === === === === === === === === === === === === - - isClassMethod(): boolean { - return this.isRelational("<") || super.isClassMethod(); - } - - isClassProperty(): boolean { - return this.match(tt.colon) || super.isClassProperty(); - } - - parseMaybeDefault(...args): N.Pattern { - const node = super.parseMaybeDefault(...args); - - if (node.type === "AssignmentPattern" - && node.typeAnnotation - && node.right.start < node.typeAnnotation.start) { - this.raise( - node.typeAnnotation.start, - "Type annotations must come before default assignments, " + - "e.g. instead of `age = 25: number` use `age: number = 25`"); + return super.parseArrow(node); } - return node; - } + // Allow type annotations inside of a parameter list. + parseAssignableListItemTypes(param: N.Pattern) { + if (this.eat(tt.question)) { + if (param.type !== "Identifier") { + throw this.raise( + param.start, + "A binding pattern parameter cannot be optional in an implementation signature.", + ); + } - // ensure that inside types, we bypass the jsx parser plugin - readToken(code: number): void { - if (this.state.inType && (code === 62 || code === 60)) { - return this.finishOp(tt.relational, 1); - } else { - return super.readToken(code); - } - } - - toAssignableList( - exprList: N.Expression[], - isBinding: ?boolean, - contextDescription: string): $ReadOnlyArray { - - for (let i = 0; i < exprList.length; i++) { - const expr = exprList[i]; - if (expr && expr.type === "TypeCastExpression") { - exprList[i] = this.typeCastToParameter(expr); + param.optional = true; } + const type = this.tsTryParseTypeAnnotation(); + if (type) param.typeAnnotation = type; + return this.finishNode(param, param.type); } - return super.toAssignableList(exprList, isBinding, contextDescription); - } - typeCastToParameter(node: N.TypeCastExpression): N.Node { - node.expression.typeAnnotation = node.typeAnnotation; - - return this.finishNodeAt( - node.expression, - node.expression.type, - node.typeAnnotation.end, - node.typeAnnotation.loc.end - ); - } - - toReferencedList(exprList: $ReadOnlyArray): $ReadOnlyArray { - for (let i = 0; i < exprList.length; i++) { - const expr = exprList[i]; - if (expr && expr._exprListItem && expr.type === "TypeCastExpression") { - this.raise(expr.start, "Did not expect a type annotation here."); + toAssignable( + node: N.Node, + isBinding: ?boolean, + contextDescription: string, + ): N.Node { + switch (node.type) { + case "TypeCastExpression": + return super.toAssignable( + this.typeCastToParameter(node), + isBinding, + contextDescription, + ); + case "TSParameterProperty": + return super.toAssignable(node, isBinding, contextDescription); + default: + return super.toAssignable(node, isBinding, contextDescription); } } - return exprList; - } + checkLVal( + expr: N.Expression, + isBinding: ?boolean, + checkClashes: ?{ [key: string]: boolean }, + contextDescription: string, + ): void { + switch (expr.type) { + case "TypeCastExpression": + // Allow "typecasts" to appear on the left of assignment expressions, + // because it may be in an arrow function. + // e.g. `const f = (foo: number = 0) => foo;` + return; + case "TSParameterProperty": + this.checkLVal( + expr.parameter, + isBinding, + checkClashes, + "parameter property", + ); + return; + default: + super.checkLVal(expr, isBinding, checkClashes, contextDescription); + return; + } + } - shouldParseArrow() { - return this.match(tt.colon) || super.shouldParseArrow(); - } + parseBindingAtom(): N.Pattern { + switch (this.state.type) { + case tt._this: + // "this" may be the name of a parameter, so allow it. + return this.parseIdentifier(/* liberal */ true); + default: + return super.parseBindingAtom(); + } + } - shouldParseAsyncArrow(): boolean { - return this.match(tt.colon) || super.shouldParseAsyncArrow(); - } -}; + // === === === === === === === === === === === === === === === === + // Note: All below methods are duplicates of something in flow.js. + // Not sure what the best way to combine these is. + // === === === === === === === === === === === === === === === === + + isClassMethod(): boolean { + return this.isRelational("<") || super.isClassMethod(); + } + + isClassProperty(): boolean { + return this.match(tt.colon) || super.isClassProperty(); + } + + parseMaybeDefault(...args): N.Pattern { + const node = super.parseMaybeDefault(...args); + + if ( + node.type === "AssignmentPattern" && + node.typeAnnotation && + node.right.start < node.typeAnnotation.start + ) { + this.raise( + node.typeAnnotation.start, + "Type annotations must come before default assignments, " + + "e.g. instead of `age = 25: number` use `age: number = 25`", + ); + } + + return node; + } + + // ensure that inside types, we bypass the jsx parser plugin + readToken(code: number): void { + if (this.state.inType && (code === 62 || code === 60)) { + return this.finishOp(tt.relational, 1); + } else { + return super.readToken(code); + } + } + + toAssignableList( + exprList: N.Expression[], + isBinding: ?boolean, + contextDescription: string, + ): $ReadOnlyArray { + for (let i = 0; i < exprList.length; i++) { + const expr = exprList[i]; + if (expr && expr.type === "TypeCastExpression") { + exprList[i] = this.typeCastToParameter(expr); + } + } + return super.toAssignableList(exprList, isBinding, contextDescription); + } + + typeCastToParameter(node: N.TypeCastExpression): N.Node { + node.expression.typeAnnotation = node.typeAnnotation; + + return this.finishNodeAt( + node.expression, + node.expression.type, + node.typeAnnotation.end, + node.typeAnnotation.loc.end, + ); + } + + toReferencedList( + exprList: $ReadOnlyArray, + ): $ReadOnlyArray { + for (let i = 0; i < exprList.length; i++) { + const expr = exprList[i]; + if (expr && expr._exprListItem && expr.type === "TypeCastExpression") { + this.raise(expr.start, "Did not expect a type annotation here."); + } + } + + return exprList; + } + + shouldParseArrow() { + return this.match(tt.colon) || super.shouldParseArrow(); + } + + shouldParseAsyncArrow(): boolean { + return this.match(tt.colon) || super.shouldParseAsyncArrow(); + } + }; diff --git a/src/tokenizer/context.js b/src/tokenizer/context.js index 67a62c9657..f46eafc8c4 100644 --- a/src/tokenizer/context.js +++ b/src/tokenizer/context.js @@ -27,27 +27,30 @@ export class TokContext { } export const types: { - [key: string]: TokContext; + [key: string]: TokContext, } = { braceStatement: new TokContext("{", false), braceExpression: new TokContext("{", true), templateQuasi: new TokContext("${", true), parenStatement: new TokContext("(", false), parenExpression: new TokContext("(", true), - template: new TokContext("`", true, true, (p) => p.readTmplToken()), - functionExpression: new TokContext("function", true) + template: new TokContext("`", true, true, p => p.readTmplToken()), + functionExpression: new TokContext("function", true), }; // Token-specific context update code -tt.parenR.updateContext = tt.braceR.updateContext = function () { +tt.parenR.updateContext = tt.braceR.updateContext = function() { if (this.state.context.length === 1) { this.state.exprAllowed = true; return; } const out = this.state.context.pop(); - if (out === types.braceStatement && this.curContext() === types.functionExpression) { + if ( + out === types.braceStatement && + this.curContext() === types.functionExpression + ) { this.state.context.pop(); this.state.exprAllowed = false; } else if (out === types.templateQuasi) { @@ -57,7 +60,7 @@ tt.parenR.updateContext = tt.braceR.updateContext = function () { } }; -tt.name.updateContext = function (prevType) { +tt.name.updateContext = function(prevType) { this.state.exprAllowed = false; if (prevType === tt._let || prevType === tt._const || prevType === tt._var) { @@ -67,28 +70,35 @@ tt.name.updateContext = function (prevType) { } }; -tt.braceL.updateContext = function (prevType) { - this.state.context.push(this.braceIsBlock(prevType) ? types.braceStatement : types.braceExpression); +tt.braceL.updateContext = function(prevType) { + this.state.context.push( + this.braceIsBlock(prevType) ? types.braceStatement : types.braceExpression, + ); this.state.exprAllowed = true; }; -tt.dollarBraceL.updateContext = function () { +tt.dollarBraceL.updateContext = function() { this.state.context.push(types.templateQuasi); this.state.exprAllowed = true; }; -tt.parenL.updateContext = function (prevType) { - const statementParens = prevType === tt._if || prevType === tt._for || - prevType === tt._with || prevType === tt._while; - this.state.context.push(statementParens ? types.parenStatement : types.parenExpression); +tt.parenL.updateContext = function(prevType) { + const statementParens = + prevType === tt._if || + prevType === tt._for || + prevType === tt._with || + prevType === tt._while; + this.state.context.push( + statementParens ? types.parenStatement : types.parenExpression, + ); this.state.exprAllowed = true; }; -tt.incDec.updateContext = function () { +tt.incDec.updateContext = function() { // tokExprAllowed stays unchanged }; -tt._function.updateContext = function () { +tt._function.updateContext = function() { if (this.curContext() !== types.braceStatement) { this.state.context.push(types.functionExpression); } @@ -96,7 +106,7 @@ tt._function.updateContext = function () { this.state.exprAllowed = false; }; -tt.backQuote.updateContext = function () { +tt.backQuote.updateContext = function() { if (this.curContext() === types.template) { this.state.context.pop(); } else { diff --git a/src/tokenizer/index.js b/src/tokenizer/index.js index 0d5e0d7b26..e116cc1cca 100644 --- a/src/tokenizer/index.js +++ b/src/tokenizer/index.js @@ -4,33 +4,41 @@ import type { Options } from "../options"; import type { Position } from "../util/location"; -import { isIdentifierStart, isIdentifierChar, isKeyword } from "../util/identifier"; +import { + isIdentifierStart, + isIdentifierChar, + isKeyword, +} from "../util/identifier"; import { types as tt, keywords as keywordTypes, type TokenType } from "./types"; import { type TokContext, types as ct } from "./context"; import LocationParser from "../parser/location"; import { SourceLocation } from "../util/location"; -import { lineBreak, lineBreakG, isNewLine, nonASCIIwhitespace } from "../util/whitespace"; +import { + lineBreak, + lineBreakG, + isNewLine, + nonASCIIwhitespace, +} from "../util/whitespace"; import State from "./state"; - // The following character codes are forbidden from being // an immediate sibling of NumericLiteralSeparator _ const forbiddenNumericSeparatorSiblings = { decBinOct: [ - 46, // . - 66, // B - 69, // E - 79, // O - 95, // _ (multiple separators are not allowed) - 98, // b + 46, // . + 66, // B + 69, // E + 79, // O + 95, // _ (multiple separators are not allowed) + 98, // b 101, // e 111, // o ], hex: [ - 46, // . - 88, // X - 95, // _ (multiple separators are not allowed) + 46, // . + 88, // X + 95, // _ (multiple separators are not allowed) 120, // x ], }; @@ -59,10 +67,13 @@ export class Token { function codePointToString(code: number): string { // UTF-16 Decoding - if (code <= 0xFFFF) { + if (code <= 0xffff) { return String.fromCharCode(code); } else { - return String.fromCharCode(((code - 0x10000) >> 10) + 0xD800, ((code - 0x10000) & 1023) + 0xDC00); + return String.fromCharCode( + ((code - 0x10000) >> 10) + 0xd800, + ((code - 0x10000) & 1023) + 0xdc00, + ); } } @@ -75,7 +86,7 @@ export default class Tokenizer extends LocationParser { constructor(options: Options, input: string) { super(); - this.state = new State; + this.state = new State(); this.state.init(options, input); this.isLookahead = false; } @@ -140,7 +151,8 @@ export default class Tokenizer extends LocationParser { if (!this.match(tt.num) && !this.match(tt.string)) return; this.state.pos = this.state.start; while (this.state.pos < this.state.lineStart) { - this.state.lineStart = this.input.lastIndexOf("\n", this.state.lineStart - 2) + 1; + this.state.lineStart = + this.input.lastIndexOf("\n", this.state.lineStart - 2) + 1; --this.state.curLine; } this.nextToken(); @@ -188,13 +200,20 @@ export default class Tokenizer extends LocationParser { return (code << 10) + next - 0x35fdc00; } - pushComment(block: boolean, text: string, start: number, end: number, startLoc: Position, endLoc: Position): void { + pushComment( + block: boolean, + text: string, + start: number, + end: number, + startLoc: Position, + endLoc: Position, + ): void { const comment = { type: block ? "CommentBlock" : "CommentLine", value: text, start: start, end: end, - loc: new SourceLocation(startLoc, endLoc) + loc: new SourceLocation(startLoc, endLoc), }; if (!this.isLookahead) { @@ -207,31 +226,54 @@ export default class Tokenizer extends LocationParser { skipBlockComment(): void { const startLoc = this.state.curPosition(); const start = this.state.pos; - const end = this.input.indexOf("*/", this.state.pos += 2); + const end = this.input.indexOf("*/", (this.state.pos += 2)); if (end === -1) this.raise(this.state.pos - 2, "Unterminated comment"); this.state.pos = end + 2; lineBreakG.lastIndex = start; let match; - while ((match = lineBreakG.exec(this.input)) && match.index < this.state.pos) { + while ( + (match = lineBreakG.exec(this.input)) && + match.index < this.state.pos + ) { ++this.state.curLine; this.state.lineStart = match.index + match[0].length; } - this.pushComment(true, this.input.slice(start + 2, end), start, this.state.pos, startLoc, this.state.curPosition()); + this.pushComment( + true, + this.input.slice(start + 2, end), + start, + this.state.pos, + startLoc, + this.state.curPosition(), + ); } skipLineComment(startSkip: number): void { const start = this.state.pos; const startLoc = this.state.curPosition(); - let ch = this.input.charCodeAt(this.state.pos += startSkip); + let ch = this.input.charCodeAt((this.state.pos += startSkip)); if (this.state.pos < this.input.length) { - while (ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233 && ++this.state.pos < this.input.length) { + while ( + ch !== 10 && + ch !== 13 && + ch !== 8232 && + ch !== 8233 && + ++this.state.pos < this.input.length + ) { ch = this.input.charCodeAt(this.state.pos); } } - this.pushComment(false, this.input.slice(start + startSkip, this.state.pos), start, this.state.pos, startLoc, this.state.curPosition()); + this.pushComment( + false, + this.input.slice(start + startSkip, this.state.pos), + start, + this.state.pos, + startLoc, + this.state.curPosition(), + ); } // Called at the start of the parse and after every token. Skips @@ -241,7 +283,8 @@ export default class Tokenizer extends LocationParser { loop: while (this.state.pos < this.input.length) { const ch = this.input.charCodeAt(this.state.pos); switch (ch) { - case 32: case 160: // ' ' + case 32: + case 160: // ' ' ++this.state.pos; break; @@ -250,7 +293,9 @@ export default class Tokenizer extends LocationParser { ++this.state.pos; } - case 10: case 8232: case 8233: + case 10: + case 8232: + case 8233: ++this.state.pos; ++this.state.curLine; this.state.lineStart = this.state.pos; @@ -272,7 +317,10 @@ export default class Tokenizer extends LocationParser { break; default: - if (ch > 8 && ch < 14 || ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { + if ( + (ch > 8 && ch < 14) || + (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) + ) { ++this.state.pos; } else { break loop; @@ -312,7 +360,8 @@ export default class Tokenizer extends LocationParser { } const next2 = this.input.charCodeAt(this.state.pos + 2); - if (next === 46 && next2 === 46) { // 46 = dot '.' + if (next === 46 && next2 === 46) { + // 46 = dot '.' this.state.pos += 3; return this.finishToken(tt.ellipsis); } else { @@ -321,7 +370,8 @@ export default class Tokenizer extends LocationParser { } } - readToken_slash(): void { // '/' + readToken_slash(): void { + // '/' if (this.state.exprAllowed) { ++this.state.pos; return this.readRegexp(); @@ -335,12 +385,14 @@ export default class Tokenizer extends LocationParser { } } - readToken_mult_modulo(code: number): void { // '%*' + readToken_mult_modulo(code: number): void { + // '%*' let type = code === 42 ? tt.star : tt.modulo; let width = 1; let next = this.input.charCodeAt(this.state.pos + 1); - if (next === 42) { // '*' + if (next === 42) { + // '*' width++; next = this.input.charCodeAt(this.state.pos + 2); type = tt.exponent; @@ -354,15 +406,19 @@ export default class Tokenizer extends LocationParser { return this.finishOp(type, width); } - readToken_pipe_amp(code: number): void { // '|&' + readToken_pipe_amp(code: number): void { + // '|&' const next = this.input.charCodeAt(this.state.pos + 1); - if (next === code) return this.finishOp(code === 124 ? tt.logicalOR : tt.logicalAND, 2); + if (next === code) + return this.finishOp(code === 124 ? tt.logicalOR : tt.logicalAND, 2); if (next === 61) return this.finishOp(tt.assign, 2); - if (code === 124 && next === 125 && this.hasPlugin("flow")) return this.finishOp(tt.braceBarR, 2); + if (code === 124 && next === 125 && this.hasPlugin("flow")) + return this.finishOp(tt.braceBarR, 2); return this.finishOp(code === 124 ? tt.bitwiseOR : tt.bitwiseAND, 1); } - readToken_caret(): void { // '^' + readToken_caret(): void { + // '^' const next = this.input.charCodeAt(this.state.pos + 1); if (next === 61) { return this.finishOp(tt.assign, 2); @@ -371,11 +427,16 @@ export default class Tokenizer extends LocationParser { } } - readToken_plus_min(code: number): void { // '+-' + readToken_plus_min(code: number): void { + // '+-' const next = this.input.charCodeAt(this.state.pos + 1); if (next === code) { - if (next === 45 && this.input.charCodeAt(this.state.pos + 2) === 62 && lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.pos))) { + if ( + next === 45 && + this.input.charCodeAt(this.state.pos + 2) === 62 && + lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.pos)) + ) { // A `-->` line comment this.skipLineComment(3); this.skipSpace(); @@ -391,17 +452,25 @@ export default class Tokenizer extends LocationParser { } } - readToken_lt_gt(code: number): void { // '<>' + readToken_lt_gt(code: number): void { + // '<>' const next = this.input.charCodeAt(this.state.pos + 1); let size = 1; if (next === code) { - size = code === 62 && this.input.charCodeAt(this.state.pos + 2) === 62 ? 3 : 2; - if (this.input.charCodeAt(this.state.pos + size) === 61) return this.finishOp(tt.assign, size + 1); + size = + code === 62 && this.input.charCodeAt(this.state.pos + 2) === 62 ? 3 : 2; + if (this.input.charCodeAt(this.state.pos + size) === 61) + return this.finishOp(tt.assign, size + 1); return this.finishOp(tt.bitShift, size); } - if (next === 33 && code === 60 && this.input.charCodeAt(this.state.pos + 2) === 45 && this.input.charCodeAt(this.state.pos + 3) === 45) { + if ( + next === 33 && + code === 60 && + this.input.charCodeAt(this.state.pos + 2) === 45 && + this.input.charCodeAt(this.state.pos + 3) === 45 + ) { if (this.inModule) this.unexpected(); // `