eslint-plugin-jest 28.4.0 → 28.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -307,10 +307,15 @@ enabled in.\
307
307
  set to warn in.\
308
308
  ✅ Set in the `recommended`
309
309
  [configuration](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations).\
310
- 🎨 Set in the `style` [configuration](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations).\
311
- 🔧 Automatically fixable by the
310
+ 🎨
311
+ Set in the `style`
312
+ [configuration](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations).\
313
+ 🔧
314
+ Automatically fixable by the
312
315
  [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
313
- 💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
316
+ 💡
317
+ Manually fixable by
318
+ [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
314
319
 
315
320
  | Name                          | Description | 💼 | ⚠️ | 🔧 | 💡 |
316
321
  | :--------------------------------------------------------------------------- | :------------------------------------------------------------------------ | :-- | :-- | :-- | :-- |
@@ -350,6 +355,7 @@ set to warn in.\
350
355
  | [prefer-hooks-in-order](docs/rules/prefer-hooks-in-order.md) | Prefer having hooks in a consistent order | | | | |
351
356
  | [prefer-hooks-on-top](docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | | | |
352
357
  | [prefer-importing-jest-globals](docs/rules/prefer-importing-jest-globals.md) | Prefer importing Jest globals | | | 🔧 | |
358
+ | [prefer-jest-mocked](docs/rules/prefer-jest-mocked.md) | Prefer `jest.mocked()` over `fn as jest.Mock` | | | 🔧 | |
353
359
  | [prefer-lowercase-title](docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names | | | 🔧 | |
354
360
  | [prefer-mock-promise-shorthand](docs/rules/prefer-mock-promise-shorthand.md) | Prefer mock resolved/rejected shorthands for promises | | | 🔧 | |
355
361
  | [prefer-snapshot-hint](docs/rules/prefer-snapshot-hint.md) | Prefer including a hint with external snapshots | | | | |
@@ -0,0 +1,38 @@
1
+ # Prefer `jest.mocked()` over `fn as jest.Mock` (`prefer-jest-mocked`)
2
+
3
+ 🔧 This rule is automatically fixable by the
4
+ [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
5
+
6
+ <!-- end auto-generated rule header -->
7
+
8
+ When working with mocks of functions using Jest, it's recommended to use the
9
+ `jest.mocked()` helper function to properly type the mocked functions. This rule
10
+ enforces the use of `jest.mocked()` for better type safety and readability.
11
+
12
+ Restricted types:
13
+
14
+ - `jest.Mock`
15
+ - `jest.MockedFunction`
16
+ - `jest.MockedClass`
17
+ - `jest.MockedObject`
18
+
19
+ ## Rule details
20
+
21
+ The following patterns are warnings:
22
+
23
+ ```typescript
24
+ (foo as jest.Mock).mockReturnValue(1);
25
+ const mock = (foo as jest.Mock).mockReturnValue(1);
26
+ (foo as unknown as jest.Mock).mockReturnValue(1);
27
+ (Obj.foo as jest.Mock).mockReturnValue(1);
28
+ ([].foo as jest.Mock).mockReturnValue(1);
29
+ ```
30
+
31
+ The following patterns are not warnings:
32
+
33
+ ```js
34
+ jest.mocked(foo).mockReturnValue(1);
35
+ const mock = jest.mocked(foo).mockReturnValue(1);
36
+ jest.mocked(Obj.foo).mockReturnValue(1);
37
+ jest.mocked([].foo).mockReturnValue(1);
38
+ ```
package/lib/index.js CHANGED
@@ -4,7 +4,7 @@ var _fs = require("fs");
4
4
  var _path = require("path");
5
5
  var _package = require("../package.json");
6
6
  var _globals = _interopRequireDefault(require("./globals.json"));
7
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
7
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
8
  // copied from https://github.com/babel/babel/blob/d8da63c929f2d28c401571e2a43166678c555bc4/packages/babel-helpers/src/helpers.js#L602-L606
9
9
  /* istanbul ignore next */
10
10
  const interopRequireDefault = obj => obj && obj.__esModule ? obj : {
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _utils = require("@typescript-eslint/utils");
8
+ var _utils2 = require("./utils");
9
+ const mockTypes = ['Mock', 'MockedFunction', 'MockedClass', 'MockedObject'];
10
+ var _default = exports.default = (0, _utils2.createRule)({
11
+ name: __filename,
12
+ meta: {
13
+ docs: {
14
+ description: 'Prefer `jest.mocked()` over `fn as jest.Mock`'
15
+ },
16
+ messages: {
17
+ useJestMocked: 'Prefer `jest.mocked()`'
18
+ },
19
+ schema: [],
20
+ type: 'suggestion',
21
+ fixable: 'code'
22
+ },
23
+ defaultOptions: [],
24
+ create(context) {
25
+ function check(node) {
26
+ const {
27
+ typeAnnotation
28
+ } = node;
29
+ if (typeAnnotation.type !== _utils.AST_NODE_TYPES.TSTypeReference) {
30
+ return;
31
+ }
32
+ const {
33
+ typeName
34
+ } = typeAnnotation;
35
+ if (typeName.type !== _utils.AST_NODE_TYPES.TSQualifiedName) {
36
+ return;
37
+ }
38
+ const {
39
+ left,
40
+ right
41
+ } = typeName;
42
+ if (left.type !== _utils.AST_NODE_TYPES.Identifier || right.type !== _utils.AST_NODE_TYPES.Identifier || left.name !== 'jest' || !mockTypes.includes(right.name)) {
43
+ return;
44
+ }
45
+ const fnName = (0, _utils2.getSourceCode)(context).text.slice(...(0, _utils2.followTypeAssertionChain)(node.expression).range);
46
+ context.report({
47
+ node: node,
48
+ messageId: 'useJestMocked',
49
+ fix(fixer) {
50
+ return fixer.replaceText(node, `jest.mocked(${fnName})`);
51
+ }
52
+ });
53
+ }
54
+ return {
55
+ TSAsExpression(node) {
56
+ if (node.parent.type === _utils.AST_NODE_TYPES.TSAsExpression) {
57
+ return;
58
+ }
59
+ check(node);
60
+ },
61
+ TSTypeAssertion(node) {
62
+ check(node);
63
+ }
64
+ };
65
+ }
66
+ });
@@ -28,13 +28,19 @@ const getPromiseCallExpressionNode = node => {
28
28
  return null;
29
29
  };
30
30
  const findPromiseCallExpressionNode = node => node.parent?.parent && [_utils.AST_NODE_TYPES.CallExpression, _utils.AST_NODE_TYPES.ArrayExpression].includes(node.parent.type) ? getPromiseCallExpressionNode(node.parent) : null;
31
- const findFirstAsyncFunction = ({
31
+ const findFirstFunctionExpression = ({
32
32
  parent
33
33
  }) => {
34
34
  if (!parent) {
35
35
  return null;
36
36
  }
37
- return (0, _utils2.isFunction)(parent) && parent.async ? parent : findFirstAsyncFunction(parent);
37
+ return (0, _utils2.isFunction)(parent) ? parent : findFirstFunctionExpression(parent);
38
+ };
39
+ const getNormalizeFunctionExpression = functionExpression => {
40
+ if (functionExpression.parent.type === _utils.AST_NODE_TYPES.Property && functionExpression.type === _utils.AST_NODE_TYPES.FunctionExpression) {
41
+ return functionExpression.parent;
42
+ }
43
+ return functionExpression;
38
44
  };
39
45
  const getParentIfThenified = node => {
40
46
  const grandParentNode = node.parent?.parent;
@@ -114,6 +120,7 @@ var _default = exports.default = (0, _utils2.createRule)({
114
120
  }]) {
115
121
  // Context state
116
122
  const arrayExceptions = new Set();
123
+ const descriptors = [];
117
124
  const pushPromiseArrayException = loc => arrayExceptions.add(promiseArrayExceptionKey(loc));
118
125
 
119
126
  /**
@@ -224,7 +231,7 @@ var _default = exports.default = (0, _utils2.createRule)({
224
231
  } = jestFnCall;
225
232
  const parentNode = matcher.parent.parent;
226
233
  const shouldBeAwaited = jestFnCall.modifiers.some(nod => (0, _utils2.getAccessorValue)(nod) !== 'not') || asyncMatchers.includes((0, _utils2.getAccessorValue)(matcher));
227
- if (!parentNode?.parent || !shouldBeAwaited) {
234
+ if (!parentNode.parent || !shouldBeAwaited) {
228
235
  return;
229
236
  }
230
237
  /**
@@ -232,7 +239,6 @@ var _default = exports.default = (0, _utils2.createRule)({
232
239
  * for the array object, not for each individual assertion.
233
240
  */
234
241
  const isParentArrayExpression = parentNode.parent.type === _utils.AST_NODE_TYPES.ArrayExpression;
235
- const orReturned = alwaysAwait ? '' : ' or returned';
236
242
  /**
237
243
  * An async assertion can be chained with `then` or `catch` statements.
238
244
  * In that case our target CallExpression node is the one with
@@ -245,30 +251,51 @@ var _default = exports.default = (0, _utils2.createRule)({
245
251
  !isAcceptableReturnNode(finalNode.parent, !alwaysAwait) &&
246
252
  // if we didn't warn user already
247
253
  !promiseArrayExceptionExists(finalNode.loc)) {
254
+ descriptors.push({
255
+ node: finalNode,
256
+ messageId: targetNode === finalNode ? 'asyncMustBeAwaited' : 'promisesWithAsyncAssertionsMustBeAwaited'
257
+ });
258
+ }
259
+ if (isParentArrayExpression) {
260
+ pushPromiseArrayException(finalNode.loc);
261
+ }
262
+ },
263
+ 'Program:exit'() {
264
+ const fixes = [];
265
+ descriptors.forEach(({
266
+ node,
267
+ messageId
268
+ }, index) => {
269
+ const orReturned = alwaysAwait ? '' : ' or returned';
248
270
  context.report({
249
- loc: finalNode.loc,
271
+ loc: node.loc,
250
272
  data: {
251
273
  orReturned
252
274
  },
253
- messageId: finalNode === targetNode ? 'asyncMustBeAwaited' : 'promisesWithAsyncAssertionsMustBeAwaited',
275
+ messageId,
254
276
  node,
255
277
  fix(fixer) {
256
- if (!findFirstAsyncFunction(finalNode)) {
257
- return [];
278
+ const functionExpression = findFirstFunctionExpression(node);
279
+ if (!functionExpression) {
280
+ return null;
281
+ }
282
+ const foundAsyncFixer = fixes.some(fix => fix.text === 'async ');
283
+ if (!functionExpression.async && !foundAsyncFixer) {
284
+ const targetFunction = getNormalizeFunctionExpression(functionExpression);
285
+ fixes.push(fixer.insertTextBefore(targetFunction, 'async '));
258
286
  }
259
- const returnStatement = finalNode.parent?.type === _utils.AST_NODE_TYPES.ReturnStatement ? finalNode.parent : null;
287
+ const returnStatement = node.parent?.type === _utils.AST_NODE_TYPES.ReturnStatement ? node.parent : null;
260
288
  if (alwaysAwait && returnStatement) {
261
289
  const sourceCodeText = (0, _utils2.getSourceCode)(context).getText(returnStatement);
262
290
  const replacedText = sourceCodeText.replace('return', 'await');
263
- return fixer.replaceText(returnStatement, replacedText);
291
+ fixes.push(fixer.replaceText(returnStatement, replacedText));
292
+ } else {
293
+ fixes.push(fixer.insertTextBefore(node, 'await '));
264
294
  }
265
- return fixer.insertTextBefore(finalNode, 'await ');
295
+ return index === descriptors.length - 1 ? fixes : null;
266
296
  }
267
297
  });
268
- if (isParentArrayExpression) {
269
- pushPromiseArrayException(finalNode.loc);
270
- }
271
- }
298
+ });
272
299
  }
273
300
  };
274
301
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-jest",
3
- "version": "28.4.0",
3
+ "version": "28.6.0",
4
4
  "description": "ESLint rules for Jest",
5
5
  "keywords": [
6
6
  "eslint",
@@ -48,13 +48,6 @@
48
48
  "singleQuote": true
49
49
  },
50
50
  "release": {
51
- "branches": [
52
- "main",
53
- {
54
- "name": "next",
55
- "prerelease": true
56
- }
57
- ],
58
51
  "plugins": [
59
52
  "@semantic-release/commit-analyzer",
60
53
  "@semantic-release/release-notes-generator",
@@ -65,7 +58,7 @@
65
58
  ]
66
59
  },
67
60
  "dependencies": {
68
- "@typescript-eslint/utils": "^6.0.0"
61
+ "@typescript-eslint/utils": "^6.0.0 || ^7.0.0"
69
62
  },
70
63
  "devDependencies": {
71
64
  "@babel/cli": "^7.4.4",
@@ -83,6 +76,7 @@
83
76
  "@types/node": "^14.18.26",
84
77
  "@typescript-eslint/eslint-plugin": "^6.0.0",
85
78
  "@typescript-eslint/parser": "^6.0.0",
79
+ "@typescript-eslint/utils": "^6.0.0",
86
80
  "babel-jest": "^29.0.0",
87
81
  "babel-plugin-replace-ts-export-assignment": "^0.0.2",
88
82
  "dedent": "^1.5.0",
@@ -105,7 +99,7 @@
105
99
  "pinst": "^3.0.0",
106
100
  "prettier": "^3.0.0",
107
101
  "rimraf": "^5.0.0",
108
- "semantic-release": "^23.0.0",
102
+ "semantic-release": "^24.0.0",
109
103
  "semver": "^7.3.5",
110
104
  "strip-ansi": "^6.0.0",
111
105
  "ts-node": "^10.2.1",