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
101 changes: 55 additions & 46 deletions src/rules/__tests__/no-deprecated-functions.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { TSESLint } from '@typescript-eslint/utils';
import type { TSESLint } from '@typescript-eslint/utils';
import rule from '../no-deprecated-functions';
import {
type JestVersion,
detectJestVersion,
} from '../utils/detectJestVersion';
import {
FlatCompatRuleTester as RuleTester,
usingFlatConfig,
} from './test-utils';

jest.mock('../utils/detectJestVersion');

const detectJestVersionMock = detectJestVersion as jest.MockedFunction<
typeof detectJestVersion
import { type JestVersion, getJestVersion } from '../utils/detectJestVersion';
import { FlatCompatRuleTester as RuleTester } from './test-utils';

jest.mock('../utils/detectJestVersion', () => {
const actual = jest.requireActual('../utils/detectJestVersion');

jest.spyOn(actual, 'getJestVersion');

return actual;
});

const getJestVersionMock = getJestVersion as jest.MockedFunction<
typeof getJestVersion
>;

const ruleTester = new RuleTester();
Expand Down Expand Up @@ -60,6 +60,31 @@ const generateInvalidCases = (
];
};

const generateNotDetectedCases = (
deprecation: string,
): Array<TSESLint.InvalidTestCase<'jestNotDetected', []>> => {
const [deprecatedName, deprecatedFunc] = deprecation.split('.');
const settings = { jest: { version: undefined } };
const errors: [TSESLint.TestCaseError<'jestNotDetected'>] = [
{ messageId: 'jestNotDetected' },
];

return [
{
code: `${deprecation}()`,
output: null,
settings,
errors,
},
{
code: `${deprecatedName}['${deprecatedFunc}']()`,
output: null,
settings,
errors,
},
];
};

// contains the cache-clearing beforeEach so we can test the cache too
describe('the rule', () => {
// a few sanity checks before doing our massive loop
Expand All @@ -72,7 +97,7 @@ describe('the rule', () => {
...generateValidCases(25, 'jest.genMockFromModule'),
...generateValidCases('25.1.1', 'jest.genMockFromModule'),
...generateValidCases('17.2', 'require.requireActual'),
],
].filter(testCase => testCase.settings?.jest?.version),
invalid: [
...generateInvalidCases(
21,
Expand All @@ -90,14 +115,14 @@ describe('the rule', () => {
'jest.genMockFromModule',
'jest.createMockFromModule',
),
],
].filter(testCase => testCase.settings?.jest?.version),
});

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 () => {
detectJestVersionMock.mockReturnValue(jestVersion);
getJestVersionMock.mockReturnValue(jestVersion);
});

const allowedFunctions: string[] = [];
Expand Down Expand Up @@ -147,37 +172,21 @@ describe('the rule', () => {
});
});

describe('when there is an error in detecting the jest version', () => {
beforeEach(() => {
detectJestVersionMock.mockImplementation(() => {
throw new Error('oh noes!');
});
describe('when jest version not detected', () => {
beforeEach(async () => {
getJestVersionMock.mockReturnValue(null);
});

it('bubbles the error up', () => {
expect(() => {
const linter = new TSESLint.Linter();

/* istanbul ignore if */
if (usingFlatConfig) {
linter.verify('jest.resetModuleRegistry()', [
{
plugins: {
jest: { rules: { 'no-deprecated-functions': rule } },
},
rules: { 'jest/no-deprecated-functions': 'error' },
},
]);

return;
}

linter.defineRule('no-deprecated-functions', rule);

linter.verify('jest.resetModuleRegistry()', {
rules: { 'no-deprecated-functions': 'error' },
});
}).toThrow('oh noes!');
ruleTester.run('no jest version', rule, {
valid: ['jest', 'require("fs")'],
invalid: [
'jest.resetModuleRegistry',
'jest.addMatchers',
'require.requireMock',
'require.requireActual',
'jest.runTimersToTime',
'jest.genMockFromModule',
].flatMap(generateNotDetectedCases),
});
});
});
38 changes: 13 additions & 25 deletions src/rules/no-deprecated-functions.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,11 @@
import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
import {
type JestVersion,
type EslintPluginJestRuleContext,
createRule,
detectJestVersion,
getJestVersion,
getNodeName,
} from './utils';

interface ContextSettings {
jest?: EslintPluginJestSettings;
}

interface EslintPluginJestSettings {
version: JestVersion | string;
}

const parseJestVersion = (rawVersion: number | string): JestVersion => {
if (typeof rawVersion === 'number') {
return rawVersion;
}

const [majorVersion] = rawVersion.split('.');

return parseInt(majorVersion, 10);
};

export default createRule({
name: __filename,
meta: {
Expand All @@ -33,17 +15,20 @@ export default createRule({
messages: {
deprecatedFunction:
'`{{ deprecation }}` has been deprecated in favor of `{{ replacement }}`',
jestNotDetected:
'Unable to detect Jest version - please ensure jest package is installed or set version explicitly',
},
type: 'suggestion',
schema: [],
fixable: 'code',
},
defaultOptions: [],
create(context) {
const jestVersion = parseJestVersion(
(context.settings as ContextSettings)?.jest?.version ||
detectJestVersion(),
);
// If jest version is not detected, it is set to Infinity so that all possible deprecations
// are reported with a "jest not detected" error message
const jestVersion =
getJestVersion(context as EslintPluginJestRuleContext) || Infinity;
const jestNotDetected = jestVersion === Infinity;

const deprecations: Record<string, string> = {
...(jestVersion >= 15 && {
Expand Down Expand Up @@ -80,13 +65,16 @@ export default createRule({
const { callee } = node;

context.report({
messageId: 'deprecatedFunction',
messageId: jestNotDetected ? 'jestNotDetected' : 'deprecatedFunction',
data: {
deprecation,
replacement,
},
node,
fix(fixer) {
if (jestNotDetected) {
return [];
}
let [name, func] = replacement.split('.');

if (callee.property.type === AST_NODE_TYPES.Literal) {
Expand Down
12 changes: 5 additions & 7 deletions src/rules/utils/__tests__/detectJestVersion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,10 @@ describe('detectJestVersion', () => {
});

describe('when the package.json is missing the version property', () => {
it('throws an error', () => {
it('returns null', () => {
packageJsonFactory.mockReturnValue({});

expect(() => detectJestVersion()).toThrow(
/Unable to detect Jest version/iu,
);
expect(detectJestVersion()).toBeNull();
});
});

Expand Down Expand Up @@ -184,7 +182,7 @@ describe('detectJestVersion', () => {
});

describe('when jest is not installed', () => {
it('throws an error', () => {
it('returns null', () => {
const projectDir = setupFakeProject({
'package.json': { name: 'no-jest' },
[`node_modules/${relativePathToFn}`]: compiledFn,
Expand All @@ -193,8 +191,8 @@ describe('detectJestVersion', () => {

const { stdout, stderr } = runDetectJestVersion(projectDir);

expect(stdout).toBe('undefined');
expect(stderr).toContain('Unable to detect Jest version');
expect(stdout).toBe('null');
expect(stderr).toContain('');
});
});

Expand Down
49 changes: 42 additions & 7 deletions src/rules/utils/detectJestVersion.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
import type { JSONSchemaForNPMPackageJsonFiles } from '@schemastore/package';
import type { RuleContext } from '@typescript-eslint/utils/ts-eslint';

interface ContextSettings {
[key: string]: unknown;
jest?: EslintPluginJestSettings;
}

interface EslintPluginJestSettings {
version: JestVersion | string;
}

export interface EslintPluginJestRuleContext
extends Readonly<RuleContext<never, []>> {
settings: ContextSettings;
}

export type JestVersion =
| 14
Expand All @@ -22,7 +37,25 @@ export type JestVersion =

let cachedJestVersion: JestVersion | null = null;

export const detectJestVersion = (): JestVersion => {
const parseJestVersion = (rawVersion: number | string): JestVersion => {
if (typeof rawVersion === 'number') {
return rawVersion;
}

const [majorVersion] = rawVersion.split('.');

return parseInt(majorVersion, 10);
};

export const getContextJestVersion = (
context: EslintPluginJestRuleContext,
): JestVersion | null => {
return context.settings.jest?.version
? parseJestVersion(context.settings.jest.version)
: null;
};

export const detectJestVersion = (): JestVersion | null => {
if (cachedJestVersion) {
return cachedJestVersion;
}
Expand All @@ -35,13 +68,15 @@ export const detectJestVersion = (): JestVersion => {
require(jestPath) as JSONSchemaForNPMPackageJsonFiles;

if (jestPackageJson.version) {
const [majorVersion] = jestPackageJson.version.split('.');

return (cachedJestVersion = parseInt(majorVersion, 10));
return (cachedJestVersion = parseJestVersion(jestPackageJson.version));
}
} catch {}

throw new Error(
'Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly',
);
return null;
};

export const getJestVersion = (
context: EslintPluginJestRuleContext,
): JestVersion | null => {
return getContextJestVersion(context) || detectJestVersion();
};
Loading