Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/rules/no-deprecated-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
195 changes: 41 additions & 154 deletions src/rules/__tests__/no-deprecated-functions.test.ts
Original file line number Diff line number Diff line change
@@ -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<string>}
*/
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<string>}
*/
const setupFakeProjectDirectory = async (
jestVersion: JestVersion,
): Promise<string> => {
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<TSESLint.ValidTestCase<never>> => {
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<TSESLint.InvalidTestCase<'deprecatedFunction', never>> => {
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 } },
];
Expand All @@ -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'),
Expand All @@ -155,10 +95,6 @@ describe('the rule', () => {
describe.each<JestVersion>([
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 = (
[
Expand All @@ -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), []),
Expand All @@ -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',
);
},
);
});
54 changes: 18 additions & 36 deletions src/rules/no-deprecated-functions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { JSONSchemaForNPMPackageJsonFiles } from '@schemastore/package';
import {
AST_NODE_TYPES,
TSESTree,
Expand Down Expand Up @@ -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: {
Expand All @@ -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<string, string> = {
...(jestVersion >= 15 && {
Expand Down
Loading