Skip to content

Commit f2da186

Browse files
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>
1 parent 0d32e3f commit f2da186

19 files changed

Lines changed: 375 additions & 197 deletions

File tree

babel.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ module.exports = function (api) {
106106
plugins: [
107107
// TODO: Use @babel/preset-flow when
108108
// https://github.com/babel/babel/issues/7233 is fixed
109-
"@babel/plugin-transform-flow-strip-types",
109+
[
110+
"@babel/plugin-transform-flow-strip-types",
111+
{ allowDeclareFields: true },
112+
],
110113
[
111114
"@babel/proposal-object-rest-spread",
112115
{ useBuiltIns: true, loose: true },

packages/babel-helper-compilation-targets/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@
2222
],
2323
"dependencies": {
2424
"@babel/compat-data": "workspace:^7.10.4",
25+
"@babel/helper-validator-option": "workspace:^7.11.4",
2526
"browserslist": "^4.12.0",
26-
"invariant": "^2.2.4",
27-
"levenary": "^1.1.1",
2827
"semver": "^5.5.0"
2928
},
3029
"peerDependencies": {

packages/babel-helper-compilation-targets/src/index.js

Lines changed: 49 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// @flow
22

33
import browserslist from "browserslist";
4-
import findSuggestion from "levenary";
5-
import invariant from "invariant";
4+
import { findSuggestion } from "@babel/helper-validator-option";
65
import browserModulesData from "@babel/compat-data/native-modules";
76

87
import {
@@ -11,9 +10,11 @@ import {
1110
isUnreleasedVersion,
1211
getLowestUnreleased,
1312
} from "./utils";
13+
import { OptionValidator } from "@babel/helper-validator-option";
1414
import { browserNameMap } from "./targets";
1515
import { TargetNames } from "./options";
16-
import type { Target, Targets, InputTargets, Browsers } from "./types";
16+
import { name as packageName } from "../package.json";
17+
import type { Targets, InputTargets, Browsers, TargetsTuple } from "./types";
1718

1819
export type { Targets, InputTargets };
1920

@@ -22,6 +23,7 @@ export { getInclusionReasons } from "./debug";
2223
export { default as filterItems, isRequired } from "./filter-items";
2324
export { unreleasedLabels } from "./targets";
2425

26+
const v = new OptionValidator(packageName);
2527
const browserslistDefaults = browserslist.defaults;
2628

2729
const validBrowserslistTargets = [
@@ -39,29 +41,28 @@ function objectToBrowserslist(object: Targets): Array<string> {
3941
}, []);
4042
}
4143

42-
function validateTargetNames(targets: InputTargets): Targets {
44+
function validateTargetNames(targets: Targets): TargetsTuple {
4345
const validTargets = Object.keys(TargetNames);
44-
for (const target in targets) {
45-
if (!TargetNames[target]) {
46+
for (const target of Object.keys(targets)) {
47+
if (!(target in TargetNames)) {
4648
throw new Error(
47-
`Invalid Option: '${target}' is not a valid target
48-
Maybe you meant to use '${findSuggestion(target, validTargets)}'?`,
49+
v.formatMessage(`'${target}' is not a valid target
50+
- Did you mean '${findSuggestion(target, validTargets)}'?`),
4951
);
5052
}
5153
}
5254

53-
// $FlowIgnore
54-
return targets;
55+
return (targets: any);
5556
}
5657

5758
export function isBrowsersQueryValid(browsers: Browsers | Targets): boolean {
5859
return typeof browsers === "string" || Array.isArray(browsers);
5960
}
6061

6162
function validateBrowsers(browsers: Browsers | void) {
62-
invariant(
63-
typeof browsers === "undefined" || isBrowsersQueryValid(browsers),
64-
`Invalid Option: '${String(browsers)}' is not a valid browserslist query`,
63+
v.invariant(
64+
browsers === undefined || isBrowsersQueryValid(browsers),
65+
`'${String(browsers)}' is not a valid browserslist query`,
6566
);
6667

6768
return browsers;
@@ -110,8 +111,10 @@ function getLowestVersions(browsers: Array<string>): Targets {
110111
}, {});
111112
}
112113

113-
function outputDecimalWarning(decimalTargets: Array<Object>): void {
114-
if (!decimalTargets?.length) {
114+
function outputDecimalWarning(
115+
decimalTargets: Array<{| target: string, value: string |}>,
116+
): void {
117+
if (!decimalTargets.length) {
115118
return;
116119
}
117120

@@ -133,7 +136,9 @@ function semverifyTarget(target, value) {
133136
return semverify(value);
134137
} catch (error) {
135138
throw new Error(
136-
`Invalid Option: '${value}' is not a valid value for 'targets.${target}'.`,
139+
v.formatMessage(
140+
`'${value}' is not a valid value for 'targets.${target}'.`,
141+
),
137142
);
138143
}
139144
}
@@ -156,16 +161,17 @@ const targetParserMap = {
156161
},
157162
};
158163

159-
type ParsedResult = {
160-
targets: Targets,
161-
decimalWarnings: Array<Object>,
162-
};
164+
function generateTargets(inputTargets: InputTargets): Targets {
165+
const input = { ...inputTargets };
166+
delete input.esmodules;
167+
delete input.browsers;
168+
return ((input: any): Targets);
169+
}
163170

164171
export default function getTargets(
165172
inputTargets: InputTargets = {},
166173
options: Object = {},
167174
): Targets {
168-
const targetOpts: Targets = {};
169175
let { browsers } = inputTargets;
170176

171177
// `esmodules` as a target indicates the specific set of browsers supporting ES Modules.
@@ -180,12 +186,8 @@ export default function getTargets(
180186
// Parse browsers target via browserslist
181187
const browsersquery = validateBrowsers(browsers);
182188

183-
// Remove esmodules after being consumed to fix `hasTargets` below
184-
const input = { ...inputTargets };
185-
delete input.esmodules;
186-
delete input.browsers;
187-
188-
let targets: Targets = validateTargetNames(input);
189+
const input = generateTargets(inputTargets);
190+
let targets: TargetsTuple = validateTargetNames(input);
189191

190192
const shouldParseBrowsers = !!browsersquery;
191193
const hasTargets = shouldParseBrowsers || Object.keys(targets).length > 0;
@@ -218,34 +220,28 @@ export default function getTargets(
218220
}
219221

220222
// Parse remaining targets
221-
const parsed = (Object.keys(targets): Array<Target>).sort().reduce(
222-
(results: ParsedResult, target: $Keys<Targets>): ParsedResult => {
223-
const value = targets[target];
224-
225-
// Warn when specifying minor/patch as a decimal
226-
if (typeof value === "number" && value % 1 !== 0) {
227-
results.decimalWarnings.push({ target, value });
228-
}
229-
230-
// Check if we have a target parser?
231-
// $FlowIgnore - Flow doesn't like that some targetParserMap[target] might be missing
232-
const parser = targetParserMap[target] ?? targetParserMap.__default;
233-
const [parsedTarget, parsedValue] = parser(target, value);
223+
const result: Targets = {};
224+
const decimalWarnings = [];
225+
for (const target of Object.keys(targets).sort()) {
226+
const value = targets[target];
227+
228+
// Warn when specifying minor/patch as a decimal
229+
if (typeof value === "number" && value % 1 !== 0) {
230+
decimalWarnings.push({ target, value });
231+
}
234232

235-
if (parsedValue) {
236-
// Merge (lowest wins)
237-
results.targets[parsedTarget] = parsedValue;
238-
}
233+
// Check if we have a target parser?
234+
// $FlowIgnore - Flow doesn't like that some targetParserMap[target] might be missing
235+
const parser = targetParserMap[target] ?? targetParserMap.__default;
236+
const [parsedTarget, parsedValue] = parser(target, value);
239237

240-
return results;
241-
},
242-
{
243-
targets: targetOpts,
244-
decimalWarnings: [],
245-
},
246-
);
238+
if (parsedValue) {
239+
// Merge (lowest wins)
240+
result[parsedTarget] = parsedValue;
241+
}
242+
}
247243

248-
outputDecimalWarning(parsed.decimalWarnings);
244+
outputDecimalWarning(decimalWarnings);
249245

250-
return parsed.targets;
246+
return result;
251247
}

packages/babel-helper-compilation-targets/src/types.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export type Targets = {
1818
[target: Target]: string,
1919
};
2020

21+
export type TargetsTuple = {|
22+
[target: Target]: string,
23+
|};
24+
2125
export type Browsers = string | Array<string>;
2226

2327
export type InputTargets = {

packages/babel-helper-compilation-targets/src/utils.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
// @flow
2-
3-
import invariant from "invariant";
42
import semver from "semver";
5-
3+
import { OptionValidator } from "@babel/helper-validator-option";
4+
import { name as packageName } from "../package.json";
65
import { unreleasedLabels } from "./targets";
76
import type { Target, Targets } from "./types";
87

98
const versionRegExp = /^(\d+|\d+.\d+)$/;
109

10+
const v = new OptionValidator(packageName);
11+
1112
export function semverMin(first: ?string, second: string): string {
1213
return first && semver.lt(first, second) ? first : second;
1314
}
@@ -19,7 +20,7 @@ export function semverify(version: number | string): string {
1920
return version;
2021
}
2122

22-
invariant(
23+
v.invariant(
2324
typeof version === "number" ||
2425
(typeof version === "string" && versionRegExp.test(version)),
2526
`'${version}' is not a valid version`,

packages/babel-helper-compilation-targets/test/__snapshots__/targets-parser.spec.js.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,5 @@ Object {
5757
"samsung": "8.2.0",
5858
}
5959
`;
60+
61+
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'."`;

packages/babel-helper-compilation-targets/test/targets-parser.spec.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,12 @@ describe("getTargets", () => {
260260
});
261261
});
262262
});
263+
264+
describe("exception", () => {
265+
it("throws when version is not a semver", () => {
266+
expect(() =>
267+
getTargets({ chrome: "seventy-two" }),
268+
).toThrowErrorMatchingSnapshot();
269+
});
270+
});
263271
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
src
2+
test
3+
*.log
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# @babel/helper-validator-option
2+
3+
> Validate plugin/preset options
4+
5+
See our website [@babel/helper-validator-option](https://babeljs.io/docs/en/next/babel-helper-validator-option.html) for more information.
6+
7+
## Install
8+
9+
Using npm:
10+
11+
```sh
12+
npm install --save-dev @babel/helper-validator-option
13+
```
14+
15+
or using yarn:
16+
17+
```sh
18+
yarn add @babel/helper-validator-option --dev
19+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "@babel/helper-validator-option",
3+
"version": "7.11.4",
4+
"description": "Validate plugin/preset options",
5+
"repository": {
6+
"type": "git",
7+
"url": "https://github.com/babel/babel.git",
8+
"directory": "packages/babel-helper-validator-option"
9+
},
10+
"license": "MIT",
11+
"publishConfig": {
12+
"access": "public"
13+
},
14+
"main": "./lib/index.js",
15+
"exports": "./lib/index.js"
16+
}

0 commit comments

Comments
 (0)