Skip to content

Commit f0cfd50

Browse files
feat(jest-validate): Allow deprecation warnings for unknown options (#14499)
1 parent 176b229 commit f0cfd50

File tree

6 files changed

+131
-27
lines changed

6 files changed

+131
-27
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
### Features
44

55
- `[create-jest]` Add `npm init` / `yarn create` initialiser for Jest projects ([#14465](https://github.com/jestjs/jest/pull/14453))
6+
- `[jest-validate]` Allow deprecation warnings for unknown options ([#14499](https://github.com/jestjs/jest/pull/14499))
67

78
### Fixes
89

packages/jest-validate/README.md

+20
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,23 @@ Custom Deprecation:
194194

195195
Documentation: http://custom-docs.com
196196
```
197+
198+
## Example validating CLI arguments
199+
200+
```js
201+
import {validate} from 'jest-validate';
202+
203+
validateCLIOptions(argv, {...allowedOptions, deprecatedOptions});
204+
```
205+
206+
If `argv` contains a deprecated option that is not specifid in `allowedOptions`, `validateCLIOptions` will throw an error with the message specified in the `deprecatedOptions` config:
207+
208+
```bash
209+
● collectCoverageOnlyFrom:
210+
211+
Option "collectCoverageOnlyFrom" was replaced by "collectCoverageFrom"
212+
213+
CLI Options Documentation: https://jestjs.io/docs/en/cli.html
214+
```
215+
216+
If the deprecation option is still listed in the `allowedOptions` config, then `validateCLIOptions` will print the warning wihout throwing an error.

packages/jest-validate/src/__tests__/__snapshots__/validateCLIOptions.test.ts.snap

+20
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ exports[`fails for unknown option 1`] = `
3131
<red></color>"
3232
`;
3333
34+
exports[`handles deprecated CLI options print warning for deprecated options that are listed in config 1`] = `
35+
"<yellow><bold>foo</intensity>:</color>
36+
<yellow></color>
37+
<yellow>Deprecation message</color>
38+
<yellow></color>
39+
<yellow> <bold>CLI Options Documentation:</intensity></color>
40+
<yellow> https://jestjs.io/docs/cli</color>
41+
<yellow></color>"
42+
`;
43+
44+
exports[`handles deprecated CLI options throw an error for deprecated options that are not listed in config 1`] = `
45+
"<red><bold>foo</intensity>:</color>
46+
<red></color>
47+
<red>Deprecation message</color>
48+
<red></color>
49+
<red> <bold>CLI Options Documentation:</intensity></color>
50+
<red> https://jestjs.io/docs/cli</color>
51+
<red></color>"
52+
`;
53+
3454
exports[`shows suggestion when unrecognized cli param length > 1 1`] = `
3555
"<red><bold><bold>●</intensity><bold> Unrecognized CLI Parameter</intensity>:</color>
3656
<red></color>

packages/jest-validate/src/__tests__/validateCLIOptions.test.ts

+47
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*
77
*/
88

9+
import type {DeprecatedOptions} from '../types';
910
import validateCLIOptions from '../validateCLIOptions';
1011

1112
test('validates yargs special options', () => {
@@ -59,3 +60,49 @@ test('shows suggestion when unrecognized cli param length > 1', () => {
5960

6061
expect(() => validateCLIOptions(argv)).toThrowErrorMatchingSnapshot();
6162
});
63+
64+
describe('handles deprecated CLI options', () => {
65+
beforeEach(() => {
66+
jest.spyOn(console, 'warn');
67+
});
68+
69+
afterEach(() => {
70+
jest.mocked(console.warn).mockRestore();
71+
});
72+
73+
test('print warning for deprecated options that are listed in config', () => {
74+
const optionName = 'foo';
75+
const argv = {
76+
$0: 'foo',
77+
_: ['bar'],
78+
[optionName]: true,
79+
};
80+
81+
validateCLIOptions(argv, {
82+
deprecationEntries: {
83+
[optionName]: () => 'Deprecation message',
84+
} as DeprecatedOptions,
85+
[optionName]: {},
86+
});
87+
88+
expect(jest.mocked(console.warn).mock.calls[0][0]).toMatchSnapshot();
89+
});
90+
91+
test('throw an error for deprecated options that are not listed in config', () => {
92+
const optionName = 'foo';
93+
94+
const argv = {
95+
$0: 'foo',
96+
_: ['bar'],
97+
[optionName]: true,
98+
};
99+
100+
expect(() =>
101+
validateCLIOptions(argv, {
102+
deprecationEntries: {
103+
[optionName]: () => 'Deprecation message',
104+
} as DeprecatedOptions,
105+
}),
106+
).toThrowErrorMatchingSnapshot();
107+
});
108+
});

packages/jest-validate/src/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export type DeprecatedOptionFunc = (arg: Record<string, unknown>) => string;
1515

1616
export type DeprecatedOptions = Record<string, DeprecatedOptionFunc>;
1717

18+
export type DeprecationItem = {fatal: boolean; name: string};
19+
1820
export type ValidationOptions = {
1921
comment?: string;
2022
condition?: (option: unknown, validOption: unknown) => boolean;

packages/jest-validate/src/validateCLIOptions.ts

+41-27
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,17 @@ import camelcase = require('camelcase');
99
import chalk = require('chalk');
1010
import type {Options} from 'yargs';
1111
import type {Config} from '@jest/types';
12-
import defaultConfig from './defaultConfig';
13-
import {deprecationWarning} from './deprecated';
14-
import type {DeprecatedOptionFunc, DeprecatedOptions} from './types';
15-
import {ValidationError, createDidYouMeanMessage, format} from './utils';
12+
import type {
13+
DeprecatedOptionFunc,
14+
DeprecatedOptions,
15+
DeprecationItem,
16+
} from './types';
17+
import {
18+
ValidationError,
19+
createDidYouMeanMessage,
20+
format,
21+
logValidationWarning,
22+
} from './utils';
1623

1724
const BULLET: string = chalk.bold('\u25cf');
1825
export const DOCUMENTATION_NOTE = ` ${chalk.bold('CLI Options Documentation:')}
@@ -48,16 +55,21 @@ const createCLIValidationError = (
4855
return new ValidationError(title, message, comment);
4956
};
5057

51-
const logDeprecatedOptions = (
52-
deprecatedOptions: Array<string>,
58+
const validateDeprecatedOptions = (
59+
deprecatedOptions: Array<DeprecationItem>,
5360
deprecationEntries: DeprecatedOptions,
5461
argv: Config.Argv,
5562
) => {
5663
deprecatedOptions.forEach(opt => {
57-
deprecationWarning(argv, opt, deprecationEntries, {
58-
...defaultConfig,
59-
comment: DOCUMENTATION_NOTE,
60-
});
64+
const name = opt.name;
65+
const message = deprecationEntries[name](argv);
66+
const comment = DOCUMENTATION_NOTE;
67+
68+
if (opt.fatal) {
69+
throw new ValidationError(name, message, comment);
70+
} else {
71+
logValidationWarning(name, message, comment);
72+
}
6173
});
6274
};
6375

@@ -69,29 +81,19 @@ export default function validateCLIOptions(
6981
rawArgv: Array<string> = [],
7082
): boolean {
7183
const yargsSpecialOptions = ['$0', '_', 'help', 'h'];
72-
const deprecationEntries = options.deprecationEntries ?? {};
84+
7385
const allowedOptions = Object.keys(options).reduce(
7486
(acc, option) =>
7587
acc.add(option).add((options[option].alias as string) || option),
7688
new Set(yargsSpecialOptions),
7789
);
78-
const unrecognizedOptions = Object.keys(argv).filter(
79-
arg =>
80-
!allowedOptions.has(camelcase(arg, {locale: 'en-US'})) &&
81-
!allowedOptions.has(arg) &&
82-
(!rawArgv.length || rawArgv.includes(arg)),
83-
[],
84-
);
85-
86-
if (unrecognizedOptions.length) {
87-
throw createCLIValidationError(unrecognizedOptions, allowedOptions);
88-
}
8990

91+
const deprecationEntries = options.deprecationEntries ?? {};
9092
const CLIDeprecations = Object.keys(deprecationEntries).reduce<
9193
Record<string, DeprecatedOptionFunc>
9294
>((acc, entry) => {
95+
acc[entry] = deprecationEntries[entry];
9396
if (options[entry]) {
94-
acc[entry] = deprecationEntries[entry];
9597
const alias = options[entry].alias as string;
9698
if (alias) {
9799
acc[alias] = deprecationEntries[entry];
@@ -100,12 +102,24 @@ export default function validateCLIOptions(
100102
return acc;
101103
}, {});
102104
const deprecations = new Set(Object.keys(CLIDeprecations));
103-
const deprecatedOptions = Object.keys(argv).filter(
104-
arg => deprecations.has(arg) && argv[arg] != null,
105-
);
105+
const deprecatedOptions = Object.keys(argv)
106+
.filter(arg => deprecations.has(arg) && argv[arg] != null)
107+
.map(arg => ({fatal: !allowedOptions.has(arg), name: arg}));
106108

107109
if (deprecatedOptions.length) {
108-
logDeprecatedOptions(deprecatedOptions, CLIDeprecations, argv);
110+
validateDeprecatedOptions(deprecatedOptions, CLIDeprecations, argv);
111+
}
112+
113+
const unrecognizedOptions = Object.keys(argv).filter(
114+
arg =>
115+
!allowedOptions.has(camelcase(arg, {locale: 'en-US'})) &&
116+
!allowedOptions.has(arg) &&
117+
(!rawArgv.length || rawArgv.includes(arg)),
118+
[],
119+
);
120+
121+
if (unrecognizedOptions.length) {
122+
throw createCLIValidationError(unrecognizedOptions, allowedOptions);
109123
}
110124

111125
return true;

0 commit comments

Comments
 (0)