[babylon] Refactor mixin plugins handling & validation (#7999)
This commit is contained in:
parent
b33823e7f8
commit
ffe04d9195
@ -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);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
50
packages/babel-parser/src/plugin-utils.js
Normal file
50
packages/babel-parser/src/plugin-utils.js
Normal 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,
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user