From c292140254c072f199e59d21a8207efc33bf4a40 Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Wed, 20 Dec 2017 16:05:05 -0800 Subject: [PATCH] Implement an 'overrides' config array to process in order for overrides. --- .../babel-core/src/config/config-chain.js | 114 ++++++++++++++++++ .../config/validation/option-assertions.js | 14 +-- .../src/config/validation/options.js | 27 ++++- packages/babel-core/test/config-chain.js | 34 ++++++ 4 files changed, 181 insertions(+), 8 deletions(-) diff --git a/packages/babel-core/src/config/config-chain.js b/packages/babel-core/src/config/config-chain.js index f7a65b1c19..06154c57e5 100644 --- a/packages/babel-core/src/config/config-chain.js +++ b/packages/babel-core/src/config/config-chain.js @@ -62,6 +62,9 @@ export const buildPresetChain: ( init: arg => arg, root: preset => loadPresetDescriptors(preset), env: (preset, envName) => loadPresetEnvDescriptors(preset)(envName), + overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index), + overridesEnv: (preset, index, envName) => + loadPresetOverridesEnvDescriptors(preset)(index)(envName), }); const loadPresetDescriptors = makeWeakCache((preset: PresetInstance) => buildRootDescriptors(preset, preset.alias, createUncachedDescriptors), @@ -76,6 +79,30 @@ const loadPresetEnvDescriptors = makeWeakCache((preset: PresetInstance) => ), ), ); +const loadPresetOverridesDescriptors = makeWeakCache((preset: PresetInstance) => + makeStrongCache((index: number) => + buildOverrideDescriptors( + preset, + preset.alias, + createUncachedDescriptors, + index, + ), + ), +); +const loadPresetOverridesEnvDescriptors = makeWeakCache( + (preset: PresetInstance) => + makeStrongCache((index: number) => + makeStrongCache((envName: string) => + buildOverrideEnvDescriptors( + preset, + preset.alias, + createUncachedDescriptors, + index, + envName, + ), + ), + ), +); /** * Build a config chain for Babel's full root configuration. @@ -142,6 +169,16 @@ const loadProgrammaticChain = makeChainWalker({ root: input => buildRootDescriptors(input, "base", createCachedDescriptors), env: (input, envName) => buildEnvDescriptors(input, "base", createCachedDescriptors, envName), + overrides: (input, index) => + buildOverrideDescriptors(input, "base", createCachedDescriptors, index), + overridesEnv: (input, index, envName) => + buildOverrideEnvDescriptors( + input, + "base", + createCachedDescriptors, + index, + envName, + ), }); /** @@ -151,6 +188,9 @@ const loadFileChain = makeChainWalker({ init: input => validateFile(input), root: file => loadFileDescriptors(file), env: (file, envName) => loadFileEnvDescriptors(file)(envName), + overrides: (file, index) => loadFileOverridesDescriptors(file)(index), + overridesEnv: (file, index, envName) => + loadFileOverridesEnvDescriptors(file)(index)(envName), }); const validateFile = makeWeakCache((file: ConfigFile): ValidatedFile => ({ filepath: file.filepath, @@ -170,6 +210,29 @@ const loadFileEnvDescriptors = makeWeakCache((file: ValidatedFile) => ), ), ); +const loadFileOverridesDescriptors = makeWeakCache((file: ValidatedFile) => + makeStrongCache((index: number) => + buildOverrideDescriptors( + file, + file.filepath, + createUncachedDescriptors, + index, + ), + ), +); +const loadFileOverridesEnvDescriptors = makeWeakCache((file: ValidatedFile) => + makeStrongCache((index: number) => + makeStrongCache((envName: string) => + buildOverrideEnvDescriptors( + file, + file.filepath, + createUncachedDescriptors, + index, + envName, + ), + ), + ), +); function buildRootDescriptors({ dirname, options }, alias, descriptors) { return descriptors(dirname, options, alias); @@ -185,6 +248,38 @@ function buildEnvDescriptors( return opts ? descriptors(dirname, opts, `${alias}.env["${envName}"]`) : null; } +function buildOverrideDescriptors( + { dirname, options }, + alias, + descriptors, + index, +) { + const opts = options.overrides && options.overrides[index]; + if (!opts) throw new Error("Assertion failure - missing override"); + + return descriptors(dirname, opts, `${alias}.overrides[${index}]`); +} + +function buildOverrideEnvDescriptors( + { dirname, options }, + alias, + descriptors, + index, + envName, +) { + const override = options.overrides && options.overrides[index]; + if (!override) throw new Error("Assertion failure - missing override"); + + const opts = override.env && override.env[envName]; + return opts + ? descriptors( + dirname, + opts, + `${alias}.overrides[${index}].env["${envName}"]`, + ) + : null; +} + function makeChainWalker< ArgT, InnerT: { options: ValidatedOptions, dirname: string }, @@ -192,10 +287,14 @@ function makeChainWalker< init, root, env, + overrides, + overridesEnv, }: { init: ArgT => InnerT, root: InnerT => OptionsAndDescriptors, env: (InnerT, string) => OptionsAndDescriptors | null, + overrides: (InnerT, number) => OptionsAndDescriptors, + overridesEnv: (InnerT, number, string) => OptionsAndDescriptors | null, }): (ArgT, ConfigContext, Set | void) => ConfigChain | null { return (arg, context, files = new Set()) => { const input = init(arg); @@ -212,6 +311,21 @@ function makeChainWalker< if (envOpts && configIsApplicable(envOpts, dirname, context)) { flattenedConfigs.push(envOpts); } + + (rootOpts.options.overrides || []).forEach((_, index) => { + const overrideOps = overrides(input, index); + if (configIsApplicable(overrideOps, dirname, context)) { + flattenedConfigs.push(overrideOps); + + const overrideEnvOpts = overridesEnv(input, index, context.envName); + if ( + overrideEnvOpts && + configIsApplicable(overrideEnvOpts, dirname, context) + ) { + flattenedConfigs.push(overrideEnvOpts); + } + } + }); } // Process 'ignore' and 'only' before 'extends' items are processed so diff --git a/packages/babel-core/src/config/validation/option-assertions.js b/packages/babel-core/src/config/validation/option-assertions.js index 312a1bfddc..c6330e7176 100644 --- a/packages/babel-core/src/config/validation/option-assertions.js +++ b/packages/babel-core/src/config/validation/option-assertions.js @@ -105,6 +105,13 @@ export function assertObject(key: string, value: mixed): {} | void { return value; } +export function assertArray(key: string, value: mixed): ?$ReadOnlyArray { + if (value != null && !Array.isArray(value)) { + throw new Error(`.${key} must be an array, or undefined`); + } + return value; +} + export function assertIgnoreList(key: string, value: mixed): IgnoreList | void { const arr = assertArray(key, value); if (arr) { @@ -225,10 +232,3 @@ function assertPluginTarget( } return value; } - -function assertArray(key: string, value: mixed): ?$ReadOnlyArray { - if (value != null && !Array.isArray(value)) { - throw new Error(`.${key} must be an array, or undefined`); - } - return value; -} diff --git a/packages/babel-core/src/config/validation/options.js b/packages/babel-core/src/config/validation/options.js index 21def26551..96b51f73ef 100644 --- a/packages/babel-core/src/config/validation/options.js +++ b/packages/babel-core/src/config/validation/options.js @@ -5,6 +5,7 @@ import { assertString, assertBoolean, assertObject, + assertArray, assertInputSourceMap, assertIgnoreList, assertPluginList, @@ -45,6 +46,9 @@ const NONPRESET_VALIDATORS: ValidatorSet = { $PropertyType, >), only: (assertIgnoreList: Validator<$PropertyType>), + overrides: (assertOverridesList: Validator< + $PropertyType, + >), // We could limit these to 'overrides' blocks, but it's not clear why we'd // bother, when the ability to limit a config to a specific set of files @@ -156,6 +160,7 @@ export type ValidatedOptions = { env?: EnvSet, ignore?: IgnoreList, only?: IgnoreList, + overrides?: OverridesList, // Generally verify if a given config object should be applied to the given file. test?: ConfigApplicableTest, @@ -215,6 +220,7 @@ export type PluginItem = | [PluginTarget, PluginOptions, string]; export type PluginList = $ReadOnlyArray; +export type OverridesList = Array; export type ConfigApplicableTest = IgnoreItem | Array; export type SourceMapsOption = boolean | "inline" | "both"; @@ -222,7 +228,7 @@ export type SourceTypeOption = "module" | "script" | "unambiguous"; export type CompactOption = boolean | "auto"; export type RootInputSourceMapOption = {} | boolean; -export type OptionsType = "arguments" | "file" | "env" | "preset"; +export type OptionsType = "arguments" | "file" | "env" | "preset" | "override"; export function validate(type: OptionsType, opts: {}): ValidatedOptions { assertNoDuplicateSourcemap(opts); @@ -237,6 +243,12 @@ export function validate(type: OptionsType, opts: {}): ValidatedOptions { if (type === "env" && key === "env") { throw new Error(`.${key} is not allowed inside another env block`); } + if (type === "env" && key === "overrides") { + throw new Error(`.${key} is not allowed inside an env block`); + } + if (type === "override" && key === "overrides") { + throw new Error(`.${key} is not allowed inside an overrides block`); + } const validator = COMMON_VALIDATORS[key] || @@ -287,3 +299,16 @@ function assertEnvSet(key: string, value: mixed): EnvSet { } return (obj: any); } + +function assertOverridesList(key: string, value: mixed): OverridesList { + const arr = assertArray(key, value); + if (arr) { + for (const [index, item] of arr.entries()) { + const env = assertObject(`${index}`, item); + if (!env) throw new Error(`.${key}[${index}] must be an object`); + + validate("override", env); + } + } + return (arr: any); +} diff --git a/packages/babel-core/test/config-chain.js b/packages/babel-core/test/config-chain.js index 867d946818..f40ae9989b 100644 --- a/packages/babel-core/test/config-chain.js +++ b/packages/babel-core/test/config-chain.js @@ -801,6 +801,40 @@ describe("buildConfigChain", function() { }); }); + describe("overrides merging", () => { + it("should apply matching overrides over base configs", () => { + const opts = loadOptions({ + filename: fixture("nonexistant-fake", "src.js"), + babelrc: false, + comments: true, + overrides: [ + { + test: fixture("nonexistant-fake"), + comments: false, + }, + ], + }); + + assert.equal(opts.comments, false); + }); + + it("should not apply non-matching overrides over base configs", () => { + const opts = loadOptions({ + filename: fixture("nonexistant-fake", "src.js"), + babelrc: false, + comments: true, + overrides: [ + { + test: fixture("nonexistant-unknown"), + comments: false, + }, + ], + }); + + assert.equal(opts.comments, true); + }); + }); + describe("config files", () => { const getDefaults = () => ({ babelrc: false,