Skip to content

Commit

Permalink
feat(jest-validate): Allow deprecation warnings for unknown options (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
dj-stormtrooper authored Sep 11, 2023
1 parent 176b229 commit f0cfd50
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Features

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

### Fixes

Expand Down
20 changes: 20 additions & 0 deletions packages/jest-validate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,23 @@ Custom Deprecation:

Documentation: http://custom-docs.com
```

## Example validating CLI arguments

```js
import {validate} from 'jest-validate';

validateCLIOptions(argv, {...allowedOptions, deprecatedOptions});
```

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:

```bash
● collectCoverageOnlyFrom:

Option "collectCoverageOnlyFrom" was replaced by "collectCoverageFrom"

CLI Options Documentation: https://jestjs.io/docs/en/cli.html
```

If the deprecation option is still listed in the `allowedOptions` config, then `validateCLIOptions` will print the warning wihout throwing an error.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ exports[`fails for unknown option 1`] = `
<red></color>"
`;
exports[`handles deprecated CLI options print warning for deprecated options that are listed in config 1`] = `
"<yellow><bold>foo</intensity>:</color>
<yellow></color>
<yellow>Deprecation message</color>
<yellow></color>
<yellow> <bold>CLI Options Documentation:</intensity></color>
<yellow> https://jestjs.io/docs/cli</color>
<yellow></color>"
`;
exports[`handles deprecated CLI options throw an error for deprecated options that are not listed in config 1`] = `
"<red><bold>foo</intensity>:</color>
<red></color>
<red>Deprecation message</color>
<red></color>
<red> <bold>CLI Options Documentation:</intensity></color>
<red> https://jestjs.io/docs/cli</color>
<red></color>"
`;
exports[`shows suggestion when unrecognized cli param length > 1 1`] = `
"<red><bold><bold>●</intensity><bold> Unrecognized CLI Parameter</intensity>:</color>
<red></color>
Expand Down
47 changes: 47 additions & 0 deletions packages/jest-validate/src/__tests__/validateCLIOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*
*/

import type {DeprecatedOptions} from '../types';
import validateCLIOptions from '../validateCLIOptions';

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

expect(() => validateCLIOptions(argv)).toThrowErrorMatchingSnapshot();
});

describe('handles deprecated CLI options', () => {
beforeEach(() => {
jest.spyOn(console, 'warn');
});

afterEach(() => {
jest.mocked(console.warn).mockRestore();
});

test('print warning for deprecated options that are listed in config', () => {
const optionName = 'foo';
const argv = {
$0: 'foo',
_: ['bar'],
[optionName]: true,
};

validateCLIOptions(argv, {
deprecationEntries: {
[optionName]: () => 'Deprecation message',
} as DeprecatedOptions,
[optionName]: {},
});

expect(jest.mocked(console.warn).mock.calls[0][0]).toMatchSnapshot();
});

test('throw an error for deprecated options that are not listed in config', () => {
const optionName = 'foo';

const argv = {
$0: 'foo',
_: ['bar'],
[optionName]: true,
};

expect(() =>
validateCLIOptions(argv, {
deprecationEntries: {
[optionName]: () => 'Deprecation message',
} as DeprecatedOptions,
}),
).toThrowErrorMatchingSnapshot();
});
});
2 changes: 2 additions & 0 deletions packages/jest-validate/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export type DeprecatedOptionFunc = (arg: Record<string, unknown>) => string;

export type DeprecatedOptions = Record<string, DeprecatedOptionFunc>;

export type DeprecationItem = {fatal: boolean; name: string};

export type ValidationOptions = {
comment?: string;
condition?: (option: unknown, validOption: unknown) => boolean;
Expand Down
68 changes: 41 additions & 27 deletions packages/jest-validate/src/validateCLIOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ import camelcase = require('camelcase');
import chalk = require('chalk');
import type {Options} from 'yargs';
import type {Config} from '@jest/types';
import defaultConfig from './defaultConfig';
import {deprecationWarning} from './deprecated';
import type {DeprecatedOptionFunc, DeprecatedOptions} from './types';
import {ValidationError, createDidYouMeanMessage, format} from './utils';
import type {
DeprecatedOptionFunc,
DeprecatedOptions,
DeprecationItem,
} from './types';
import {
ValidationError,
createDidYouMeanMessage,
format,
logValidationWarning,
} from './utils';

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

const logDeprecatedOptions = (
deprecatedOptions: Array<string>,
const validateDeprecatedOptions = (
deprecatedOptions: Array<DeprecationItem>,
deprecationEntries: DeprecatedOptions,
argv: Config.Argv,
) => {
deprecatedOptions.forEach(opt => {
deprecationWarning(argv, opt, deprecationEntries, {
...defaultConfig,
comment: DOCUMENTATION_NOTE,
});
const name = opt.name;
const message = deprecationEntries[name](argv);
const comment = DOCUMENTATION_NOTE;

if (opt.fatal) {
throw new ValidationError(name, message, comment);
} else {
logValidationWarning(name, message, comment);
}
});
};

Expand All @@ -69,29 +81,19 @@ export default function validateCLIOptions(
rawArgv: Array<string> = [],
): boolean {
const yargsSpecialOptions = ['$0', '_', 'help', 'h'];
const deprecationEntries = options.deprecationEntries ?? {};

const allowedOptions = Object.keys(options).reduce(
(acc, option) =>
acc.add(option).add((options[option].alias as string) || option),
new Set(yargsSpecialOptions),
);
const unrecognizedOptions = Object.keys(argv).filter(
arg =>
!allowedOptions.has(camelcase(arg, {locale: 'en-US'})) &&
!allowedOptions.has(arg) &&
(!rawArgv.length || rawArgv.includes(arg)),
[],
);

if (unrecognizedOptions.length) {
throw createCLIValidationError(unrecognizedOptions, allowedOptions);
}

const deprecationEntries = options.deprecationEntries ?? {};
const CLIDeprecations = Object.keys(deprecationEntries).reduce<
Record<string, DeprecatedOptionFunc>
>((acc, entry) => {
acc[entry] = deprecationEntries[entry];
if (options[entry]) {
acc[entry] = deprecationEntries[entry];
const alias = options[entry].alias as string;
if (alias) {
acc[alias] = deprecationEntries[entry];
Expand All @@ -100,12 +102,24 @@ export default function validateCLIOptions(
return acc;
}, {});
const deprecations = new Set(Object.keys(CLIDeprecations));
const deprecatedOptions = Object.keys(argv).filter(
arg => deprecations.has(arg) && argv[arg] != null,
);
const deprecatedOptions = Object.keys(argv)
.filter(arg => deprecations.has(arg) && argv[arg] != null)
.map(arg => ({fatal: !allowedOptions.has(arg), name: arg}));

if (deprecatedOptions.length) {
logDeprecatedOptions(deprecatedOptions, CLIDeprecations, argv);
validateDeprecatedOptions(deprecatedOptions, CLIDeprecations, argv);
}

const unrecognizedOptions = Object.keys(argv).filter(
arg =>
!allowedOptions.has(camelcase(arg, {locale: 'en-US'})) &&
!allowedOptions.has(arg) &&
(!rawArgv.length || rawArgv.includes(arg)),
[],
);

if (unrecognizedOptions.length) {
throw createCLIValidationError(unrecognizedOptions, allowedOptions);
}

return true;
Expand Down

0 comments on commit f0cfd50

Please sign in to comment.