Add RegExp support to include/exclude preset-env options (#7242)

* Add support for RegExp includes/excludes

* Keep the plugin order

* Detect invalid modules in regexp

* Add more tests for regexp

* Cover builtins, and unnormalized in the RegExp tests

* Remove babel-plugin- in all positions

* Change babel-plugin- prefix to string

* Add a test for the same module in include/exclude

* Handle partial matches explicitly

* Remove extra valid regexp check

* Optimise validation of plugins

* Optimise selecting the plugins

* Fix undefined include/exclude option

* Update documentation to reflect the new include matching

* Fix typo

* Apply reviews

Use regexp.test instead of string.match (slower)

Define flatten helper

Do not normalize babel-plugin anywhere in the string
This commit is contained in:
Amin Marashi 2018-03-18 22:54:43 +08:00 committed by Mateusz Burzyński
parent d260bfaec4
commit 8eee435cd6
3 changed files with 103 additions and 57 deletions

View File

@ -245,7 +245,7 @@ Outputs the targets/plugins used and the version specified in [plugin data versi
### `include`
`Array<string>`, defaults to `[]`.
`Array<string|RegExp>`, defaults to `[]`.
An array of plugins to always include.
@ -255,6 +255,16 @@ Valid options include any:
- [Built-ins](https://github.com/babel/babel/blob/master/packages/babel-preset-env/data/built-in-features.js), such as `es6.map`, `es6.set`, or `es6.object.assign`.
Plugin names can be fully or partially specified (or using `RegExp`).
Acceptable inputs:
- Full name (`string`): `"es6.math.sign"`
- Partial name (`string`): `"es6.math.*"` (resolves to all plugins with `es6.math` prefix)
- `RegExp` Object: `/^transform-.*$/` or `new RegExp("^transform-modules-.*")`
Note that the above `.` is the `RegExp` equivalent to match any character, and not the actual `'.'` character. Also note that to match any character `.*` is used in `RegExp` as opposed to `*` in `glob` format.
This option is useful if there is a bug in a native implementation, or a combination of a non-supported feature + a supported one doesn't work.
For example, Node 4 supports native classes but not spread. If `super` is used with a spread argument, then the `@babel/plugin-transform-classes` transform needs to be `include`d, as it is not possible to transpile a spread with `super` otherwise.
@ -263,7 +273,7 @@ For example, Node 4 supports native classes but not spread. If `super` is used w
### `exclude`
`Array<string>`, defaults to `[]`.
`Array<string|RegExp>`, defaults to `[]`.
An array of plugins to always exclude/remove.

View File

@ -15,26 +15,44 @@ const validIncludesAndExcludes = new Set([
...defaultWebIncludes,
]);
export const validateIncludesAndExcludes = (
opts: Array<string> = [],
type: string,
): Array<string> => {
invariant(
Array.isArray(opts),
`Invalid Option: The '${type}' option must be an Array<String> of plugins/built-ins`,
const pluginToRegExp = (plugin: any): RegExp => {
if (plugin instanceof RegExp) return plugin;
try {
return new RegExp(`^${normalizePluginName(plugin)}$`);
} catch (e) {
return null;
}
};
const selectPlugins = (regexp: RegExp): Array<string> =>
Array.from(validIncludesAndExcludes).filter(
item => regexp instanceof RegExp && regexp.test(item),
);
const unknownOpts = opts.filter(opt => !validIncludesAndExcludes.has(opt));
const flatten = array => [].concat(...array);
const expandIncludesAndExcludes = (
plugins: Array<string | RegExp> = [],
type: string,
): Array<string> => {
if (plugins.length === 0) return plugins;
const selectedPlugins = plugins.map(plugin =>
selectPlugins(pluginToRegExp(plugin)),
);
const invalidRegExpList = plugins.filter(
(p, i) => selectedPlugins[i].length === 0,
);
invariant(
unknownOpts.length === 0,
`Invalid Option: The plugins/built-ins '${unknownOpts.join(
invalidRegExpList.length === 0,
`Invalid Option: The plugins/built-ins '${invalidRegExpList.join(
", ",
)}' passed to the '${type}' option are not
valid. Please check data/[plugin-features|built-in-features].js in babel-preset-env`,
);
return opts;
return flatten(selectedPlugins);
};
const validBrowserslistTargets = [
@ -45,9 +63,6 @@ const validBrowserslistTargets = [
export const normalizePluginName = (plugin: string): string =>
plugin.replace(/^babel-plugin-/, "");
export const normalizePluginNames = (plugins: Array<string>): Array<string> =>
plugins.map(normalizePluginName);
export const checkDuplicateIncludeExcludes = (
include: Array<string> = [],
exclude: Array<string> = [],
@ -138,20 +153,16 @@ export const validateUseBuiltInsOption = (
};
export default function normalizeOptions(opts: Options) {
if (opts.exclude) {
opts.exclude = normalizePluginNames(opts.exclude);
}
const include = expandIncludesAndExcludes(opts.include, "include");
const exclude = expandIncludesAndExcludes(opts.exclude, "exclude");
if (opts.include) {
opts.include = normalizePluginNames(opts.include);
}
checkDuplicateIncludeExcludes(opts.include, opts.exclude);
checkDuplicateIncludeExcludes(include, exclude);
return {
configPath: validateConfigPathOption(opts.configPath),
debug: opts.debug,
exclude: validateIncludesAndExcludes(opts.exclude, "exclude"),
include,
exclude,
forceAllTransforms: validateBoolOption(
"forceAllTransforms",
opts.forceAllTransforms,
@ -160,7 +171,6 @@ export default function normalizeOptions(opts: Options) {
ignoreBrowserslistConfig: validateIgnoreBrowserslistConfig(
opts.ignoreBrowserslistConfig,
),
include: validateIncludesAndExcludes(opts.include, "include"),
loose: validateBoolOption("loose", opts.loose, false),
modules: validateModulesOption(opts.modules),
shippedProposals: validateBoolOption(

View File

@ -5,12 +5,10 @@ const assert = require("assert");
const {
checkDuplicateIncludeExcludes,
normalizePluginNames,
validateBoolOption,
validateIncludesAndExcludes,
validateModulesOption,
normalizePluginName,
} = normalizeOptions;
describe("normalize-options", () => {
describe("normalizeOptions", () => {
it("should return normalized `include` and `exclude`", () => {
@ -23,6 +21,11 @@ describe("normalize-options", () => {
]);
});
it("should not normalize babel-plugin with prefix", () => {
const normalized = normalizePluginName("prefix-babel-plugin-postfix");
assert.equal(normalized, "prefix-babel-plugin-postfix");
});
it("should throw if duplicate names in `include` and `exclude`", () => {
const normalizeWithSameIncludes = () => {
normalizeOptions.default({
@ -34,6 +37,57 @@ describe("normalize-options", () => {
});
});
describe("RegExp include/exclude", () => {
it("should not allow invalid plugins in `include` and `exclude`", () => {
const normalizeWithNonExistingPlugin = () => {
normalizeOptions.default({
include: ["non-existing-plugin"],
});
};
assert.throws(normalizeWithNonExistingPlugin, Error);
});
it("should expand regular expressions in `include` and `exclude`", () => {
const normalized = normalizeOptions.default({
include: ["^[a-z]*-spread", "babel-plugin-transform-classes"],
});
assert.deepEqual(normalized.include, [
"transform-spread",
"transform-classes",
]);
});
it("should expand regular expressions in `include` and `exclude`", () => {
const normalized = normalizeOptions.default({
exclude: ["es6.math.log.*"],
});
assert.deepEqual(normalized.exclude, [
"es6.math.log1p",
"es6.math.log10",
"es6.math.log2",
]);
});
it("should not allow the same modules in `include` and `exclude`", () => {
const normalizeWithNonExistingPlugin = () => {
normalizeOptions.default({
include: ["es6.math.log2"],
exclude: ["es6.math.log.*"],
});
};
assert.throws(normalizeWithNonExistingPlugin, Error);
});
it("should not do partial match if not explicitly defined `include` and `exclude`", () => {
const normalized = normalizeOptions.default({
include: ["es6.reflect.set-prototype-of"],
exclude: ["es6.reflect.set"],
});
assert.deepEqual(normalized.include, ["es6.reflect.set-prototype-of"]);
assert.deepEqual(normalized.exclude, ["es6.reflect.set"]);
});
});
describe("validateBoolOption", () => {
it("`undefined` option returns false", () => {
assert(validateBoolOption("test", undefined, false) === false);
@ -71,24 +125,6 @@ describe("normalize-options", () => {
});
});
describe("normalizePluginNames", function() {
it("should drop `babel-plugin-` prefix if needed", function() {
assert.deepEqual(
normalizePluginNames([
"babel-plugin-transform-object-super",
"transform-parameters",
]),
["transform-object-super", "transform-parameters"],
);
});
it("should not throw if no duplicate names in both", function() {
assert.doesNotThrow(() => {
checkDuplicateIncludeExcludes(["transform-regenerator"], ["map"]);
}, Error);
});
});
describe("validateModulesOption", () => {
it("`undefined` option returns commonjs", () => {
assert(validateModulesOption() === "commonjs");
@ -126,14 +162,4 @@ describe("normalize-options", () => {
}, Error);
});
});
describe("validateIncludesAndExcludes", function() {
it("should return empty arrays if undefined", function() {
assert.deepEqual(validateIncludesAndExcludes(), []);
});
it("should throw if not in features", function() {
assert.throws(() => {
validateIncludesAndExcludes(["asdf"]);
}, Error);
});
});
});