merge plugin manager into option manager

This commit is contained in:
Sebastian McKenzie 2015-09-15 06:32:24 +01:00
parent 4924a9adea
commit 69f67c9ec3
2 changed files with 146 additions and 159 deletions

View File

@ -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);

View File

@ -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);
}
}
}