From 73a5d3653a9cadc0569dba425c1d05333123a475 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sun, 3 May 2020 22:15:11 +1200 Subject: [PATCH] feat: create `no-deprecated-functions` rule --- docs/rules/no-deprecated-functions.md | 43 +++++++++++ .../__snapshots__/rules.test.ts.snap | 1 + src/__tests__/rules.test.ts | 2 +- .../__tests__/no-deprecated-functions.ts | 48 +++++++++++++ src/rules/no-deprecated-functions.ts | 71 +++++++++++++++++++ 5 files changed, 164 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/docs/rules/no-deprecated-functions.md b/docs/rules/no-deprecated-functions.md new file mode 100644 index 000000000..6e1678b77 --- /dev/null +++ b/docs/rules/no-deprecated-functions.md @@ -0,0 +1,43 @@ +# Warns on usage 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 removed in Jest 26. + +Originally in the early days of jest, the `requireActual`& `requireMock` +functions were placed onto the `require` function. + +These functions were later moved onto the `jest` global, and their use via +`require` deprecated. Finally, the release of Jest 26 saw them removed from the +`require` function all together. + +The PR implementing the removal can be found +[here](https://github.com/facebook/jest/pull/9854). + +### `jest.addMatchers` + +This function has been replaced with `expect.extend`, and will ideally be +removed in Jest 27. + +### `jest.resetModuleRegistry` + +This function has been renamed to `resetModules`, and will ideally be removed in +Jest 27. + +### `jest.runTimersToTime` + +This function has been renamed to `advanceTimersByTime`, and will ideally be +removed in Jest 27. 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..d000669a7 --- /dev/null +++ b/src/rules/__tests__/no-deprecated-functions.ts @@ -0,0 +1,48 @@ +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'], +].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..f0d5c7921 --- /dev/null +++ b/src/rules/no-deprecated-functions.ts @@ -0,0 +1,71 @@ +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: 'Warns on usage 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', + }; + + 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), + ]; + }, + }); + }, + }; + }, +});