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:
Huáng Jùnliàng 2020-09-24 16:23:35 -04:00 committed by GitHub
parent 0d32e3fc36
commit f2da186714
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 374 additions and 196 deletions

View File

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

View File

@ -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": {

View File

@ -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,34 +220,28 @@ 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 = [];
const value = targets[target]; for (const target of Object.keys(targets).sort()) {
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?
// $FlowIgnore - Flow doesn't like that some targetParserMap[target] might be missing // $FlowIgnore - Flow doesn't like that some targetParserMap[target] might be missing
const parser = targetParserMap[target] ?? targetParserMap.__default; const parser = targetParserMap[target] ?? targetParserMap.__default;
const [parsedTarget, parsedValue] = parser(target, value); const [parsedTarget, parsedValue] = parser(target, value);
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;
} }

View File

@ -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 = {

View File

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

View File

@ -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'."`;

View File

@ -260,4 +260,12 @@ describe("getTargets", () => {
}); });
}); });
}); });
describe("exception", () => {
it("throws when version is not a semver", () => {
expect(() =>
getTargets({ chrome: "seventy-two" }),
).toThrowErrorMatchingSnapshot();
});
});
}); });

View File

@ -0,0 +1,3 @@
src
test
*.log

View 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
```

View 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"
}

View File

@ -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))];
}

View File

@ -0,0 +1,2 @@
export { OptionValidator } from "./validator";
export { findSuggestion } from "./find-suggestion";

View 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}`;
}
}

View File

@ -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);
});
});

View File

@ -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();
});
});
});

View File

@ -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": {

View File

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

View File

@ -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(() => {

View File

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