From ffe04d9195ce6c59b4facd6129c5ff3b0132ecda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 23 May 2018 21:28:05 +0200 Subject: [PATCH] [babylon] Refactor mixin plugins handling & validation (#7999) --- packages/babel-parser/src/index.js | 81 ++++++----------------- packages/babel-parser/src/options.js | 4 +- packages/babel-parser/src/parser/base.js | 3 +- packages/babel-parser/src/parser/index.js | 19 +++--- packages/babel-parser/src/plugin-utils.js | 50 ++++++++++++++ 5 files changed, 84 insertions(+), 73 deletions(-) create mode 100644 packages/babel-parser/src/plugin-utils.js diff --git a/packages/babel-parser/src/index.js b/packages/babel-parser/src/index.js index 23bf9cf9de..d9f8126955 100755 --- a/packages/babel-parser/src/index.js +++ b/packages/babel-parser/src/index.js @@ -1,22 +1,20 @@ // @flow -import type { Options } from "./options"; -import Parser, { plugins } from "./parser"; +import { type Options } from "./options"; +import { + hasPlugin, + validatePlugins, + mixinPluginNames, + mixinPlugins, + type PluginList, +} from "./plugin-utils"; +import Parser from "./parser"; import { types as tokTypes } from "./tokenizer/types"; import "./tokenizer/context"; import type { Expression, File } from "./types"; -import estreePlugin from "./plugins/estree"; -import flowPlugin from "./plugins/flow"; -import jsxPlugin from "./plugins/jsx"; -import typescriptPlugin from "./plugins/typescript"; -plugins.estree = estreePlugin; -plugins.flow = flowPlugin; -plugins.jsx = jsxPlugin; -plugins.typescript = typescriptPlugin; - export function parse(input: string, options?: Options): File { if (options && options.sourceType === "unambiguous") { options = { @@ -55,70 +53,31 @@ export function parseExpression(input: string, options?: Options): Expression { export { tokTypes }; function getParser(options: ?Options, input: string): Parser { - const cls = - options && options.plugins ? getParserClass(options.plugins) : Parser; + let cls = Parser; + if (options && options.plugins) { + validatePlugins(options.plugins); + cls = getParserClass(options.plugins); + } + return new cls(options, input); } const parserClassCache: { [key: string]: Class } = {}; /** Get a Parser class with plugins applied. */ -function getParserClass( - pluginsFromOptions: $ReadOnlyArray, -): Class { - if ( - hasPlugin(pluginsFromOptions, "decorators") && - hasPlugin(pluginsFromOptions, "decorators-legacy") - ) { - throw new Error( - "Cannot use the decorators and decorators-legacy plugin together", - ); - } - - // Filter out just the plugins that have an actual mixin associated with them. - let pluginList = pluginsFromOptions.filter(plugin => { - const p = getPluginName(plugin); - return p === "estree" || p === "flow" || p === "jsx" || p === "typescript"; - }); - - if (hasPlugin(pluginList, "flow")) { - // ensure flow plugin loads last - pluginList = pluginList.filter(p => getPluginName(p) !== "flow"); - pluginList.push("flow"); - } - - if (hasPlugin(pluginList, "flow") && hasPlugin(pluginList, "typescript")) { - throw new Error("Cannot combine flow and typescript plugins."); - } - - if (hasPlugin(pluginList, "typescript")) { - // ensure typescript plugin loads last - pluginList = pluginList.filter(p => getPluginName(p) !== "typescript"); - pluginList.push("typescript"); - } - - if (hasPlugin(pluginList, "estree")) { - // ensure estree plugin loads first - pluginList = pluginList.filter(p => getPluginName(p) !== "estree"); - pluginList.unshift("estree"); - } +function getParserClass(pluginsFromOptions: PluginList): Class { + const pluginList = mixinPluginNames.filter(name => + hasPlugin(pluginsFromOptions, name), + ); const key = pluginList.join("/"); let cls = parserClassCache[key]; if (!cls) { cls = Parser; for (const plugin of pluginList) { - cls = plugins[plugin](cls); + cls = mixinPlugins[plugin](cls); } parserClassCache[key] = cls; } return cls; } - -function getPluginName(plugin) { - return Array.isArray(plugin) ? plugin[0] : plugin; -} - -function hasPlugin(pluginsList, name) { - return pluginsList.some(plugin => getPluginName(plugin) === name); -} diff --git a/packages/babel-parser/src/options.js b/packages/babel-parser/src/options.js index a8d5be16ba..121dde7ef1 100755 --- a/packages/babel-parser/src/options.js +++ b/packages/babel-parser/src/options.js @@ -1,5 +1,7 @@ // @flow +import type { PluginList } from "./plugin-utils"; + // A second optional argument can be given to further configure // the parser process. These options are recognized: @@ -11,7 +13,7 @@ export type Options = { allowReturnOutsideFunction: boolean, allowImportExportEverywhere: boolean, allowSuperOutsideMethod: boolean, - plugins: $ReadOnlyArray, + plugins: PluginList, strictMode: ?boolean, ranges: boolean, tokens: boolean, diff --git a/packages/babel-parser/src/parser/base.js b/packages/babel-parser/src/parser/base.js index e0dc90ddf8..7c84857752 100644 --- a/packages/babel-parser/src/parser/base.js +++ b/packages/babel-parser/src/parser/base.js @@ -4,12 +4,13 @@ import type { Options } from "../options"; import { reservedWords } from "../util/identifier"; import type State from "../tokenizer/state"; +import type { PluginsMap } from "./index"; export default class BaseParser { // Properties set by constructor in index.js options: Options; inModule: boolean; - plugins: { [key: string]: boolean }; + plugins: PluginsMap; filename: ?string; sawUnambiguousESM: boolean = false; diff --git a/packages/babel-parser/src/parser/index.js b/packages/babel-parser/src/parser/index.js index f537950d8a..a84603c41c 100644 --- a/packages/babel-parser/src/parser/index.js +++ b/packages/babel-parser/src/parser/index.js @@ -2,12 +2,13 @@ import type { Options } from "../options"; import type { File } from "../types"; +import type { PluginList } from "../plugin-utils"; import { getOptions } from "../options"; import StatementParser from "./statement"; -export const plugins: { - [name: string]: (superClass: Class) => Class, -} = {}; +export type PluginsMap = { + [key: string]: { [option: string]: any }, +}; export default class Parser extends StatementParser { constructor(options: ?Options, input: string) { @@ -29,13 +30,11 @@ export default class Parser extends StatementParser { } } -function pluginsMap( - pluginList: $ReadOnlyArray, -): { [key: string]: boolean } { - const pluginMap = Object.create(null); - for (const plugin of pluginList) { - const [name, options = {}] = Array.isArray(plugin) ? plugin : [plugin]; - pluginMap[name] = options; +function pluginsMap(plugins: PluginList): PluginsMap { + const pluginMap: PluginsMap = (Object.create(null): Object); + for (const plugin of plugins) { + if (Array.isArray(plugin)) pluginMap[plugin[0]] = plugin[1] || {}; + else pluginMap[plugin] = {}; } return pluginMap; } diff --git a/packages/babel-parser/src/plugin-utils.js b/packages/babel-parser/src/plugin-utils.js new file mode 100644 index 0000000000..d6d2612096 --- /dev/null +++ b/packages/babel-parser/src/plugin-utils.js @@ -0,0 +1,50 @@ +// @flow + +import type Parser from "./parser"; + +export type Plugin = string | [string, Object]; + +export type PluginList = $ReadOnlyArray; + +export type MixinPlugin = (superClass: Class) => Class; + +export function hasPlugin(plugins: PluginList, name: string): boolean { + return plugins.some(plugin => { + if (Array.isArray(plugin)) { + return plugin[0] === name; + } else { + return plugin === name; + } + }); +} + +export function validatePlugins(plugins: PluginList) { + if ( + hasPlugin(plugins, "decorators") && + hasPlugin(plugins, "decorators-legacy") + ) { + throw new Error( + "Cannot use the decorators and decorators-legacy plugin together", + ); + } + + if (hasPlugin(plugins, "flow") && hasPlugin(plugins, "typescript")) { + throw new Error("Cannot combine flow and typescript plugins."); + } +} + +// These plugins are defined using a mixin which extends the parser class. + +import estree from "./plugins/estree"; +import flow from "./plugins/flow"; +import jsx from "./plugins/jsx"; +import typescript from "./plugins/typescript"; + +// NOTE: estree must load first; flow and typescript must load last. +export const mixinPluginNames = ["estree", "jsx", "flow", "typescript"]; +export const mixinPlugins: { [name: string]: MixinPlugin } = { + estree, + jsx, + flow, + typescript, +};