diff --git a/packages/babel-core/src/config/index.js b/packages/babel-core/src/config/index.js index 3c8f49a157..a37b7bc55b 100644 --- a/packages/babel-core/src/config/index.js +++ b/packages/babel-core/src/config/index.js @@ -1,5 +1,5 @@ import type Plugin from "./plugin"; -import OptionManager from "./option-manager"; +import manageOptions from "./option-manager"; export type ResolvedConfig = { options: Object, @@ -10,21 +10,5 @@ export type ResolvedConfig = { * Standard API for loading Babel configuration data. Not for public consumption. */ export default function loadConfig(opts: Object): ResolvedConfig|null { - const mergedOpts = new OptionManager().init(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, - }; + return manageOptions(opts); } diff --git a/packages/babel-core/src/config/loading/files/index-browser.js b/packages/babel-core/src/config/loading/files/index-browser.js index 5653c18160..b07fe3b2de 100644 --- a/packages/babel-core/src/config/loading/files/index-browser.js +++ b/packages/babel-core/src/config/loading/files/index-browser.js @@ -25,18 +25,18 @@ export function resolvePreset(name: string, dirname: string): string|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`); } -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`); } -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`); } -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`); } diff --git a/packages/babel-core/src/config/loading/files/plugins.js b/packages/babel-core/src/config/loading/files/plugins.js index 539683a635..e6704d5fe0 100644 --- a/packages/babel-core/src/config/loading/files/plugins.js +++ b/packages/babel-core/src/config/loading/files/plugins.js @@ -26,27 +26,27 @@ export function resolvePreset(presetName: string, dirname: string): string|null 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); if (!filepath) throw new Error(`Plugin ${name} not found relative to ${dirname}`); return { 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); if (!filepath) throw new Error(`Preset ${name} not found relative to ${dirname}`); return { 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); 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 { 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); 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 { filepath, - generator: mod.print, + value: mod.print, }; } diff --git a/packages/babel-core/src/config/option-manager.js b/packages/babel-core/src/config/option-manager.js index 0f9b582812..caf3ead145 100644 --- a/packages/babel-core/src/config/option-manager.js +++ b/packages/babel-core/src/config/option-manager.js @@ -2,31 +2,18 @@ import * as context from "../index"; import Plugin from "./plugin"; import * as messages from "babel-messages"; import defaults from "lodash/defaults"; -import cloneDeepWith from "lodash/cloneDeepWith"; import merge from "./helpers/merge"; import removed from "./removed"; import buildConfigChain from "./build-config-chain"; import path from "path"; +import traverse from "babel-traverse"; +import clone from "lodash/clone"; import { loadPlugin, loadPreset, loadParser, loadGenerator } from "./loading/files"; -type PluginObject = { - pre?: Function; - post?: Function; - manipulateOptions?: Function; - - visitor: ?{ - [key: string]: Function | { - enter?: Function | Array; - exit?: Function | Array; - } - }; -}; - type MergeOptions = { type: "arguments"|"options"|"preset", options?: Object, - extending?: Object, alias: string, loc?: string, dirname?: string @@ -74,289 +61,67 @@ const optionNames = new Set([ "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() { - this.options = OptionManager.createBareOptions(); + this.options = createInitialOptions(); + this.passes = [[]]; } options: Object; - - static memoisedPlugins: Array<{ - container: Function; - plugin: Plugin; - }>; - - static memoisePluginContainer(fn, loc, i, alias) { - for (const cache of (OptionManager.memoisedPlugins: Array)) { - 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]; - }); - } + passes: Array>; /** * 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 - * main options, at presets level it's presets options). + * base options. * * - `alias` is used to output pretty traces back to the original source. * - `loc` is used to point to the original config. * - `dirname` is used to resolve plugins relative to it. */ - mergeOptions({ - type, - options: rawOpts, - extending: extendingOpts, - alias, - loc, - dirname, - }: MergeOptions) { - alias = alias || "foreign"; - if (!rawOpts) return; + mergeOptions(config: MergeOptions, pass?: Array) { + const result = loadConfig(config); - // - if (typeof rawOpts !== "object" || Array.isArray(rawOpts)) { - throw new TypeError(`Invalid options type for ${alias}`); - } + const plugins = result.plugins.map((descriptor) => loadPluginDescriptor(descriptor)); + const presets = result.presets.map((descriptor) => loadPresetDescriptor(descriptor)); - // - const opts = cloneDeepWith(rawOpts, (val) => { - if (val instanceof Plugin) { - return val; - } - }); + const passPerPreset = config.options.passPerPreset; + pass = pass || this.passes[0]; - // - dirname = dirname || process.cwd(); - loc = loc || alias; - - if (type !== "arguments") { - if (opts.filename !== undefined) { - throw new Error(`${alias}.filename is only allowed as a root argument`); + // resolve presets + if (presets.length > 0) { + let presetPasses = null; + if (passPerPreset) { + presetPasses = presets.map(() => []); + // The passes are created in the same order as the preset list, but are inserted before any + // existing additional passes. + this.passes.splice(1, 0, ...presetPasses); } - if (opts.babelrc !== undefined) { - throw new Error(`${alias}.babelrc is only allowed as a root argument`); - } - } - - 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; + presets.forEach((presetConfig, i) => { + this.mergeOptions(presetConfig, presetPasses ? presetPasses[i] : pass); + }); } // resolve plugins - if (opts.plugins) { - if (!Array.isArray(rawOpts.plugins)) throw new Error(`${alias}.plugins should be an array`); - - opts.plugins = OptionManager.normalisePlugins(loc, dirname, opts.plugins); + if (plugins.length > 0) { + pass.unshift(...plugins); } - // resolve presets - 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, 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); + merge(this.options, result.options); } init(opts: Object = {}): Object { @@ -379,6 +144,13 @@ export default class OptionManager { 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) { opts.sourceMaps = true; } @@ -406,8 +178,280 @@ export default class OptionManager { 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, + }; +} diff --git a/packages/babel-core/src/config/plugin.js b/packages/babel-core/src/config/plugin.js index fb637d7cee..5db1e1dd00 100644 --- a/packages/babel-core/src/config/plugin.js +++ b/packages/babel-core/src/config/plugin.js @@ -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 { constructor(plugin: Object, key?: string) { - this.initialized = false; - this.raw = Object.assign({}, plugin); - this.key = this.take("name") || key; + this.key = plugin.name || key; - this.manipulateOptions = this.take("manipulateOptions"); - this.post = this.take("post"); - this.pre = this.take("pre"); - this.visitor = this.normaliseVisitor(clone(this.take("visitor")) || {}); + this.manipulateOptions = plugin.manipulateOptions; + this.post = plugin.post; + this.pre = plugin.pre; + this.visitor = plugin.visitor; } - initialized: boolean; - raw: Object; + key: ?string; manipulateOptions: ?Function; post: ?Function; pre: ?Function; 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 = [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; - } } diff --git a/packages/babel-core/test/api.js b/packages/babel-core/test/api.js index 2f7a078192..e86f2a54af 100644 --- a/packages/babel-core/test/api.js +++ b/packages/babel-core/test/api.js @@ -146,7 +146,7 @@ describe("api", function () { plugins: [__dirname + "/../../babel-plugin-syntax-jsx", false], }); }, - /TypeError: \[BABEL\] unknown: Falsy value found in plugins/ + /Error: \[BABEL\] unknown: Unexpected falsy value: false/ ); }); diff --git a/packages/babel-core/test/option-manager.js b/packages/babel-core/test/option-manager.js index a2cfd86faf..cf2fcaa484 100644 --- a/packages/babel-core/test/option-manager.js +++ b/packages/babel-core/test/option-manager.js @@ -1,23 +1,23 @@ import assert from "assert"; -import OptionManager from "../lib/config/option-manager"; +import manageOptions from "../lib/config/option-manager"; import path from "path"; describe("option-manager", () => { - describe("memoisePluginContainer", () => { - it("throws for babel 5 plugin", () => { - return assert.throws( - () => OptionManager.memoisePluginContainer(({ Plugin }) => new Plugin("object-assign", {})), - /Babel 5 plugin is being run with Babel 6/ - ); - }); + it("throws for babel 5 plugin", () => { + return assert.throws(() => { + manageOptions({ + plugins: [ + ({ Plugin }) => new Plugin("object-assign", {}), + ], + }); + }, /Babel 5 plugin is being run with Babel 6/); }); describe("mergeOptions", () => { it("throws for removed babel 5 options", () => { return assert.throws( () => { - const opt = new OptionManager(); - opt.init({ + manageOptions({ "randomOption": true, }); }, @@ -28,8 +28,7 @@ describe("option-manager", () => { it("throws for removed babel 5 options", () => { return assert.throws( () => { - const opt = new OptionManager(); - opt.init({ + manageOptions({ "auxiliaryComment": true, "blacklist": true, }); @@ -42,12 +41,11 @@ describe("option-manager", () => { it("throws for resolved but erroring preset", () => { return assert.throws( () => { - const opt = new OptionManager(); - opt.init({ + manageOptions({ "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 () { function presetTest(name) { it(name, function () { - const opt = new OptionManager(); - const options = opt.init({ + const { options, passes } = manageOptions({ "presets": [path.join(__dirname, "fixtures/option-manager/presets", name)], }); assert.equal(true, Array.isArray(options.plugins)); assert.equal(1, options.plugins.length); + assert.equal(1, passes.length); + assert.equal(1, passes[0].length); }); } function presetThrowsTest(name, msg) { it(name, function () { - const opt = new OptionManager(); - assert.throws(() => opt.init({ + assert.throws(() => manageOptions({ "presets": [path.join(__dirname, "fixtures/option-manager/presets", name)], }), msg); }); @@ -79,8 +77,8 @@ describe("option-manager", () => { presetTest("es2015_default_function"); presetTest("es2015_default_object"); - presetThrowsTest("es2015_named", /Preset must export a default export when using ES6 modules/); - presetThrowsTest("es2015_invalid", /Unsupported preset format: string/); - presetThrowsTest("es5_invalid", /Unsupported preset format: string/); + presetThrowsTest("es2015_named", /Must export a default export when using ES6 modules/); + presetThrowsTest("es2015_invalid", /Unsupported format: string/); + presetThrowsTest("es5_invalid", /Unsupported format: string/); }); }); diff --git a/packages/babel-messages/src/index.js b/packages/babel-messages/src/index.js index aefd7d46a0..53b559d437 100644 --- a/packages/babel-messages/src/index.js +++ b/packages/babel-messages/src/index.js @@ -40,7 +40,7 @@ export const MESSAGES = { 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", 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", }; /**