From 8f92df8aaa23776cb90bdb0f1dd003193bf0cfa1 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Fri, 8 Sep 2023 00:46:36 +0400 Subject: [PATCH 1/8] fix(jest-validate): Allow deprecation warnings for unknown options --- .../jest-validate/src/validateCLIOptions.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/jest-validate/src/validateCLIOptions.ts b/packages/jest-validate/src/validateCLIOptions.ts index 5931bcffc3d6..db9611f6e773 100644 --- a/packages/jest-validate/src/validateCLIOptions.ts +++ b/packages/jest-validate/src/validateCLIOptions.ts @@ -69,29 +69,13 @@ export default function validateCLIOptions( rawArgv: Array = [], ): 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 >((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]; @@ -108,5 +92,22 @@ export default function validateCLIOptions( logDeprecatedOptions(deprecatedOptions, CLIDeprecations, argv); } + 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); + } + return true; } From f738bc5d118699dd01b5b374e15472f53a6a4109 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Fri, 8 Sep 2023 00:57:40 +0400 Subject: [PATCH 2/8] docs: Changelog update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce2bc5c5d9c..0c24bfa49b71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - `[jest-snapshot]` Allow for strings as well as template literals in inline snapshots ([#14465](https://github.com/jestjs/jest/pull/14465)) - `[@jest/test-sequencer]` Calculate test runtime if `perStats.duration` is missing ([#14473](https://github.com/jestjs/jest/pull/14473)) +- `[jest-validate]` Allow deprecation warnings for unknown options ([#14499](https://github.com/jestjs/jest/pull/14499)) ### Performance From 5466c6ca3e5a9d7a5fd17844dd2be236c3e61d77 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Sun, 10 Sep 2023 23:20:22 +0400 Subject: [PATCH 3/8] feat: Add an ability to pass fatal errors to deprecation logger --- packages/jest-validate/src/types.ts | 2 + .../jest-validate/src/validateCLIOptions.ts | 40 ++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/packages/jest-validate/src/types.ts b/packages/jest-validate/src/types.ts index cefd03bfe916..189039d88bc8 100644 --- a/packages/jest-validate/src/types.ts +++ b/packages/jest-validate/src/types.ts @@ -15,6 +15,8 @@ export type DeprecatedOptionFunc = (arg: Record) => string; export type DeprecatedOptions = Record; +export type DeprecationItem = {fatal: boolean; name: string}; + export type ValidationOptions = { comment?: string; condition?: (option: unknown, validOption: unknown) => boolean; diff --git a/packages/jest-validate/src/validateCLIOptions.ts b/packages/jest-validate/src/validateCLIOptions.ts index db9611f6e773..ee72be0b647e 100644 --- a/packages/jest-validate/src/validateCLIOptions.ts +++ b/packages/jest-validate/src/validateCLIOptions.ts @@ -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:')} @@ -48,16 +55,21 @@ const createCLIValidationError = ( return new ValidationError(title, message, comment); }; -const logDeprecatedOptions = ( - deprecatedOptions: Array, +const validateDeprecatedOptions = ( + deprecatedOptions: Array, 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); + } }); }; @@ -84,12 +96,12 @@ 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: false, name: arg})); if (deprecatedOptions.length) { - logDeprecatedOptions(deprecatedOptions, CLIDeprecations, argv); + validateDeprecatedOptions(deprecatedOptions, CLIDeprecations, argv); } const allowedOptions = Object.keys(options).reduce( From 4eae5adac50950cc824fd0ec3ee4158e775029a0 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Sun, 10 Sep 2023 23:22:54 +0400 Subject: [PATCH 4/8] feat: Add fatal deprecation errors --- packages/jest-validate/src/validateCLIOptions.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/jest-validate/src/validateCLIOptions.ts b/packages/jest-validate/src/validateCLIOptions.ts index ee72be0b647e..0e227ce872e2 100644 --- a/packages/jest-validate/src/validateCLIOptions.ts +++ b/packages/jest-validate/src/validateCLIOptions.ts @@ -82,6 +82,12 @@ export default function validateCLIOptions( ): boolean { const yargsSpecialOptions = ['$0', '_', 'help', 'h']; + const allowedOptions = Object.keys(options).reduce( + (acc, option) => + acc.add(option).add((options[option].alias as string) || option), + new Set(yargsSpecialOptions), + ); + const deprecationEntries = options.deprecationEntries ?? {}; const CLIDeprecations = Object.keys(deprecationEntries).reduce< Record @@ -98,17 +104,12 @@ export default function validateCLIOptions( const deprecations = new Set(Object.keys(CLIDeprecations)); const deprecatedOptions = Object.keys(argv) .filter(arg => deprecations.has(arg) && argv[arg] != null) - .map(arg => ({fatal: false, name: arg})); + .map(arg => ({fatal: !allowedOptions.has(arg), name: arg})); if (deprecatedOptions.length) { validateDeprecatedOptions(deprecatedOptions, CLIDeprecations, argv); } - 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'})) && From 7de422d81e3ef76dc732eb2d86763f79c249d00c Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Sun, 10 Sep 2023 23:46:03 +0400 Subject: [PATCH 5/8] test: Deprecated CLI options --- .../validateCLIOptions.test.ts.snap | 20 ++++++++ .../src/__tests__/validateCLIOptions.test.ts | 47 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/packages/jest-validate/src/__tests__/__snapshots__/validateCLIOptions.test.ts.snap b/packages/jest-validate/src/__tests__/__snapshots__/validateCLIOptions.test.ts.snap index 9531b979a3bc..db5f2152ad08 100644 --- a/packages/jest-validate/src/__tests__/__snapshots__/validateCLIOptions.test.ts.snap +++ b/packages/jest-validate/src/__tests__/__snapshots__/validateCLIOptions.test.ts.snap @@ -31,6 +31,26 @@ exports[`fails for unknown option 1`] = ` " `; +exports[`handles deprecated CLI options print warning for deprecated options that are listed in config 1`] = ` +"foo: + +Deprecation message + + CLI Options Documentation: + https://jestjs.io/docs/cli +" +`; + +exports[`handles deprecated CLI options throw an error for deprecated options that are not listed in config 1`] = ` +"foo: + +Deprecation message + + CLI Options Documentation: + https://jestjs.io/docs/cli +" +`; + exports[`shows suggestion when unrecognized cli param length > 1 1`] = ` " Unrecognized CLI Parameter: diff --git a/packages/jest-validate/src/__tests__/validateCLIOptions.test.ts b/packages/jest-validate/src/__tests__/validateCLIOptions.test.ts index 51f92528a904..d4116b7ea8b6 100644 --- a/packages/jest-validate/src/__tests__/validateCLIOptions.test.ts +++ b/packages/jest-validate/src/__tests__/validateCLIOptions.test.ts @@ -6,6 +6,7 @@ * */ +import type {DeprecatedOptions} from '../types'; import validateCLIOptions from '../validateCLIOptions'; test('validates yargs special options', () => { @@ -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(); + }); +}); From 7a930b36b06f62879c7d56aa4d82600f9dd844c6 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Mon, 11 Sep 2023 19:20:03 +0400 Subject: [PATCH 6/8] docs: Changelog update --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aedf0be17d2d..6066b005efcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,13 @@ ### 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 - `[jest-resolver]` Replace unmatched capture groups in `moduleNameMapper` with empty string instead of `undefined` ([#14507](https://github.com/jestjs/jest/pull/14507)) - `[jest-snapshot]` Allow for strings as well as template literals in inline snapshots ([#14465](https://github.com/jestjs/jest/pull/14465)) - `[@jest/test-sequencer]` Calculate test runtime if `perStats.duration` is missing ([#14473](https://github.com/jestjs/jest/pull/14473)) -- `[jest-validate]` Allow deprecation warnings for unknown options ([#14499](https://github.com/jestjs/jest/pull/14499)) ### Performance From 521931948fc759d02d84684bd8dfbb74891f0dfc Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Mon, 11 Sep 2023 19:38:20 +0400 Subject: [PATCH 7/8] docs: Add example of validating CLI options --- packages/jest-validate/README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/jest-validate/README.md b/packages/jest-validate/README.md index 2406bd568ec2..ec8be97b37cb 100644 --- a/packages/jest-validate/README.md +++ b/packages/jest-validate/README.md @@ -194,3 +194,26 @@ 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. From 573aae3aa7d0492a69787baee1a8b7a85f43c4d4 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Mon, 11 Sep 2023 21:40:13 +0400 Subject: [PATCH 8/8] fix: Prettier for readme --- packages/jest-validate/README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/jest-validate/README.md b/packages/jest-validate/README.md index ec8be97b37cb..41b275ce415a 100644 --- a/packages/jest-validate/README.md +++ b/packages/jest-validate/README.md @@ -200,10 +200,7 @@ Custom Deprecation: ```js import {validate} from 'jest-validate'; -validateCLIOptions( - argv, - { ...allowedOptions, deprecatedOptions }, -); +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: