From 9efb02f60f8f15518e9e4636846f1563b2d73fe8 Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Thu, 9 Jul 2015 21:11:51 +0100 Subject: [PATCH] rewrite options handling to be MUCH more maintainable and less like spaghetti --- src/babel/helpers/merge.js | 4 +- src/babel/transformation/file/index.js | 64 +------ .../transformation/file/options/config.json | 4 +- .../file/options/option-manager.js | 168 ++++++++++++++++++ .../transformation/file/options/resolve-rc.js | 61 ------- 5 files changed, 177 insertions(+), 124 deletions(-) create mode 100644 src/babel/transformation/file/options/option-manager.js delete mode 100644 src/babel/transformation/file/options/resolve-rc.js diff --git a/src/babel/helpers/merge.js b/src/babel/helpers/merge.js index 6257fdfb98..82b2eec8f7 100644 --- a/src/babel/helpers/merge.js +++ b/src/babel/helpers/merge.js @@ -3,8 +3,8 @@ import merge from "lodash/object/merge"; export default function (dest, src) { if (!dest || !src) return; - return merge(dest, src, function(a, b) { - if (Array.isArray(a)) { + return merge(dest, src, function (a, b) { + if (b && Array.isArray(a)) { var c = a.slice(0); for (var v of b) { if (a.indexOf(v) < 0) { diff --git a/src/babel/transformation/file/index.js b/src/babel/transformation/file/index.js index a7202f95ad..3aee64ded9 100644 --- a/src/babel/transformation/file/index.js +++ b/src/babel/transformation/file/index.js @@ -1,25 +1,20 @@ -import { validateOption, normaliseOptions, config as optionsConfig } from "./options"; import convertSourceMap from "convert-source-map"; import moduleFormatters from "../modules"; +import OptionManager from "./options/option-manager"; import PluginManager from "./plugin-manager"; import shebangRegex from "shebang-regex"; import NodePath from "../../traversal/path"; import isFunction from "lodash/lang/isFunction"; -import isAbsolute from "path-is-absolute"; -import resolveRc from "./options/resolve-rc"; import sourceMap from "source-map"; import generate from "../../generation"; import codeFrame from "../../helpers/code-frame"; import defaults from "lodash/object/defaults"; import includes from "lodash/collection/includes"; import traverse from "../../traversal"; -import assign from "lodash/object/assign"; import Logger from "./logger"; import Plugin from "../plugin"; import parse from "../../helpers/parse"; -import merge from "../../helpers/merge"; import slash from "slash"; -import clone from "lodash/lang/clone"; import Hub from "../../traversal/hub"; import * as util from "../../util"; import path from "path"; @@ -50,10 +45,9 @@ export default class File { this.pipeline = pipeline; this.log = new Logger(this, opts.filename || "unknown"); + this.opts = this.initOptions(opts); this.ast = {}; - this.normaliseOptions(opts); - this.buildTransformers(); this.hub = new Hub(this); @@ -98,56 +92,8 @@ export default class File { static soloHelpers = []; - static options = optionsConfig; - - normaliseOptions(opts: Object) { - opts = this.opts = normaliseOptions(assign({}, opts)); - - // resolve babelrc - if (opts.filename) { - var rcFilename = opts.filename; - if (!isAbsolute(rcFilename)) rcFilename = path.join(process.cwd(), rcFilename); - opts = resolveRc(rcFilename, opts); - } - - // check for unknown options - for (let key in opts) { - if (key[0] === "_") continue; - - let option = File.options[key]; - if (!option) this.log.error(`Unknown option: ${key}`, ReferenceError); - } - - // merge in environment options - var envKey = process.env.BABEL_ENV || process.env.NODE_ENV || "development"; - if (opts.env) merge(opts, normaliseOptions(opts.env[envKey])); - - // normalise options - for (let key in File.options) { - let option = File.options[key]; - var val = opts[key]; - - // optional - if (!val && option.optional) continue; - - // deprecated - if (val && option.deprecated) { - this.log.deprecate("Deprecated option " + key + ": " + option.deprecated); - } - - // default - if (val == null) val = clone(option.default); - - // validate - if (val) val = validateOption(key, val, this.pipeline); - - // aaliases - if (option.alias) { - opts[option.alias] = opts[option.alias] || val; - } else { - opts[key] = val; - } - } + initOptions(opts) { + opts = new OptionManager(this.log, this.pipeline).init(opts); if (opts.inputSourceMap) { opts.sourceMaps = true; @@ -195,7 +141,7 @@ export default class File { } return opts; - }; + } isLoose(key: string) { return includes(this.opts.loose, key); diff --git a/src/babel/transformation/file/options/config.json b/src/babel/transformation/file/options/config.json index 800592db7c..b22f409bce 100644 --- a/src/babel/transformation/file/options/config.json +++ b/src/babel/transformation/file/options/config.json @@ -253,7 +253,7 @@ }, "babelrc": { - "hidden": true, - "description": "do not load the same .babelrc file twice" + "description": "Specify a custom list of babelrc files to use", + "type": "list" } } diff --git a/src/babel/transformation/file/options/option-manager.js b/src/babel/transformation/file/options/option-manager.js new file mode 100644 index 0000000000..e2fdfe58de --- /dev/null +++ b/src/babel/transformation/file/options/option-manager.js @@ -0,0 +1,168 @@ +import stripJsonComments from "strip-json-comments"; +import { validateOption, normaliseOptions } from "./index"; +import isAbsolute from "path-is-absolute"; +import clone from "lodash/lang/clone"; +import merge from "../../../helpers/merge"; +import config from "./config"; +import path from "path"; +import fs from "fs"; +import pathExists from "path-exists"; + +var existsCache = {}; +var jsonCache = {}; + +const CONFIG_FILENAME = ".babelrc"; + +function exists(filename) { + var cached = existsCache[filename]; + if (cached != null) { + return cached; + } else { + return existsCache[filename] = pathExists.sync(filename); + } +} + +export default class OptionManager { + constructor(log, pipeline) { + this.resolvedConfigs = []; + this.options = OptionManager.createBareOptions(); + this.pipeline = pipeline; + this.log = log; + } + + /** + * Description + */ + + static createBareOptions() { + var opts = {}; + + for (var key in config) { + var opt = config[key]; + opts[key] = clone(opt.default); + } + + return opts; + } + + /** + * Description + */ + + addConfig(loc) { + if (this.resolvedConfigs.indexOf(loc) >= 0) return; + + var content = fs.readFileSync(loc, "utf8"); + var opts; + + try { + opts = jsonCache[content] = jsonCache[content] || JSON.parse(stripJsonComments(content)); + } catch (err) { + err.message = `${loc}: ${err.message}`; + throw err; + } + + this.mergeOptions(opts, loc); + this.resolvedConfigs.push(loc); + } + + /** + * Description + */ + + mergeOptions(opts, alias) { + if (!opts) return; + + for (let key in opts) { + if (key[0] === "_") continue; + + let option = config[key]; + + // check for an unknown option + if (!option) this.log.error(`Unknown option: ${alias}.${key}`, ReferenceError); + } + + // normalise options + normaliseOptions(opts); + + // merge them into this current files options + merge(this.options, opts); + } + + /** + * Description + */ + + findConfigs(loc) { + if (!loc) return; + + if (!isAbsolute(loc)) { + loc = path.join(process.cwd(), loc); + } + + while (loc !== (loc = path.dirname(loc))) { + if (this.options.breakConfig) return; + + var configLoc = path.join(loc, CONFIG_FILENAME); + if (exists(configLoc)) this.addConfig(configLoc); + } + } + + /** + * Description + */ + + normaliseOptions() { + var opts = this.options; + + for (let key in config) { + var option = config[key]; + var val = opts[key]; + + // optional + if (!val && option.optional) continue; + + // deprecated + if (val && option.deprecated) { + this.log.deprecate(`Deprecated option ${key}: ${option.deprecated}`); + } + + // validate + if (val) val = validateOption(key, val, this.pipeline); + + // aaliases + if (option.alias) { + opts[option.alias] = opts[option.alias] || val; + } else { + opts[key] = val; + } + } + } + + /** + * Description + */ + + 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 + 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}`); + } + + // normalise + this.normaliseOptions(opts); + + return this.options; + } +} diff --git a/src/babel/transformation/file/options/resolve-rc.js b/src/babel/transformation/file/options/resolve-rc.js deleted file mode 100644 index a9fdbdd98a..0000000000 --- a/src/babel/transformation/file/options/resolve-rc.js +++ /dev/null @@ -1,61 +0,0 @@ -import stripJsonComments from "strip-json-comments"; -import { normaliseOptions } from "./index"; -import merge from "../../../helpers/merge"; -import path from "path"; -import fs from "fs"; -import pathExists from "path-exists"; - -var cache = {}; -var jsons = {}; - -function exists(filename) { - var cached = cache[filename]; - if (cached != null) return cached; - return cache[filename] = pathExists.sync(filename); -} - -export default function (loc, opts = {}) { - var rel = ".babelrc"; - - if (!opts.babelrc) { - opts.babelrc = []; - } - - function find(start, rel) { - var file = path.join(start, rel); - - if (opts.babelrc.indexOf(file) >= 0) { - return; - } - - if (exists(file)) { - var content = fs.readFileSync(file, "utf8"); - var json; - - try { - json = jsons[content] = jsons[content] || JSON.parse(stripJsonComments(content)); - normaliseOptions(json); - } catch (err) { - err.message = `${file}: ${err.message}`; - throw err; - } - - opts.babelrc.push(file); - - if (json.breakConfig) return; - - merge(opts, json); - } - - var up = path.dirname(start); - if (up !== start) { // root - find(up, rel); - } - } - - if (opts.babelrc.indexOf(loc) < 0 && opts.breakConfig !== true) { - find(loc, rel); - } - - return opts; -}