[babylon] Refactor mixin plugins handling & validation (#7999)
This commit is contained in:
parent
b33823e7f8
commit
ffe04d9195
@ -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);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
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