eslint-plugin-jest 28.5.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 +9 -3
- package/docs/rules/prefer-jest-mocked.md +38 -0
- package/lib/index.js +1 -1
- package/lib/rules/prefer-jest-mocked.js +66 -0
- package/lib/rules/valid-expect.js +42 -15
- package/package.json +2 -9
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
|
-
🎨
|
|
311
|
-
|
|
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
|
-
💡
|
|
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(
|
|
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
|
|
31
|
+
const findFirstFunctionExpression = ({
|
|
32
32
|
parent
|
|
33
33
|
}) => {
|
|
34
34
|
if (!parent) {
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
|
-
return (0, _utils2.isFunction)(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
|
|
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:
|
|
271
|
+
loc: node.loc,
|
|
250
272
|
data: {
|
|
251
273
|
orReturned
|
|
252
274
|
},
|
|
253
|
-
messageId
|
|
275
|
+
messageId,
|
|
254
276
|
node,
|
|
255
277
|
fix(fixer) {
|
|
256
|
-
|
|
257
|
-
|
|
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 =
|
|
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
|
-
|
|
291
|
+
fixes.push(fixer.replaceText(returnStatement, replacedText));
|
|
292
|
+
} else {
|
|
293
|
+
fixes.push(fixer.insertTextBefore(node, 'await '));
|
|
264
294
|
}
|
|
265
|
-
return
|
|
295
|
+
return index === descriptors.length - 1 ? fixes : null;
|
|
266
296
|
}
|
|
267
297
|
});
|
|
268
|
-
|
|
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.
|
|
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",
|
|
@@ -106,7 +99,7 @@
|
|
|
106
99
|
"pinst": "^3.0.0",
|
|
107
100
|
"prettier": "^3.0.0",
|
|
108
101
|
"rimraf": "^5.0.0",
|
|
109
|
-
"semantic-release": "^
|
|
102
|
+
"semantic-release": "^24.0.0",
|
|
110
103
|
"semver": "^7.3.5",
|
|
111
104
|
"strip-ansi": "^6.0.0",
|
|
112
105
|
"ts-node": "^10.2.1",
|