diff --git a/packages/babel-core/src/transformation/file/options/option-manager.js b/packages/babel-core/src/transformation/file/options/option-manager.js index f1032a0e8d..67eb61b44b 100644 --- a/packages/babel-core/src/transformation/file/options/option-manager.js +++ b/packages/babel-core/src/transformation/file/options/option-manager.js @@ -1,4 +1,8 @@ -import { validateOption, normaliseOptions } from "./index"; +import Plugin from "../../plugin"; +import * as messages from "babel-messages"; +import * as context from "../../../api/node"; +import { normaliseOptions } from "./index"; +import resolve from "../../../helpers/resolve"; import json5 from "json5"; import isAbsolute from "path-is-absolute"; import pathExists from "path-exists"; @@ -32,6 +36,27 @@ export default class OptionManager { this.log = log; } + static memoisedPlugins = []; + + static memoisePluginContainer(fn, loc, i) { + for (var cache of (OptionManager.memoisedPlugins: Array)) { + if (cache.container === fn) return cache.plugin; + } + + var obj = fn(context); + + if (typeof obj === "object") { + var plugin = new Plugin(obj); + OptionManager.memoisedPlugins.push({ + container: fn, + plugin: plugin + }); + return plugin; + } else { + throw new TypeError(messages.get("pluginNotObject", loc, i, typeof obj)); + } + } + static createBareOptions() { var opts = {}; @@ -43,6 +68,41 @@ export default class OptionManager { return opts; } + static normalisePlugins(loc, dirname, plugins) { + return plugins.map(function (val, i) { + var plugin, options; + + // destructure plugins + if (Array.isArray(val)) { + [plugin, options] = val; + } else { + plugin = val; + } + + // allow plugins to be specified as strings + if (typeof plugin === "string") { + var pluginLoc = resolve(`babel-plugin-${plugin}`, dirname) || resolve(plugin, dirname); + if (pluginLoc) { + plugin = require(pluginLoc); + } else { + throw new ReferenceError(messages.get("pluginUnknown", plugin, loc, i)); + } + } + + // allow plugin containers to be specified so they don't have to manually require + if (typeof plugin === "function") { + plugin = OptionManager.memoisePluginContainer(plugin, loc, i); + } else { + throw new TypeError(messages.get("pluginNotFunction", loc, i)); + } + + // validate + plugin.validate(loc, i); + + return [plugin, options]; + }); + } + addConfig(loc, key?, json=json5) { if (this.resolvedConfigs.indexOf(loc) >= 0) return; @@ -61,23 +121,78 @@ export default class OptionManager { this.resolvedConfigs.push(loc); } - mergeOptions(opts, alias = "foreign") { + /** + * This is called when we want to merge the input `opts` into our + * 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(opts, alias = "foreign", loc, dirname) { if (!opts) return; - for (let key in opts) { - if (key[0] === "_") continue; + dirname = dirname || process.cwd(); + loc = loc || alias; + for (let key in opts) { let option = config[key]; // check for an unknown option - if (!option) this.log.error(`Unknown option: ${alias}.${key}`, ReferenceError); + if (!option && this.log) this.log.error(`Unknown option: ${alias}.${key}`, ReferenceError); } // normalise options normaliseOptions(opts); + // resolve plugins + if (opts.plugins) { + opts.plugins = OptionManager.normalisePlugins(loc, dirname, opts.plugins); + } + + // add extends clause + if (opts.extends) { + this.addConfig(resolve(opts.extends, dirname)); + delete opts.extends; + } + + // resolve presets + if (opts.presets) { + this.mergePresets(opts.presets, dirname); + delete opts.presets; + } + + var envOpts; + var envKey = process.env.BABEL_ENV || process.env.NODE_ENV || "development"; + if (opts.env) { + envOpts = opts.env[envKey]; + delete opts.env; + } + // merge them into this current files options merge(this.options, opts); + + // merge in env options + this.mergeOptions(envOpts, `${alias}.env.${envKey}`); + } + + mergePresets(presets: Array, dirname) { + for (var val of presets) { + if (typeof val === "string") { + var presetLoc = resolve(`babel-preset-${val}`, dirname) || resolve(val, dirname); + if (presetLoc) { + var presetOpts = require(presetLoc); + this.mergeOptions(presetOpts, presetLoc, presetLoc, path.dirname(presetLoc)); + } else { + throw new Error("todo"); + } + } else if (typeof val === "object") { + this.mergeOptions(val); + } else { + throw new Error("todo"); + } + } } addIgnoreConfig(loc) { @@ -91,10 +206,6 @@ export default class OptionManager { this.mergeOptions({ ignore: lines }, loc); } - /** - * Description - */ - findConfigs(loc) { if (!loc) return; @@ -102,17 +213,33 @@ export default class OptionManager { loc = path.join(process.cwd(), loc); } + var foundConfig = false; + var foundIgnore = false; + while (loc !== (loc = path.dirname(loc))) { - if (this.options.breakConfig) return; + if (!foundConfig) { + var configLoc = path.join(loc, BABELRC_FILENAME); + if (exists(configLoc)) { + this.addConfig(configLoc); + foundConfig = true; + } - var configLoc = path.join(loc, BABELRC_FILENAME); - if (exists(configLoc)) this.addConfig(configLoc); + var pkgLoc = path.join(loc, PACKAGE_FILENAME); + if (exists(pkgLoc)) { + this.addConfig(pkgLoc, "babel", JSON); + foundConfig = true; + } + } - var pkgLoc = path.join(loc, PACKAGE_FILENAME); - if (exists(pkgLoc)) this.addConfig(pkgLoc, "babel", JSON); + if (!foundIgnore) { + var ignoreLoc = path.join(loc, BABELIGNORE_FILENAME); + if (exists(ignoreLoc)) { + this.addIgnoreConfig(ignoreLoc); + foundIgnore = true; + } + } - var ignoreLoc = path.join(loc, BABELIGNORE_FILENAME); - if (exists(ignoreLoc)) this.addIgnoreConfig(ignoreLoc); + if (foundIgnore && foundConfig) return; } } @@ -126,17 +253,7 @@ export default class OptionManager { // optional if (!val && option.optional) continue; - // deprecated - if (this.log && val && option.deprecated) { - this.log.deprecate(`Deprecated option ${key}: ${option.deprecated}`); - } - - // validate - if (this.pipeline && val) { - val = validateOption(key, val, this.pipeline); - } - - // aaliases + // aliases if (option.alias) { opts[option.alias] = opts[option.alias] || val; } else { @@ -146,23 +263,13 @@ export default class OptionManager { } init(opts) { - this.mergeOptions(opts, "direct"); - - // babelrc option - if (opts.babelrc) { - for (var loc of (opts.babelrc: Array)) this.addConfig(loc); - } - // resolve all .babelrc files if (opts.babelrc !== false) { this.findConfigs(opts.filename); } - // merge in env - var envKey = process.env.BABEL_ENV || process.env.NODE_ENV || "development"; - if (this.options.env) { - this.mergeOptions(this.options.env[envKey], `direct.env.${envKey}`); - } + // merge in base options + this.mergeOptions(opts, "base"); // normalise this.normaliseOptions(opts); diff --git a/packages/babel-core/src/transformation/file/plugin-manager.js b/packages/babel-core/src/transformation/file/plugin-manager.js deleted file mode 100644 index 585e3280d6..0000000000 --- a/packages/babel-core/src/transformation/file/plugin-manager.js +++ /dev/null @@ -1,120 +0,0 @@ -import Plugin from "../plugin"; -import * as types from "babel-types"; -import * as messages from "babel-messages"; -import resolve from "try-resolve"; -import traverse from "babel-traverse"; -import parse from "../../helpers/parse"; - -var context = { - messages, - Plugin, - types, - parse, - traverse -}; - -export default class PluginManager { - static memoisedPlugins = []; - - static memoisePluginContainer(fn) { - for (var i = 0; i < PluginManager.memoisedPlugins.length; i++) { - var plugin = PluginManager.memoisedPlugins[i]; - if (plugin.container === fn) return plugin.transformer; - } - - var transformer = fn(context); - PluginManager.memoisedPlugins.push({ - container: fn, - transformer: transformer - }); - return transformer; - } - - static positions = ["before", "after"]; - - constructor({ file, transformers, before, after } = { transformers: {}, before: [], after: [] }) { - this.transformers = transformers; - this.file = file; - this.before = before; - this.after = after; - } - - subnormaliseString(name, position) { - // this is a plugin in the form of "foobar" or "foobar:after" - // where the optional colon is the delimiter for plugin position in the transformer stack - - var match = name.match(/^(.*?):(after|before)$/); - if (match) [, name, position] = match; - - var loc = resolve.relative(`babel-plugin-${name}`) || resolve.relative(name); - if (loc) { - var plugin = require(loc); - return { - position: position, - plugin: plugin.default || plugin - }; - } else { - throw new ReferenceError(messages.get("pluginUnknown", name)); - } - } - - validate(name, plugin) { - // validate transformer key - var key = plugin.key; - if (this.transformers[key]) { - throw new ReferenceError(messages.get("pluginKeyCollision", key)); - } - - // validate Transformer instance - if (!plugin.buildPass || plugin.constructor.name !== "Plugin") { - throw new TypeError(messages.get("pluginNotTransformer", name)); - } - - // register as a plugin - plugin.metadata.plugin = true; - } - - add(name) { - var position; - var plugin; - - if (name) { - if (typeof name === "object" && name.transformer) { - ({ transformer: plugin, position } = name); - } else if (typeof name !== "string") { - // not a string so we'll just assume that it's a direct Transformer instance, if not then - // the checks later on will complain - plugin = name; - } - - if (typeof name === "string") { - ({ plugin, position } = this.subnormaliseString(name, position)); - } - } else { - throw new TypeError(messages.get("pluginIllegalKind", typeof name, name)); - } - - // default position - position = position || "before"; - - // validate position - if (PluginManager.positions.indexOf(position) < 0) { - throw new TypeError(messages.get("pluginIllegalPosition", position, name)); - } - - // allow plugin containers to be specified so they don't have to manually require - if (typeof plugin === "function") { - plugin = PluginManager.memoisePluginContainer(plugin); - } - - // - this.validate(name, plugin); - - // build! - var pass = this.transformers[plugin.key] = plugin.buildPass(this.file); - if (pass.canTransform()) { - var stack = position === "before" ? this.before : this.after; - stack.push(pass); - } - } -}