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: [
// TODO: Use @babel/preset-flow when
// 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",
{ useBuiltIns: true, loose: true },

View File

@ -22,9 +22,8 @@
],
"dependencies": {
"@babel/compat-data": "workspace:^7.10.4",
"@babel/helper-validator-option": "workspace:^7.11.4",
"browserslist": "^4.12.0",
"invariant": "^2.2.4",
"levenary": "^1.1.1",
"semver": "^5.5.0"
},
"peerDependencies": {

View File

@ -1,8 +1,7 @@
// @flow
import browserslist from "browserslist";
import findSuggestion from "levenary";
import invariant from "invariant";
import { findSuggestion } from "@babel/helper-validator-option";
import browserModulesData from "@babel/compat-data/native-modules";
import {
@ -11,9 +10,11 @@ import {
isUnreleasedVersion,
getLowestUnreleased,
} from "./utils";
import { OptionValidator } from "@babel/helper-validator-option";
import { browserNameMap } from "./targets";
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 };
@ -22,6 +23,7 @@ export { getInclusionReasons } from "./debug";
export { default as filterItems, isRequired } from "./filter-items";
export { unreleasedLabels } from "./targets";
const v = new OptionValidator(packageName);
const browserslistDefaults = browserslist.defaults;
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);
for (const target in targets) {
if (!TargetNames[target]) {
for (const target of Object.keys(targets)) {
if (!(target in TargetNames)) {
throw new Error(
`Invalid Option: '${target}' is not a valid target
Maybe you meant to use '${findSuggestion(target, validTargets)}'?`,
v.formatMessage(`'${target}' is not a valid target
- Did you mean '${findSuggestion(target, validTargets)}'?`),
);
}
}
// $FlowIgnore
return targets;
return (targets: any);
}
export function isBrowsersQueryValid(browsers: Browsers | Targets): boolean {
@ -59,9 +60,9 @@ export function isBrowsersQueryValid(browsers: Browsers | Targets): boolean {
}
function validateBrowsers(browsers: Browsers | void) {
invariant(
typeof browsers === "undefined" || isBrowsersQueryValid(browsers),
`Invalid Option: '${String(browsers)}' is not a valid browserslist query`,
v.invariant(
browsers === undefined || isBrowsersQueryValid(browsers),
`'${String(browsers)}' is not a valid browserslist query`,
);
return browsers;
@ -110,8 +111,10 @@ function getLowestVersions(browsers: Array<string>): Targets {
}, {});
}
function outputDecimalWarning(decimalTargets: Array<Object>): void {
if (!decimalTargets?.length) {
function outputDecimalWarning(
decimalTargets: Array<{| target: string, value: string |}>,
): void {
if (!decimalTargets.length) {
return;
}
@ -133,7 +136,9 @@ function semverifyTarget(target, value) {
return semverify(value);
} catch (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 = {
targets: Targets,
decimalWarnings: Array<Object>,
};
function generateTargets(inputTargets: InputTargets): Targets {
const input = { ...inputTargets };
delete input.esmodules;
delete input.browsers;
return ((input: any): Targets);
}
export default function getTargets(
inputTargets: InputTargets = {},
options: Object = {},
): Targets {
const targetOpts: Targets = {};
let { browsers } = inputTargets;
// `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
const browsersquery = validateBrowsers(browsers);
// Remove esmodules after being consumed to fix `hasTargets` below
const input = { ...inputTargets };
delete input.esmodules;
delete input.browsers;
let targets: Targets = validateTargetNames(input);
const input = generateTargets(inputTargets);
let targets: TargetsTuple = validateTargetNames(input);
const shouldParseBrowsers = !!browsersquery;
const hasTargets = shouldParseBrowsers || Object.keys(targets).length > 0;
@ -218,34 +220,28 @@ export default function getTargets(
}
// Parse remaining targets
const parsed = (Object.keys(targets): Array<Target>).sort().reduce(
(results: ParsedResult, target: $Keys<Targets>): ParsedResult => {
const value = targets[target];
const result: Targets = {};
const decimalWarnings = [];
for (const target of Object.keys(targets).sort()) {
const value = targets[target];
// Warn when specifying minor/patch as a decimal
if (typeof value === "number" && value % 1 !== 0) {
results.decimalWarnings.push({ target, value });
}
// Warn when specifying minor/patch as a decimal
if (typeof value === "number" && value % 1 !== 0) {
decimalWarnings.push({ target, value });
}
// Check if we have a target parser?
// $FlowIgnore - Flow doesn't like that some targetParserMap[target] might be missing
const parser = targetParserMap[target] ?? targetParserMap.__default;
const [parsedTarget, parsedValue] = parser(target, value);
// Check if we have a target parser?
// $FlowIgnore - Flow doesn't like that some targetParserMap[target] might be missing
const parser = targetParserMap[target] ?? targetParserMap.__default;
const [parsedTarget, parsedValue] = parser(target, value);
if (parsedValue) {
// Merge (lowest wins)
results.targets[parsedTarget] = parsedValue;
}
if (parsedValue) {
// Merge (lowest wins)
result[parsedTarget] = parsedValue;
}
}
return results;
},
{
targets: targetOpts,
decimalWarnings: [],
},
);
outputDecimalWarning(decimalWarnings);
outputDecimalWarning(parsed.decimalWarnings);
return parsed.targets;
return result;
}

View File

@ -18,6 +18,10 @@ export type Targets = {
[target: Target]: string,
};
export type TargetsTuple = {|
[target: Target]: string,
|};
export type Browsers = string | Array<string>;
export type InputTargets = {

View File

@ -1,13 +1,14 @@
// @flow
import invariant from "invariant";
import semver from "semver";
import { OptionValidator } from "@babel/helper-validator-option";
import { name as packageName } from "../package.json";
import { unreleasedLabels } from "./targets";
import type { Target, Targets } from "./types";
const versionRegExp = /^(\d+|\d+.\d+)$/;
const v = new OptionValidator(packageName);
export function semverMin(first: ?string, second: string): string {
return first && semver.lt(first, second) ? first : second;
}
@ -19,7 +20,7 @@ export function semverify(version: number | string): string {
return version;
}
invariant(
v.invariant(
typeof version === "number" ||
(typeof version === "string" && versionRegExp.test(version)),
`'${version}' is not a valid version`,

View File

@ -57,3 +57,5 @@ Object {
"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-module-imports": "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-class-properties": "workspace:^7.10.4",
"@babel/plugin-proposal-dynamic-import": "workspace:^7.10.4",
@ -80,8 +81,6 @@
"@babel/types": "workspace:^7.11.5",
"browserslist": "^4.12.0",
"core-js-compat": "^3.6.2",
"invariant": "^2.2.2",
"levenary": "^1.1.1",
"semver": "^5.5.0"
},
"peerDependencies": {

View File

@ -1,13 +1,13 @@
// @flow
import corejs3Polyfills from "core-js-compat/data";
import findSuggestion from "levenary";
import invariant from "invariant";
import { coerce, SemVer } from "semver";
import corejs2Polyfills from "@babel/compat-data/corejs2-built-ins";
import { plugins as pluginsList } from "./plugins-compat-data";
import moduleTransformations from "./module-transformations";
import { TopLevelOptions, ModulesOption, UseBuiltInsOption } from "./options";
import { OptionValidator } from "@babel/helper-validator-option";
import { defaultWebIncludes } from "./polyfills/corejs2/get-platform-specific-default";
import { name as packageName } from "../package.json";
import type {
BuiltInsOption,
@ -18,18 +18,7 @@ import type {
PluginListOption,
} from "./types";
const validateTopLevelOptions = (options: Options) => {
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 v = new OptionValidator(packageName);
const allPluginsList = Object.keys(pluginsList);
@ -89,9 +78,9 @@ const expandIncludesAndExcludes = (
(p, i) => selectedPlugins[i].length === 0,
);
invariant(
v.invariant(
invalidRegExpList.length === 0,
`Invalid Option: The plugins/built-ins '${invalidRegExpList.join(
`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`,
@ -109,9 +98,9 @@ export const checkDuplicateIncludeExcludes = (
) => {
const duplicates = include.filter(opt => exclude.indexOf(opt) >= 0);
invariant(
v.invariant(
duplicates.length === 0,
`Invalid Option: The plugins/built-ins '${duplicates.join(
`The plugins/built-ins '${duplicates.join(
", ",
)}' were found in both the "include" and
"exclude" options.`,
@ -126,61 +115,12 @@ const normalizeTargets = 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 = (
modulesOpt: ModuleOption = ModulesOption.auto,
) => {
invariant(
v.invariant(
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` +
` - a specific module type: 'commonjs', 'amd', 'umd', 'systemjs'` +
` - 'auto' (default) which will automatically select 'false' if the current\n` +
@ -193,10 +133,10 @@ export const validateModulesOption = (
export const validateUseBuiltInsOption = (
builtInsOpt: BuiltInsOption = false,
) => {
invariant(
v.invariant(
UseBuiltInsOption[builtInsOpt.toString()] ||
builtInsOpt === UseBuiltInsOption.false,
`Invalid Option: The 'useBuiltIns' option must be either
`The 'useBuiltIns' option must be either
'false' (default) to indicate no polyfill,
'"entry"' to indicate replacing the entry polyfill, or
'"usage"' to import only used polyfills per file`,
@ -258,7 +198,7 @@ export function normalizeCoreJSOption(
}
export default function normalizeOptions(opts: Options) {
validateTopLevelOptions(opts);
v.validateTopLevelOptions(opts, TopLevelOptions);
const useBuiltIns = validateUseBuiltInsOption(opts.useBuiltIns);
@ -278,38 +218,42 @@ export default function normalizeOptions(opts: Options) {
checkDuplicateIncludeExcludes(include, exclude);
const shippedProposals = validateBoolOption(
TopLevelOptions.shippedProposals,
opts.shippedProposals,
false,
);
return {
bugfixes: validateBoolOption(
bugfixes: v.validateBooleanOption(
TopLevelOptions.bugfixes,
opts.bugfixes,
false,
),
configPath: validateConfigPathOption(opts.configPath),
configPath: v.validateStringOption(
TopLevelOptions.configPath,
opts.configPath,
process.cwd(),
),
corejs,
debug: validateBoolOption(TopLevelOptions.debug, opts.debug, false),
debug: v.validateBooleanOption(TopLevelOptions.debug, opts.debug, false),
include,
exclude,
forceAllTransforms: validateBoolOption(
forceAllTransforms: v.validateBooleanOption(
TopLevelOptions.forceAllTransforms,
opts.forceAllTransforms,
false,
),
ignoreBrowserslistConfig: validateIgnoreBrowserslistConfig(
ignoreBrowserslistConfig: v.validateBooleanOption(
TopLevelOptions.ignoreBrowserslistConfig,
opts.ignoreBrowserslistConfig,
false,
),
loose: validateBoolOption(TopLevelOptions.loose, opts.loose, false),
loose: v.validateBooleanOption(TopLevelOptions.loose, opts.loose, false),
modules: validateModulesOption(opts.modules),
shippedProposals,
spec: validateBoolOption(TopLevelOptions.spec, opts.spec, false),
shippedProposals: v.validateBooleanOption(
TopLevelOptions.shippedProposals,
opts.shippedProposals,
false,
),
spec: v.validateBooleanOption(TopLevelOptions.spec, opts.spec, false),
targets: normalizeTargets(opts.targets),
useBuiltIns: useBuiltIns,
browserslistEnv: validateStringOption(
browserslistEnv: v.validateStringOption(
TopLevelOptions.browserslistEnv,
opts.browserslistEnv,
),

View File

@ -4,8 +4,6 @@ const normalizeOptions = require("../lib/normalize-options.js");
const {
checkDuplicateIncludeExcludes,
validateBoolOption,
validateStringOption,
validateModulesOption,
validateUseBuiltInsOption,
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 () {
it("should throw if duplicate names in both", function () {
expect(() => {

View File

@ -358,9 +358,8 @@ __metadata:
"@babel/compat-data": "workspace:^7.10.4"
"@babel/core": "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
invariant: ^2.2.4
levenary: ^1.1.1
semver: ^5.5.0
peerDependencies:
"@babel/core": ^7.0.0
@ -794,6 +793,12 @@ __metadata:
languageName: unknown
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":
version: 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-plugin-test-runner": "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-class-properties": "workspace:^7.10.4"
"@babel/plugin-proposal-dynamic-import": "workspace:^7.10.4"
@ -3018,8 +3024,6 @@ __metadata:
"@babel/types": "workspace:^7.11.5"
browserslist: ^4.12.0
core-js-compat: ^3.6.2
invariant: ^2.2.2
levenary: ^1.1.1
semver: ^5.5.0
peerDependencies:
"@babel/core": ^7.0.0-0