diff --git a/src/index.js b/src/index.js index c2911f455f..a797b55f15 100755 --- a/src/index.js +++ b/src/index.js @@ -19,11 +19,11 @@ plugins.flow = flowPlugin; plugins.jsx = jsxPlugin; export function parse(input, options) { - return new Parser(options, input).parse(); + return getParser(options, input).parse(); } export function parseExpression(input, options) { - const parser = new Parser(options, input); + const parser = getParser(options, input); if (parser.options.strictMode) { parser.state.strict = true; } @@ -32,3 +32,39 @@ export function parseExpression(input, options) { export { tokTypes }; + +function getParser(options, input) { + const cls = options && options.plugins ? getParserClass(options.plugins) : Parser; + return new cls(options, input); +} + +const parserClassCache = {}; + +/** Get a Parser class with plugins applied. */ +function getParserClass(pluginsFromOptions) { + // Filter out just the plugins that have an actual mixin associated with them. + let pluginList = pluginsFromOptions.filter((p) => p === "estree" || p === "flow" || p === "jsx"); + + if (pluginList.indexOf("flow") >= 0) { + // ensure flow plugin loads last + pluginList = pluginList.filter((plugin) => plugin !== "flow"); + pluginList.push("flow"); + } + + if (pluginList.indexOf("estree") >= 0) { + // ensure estree plugin loads first + pluginList = pluginList.filter((plugin) => plugin !== "estree"); + pluginList.unshift("estree"); + } + + const key = pluginList.join("/"); + let cls = parserClassCache[key]; + if (!cls) { + cls = Parser; + for (const plugin of pluginList) { + cls = plugins[plugin](cls); + } + parserClassCache[key] = cls; + } + return cls; +} diff --git a/src/parser/index.js b/src/parser/index.js index 2a4768affb..9600040c5f 100644 --- a/src/parser/index.js +++ b/src/parser/index.js @@ -12,7 +12,7 @@ export default class Parser extends Tokenizer { this.options = options; this.inModule = this.options.sourceType === "module"; this.input = input; - this.plugins = this.loadPlugins(this.options.plugins); + this.plugins = pluginsMap(this.options.plugins); this.filename = options.sourceFilename; // If enabled, skip leading hashbang line. @@ -33,37 +33,6 @@ export default class Parser extends Tokenizer { return !!this.plugins[name]; } - extend(name: string, f: Function) { - this[name] = f(this[name]); - } - - loadPlugins(pluginList: Array): { [key: string]: boolean } { - const pluginMap = {}; - - if (pluginList.indexOf("flow") >= 0) { - // ensure flow plugin loads last - pluginList = pluginList.filter((plugin) => plugin !== "flow"); - pluginList.push("flow"); - } - - if (pluginList.indexOf("estree") >= 0) { - // ensure estree plugin loads first - pluginList = pluginList.filter((plugin) => plugin !== "estree"); - pluginList.unshift("estree"); - } - - for (const name of pluginList) { - if (!pluginMap[name]) { - pluginMap[name] = true; - - const plugin = plugins[name]; - if (plugin) plugin(this); - } - } - - return pluginMap; - } - parse(): { type: "File", program: { @@ -77,3 +46,11 @@ export default class Parser extends Tokenizer { return this.parseTopLevel(file, program); } } + +function pluginsMap(pluginList: $ReadOnlyArray): { [key: string]: boolean } { + const pluginMap = {}; + for (const name of pluginList) { + pluginMap[name] = true; + } + return pluginMap; +} diff --git a/src/plugins/estree.js b/src/plugins/estree.js index 3502e311bf..585cd7dc2b 100644 --- a/src/plugins/estree.js +++ b/src/plugins/estree.js @@ -43,206 +43,178 @@ function isSimpleProperty(node) { node.method === false; } -export default function (instance) { - instance.extend("checkDeclaration", function(inner) { - return function (node) { - if (isSimpleProperty(node)) { - this.checkDeclaration(node.value); +export default (superClass) => class extends superClass { + checkDeclaration(node) { + if (isSimpleProperty(node)) { + this.checkDeclaration(node.value); + } else { + super.checkDeclaration(node); + } + } + + checkGetterSetterParamCount(prop) { + const paramCount = prop.kind === "get" ? 0 : 1; + if (prop.value.params.length !== paramCount) { + const start = prop.start; + if (prop.kind === "get") { + this.raise(start, "getter should have no params"); } else { - inner.call(this, node); + this.raise(start, "setter should have exactly one param"); } - }; - }); + } + } - instance.extend("checkGetterSetterParamCount", function() { - return function (prop) { - const paramCount = prop.kind === "get" ? 0 : 1; - if (prop.value.params.length !== paramCount) { - const start = prop.start; - if (prop.kind === "get") { - this.raise(start, "getter should have no params"); + checkLVal(expr, isBinding, checkClashes, ...args) { + 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, ...args); + } + } + + checkPropClash(prop, propHash) { + 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, isExpression) { + if (!isExpression && node.body.body.length > 0) { + for (const directive of (node.body.body: Array)) { + if (directive.type === "ExpressionStatement" && directive.expression.type === "Literal") { + if (directive.expression.value === "use strict") return true; } else { - this.raise(start, "setter should have exactly one param"); - } - } - }; - }); - - instance.extend("checkLVal", function(inner) { - return function (expr, isBinding, checkClashes, ...args) { - switch (expr.type) { - case "ObjectPattern": - expr.properties.forEach((prop) => { - this.checkLVal( - prop.type === "Property" ? prop.value : prop, - isBinding, - checkClashes, - "object destructuring pattern" - ); - }); + // Break for the first non literal expression break; - default: - inner.call(this, expr, isBinding, checkClashes, ...args); + } } - }; - }); + } - instance.extend("checkPropClash", function () { - return function (prop, propHash) { - if (prop.computed || !isSimpleProperty(prop)) return; + return false; + } - const key = prop.key; - // It is either an Identifier or a String/NumericLiteral - const name = key.type === "Identifier" ? key.name : String(key.value); + isValidDirective(stmt) { + return stmt.type === "ExpressionStatement" && + stmt.expression.type === "Literal" && + typeof stmt.expression.value === "string" && + (!stmt.expression.extra || !stmt.expression.extra.parenthesized); + } - if (name === "__proto__") { - if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property"); - propHash.proto = true; - } - }; - }); + parseBlockBody(node, ...args) { + super.parseBlockBody(node, ...args); - instance.extend("isStrictBody", function () { - return function (node, isExpression) { - if (!isExpression && node.body.body.length > 0) { - for (const directive of (node.body.body: Array)) { - 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; - } + node.directives.reverse().forEach((directive) => { + node.body.unshift(this.directiveToStmt(directive)); + }); + delete node.directives; + } + + parseClassMethod(classBody, ...args) { + super.parseClassMethod(classBody, ...args); + + const body = classBody.body; + body[body.length - 1].type = "MethodDefinition"; + } + + parseExprAtom(...args) { + 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(...args); + } + } + + parseLiteral(...args) { + const node = super.parseLiteral(...args); + node.raw = node.extra.raw; + delete node.extra; + + return node; + } + + parseMethod(node, ...args) { + let funcNode = this.startNode(); + funcNode.kind = node.kind; // provide kind, so super method correctly sets state + funcNode = super.parseMethod(funcNode, ...args); + delete funcNode.kind; + node.value = this.finishNode(funcNode, "FunctionExpression"); + + return node; + } + + parseObjectMethod(...args) { + const node = super.parseObjectMethod(...args); + + if (node) { + if (node.kind === "method") node.kind = "init"; + node.type = "Property"; + } + + return node; + } + + parseObjectProperty(...args) { + const node = super.parseObjectProperty(...args); + + if (node) { + node.kind = "init"; + node.type = "Property"; + } + + return node; + } + + toAssignable(node, isBinding, ...args) { + if (isSimpleProperty(node)) { + this.toAssignable(node.value, isBinding, ...args); + + return node; + } else if (node.type === "ObjectExpression") { + node.type = "ObjectPattern"; + for (const prop of (node.properties: Array)) { + 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 false; - }; - }); - - instance.extend("isValidDirective", function () { - return function (stmt) { - return stmt.type === "ExpressionStatement" && - stmt.expression.type === "Literal" && - typeof stmt.expression.value === "string" && - (!stmt.expression.extra || !stmt.expression.extra.parenthesized); - }; - }); - - instance.extend("parseBlockBody", function (inner) { - return function (node, ...args) { - inner.call(this, node, ...args); - - node.directives.reverse().forEach((directive) => { - node.body.unshift(this.directiveToStmt(directive)); - }); - delete node.directives; - }; - }); - - instance.extend("parseClassMethod", function (inner) { - return function (classBody, ...args) { - inner.call(this, classBody, ...args); - - const body = classBody.body; - body[body.length - 1].type = "MethodDefinition"; - }; - }); - - instance.extend("parseExprAtom", function(inner) { - return function (...args) { - 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 inner.call(this, ...args); - } - }; - }); - - instance.extend("parseLiteral", function(inner) { - return function (...args) { - const node = inner.call(this, ...args); - node.raw = node.extra.raw; - delete node.extra; - return node; - }; - }); + } - instance.extend("parseMethod", function(inner) { - return function (node, ...args) { - let funcNode = this.startNode(); - funcNode.kind = node.kind; // provide kind, so inner method correctly sets state - funcNode = inner.call(this, funcNode, ...args); - delete funcNode.kind; - node.value = this.finishNode(funcNode, "FunctionExpression"); - - return node; - }; - }); - - instance.extend("parseObjectMethod", function(inner) { - return function (...args) { - const node = inner.call(this, ...args); - - if (node) { - if (node.kind === "method") node.kind = "init"; - node.type = "Property"; - } - - return node; - }; - }); - - instance.extend("parseObjectProperty", function(inner) { - return function (...args) { - const node = inner.call(this, ...args); - - if (node) { - node.kind = "init"; - node.type = "Property"; - } - - return node; - }; - }); - - instance.extend("toAssignable", function(inner) { - return function (node, isBinding, ...args) { - if (isSimpleProperty(node)) { - this.toAssignable(node.value, isBinding, ...args); - - return node; - } else if (node.type === "ObjectExpression") { - node.type = "ObjectPattern"; - for (const prop of (node.properties: Array)) { - 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 inner.call(this, node, isBinding, ...args); - }; - }); -} + return super.toAssignable(node, isBinding, ...args); + } +}; diff --git a/src/plugins/flow.js b/src/plugins/flow.js index 15302e40dd..2eed420119 100644 --- a/src/plugins/flow.js +++ b/src/plugins/flow.js @@ -865,498 +865,433 @@ pp.flowParseVariance = function() { return variance; }; -export default function (instance) { +export default (superClass) => class extends superClass { // plain function return types: function name(): string {} - instance.extend("parseFunctionBody", function (inner) { - return function (node, allowExpression) { - if (this.match(tt.colon) && !allowExpression) { - // if allowExpression is true then we're parsing an arrow function and if - // there's a return type then it's been handled elsewhere - const typeNode = this.startNode(); - [typeNode.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser(); + parseFunctionBody(node, allowExpression) { + if (this.match(tt.colon) && !allowExpression) { + // if allowExpression is true then we're parsing an arrow function and if + // there's a return type then it's been handled elsewhere + const typeNode = this.startNode(); + [typeNode.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser(); - node.returnType = typeNode.typeAnnotation - ? this.finishNode(typeNode, "TypeAnnotation") - : null; - } + node.returnType = typeNode.typeAnnotation + ? this.finishNode(typeNode, "TypeAnnotation") + : null; + } - return inner.call(this, node, allowExpression); - }; - }); + return super.parseFunctionBody(node, allowExpression); + } // interfaces - instance.extend("parseStatement", function (inner) { - return function (declaration, topLevel) { - // 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 inner.call(this, declaration, topLevel); - } - }; - }); + parseStatement(declaration, topLevel) { + // 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 - instance.extend("parseExpressionStatement", function (inner) { - return function (node, expr) { - 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)) { - 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); - } + parseExpressionStatement(node, expr) { + 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)) { + 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 inner.call(this, node, expr); - }; - }); + return super.parseExpressionStatement(node, expr); + } // export type - instance.extend("shouldParseExportDeclaration", function (inner) { - return function () { - return this.isContextual("type") - || this.isContextual("interface") - || inner.call(this); - }; - }); + shouldParseExportDeclaration() { + return this.isContextual("type") + || this.isContextual("interface") + || super.shouldParseExportDeclaration(); + } - instance.extend("parseConditional", function (inner) { - return function (expr, noIn, startPos, startLoc, refNeedsArrowPos) { - // 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 inner.call(this, 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 inner.call(this, expr, noIn, startPos, startLoc); - }; - }); - - instance.extend("parseParenItem", function (inner) { - return function (node, startPos, startLoc) { - node = inner.call(this, 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; - }; - }); - - instance.extend("parseExport", function (inner) { - return function (node) { - node = inner.call(this, node); - if (node.type === "ExportNamedDeclaration") { - node.exportKind = node.exportKind || "value"; - } - return node; - }; - }); - - instance.extend("parseExportDeclaration", function (inner) { - return function (node) { - 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; + parseConditional(expr, noIn, startPos, startLoc, refNeedsArrowPos) { + // 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 { - // export type Foo = Bar; - return this.flowParseTypeAlias(declarationNode); + // istanbul ignore next: no such error is expected + throw err; } - } else if (this.isContextual("interface")) { - node.exportKind = "type"; - const declarationNode = this.startNode(); - this.next(); - return this.flowParseInterface(declarationNode); - } else { - return inner.call(this, node); } - }; - }); + } - instance.extend("parseClassId", function (inner) { - return function (node) { - inner.apply(this, arguments); - if (this.isRelational("<")) { - node.typeParameters = this.flowParseTypeParameterDeclaration(); + return super.parseConditional(expr, noIn, startPos, startLoc); + } + + parseParenItem(node, startPos, startLoc) { + 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) { + node = super.parseExport(node); + if (node.type === "ExportNamedDeclaration") { + node.exportKind = node.exportKind || "value"; + } + return node; + } + + parseExportDeclaration(node) { + 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; + } 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, ...args) { + super.parseClassId(node, ...args); + 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 - instance.extend("isKeyword", function (inner) { - return function (name) { - if (this.state.inType && name === "void") { - return false; - } else { - return inner.call(this, name); - } - }; - }); + isKeyword(name) { + if (this.state.inType && name === "void") { + return false; + } else { + return super.isKeyword(name); + } + } // ensure that inside flow types, we bypass the jsx parser plugin - instance.extend("readToken", function (inner) { - return function (code) { - if (this.state.inType && (code === 62 || code === 60)) { - return this.finishOp(tt.relational, 1); - } else { - return inner.call(this, code); - } - }; - }); + readToken(code) { + if (this.state.inType && (code === 62 || code === 60)) { + return this.finishOp(tt.relational, 1); + } else { + return super.readToken(code); + } + } // don't lex any token as a jsx one inside a flow type - instance.extend("jsx_readToken", function (inner) { - return function () { - if (!this.state.inType) return inner.call(this); - }; - }); + jsx_readToken() { + if (!this.state.inType) return super.jsx_readToken(); + } - instance.extend("toAssignable", function (inner) { - return function (node, isBinding, contextDescription) { - if (node.type === "TypeCastExpression") { - return inner.call(this, this.typeCastToParameter(node), isBinding, contextDescription); - } else { - return inner.call(this, node, isBinding, contextDescription); - } - }; - }); + toAssignable(node, isBinding, contextDescription) { + 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 - instance.extend("toAssignableList", function (inner) { - return function (exprList, isBinding, contextDescription) { - for (let i = 0; i < exprList.length; i++) { - const expr = exprList[i]; - if (expr && expr.type === "TypeCastExpression") { - exprList[i] = this.typeCastToParameter(expr); - } + toAssignableList(exprList, isBinding, contextDescription) { + for (let i = 0; i < exprList.length; i++) { + const expr = exprList[i]; + if (expr && expr.type === "TypeCastExpression") { + exprList[i] = this.typeCastToParameter(expr); } - return inner.call(this, exprList, isBinding, contextDescription); - }; - }); + } + 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 - instance.extend("toReferencedList", function () { - return function (exprList) { - 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"); - } + toReferencedList(exprList) { + 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 exprList; - }; - }); + return exprList; + } // parse an item inside a expression list eg. `(NODE, NODE)` where NODE represents // the position where this function is called - instance.extend("parseExprListItem", function (inner) { - return function (...args) { - const container = this.startNode(); - const node = inner.call(this, ...args); - if (this.match(tt.colon)) { - container._exprListItem = true; - container.expression = node; - container.typeAnnotation = this.flowParseTypeAnnotation(); - return this.finishNode(container, "TypeCastExpression"); - } else { - return node; - } - }; - }); + parseExprListItem(...args) { + const container = this.startNode(); + const node = super.parseExprListItem(...args); + if (this.match(tt.colon)) { + container._exprListItem = true; + container.expression = node; + container.typeAnnotation = this.flowParseTypeAnnotation(); + return this.finishNode(container, "TypeCastExpression"); + } else { + return node; + } + } - instance.extend("checkLVal", function (inner) { - return function (node) { - if (node.type !== "TypeCastExpression") { - return inner.apply(this, arguments); - } - }; - }); + checkLVal(node, ...args) { + if (node.type !== "TypeCastExpression") { + return super.checkLVal(node, ...args); + } + } // parse class property type annotations - instance.extend("parseClassProperty", function (inner) { - return function (node) { - if (this.match(tt.colon)) { - node.typeAnnotation = this.flowParseTypeAnnotation(); - } - return inner.call(this, node); - }; - }); + parseClassProperty(node) { + 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 - instance.extend("isClassMethod", function (inner) { - return function () { - return this.isRelational("<") || inner.call(this); - }; - }); + isClassMethod() { + return this.isRelational("<") || super.isClassMethod(); + } // determine whether or not we're currently in the position where a class property would appear - instance.extend("isClassProperty", function (inner) { - return function () { - return this.match(tt.colon) || inner.call(this); - }; - }); + isClassProperty() { + return this.match(tt.colon) || super.isClassProperty(); + } // parse type parameters for class methods - instance.extend("parseClassMethod", function (inner) { - return function (classBody, method, ...args) { - if (method.variance) { - this.unexpected(method.variance.start); - } - delete method.variance; - if (this.isRelational("<")) { - method.typeParameters = this.flowParseTypeParameterDeclaration(); - } + parseClassMethod(classBody, method, ...args) { + if (method.variance) { + this.unexpected(method.variance.start); + } + delete method.variance; + if (this.isRelational("<")) { + method.typeParameters = this.flowParseTypeParameterDeclaration(); + } - inner.call(this, classBody, method, ...args); - }; - }); + super.parseClassMethod(classBody, method, ...args); + } // parse a the super class type parameters and implements - instance.extend("parseClassSuper", function (inner) { - return function (node, isStatement) { - inner.call(this, node, isStatement); - if (node.superClass && this.isRelational("<")) { - node.superTypeParameters = this.flowParseTypeParameterInstantiation(); - } - if (this.isContextual("implements")) { - this.next(); - const implemented = 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)); - } - }; - }); + parseClassSuper(node, isStatement) { + super.parseClassSuper(node, isStatement); + if (node.superClass && this.isRelational("<")) { + node.superTypeParameters = this.flowParseTypeParameterInstantiation(); + } + if (this.isContextual("implements")) { + this.next(); + const implemented = 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)); + } + } - instance.extend("parsePropertyName", function (inner) { - return function (node) { - const variance = this.flowParseVariance(); - const key = inner.call(this, node); - node.variance = variance; - return key; - }; - }); + parsePropertyName(node) { + const variance = this.flowParseVariance(); + const key = super.parsePropertyName(node); + node.variance = variance; + return key; + } // parse type parameters for object method shorthand - instance.extend("parseObjPropValue", function (inner) { - return function (prop) { - if (prop.variance) { - this.unexpected(prop.variance.start); - } - delete prop.variance; + parseObjPropValue(prop, ...args) { + if (prop.variance) { + this.unexpected(prop.variance.start); + } + delete prop.variance; - let typeParameters; + let typeParameters; - // method shorthand - if (this.isRelational("<")) { - typeParameters = this.flowParseTypeParameterDeclaration(); - if (!this.match(tt.parenL)) this.unexpected(); - } + // method shorthand + if (this.isRelational("<")) { + typeParameters = this.flowParseTypeParameterDeclaration(); + if (!this.match(tt.parenL)) this.unexpected(); + } - inner.apply(this, arguments); + super.parseObjPropValue(prop, ...args); - // add typeParameters if we found them - if (typeParameters) { - (prop.value || prop).typeParameters = typeParameters; - } - }; - }); + // add typeParameters if we found them + if (typeParameters) { + (prop.value || prop).typeParameters = typeParameters; + } + } - instance.extend("parseAssignableListItemTypes", function () { - return function (param) { - if (this.eat(tt.question)) { - param.optional = true; - } - if (this.match(tt.colon)) { - param.typeAnnotation = this.flowParseTypeAnnotation(); - } - this.finishNode(param, param.type); - return param; - }; - }); + parseAssignableListItemTypes(param) { + if (this.eat(tt.question)) { + param.optional = true; + } + if (this.match(tt.colon)) { + param.typeAnnotation = this.flowParseTypeAnnotation(); + } + this.finishNode(param, param.type); + return param; + } - instance.extend("parseMaybeDefault", function (inner) { - return function (...args) { - const node = inner.apply(this, args); + parseMaybeDefault(...args) { + 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; - }; - }); + 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 - instance.extend("parseImportSpecifiers", function (inner) { - return function (node) { - node.importKind = "value"; + parseImportSpecifiers(node) { + 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; - } + 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; } + } - inner.call(this, node); - }; - }); + super.parseImportSpecifiers(node); + } // parse import-type/typeof shorthand - instance.extend("parseImportSpecifier", function () { - return function (node) { - const specifier = this.startNode(); - const firstIdentLoc = this.state.start; - const firstIdent = this.parseIdentifier(true); + parseImportSpecifier(node) { + 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 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); + 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; - if (this.eatContextual("as")) { - specifier.local = this.parseIdentifier(); - } else { - isBinding = true; - specifier.local = specifier.imported.__clone(); - } + specifier.local = as_ident.__clone(); } else { - isBinding = true; + // `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 ( + (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); + 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")); - }; - }); + this.checkLVal(specifier.local, true, undefined, "import specifier"); + node.specifiers.push(this.finishNode(specifier, "ImportSpecifier")); + } // parse function type parameters - function foo() {} - instance.extend("parseFunctionParams", function (inner) { - return function (node) { - if (this.isRelational("<")) { - node.typeParameters = this.flowParseTypeParameterDeclaration(); - } - inner.call(this, node); - }; - }); + parseFunctionParams(node) { + if (this.isRelational("<")) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } + super.parseFunctionParams(node); + } // parse flow type annotations on variable declarator heads - let foo: string = bar - instance.extend("parseVarHead", function (inner) { - return function (decl) { - inner.call(this, decl); - if (this.match(tt.colon)) { - decl.id.typeAnnotation = this.flowParseTypeAnnotation(); - this.finishNode(decl.id, decl.id.type); - } - }; - }); + parseVarHead(decl) { + 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 => {}); - instance.extend("parseAsyncArrowFromCallExpression", function (inner) { - return function (node, call) { - if (this.match(tt.colon)) { - const oldNoAnonFunctionType = this.state.noAnonFunctionType; - this.state.noAnonFunctionType = true; - node.returnType = this.flowParseTypeAnnotation(); - this.state.noAnonFunctionType = oldNoAnonFunctionType; - } + parseAsyncArrowFromCallExpression(node, call) { + if (this.match(tt.colon)) { + const oldNoAnonFunctionType = this.state.noAnonFunctionType; + this.state.noAnonFunctionType = true; + node.returnType = this.flowParseTypeAnnotation(); + this.state.noAnonFunctionType = oldNoAnonFunctionType; + } - return inner.call(this, node, call); - }; - }); + return super.parseAsyncArrowFromCallExpression(node, call); + } // todo description - instance.extend("shouldParseAsyncArrow", function (inner) { - return function () { - return this.match(tt.colon) || inner.call(this); - }; - }); + shouldParseAsyncArrow() { + 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 @@ -1367,99 +1302,93 @@ export default function (instance) { // 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 inner function - instance.extend("parseMaybeAssign", function (inner) { - return function (...args) { - let jsxError = null; - if (tt.jsxTagStart && this.match(tt.jsxTagStart)) { - const state = this.state.clone(); - try { - return inner.apply(this, args); - } catch (err) { - if (err instanceof SyntaxError) { - this.state = state; - jsxError = err; - } else { - // istanbul ignore next: no such error is expected - throw err; - } + // 3. This is neither. Just call the super method + parseMaybeAssign(...args) { + let jsxError = null; + if (tt.jsxTagStart && this.match(tt.jsxTagStart)) { + const state = this.state.clone(); + try { + return super.parseMaybeAssign(...args); + } catch (err) { + if (err instanceof SyntaxError) { + this.state = state; + jsxError = err; + } else { + // istanbul ignore next: no such error is expected + throw err; } } + } - if (jsxError != null || this.isRelational("<")) { - // Need to push something onto the context to stop - // the JSX plugin from messing with the tokens - this.state.context.push(ct.parenExpression); - let arrowExpression; - let typeParameters; - try { - typeParameters = this.flowParseTypeParameterDeclaration(); - - arrowExpression = inner.apply(this, args); - arrowExpression.typeParameters = typeParameters; - this.resetStartLocationFromNode(arrowExpression, typeParameters); - } catch (err) { - this.state.context.pop(); - - throw jsxError || err; - } + if (jsxError != null || this.isRelational("<")) { + // Need to push something onto the context to stop + // the JSX plugin from messing with the tokens + this.state.context.push(ct.parenExpression); + let arrowExpression; + let typeParameters; + try { + typeParameters = this.flowParseTypeParameterDeclaration(); + arrowExpression = super.parseMaybeAssign(...args); + arrowExpression.typeParameters = typeParameters; + this.resetStartLocationFromNode(arrowExpression, typeParameters); + } catch (err) { this.state.context.pop(); - 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", - ); - } + throw jsxError || err; } - return inner.apply(this, args); - }; - }); + this.state.context.pop(); + + 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(...args); + } // handle return types for arrow functions - instance.extend("parseArrow", function (inner) { - return function (node) { - if (this.match(tt.colon)) { - const state = this.state.clone(); - try { - const oldNoAnonFunctionType = this.state.noAnonFunctionType; - this.state.noAnonFunctionType = true; + parseArrow(node) { + if (this.match(tt.colon)) { + const state = this.state.clone(); + try { + const oldNoAnonFunctionType = this.state.noAnonFunctionType; + this.state.noAnonFunctionType = true; - const typeNode = this.startNode(); - [typeNode.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser(); + const typeNode = this.startNode(); + [typeNode.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser(); - this.state.noAnonFunctionType = oldNoAnonFunctionType; + this.state.noAnonFunctionType = oldNoAnonFunctionType; - if (this.canInsertSemicolon()) this.unexpected(); - if (!this.match(tt.arrow)) this.unexpected(); + 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; - } + // 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 inner.call(this, node); - }; - }); + return super.parseArrow(node); + } - instance.extend("shouldParseArrow", function (inner) { - return function () { - return this.match(tt.colon) || inner.call(this); - }; - }); -} + shouldParseArrow() { + return this.match(tt.colon) || super.shouldParseArrow(); + } +}; diff --git a/src/plugins/jsx/index.js b/src/plugins/jsx/index.js index 49231318f4..7309fa9121 100644 --- a/src/plugins/jsx/index.js +++ b/src/plugins/jsx/index.js @@ -395,72 +395,66 @@ pp.jsxParseElement = function() { return this.jsxParseElementAt(startPos, startLoc); }; -export default function(instance) { - instance.extend("parseExprAtom", function(inner) { - return function(refShortHandDefaultPos) { - if (this.match(tt.jsxText)) { - return this.parseLiteral(this.state.value, "JSXText"); - } else if (this.match(tt.jsxTagStart)) { - return this.jsxParseElement(); - } else { - return inner.call(this, refShortHandDefaultPos); - } - }; - }); +export default (superClass) => class extends superClass { + parseExprAtom(refShortHandDefaultPos) { + 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); + } + } - instance.extend("readToken", function(inner) { - return function(code) { - if (this.state.inPropertyName) return inner.call(this, code); + readToken(code) { + if (this.state.inPropertyName) return super.readToken(code); - const context = this.curContext(); + const context = this.curContext(); - if (context === tc.j_expr) { - return this.jsxReadToken(); + if (context === tc.j_expr) { + return this.jsxReadToken(); + } + + if (context === tc.j_oTag || context === tc.j_cTag) { + if (isIdentifierStart(code)) { + return this.jsxReadWord(); } - 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) { + if (code === 62) { ++this.state.pos; - return this.finishToken(tt.jsxTagStart); + return this.finishToken(tt.jsxTagEnd); } - return inner.call(this, code); - }; - }); + if ((code === 34 || code === 39) && context === tc.j_oTag) { + return this.jsxReadString(code); + } + } - instance.extend("updateContext", function(inner) { - return function(prevType) { - 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 { - inner.call(this, 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; + if (code === 60 && this.state.exprAllowed) { + ++this.state.pos; + return this.finishToken(tt.jsxTagStart); + } + + return super.readToken(code); + } + + updateContext(prevType) { + 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 { - return inner.call(this, prevType); + 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); + } + } +};