refactor: add @babel/helper-validator-option (#12006)
* refactor: add @babel/helper-validator-option * refactor: simplify validateTopLevelOptions * perf: the recursive version is not practically fast * Update packages/babel-helper-validator-option/README.md Co-authored-by: Brian Ng <bng412@gmail.com> * Update packages/babel-helper-validator-option/src/validator.js * fix: incorrect type annotation * refactor: use babel/helper-option-validator in babel/compat-data * chore: fix flow types error * Address review comments * address review comments Co-authored-by: Brian Ng <bng412@gmail.com>
This commit is contained in:
parent
0d32e3fc36
commit
f2da186714
@ -106,7 +106,10 @@ module.exports = function (api) {
|
|||||||
plugins: [
|
plugins: [
|
||||||
// TODO: Use @babel/preset-flow when
|
// TODO: Use @babel/preset-flow when
|
||||||
// https://github.com/babel/babel/issues/7233 is fixed
|
// https://github.com/babel/babel/issues/7233 is fixed
|
||||||
|
[
|
||||||
"@babel/plugin-transform-flow-strip-types",
|
"@babel/plugin-transform-flow-strip-types",
|
||||||
|
{ allowDeclareFields: true },
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"@babel/proposal-object-rest-spread",
|
"@babel/proposal-object-rest-spread",
|
||||||
{ useBuiltIns: true, loose: true },
|
{ useBuiltIns: true, loose: true },
|
||||||
|
|||||||
@ -22,9 +22,8 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/compat-data": "workspace:^7.10.4",
|
"@babel/compat-data": "workspace:^7.10.4",
|
||||||
|
"@babel/helper-validator-option": "workspace:^7.11.4",
|
||||||
"browserslist": "^4.12.0",
|
"browserslist": "^4.12.0",
|
||||||
"invariant": "^2.2.4",
|
|
||||||
"levenary": "^1.1.1",
|
|
||||||
"semver": "^5.5.0"
|
"semver": "^5.5.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import browserslist from "browserslist";
|
import browserslist from "browserslist";
|
||||||
import findSuggestion from "levenary";
|
import { findSuggestion } from "@babel/helper-validator-option";
|
||||||
import invariant from "invariant";
|
|
||||||
import browserModulesData from "@babel/compat-data/native-modules";
|
import browserModulesData from "@babel/compat-data/native-modules";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -11,9 +10,11 @@ import {
|
|||||||
isUnreleasedVersion,
|
isUnreleasedVersion,
|
||||||
getLowestUnreleased,
|
getLowestUnreleased,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
import { OptionValidator } from "@babel/helper-validator-option";
|
||||||
import { browserNameMap } from "./targets";
|
import { browserNameMap } from "./targets";
|
||||||
import { TargetNames } from "./options";
|
import { TargetNames } from "./options";
|
||||||
import type { Target, Targets, InputTargets, Browsers } from "./types";
|
import { name as packageName } from "../package.json";
|
||||||
|
import type { Targets, InputTargets, Browsers, TargetsTuple } from "./types";
|
||||||
|
|
||||||
export type { Targets, InputTargets };
|
export type { Targets, InputTargets };
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ export { getInclusionReasons } from "./debug";
|
|||||||
export { default as filterItems, isRequired } from "./filter-items";
|
export { default as filterItems, isRequired } from "./filter-items";
|
||||||
export { unreleasedLabels } from "./targets";
|
export { unreleasedLabels } from "./targets";
|
||||||
|
|
||||||
|
const v = new OptionValidator(packageName);
|
||||||
const browserslistDefaults = browserslist.defaults;
|
const browserslistDefaults = browserslist.defaults;
|
||||||
|
|
||||||
const validBrowserslistTargets = [
|
const validBrowserslistTargets = [
|
||||||
@ -39,19 +41,18 @@ function objectToBrowserslist(object: Targets): Array<string> {
|
|||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateTargetNames(targets: InputTargets): Targets {
|
function validateTargetNames(targets: Targets): TargetsTuple {
|
||||||
const validTargets = Object.keys(TargetNames);
|
const validTargets = Object.keys(TargetNames);
|
||||||
for (const target in targets) {
|
for (const target of Object.keys(targets)) {
|
||||||
if (!TargetNames[target]) {
|
if (!(target in TargetNames)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid Option: '${target}' is not a valid target
|
v.formatMessage(`'${target}' is not a valid target
|
||||||
Maybe you meant to use '${findSuggestion(target, validTargets)}'?`,
|
- Did you mean '${findSuggestion(target, validTargets)}'?`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// $FlowIgnore
|
return (targets: any);
|
||||||
return targets;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isBrowsersQueryValid(browsers: Browsers | Targets): boolean {
|
export function isBrowsersQueryValid(browsers: Browsers | Targets): boolean {
|
||||||
@ -59,9 +60,9 @@ export function isBrowsersQueryValid(browsers: Browsers | Targets): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validateBrowsers(browsers: Browsers | void) {
|
function validateBrowsers(browsers: Browsers | void) {
|
||||||
invariant(
|
v.invariant(
|
||||||
typeof browsers === "undefined" || isBrowsersQueryValid(browsers),
|
browsers === undefined || isBrowsersQueryValid(browsers),
|
||||||
`Invalid Option: '${String(browsers)}' is not a valid browserslist query`,
|
`'${String(browsers)}' is not a valid browserslist query`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return browsers;
|
return browsers;
|
||||||
@ -110,8 +111,10 @@ function getLowestVersions(browsers: Array<string>): Targets {
|
|||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function outputDecimalWarning(decimalTargets: Array<Object>): void {
|
function outputDecimalWarning(
|
||||||
if (!decimalTargets?.length) {
|
decimalTargets: Array<{| target: string, value: string |}>,
|
||||||
|
): void {
|
||||||
|
if (!decimalTargets.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +136,9 @@ function semverifyTarget(target, value) {
|
|||||||
return semverify(value);
|
return semverify(value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid Option: '${value}' is not a valid value for 'targets.${target}'.`,
|
v.formatMessage(
|
||||||
|
`'${value}' is not a valid value for 'targets.${target}'.`,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,16 +161,17 @@ const targetParserMap = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type ParsedResult = {
|
function generateTargets(inputTargets: InputTargets): Targets {
|
||||||
targets: Targets,
|
const input = { ...inputTargets };
|
||||||
decimalWarnings: Array<Object>,
|
delete input.esmodules;
|
||||||
};
|
delete input.browsers;
|
||||||
|
return ((input: any): Targets);
|
||||||
|
}
|
||||||
|
|
||||||
export default function getTargets(
|
export default function getTargets(
|
||||||
inputTargets: InputTargets = {},
|
inputTargets: InputTargets = {},
|
||||||
options: Object = {},
|
options: Object = {},
|
||||||
): Targets {
|
): Targets {
|
||||||
const targetOpts: Targets = {};
|
|
||||||
let { browsers } = inputTargets;
|
let { browsers } = inputTargets;
|
||||||
|
|
||||||
// `esmodules` as a target indicates the specific set of browsers supporting ES Modules.
|
// `esmodules` as a target indicates the specific set of browsers supporting ES Modules.
|
||||||
@ -180,12 +186,8 @@ export default function getTargets(
|
|||||||
// Parse browsers target via browserslist
|
// Parse browsers target via browserslist
|
||||||
const browsersquery = validateBrowsers(browsers);
|
const browsersquery = validateBrowsers(browsers);
|
||||||
|
|
||||||
// Remove esmodules after being consumed to fix `hasTargets` below
|
const input = generateTargets(inputTargets);
|
||||||
const input = { ...inputTargets };
|
let targets: TargetsTuple = validateTargetNames(input);
|
||||||
delete input.esmodules;
|
|
||||||
delete input.browsers;
|
|
||||||
|
|
||||||
let targets: Targets = validateTargetNames(input);
|
|
||||||
|
|
||||||
const shouldParseBrowsers = !!browsersquery;
|
const shouldParseBrowsers = !!browsersquery;
|
||||||
const hasTargets = shouldParseBrowsers || Object.keys(targets).length > 0;
|
const hasTargets = shouldParseBrowsers || Object.keys(targets).length > 0;
|
||||||
@ -218,13 +220,14 @@ export default function getTargets(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse remaining targets
|
// Parse remaining targets
|
||||||
const parsed = (Object.keys(targets): Array<Target>).sort().reduce(
|
const result: Targets = {};
|
||||||
(results: ParsedResult, target: $Keys<Targets>): ParsedResult => {
|
const decimalWarnings = [];
|
||||||
|
for (const target of Object.keys(targets).sort()) {
|
||||||
const value = targets[target];
|
const value = targets[target];
|
||||||
|
|
||||||
// Warn when specifying minor/patch as a decimal
|
// Warn when specifying minor/patch as a decimal
|
||||||
if (typeof value === "number" && value % 1 !== 0) {
|
if (typeof value === "number" && value % 1 !== 0) {
|
||||||
results.decimalWarnings.push({ target, value });
|
decimalWarnings.push({ target, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have a target parser?
|
// Check if we have a target parser?
|
||||||
@ -234,18 +237,11 @@ export default function getTargets(
|
|||||||
|
|
||||||
if (parsedValue) {
|
if (parsedValue) {
|
||||||
// Merge (lowest wins)
|
// Merge (lowest wins)
|
||||||
results.targets[parsedTarget] = parsedValue;
|
result[parsedTarget] = parsedValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
outputDecimalWarning(decimalWarnings);
|
||||||
},
|
|
||||||
{
|
|
||||||
targets: targetOpts,
|
|
||||||
decimalWarnings: [],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
outputDecimalWarning(parsed.decimalWarnings);
|
return result;
|
||||||
|
|
||||||
return parsed.targets;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,10 @@ export type Targets = {
|
|||||||
[target: Target]: string,
|
[target: Target]: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TargetsTuple = {|
|
||||||
|
[target: Target]: string,
|
||||||
|
|};
|
||||||
|
|
||||||
export type Browsers = string | Array<string>;
|
export type Browsers = string | Array<string>;
|
||||||
|
|
||||||
export type InputTargets = {
|
export type InputTargets = {
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import invariant from "invariant";
|
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
|
import { OptionValidator } from "@babel/helper-validator-option";
|
||||||
|
import { name as packageName } from "../package.json";
|
||||||
import { unreleasedLabels } from "./targets";
|
import { unreleasedLabels } from "./targets";
|
||||||
import type { Target, Targets } from "./types";
|
import type { Target, Targets } from "./types";
|
||||||
|
|
||||||
const versionRegExp = /^(\d+|\d+.\d+)$/;
|
const versionRegExp = /^(\d+|\d+.\d+)$/;
|
||||||
|
|
||||||
|
const v = new OptionValidator(packageName);
|
||||||
|
|
||||||
export function semverMin(first: ?string, second: string): string {
|
export function semverMin(first: ?string, second: string): string {
|
||||||
return first && semver.lt(first, second) ? first : second;
|
return first && semver.lt(first, second) ? first : second;
|
||||||
}
|
}
|
||||||
@ -19,7 +20,7 @@ export function semverify(version: number | string): string {
|
|||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
invariant(
|
v.invariant(
|
||||||
typeof version === "number" ||
|
typeof version === "number" ||
|
||||||
(typeof version === "string" && versionRegExp.test(version)),
|
(typeof version === "string" && versionRegExp.test(version)),
|
||||||
`'${version}' is not a valid version`,
|
`'${version}' is not a valid version`,
|
||||||
|
|||||||
@ -57,3 +57,5 @@ Object {
|
|||||||
"samsung": "8.2.0",
|
"samsung": "8.2.0",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`getTargets exception throws when version is not a semver 1`] = `"@babel/helper-compilation-targets: 'seventy-two' is not a valid value for 'targets.chrome'."`;
|
||||||
|
|||||||
@ -260,4 +260,12 @@ describe("getTargets", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("exception", () => {
|
||||||
|
it("throws when version is not a semver", () => {
|
||||||
|
expect(() =>
|
||||||
|
getTargets({ chrome: "seventy-two" }),
|
||||||
|
).toThrowErrorMatchingSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
3
packages/babel-helper-validator-option/.npmignore
Normal file
3
packages/babel-helper-validator-option/.npmignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
src
|
||||||
|
test
|
||||||
|
*.log
|
||||||
19
packages/babel-helper-validator-option/README.md
Normal file
19
packages/babel-helper-validator-option/README.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# @babel/helper-validator-option
|
||||||
|
|
||||||
|
> Validate plugin/preset options
|
||||||
|
|
||||||
|
See our website [@babel/helper-validator-option](https://babeljs.io/docs/en/next/babel-helper-validator-option.html) for more information.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Using npm:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --save-dev @babel/helper-validator-option
|
||||||
|
```
|
||||||
|
|
||||||
|
or using yarn:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @babel/helper-validator-option --dev
|
||||||
|
```
|
||||||
16
packages/babel-helper-validator-option/package.json
Normal file
16
packages/babel-helper-validator-option/package.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@babel/helper-validator-option",
|
||||||
|
"version": "7.11.4",
|
||||||
|
"description": "Validate plugin/preset options",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/babel/babel.git",
|
||||||
|
"directory": "packages/babel-helper-validator-option"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"main": "./lib/index.js",
|
||||||
|
"exports": "./lib/index.js"
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
const { min } = Math;
|
||||||
|
|
||||||
|
// a minimal leven distance implementation
|
||||||
|
// balanced maintenability with code size
|
||||||
|
// It is not blazingly fast but should be okay for Babel user case
|
||||||
|
// where it will be run for at most tens of time on strings
|
||||||
|
// that have less than 20 ASCII characters
|
||||||
|
|
||||||
|
// https://rosettacode.org/wiki/Levenshtein_distance#ES5
|
||||||
|
function levenshtein(a, b) {
|
||||||
|
let t = [],
|
||||||
|
u = [],
|
||||||
|
i,
|
||||||
|
j;
|
||||||
|
const m = a.length,
|
||||||
|
n = b.length;
|
||||||
|
if (!m) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
if (!n) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
for (j = 0; j <= n; j++) {
|
||||||
|
t[j] = j;
|
||||||
|
}
|
||||||
|
for (i = 1; i <= m; i++) {
|
||||||
|
for (u = [i], j = 1; j <= n; j++) {
|
||||||
|
u[j] =
|
||||||
|
a[i - 1] === b[j - 1] ? t[j - 1] : min(t[j - 1], t[j], u[j - 1]) + 1;
|
||||||
|
}
|
||||||
|
t = u;
|
||||||
|
}
|
||||||
|
return u[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a string `str` and an array of candidates `arr`,
|
||||||
|
* return the first of elements in candidates that has minimal
|
||||||
|
* Levenshtein distance with `str`.
|
||||||
|
* @export
|
||||||
|
* @param {string} str
|
||||||
|
* @param {string[]} arr
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function findSuggestion(str: string, arr: string[]): string {
|
||||||
|
const distances = arr.map<number>(el => levenshtein(el, str));
|
||||||
|
return arr[distances.indexOf(min(...distances))];
|
||||||
|
}
|
||||||
2
packages/babel-helper-validator-option/src/index.js
Normal file
2
packages/babel-helper-validator-option/src/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { OptionValidator } from "./validator";
|
||||||
|
export { findSuggestion } from "./find-suggestion";
|
||||||
81
packages/babel-helper-validator-option/src/validator.js
Normal file
81
packages/babel-helper-validator-option/src/validator.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// @flow
|
||||||
|
import { findSuggestion } from "./find-suggestion.js";
|
||||||
|
|
||||||
|
export class OptionValidator {
|
||||||
|
declare descriptor: string;
|
||||||
|
constructor(descriptor: string) {
|
||||||
|
this.descriptor = descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate if the given `options` follow the name of keys defined in the `TopLevelOptionShape`
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {Object} TopLevelOptionShape
|
||||||
|
* An object with all the valid key names that `options` should be allowed to have
|
||||||
|
* The property values of `TopLevelOptionShape` can be arbitrary
|
||||||
|
* @memberof OptionValidator
|
||||||
|
*/
|
||||||
|
validateTopLevelOptions(options: Object, TopLevelOptionShape: Object): void {
|
||||||
|
const validOptionNames = Object.keys(TopLevelOptionShape);
|
||||||
|
for (const option of Object.keys(options)) {
|
||||||
|
if (!validOptionNames.includes(option)) {
|
||||||
|
throw new Error(
|
||||||
|
this.formatMessage(`'${option}' is not a valid top-level option.
|
||||||
|
- Did you mean '${findSuggestion(option, validOptionNames)}'?`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: we do not consider rewrite them to high order functions
|
||||||
|
// until we have to support `validateNumberOption`.
|
||||||
|
validateBooleanOption(
|
||||||
|
name: string,
|
||||||
|
value?: boolean,
|
||||||
|
defaultValue?: boolean,
|
||||||
|
): boolean | void {
|
||||||
|
if (value === undefined) {
|
||||||
|
value = defaultValue;
|
||||||
|
} else {
|
||||||
|
this.invariant(
|
||||||
|
typeof value === "boolean",
|
||||||
|
`'${name}' option must be a boolean.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateStringOption(
|
||||||
|
name: string,
|
||||||
|
value?: string,
|
||||||
|
defaultValue?: string,
|
||||||
|
): string | void {
|
||||||
|
if (value === undefined) {
|
||||||
|
value = defaultValue;
|
||||||
|
} else {
|
||||||
|
this.invariant(
|
||||||
|
typeof value === "string",
|
||||||
|
`'${name}' option must be a string.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* A helper interface copied from the `invariant` npm package.
|
||||||
|
* It throws given `message` when `condition` is not met
|
||||||
|
*
|
||||||
|
* @param {boolean} condition
|
||||||
|
* @param {string} message
|
||||||
|
* @memberof OptionValidator
|
||||||
|
*/
|
||||||
|
invariant(condition: boolean, message: string): void {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(this.formatMessage(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatMessage(message: string): string {
|
||||||
|
return `${this.descriptor}: ${message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { findSuggestion } from "..";
|
||||||
|
|
||||||
|
describe("findSuggestion", function () {
|
||||||
|
test.each([
|
||||||
|
["cat", ["cow", "dog", "pig"], "cow"],
|
||||||
|
["uglifyjs", [], undefined],
|
||||||
|
])("findSuggestion(%p, %p) returns %p", (str, arr, expected) => {
|
||||||
|
expect(findSuggestion(str, arr)).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
import { OptionValidator } from "..";
|
||||||
|
|
||||||
|
describe("OptionValidator", () => {
|
||||||
|
describe("validateTopLevelOptions", () => {
|
||||||
|
let v;
|
||||||
|
beforeAll(() => {
|
||||||
|
v = new OptionValidator("test-descriptor");
|
||||||
|
});
|
||||||
|
it("should throw when option key is not found", () => {
|
||||||
|
expect(() =>
|
||||||
|
v.validateTopLevelOptions(
|
||||||
|
{ unknown: "options" },
|
||||||
|
{ foo: "foo" },
|
||||||
|
"test",
|
||||||
|
),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
it("should throw when option key is an own property but not found", () => {
|
||||||
|
expect(() =>
|
||||||
|
v.validateTopLevelOptions(
|
||||||
|
{ hasOwnProperty: "foo" },
|
||||||
|
{
|
||||||
|
foo: "foo",
|
||||||
|
bar: "bar",
|
||||||
|
aLongPropertyKeyToSeeLevenPerformance: "a",
|
||||||
|
},
|
||||||
|
"test",
|
||||||
|
),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("validateBooleanOption", () => {
|
||||||
|
let v;
|
||||||
|
beforeAll(() => {
|
||||||
|
v = new OptionValidator("test-descriptor");
|
||||||
|
});
|
||||||
|
it("`undefined` option returns false", () => {
|
||||||
|
expect(v.validateBooleanOption("test", undefined, false)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`false` option returns false", () => {
|
||||||
|
expect(v.validateBooleanOption("test", false, false)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`true` option returns true", () => {
|
||||||
|
expect(v.validateBooleanOption("test", true, false)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("array option is invalid", () => {
|
||||||
|
expect(() => {
|
||||||
|
v.validateBooleanOption("test", [], false);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("validateStringOption", () => {
|
||||||
|
let v;
|
||||||
|
beforeAll(() => {
|
||||||
|
v = new OptionValidator("test-descriptor");
|
||||||
|
});
|
||||||
|
it("`undefined` option default", () => {
|
||||||
|
expect(v.validateStringOption("test", undefined, "default")).toBe(
|
||||||
|
"default",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`value` option returns value", () => {
|
||||||
|
expect(v.validateStringOption("test", "value", "default")).toBe("value");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("no default returns undefined", () => {
|
||||||
|
expect(v.validateStringOption("test", undefined)).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("array option is invalid", () => {
|
||||||
|
expect(() => {
|
||||||
|
v.validateStringOption("test", [], "default");
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -19,6 +19,7 @@
|
|||||||
"@babel/helper-compilation-targets": "workspace:^7.10.4",
|
"@babel/helper-compilation-targets": "workspace:^7.10.4",
|
||||||
"@babel/helper-module-imports": "workspace:^7.10.4",
|
"@babel/helper-module-imports": "workspace:^7.10.4",
|
||||||
"@babel/helper-plugin-utils": "workspace:^7.10.4",
|
"@babel/helper-plugin-utils": "workspace:^7.10.4",
|
||||||
|
"@babel/helper-validator-option": "workspace:^7.11.4",
|
||||||
"@babel/plugin-proposal-async-generator-functions": "workspace:^7.10.4",
|
"@babel/plugin-proposal-async-generator-functions": "workspace:^7.10.4",
|
||||||
"@babel/plugin-proposal-class-properties": "workspace:^7.10.4",
|
"@babel/plugin-proposal-class-properties": "workspace:^7.10.4",
|
||||||
"@babel/plugin-proposal-dynamic-import": "workspace:^7.10.4",
|
"@babel/plugin-proposal-dynamic-import": "workspace:^7.10.4",
|
||||||
@ -80,8 +81,6 @@
|
|||||||
"@babel/types": "workspace:^7.11.5",
|
"@babel/types": "workspace:^7.11.5",
|
||||||
"browserslist": "^4.12.0",
|
"browserslist": "^4.12.0",
|
||||||
"core-js-compat": "^3.6.2",
|
"core-js-compat": "^3.6.2",
|
||||||
"invariant": "^2.2.2",
|
|
||||||
"levenary": "^1.1.1",
|
|
||||||
"semver": "^5.5.0"
|
"semver": "^5.5.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import corejs3Polyfills from "core-js-compat/data";
|
import corejs3Polyfills from "core-js-compat/data";
|
||||||
import findSuggestion from "levenary";
|
|
||||||
import invariant from "invariant";
|
|
||||||
import { coerce, SemVer } from "semver";
|
import { coerce, SemVer } from "semver";
|
||||||
import corejs2Polyfills from "@babel/compat-data/corejs2-built-ins";
|
import corejs2Polyfills from "@babel/compat-data/corejs2-built-ins";
|
||||||
import { plugins as pluginsList } from "./plugins-compat-data";
|
import { plugins as pluginsList } from "./plugins-compat-data";
|
||||||
import moduleTransformations from "./module-transformations";
|
import moduleTransformations from "./module-transformations";
|
||||||
import { TopLevelOptions, ModulesOption, UseBuiltInsOption } from "./options";
|
import { TopLevelOptions, ModulesOption, UseBuiltInsOption } from "./options";
|
||||||
|
import { OptionValidator } from "@babel/helper-validator-option";
|
||||||
import { defaultWebIncludes } from "./polyfills/corejs2/get-platform-specific-default";
|
import { defaultWebIncludes } from "./polyfills/corejs2/get-platform-specific-default";
|
||||||
|
import { name as packageName } from "../package.json";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BuiltInsOption,
|
BuiltInsOption,
|
||||||
@ -18,18 +18,7 @@ import type {
|
|||||||
PluginListOption,
|
PluginListOption,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
const validateTopLevelOptions = (options: Options) => {
|
const v = new OptionValidator(packageName);
|
||||||
const validOptions = Object.keys(TopLevelOptions);
|
|
||||||
|
|
||||||
for (const option in options) {
|
|
||||||
if (!TopLevelOptions[option]) {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid Option: ${option} is not a valid top-level option.
|
|
||||||
Maybe you meant to use '${findSuggestion(option, validOptions)}'?`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const allPluginsList = Object.keys(pluginsList);
|
const allPluginsList = Object.keys(pluginsList);
|
||||||
|
|
||||||
@ -89,9 +78,9 @@ const expandIncludesAndExcludes = (
|
|||||||
(p, i) => selectedPlugins[i].length === 0,
|
(p, i) => selectedPlugins[i].length === 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
invariant(
|
v.invariant(
|
||||||
invalidRegExpList.length === 0,
|
invalidRegExpList.length === 0,
|
||||||
`Invalid Option: The plugins/built-ins '${invalidRegExpList.join(
|
`The plugins/built-ins '${invalidRegExpList.join(
|
||||||
", ",
|
", ",
|
||||||
)}' passed to the '${type}' option are not
|
)}' passed to the '${type}' option are not
|
||||||
valid. Please check data/[plugin-features|built-in-features].js in babel-preset-env`,
|
valid. Please check data/[plugin-features|built-in-features].js in babel-preset-env`,
|
||||||
@ -109,9 +98,9 @@ export const checkDuplicateIncludeExcludes = (
|
|||||||
) => {
|
) => {
|
||||||
const duplicates = include.filter(opt => exclude.indexOf(opt) >= 0);
|
const duplicates = include.filter(opt => exclude.indexOf(opt) >= 0);
|
||||||
|
|
||||||
invariant(
|
v.invariant(
|
||||||
duplicates.length === 0,
|
duplicates.length === 0,
|
||||||
`Invalid Option: The plugins/built-ins '${duplicates.join(
|
`The plugins/built-ins '${duplicates.join(
|
||||||
", ",
|
", ",
|
||||||
)}' were found in both the "include" and
|
)}' were found in both the "include" and
|
||||||
"exclude" options.`,
|
"exclude" options.`,
|
||||||
@ -126,61 +115,12 @@ const normalizeTargets = targets => {
|
|||||||
return { ...targets };
|
return { ...targets };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validateConfigPathOption = (
|
|
||||||
configPath: string = process.cwd(),
|
|
||||||
) => {
|
|
||||||
invariant(
|
|
||||||
typeof configPath === "string",
|
|
||||||
`Invalid Option: The configPath option '${configPath}' is invalid, only strings are allowed.`,
|
|
||||||
);
|
|
||||||
return configPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validateBoolOption = (
|
|
||||||
name: string,
|
|
||||||
value?: boolean,
|
|
||||||
defaultValue: boolean,
|
|
||||||
) => {
|
|
||||||
if (typeof value === "undefined") {
|
|
||||||
value = defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value !== "boolean") {
|
|
||||||
throw new Error(`Preset env: '${name}' option must be a boolean.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validateStringOption = (
|
|
||||||
name: string,
|
|
||||||
value?: string,
|
|
||||||
defaultValue?: string,
|
|
||||||
) => {
|
|
||||||
if (typeof value === "undefined") {
|
|
||||||
value = defaultValue;
|
|
||||||
} else if (typeof value !== "string") {
|
|
||||||
throw new Error(`Preset env: '${name}' option must be a string.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validateIgnoreBrowserslistConfig = (
|
|
||||||
ignoreBrowserslistConfig: boolean,
|
|
||||||
) =>
|
|
||||||
validateBoolOption(
|
|
||||||
TopLevelOptions.ignoreBrowserslistConfig,
|
|
||||||
ignoreBrowserslistConfig,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const validateModulesOption = (
|
export const validateModulesOption = (
|
||||||
modulesOpt: ModuleOption = ModulesOption.auto,
|
modulesOpt: ModuleOption = ModulesOption.auto,
|
||||||
) => {
|
) => {
|
||||||
invariant(
|
v.invariant(
|
||||||
ModulesOption[modulesOpt.toString()] || modulesOpt === ModulesOption.false,
|
ModulesOption[modulesOpt.toString()] || modulesOpt === ModulesOption.false,
|
||||||
`Invalid Option: The 'modules' option must be one of \n` +
|
`The 'modules' option must be one of \n` +
|
||||||
` - 'false' to indicate no module processing\n` +
|
` - 'false' to indicate no module processing\n` +
|
||||||
` - a specific module type: 'commonjs', 'amd', 'umd', 'systemjs'` +
|
` - a specific module type: 'commonjs', 'amd', 'umd', 'systemjs'` +
|
||||||
` - 'auto' (default) which will automatically select 'false' if the current\n` +
|
` - 'auto' (default) which will automatically select 'false' if the current\n` +
|
||||||
@ -193,10 +133,10 @@ export const validateModulesOption = (
|
|||||||
export const validateUseBuiltInsOption = (
|
export const validateUseBuiltInsOption = (
|
||||||
builtInsOpt: BuiltInsOption = false,
|
builtInsOpt: BuiltInsOption = false,
|
||||||
) => {
|
) => {
|
||||||
invariant(
|
v.invariant(
|
||||||
UseBuiltInsOption[builtInsOpt.toString()] ||
|
UseBuiltInsOption[builtInsOpt.toString()] ||
|
||||||
builtInsOpt === UseBuiltInsOption.false,
|
builtInsOpt === UseBuiltInsOption.false,
|
||||||
`Invalid Option: The 'useBuiltIns' option must be either
|
`The 'useBuiltIns' option must be either
|
||||||
'false' (default) to indicate no polyfill,
|
'false' (default) to indicate no polyfill,
|
||||||
'"entry"' to indicate replacing the entry polyfill, or
|
'"entry"' to indicate replacing the entry polyfill, or
|
||||||
'"usage"' to import only used polyfills per file`,
|
'"usage"' to import only used polyfills per file`,
|
||||||
@ -258,7 +198,7 @@ export function normalizeCoreJSOption(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function normalizeOptions(opts: Options) {
|
export default function normalizeOptions(opts: Options) {
|
||||||
validateTopLevelOptions(opts);
|
v.validateTopLevelOptions(opts, TopLevelOptions);
|
||||||
|
|
||||||
const useBuiltIns = validateUseBuiltInsOption(opts.useBuiltIns);
|
const useBuiltIns = validateUseBuiltInsOption(opts.useBuiltIns);
|
||||||
|
|
||||||
@ -278,38 +218,42 @@ export default function normalizeOptions(opts: Options) {
|
|||||||
|
|
||||||
checkDuplicateIncludeExcludes(include, exclude);
|
checkDuplicateIncludeExcludes(include, exclude);
|
||||||
|
|
||||||
const shippedProposals = validateBoolOption(
|
|
||||||
TopLevelOptions.shippedProposals,
|
|
||||||
opts.shippedProposals,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bugfixes: validateBoolOption(
|
bugfixes: v.validateBooleanOption(
|
||||||
TopLevelOptions.bugfixes,
|
TopLevelOptions.bugfixes,
|
||||||
opts.bugfixes,
|
opts.bugfixes,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
configPath: validateConfigPathOption(opts.configPath),
|
configPath: v.validateStringOption(
|
||||||
|
TopLevelOptions.configPath,
|
||||||
|
opts.configPath,
|
||||||
|
process.cwd(),
|
||||||
|
),
|
||||||
corejs,
|
corejs,
|
||||||
debug: validateBoolOption(TopLevelOptions.debug, opts.debug, false),
|
debug: v.validateBooleanOption(TopLevelOptions.debug, opts.debug, false),
|
||||||
include,
|
include,
|
||||||
exclude,
|
exclude,
|
||||||
forceAllTransforms: validateBoolOption(
|
forceAllTransforms: v.validateBooleanOption(
|
||||||
TopLevelOptions.forceAllTransforms,
|
TopLevelOptions.forceAllTransforms,
|
||||||
opts.forceAllTransforms,
|
opts.forceAllTransforms,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
ignoreBrowserslistConfig: validateIgnoreBrowserslistConfig(
|
ignoreBrowserslistConfig: v.validateBooleanOption(
|
||||||
|
TopLevelOptions.ignoreBrowserslistConfig,
|
||||||
opts.ignoreBrowserslistConfig,
|
opts.ignoreBrowserslistConfig,
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
loose: validateBoolOption(TopLevelOptions.loose, opts.loose, false),
|
loose: v.validateBooleanOption(TopLevelOptions.loose, opts.loose, false),
|
||||||
modules: validateModulesOption(opts.modules),
|
modules: validateModulesOption(opts.modules),
|
||||||
shippedProposals,
|
shippedProposals: v.validateBooleanOption(
|
||||||
spec: validateBoolOption(TopLevelOptions.spec, opts.spec, false),
|
TopLevelOptions.shippedProposals,
|
||||||
|
opts.shippedProposals,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
spec: v.validateBooleanOption(TopLevelOptions.spec, opts.spec, false),
|
||||||
targets: normalizeTargets(opts.targets),
|
targets: normalizeTargets(opts.targets),
|
||||||
useBuiltIns: useBuiltIns,
|
useBuiltIns: useBuiltIns,
|
||||||
browserslistEnv: validateStringOption(
|
browserslistEnv: v.validateStringOption(
|
||||||
TopLevelOptions.browserslistEnv,
|
TopLevelOptions.browserslistEnv,
|
||||||
opts.browserslistEnv,
|
opts.browserslistEnv,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -4,8 +4,6 @@ const normalizeOptions = require("../lib/normalize-options.js");
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
checkDuplicateIncludeExcludes,
|
checkDuplicateIncludeExcludes,
|
||||||
validateBoolOption,
|
|
||||||
validateStringOption,
|
|
||||||
validateModulesOption,
|
validateModulesOption,
|
||||||
validateUseBuiltInsOption,
|
validateUseBuiltInsOption,
|
||||||
normalizePluginName,
|
normalizePluginName,
|
||||||
@ -172,48 +170,6 @@ describe("normalize-options", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("validateBoolOption", () => {
|
|
||||||
it("`undefined` option returns false", () => {
|
|
||||||
expect(validateBoolOption("test", undefined, false)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`false` option returns false", () => {
|
|
||||||
expect(validateBoolOption("test", false, false)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`true` option returns true", () => {
|
|
||||||
expect(validateBoolOption("test", true, false)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("array option is invalid", () => {
|
|
||||||
expect(() => {
|
|
||||||
validateBoolOption("test", [], false);
|
|
||||||
}).toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("validateStringOption", () => {
|
|
||||||
it("`undefined` option default", () => {
|
|
||||||
expect(validateStringOption("test", undefined, "default")).toBe(
|
|
||||||
"default",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`value` option returns value", () => {
|
|
||||||
expect(validateStringOption("test", "value", "default")).toBe("value");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("no default returns undefined", () => {
|
|
||||||
expect(validateStringOption("test", undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("array option is invalid", () => {
|
|
||||||
expect(() => {
|
|
||||||
validateStringOption("test", [], "default");
|
|
||||||
}).toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("checkDuplicateIncludeExcludes", function () {
|
describe("checkDuplicateIncludeExcludes", function () {
|
||||||
it("should throw if duplicate names in both", function () {
|
it("should throw if duplicate names in both", function () {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
|
|||||||
12
yarn.lock
12
yarn.lock
@ -358,9 +358,8 @@ __metadata:
|
|||||||
"@babel/compat-data": "workspace:^7.10.4"
|
"@babel/compat-data": "workspace:^7.10.4"
|
||||||
"@babel/core": "workspace:^7.10.4"
|
"@babel/core": "workspace:^7.10.4"
|
||||||
"@babel/helper-plugin-test-runner": "workspace:^7.10.4"
|
"@babel/helper-plugin-test-runner": "workspace:^7.10.4"
|
||||||
|
"@babel/helper-validator-option": "workspace:^7.11.4"
|
||||||
browserslist: ^4.12.0
|
browserslist: ^4.12.0
|
||||||
invariant: ^2.2.4
|
|
||||||
levenary: ^1.1.1
|
|
||||||
semver: ^5.5.0
|
semver: ^5.5.0
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@babel/core": ^7.0.0
|
"@babel/core": ^7.0.0
|
||||||
@ -794,6 +793,12 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@babel/helper-validator-option@workspace:^7.11.4, @babel/helper-validator-option@workspace:packages/babel-helper-validator-option":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "@babel/helper-validator-option@workspace:packages/babel-helper-validator-option"
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"@babel/helper-wrap-function@npm:^7.10.4":
|
"@babel/helper-wrap-function@npm:^7.10.4":
|
||||||
version: 7.10.4
|
version: 7.10.4
|
||||||
resolution: "@babel/helper-wrap-function@npm:7.10.4"
|
resolution: "@babel/helper-wrap-function@npm:7.10.4"
|
||||||
@ -2957,6 +2962,7 @@ __metadata:
|
|||||||
"@babel/helper-module-imports": "workspace:^7.10.4"
|
"@babel/helper-module-imports": "workspace:^7.10.4"
|
||||||
"@babel/helper-plugin-test-runner": "workspace:^7.10.4"
|
"@babel/helper-plugin-test-runner": "workspace:^7.10.4"
|
||||||
"@babel/helper-plugin-utils": "workspace:^7.10.4"
|
"@babel/helper-plugin-utils": "workspace:^7.10.4"
|
||||||
|
"@babel/helper-validator-option": "workspace:^7.11.4"
|
||||||
"@babel/plugin-proposal-async-generator-functions": "workspace:^7.10.4"
|
"@babel/plugin-proposal-async-generator-functions": "workspace:^7.10.4"
|
||||||
"@babel/plugin-proposal-class-properties": "workspace:^7.10.4"
|
"@babel/plugin-proposal-class-properties": "workspace:^7.10.4"
|
||||||
"@babel/plugin-proposal-dynamic-import": "workspace:^7.10.4"
|
"@babel/plugin-proposal-dynamic-import": "workspace:^7.10.4"
|
||||||
@ -3018,8 +3024,6 @@ __metadata:
|
|||||||
"@babel/types": "workspace:^7.11.5"
|
"@babel/types": "workspace:^7.11.5"
|
||||||
browserslist: ^4.12.0
|
browserslist: ^4.12.0
|
||||||
core-js-compat: ^3.6.2
|
core-js-compat: ^3.6.2
|
||||||
invariant: ^2.2.2
|
|
||||||
levenary: ^1.1.1
|
|
||||||
semver: ^5.5.0
|
semver: ^5.5.0
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@babel/core": ^7.0.0-0
|
"@babel/core": ^7.0.0-0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user