Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jest-community/eslint-plugin-jest
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v28.0.0
Choose a base ref
...
head repository: jest-community/eslint-plugin-jest
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v28.1.0
Choose a head ref
  • 3 commits
  • 10 files changed
  • 4 contributors

Commits on Apr 6, 2024

  1. chore: remove patch (#1549)

    SimenB authored Apr 6, 2024
    Copy the full SHA
    9aa7aee View commit details
  2. feat: add prefer-importing-jest-globals rule (#1490)

    * feat: prefer importing jest globals [new rule]
    
    - Fix #1101
    
    Issue: #1101
    
    * test(prefer-importing-jest-globals): use `FlatCompatRuleTester`
    
    * test(prefer-importing-jest-globals): clean up tests
    
    * test(prefer-importing-jest-globals): explicitly set the source type to `script`
    
    * fix(prefer-importing-jest-globals): remove unneeded properties from rule config
    
    * fix(prefer-importing-jest-globals): support template literals
    
    * refactor(prefer-importing-jest-globals): use accessor utilities and optional chaining
    
    * refactor(prefer-importing-jest-globals): remove unneeded code
    
    * refactor(prefer-importing-jest-globals): use a Set
    
    ---------
    
    Co-authored-by: Gareth Jones <Jones258@Gmail.com>
    MadeinFrance and G-Rath authored Apr 6, 2024
    Copy the full SHA
    37478d8 View commit details
  3. chore(release): 28.1.0 [skip ci]

    # [28.1.0](v28.0.0...v28.1.0) (2024-04-06)
    
    ### Features
    
    * add `prefer-importing-jest-globals` rule ([#1490](#1490)) ([37478d8](37478d8)), closes [#1101](#1101)
    semantic-release-bot committed Apr 6, 2024
    Copy the full SHA
    6aaabc4 View commit details

This file was deleted.

7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# [28.1.0](https://github.com/jest-community/eslint-plugin-jest/compare/v28.0.0...v28.1.0) (2024-04-06)


### Features

* add `prefer-importing-jest-globals` rule ([#1490](https://github.com/jest-community/eslint-plugin-jest/issues/1490)) ([37478d8](https://github.com/jest-community/eslint-plugin-jest/commit/37478d860eb15841f2ab73bb3fb6d94f51841638)), closes [#1101](https://github.com/jest-community/eslint-plugin-jest/issues/1101)

# [28.0.0](https://github.com/jest-community/eslint-plugin-jest/compare/v27.9.0...v28.0.0) (2024-04-06)


1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -328,6 +328,7 @@ set to warn in.\
| [prefer-expect-resolves](docs/rules/prefer-expect-resolves.md) | Prefer `await expect(...).resolves` over `expect(await ...)` syntax | | | 🔧 | |
| [prefer-hooks-in-order](docs/rules/prefer-hooks-in-order.md) | Prefer having hooks in a consistent order | | | | |
| [prefer-hooks-on-top](docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | | | |
| [prefer-importing-jest-globals](docs/rules/prefer-importing-jest-globals.md) | Prefer importing Jest globals | | | 🔧 | |
| [prefer-lowercase-title](docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names | | | 🔧 | |
| [prefer-mock-promise-shorthand](docs/rules/prefer-mock-promise-shorthand.md) | Prefer mock resolved/rejected shorthands for promises | | | 🔧 | |
| [prefer-snapshot-hint](docs/rules/prefer-snapshot-hint.md) | Prefer including a hint with external snapshots | | | | |
47 changes: 47 additions & 0 deletions docs/rules/prefer-importing-jest-globals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Prefer importing Jest globals (`prefer-importing-jest-globals`)

🔧 This rule is automatically fixable by the
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->

This rule aims to enforce explicit imports from `@jest/globals`.

1. This is useful for ensuring that the Jest APIs are imported the same way in
the codebase.
2. When you can't modify Jest's
[`injectGlobals`](https://jestjs.io/docs/configuration#injectglobals-boolean)
configuration property, this rule can help to ensure that the Jest globals
are imported explicitly and facilitate a migration to `@jest/globals`.

## Rule details

Examples of **incorrect** code for this rule

```js
/* eslint jest/prefer-importing-jest-globals: "error" */

describe('foo', () => {
it('accepts this input', () => {
// ...
});
});
```

Examples of **correct** code for this rule

```js
/* eslint jest/prefer-importing-jest-globals: "error" */

import { describe, it } from '@jest/globals';

describe('foo', () => {
it('accepts this input', () => {
// ...
});
});
```

## Further Reading

- [Documentation](https://jestjs.io/docs/api)
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-jest",
"version": "28.0.0",
"version": "28.1.0",
"description": "ESLint rules for Jest",
"keywords": [
"eslint",
@@ -130,8 +130,5 @@
},
"publishConfig": {
"provenance": true
},
"resolutions": {
"@typescript-eslint/typescript-estree@5.62.0": "patch:@typescript-eslint/typescript-estree@npm:^5.62.0#./.yarn/patches/@typescript-eslint-typescript-estree-npm-5.62.0-5d1ea132a9.patch"
}
}
2 changes: 2 additions & 0 deletions src/__tests__/__snapshots__/rules.test.ts.snap
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ exports[`rules should export configs that refer to actual rules 1`] = `
"jest/prefer-expect-resolves": "error",
"jest/prefer-hooks-in-order": "error",
"jest/prefer-hooks-on-top": "error",
"jest/prefer-importing-jest-globals": "error",
"jest/prefer-lowercase-title": "error",
"jest/prefer-mock-promise-shorthand": "error",
"jest/prefer-snapshot-hint": "error",
@@ -126,6 +127,7 @@ exports[`rules should export configs that refer to actual rules 1`] = `
"jest/prefer-expect-resolves": "error",
"jest/prefer-hooks-in-order": "error",
"jest/prefer-hooks-on-top": "error",
"jest/prefer-importing-jest-globals": "error",
"jest/prefer-lowercase-title": "error",
"jest/prefer-mock-promise-shorthand": "error",
"jest/prefer-snapshot-hint": "error",
2 changes: 1 addition & 1 deletion src/__tests__/rules.test.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { existsSync } from 'fs';
import { resolve } from 'path';
import plugin from '../';

const numberOfRules = 52;
const numberOfRules = 53;
const ruleNames = Object.keys(plugin.rules);
const deprecatedRules = Object.entries(plugin.rules)
.filter(([, rule]) => rule.meta.deprecated)
504 changes: 504 additions & 0 deletions src/rules/__tests__/prefer-importing-jest-globals.test.ts

Large diffs are not rendered by default.

163 changes: 163 additions & 0 deletions src/rules/prefer-importing-jest-globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
import {
createRule,
getAccessorValue,
getSourceCode,
isIdentifier,
isStringNode,
isSupportedAccessor,
parseJestFnCall,
} from './utils';

const createFixerImports = (
isModule: boolean,
functionsToImport: Set<string>,
) => {
const allImportsFormatted = Array.from(functionsToImport).sort().join(', ');

return isModule
? `import { ${allImportsFormatted} } from '@jest/globals';`
: `const { ${allImportsFormatted} } = require('@jest/globals');`;
};

export default createRule({
name: __filename,
meta: {
docs: {
description: 'Prefer importing Jest globals',
},
messages: {
preferImportingJestGlobal: `Import the following Jest functions from '@jest/globals': {{ jestFunctions }}`,
},
fixable: 'code',
type: 'problem',
schema: [],
},
defaultOptions: [],
create(context) {
const importedFunctionsWithSource: Record<string, string> = {};
const functionsToImport = new Set<string>();
let reportingNode: TSESTree.Node;

return {
ImportDeclaration(node: TSESTree.ImportDeclaration) {
node.specifiers.forEach(specifier => {
if (specifier.type === 'ImportSpecifier') {
importedFunctionsWithSource[specifier.local.name] =
node.source.value;
}
});
},
CallExpression(node: TSESTree.CallExpression) {
const jestFnCall = parseJestFnCall(node, context);

if (!jestFnCall) {
return;
}

if (jestFnCall.head.type !== 'import') {
functionsToImport.add(jestFnCall.name);
reportingNode ||= jestFnCall.head.node;
}
},
'Program:exit'() {
// this means we found at least one function to import
if (!reportingNode) {
return;
}

const isModule = context.parserOptions.sourceType === 'module';

context.report({
node: reportingNode,
messageId: 'preferImportingJestGlobal',
data: { jestFunctions: Array.from(functionsToImport).join(', ') },
fix(fixer) {
const sourceCode = getSourceCode(context);
const [firstNode] = sourceCode.ast.body;

// check if "use strict" directive exists
if (
firstNode.type === AST_NODE_TYPES.ExpressionStatement &&
isStringNode(firstNode.expression, 'use strict')
) {
return fixer.insertTextAfter(
firstNode,
`\n${createFixerImports(isModule, functionsToImport)}`,
);
}

const importNode = sourceCode.ast.body.find(
node =>
node.type === AST_NODE_TYPES.ImportDeclaration &&
node.source.value === '@jest/globals',
);

if (importNode?.type === AST_NODE_TYPES.ImportDeclaration) {
for (const specifier of importNode.specifiers) {
if (
specifier.type === AST_NODE_TYPES.ImportSpecifier &&
specifier.imported?.name
) {
functionsToImport.add(specifier.imported.name);
}

if (specifier.type === AST_NODE_TYPES.ImportDefaultSpecifier) {
functionsToImport.add(specifier.local.name);
}
}

return fixer.replaceText(
importNode,
createFixerImports(isModule, functionsToImport),
);
}

const requireNode = sourceCode.ast.body.find(
node =>
node.type === AST_NODE_TYPES.VariableDeclaration &&
node.declarations.some(
declaration =>
declaration.init?.type === AST_NODE_TYPES.CallExpression &&
isIdentifier(declaration.init.callee, 'require') &&
isStringNode(
declaration.init.arguments[0],
'@jest/globals',
) &&
(declaration.id.type === AST_NODE_TYPES.Identifier ||
declaration.id.type === AST_NODE_TYPES.ObjectPattern),
),
);

if (requireNode?.type !== AST_NODE_TYPES.VariableDeclaration) {
return fixer.insertTextBefore(
reportingNode,
`${createFixerImports(isModule, functionsToImport)}\n`,
);
}

if (
requireNode.declarations[0]?.id.type ===
AST_NODE_TYPES.ObjectPattern
) {
for (const property of requireNode.declarations[0].id
.properties) {
if (
property.type === AST_NODE_TYPES.Property &&
isSupportedAccessor(property.key)
) {
functionsToImport.add(getAccessorValue(property.key));
}
}
}

return fixer.replaceText(
requireNode,
`${createFixerImports(isModule, functionsToImport)}`,
);
},
});
},
};
},
});
38 changes: 10 additions & 28 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -3054,26 +3054,7 @@ __metadata:
languageName: node
linkType: hard

"@typescript-eslint/typescript-estree@npm:6.21.0":
version: 6.21.0
resolution: "@typescript-eslint/typescript-estree@npm:6.21.0"
dependencies:
"@typescript-eslint/types": 6.21.0
"@typescript-eslint/visitor-keys": 6.21.0
debug: ^4.3.4
globby: ^11.1.0
is-glob: ^4.0.3
minimatch: 9.0.3
semver: ^7.5.4
ts-api-utils: ^1.0.1
peerDependenciesMeta:
typescript:
optional: true
checksum: dec02dc107c4a541e14fb0c96148f3764b92117c3b635db3a577b5a56fc48df7a556fa853fb82b07c0663b4bf2c484c9f245c28ba3e17e5cb0918ea4cab2ea21
languageName: node
linkType: hard

"@typescript-eslint/typescript-estree@npm:^5.62.0":
"@typescript-eslint/typescript-estree@npm:5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/typescript-estree@npm:5.62.0"
dependencies:
@@ -3091,21 +3072,22 @@ __metadata:
languageName: node
linkType: hard

"@typescript-eslint/typescript-estree@patch:@typescript-eslint/typescript-estree@npm:^5.62.0#./.yarn/patches/@typescript-eslint-typescript-estree-npm-5.62.0-5d1ea132a9.patch::locator=eslint-plugin-jest%40workspace%3A.":
version: 5.62.0
resolution: "@typescript-eslint/typescript-estree@patch:@typescript-eslint/typescript-estree@npm%3A5.62.0#./.yarn/patches/@typescript-eslint-typescript-estree-npm-5.62.0-5d1ea132a9.patch::version=5.62.0&hash=bbaf25&locator=eslint-plugin-jest%40workspace%3A."
"@typescript-eslint/typescript-estree@npm:6.21.0":
version: 6.21.0
resolution: "@typescript-eslint/typescript-estree@npm:6.21.0"
dependencies:
"@typescript-eslint/types": 5.62.0
"@typescript-eslint/visitor-keys": 5.62.0
"@typescript-eslint/types": 6.21.0
"@typescript-eslint/visitor-keys": 6.21.0
debug: ^4.3.4
globby: ^11.1.0
is-glob: ^4.0.3
semver: ^7.3.7
tsutils: ^3.21.0
minimatch: 9.0.3
semver: ^7.5.4
ts-api-utils: ^1.0.1
peerDependenciesMeta:
typescript:
optional: true
checksum: 166673643210b8786c3190b3261c22e1b67025489f579183e1cb6cb58a80ea0dd2e12c98aec86ad557b852fb9fd5ae1dc9c913bccdb96a57459228af7adb0c63
checksum: dec02dc107c4a541e14fb0c96148f3764b92117c3b635db3a577b5a56fc48df7a556fa853fb82b07c0663b4bf2c484c9f245c28ba3e17e5cb0918ea4cab2ea21
languageName: node
linkType: hard