Skip to content

Commit

Permalink
feat: prefer importing jest globals for specific types (#1568)
Browse files Browse the repository at this point in the history
* 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 <Jones258@Gmail.com>
  • Loading branch information
tomquist and G-Rath authored Apr 27, 2024
1 parent 2f21f33 commit c464ae3
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 3 deletions.
36 changes: 36 additions & 0 deletions docs/rules/prefer-importing-jest-globals.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,42 @@ describe('foo', () => {
});
```

## Options

This rule can be configured as follows

```json
{
"type": "object",
"properties": {
"types": {
"type": "array",
"items": {
"type": "string",
"enum": ["hook", "describe", "test", "expect", "jest", "unknown"]
}
}
},
"additionalProperties": false
}
```

#### types

A list of Jest global types to enforce explicit imports for. By default, all
Jest globals are enforced.

This option is useful when you only want to enforce explicit imports for a
subset of Jest globals. For instance, when migrating to ESM, you might want to
enforce explicit imports only for the `jest` global, as of
[Jest's ESM documentation](https://jestjs.io/docs/ecmascript-modules#differences-between-esm-and-commonjs).

```json5
{
'jest/prefer-importing-jest-globals': ['error', { types: ['jest'] }],
}
```

## Further Reading

- [Documentation](https://jestjs.io/docs/api)
46 changes: 46 additions & 0 deletions src/rules/__tests__/prefer-importing-jest-globals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
`,
parserOptions: { sourceType: 'module' },
},
{
code: dedent`
test('should pass', () => {
expect(true).toBeDefined();
});
`,
options: [{ types: ['jest'] }],
parserOptions: { sourceType: 'module' },
},
{
code: dedent`
const { it } = require('@jest/globals');
it('should pass', () => {
expect(true).toBeDefined();
});
`,
options: [{ types: ['test'] }],
parserOptions: { sourceType: 'module' },
},
{
code: dedent`
// with require
Expand Down Expand Up @@ -85,6 +104,33 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
},
],
},
{
code: dedent`
jest.useFakeTimers();
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
output: dedent`
import { jest } from '@jest/globals';
jest.useFakeTimers();
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
options: [{ types: ['jest'] }],
parserOptions: { sourceType: 'module' },
errors: [
{
endColumn: 5,
column: 1,
line: 1,
messageId: 'preferImportingJestGlobal',
},
],
},
{
code: dedent`
import React from 'react';
Expand Down
34 changes: 31 additions & 3 deletions src/rules/prefer-importing-jest-globals.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
import {
type JestFnType,
createRule,
getAccessorValue,
getSourceCode,
Expand All @@ -20,6 +21,15 @@ const createFixerImports = (
: `const { ${allImportsFormatted} } = require('@jest/globals');`;
};

const allJestFnTypes: JestFnType[] = [
'hook',
'describe',
'test',
'expect',
'jest',
'unknown',
];

export default createRule({
name: __filename,
meta: {
Expand All @@ -31,10 +41,25 @@ export default createRule({
},
fixable: 'code',
type: 'problem',
schema: [],
schema: [
{
type: 'object',
properties: {
types: {
type: 'array',
items: {
type: 'string',
enum: allJestFnTypes,
},
},
},
additionalProperties: false,
},
],
},
defaultOptions: [],
defaultOptions: [{ types: allJestFnTypes }],
create(context) {
const { types = allJestFnTypes } = context.options[0] || {};
const importedFunctionsWithSource: Record<string, string> = {};
const functionsToImport = new Set<string>();
let reportingNode: TSESTree.Node;
Expand All @@ -55,7 +80,10 @@ export default createRule({
return;
}

if (jestFnCall.head.type !== 'import') {
if (
jestFnCall.head.type !== 'import' &&
types.includes(jestFnCall.type)
) {
functionsToImport.add(jestFnCall.name);
reportingNode ||= jestFnCall.head.node;
}
Expand Down

0 comments on commit c464ae3

Please sign in to comment.