[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
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<Parser> } = {};
/** Get a Parser class with plugins applied. */
function getParserClass(
pluginsFromOptions: $ReadOnlyArray<string>,
): Class<Parser> {
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<Parser> {
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);
}

View File

@ -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<string>,
plugins: PluginList,
strictMode: ?boolean,
ranges: boolean,
tokens: boolean,

View File

@ -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;

View File

@ -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<Parser>) => Class<Parser>,
} = {};
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<string>,
): { [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;
}

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,
};