diff --git a/docs/rules/no-deprecated-functions.md b/docs/rules/no-deprecated-functions.md index c591e0aeb..1990d3fea 100644 --- a/docs/rules/no-deprecated-functions.md +++ b/docs/rules/no-deprecated-functions.md @@ -6,6 +6,37 @@ 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. +## Jest version + +This rule requires configuration to tell it which version of Jest is running. +You provide this through `eslint`'s +["shared settings"](https://eslint.org/docs/user-guide/configuring/configuration-files#adding-shared-settings). + +Example: + +```json +{ + "settings": { + "jest": { + "version": 27 + } + } +} +``` + +To avoid hard-coding a number, you can also fetch it from the installed version +of Jest if you use a JavaScript config file such as `.eslintrc.js`: + +```js +module.exports = { + settings: { + jest: { + version: require('jest/package.json').version, + }, + }, +}; +``` + ## Rule details This rule warns about calls to deprecated functions, and provides details on diff --git a/package.json b/package.json index 7f15995d6..4e8f30220 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,6 @@ "@babel/preset-typescript": "^7.3.3", "@commitlint/cli": "^13.1.0", "@commitlint/config-conventional": "^13.1.0", - "@schemastore/package": "^0.0.6", "@semantic-release/changelog": "^5.0.1", "@semantic-release/git": "^9.0.0", "@types/dedent": "^0.7.0", diff --git a/src/rules/__tests__/no-deprecated-functions.test.ts b/src/rules/__tests__/no-deprecated-functions.test.ts index 3c753def8..20b6ca8a1 100644 --- a/src/rules/__tests__/no-deprecated-functions.test.ts +++ b/src/rules/__tests__/no-deprecated-functions.test.ts @@ -1,82 +1,38 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { JSONSchemaForNPMPackageJsonFiles } from '@schemastore/package'; import { TSESLint } from '@typescript-eslint/experimental-utils'; -import rule, { - JestVersion, - _clearCachedJestVersion, -} from '../no-deprecated-functions'; +import rule, { JestVersion } from '../no-deprecated-functions'; const ruleTester = new TSESLint.RuleTester(); -// pin the original cwd so that we can restore it after each test -const projectDir = process.cwd(); - -afterEach(() => process.chdir(projectDir)); - -/** - * Makes a new temp directory, prefixed with `eslint-plugin-jest-` - * - * @return {Promise} - */ -const makeTempDir = async () => - fs.mkdtempSync(path.join(os.tmpdir(), 'eslint-plugin-jest-')); - -/** - * Sets up a fake project with a `package.json` file located in - * `node_modules/jest` whose version is set to the given `jestVersion`. - * - * @param {JestVersion} jestVersion - * - * @return {Promise} - */ -const setupFakeProjectDirectory = async ( - jestVersion: JestVersion, -): Promise => { - const jestPackageJson: JSONSchemaForNPMPackageJsonFiles = { - name: 'jest', - version: `${jestVersion}.0.0`, - }; - - const tempDir = await makeTempDir(); - const jestPackagePath = path.join(tempDir, 'node_modules', 'jest'); - - // todo: remove in node@10 & replace with { recursive: true } - fs.mkdirSync(path.join(tempDir, 'node_modules')); - - fs.mkdirSync(jestPackagePath); - await fs.writeFileSync( - path.join(jestPackagePath, 'package.json'), - JSON.stringify(jestPackageJson), - ); - - return tempDir; -}; - const generateValidCases = ( - jestVersion: JestVersion | undefined, + jestVersion: JestVersion, functionCall: string, ): Array> => { const [name, func] = functionCall.split('.'); const settings = { jest: { version: jestVersion } } as const; + const settingsString = { jest: { version: `${jestVersion}.0.0` } } as const; return [ { settings, code: `${functionCall}()` }, { settings, code: `${functionCall}` }, { settings, code: `${name}['${func}']()` }, { settings, code: `${name}['${func}']` }, + + { settings: settingsString, code: `${functionCall}()` }, + { settings: settingsString, code: `${functionCall}` }, + { settings: settingsString, code: `${name}['${func}']()` }, + { settings: settingsString, code: `${name}['${func}']` }, ]; }; const generateInvalidCases = ( - jestVersion: JestVersion | undefined, + jestVersion: JestVersion, deprecation: string, replacement: string, ): Array> => { const [deprecatedName, deprecatedFunc] = deprecation.split('.'); const [replacementName, replacementFunc] = replacement.split('.'); const settings = { jest: { version: jestVersion } }; + const settingsString = { jest: { version: `${jestVersion}.0.0` } } as const; const errors: [TSESLint.TestCaseError<'deprecatedFunction'>] = [ { messageId: 'deprecatedFunction', data: { deprecation, replacement } }, ]; @@ -94,45 +50,29 @@ const generateInvalidCases = ( settings, errors, }, + + { + code: `${deprecation}()`, + output: `${replacement}()`, + settings: settingsString, + errors, + }, + { + code: `${deprecatedName}['${deprecatedFunc}']()`, + output: `${replacementName}['${replacementFunc}']()`, + settings: settingsString, + errors, + }, ]; }; -describe('the jest version cache', () => { - beforeEach(async () => process.chdir(await setupFakeProjectDirectory(17))); - - // change the jest version *after* each test case - afterEach(async () => { - const jestPackageJson: JSONSchemaForNPMPackageJsonFiles = { - name: 'jest', - version: '24.0.0', - }; - - const tempDir = process.cwd(); - - await fs.writeFileSync( - path.join(tempDir, 'node_modules', 'jest', 'package.json'), - JSON.stringify(jestPackageJson), - ); - }); - - ruleTester.run('no-deprecated-functions', rule, { - valid: [ - 'require("fs")', // this will cause jest version to be read & cached - 'jest.requireActual()', // deprecated after jest 17 - ], - invalid: [], - }); -}); - -// contains the cache-clearing beforeEach so we can test the cache too describe('the rule', () => { - beforeEach(() => _clearCachedJestVersion()); - // a few sanity checks before doing our massive loop ruleTester.run('no-deprecated-functions', rule, { valid: [ - 'jest', - 'require("fs")', + ...generateValidCases(20, 'jest'), + ...generateValidCases(20, 'require("fs")'), + ...generateValidCases(14, 'jest.resetModuleRegistry'), ...generateValidCases(17, 'require.requireActual'), ...generateValidCases(25, 'jest.genMockFromModule'), @@ -155,10 +95,6 @@ describe('the rule', () => { describe.each([ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, ])('when using jest version %i', jestVersion => { - beforeEach(async () => - process.chdir(await setupFakeProjectDirectory(jestVersion)), - ); - const allowedFunctions: string[] = []; const deprecations = ( [ @@ -179,10 +115,11 @@ describe('the rule', () => { return true; }); - ruleTester.run('explict jest version', rule, { + ruleTester.run(`with version ${jestVersion}`, rule, { valid: [ - 'jest', - 'require("fs")', + ...generateValidCases(20, 'jest'), + ...generateValidCases(20, 'require("fs")'), + ...allowedFunctions .map(func => generateValidCases(jestVersion, func)) .reduce((acc, arr) => acc.concat(arr), []), @@ -193,67 +130,17 @@ describe('the rule', () => { ) .reduce((acc, arr) => acc.concat(arr), []), }); - - ruleTester.run('detected jest version', rule, { - valid: [ - 'jest', - 'require("fs")', - ...allowedFunctions - .map(func => generateValidCases(undefined, func)) - .reduce((acc, arr) => acc.concat(arr), []), - ], - invalid: deprecations - .map(([, deprecation, replacement]) => - generateInvalidCases(undefined, deprecation, replacement), - ) - .reduce((acc, arr) => acc.concat(arr), []), - }); }); - describe('when no jest version is provided', () => { - describe('when the jest package.json is missing the version property', () => { - beforeEach(async () => { - const tempDir = await setupFakeProjectDirectory(1); - - await fs.writeFileSync( - path.join(tempDir, 'node_modules', 'jest', 'package.json'), - JSON.stringify({}), - ); - - process.chdir(tempDir); - }); - - it('requires the version to be set explicitly', () => { - expect(() => { - const linter = new TSESLint.Linter(); - - linter.defineRule('no-deprecated-functions', rule); - - linter.verify('jest.resetModuleRegistry()', { - rules: { 'no-deprecated-functions': 'error' }, - }); - }).toThrow( - 'Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly', - ); - }); - }); - - describe('when the jest package.json is not found', () => { - beforeEach(async () => process.chdir(await makeTempDir())); - - it('requires the version to be set explicitly', () => { - expect(() => { - const linter = new TSESLint.Linter(); - - linter.defineRule('no-deprecated-functions', rule); - - linter.verify('jest.resetModuleRegistry()', { - rules: { 'no-deprecated-functions': 'error' }, - }); - }).toThrow( - 'Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly', - ); - }); - }); - }); + test.each(['mumbo jumbo', [], {}, null, undefined, ''])( + 'invalid version, %j', + version => { + expect(() => { + // @ts-expect-error: subset of `context` + rule.create({ settings: { jest: { version } } }); + }).toThrow( + 'Jest version not provided through settings - see https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-deprecated-functions.md#jest-version', + ); + }, + ); }); diff --git a/src/rules/no-deprecated-functions.ts b/src/rules/no-deprecated-functions.ts index 94d56aa20..e93fc56a7 100644 --- a/src/rules/no-deprecated-functions.ts +++ b/src/rules/no-deprecated-functions.ts @@ -1,4 +1,3 @@ -import { JSONSchemaForNPMPackageJsonFiles } from '@schemastore/package'; import { AST_NODE_TYPES, TSESTree, @@ -27,40 +26,9 @@ export type JestVersion = | number; interface EslintPluginJestSettings { - version: JestVersion; + version: JestVersion | string; } -let cachedJestVersion: JestVersion | null = null; - -/** @internal */ -export const _clearCachedJestVersion = () => (cachedJestVersion = null); - -const detectJestVersion = (): JestVersion => { - if (cachedJestVersion) { - return cachedJestVersion; - } - - try { - const jestPath = require.resolve('jest/package.json', { - paths: [process.cwd()], - }); - - const jestPackageJson = - // eslint-disable-next-line @typescript-eslint/no-require-imports - require(jestPath) as JSONSchemaForNPMPackageJsonFiles; - - if (jestPackageJson.version) { - const [majorVersion] = jestPackageJson.version.split('.'); - - return (cachedJestVersion = parseInt(majorVersion, 10)); - } - } catch {} - - throw new Error( - 'Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly', - ); -}; - export default createRule({ name: __filename, meta: { @@ -79,9 +47,23 @@ export default createRule({ }, defaultOptions: [], create(context) { - const jestVersion = - (context.settings as ContextSettings)?.jest?.version || - detectJestVersion(); + let jestVersion = (context.settings as ContextSettings)?.jest?.version; + + if (typeof jestVersion === 'string') { + const [majorVersion] = jestVersion.split('.'); + + jestVersion = parseInt(majorVersion, 10); + + if (Number.isNaN(jestVersion)) { + jestVersion = undefined; + } + } + + if (typeof jestVersion !== 'number') { + throw new Error( + 'Jest version not provided through settings - see https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-deprecated-functions.md#jest-version', + ); + } const deprecations: Record = { ...(jestVersion >= 15 && { diff --git a/yarn.lock b/yarn.lock index 90b70a83f..07429085d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2120,13 +2120,6 @@ __metadata: languageName: node linkType: hard -"@schemastore/package@npm:^0.0.6": - version: 0.0.6 - resolution: "@schemastore/package@npm:0.0.6" - checksum: f6cf21d690d93cb0c7c5dffe0d930dd3d8dc3e5ca2ed5b439dd672c94d3516460f06c03693696762f5052a4dca48356572cd2623dfe5bf5dc8597422edb4a373 - languageName: node - linkType: hard - "@semantic-release/changelog@npm:^5.0.1": version: 5.0.1 resolution: "@semantic-release/changelog@npm:5.0.1" @@ -4565,7 +4558,6 @@ __metadata: "@babel/preset-typescript": ^7.3.3 "@commitlint/cli": ^13.1.0 "@commitlint/config-conventional": ^13.1.0 - "@schemastore/package": ^0.0.6 "@semantic-release/changelog": ^5.0.1 "@semantic-release/git": ^9.0.0 "@types/dedent": ^0.7.0