Allow config objects to use test/include/exclude to limit application to specific files.

This commit is contained in:
Logan Smyth
2017-12-20 16:03:57 -08:00
parent 193e841d89
commit f4a24a38ca
4 changed files with 514 additions and 6 deletions

View File

@@ -7,6 +7,7 @@ import {
validate,
type ValidatedOptions,
type IgnoreList,
type ConfigApplicableTest,
} from "./validation/options";
const debug = buildDebug("babel:config:config-chain");
@@ -204,11 +205,13 @@ function makeChainWalker<
const flattenedConfigs = [];
const rootOpts = root(input);
flattenedConfigs.push(rootOpts);
if (configIsApplicable(rootOpts, dirname, context)) {
flattenedConfigs.push(rootOpts);
const envOpts = env(input, context.envName);
if (envOpts) {
flattenedConfigs.push(envOpts);
const envOpts = env(input, context.envName);
if (envOpts && configIsApplicable(envOpts, dirname, context)) {
flattenedConfigs.push(envOpts);
}
}
// Process 'ignore' and 'only' before 'extends' items are processed so
@@ -355,6 +358,42 @@ function dedupDescriptors(
}, []);
}
function configIsApplicable(
{ options }: OptionsAndDescriptors,
dirname: string,
context: ConfigContext,
): boolean {
return (
(options.test === undefined ||
configFieldIsApplicable(context, options.test, dirname)) &&
(options.include === undefined ||
configFieldIsApplicable(context, options.include, dirname)) &&
(options.exclude === undefined ||
!configFieldIsApplicable(context, options.exclude, dirname))
);
}
function configFieldIsApplicable(
context: ConfigContext,
test: ConfigApplicableTest,
dirname: string,
): boolean {
if (context.filename === null) {
throw new Error(
`Configuration contains explicit test/include/exclude checks, but no filename was passed to Babel`,
);
}
// $FlowIgnore - Flow refinements aren't quite smart enough for this :(
const ctx: ConfigContextNamed = context;
const patterns = Array.isArray(test) ? test : [test];
// Disabling negation here because it's a bit buggy from
// https://github.com/babel/babel/issues/6907 and it's not clear that it is
// needed since users can use 'exclude' alongside 'test'/'include'.
return matchesPatterns(ctx, patterns, dirname, false /* allowNegation */);
}
/**
* Tests if a filename should be ignored based on "ignore" and "only" options.
*/
@@ -403,6 +442,7 @@ function matchesPatterns(
context: ConfigContextNamed,
patterns: IgnoreList,
dirname: string,
allowNegation?: boolean = true,
): boolean {
const res = [];
const strings = [];
@@ -424,13 +464,19 @@ function matchesPatterns(
const absolutePatterns = strings.map(pattern => {
// Preserve the "!" prefix so that micromatch can use it for negation.
const negate = pattern[0] === "!";
if (negate && !allowNegation) {
throw new Error(`Negation of file paths is not supported.`);
}
if (negate) pattern = pattern.slice(1);
return (negate ? "!" : "") + path.resolve(dirname, pattern);
});
if (
micromatch(possibleDirs, absolutePatterns, { nocase: true }).length > 0
micromatch(possibleDirs, absolutePatterns, {
nocase: true,
nonegate: !allowNegation,
}).length > 0
) {
return true;
}

View File

@@ -6,6 +6,7 @@ import type {
PluginList,
PluginItem,
PluginTarget,
ConfigApplicableTest,
SourceMapsOption,
SourceTypeOption,
CompactOption,
@@ -122,12 +123,38 @@ function assertIgnoreItem(
!(value instanceof RegExp)
) {
throw new Error(
`.${key}[${index}] must be an array of string/Funtion/RegExp values, or or undefined`,
`.${key}[${index}] must be an array of string/Funtion/RegExp values, or undefined`,
);
}
return value;
}
export function assertConfigApplicableTest(
key: string,
value: mixed,
): ConfigApplicableTest | void {
if (Array.isArray(value)) {
value.forEach((item, i) => {
if (!checkValidTest(item)) {
throw new Error(`.${key}[${i}] must be a string/Function/RegExp.`);
}
});
} else if (!checkValidTest(value)) {
throw new Error(
`.${key} must be a string/Function/RegExp, or an array of those`,
);
}
return (value: any);
}
function checkValidTest(value: mixed): boolean {
return (
typeof value === "string" ||
typeof value === "function" ||
value instanceof RegExp
);
}
export function assertPluginList(key: string, value: mixed): PluginList | void {
const arr = assertArray(key, value);
if (arr) {

View File

@@ -8,6 +8,7 @@ import {
assertInputSourceMap,
assertIgnoreList,
assertPluginList,
assertConfigApplicableTest,
assertFunction,
assertSourceMaps,
assertCompact,
@@ -44,6 +45,19 @@ const NONPRESET_VALIDATORS: ValidatorSet = {
$PropertyType<ValidatedOptions, "ignore">,
>),
only: (assertIgnoreList: Validator<$PropertyType<ValidatedOptions, "only">>),
// 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
// is a fairly general useful feature.
test: (assertConfigApplicableTest: Validator<
$PropertyType<ValidatedOptions, "test">,
>),
include: (assertConfigApplicableTest: Validator<
$PropertyType<ValidatedOptions, "include">,
>),
exclude: (assertConfigApplicableTest: Validator<
$PropertyType<ValidatedOptions, "exclude">,
>),
};
const COMMON_VALIDATORS: ValidatorSet = {
@@ -143,6 +157,11 @@ export type ValidatedOptions = {
ignore?: IgnoreList,
only?: IgnoreList,
// Generally verify if a given config object should be applied to the given file.
test?: ConfigApplicableTest,
include?: ConfigApplicableTest,
exclude?: ConfigApplicableTest,
presets?: PluginList,
plugins?: PluginList,
passPerPreset?: boolean,
@@ -196,6 +215,8 @@ export type PluginItem =
| [PluginTarget, PluginOptions, string];
export type PluginList = $ReadOnlyArray<PluginItem>;
export type ConfigApplicableTest = IgnoreItem | Array<IgnoreItem>;
export type SourceMapsOption = boolean | "inline" | "both";
export type SourceTypeOption = "module" | "script" | "unambiguous";
export type CompactOption = boolean | "auto";