Skip to content

Commit c464ae3

Browse files
tomquistG-Rath
andauthored
feat: prefer importing jest globals for specific types (#1568)
* feat: prefer importing jest globals for specific types Accessing the `jest` global in ESM must be done either through `import.meta.jest` or by importing it from `@jest/globals`. The latter is useful while migrating to ESM because the former is not accessible in non-ESM. This adds an option to specify the types of globals for which we want to enforce the import. * refactor: remove unneeded cast --------- Co-authored-by: Gareth Jones <[email protected]>
1 parent 2f21f33 commit c464ae3

File tree

3 files changed

+113
-3
lines changed

3 files changed

+113
-3
lines changed

docs/rules/prefer-importing-jest-globals.md

+36
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,42 @@ describe('foo', () => {
4242
});
4343
```
4444

45+
## Options
46+
47+
This rule can be configured as follows
48+
49+
```json
50+
{
51+
"type": "object",
52+
"properties": {
53+
"types": {
54+
"type": "array",
55+
"items": {
56+
"type": "string",
57+
"enum": ["hook", "describe", "test", "expect", "jest", "unknown"]
58+
}
59+
}
60+
},
61+
"additionalProperties": false
62+
}
63+
```
64+
65+
#### types
66+
67+
A list of Jest global types to enforce explicit imports for. By default, all
68+
Jest globals are enforced.
69+
70+
This option is useful when you only want to enforce explicit imports for a
71+
subset of Jest globals. For instance, when migrating to ESM, you might want to
72+
enforce explicit imports only for the `jest` global, as of
73+
[Jest's ESM documentation](https://jestjs.io/docs/ecmascript-modules#differences-between-esm-and-commonjs).
74+
75+
```json5
76+
{
77+
'jest/prefer-importing-jest-globals': ['error', { types: ['jest'] }],
78+
}
79+
```
80+
4581
## Further Reading
4682

4783
- [Documentation](https://jestjs.io/docs/api)

src/rules/__tests__/prefer-importing-jest-globals.test.ts

+46
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,25 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
2222
`,
2323
parserOptions: { sourceType: 'module' },
2424
},
25+
{
26+
code: dedent`
27+
test('should pass', () => {
28+
expect(true).toBeDefined();
29+
});
30+
`,
31+
options: [{ types: ['jest'] }],
32+
parserOptions: { sourceType: 'module' },
33+
},
34+
{
35+
code: dedent`
36+
const { it } = require('@jest/globals');
37+
it('should pass', () => {
38+
expect(true).toBeDefined();
39+
});
40+
`,
41+
options: [{ types: ['test'] }],
42+
parserOptions: { sourceType: 'module' },
43+
},
2544
{
2645
code: dedent`
2746
// with require
@@ -85,6 +104,33 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
85104
},
86105
],
87106
},
107+
{
108+
code: dedent`
109+
jest.useFakeTimers();
110+
describe("suite", () => {
111+
test("foo");
112+
expect(true).toBeDefined();
113+
})
114+
`,
115+
output: dedent`
116+
import { jest } from '@jest/globals';
117+
jest.useFakeTimers();
118+
describe("suite", () => {
119+
test("foo");
120+
expect(true).toBeDefined();
121+
})
122+
`,
123+
options: [{ types: ['jest'] }],
124+
parserOptions: { sourceType: 'module' },
125+
errors: [
126+
{
127+
endColumn: 5,
128+
column: 1,
129+
line: 1,
130+
messageId: 'preferImportingJestGlobal',
131+
},
132+
],
133+
},
88134
{
89135
code: dedent`
90136
import React from 'react';

src/rules/prefer-importing-jest-globals.ts

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
22
import {
3+
type JestFnType,
34
createRule,
45
getAccessorValue,
56
getSourceCode,
@@ -20,6 +21,15 @@ const createFixerImports = (
2021
: `const { ${allImportsFormatted} } = require('@jest/globals');`;
2122
};
2223

24+
const allJestFnTypes: JestFnType[] = [
25+
'hook',
26+
'describe',
27+
'test',
28+
'expect',
29+
'jest',
30+
'unknown',
31+
];
32+
2333
export default createRule({
2434
name: __filename,
2535
meta: {
@@ -31,10 +41,25 @@ export default createRule({
3141
},
3242
fixable: 'code',
3343
type: 'problem',
34-
schema: [],
44+
schema: [
45+
{
46+
type: 'object',
47+
properties: {
48+
types: {
49+
type: 'array',
50+
items: {
51+
type: 'string',
52+
enum: allJestFnTypes,
53+
},
54+
},
55+
},
56+
additionalProperties: false,
57+
},
58+
],
3559
},
36-
defaultOptions: [],
60+
defaultOptions: [{ types: allJestFnTypes }],
3761
create(context) {
62+
const { types = allJestFnTypes } = context.options[0] || {};
3863
const importedFunctionsWithSource: Record<string, string> = {};
3964
const functionsToImport = new Set<string>();
4065
let reportingNode: TSESTree.Node;
@@ -55,7 +80,10 @@ export default createRule({
5580
return;
5681
}
5782

58-
if (jestFnCall.head.type !== 'import') {
83+
if (
84+
jestFnCall.head.type !== 'import' &&
85+
types.includes(jestFnCall.type)
86+
) {
5987
functionsToImport.add(jestFnCall.name);
6088
reportingNode ||= jestFnCall.head.node;
6189
}

0 commit comments

Comments
 (0)