Implement an 'overrides' config array to process in order for overrides.

This commit is contained in:
Logan Smyth
2017-12-20 16:05:05 -08:00
parent f4a24a38ca
commit c292140254
4 changed files with 181 additions and 8 deletions

View File

@@ -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<ConfigFile> | 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

View File

@@ -105,6 +105,13 @@ export function assertObject(key: string, value: mixed): {} | void {
return value;
}
export function assertArray(key: string, value: mixed): ?$ReadOnlyArray<mixed> {
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<mixed> {
if (value != null && !Array.isArray(value)) {
throw new Error(`.${key} must be an array, or undefined`);
}
return value;
}

View File

@@ -5,6 +5,7 @@ import {
assertString,
assertBoolean,
assertObject,
assertArray,
assertInputSourceMap,
assertIgnoreList,
assertPluginList,
@@ -45,6 +46,9 @@ const NONPRESET_VALIDATORS: ValidatorSet = {
$PropertyType<ValidatedOptions, "ignore">,
>),
only: (assertIgnoreList: Validator<$PropertyType<ValidatedOptions, "only">>),
overrides: (assertOverridesList: Validator<
$PropertyType<ValidatedOptions, "overrides">,
>),
// 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<ValidatedOptions>,
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<PluginItem>;
export type OverridesList = Array<ValidatedOptions>;
export type ConfigApplicableTest = IgnoreItem | Array<IgnoreItem>;
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<ValidatedOptions> {
}
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);
}

View File

@@ -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,