From 55d0504cadc945b770d7c3b6d3cab425c9b76d0f Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 4 May 2020 19:49:35 +1200 Subject: [PATCH] feat: create `no-deprecated-functions` (#560) --- README.md | 2 + docs/rules/no-deprecated-functions.md | 46 ++++++++++++ .../__snapshots__/rules.test.ts.snap | 1 + src/__tests__/rules.test.ts | 2 +- .../__tests__/no-deprecated-functions.ts | 49 +++++++++++++ src/rules/no-deprecated-functions.ts | 72 +++++++++++++++++++ 6 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 docs/rules/no-deprecated-functions.md create mode 100644 src/rules/__tests__/no-deprecated-functions.ts create mode 100644 src/rules/no-deprecated-functions.ts diff --git a/README.md b/README.md index 3523df755..450638c9b 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ installations requiring long-term consistency. | [lowercase-name][] | Disallow capitalized test names | | ![fixable-green][] | | [no-alias-methods][] | Disallow alias methods | ![style][] | ![fixable-green][] | | [no-commented-out-tests][] | Disallow commented out tests | ![recommended][] | | +| [no-deprecated-functions][] | Disallow use of deprecated functions | | ![fixable-green][] | | [no-disabled-tests][] | Disallow disabled tests | ![recommended][] | | | [no-duplicate-hooks][] | Disallow duplicate hooks within a `describe` block | | | | [no-expect-resolves][] | Disallow using `expect().resolves` | | | @@ -170,6 +171,7 @@ https://github.com/dangreenisrael/eslint-plugin-jest-formatting [lowercase-name]: docs/rules/lowercase-name.md [no-alias-methods]: docs/rules/no-alias-methods.md [no-commented-out-tests]: docs/rules/no-commented-out-tests.md +[no-deprecated-functions]: docs/rules/no-deprecated-functions.md [no-disabled-tests]: docs/rules/no-disabled-tests.md [no-duplicate-hooks]: docs/rules/no-duplicate-hooks.md [no-expect-resolves]: docs/rules/no-expect-resolves.md diff --git a/docs/rules/no-deprecated-functions.md b/docs/rules/no-deprecated-functions.md new file mode 100644 index 000000000..18a87affe --- /dev/null +++ b/docs/rules/no-deprecated-functions.md @@ -0,0 +1,46 @@ +# Disallow use of deprecated functions (no-deprecated-functions) + +Over the years Jest has accrued some debt in the form of functions that have +either been renamed for clarity, or replaced with more powerful APIs. + +While typically these deprecated functions are kept in the codebase for a number +of majors, eventually they are removed completely. + +## Rule details + +This rule warns about calls to deprecated functions, and provides details on +what to replace them with. + +This rule can also autofix a number of these deprecations for you. + +### `require.requireActual` & `require.requireMock` + +These functions were replaced in Jest 21 and removed in Jest 26. + +Originally, the `requireActual` & `requireMock` the `requireActual`& +`requireMock` functions were placed onto the `require` function. + +These functions were later moved onto the `jest` object in order to be easier +for type checkers to handle, and their use via `require` deprecated. Finally, +the release of Jest 26 saw them removed from the `require` function all +together. + +### `jest.addMatchers` + +This function was replaced with `expect.extend` in Jest 17, and is scheduled for +removal in Jest 27. + +### `jest.resetModuleRegistry` + +This function was renamed to `resetModules` in Jest 15, and is scheduled for +removal in Jest 27. + +### `jest.runTimersToTime` + +This function was renamed to `advanceTimersByTime` in Jest 22, and is scheduled +for removal in Jest 27. + +### `jest.genMockFromModule` + +This function was renamed to `createMockFromModule` in Jest 26, and is scheduled +for removal in a future version of Jest. diff --git a/src/__tests__/__snapshots__/rules.test.ts.snap b/src/__tests__/__snapshots__/rules.test.ts.snap index 831fe0269..ec79e907d 100644 --- a/src/__tests__/__snapshots__/rules.test.ts.snap +++ b/src/__tests__/__snapshots__/rules.test.ts.snap @@ -15,6 +15,7 @@ Object { "jest/lowercase-name": "error", "jest/no-alias-methods": "error", "jest/no-commented-out-tests": "error", + "jest/no-deprecated-functions": "error", "jest/no-disabled-tests": "error", "jest/no-duplicate-hooks": "error", "jest/no-expect-resolves": "error", diff --git a/src/__tests__/rules.test.ts b/src/__tests__/rules.test.ts index b5ebf1e03..f90174218 100644 --- a/src/__tests__/rules.test.ts +++ b/src/__tests__/rules.test.ts @@ -3,7 +3,7 @@ import { resolve } from 'path'; import plugin from '../'; const ruleNames = Object.keys(plugin.rules); -const numberOfRules = 40; +const numberOfRules = 41; describe('rules', () => { it('should have a corresponding doc for each rule', () => { diff --git a/src/rules/__tests__/no-deprecated-functions.ts b/src/rules/__tests__/no-deprecated-functions.ts new file mode 100644 index 000000000..40805d2ca --- /dev/null +++ b/src/rules/__tests__/no-deprecated-functions.ts @@ -0,0 +1,49 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import rule from '../no-deprecated-functions'; + +const ruleTester = new TSESLint.RuleTester(); + +[ + ['require.requireMock', 'jest.requireMock'], + ['require.requireActual', 'jest.requireActual'], + ['jest.addMatchers', 'expect.extend'], + ['jest.resetModuleRegistry', 'jest.resetModules'], + ['jest.runTimersToTime', 'jest.advanceTimersByTime'], + ['jest.genMockFromModule', 'jest.createMockFromModule'], +].forEach(([deprecation, replacement]) => { + const [deprecatedName, deprecatedFunc] = deprecation.split('.'); + const [replacementName, replacementFunc] = replacement.split('.'); + + ruleTester.run(`${deprecation} -> ${replacement}`, rule, { + valid: [ + 'jest', + 'require("fs")', + `${replacement}()`, + replacement, + `${replacementName}['${replacementFunc}']()`, + `${replacementName}['${replacementFunc}']`, + ], + invalid: [ + { + code: `${deprecation}()`, + output: `${replacement}()`, + errors: [ + { + messageId: 'deprecatedFunction', + data: { deprecation, replacement }, + }, + ], + }, + { + code: `${deprecatedName}['${deprecatedFunc}']()`, + output: `${replacementName}['${replacementFunc}']()`, + errors: [ + { + messageId: 'deprecatedFunction', + data: { deprecation, replacement }, + }, + ], + }, + ], + }); +}); diff --git a/src/rules/no-deprecated-functions.ts b/src/rules/no-deprecated-functions.ts new file mode 100644 index 000000000..60c0ad382 --- /dev/null +++ b/src/rules/no-deprecated-functions.ts @@ -0,0 +1,72 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import { createRule, getNodeName } from './utils'; + +export default createRule({ + name: __filename, + meta: { + docs: { + category: 'Best Practices', + description: 'Disallow use of deprecated functions', + recommended: false, + }, + messages: { + deprecatedFunction: + '`{{ deprecation }}` has been deprecated in favor of `{{ replacement }}`', + }, + type: 'suggestion', + schema: [], + fixable: 'code', + }, + defaultOptions: [], + create(context) { + const deprecations: Record = { + 'require.requireMock': 'jest.requireMock', + 'require.requireActual': 'jest.requireActual', + 'jest.addMatchers': 'expect.extend', + 'jest.resetModuleRegistry': 'jest.resetModules', + 'jest.runTimersToTime': 'jest.advanceTimersByTime', + 'jest.genMockFromModule': 'jest.createMockFromModule', + }; + + return { + CallExpression(node: TSESTree.CallExpression) { + if (node.callee.type !== AST_NODE_TYPES.MemberExpression) { + return; + } + + const deprecation = getNodeName(node); + + if (!deprecation || !(deprecation in deprecations)) { + return; + } + + const replacement = deprecations[deprecation]; + const { callee } = node; + + context.report({ + messageId: 'deprecatedFunction', + data: { + deprecation, + replacement, + }, + node, + fix(fixer) { + let [name, func] = replacement.split('.'); + + if (callee.property.type === AST_NODE_TYPES.Literal) { + func = `'${func}'`; + } + + return [ + fixer.replaceText(callee.object, name), + fixer.replaceText(callee.property, func), + ]; + }, + }); + }, + }; + }, +});