Merge pull request #5602 from loganfsmyth/option-manager-cleanup
Refactor OptionManager to be a short class with a bunch of pure helper functions.
This commit is contained in:
commit
ad250ef10a
@ -1,5 +1,5 @@
|
|||||||
import type Plugin from "./plugin";
|
import type Plugin from "./plugin";
|
||||||
import OptionManager from "./option-manager";
|
import manageOptions from "./option-manager";
|
||||||
|
|
||||||
export type ResolvedConfig = {
|
export type ResolvedConfig = {
|
||||||
options: Object,
|
options: Object,
|
||||||
@ -10,21 +10,5 @@ export type ResolvedConfig = {
|
|||||||
* Standard API for loading Babel configuration data. Not for public consumption.
|
* Standard API for loading Babel configuration data. Not for public consumption.
|
||||||
*/
|
*/
|
||||||
export default function loadConfig(opts: Object): ResolvedConfig|null {
|
export default function loadConfig(opts: Object): ResolvedConfig|null {
|
||||||
const mergedOpts = new OptionManager().init(opts);
|
return manageOptions(opts);
|
||||||
if (!mergedOpts) return null;
|
|
||||||
|
|
||||||
let passes = [];
|
|
||||||
if (mergedOpts.plugins) {
|
|
||||||
passes.push(mergedOpts.plugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
// With "passPerPreset" enabled there may still be presets in the options.
|
|
||||||
if (mergedOpts.presets) {
|
|
||||||
passes = passes.concat(mergedOpts.presets.map((preset) => preset.plugins).filter(Boolean));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
options: mergedOpts,
|
|
||||||
passes,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,18 +25,18 @@ export function resolvePreset(name: string, dirname: string): string|null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadPlugin(name: string, dirname: string): { filepath: string, plugin: mixed } {
|
export function loadPlugin(name: string, dirname: string): { filepath: string, value: mixed } {
|
||||||
throw new Error(`Cannot load plugin ${name} relative to ${dirname} in a browser`);
|
throw new Error(`Cannot load plugin ${name} relative to ${dirname} in a browser`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadPreset(name: string, dirname: string): { filepath: string, preset: mixed } {
|
export function loadPreset(name: string, dirname: string): { filepath: string, value: mixed } {
|
||||||
throw new Error(`Cannot load preset ${name} relative to ${dirname} in a browser`);
|
throw new Error(`Cannot load preset ${name} relative to ${dirname} in a browser`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadParser(name: string, dirname: string): { filepath: string, parser: Function } {
|
export function loadParser(name: string, dirname: string): { filepath: string, value: Function } {
|
||||||
throw new Error(`Cannot load parser ${name} relative to ${dirname} in a browser`);
|
throw new Error(`Cannot load parser ${name} relative to ${dirname} in a browser`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadGenerator(name: string, dirname: string): { filepath: string, generator: Function } {
|
export function loadGenerator(name: string, dirname: string): { filepath: string, value: Function } {
|
||||||
throw new Error(`Cannot load generator ${name} relative to ${dirname} in a browser`);
|
throw new Error(`Cannot load generator ${name} relative to ${dirname} in a browser`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,27 +26,27 @@ export function resolvePreset(presetName: string, dirname: string): string|null
|
|||||||
return resolveFromPossibleNames(possibleNames, dirname);
|
return resolveFromPossibleNames(possibleNames, dirname);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadPlugin(name: string, dirname: string): { filepath: string, plugin: mixed } {
|
export function loadPlugin(name: string, dirname: string): { filepath: string, value: mixed } {
|
||||||
const filepath = resolvePlugin(name, dirname);
|
const filepath = resolvePlugin(name, dirname);
|
||||||
if (!filepath) throw new Error(`Plugin ${name} not found relative to ${dirname}`);
|
if (!filepath) throw new Error(`Plugin ${name} not found relative to ${dirname}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filepath,
|
filepath,
|
||||||
plugin: requireModule(filepath),
|
value: requireModule(filepath),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadPreset(name: string, dirname: string): { filepath: string, preset: mixed } {
|
export function loadPreset(name: string, dirname: string): { filepath: string, value: mixed } {
|
||||||
const filepath = resolvePreset(name, dirname);
|
const filepath = resolvePreset(name, dirname);
|
||||||
if (!filepath) throw new Error(`Preset ${name} not found relative to ${dirname}`);
|
if (!filepath) throw new Error(`Preset ${name} not found relative to ${dirname}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filepath,
|
filepath,
|
||||||
preset: requireModule(filepath),
|
value: requireModule(filepath),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadParser(name: string, dirname: string): { filepath: string, parser: Function } {
|
export function loadParser(name: string, dirname: string): { filepath: string, value: Function } {
|
||||||
const filepath = resolveQuiet(name, dirname);
|
const filepath = resolveQuiet(name, dirname);
|
||||||
if (!filepath) throw new Error(`Parser ${name} not found relative to ${dirname}`);
|
if (!filepath) throw new Error(`Parser ${name} not found relative to ${dirname}`);
|
||||||
|
|
||||||
@ -61,11 +61,11 @@ export function loadParser(name: string, dirname: string): { filepath: string, p
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
filepath,
|
filepath,
|
||||||
parser: mod.parse,
|
value: mod.parse,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadGenerator(name: string, dirname: string): { filepath: string, generator: Function } {
|
export function loadGenerator(name: string, dirname: string): { filepath: string, value: Function } {
|
||||||
const filepath = resolveQuiet(name, dirname);
|
const filepath = resolveQuiet(name, dirname);
|
||||||
if (!filepath) throw new Error(`Generator ${name} not found relative to ${dirname}`);
|
if (!filepath) throw new Error(`Generator ${name} not found relative to ${dirname}`);
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export function loadGenerator(name: string, dirname: string): { filepath: string
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
filepath,
|
filepath,
|
||||||
generator: mod.print,
|
value: mod.print,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,31 +2,18 @@ import * as context from "../index";
|
|||||||
import Plugin from "./plugin";
|
import Plugin from "./plugin";
|
||||||
import * as messages from "babel-messages";
|
import * as messages from "babel-messages";
|
||||||
import defaults from "lodash/defaults";
|
import defaults from "lodash/defaults";
|
||||||
import cloneDeepWith from "lodash/cloneDeepWith";
|
|
||||||
import merge from "./helpers/merge";
|
import merge from "./helpers/merge";
|
||||||
import removed from "./removed";
|
import removed from "./removed";
|
||||||
import buildConfigChain from "./build-config-chain";
|
import buildConfigChain from "./build-config-chain";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import traverse from "babel-traverse";
|
||||||
|
import clone from "lodash/clone";
|
||||||
|
|
||||||
import { loadPlugin, loadPreset, loadParser, loadGenerator } from "./loading/files";
|
import { loadPlugin, loadPreset, loadParser, loadGenerator } from "./loading/files";
|
||||||
|
|
||||||
type PluginObject = {
|
|
||||||
pre?: Function;
|
|
||||||
post?: Function;
|
|
||||||
manipulateOptions?: Function;
|
|
||||||
|
|
||||||
visitor: ?{
|
|
||||||
[key: string]: Function | {
|
|
||||||
enter?: Function | Array<Function>;
|
|
||||||
exit?: Function | Array<Function>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type MergeOptions = {
|
type MergeOptions = {
|
||||||
type: "arguments"|"options"|"preset",
|
type: "arguments"|"options"|"preset",
|
||||||
options?: Object,
|
options?: Object,
|
||||||
extending?: Object,
|
|
||||||
alias: string,
|
alias: string,
|
||||||
loc?: string,
|
loc?: string,
|
||||||
dirname?: string
|
dirname?: string
|
||||||
@ -74,289 +61,67 @@ const optionNames = new Set([
|
|||||||
"generatorOpts",
|
"generatorOpts",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export default class OptionManager {
|
const ALLOWED_PLUGIN_KEYS = new Set([
|
||||||
|
"name",
|
||||||
|
"manipulateOptions",
|
||||||
|
"pre",
|
||||||
|
"post",
|
||||||
|
"visitor",
|
||||||
|
"inherits",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default function manageOptions(opts?: Object) {
|
||||||
|
return new OptionManager().init(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
class OptionManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.options = OptionManager.createBareOptions();
|
this.options = createInitialOptions();
|
||||||
|
this.passes = [[]];
|
||||||
}
|
}
|
||||||
|
|
||||||
options: Object;
|
options: Object;
|
||||||
|
passes: Array<Array<Plugin>>;
|
||||||
static memoisedPlugins: Array<{
|
|
||||||
container: Function;
|
|
||||||
plugin: Plugin;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
static memoisePluginContainer(fn, loc, i, alias) {
|
|
||||||
for (const cache of (OptionManager.memoisedPlugins: Array<Object>)) {
|
|
||||||
if (cache.container === fn) return cache.plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
let obj: ?PluginObject;
|
|
||||||
|
|
||||||
if (typeof fn === "function") {
|
|
||||||
obj = fn(context);
|
|
||||||
} else {
|
|
||||||
obj = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof obj === "object") {
|
|
||||||
const plugin = new Plugin(obj, alias);
|
|
||||||
OptionManager.memoisedPlugins.push({
|
|
||||||
container: fn,
|
|
||||||
plugin: plugin,
|
|
||||||
});
|
|
||||||
return plugin;
|
|
||||||
} else {
|
|
||||||
throw new TypeError(messages.get("pluginNotObject", loc, i, typeof obj) + loc + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static createBareOptions() {
|
|
||||||
return {
|
|
||||||
sourceType: "module",
|
|
||||||
babelrc: true,
|
|
||||||
filename: "unknown",
|
|
||||||
code: true,
|
|
||||||
metadata: true,
|
|
||||||
ast: true,
|
|
||||||
comments: true,
|
|
||||||
compact: "auto",
|
|
||||||
highlightCode: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static normalisePlugin(plugin, loc, i, alias) {
|
|
||||||
plugin = plugin.__esModule ? plugin.default : plugin;
|
|
||||||
|
|
||||||
if (!(plugin instanceof Plugin)) {
|
|
||||||
// allow plugin containers to be specified so they don't have to manually require
|
|
||||||
if (typeof plugin === "function" || typeof plugin === "object") {
|
|
||||||
plugin = OptionManager.memoisePluginContainer(plugin, loc, i, alias);
|
|
||||||
} else {
|
|
||||||
throw new TypeError(messages.get("pluginNotFunction", loc, i, typeof plugin));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.init(loc, i);
|
|
||||||
|
|
||||||
return plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
static normalisePlugins(loc, dirname, plugins) {
|
|
||||||
return plugins.map(function (val, i) {
|
|
||||||
let plugin, options;
|
|
||||||
|
|
||||||
if (!val) {
|
|
||||||
throw new TypeError("Falsy value found in plugins");
|
|
||||||
}
|
|
||||||
|
|
||||||
// destructure plugins
|
|
||||||
if (Array.isArray(val)) {
|
|
||||||
[plugin, options] = val;
|
|
||||||
} else {
|
|
||||||
plugin = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
const alias = typeof plugin === "string" ? plugin : `${loc}$${i}`;
|
|
||||||
|
|
||||||
// allow plugins to be specified as strings
|
|
||||||
if (typeof plugin === "string") {
|
|
||||||
plugin = loadPlugin(plugin, dirname).plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin = OptionManager.normalisePlugin(plugin, loc, i, alias);
|
|
||||||
|
|
||||||
return [plugin, options];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is called when we want to merge the input `opts` into the
|
* This is called when we want to merge the input `opts` into the
|
||||||
* base options (passed as the `extendingOpts`: at top-level it's the
|
* base options.
|
||||||
* main options, at presets level it's presets options).
|
|
||||||
*
|
*
|
||||||
* - `alias` is used to output pretty traces back to the original source.
|
* - `alias` is used to output pretty traces back to the original source.
|
||||||
* - `loc` is used to point to the original config.
|
* - `loc` is used to point to the original config.
|
||||||
* - `dirname` is used to resolve plugins relative to it.
|
* - `dirname` is used to resolve plugins relative to it.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
mergeOptions({
|
mergeOptions(config: MergeOptions, pass?: Array<Plugin>) {
|
||||||
type,
|
const result = loadConfig(config);
|
||||||
options: rawOpts,
|
|
||||||
extending: extendingOpts,
|
|
||||||
alias,
|
|
||||||
loc,
|
|
||||||
dirname,
|
|
||||||
}: MergeOptions) {
|
|
||||||
alias = alias || "foreign";
|
|
||||||
if (!rawOpts) return;
|
|
||||||
|
|
||||||
//
|
const plugins = result.plugins.map((descriptor) => loadPluginDescriptor(descriptor));
|
||||||
if (typeof rawOpts !== "object" || Array.isArray(rawOpts)) {
|
const presets = result.presets.map((descriptor) => loadPresetDescriptor(descriptor));
|
||||||
throw new TypeError(`Invalid options type for ${alias}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
const passPerPreset = config.options.passPerPreset;
|
||||||
const opts = cloneDeepWith(rawOpts, (val) => {
|
pass = pass || this.passes[0];
|
||||||
if (val instanceof Plugin) {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
// resolve presets
|
||||||
dirname = dirname || process.cwd();
|
if (presets.length > 0) {
|
||||||
loc = loc || alias;
|
let presetPasses = null;
|
||||||
|
if (passPerPreset) {
|
||||||
if (type !== "arguments") {
|
presetPasses = presets.map(() => []);
|
||||||
if (opts.filename !== undefined) {
|
// The passes are created in the same order as the preset list, but are inserted before any
|
||||||
throw new Error(`${alias}.filename is only allowed as a root argument`);
|
// existing additional passes.
|
||||||
|
this.passes.splice(1, 0, ...presetPasses);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.babelrc !== undefined) {
|
presets.forEach((presetConfig, i) => {
|
||||||
throw new Error(`${alias}.babelrc is only allowed as a root argument`);
|
this.mergeOptions(presetConfig, presetPasses ? presetPasses[i] : pass);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "preset") {
|
|
||||||
if (opts.only !== undefined) throw new Error(`${alias}.only is not supported in a preset`);
|
|
||||||
if (opts.ignore !== undefined) throw new Error(`${alias}.ignore is not supported in a preset`);
|
|
||||||
if (opts.extends !== undefined) throw new Error(`${alias}.extends is not supported in a preset`);
|
|
||||||
if (opts.env !== undefined) throw new Error(`${alias}.env is not supported in a preset`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.sourceMap !== undefined) {
|
|
||||||
if (opts.sourceMaps !== undefined) {
|
|
||||||
throw new Error(`Both ${alias}.sourceMap and .sourceMaps have been set`);
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.sourceMaps = opts.sourceMap;
|
|
||||||
delete opts.sourceMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in opts) {
|
|
||||||
// check for an unknown option
|
|
||||||
if (!optionNames.has(key)) {
|
|
||||||
if (removed[key]) {
|
|
||||||
throw new ReferenceError(`Using removed Babel 5 option: ${alias}.${key} - ${removed[key].message}`);
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const unknownOptErr = `Unknown option: ${alias}.${key}. Check out http://babeljs.io/docs/usage/options/ for more information about options.`;
|
|
||||||
|
|
||||||
throw new ReferenceError(unknownOptErr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.parserOpts && typeof opts.parserOpts.parser === "string") {
|
|
||||||
opts.parserOpts.parser = loadParser(opts.parserOpts.parser, dirname).parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.generatorOpts && typeof opts.generatorOpts.generator === "string") {
|
|
||||||
opts.generatorOpts.generator = loadGenerator(opts.generatorOpts.generator, dirname).generator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve plugins
|
// resolve plugins
|
||||||
if (opts.plugins) {
|
if (plugins.length > 0) {
|
||||||
if (!Array.isArray(rawOpts.plugins)) throw new Error(`${alias}.plugins should be an array`);
|
pass.unshift(...plugins);
|
||||||
|
|
||||||
opts.plugins = OptionManager.normalisePlugins(loc, dirname, opts.plugins);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve presets
|
merge(this.options, result.options);
|
||||||
if (opts.presets) {
|
|
||||||
if (!Array.isArray(rawOpts.presets)) throw new Error(`${alias}.presets should be an array`);
|
|
||||||
|
|
||||||
opts.presets = this.resolvePresets(opts.presets, dirname, (preset, presetLoc) => {
|
|
||||||
this.mergeOptions({
|
|
||||||
type: "preset",
|
|
||||||
options: preset,
|
|
||||||
|
|
||||||
// For `passPerPreset` we merge child options back into the preset object instead of the root.
|
|
||||||
extending: opts.passPerPreset ? preset : null,
|
|
||||||
alias: presetLoc,
|
|
||||||
loc: presetLoc,
|
|
||||||
dirname: dirname,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// If not passPerPreset, the plugins have all been merged into the parent config so the presets
|
|
||||||
// list is not needed.
|
|
||||||
if (!opts.passPerPreset) delete opts.presets;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge them into current extending options in case of top-level
|
|
||||||
// options. In case of presets, just re-assign options which are got
|
|
||||||
// normalized during the `mergeOptions`.
|
|
||||||
if (rawOpts === extendingOpts) {
|
|
||||||
Object.assign(extendingOpts, opts);
|
|
||||||
} else {
|
|
||||||
merge(extendingOpts || this.options, opts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves presets options which can be either direct object data,
|
|
||||||
* or a module name to require.
|
|
||||||
*/
|
|
||||||
resolvePresets(presets: Array<string | Object>, dirname: string, onResolve?) {
|
|
||||||
return presets.map((preset) => {
|
|
||||||
let options;
|
|
||||||
if (Array.isArray(preset)) {
|
|
||||||
if (preset.length > 2) {
|
|
||||||
throw new Error(`Unexpected extra options ${JSON.stringify(preset.slice(2))} passed to preset.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
[preset, options] = preset;
|
|
||||||
}
|
|
||||||
|
|
||||||
let presetLoc;
|
|
||||||
try {
|
|
||||||
if (typeof preset === "string") {
|
|
||||||
({
|
|
||||||
filepath: presetLoc,
|
|
||||||
preset,
|
|
||||||
} = loadPreset(preset, dirname));
|
|
||||||
}
|
|
||||||
const resolvedPreset = this.loadPreset(preset, options, { dirname });
|
|
||||||
|
|
||||||
if (onResolve) onResolve(resolvedPreset, presetLoc);
|
|
||||||
|
|
||||||
return resolvedPreset;
|
|
||||||
} catch (e) {
|
|
||||||
if (presetLoc) {
|
|
||||||
e.message += ` (While processing preset: ${JSON.stringify(presetLoc)})`;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to load one preset. The input is either the module name of the preset,
|
|
||||||
* a function, or an object
|
|
||||||
*/
|
|
||||||
loadPreset(preset, options, meta) {
|
|
||||||
let presetFactory = preset;
|
|
||||||
|
|
||||||
if (typeof presetFactory === "object" && presetFactory.__esModule) {
|
|
||||||
if (presetFactory.default) {
|
|
||||||
presetFactory = presetFactory.default;
|
|
||||||
} else {
|
|
||||||
throw new Error("Preset must export a default export when using ES6 modules.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow simple object exports
|
|
||||||
if (typeof presetFactory === "object") {
|
|
||||||
return presetFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof presetFactory !== "function") {
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
throw new Error(`Unsupported preset format: ${typeof presetFactory}. Expected preset to return a function.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return presetFactory(context, options, meta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init(opts: Object = {}): Object {
|
init(opts: Object = {}): Object {
|
||||||
@ -379,6 +144,13 @@ export default class OptionManager {
|
|||||||
|
|
||||||
opts = this.options;
|
opts = this.options;
|
||||||
|
|
||||||
|
// Tack the passes onto the object itself so that, if this object is passed back to Babel a second time,
|
||||||
|
// it will be in the right structure to not change behavior.
|
||||||
|
opts.plugins = this.passes[0];
|
||||||
|
opts.presets = this.passes.slice(1)
|
||||||
|
.filter((plugins) => plugins.length > 0)
|
||||||
|
.map((plugins) => ({ plugins }));
|
||||||
|
|
||||||
if (opts.inputSourceMap) {
|
if (opts.inputSourceMap) {
|
||||||
opts.sourceMaps = true;
|
opts.sourceMaps = true;
|
||||||
}
|
}
|
||||||
@ -406,8 +178,280 @@ export default class OptionManager {
|
|||||||
sourceMapTarget: basenameRelative,
|
sourceMapTarget: basenameRelative,
|
||||||
});
|
});
|
||||||
|
|
||||||
return opts;
|
return {
|
||||||
|
options: opts,
|
||||||
|
passes: this.passes,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionManager.memoisedPlugins = [];
|
/**
|
||||||
|
* Load and validate the given config into a set of options, plugins, and presets.
|
||||||
|
*/
|
||||||
|
function loadConfig(config) {
|
||||||
|
const options = normalizeOptions(config);
|
||||||
|
|
||||||
|
const plugins = (config.options.plugins || []).map((plugin, index) => {
|
||||||
|
const { filepath, value, options } = normalizePair(plugin, loadPlugin, config.dirname);
|
||||||
|
|
||||||
|
return {
|
||||||
|
alias: filepath || `${config.loc}$${index}`,
|
||||||
|
loc: filepath || config.loc,
|
||||||
|
value,
|
||||||
|
options,
|
||||||
|
dirname: config.dirname,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const presets = (config.options.presets || []).map((preset, index) => {
|
||||||
|
const { filepath, value, options } = normalizePair(preset, loadPreset, config.dirname);
|
||||||
|
|
||||||
|
return {
|
||||||
|
alias: filepath || `${config.loc}$${index}`,
|
||||||
|
loc: filepath || config.loc,
|
||||||
|
value,
|
||||||
|
options,
|
||||||
|
dirname: config.dirname,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { options, plugins, presets };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a generic plugin/preset from the given descriptor loaded from the config object.
|
||||||
|
*/
|
||||||
|
function loadDescriptor(descriptor, skipOptions) {
|
||||||
|
if (typeof descriptor.value !== "function") return { value: descriptor.value, descriptor };
|
||||||
|
|
||||||
|
const { value, options } = descriptor;
|
||||||
|
let item;
|
||||||
|
try {
|
||||||
|
if (skipOptions) {
|
||||||
|
item = value(context);
|
||||||
|
} else {
|
||||||
|
item = value(context, options, { dirname: descriptor.dirname });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (descriptor.alias) e.message += ` (While processing: ${JSON.stringify(descriptor.alias)})`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item || typeof item !== "object") {
|
||||||
|
throw new Error("Plugin/Preset did not return an object.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { value: item, descriptor };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a plugin for the given descriptor, returning the plugin/options pair.
|
||||||
|
*/
|
||||||
|
const PLUGIN_CACHE = new WeakMap();
|
||||||
|
function loadPluginDescriptor(descriptor) {
|
||||||
|
if (descriptor.value instanceof Plugin) return [ descriptor.value, descriptor.options ];
|
||||||
|
|
||||||
|
let result = PLUGIN_CACHE.get(descriptor.value);
|
||||||
|
if (!result) {
|
||||||
|
result = instantiatePlugin(loadDescriptor(descriptor, true /* skipOptions */));
|
||||||
|
PLUGIN_CACHE.set(descriptor.value, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ result, descriptor.options];
|
||||||
|
}
|
||||||
|
|
||||||
|
function instantiatePlugin({ value: pluginObject, descriptor }) {
|
||||||
|
Object.keys(pluginObject).forEach((key) => {
|
||||||
|
if (!ALLOWED_PLUGIN_KEYS.has(key)) {
|
||||||
|
throw new Error(messages.get("pluginInvalidProperty", descriptor.alias, key));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (pluginObject.visitor && (pluginObject.visitor.enter || pluginObject.visitor.exit)) {
|
||||||
|
throw new Error("Plugins aren't allowed to specify catch-all enter/exit handlers. " +
|
||||||
|
"Please target individual nodes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugin = Object.assign({}, pluginObject, {
|
||||||
|
visitor: clone(pluginObject.visitor || {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
traverse.explode(plugin.visitor);
|
||||||
|
|
||||||
|
let inheritsDescriptor;
|
||||||
|
let inherits;
|
||||||
|
if (plugin.inherits) {
|
||||||
|
inheritsDescriptor = {
|
||||||
|
alias: `${descriptor.loc}$inherits`,
|
||||||
|
loc: descriptor.loc,
|
||||||
|
value: plugin.inherits,
|
||||||
|
options: descriptor.options,
|
||||||
|
};
|
||||||
|
|
||||||
|
inherits = loadPluginDescriptor(inheritsDescriptor)[0];
|
||||||
|
|
||||||
|
plugin.pre = chain(inherits.pre, plugin.pre);
|
||||||
|
plugin.post = chain(inherits.post, plugin.post);
|
||||||
|
plugin.manipulateOptions = chain(inherits.manipulateOptions, plugin.manipulateOptions);
|
||||||
|
plugin.visitor = traverse.visitors.merge([inherits.visitor, plugin.visitor]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Plugin(plugin, descriptor.alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a config object that will act as the root of a new nested config.
|
||||||
|
*/
|
||||||
|
function loadPresetDescriptor(descriptor) {
|
||||||
|
return {
|
||||||
|
type: "preset",
|
||||||
|
options: loadDescriptor(descriptor).value,
|
||||||
|
alias: descriptor.alias,
|
||||||
|
loc: descriptor.loc,
|
||||||
|
dirname: descriptor.dirname,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate and return the options object for the config.
|
||||||
|
*/
|
||||||
|
function normalizeOptions(config) {
|
||||||
|
const alias = config.alias || "foreign";
|
||||||
|
const type = config.type;
|
||||||
|
|
||||||
|
//
|
||||||
|
if (typeof config.options !== "object" || Array.isArray(config.options)) {
|
||||||
|
throw new TypeError(`Invalid options type for ${alias}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
const options = Object.assign({}, config.options);
|
||||||
|
|
||||||
|
if (type !== "arguments") {
|
||||||
|
if (options.filename !== undefined) {
|
||||||
|
throw new Error(`${alias}.filename is only allowed as a root argument`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.babelrc !== undefined) {
|
||||||
|
throw new Error(`${alias}.babelrc is only allowed as a root argument`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "preset") {
|
||||||
|
if (options.only !== undefined) throw new Error(`${alias}.only is not supported in a preset`);
|
||||||
|
if (options.ignore !== undefined) throw new Error(`${alias}.ignore is not supported in a preset`);
|
||||||
|
if (options.extends !== undefined) throw new Error(`${alias}.extends is not supported in a preset`);
|
||||||
|
if (options.env !== undefined) throw new Error(`${alias}.env is not supported in a preset`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.sourceMap !== undefined) {
|
||||||
|
if (options.sourceMaps !== undefined) {
|
||||||
|
throw new Error(`Both ${alias}.sourceMap and .sourceMaps have been set`);
|
||||||
|
}
|
||||||
|
|
||||||
|
options.sourceMaps = options.sourceMap;
|
||||||
|
delete options.sourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in options) {
|
||||||
|
// check for an unknown option
|
||||||
|
if (!optionNames.has(key)) {
|
||||||
|
if (removed[key]) {
|
||||||
|
throw new ReferenceError(`Using removed Babel 5 option: ${alias}.${key} - ${removed[key].message}`);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const unknownOptErr = `Unknown option: ${alias}.${key}. Check out http://babeljs.io/docs/usage/options/ for more information about options.`;
|
||||||
|
|
||||||
|
throw new ReferenceError(unknownOptErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.parserOpts && typeof options.parserOpts.parser === "string") {
|
||||||
|
options.parserOpts = Object.assign({}, options.parserOpts);
|
||||||
|
options.parserOpts.parser = loadParser(options.parserOpts.parser, config.dirname).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.generatorOpts && typeof options.generatorOpts.generator === "string") {
|
||||||
|
options.generatorOpts = Object.assign({}, options.generatorOpts);
|
||||||
|
options.generatorOpts.generator = loadGenerator(options.generatorOpts.generator, config.dirname).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.options.presets && !Array.isArray(config.options.presets)) {
|
||||||
|
throw new Error(`${alias}.presets should be an array`);
|
||||||
|
}
|
||||||
|
if (config.options.plugins && !Array.isArray(config.options.plugins)) {
|
||||||
|
throw new Error(`${alias}.plugins should be an array`);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete options.passPerPreset;
|
||||||
|
delete options.plugins;
|
||||||
|
delete options.presets;
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a plugin/preset item, resolve it into a standard format.
|
||||||
|
*/
|
||||||
|
function normalizePair(pair, resolver, dirname) {
|
||||||
|
let options;
|
||||||
|
let value = pair;
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (value.length > 2) {
|
||||||
|
throw new Error(`Unexpected extra options ${JSON.stringify(value.slice(2))}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
[value, options] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let filepath = null;
|
||||||
|
if (typeof value === "string") {
|
||||||
|
({
|
||||||
|
filepath,
|
||||||
|
value,
|
||||||
|
} = resolver(value, dirname));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "object" && value.__esModule) {
|
||||||
|
if (value.default) {
|
||||||
|
value = value.default;
|
||||||
|
} else {
|
||||||
|
throw new Error("Must export a default export when using ES6 modules.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
throw new Error(`Unexpected falsy value: ${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = typeof value;
|
||||||
|
if (type !== "object" && type !== "function") {
|
||||||
|
throw new Error(`Unsupported format: ${type}. Expected an object or a function.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { filepath, value, options };
|
||||||
|
}
|
||||||
|
|
||||||
|
function chain(a, b) {
|
||||||
|
const fns = [a, b].filter(Boolean);
|
||||||
|
if (fns.length <= 1) return fns[0];
|
||||||
|
|
||||||
|
return function(...args) {
|
||||||
|
for (const fn of fns) {
|
||||||
|
fn.apply(this, args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInitialOptions() {
|
||||||
|
return {
|
||||||
|
sourceType: "module",
|
||||||
|
babelrc: true,
|
||||||
|
filename: "unknown",
|
||||||
|
code: true,
|
||||||
|
metadata: true,
|
||||||
|
ast: true,
|
||||||
|
comments: true,
|
||||||
|
compact: "auto",
|
||||||
|
highlightCode: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -1,90 +1,16 @@
|
|||||||
import OptionManager from "./option-manager";
|
|
||||||
import * as messages from "babel-messages";
|
|
||||||
import traverse from "babel-traverse";
|
|
||||||
import clone from "lodash/clone";
|
|
||||||
|
|
||||||
const GLOBAL_VISITOR_PROPS = ["enter", "exit"];
|
|
||||||
|
|
||||||
export default class Plugin {
|
export default class Plugin {
|
||||||
constructor(plugin: Object, key?: string) {
|
constructor(plugin: Object, key?: string) {
|
||||||
this.initialized = false;
|
this.key = plugin.name || key;
|
||||||
this.raw = Object.assign({}, plugin);
|
|
||||||
this.key = this.take("name") || key;
|
|
||||||
|
|
||||||
this.manipulateOptions = this.take("manipulateOptions");
|
this.manipulateOptions = plugin.manipulateOptions;
|
||||||
this.post = this.take("post");
|
this.post = plugin.post;
|
||||||
this.pre = this.take("pre");
|
this.pre = plugin.pre;
|
||||||
this.visitor = this.normaliseVisitor(clone(this.take("visitor")) || {});
|
this.visitor = plugin.visitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialized: boolean;
|
key: ?string;
|
||||||
raw: Object;
|
|
||||||
manipulateOptions: ?Function;
|
manipulateOptions: ?Function;
|
||||||
post: ?Function;
|
post: ?Function;
|
||||||
pre: ?Function;
|
pre: ?Function;
|
||||||
visitor: Object;
|
visitor: Object;
|
||||||
|
|
||||||
take(key) {
|
|
||||||
const val = this.raw[key];
|
|
||||||
delete this.raw[key];
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
chain(target, key) {
|
|
||||||
if (!target[key]) return this[key];
|
|
||||||
if (!this[key]) return target[key];
|
|
||||||
|
|
||||||
const fns: Array<?Function> = [target[key], this[key]];
|
|
||||||
|
|
||||||
return function (...args) {
|
|
||||||
let val;
|
|
||||||
for (const fn of fns) {
|
|
||||||
if (fn) {
|
|
||||||
const ret = fn.apply(this, args);
|
|
||||||
if (ret != null) val = ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
maybeInherit(loc: string) {
|
|
||||||
let inherits = this.take("inherits");
|
|
||||||
if (!inherits) return;
|
|
||||||
|
|
||||||
inherits = OptionManager.normalisePlugin(inherits, loc, "inherits");
|
|
||||||
|
|
||||||
this.manipulateOptions = this.chain(inherits, "manipulateOptions");
|
|
||||||
this.post = this.chain(inherits, "post");
|
|
||||||
this.pre = this.chain(inherits, "pre");
|
|
||||||
this.visitor = traverse.visitors.merge([inherits.visitor, this.visitor]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We lazy initialise parts of a plugin that rely on contextual information such as
|
|
||||||
* position on disk and how it was specified.
|
|
||||||
*/
|
|
||||||
|
|
||||||
init(loc: string, i: number) {
|
|
||||||
if (this.initialized) return;
|
|
||||||
this.initialized = true;
|
|
||||||
|
|
||||||
this.maybeInherit(loc);
|
|
||||||
|
|
||||||
for (const key in this.raw) {
|
|
||||||
throw new Error(messages.get("pluginInvalidProperty", loc, i, key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
normaliseVisitor(visitor: Object): Object {
|
|
||||||
for (const key of GLOBAL_VISITOR_PROPS) {
|
|
||||||
if (visitor[key]) {
|
|
||||||
throw new Error("Plugins aren't allowed to specify catch-all enter/exit handlers. " +
|
|
||||||
"Please target individual nodes.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
traverse.explode(visitor);
|
|
||||||
return visitor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -146,7 +146,7 @@ describe("api", function () {
|
|||||||
plugins: [__dirname + "/../../babel-plugin-syntax-jsx", false],
|
plugins: [__dirname + "/../../babel-plugin-syntax-jsx", false],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/TypeError: \[BABEL\] unknown: Falsy value found in plugins/
|
/Error: \[BABEL\] unknown: Unexpected falsy value: false/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import OptionManager from "../lib/config/option-manager";
|
import manageOptions from "../lib/config/option-manager";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
describe("option-manager", () => {
|
describe("option-manager", () => {
|
||||||
describe("memoisePluginContainer", () => {
|
it("throws for babel 5 plugin", () => {
|
||||||
it("throws for babel 5 plugin", () => {
|
return assert.throws(() => {
|
||||||
return assert.throws(
|
manageOptions({
|
||||||
() => OptionManager.memoisePluginContainer(({ Plugin }) => new Plugin("object-assign", {})),
|
plugins: [
|
||||||
/Babel 5 plugin is being run with Babel 6/
|
({ Plugin }) => new Plugin("object-assign", {}),
|
||||||
);
|
],
|
||||||
});
|
});
|
||||||
|
}, /Babel 5 plugin is being run with Babel 6/);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("mergeOptions", () => {
|
describe("mergeOptions", () => {
|
||||||
it("throws for removed babel 5 options", () => {
|
it("throws for removed babel 5 options", () => {
|
||||||
return assert.throws(
|
return assert.throws(
|
||||||
() => {
|
() => {
|
||||||
const opt = new OptionManager();
|
manageOptions({
|
||||||
opt.init({
|
|
||||||
"randomOption": true,
|
"randomOption": true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -28,8 +28,7 @@ describe("option-manager", () => {
|
|||||||
it("throws for removed babel 5 options", () => {
|
it("throws for removed babel 5 options", () => {
|
||||||
return assert.throws(
|
return assert.throws(
|
||||||
() => {
|
() => {
|
||||||
const opt = new OptionManager();
|
manageOptions({
|
||||||
opt.init({
|
|
||||||
"auxiliaryComment": true,
|
"auxiliaryComment": true,
|
||||||
"blacklist": true,
|
"blacklist": true,
|
||||||
});
|
});
|
||||||
@ -42,12 +41,11 @@ describe("option-manager", () => {
|
|||||||
it("throws for resolved but erroring preset", () => {
|
it("throws for resolved but erroring preset", () => {
|
||||||
return assert.throws(
|
return assert.throws(
|
||||||
() => {
|
() => {
|
||||||
const opt = new OptionManager();
|
manageOptions({
|
||||||
opt.init({
|
|
||||||
"presets": [path.join(__dirname, "fixtures/option-manager/not-a-preset")],
|
"presets": [path.join(__dirname, "fixtures/option-manager/not-a-preset")],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/While processing preset: .*option-manager(?:\/|\\\\)not-a-preset\.js/
|
/While processing: .*option-manager(?:\/|\\\\)not-a-preset\.js/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -55,20 +53,20 @@ describe("option-manager", () => {
|
|||||||
describe("presets", function () {
|
describe("presets", function () {
|
||||||
function presetTest(name) {
|
function presetTest(name) {
|
||||||
it(name, function () {
|
it(name, function () {
|
||||||
const opt = new OptionManager();
|
const { options, passes } = manageOptions({
|
||||||
const options = opt.init({
|
|
||||||
"presets": [path.join(__dirname, "fixtures/option-manager/presets", name)],
|
"presets": [path.join(__dirname, "fixtures/option-manager/presets", name)],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(true, Array.isArray(options.plugins));
|
assert.equal(true, Array.isArray(options.plugins));
|
||||||
assert.equal(1, options.plugins.length);
|
assert.equal(1, options.plugins.length);
|
||||||
|
assert.equal(1, passes.length);
|
||||||
|
assert.equal(1, passes[0].length);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function presetThrowsTest(name, msg) {
|
function presetThrowsTest(name, msg) {
|
||||||
it(name, function () {
|
it(name, function () {
|
||||||
const opt = new OptionManager();
|
assert.throws(() => manageOptions({
|
||||||
assert.throws(() => opt.init({
|
|
||||||
"presets": [path.join(__dirname, "fixtures/option-manager/presets", name)],
|
"presets": [path.join(__dirname, "fixtures/option-manager/presets", name)],
|
||||||
}), msg);
|
}), msg);
|
||||||
});
|
});
|
||||||
@ -79,8 +77,8 @@ describe("option-manager", () => {
|
|||||||
presetTest("es2015_default_function");
|
presetTest("es2015_default_function");
|
||||||
presetTest("es2015_default_object");
|
presetTest("es2015_default_object");
|
||||||
|
|
||||||
presetThrowsTest("es2015_named", /Preset must export a default export when using ES6 modules/);
|
presetThrowsTest("es2015_named", /Must export a default export when using ES6 modules/);
|
||||||
presetThrowsTest("es2015_invalid", /Unsupported preset format: string/);
|
presetThrowsTest("es2015_invalid", /Unsupported format: string/);
|
||||||
presetThrowsTest("es5_invalid", /Unsupported preset format: string/);
|
presetThrowsTest("es5_invalid", /Unsupported format: string/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export const MESSAGES = {
|
|||||||
pluginNotObject: "Plugin $2 specified in $1 was expected to return an object when invoked but returned $3",
|
pluginNotObject: "Plugin $2 specified in $1 was expected to return an object when invoked but returned $3",
|
||||||
pluginNotFunction: "Plugin $2 specified in $1 was expected to return a function but returned $3",
|
pluginNotFunction: "Plugin $2 specified in $1 was expected to return a function but returned $3",
|
||||||
pluginUnknown: "Unknown plugin $1 specified in $2 at $3, attempted to resolve relative to $4",
|
pluginUnknown: "Unknown plugin $1 specified in $2 at $3, attempted to resolve relative to $4",
|
||||||
pluginInvalidProperty: "Plugin $2 specified in $1 provided an invalid property of $3",
|
pluginInvalidProperty: "Plugin $1 provided an invalid property of $3",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user