From 91ba077fd2eb917de3d4641915055e8faabd2870 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Tue, 5 May 2020 12:25:27 +1200 Subject: [PATCH 1/2] feat(no-deprecated-functions): support jest `version` setting --- README.md | 17 ++ docs/rules/no-deprecated-functions.md | 22 +- package.json | 1 + .../__tests__/no-deprecated-functions.test.ts | 251 +++++++++++++++--- src/rules/no-deprecated-functions.ts | 73 ++++- yarn.lock | 5 + 6 files changed, 317 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 450638c9b..ae8aa58b9 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,23 @@ You can also whitelist the environment variables provided by Jest by doing: } ``` +The behaviour of some rules (specifically `no-deprecated-functions`) change +depending on the version of `jest` being used. + +This setting is detected automatically based off the version of the `jest` +package installed in `node_modules`, but it can also be provided explicitly if +desired: + +```json +{ + "settings": { + "jest": { + "version": 26 + } + } +} +``` + ## Shareable configurations ### Recommended diff --git a/docs/rules/no-deprecated-functions.md b/docs/rules/no-deprecated-functions.md index 18a87affe..7800088a9 100644 --- a/docs/rules/no-deprecated-functions.md +++ b/docs/rules/no-deprecated-functions.md @@ -9,10 +9,20 @@ 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. +what to replace them with, based on the version of Jest that is installed. This rule can also autofix a number of these deprecations for you. +### `jest.resetModuleRegistry` + +This function was renamed to `resetModules` in Jest 15, and is scheduled for +removal in Jest 27. + +### `jest.addMatchers` + +This function was replaced with `expect.extend` in Jest 17, and is scheduled for +removal in Jest 27. + ### `require.requireActual` & `require.requireMock` These functions were replaced in Jest 21 and removed in Jest 26. @@ -25,16 +35,6 @@ 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 diff --git a/package.json b/package.json index 734d3c8f0..07eb1829a 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@babel/preset-typescript": "^7.3.3", "@commitlint/cli": "^8.2.0", "@commitlint/config-conventional": "^8.2.0", + "@schemastore/package": "^0.0.5", "@semantic-release/changelog": "^3.0.5", "@semantic-release/git": "^7.0.17", "@types/eslint": "^6.1.3", diff --git a/src/rules/__tests__/no-deprecated-functions.test.ts b/src/rules/__tests__/no-deprecated-functions.test.ts index 40805d2ca..a8b876d89 100644 --- a/src/rules/__tests__/no-deprecated-functions.test.ts +++ b/src/rules/__tests__/no-deprecated-functions.test.ts @@ -1,49 +1,230 @@ +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 from '../no-deprecated-functions'; +import rule, { JestVersion } 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]) => { +/** + * 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, + functionCall: string, +): Array> => { + const [name, func] = functionCall.split('.'); + const settings = { jest: { version: jestVersion } } as const; + + return [ + { settings, code: `${functionCall}()` }, + { settings, code: `${functionCall}` }, + { settings, code: `${name}['${func}']()` }, + { settings, code: `${name}['${func}']` }, + ]; +}; + +const generateInvalidCases = ( + jestVersion: JestVersion | undefined, + deprecation: string, + replacement: string, +): Array> => { const [deprecatedName, deprecatedFunc] = deprecation.split('.'); const [replacementName, replacementFunc] = replacement.split('.'); + const settings = { jest: { version: jestVersion } }; + const errors: [TSESLint.TestCaseError<'deprecatedFunction'>] = [ + { messageId: 'deprecatedFunction', data: { deprecation, replacement } }, + ]; + + return [ + { + code: `${deprecation}()`, + output: `${replacement}()`, + settings, + errors, + }, + { + code: `${deprecatedName}['${deprecatedFunc}']()`, + output: `${replacementName}['${replacementFunc}']()`, + settings, + errors, + }, + ]; +}; - ruleTester.run(`${deprecation} -> ${replacement}`, rule, { +// a few sanity checks before doing our massive loop +ruleTester.run('no-deprecated-functions', rule, { + valid: [ + 'jest', + 'require("fs")', + ...generateValidCases(14, 'jest.resetModuleRegistry'), + ...generateValidCases(17, 'require.requireActual'), + ...generateValidCases(25, 'jest.genMockFromModule'), + ], + invalid: [ + ...generateInvalidCases( + 21, + 'jest.resetModuleRegistry', + 'jest.resetModules', + ), + ...generateInvalidCases(24, 'jest.addMatchers', 'expect.extend'), + ...generateInvalidCases( + 26, + 'jest.genMockFromModule', + 'jest.createMockFromModule', + ), + ], +}); + +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 = ([ + [15, 'jest.resetModuleRegistry', 'jest.resetModules'], + [17, 'jest.addMatchers', 'expect.extend'], + [21, 'require.requireMock', 'jest.requireMock'], + [21, 'require.requireActual', 'jest.requireActual'], + [22, 'jest.runTimersToTime', 'jest.advanceTimersByTime'], + [26, 'jest.genMockFromModule', 'jest.createMockFromModule'], + ] as const).filter(deprecation => { + if (deprecation[0] > jestVersion) { + allowedFunctions.push(deprecation[1]); + + return false; + } + + return true; + }); + + ruleTester.run('explict jest version', rule, { valid: [ 'jest', 'require("fs")', - `${replacement}()`, - replacement, - `${replacementName}['${replacementFunc}']()`, - `${replacementName}['${replacementFunc}']`, + ...allowedFunctions + .map(func => generateValidCases(jestVersion, func)) + .reduce((acc, arr) => acc.concat(arr), []), ], - invalid: [ - { - code: `${deprecation}()`, - output: `${replacement}()`, - errors: [ - { - messageId: 'deprecatedFunction', - data: { deprecation, replacement }, - }, - ], - }, - { - code: `${deprecatedName}['${deprecatedFunc}']()`, - output: `${replacementName}['${replacementFunc}']()`, - errors: [ - { - messageId: 'deprecatedFunction', - data: { deprecation, replacement }, - }, - ], - }, + invalid: deprecations + .map(([, deprecation, replacement]) => + generateInvalidCases(jestVersion, deprecation, replacement), + ) + .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', + ); + }); }); }); diff --git a/src/rules/no-deprecated-functions.ts b/src/rules/no-deprecated-functions.ts index 60c0ad382..605731268 100644 --- a/src/rules/no-deprecated-functions.ts +++ b/src/rules/no-deprecated-functions.ts @@ -1,9 +1,56 @@ +import { JSONSchemaForNPMPackageJsonFiles } from '@schemastore/package'; import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; import { createRule, getNodeName } from './utils'; +interface ContextSettings { + jest?: EslintPluginJestSettings; +} + +export type JestVersion = + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | number; + +interface EslintPluginJestSettings { + version: JestVersion; +} + +const detectJestVersion = (): JestVersion => { + try { + const jestPath = require.resolve('jest/package.json', { + paths: [process.cwd()], + }); + + // eslint-disable-next-line @typescript-eslint/no-require-imports + const jestPackageJson = require(jestPath) as JSONSchemaForNPMPackageJsonFiles; + + if (jestPackageJson.version) { + const [majorVersion] = jestPackageJson.version.split('.'); + + return parseInt(majorVersion); + } + } 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: { @@ -22,13 +69,27 @@ export default createRule({ }, defaultOptions: [], create(context) { + const jestVersion = + (context.settings as ContextSettings)?.jest?.version || + detectJestVersion(); + 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', + ...(jestVersion >= 15 && { + 'jest.resetModuleRegistry': 'jest.resetModules', + }), + ...(jestVersion >= 17 && { + 'jest.addMatchers': 'expect.extend', + }), + ...(jestVersion >= 21 && { + 'require.requireMock': 'jest.requireMock', + 'require.requireActual': 'jest.requireActual', + }), + ...(jestVersion >= 22 && { + 'jest.runTimersToTime': 'jest.advanceTimersByTime', + }), + ...(jestVersion >= 26 && { + 'jest.genMockFromModule': 'jest.createMockFromModule', + }), }; return { diff --git a/yarn.lock b/yarn.lock index 38a98798a..fc646aa4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1259,6 +1259,11 @@ dependencies: any-observable "^0.3.0" +"@schemastore/package@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@schemastore/package/-/package-0.0.5.tgz#67b621d5c833ad2d5a29a1acf868717b7839bb8a" + integrity sha512-0XEiMT/Rh8I0SEIO81fo5MN3AHhONFv9SJ1IIJ5OTI3PN/jG032OPlHUMxvrun7yc6YUGtufVg2WPQVK8PaH5Q== + "@semantic-release/changelog@^3.0.5": version "3.0.6" resolved "https://registry.yarnpkg.com/@semantic-release/changelog/-/changelog-3.0.6.tgz#9d68d68bf732cbba1034c028bb6720091f783b2a" From b5e58e6aed33ae3bfc2fc9d5130e65be141d9ba3 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Tue, 5 May 2020 21:20:30 +1200 Subject: [PATCH 2/2] chore(no-deprecated-functions): cache jest version --- .../__tests__/no-deprecated-functions.test.ts | 259 ++++++++++-------- src/rules/no-deprecated-functions.ts | 11 +- 2 files changed, 157 insertions(+), 113 deletions(-) diff --git a/src/rules/__tests__/no-deprecated-functions.test.ts b/src/rules/__tests__/no-deprecated-functions.test.ts index a8b876d89..7ffdd3fe8 100644 --- a/src/rules/__tests__/no-deprecated-functions.test.ts +++ b/src/rules/__tests__/no-deprecated-functions.test.ts @@ -3,7 +3,10 @@ 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 } from '../no-deprecated-functions'; +import rule, { + JestVersion, + _clearCachedJestVersion, +} from '../no-deprecated-functions'; const ruleTester = new TSESLint.RuleTester(); @@ -89,142 +92,174 @@ const generateInvalidCases = ( ]; }; -// a few sanity checks before doing our massive loop -ruleTester.run('no-deprecated-functions', rule, { - valid: [ - 'jest', - 'require("fs")', - ...generateValidCases(14, 'jest.resetModuleRegistry'), - ...generateValidCases(17, 'require.requireActual'), - ...generateValidCases(25, 'jest.genMockFromModule'), - ], - invalid: [ - ...generateInvalidCases( - 21, - 'jest.resetModuleRegistry', - 'jest.resetModules', - ), - ...generateInvalidCases(24, 'jest.addMatchers', 'expect.extend'), - ...generateInvalidCases( - 26, - 'jest.genMockFromModule', - 'jest.createMockFromModule', - ), - ], -}); +describe('the jest version cache', () => { + beforeEach(async () => process.chdir(await setupFakeProjectDirectory(17))); -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)), - ); + // change the jest version *after* each test case + afterEach(async () => { + const jestPackageJson: JSONSchemaForNPMPackageJsonFiles = { + name: 'jest', + version: '24.0.0', + }; + + const tempDir = process.cwd(); - const allowedFunctions: string[] = []; - const deprecations = ([ - [15, 'jest.resetModuleRegistry', 'jest.resetModules'], - [17, 'jest.addMatchers', 'expect.extend'], - [21, 'require.requireMock', 'jest.requireMock'], - [21, 'require.requireActual', 'jest.requireActual'], - [22, 'jest.runTimersToTime', 'jest.advanceTimersByTime'], - [26, 'jest.genMockFromModule', 'jest.createMockFromModule'], - ] as const).filter(deprecation => { - if (deprecation[0] > jestVersion) { - allowedFunctions.push(deprecation[1]); - - return false; - } - - return true; + await fs.writeFileSync( + path.join(tempDir, 'node_modules', 'jest', 'package.json'), + JSON.stringify(jestPackageJson), + ); }); - ruleTester.run('explict jest version', rule, { + ruleTester.run('no-deprecated-functions', rule, { valid: [ - 'jest', - 'require("fs")', - ...allowedFunctions - .map(func => generateValidCases(jestVersion, func)) - .reduce((acc, arr) => acc.concat(arr), []), + 'require("fs")', // this will cause jest version to be read & cached + 'jest.requireActual()', // deprecated after jest 17 ], - invalid: deprecations - .map(([, deprecation, replacement]) => - generateInvalidCases(jestVersion, deprecation, replacement), - ) - .reduce((acc, arr) => acc.concat(arr), []), + invalid: [], }); +}); + +// contains the cache-clearing beforeEach so we can test the cache too +describe('the rule', () => { + beforeEach(() => _clearCachedJestVersion()); - ruleTester.run('detected jest version', rule, { + // a few sanity checks before doing our massive loop + ruleTester.run('no-deprecated-functions', rule, { valid: [ 'jest', 'require("fs")', - ...allowedFunctions - .map(func => generateValidCases(undefined, func)) - .reduce((acc, arr) => acc.concat(arr), []), + ...generateValidCases(14, 'jest.resetModuleRegistry'), + ...generateValidCases(17, 'require.requireActual'), + ...generateValidCases(25, 'jest.genMockFromModule'), + ], + invalid: [ + ...generateInvalidCases( + 21, + 'jest.resetModuleRegistry', + 'jest.resetModules', + ), + ...generateInvalidCases(24, 'jest.addMatchers', 'expect.extend'), + ...generateInvalidCases( + 26, + 'jest.genMockFromModule', + 'jest.createMockFromModule', + ), ], - 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); + 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)), + ); - await fs.writeFileSync( - path.join(tempDir, 'node_modules', 'jest', 'package.json'), - JSON.stringify({}), - ); + const allowedFunctions: string[] = []; + const deprecations = ([ + [15, 'jest.resetModuleRegistry', 'jest.resetModules'], + [17, 'jest.addMatchers', 'expect.extend'], + [21, 'require.requireMock', 'jest.requireMock'], + [21, 'require.requireActual', 'jest.requireActual'], + [22, 'jest.runTimersToTime', 'jest.advanceTimersByTime'], + [26, 'jest.genMockFromModule', 'jest.createMockFromModule'], + ] as const).filter(deprecation => { + if (deprecation[0] > jestVersion) { + allowedFunctions.push(deprecation[1]); - process.chdir(tempDir); - }); + return false; + } - it('requires the version to be set explicitly', () => { - expect(() => { - const linter = new TSESLint.Linter(); + return true; + }); - linter.defineRule('no-deprecated-functions', rule); + ruleTester.run('explict jest version', rule, { + valid: [ + 'jest', + 'require("fs")', + ...allowedFunctions + .map(func => generateValidCases(jestVersion, func)) + .reduce((acc, arr) => acc.concat(arr), []), + ], + invalid: deprecations + .map(([, deprecation, replacement]) => + generateInvalidCases(jestVersion, deprecation, replacement), + ) + .reduce((acc, arr) => acc.concat(arr), []), + }); - 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', - ); + 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 the jest package.json is not found', () => { - beforeEach(async () => process.chdir(await makeTempDir())); + 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(); + it('requires the version to be set explicitly', () => { + expect(() => { + const linter = new TSESLint.Linter(); - linter.defineRule('no-deprecated-functions', rule); + 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', - ); + 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', + ); + }); }); }); }); diff --git a/src/rules/no-deprecated-functions.ts b/src/rules/no-deprecated-functions.ts index 605731268..cdbf42d11 100644 --- a/src/rules/no-deprecated-functions.ts +++ b/src/rules/no-deprecated-functions.ts @@ -30,7 +30,16 @@ interface EslintPluginJestSettings { version: JestVersion; } +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()], @@ -42,7 +51,7 @@ const detectJestVersion = (): JestVersion => { if (jestPackageJson.version) { const [majorVersion] = jestPackageJson.version.split('.'); - return parseInt(majorVersion); + return (cachedJestVersion = parseInt(majorVersion, 10)); } } catch {}