[babylon] Refactor mixin plugins handling & validation (#7999)

This commit is contained in:
Nicolò Ribaudo 2018-05-23 21:28:05 +02:00 committed by Henry Zhu
parent b33823e7f8
commit ffe04d9195
5 changed files with 84 additions and 73 deletions

View File

@ -1,22 +1,20 @@
// @flow // @flow
import type { Options } from "./options"; import { type Options } from "./options";
import Parser, { plugins } from "./parser"; import {
hasPlugin,
validatePlugins,
mixinPluginNames,
mixinPlugins,
type PluginList,
} from "./plugin-utils";
import Parser from "./parser";
import { types as tokTypes } from "./tokenizer/types"; import { types as tokTypes } from "./tokenizer/types";
import "./tokenizer/context"; import "./tokenizer/context";
import type { Expression, File } from "./types"; 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 { export function parse(input: string, options?: Options): File {
if (options && options.sourceType === "unambiguous") { if (options && options.sourceType === "unambiguous") {
options = { options = {
@ -55,70 +53,31 @@ export function parseExpression(input: string, options?: Options): Expression {
export { tokTypes }; export { tokTypes };
function getParser(options: ?Options, input: string): Parser { function getParser(options: ?Options, input: string): Parser {
const cls = let cls = Parser;
options && options.plugins ? getParserClass(options.plugins) : Parser; if (options && options.plugins) {
validatePlugins(options.plugins);
cls = getParserClass(options.plugins);
}
return new cls(options, input); return new cls(options, input);
} }
const parserClassCache: { [key: string]: Class<Parser> } = {}; const parserClassCache: { [key: string]: Class<Parser> } = {};
/** Get a Parser class with plugins applied. */ /** Get a Parser class with plugins applied. */
function getParserClass( function getParserClass(pluginsFromOptions: PluginList): Class<Parser> {
pluginsFromOptions: $ReadOnlyArray<string>, const pluginList = mixinPluginNames.filter(name =>
): Class<Parser> { hasPlugin(pluginsFromOptions, name),
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");
}
const key = pluginList.join("/"); const key = pluginList.join("/");
let cls = parserClassCache[key]; let cls = parserClassCache[key];
if (!cls) { if (!cls) {
cls = Parser; cls = Parser;
for (const plugin of pluginList) { for (const plugin of pluginList) {
cls = plugins[plugin](cls); cls = mixinPlugins[plugin](cls);
} }
parserClassCache[key] = cls; parserClassCache[key] = cls;
} }
return cls; return cls;
} }
function getPluginName(plugin) {
return Array.isArray(plugin) ? plugin[0] : plugin;
}
function hasPlugin(pluginsList, name) {
return pluginsList.some(plugin => getPluginName(plugin) === name);
}

View File

@ -1,5 +1,7 @@
// @flow // @flow
import type { PluginList } from "./plugin-utils";
// A second optional argument can be given to further configure // A second optional argument can be given to further configure
// the parser process. These options are recognized: // the parser process. These options are recognized:
@ -11,7 +13,7 @@ export type Options = {
allowReturnOutsideFunction: boolean, allowReturnOutsideFunction: boolean,
allowImportExportEverywhere: boolean, allowImportExportEverywhere: boolean,
allowSuperOutsideMethod: boolean, allowSuperOutsideMethod: boolean,
plugins: $ReadOnlyArray<string>, plugins: PluginList,
strictMode: ?boolean, strictMode: ?boolean,
ranges: boolean, ranges: boolean,
tokens: boolean, tokens: boolean,

View File

@ -4,12 +4,13 @@ import type { Options } from "../options";
import { reservedWords } from "../util/identifier"; import { reservedWords } from "../util/identifier";
import type State from "../tokenizer/state"; import type State from "../tokenizer/state";
import type { PluginsMap } from "./index";
export default class BaseParser { export default class BaseParser {
// Properties set by constructor in index.js // Properties set by constructor in index.js
options: Options; options: Options;
inModule: boolean; inModule: boolean;
plugins: { [key: string]: boolean }; plugins: PluginsMap;
filename: ?string; filename: ?string;
sawUnambiguousESM: boolean = false; sawUnambiguousESM: boolean = false;

View File

@ -2,12 +2,13 @@
import type { Options } from "../options"; import type { Options } from "../options";
import type { File } from "../types"; import type { File } from "../types";
import type { PluginList } from "../plugin-utils";
import { getOptions } from "../options"; import { getOptions } from "../options";
import StatementParser from "./statement"; import StatementParser from "./statement";
export const plugins: { export type PluginsMap = {
[name: string]: (superClass: Class<Parser>) => Class<Parser>, [key: string]: { [option: string]: any },
} = {}; };
export default class Parser extends StatementParser { export default class Parser extends StatementParser {
constructor(options: ?Options, input: string) { constructor(options: ?Options, input: string) {
@ -29,13 +30,11 @@ export default class Parser extends StatementParser {
} }
} }
function pluginsMap( function pluginsMap(plugins: PluginList): PluginsMap {
pluginList: $ReadOnlyArray<string>, const pluginMap: PluginsMap = (Object.create(null): Object);
): { [key: string]: boolean } { for (const plugin of plugins) {
const pluginMap = Object.create(null); if (Array.isArray(plugin)) pluginMap[plugin[0]] = plugin[1] || {};
for (const plugin of pluginList) { else pluginMap[plugin] = {};
const [name, options = {}] = Array.isArray(plugin) ? plugin : [plugin];
pluginMap[name] = options;
} }
return pluginMap; return pluginMap;
} }

View File

@ -0,0 +1,50 @@
// @flow
import type Parser from "./parser";
export type Plugin = string | [string, Object];
export type PluginList = $ReadOnlyArray<Plugin>;
export type MixinPlugin = (superClass: Class<Parser>) => Class<Parser>;
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,
};