eslint-plugin-jest 29.15.4 → 29.16.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
|
@@ -232,9 +232,7 @@ const jest = require('eslint-plugin-jest');
|
|
|
232
232
|
|
|
233
233
|
module.exports = [
|
|
234
234
|
{
|
|
235
|
-
files: [
|
|
236
|
-
/* glob matching your test files */
|
|
237
|
-
],
|
|
235
|
+
files: [/* glob matching your test files */],
|
|
238
236
|
...jest.configs['flat/recommended'],
|
|
239
237
|
},
|
|
240
238
|
];
|
|
@@ -263,9 +261,7 @@ const jest = require('eslint-plugin-jest');
|
|
|
263
261
|
|
|
264
262
|
module.exports = [
|
|
265
263
|
{
|
|
266
|
-
files: [
|
|
267
|
-
/* glob matching your test files */
|
|
268
|
-
],
|
|
264
|
+
files: [/* glob matching your test files */],
|
|
269
265
|
...jest.configs['flat/style'],
|
|
270
266
|
},
|
|
271
267
|
];
|
|
@@ -290,9 +286,7 @@ const jest = require('eslint-plugin-jest');
|
|
|
290
286
|
|
|
291
287
|
module.exports = [
|
|
292
288
|
{
|
|
293
|
-
files: [
|
|
294
|
-
/* glob matching your test files */
|
|
295
|
-
],
|
|
289
|
+
files: [/* glob matching your test files */],
|
|
296
290
|
...jest.configs['flat/all'],
|
|
297
291
|
},
|
|
298
292
|
];
|
|
@@ -46,7 +46,7 @@ Examples of **incorrect** code for this rule with the above configuration
|
|
|
46
46
|
|
|
47
47
|
```js
|
|
48
48
|
it('is false', () => {
|
|
49
|
-
// if this has a
|
|
49
|
+
// if this has a modifier (i.e. `not.toBeFalsy`), it would be considered fine
|
|
50
50
|
expect(a).toBeFalsy();
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -41,3 +41,65 @@ expect('hello world').toBe('hello sunshine');
|
|
|
41
41
|
|
|
42
42
|
expect(new Promise(r => r(0))).rejects.toThrow('oh noes!');
|
|
43
43
|
```
|
|
44
|
+
|
|
45
|
+
## Options
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"jest/valid-expect-with-promise": [
|
|
50
|
+
"error",
|
|
51
|
+
{
|
|
52
|
+
"checkThenables": false
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `checkThenables`
|
|
59
|
+
|
|
60
|
+
Default: `false`
|
|
61
|
+
|
|
62
|
+
A
|
|
63
|
+
["Thenable"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables)
|
|
64
|
+
value is an object which has a `then` method, such as a `Promise`. Other
|
|
65
|
+
Thenables include TypeScript's built-in `PromiseLike` interface and any custom
|
|
66
|
+
object that happens to have a `.then()`.
|
|
67
|
+
|
|
68
|
+
The `checkThenables` option triggers `valid-expect-with-promise` to also
|
|
69
|
+
consider all values that satisfy the Thenable shape (a `.then()` method that
|
|
70
|
+
takes two callback parameters), not just Promises. This can be useful if your
|
|
71
|
+
code works with older `Promise` polyfills instead of the native `Promise` class,
|
|
72
|
+
or in environments where the global `Promise` is not declared by TypeScript's
|
|
73
|
+
default libraries (such as with `noLib`).
|
|
74
|
+
|
|
75
|
+
Examples of **incorrect** code when `checkThenables` is `true`:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
declare function createPromiseLike(): PromiseLike<string>;
|
|
79
|
+
|
|
80
|
+
expect(createPromiseLike()).toBe('hello sunshine');
|
|
81
|
+
|
|
82
|
+
interface MyThenable {
|
|
83
|
+
then(onFulfilled: () => void, onRejected: () => void): MyThenable;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare function createMyThenable(): MyThenable;
|
|
87
|
+
|
|
88
|
+
expect(createMyThenable()).toBe('hello sunshine');
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Examples of **correct** code when `checkThenables` is `true`:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
declare function createPromiseLike(): PromiseLike<string>;
|
|
95
|
+
|
|
96
|
+
await expect(createPromiseLike()).resolves.toBe('hello sunshine');
|
|
97
|
+
|
|
98
|
+
interface MyThenable {
|
|
99
|
+
then(onFulfilled: () => void, onRejected: () => void): MyThenable;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
declare function createMyThenable(): MyThenable;
|
|
103
|
+
|
|
104
|
+
await expect(createMyThenable()).resolves.toBe('hello sunshine');
|
|
105
|
+
```
|
package/lib/rules/utils/ts.js
CHANGED
|
@@ -4,8 +4,16 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.isBuiltinSymbolLike = isBuiltinSymbolLike;
|
|
7
|
+
exports.isThenableType = isThenableType;
|
|
7
8
|
let SymbolFlags;
|
|
8
9
|
let TypeFlags;
|
|
10
|
+
function loadTypeScriptFlags() {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
12
|
+
({
|
|
13
|
+
TypeFlags,
|
|
14
|
+
SymbolFlags
|
|
15
|
+
} = require('typescript'));
|
|
16
|
+
}
|
|
9
17
|
function isSymbolFromDefaultLibrary(program, symbol) {
|
|
10
18
|
/* istanbul ignore next */
|
|
11
19
|
const declarations = symbol.getDeclarations() ?? [];
|
|
@@ -22,11 +30,7 @@ function isSymbolFromDefaultLibrary(program, symbol) {
|
|
|
22
30
|
return false;
|
|
23
31
|
}
|
|
24
32
|
function isBuiltinSymbolLike(program, type, symbolName) {
|
|
25
|
-
|
|
26
|
-
({
|
|
27
|
-
TypeFlags,
|
|
28
|
-
SymbolFlags
|
|
29
|
-
} = require('typescript'));
|
|
33
|
+
loadTypeScriptFlags();
|
|
30
34
|
return isBuiltinSymbolLikeRecurser(program, type, subType => {
|
|
31
35
|
const symbol = subType.getSymbol();
|
|
32
36
|
if (!symbol) {
|
|
@@ -39,6 +43,33 @@ function isBuiltinSymbolLike(program, type, symbolName) {
|
|
|
39
43
|
return null;
|
|
40
44
|
});
|
|
41
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Checks if the given type is thenable: it has a `then` method that accepts
|
|
49
|
+
* both a fulfillment and a rejection callback, mirroring the check performed
|
|
50
|
+
* by typescript-eslint's `no-floating-promises` rule.
|
|
51
|
+
*/
|
|
52
|
+
function isThenableType(program, node, type) {
|
|
53
|
+
loadTypeScriptFlags();
|
|
54
|
+
const checker = program.getTypeChecker();
|
|
55
|
+
return isBuiltinSymbolLikeRecurser(program, type, subType => {
|
|
56
|
+
const then = subType.getProperty('then');
|
|
57
|
+
if (!then) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return unionParts(checker.getTypeOfSymbolAtLocation(then, node)).some(thenType => thenType.getCallSignatures().some(signature => {
|
|
61
|
+
const [onFulfilled, onRejected] = signature.getParameters();
|
|
62
|
+
return onFulfilled !== undefined && onRejected !== undefined && isFunctionParam(checker, node, onFulfilled) && isFunctionParam(checker, node, onRejected);
|
|
63
|
+
}));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function isFunctionParam(checker, node, param) {
|
|
67
|
+
const paramType = checker.getApparentType(checker.getTypeOfSymbolAtLocation(param, node));
|
|
68
|
+
return unionParts(paramType).some(subType => subType.getCallSignatures().length > 0);
|
|
69
|
+
}
|
|
70
|
+
function unionParts(type) {
|
|
71
|
+
return type.isUnion() ? type.types : [type];
|
|
72
|
+
}
|
|
42
73
|
function isBuiltinSymbolLikeRecurser(program, type, predicate) {
|
|
43
74
|
if (type.isIntersection()) {
|
|
44
75
|
return type.types.some(t => isBuiltinSymbolLikeRecurser(program, t, predicate));
|
|
@@ -18,10 +18,22 @@ var _default = exports.default = (0, _utils2.createRule)({
|
|
|
18
18
|
unneededRejectResolve: 'Subject is not a promise so {{ modifier }} is not needed'
|
|
19
19
|
},
|
|
20
20
|
type: 'suggestion',
|
|
21
|
-
schema: [
|
|
21
|
+
schema: [{
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
checkThenables: {
|
|
25
|
+
type: 'boolean'
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
additionalProperties: false
|
|
29
|
+
}]
|
|
22
30
|
},
|
|
23
|
-
defaultOptions: [
|
|
24
|
-
|
|
31
|
+
defaultOptions: [{
|
|
32
|
+
checkThenables: false
|
|
33
|
+
}],
|
|
34
|
+
create(context, [{
|
|
35
|
+
checkThenables
|
|
36
|
+
}]) {
|
|
25
37
|
const services = _utils.ESLintUtils.getParserServices(context);
|
|
26
38
|
return {
|
|
27
39
|
CallExpression(node) {
|
|
@@ -30,7 +42,12 @@ var _default = exports.default = (0, _utils2.createRule)({
|
|
|
30
42
|
return;
|
|
31
43
|
}
|
|
32
44
|
const [argument] = jestFnCall.head.node.parent.arguments;
|
|
33
|
-
const
|
|
45
|
+
const argumentType = services.getTypeAtLocation(argument);
|
|
46
|
+
|
|
47
|
+
// `isBuiltinSymbolLike` only recognises the `Promise` declared in
|
|
48
|
+
// TypeScript's default libraries, so optionally check for any thenable
|
|
49
|
+
// (e.g. custom promise types in `noLib` environments, or polyfills)
|
|
50
|
+
const isPromiseLike = (0, _utils2.isBuiltinSymbolLike)(services.program, argumentType, 'Promise') || checkThenables === true && (0, _utils2.isThenableType)(services.program, services.esTreeNodeToTSNodeMap.get(argument), argumentType);
|
|
34
51
|
const promiseModifier = jestFnCall.modifiers.find(nod => (0, _utils2.getAccessorValue)(nod) !== 'not');
|
|
35
52
|
if (isPromiseLike && !promiseModifier) {
|
|
36
53
|
context.report({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-jest",
|
|
3
|
-
"version": "29.
|
|
3
|
+
"version": "29.16.0",
|
|
4
4
|
"description": "ESLint rules for Jest",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
126
126
|
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
|
127
127
|
"jest": "*",
|
|
128
|
-
"typescript": ">=4.8.4 <
|
|
128
|
+
"typescript": ">=4.8.4 <8.0.0"
|
|
129
129
|
},
|
|
130
130
|
"peerDependenciesMeta": {
|
|
131
131
|
"@typescript-eslint/eslint-plugin": {
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
"optional": true
|
|
139
139
|
}
|
|
140
140
|
},
|
|
141
|
-
"packageManager": "yarn@4.17.
|
|
141
|
+
"packageManager": "yarn@4.17.1",
|
|
142
142
|
"engines": {
|
|
143
143
|
"node": "^20.12.0 || ^22.0.0 || >=24.0.0"
|
|
144
144
|
}
|