eslint-plugin-jest 29.15.5 → 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.
|
@@ -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",
|
|
@@ -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
|
}
|