Merge pull request #125 from babel/feature/extract-option-validation

Extract option normalization into independant file
This commit is contained in:
Eric Baer 2017-01-18 09:18:27 -08:00 committed by GitHub
commit 80f93f3d87
7 changed files with 247 additions and 217 deletions

View File

@ -45,7 +45,8 @@
"babel-plugin-transform-es2015-unicode-regex": "^6.3.13",
"babel-plugin-transform-exponentiation-operator": "^6.8.0",
"babel-plugin-transform-regenerator": "^6.6.0",
"browserslist": "^1.4.0"
"browserslist": "^1.4.0",
"invariant": "^2.2.2"
},
"devDependencies": {
"babel-cli": "^6.11.4",

View File

@ -0,0 +1,5 @@
export default [
"web.timers",
"web.immediate",
"web.dom.iterable"
];

View File

@ -1,29 +1,12 @@
import pluginList from "../data/plugins.json";
import pluginFeatures from "../data/plugin-features";
import builtInsList from "../data/built-ins.json";
import browserslist from "browserslist";
import transformPolyfillRequirePlugin from "./transform-polyfill-require-plugin";
import builtInsList from "../data/built-ins.json";
import defaultInclude from "./default-includes";
import electronToChromium from "../data/electron-to-chromium";
export const MODULE_TRANSFORMATIONS = {
"amd": "transform-es2015-modules-amd",
"commonjs": "transform-es2015-modules-commonjs",
"systemjs": "transform-es2015-modules-systemjs",
"umd": "transform-es2015-modules-umd"
};
const defaultInclude = [
"web.timers",
"web.immediate",
"web.dom.iterable"
];
export const validIncludesAndExcludes = [
...Object.keys(pluginFeatures),
...Object.keys(MODULE_TRANSFORMATIONS).map((m) => MODULE_TRANSFORMATIONS[m]),
...Object.keys(builtInsList),
...defaultInclude
];
import moduleTransformations from "./module-transformations";
import normalizeOptions from "./normalize-options.js";
import pluginList from "../data/plugins.json";
import transformPolyfillRequirePlugin from "./transform-polyfill-require-plugin";
/**
* Determine if a transformation is required
@ -51,7 +34,7 @@ export const isPluginRequired = (supportedEnvironments, plugin) => {
const lowestTargetedVersion = supportedEnvironments[environment];
if (typeof lowestTargetedVersion === "string") {
throw new Error(`Target version must be a number,
throw new Error(`Target version must be a number,
'${lowestTargetedVersion}' was given for '${environment}'`);
}
@ -153,69 +136,7 @@ export const getTargets = (targets = {}) => {
return targetOps;
};
// TODO: Allow specifying plugins as either shortened or full name
// babel-plugin-transform-es2015-classes
// transform-es2015-classes
export const validateLooseOption = (looseOpt = false) => {
if (typeof looseOpt !== "boolean") {
throw new Error("Preset env: 'loose' option must be a boolean.");
}
return looseOpt;
};
export const validateModulesOption = (modulesOpt = "commonjs") => {
if (modulesOpt !== false && Object.keys(MODULE_TRANSFORMATIONS).indexOf(modulesOpt) === -1) {
throw new Error("The 'modules' option must be 'false' to indicate no modules\n" +
"or a module type which be be one of: 'commonjs' (default), 'amd', 'umd', 'systemjs'");
}
return modulesOpt;
};
export function validatePluginsOption(opts = [], type) {
if (!Array.isArray(opts)) {
throw new Error(`The '${type}' option must be an Array<string> of plugins/built-ins`);
}
const unknownOpts = [];
opts.forEach((opt) => {
if (validIncludesAndExcludes.indexOf(opt) === -1) {
unknownOpts.push(opt);
}
});
if (unknownOpts.length > 0) {
throw new Error(`Invalid plugins/built-ins '${unknownOpts}' passed to '${type}' option.
Check data/[plugin-features|built-in-features].js in babel-preset-env`);
}
return {
all: opts,
plugins: opts.filter((opt) => !opt.match(/^(es\d+|web)\./)),
builtIns: opts.filter((opt) => opt.match(/^(es\d+|web)\./))
};
}
const validateIncludeOption = (opts) => validatePluginsOption(opts, "include");
const validateExcludeOption = (opts) => validatePluginsOption(opts, "exclude");
export function checkDuplicateIncludeExcludes(include, exclude) {
const duplicates = [];
include.forEach((opt) => {
if (exclude.indexOf(opt) >= 0) {
duplicates.push(opt);
}
});
if (duplicates.length > 0) {
throw new Error(`Duplicate plugins/built-ins: '${duplicates}' found
in both the "include" and "exclude" options.`);
}
}
let hasBeenLogged = false;
let hasBeenWarned = false;
const logPlugin = (plugin, targets, list) => {
const envList = list[plugin] || {};
@ -237,21 +158,19 @@ const filterItem = (targets, exclusions, list, item) => {
return isRequired && notExcluded;
};
export const transformIncludesAndExculdes = (opts) => ({
all: opts,
plugins: opts.filter((opt) => !opt.match(/^(es\d+|web)\./)),
builtIns: opts.filter((opt) => opt.match(/^(es\d+|web)\./))
});
export default function buildPreset(context, opts = {}) {
const loose = validateLooseOption(opts.loose);
const moduleType = validateModulesOption(opts.modules);
// TODO: remove whitelist in favor of include in next major
if (opts.whitelist && !hasBeenWarned) {
hasBeenWarned = true;
console.warn(`The "whitelist" option has been deprecated
in favor of "include" to match the newly added "exclude" option (instead of "blacklist").`);
}
const include = validateIncludeOption(opts.whitelist || opts.include);
const exclude = validateExcludeOption(opts.exclude);
checkDuplicateIncludeExcludes(include.all, exclude.all);
const targets = getTargets(opts.targets);
const debug = opts.debug;
const useBuiltIns = opts.useBuiltIns;
const validatedOptions = normalizeOptions(opts);
const {debug, loose, moduleType, useBuiltIns} = validatedOptions;
const targets = getTargets(validatedOptions.targets);
const include = transformIncludesAndExculdes(validatedOptions.include);
const exclude = transformIncludesAndExculdes(validatedOptions.exclude);
const filterPlugins = filterItem.bind(null, targets, exclude.plugins, pluginList);
const transformations = Object.keys(pluginList)
@ -287,7 +206,7 @@ export default function buildPreset(context, opts = {}) {
}
const regenerator = transformations.indexOf("transform-regenerator") >= 0;
const modulePlugin = moduleType !== false && MODULE_TRANSFORMATIONS[moduleType];
const modulePlugin = moduleType !== false && moduleTransformations[moduleType];
const plugins = [];
modulePlugin &&

View File

@ -0,0 +1,6 @@
export default {
"amd": "transform-es2015-modules-amd",
"commonjs": "transform-es2015-modules-commonjs",
"systemjs": "transform-es2015-modules-systemjs",
"umd": "transform-es2015-modules-umd"
};

View File

@ -0,0 +1,93 @@
import intersection from "lodash/intersection";
import invariant from "invariant";
import builtInsList from "../data/built-ins.json";
import defaultInclude from "./default-includes";
import moduleTransformations from "./module-transformations";
import pluginFeatures from "../data/plugin-features";
const hasBeenWarned = false;
const validIncludesAndExcludes = [
...Object.keys(pluginFeatures),
...Object.keys(moduleTransformations).map((m) => moduleTransformations[m]),
...Object.keys(builtInsList),
...defaultInclude
];
export const validateIncludesAndExcludes = (opts = [], type) => {
invariant(
Array.isArray(opts),
`Invalid Option: The '${type}' option must be an Array<String> of plugins/built-ins`
);
const unknownOpts = opts.filter((opt) => !validIncludesAndExcludes.includes(opt));
invariant(
unknownOpts.length === 0,
`Invalid Option: The plugins/built-ins '${unknownOpts}' passed to the '${type}' option are not
valid. Please check data/[plugin-features|built-in-features].js in babel-preset-env`
);
return opts;
};
export const checkDuplicateIncludeExcludes = (include = [], exclude = []) => {
const duplicates = intersection(include, exclude);
invariant(
duplicates.length === 0,
`Invalid Option: The plugins/built-ins '${duplicates}' were found in both the "include" and
"exclude" options.`
);
};
// TODO: Allow specifying plugins as either shortened or full name
// babel-plugin-transform-es2015-classes
// transform-es2015-classes
export const validateLooseOption = (looseOpt = false) => {
invariant(
typeof looseOpt === "boolean",
"Invalid Option: The 'loose' option must be a boolean."
);
return looseOpt;
};
export const validateModulesOption = (modulesOpt = "commonjs") => {
invariant(
modulesOpt === false || Object.keys(moduleTransformations).includes(modulesOpt),
`Invalid Option: The 'modules' option must be either 'false' to indicate no modules, or a
module type which can be be one of: 'commonjs' (default), 'amd', 'umd', 'systemjs'.`
);
return modulesOpt;
};
export default function normalizeOptions(opts) {
// TODO: remove whitelist in favor of include in next major
if (opts.whitelist && !hasBeenWarned) {
console.warn(
`Deprecation Warning: The "whitelist" option has been deprecated in favor of "include" to
match the newly added "exclude" option (instead of "blacklist").`
);
}
invariant(
!(opts.whitelist && opts.include),
`Invalid Option: The "whitelist" and the "include" option are the same and one can be used at
a time`
);
checkDuplicateIncludeExcludes(opts.whitelist || opts.include, opts.exclude);
return {
debug: opts.debug,
exclude: validateIncludesAndExcludes(opts.exclude, "exclude"),
include: validateIncludesAndExcludes(opts.whitelist || opts.include, "include"),
loose: validateLooseOption(opts.loose),
moduleType: validateModulesOption(opts.modules),
targets: opts.targets,
useBuiltIns: opts.useBuiltIns
};
}

View File

@ -4,13 +4,6 @@ const babelPresetEnv = require("../lib/index.js");
const assert = require("assert");
const electronToChromiumData = require("../data/electron-to-chromium");
const {
validateModulesOption,
validateLooseOption,
validatePluginsOption,
checkDuplicateIncludeExcludes
} = babelPresetEnv;
describe("babel-preset-env", () => {
describe("getTargets", () => {
it("should return the current node version with option 'current'", function() {
@ -173,115 +166,27 @@ describe("babel-preset-env", () => {
});
});
describe("validateLooseOption", () => {
it("`undefined` option returns false", () => {
assert(validateLooseOption() === false);
describe("transformIncludesAndExculdes", function() {
it("should return in transforms array", function() {
assert.deepEqual(
babelPresetEnv.transformIncludesAndExculdes(["transform-es2015-arrow-functions"]),
{
all: ["transform-es2015-arrow-functions"],
plugins: ["transform-es2015-arrow-functions"],
builtIns: []
}
);
});
it("`false` option returns false", () => {
assert(validateLooseOption(false) === false);
});
it("`true` option returns true", () => {
assert(validateLooseOption(true) === true);
});
it("array option is invalid", () => {
assert.throws(() => {
validateModulesOption([]);
}, Error);
});
});
describe("validateModulesOption", () => {
it("`undefined` option returns commonjs", () => {
assert(validateModulesOption() === "commonjs");
});
it("`false` option returns commonjs", () => {
assert(validateModulesOption(false) === false);
});
it("commonjs option is valid", () => {
assert(validateModulesOption("commonjs") === "commonjs");
});
it("systemjs option is valid", () => {
assert(validateModulesOption("systemjs") === "systemjs");
});
it("amd option is valid", () => {
assert(validateModulesOption("amd") === "amd");
});
it("umd option is valid", () => {
assert(validateModulesOption("umd") === "umd");
});
it("`true` option is invalid", () => {
assert.throws(() => {
validateModulesOption(true);
}, Error);
});
it("array option is invalid", () => {
assert.throws(() => {
assert(validateModulesOption([]));
}, Error);
});
describe("validatePluginsOption", function() {
it("should return empty arrays if undefined", function() {
assert.deepEqual(validatePluginsOption(), { all: [], plugins: [], builtIns: [] });
});
it("should return in transforms array", function() {
assert.deepEqual(
validatePluginsOption(["transform-es2015-arrow-functions"]),
{
all: ["transform-es2015-arrow-functions"],
plugins: ["transform-es2015-arrow-functions"],
builtIns: []
}
);
});
it("should return in built-ins array", function() {
assert.deepEqual(
validatePluginsOption(["es6.map"]),
{
all: ["es6.map"],
plugins: [],
builtIns: ["es6.map"]
}
);
});
it("should throw if not in features", function() {
assert.throws(() => {
validatePluginsOption(["asdf"]);
}, Error);
});
});
describe("checkDuplicateIncludeExcludes", function() {
it("should throw if duplicate names in both", function() {
assert.throws(() => {
checkDuplicateIncludeExcludes(
["transform-regenerator", "map"],
["transform-regenerator", "map"]
);
}, Error);
});
it("should not throw if no duplicate names in both", function() {
assert.doesNotThrow(() => {
checkDuplicateIncludeExcludes(
["transform-regenerator"],
["map"]
);
}, Error);
});
it("should return in built-ins array", function() {
assert.deepEqual(
babelPresetEnv.transformIncludesAndExculdes(["es6.map"]),
{
all: ["es6.map"],
plugins: [],
builtIns: ["es6.map"]
}
);
});
});
});

View File

@ -0,0 +1,101 @@
"use strict";
const normalizeOptions = require("../lib/normalize-options.js");
const assert = require("assert");
const {
checkDuplicateIncludeExcludes,
validateIncludesAndExcludes,
validateLooseOption,
validateModulesOption
} = normalizeOptions;
describe("normalize-options", () => {
describe("validateLooseOption", () => {
it("`undefined` option returns false", () => {
assert(validateLooseOption() === false);
});
it("`false` option returns false", () => {
assert(validateLooseOption(false) === false);
});
it("`true` option returns true", () => {
assert(validateLooseOption(true) === true);
});
it("array option is invalid", () => {
assert.throws(() => {
validateLooseOption([]);
}, Error);
});
});
describe("checkDuplicateIncludeExcludes", function() {
it("should throw if duplicate names in both", function() {
assert.throws(() => {
checkDuplicateIncludeExcludes(
["transform-regenerator", "map"],
["transform-regenerator", "map"]
);
}, Error);
});
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");
});
it("`false` option returns commonjs", () => {
assert(validateModulesOption(false) === false);
});
it("commonjs option is valid", () => {
assert(validateModulesOption("commonjs") === "commonjs");
});
it("systemjs option is valid", () => {
assert(validateModulesOption("systemjs") === "systemjs");
});
it("amd option is valid", () => {
assert(validateModulesOption("amd") === "amd");
});
it("umd option is valid", () => {
assert(validateModulesOption("umd") === "umd");
});
it("`true` option is invalid", () => {
assert.throws(() => {
validateModulesOption(true);
}, Error);
});
it("array option is invalid", () => {
assert.throws(() => {
assert(validateModulesOption([]));
}, 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);
});
});
});