@depup/eslint-plugin-jest 29.15.1-depup.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/LICENSE +21 -0
- package/README.md +31 -0
- package/changes.json +10 -0
- package/docs/rules/consistent-test-it.md +96 -0
- package/docs/rules/expect-expect.md +152 -0
- package/docs/rules/max-expects.md +78 -0
- package/docs/rules/max-nested-describe.md +134 -0
- package/docs/rules/no-alias-methods.md +57 -0
- package/docs/rules/no-commented-out-tests.md +68 -0
- package/docs/rules/no-conditional-expect.md +142 -0
- package/docs/rules/no-conditional-in-test.md +123 -0
- package/docs/rules/no-confusing-set-timeout.md +64 -0
- package/docs/rules/no-deprecated-functions.md +59 -0
- package/docs/rules/no-disabled-tests.md +72 -0
- package/docs/rules/no-done-callback.md +100 -0
- package/docs/rules/no-duplicate-hooks.md +79 -0
- package/docs/rules/no-error-equal.md +37 -0
- package/docs/rules/no-export.md +52 -0
- package/docs/rules/no-focused-tests.md +67 -0
- package/docs/rules/no-hooks.md +178 -0
- package/docs/rules/no-identical-title.md +59 -0
- package/docs/rules/no-interpolation-in-snapshots.md +67 -0
- package/docs/rules/no-jasmine-globals.md +69 -0
- package/docs/rules/no-large-snapshots.md +180 -0
- package/docs/rules/no-mocks-import.md +34 -0
- package/docs/rules/no-restricted-jest-methods.md +53 -0
- package/docs/rules/no-restricted-matchers.md +64 -0
- package/docs/rules/no-standalone-expect.md +106 -0
- package/docs/rules/no-test-prefixes.md +42 -0
- package/docs/rules/no-test-return-statement.md +50 -0
- package/docs/rules/no-unnecessary-assertion.md +41 -0
- package/docs/rules/no-unneeded-async-expect-function.md +41 -0
- package/docs/rules/no-untyped-mock-factory.md +74 -0
- package/docs/rules/padding-around-after-all-blocks.md +34 -0
- package/docs/rules/padding-around-after-each-blocks.md +38 -0
- package/docs/rules/padding-around-all.md +20 -0
- package/docs/rules/padding-around-before-all-blocks.md +37 -0
- package/docs/rules/padding-around-before-each-blocks.md +38 -0
- package/docs/rules/padding-around-describe-blocks.md +42 -0
- package/docs/rules/padding-around-expect-groups.md +44 -0
- package/docs/rules/padding-around-test-blocks.md +48 -0
- package/docs/rules/prefer-called-with.md +39 -0
- package/docs/rules/prefer-comparison-matcher.md +62 -0
- package/docs/rules/prefer-each.md +58 -0
- package/docs/rules/prefer-ending-with-an-expect.md +170 -0
- package/docs/rules/prefer-equality-matcher.md +36 -0
- package/docs/rules/prefer-expect-assertions.md +230 -0
- package/docs/rules/prefer-expect-resolves.md +62 -0
- package/docs/rules/prefer-hooks-in-order.md +137 -0
- package/docs/rules/prefer-hooks-on-top.md +124 -0
- package/docs/rules/prefer-importing-jest-globals.md +85 -0
- package/docs/rules/prefer-jest-mocked.md +40 -0
- package/docs/rules/prefer-lowercase-title.md +128 -0
- package/docs/rules/prefer-mock-promise-shorthand.md +41 -0
- package/docs/rules/prefer-mock-return-shorthand.md +50 -0
- package/docs/rules/prefer-snapshot-hint.md +192 -0
- package/docs/rules/prefer-spy-on.md +46 -0
- package/docs/rules/prefer-strict-equal.md +29 -0
- package/docs/rules/prefer-to-be.md +63 -0
- package/docs/rules/prefer-to-contain.md +53 -0
- package/docs/rules/prefer-to-have-been-called-times.md +43 -0
- package/docs/rules/prefer-to-have-been-called.md +35 -0
- package/docs/rules/prefer-to-have-length.md +41 -0
- package/docs/rules/prefer-todo.md +33 -0
- package/docs/rules/require-hook.md +187 -0
- package/docs/rules/require-to-throw-message.md +43 -0
- package/docs/rules/require-top-level-describe.md +86 -0
- package/docs/rules/unbound-method.md +61 -0
- package/docs/rules/valid-describe-callback.md +69 -0
- package/docs/rules/valid-expect-in-promise.md +79 -0
- package/docs/rules/valid-expect-with-promise.md +43 -0
- package/docs/rules/valid-expect.md +155 -0
- package/docs/rules/valid-mock-module-path.md +72 -0
- package/docs/rules/valid-title.md +319 -0
- package/index.d.ts +28 -0
- package/lib/globals.json +15 -0
- package/lib/index.js +85 -0
- package/lib/rules/consistent-test-it.js +101 -0
- package/lib/rules/expect-expect.js +102 -0
- package/lib/rules/max-expects.js +70 -0
- package/lib/rules/max-nested-describe.js +59 -0
- package/lib/rules/no-alias-methods.js +62 -0
- package/lib/rules/no-commented-out-tests.js +44 -0
- package/lib/rules/no-conditional-expect.js +80 -0
- package/lib/rules/no-conditional-in-test.js +63 -0
- package/lib/rules/no-confusing-set-timeout.js +61 -0
- package/lib/rules/no-deprecated-functions.js +81 -0
- package/lib/rules/no-disabled-tests.js +57 -0
- package/lib/rules/no-done-callback.js +120 -0
- package/lib/rules/no-duplicate-hooks.js +52 -0
- package/lib/rules/no-error-equal.js +41 -0
- package/lib/rules/no-export.js +61 -0
- package/lib/rules/no-focused-tests.js +63 -0
- package/lib/rules/no-hooks.js +51 -0
- package/lib/rules/no-identical-title.js +73 -0
- package/lib/rules/no-interpolation-in-snapshots.js +43 -0
- package/lib/rules/no-jasmine-globals.js +140 -0
- package/lib/rules/no-large-snapshots.js +100 -0
- package/lib/rules/no-mocks-import.js +46 -0
- package/lib/rules/no-restricted-jest-methods.js +53 -0
- package/lib/rules/no-restricted-matchers.js +60 -0
- package/lib/rules/no-standalone-expect.js +114 -0
- package/lib/rules/no-test-prefixes.js +48 -0
- package/lib/rules/no-test-return-statement.js +62 -0
- package/lib/rules/no-unnecessary-assertion.js +87 -0
- package/lib/rules/no-unneeded-async-expect-function.js +57 -0
- package/lib/rules/no-untyped-mock-factory.js +61 -0
- package/lib/rules/padding-around-after-all-blocks.js +17 -0
- package/lib/rules/padding-around-after-each-blocks.js +17 -0
- package/lib/rules/padding-around-all.js +15 -0
- package/lib/rules/padding-around-before-all-blocks.js +17 -0
- package/lib/rules/padding-around-before-each-blocks.js +17 -0
- package/lib/rules/padding-around-describe-blocks.js +17 -0
- package/lib/rules/padding-around-expect-groups.js +21 -0
- package/lib/rules/padding-around-test-blocks.js +17 -0
- package/lib/rules/prefer-called-with.js +47 -0
- package/lib/rules/prefer-comparison-matcher.js +111 -0
- package/lib/rules/prefer-each.js +77 -0
- package/lib/rules/prefer-ending-with-an-expect.js +102 -0
- package/lib/rules/prefer-equality-matcher.js +87 -0
- package/lib/rules/prefer-expect-assertions.js +237 -0
- package/lib/rules/prefer-expect-resolves.js +47 -0
- package/lib/rules/prefer-hooks-in-order.js +67 -0
- package/lib/rules/prefer-hooks-on-top.js +41 -0
- package/lib/rules/prefer-importing-jest-globals.js +126 -0
- package/lib/rules/prefer-jest-mocked.js +66 -0
- package/lib/rules/prefer-lowercase-title.js +128 -0
- package/lib/rules/prefer-mock-promise-shorthand.js +88 -0
- package/lib/rules/prefer-mock-return-shorthand.js +122 -0
- package/lib/rules/prefer-snapshot-hint.js +111 -0
- package/lib/rules/prefer-spy-on.js +80 -0
- package/lib/rules/prefer-strict-equal.js +46 -0
- package/lib/rules/prefer-to-be.js +98 -0
- package/lib/rules/prefer-to-contain.js +82 -0
- package/lib/rules/prefer-to-have-been-called-times.js +70 -0
- package/lib/rules/prefer-to-have-been-called.js +59 -0
- package/lib/rules/prefer-to-have-length.js +57 -0
- package/lib/rules/prefer-todo.js +72 -0
- package/lib/rules/require-hook.js +93 -0
- package/lib/rules/require-to-throw-message.js +45 -0
- package/lib/rules/require-top-level-describe.js +86 -0
- package/lib/rules/unbound-method.js +86 -0
- package/lib/rules/utils/accessors.js +127 -0
- package/lib/rules/utils/ast-utils.js +59 -0
- package/lib/rules/utils/detectJestVersion.js +24 -0
- package/lib/rules/utils/followTypeAssertionChain.js +10 -0
- package/lib/rules/utils/index.js +71 -0
- package/lib/rules/utils/misc.js +168 -0
- package/lib/rules/utils/padding.js +308 -0
- package/lib/rules/utils/parseJestFnCall.js +325 -0
- package/lib/rules/utils/ts.js +73 -0
- package/lib/rules/valid-describe-callback.js +94 -0
- package/lib/rules/valid-expect-in-promise.js +311 -0
- package/lib/rules/valid-expect-with-promise.js +54 -0
- package/lib/rules/valid-expect.js +302 -0
- package/lib/rules/valid-mock-module-path.js +90 -0
- package/lib/rules/valid-title.js +252 -0
- package/package.json +164 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isBuiltinSymbolLike = isBuiltinSymbolLike;
|
|
7
|
+
let SymbolFlags;
|
|
8
|
+
let TypeFlags;
|
|
9
|
+
function isSymbolFromDefaultLibrary(program, symbol) {
|
|
10
|
+
/* istanbul ignore next */
|
|
11
|
+
const declarations = symbol.getDeclarations() ?? [];
|
|
12
|
+
for (const declaration of declarations) {
|
|
13
|
+
const sourceFile = declaration.getSourceFile();
|
|
14
|
+
|
|
15
|
+
/* istanbul ignore else */
|
|
16
|
+
if (program.isSourceFileDefaultLibrary(sourceFile)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* istanbul ignore next */
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
function isBuiltinSymbolLike(program, type, symbolName) {
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
|
+
({
|
|
27
|
+
TypeFlags,
|
|
28
|
+
SymbolFlags
|
|
29
|
+
} = require('typescript'));
|
|
30
|
+
return isBuiltinSymbolLikeRecurser(program, type, subType => {
|
|
31
|
+
const symbol = subType.getSymbol();
|
|
32
|
+
if (!symbol) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const actualSymbolName = symbol.getName();
|
|
36
|
+
if (actualSymbolName === symbolName && isSymbolFromDefaultLibrary(program, symbol)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function isBuiltinSymbolLikeRecurser(program, type, predicate) {
|
|
43
|
+
if (type.isIntersection()) {
|
|
44
|
+
return type.types.some(t => isBuiltinSymbolLikeRecurser(program, t, predicate));
|
|
45
|
+
}
|
|
46
|
+
if (type.isUnion()) {
|
|
47
|
+
return type.types.every(t => isBuiltinSymbolLikeRecurser(program, t, predicate));
|
|
48
|
+
}
|
|
49
|
+
if (isTypeParameter(type)) {
|
|
50
|
+
const t = type.getConstraint();
|
|
51
|
+
if (t) {
|
|
52
|
+
return isBuiltinSymbolLikeRecurser(program, t, predicate);
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const predicateResult = predicate(type);
|
|
57
|
+
if (typeof predicateResult === 'boolean') {
|
|
58
|
+
return predicateResult;
|
|
59
|
+
}
|
|
60
|
+
const symbol = type.getSymbol();
|
|
61
|
+
if (symbol && symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
|
|
62
|
+
const checker = program.getTypeChecker();
|
|
63
|
+
for (const baseType of checker.getBaseTypes(type)) {
|
|
64
|
+
if (isBuiltinSymbolLikeRecurser(program, baseType, predicate)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
function isTypeParameter(type) {
|
|
72
|
+
return (type.flags & TypeFlags.TypeParameter) !== 0;
|
|
73
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
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 paramsLocation = params => {
|
|
10
|
+
const [first] = params;
|
|
11
|
+
const last = params[params.length - 1];
|
|
12
|
+
return {
|
|
13
|
+
start: first.loc.start,
|
|
14
|
+
end: last.loc.end
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
18
|
+
name: __filename,
|
|
19
|
+
meta: {
|
|
20
|
+
type: 'problem',
|
|
21
|
+
docs: {
|
|
22
|
+
description: 'Enforce valid `describe()` callback'
|
|
23
|
+
},
|
|
24
|
+
messages: {
|
|
25
|
+
nameAndCallback: 'Describe requires name and callback arguments',
|
|
26
|
+
secondArgumentMustBeFunction: 'Second argument must be function',
|
|
27
|
+
noAsyncDescribeCallback: 'No async describe callback',
|
|
28
|
+
unexpectedDescribeArgument: 'Unexpected argument(s) in describe callback',
|
|
29
|
+
unexpectedReturnInDescribe: 'Unexpected return statement in describe callback'
|
|
30
|
+
},
|
|
31
|
+
schema: []
|
|
32
|
+
},
|
|
33
|
+
defaultOptions: [],
|
|
34
|
+
create(context) {
|
|
35
|
+
return {
|
|
36
|
+
CallExpression(node) {
|
|
37
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
38
|
+
if (jestFnCall?.type !== 'describe') {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (node.arguments.length < 1) {
|
|
42
|
+
context.report({
|
|
43
|
+
messageId: 'nameAndCallback',
|
|
44
|
+
loc: node.loc
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const [, callback] = node.arguments;
|
|
49
|
+
if (!callback) {
|
|
50
|
+
context.report({
|
|
51
|
+
messageId: 'nameAndCallback',
|
|
52
|
+
loc: paramsLocation(node.arguments)
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!(0, _utils2.isFunction)(callback)) {
|
|
57
|
+
context.report({
|
|
58
|
+
messageId: 'secondArgumentMustBeFunction',
|
|
59
|
+
loc: paramsLocation(node.arguments)
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (callback.async) {
|
|
64
|
+
context.report({
|
|
65
|
+
messageId: 'noAsyncDescribeCallback',
|
|
66
|
+
node: callback
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (jestFnCall.members.every(s => (0, _utils2.getAccessorValue)(s) !== 'each') && callback.params.length) {
|
|
70
|
+
context.report({
|
|
71
|
+
messageId: 'unexpectedDescribeArgument',
|
|
72
|
+
loc: paramsLocation(callback.params)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (callback.body.type === _utils.AST_NODE_TYPES.CallExpression) {
|
|
76
|
+
context.report({
|
|
77
|
+
messageId: 'unexpectedReturnInDescribe',
|
|
78
|
+
node: callback
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (callback.body.type === _utils.AST_NODE_TYPES.BlockStatement) {
|
|
82
|
+
callback.body.body.forEach(node => {
|
|
83
|
+
if (node.type === _utils.AST_NODE_TYPES.ReturnStatement) {
|
|
84
|
+
context.report({
|
|
85
|
+
messageId: 'unexpectedReturnInDescribe',
|
|
86
|
+
node
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
});
|
|
@@ -0,0 +1,311 @@
|
|
|
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 isPromiseChainCall = node => {
|
|
10
|
+
if (node.type === _utils.AST_NODE_TYPES.CallExpression && node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.property)) {
|
|
11
|
+
// promise methods should have at least 1 argument
|
|
12
|
+
if (node.arguments.length === 0) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
switch ((0, _utils2.getAccessorValue)(node.callee.property)) {
|
|
16
|
+
case 'then':
|
|
17
|
+
return node.arguments.length < 3;
|
|
18
|
+
case 'catch':
|
|
19
|
+
case 'finally':
|
|
20
|
+
return node.arguments.length < 2;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
25
|
+
const isTestCaseCallWithCallbackArg = (node, context) => {
|
|
26
|
+
const jestCallFn = (0, _utils2.parseJestFnCall)(node, context);
|
|
27
|
+
if (jestCallFn?.type !== 'test') {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const isJestEach = jestCallFn.members.some(s => (0, _utils2.getAccessorValue)(s) === 'each');
|
|
31
|
+
if (isJestEach && node.callee.type !== _utils.AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
32
|
+
// isJestEach but not a TaggedTemplateExpression, so this must be
|
|
33
|
+
// the `jest.each([])()` syntax which this rule doesn't support due
|
|
34
|
+
// to its complexity (see jest-community/eslint-plugin-jest#710)
|
|
35
|
+
// so we return true to trigger bailout
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const [, callback] = node.arguments;
|
|
39
|
+
const callbackArgIndex = Number(isJestEach);
|
|
40
|
+
return callback && (0, _utils2.isFunction)(callback) && callback.params.length === 1 + callbackArgIndex;
|
|
41
|
+
};
|
|
42
|
+
const isPromiseMethodThatUsesValue = (node, identifier) => {
|
|
43
|
+
const {
|
|
44
|
+
name
|
|
45
|
+
} = identifier;
|
|
46
|
+
if (node.argument === null) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (node.argument.type === _utils.AST_NODE_TYPES.CallExpression && node.argument.arguments.length > 0) {
|
|
50
|
+
const nodeName = (0, _utils2.getNodeName)(node.argument);
|
|
51
|
+
if (['Promise.all', 'Promise.allSettled'].includes(nodeName)) {
|
|
52
|
+
const [firstArg] = node.argument.arguments;
|
|
53
|
+
if (firstArg.type === _utils.AST_NODE_TYPES.ArrayExpression && firstArg.elements.some(nod => nod && (0, _utils2.isIdentifier)(nod, name))) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (['Promise.resolve', 'Promise.reject'].includes(nodeName) && node.argument.arguments.length === 1) {
|
|
58
|
+
return (0, _utils2.isIdentifier)(node.argument.arguments[0], name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return (0, _utils2.isIdentifier)(node.argument, name);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Attempts to determine if the runtime value represented by the given `identifier`
|
|
66
|
+
* is `await`ed within the given array of elements
|
|
67
|
+
*/
|
|
68
|
+
const isValueAwaitedInElements = (name, elements) => {
|
|
69
|
+
for (const element of elements) {
|
|
70
|
+
if (element?.type === _utils.AST_NODE_TYPES.AwaitExpression && (0, _utils2.isIdentifier)(element.argument, name)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (element?.type === _utils.AST_NODE_TYPES.ArrayExpression && isValueAwaitedInElements(name, element.elements)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Attempts to determine if the runtime value represented by the given `identifier`
|
|
82
|
+
* is `await`ed as an argument along the given call expression
|
|
83
|
+
*/
|
|
84
|
+
const isValueAwaitedInArguments = (name, call) => {
|
|
85
|
+
let node = call;
|
|
86
|
+
while (node) {
|
|
87
|
+
if (node.type === _utils.AST_NODE_TYPES.CallExpression) {
|
|
88
|
+
if (isValueAwaitedInElements(name, node.arguments)) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
node = node.callee;
|
|
92
|
+
}
|
|
93
|
+
if (node.type !== _utils.AST_NODE_TYPES.MemberExpression) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
node = node.object;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
};
|
|
100
|
+
const getLeftMostCallExpression = call => {
|
|
101
|
+
let leftMostCallExpression = call;
|
|
102
|
+
let node = call;
|
|
103
|
+
while (node) {
|
|
104
|
+
if (node.type === _utils.AST_NODE_TYPES.CallExpression) {
|
|
105
|
+
leftMostCallExpression = node;
|
|
106
|
+
node = node.callee;
|
|
107
|
+
}
|
|
108
|
+
if (node.type !== _utils.AST_NODE_TYPES.MemberExpression) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
node = node.object;
|
|
112
|
+
}
|
|
113
|
+
return leftMostCallExpression;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Attempts to determine if the runtime value represented by the given `identifier`
|
|
118
|
+
* is `await`ed or `return`ed within the given `body` of statements
|
|
119
|
+
*/
|
|
120
|
+
const isValueAwaitedOrReturned = (identifier, body, context) => {
|
|
121
|
+
const {
|
|
122
|
+
name
|
|
123
|
+
} = identifier;
|
|
124
|
+
for (const node of body) {
|
|
125
|
+
// skip all nodes that are before this identifier, because they'd probably
|
|
126
|
+
// be affecting a different runtime value (e.g. due to reassignment)
|
|
127
|
+
if (node.range[0] <= identifier.range[0]) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (node.type === _utils.AST_NODE_TYPES.ReturnStatement) {
|
|
131
|
+
return isPromiseMethodThatUsesValue(node, identifier);
|
|
132
|
+
}
|
|
133
|
+
if (node.type === _utils.AST_NODE_TYPES.ExpressionStatement) {
|
|
134
|
+
// it's possible that we're awaiting the value as an argument
|
|
135
|
+
if (node.expression.type === _utils.AST_NODE_TYPES.CallExpression) {
|
|
136
|
+
if (isValueAwaitedInArguments(name, node.expression)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
const leftMostCall = getLeftMostCallExpression(node.expression);
|
|
140
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node.expression, context);
|
|
141
|
+
if (jestFnCall?.type === 'expect' && leftMostCall.arguments.length > 0 && (0, _utils2.isIdentifier)(leftMostCall.arguments[0], name)) {
|
|
142
|
+
if (jestFnCall.members.some(m => {
|
|
143
|
+
const v = (0, _utils2.getAccessorValue)(m);
|
|
144
|
+
return v === _utils2.ModifierName.resolves || v === _utils2.ModifierName.rejects;
|
|
145
|
+
})) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (node.expression.type === _utils.AST_NODE_TYPES.AwaitExpression && isPromiseMethodThatUsesValue(node.expression, identifier)) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// (re)assignment changes the runtime value, so if we've not found an
|
|
155
|
+
// await or return already we act as if we've reached the end of the body
|
|
156
|
+
if (node.expression.type === _utils.AST_NODE_TYPES.AssignmentExpression) {
|
|
157
|
+
// unless we're assigning to the same identifier, in which case
|
|
158
|
+
// we might be chaining off the existing promise value
|
|
159
|
+
if ((0, _utils2.isIdentifier)(node.expression.left, name) && (0, _utils2.getNodeName)(node.expression.right)?.startsWith(`${name}.`) && isPromiseChainCall(node.expression.right)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (node.type === _utils.AST_NODE_TYPES.BlockStatement && isValueAwaitedOrReturned(identifier, node.body, context)) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
};
|
|
171
|
+
const findFirstBlockBodyUp = node => {
|
|
172
|
+
let parent = node;
|
|
173
|
+
while (parent) {
|
|
174
|
+
if (parent.type === _utils.AST_NODE_TYPES.BlockStatement) {
|
|
175
|
+
return parent.body;
|
|
176
|
+
}
|
|
177
|
+
parent = parent.parent;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* istanbul ignore next */
|
|
181
|
+
throw new Error(`Could not find BlockStatement - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
|
|
182
|
+
};
|
|
183
|
+
const isDirectlyWithinTestCaseCall = (node, context) => {
|
|
184
|
+
let parent = node;
|
|
185
|
+
while (parent) {
|
|
186
|
+
if ((0, _utils2.isFunction)(parent)) {
|
|
187
|
+
parent = parent.parent;
|
|
188
|
+
return parent.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isTypeOfJestFnCall)(parent, context, ['test']);
|
|
189
|
+
}
|
|
190
|
+
parent = parent.parent;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
};
|
|
194
|
+
const isVariableAwaitedOrReturned = (variable, context) => {
|
|
195
|
+
const body = findFirstBlockBodyUp(variable);
|
|
196
|
+
|
|
197
|
+
// it's pretty much impossible for us to track destructuring assignments,
|
|
198
|
+
// so we return true to bailout gracefully
|
|
199
|
+
if (!(0, _utils2.isIdentifier)(variable.id)) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
return isValueAwaitedOrReturned(variable.id, body, context);
|
|
203
|
+
};
|
|
204
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
205
|
+
name: __filename,
|
|
206
|
+
meta: {
|
|
207
|
+
docs: {
|
|
208
|
+
description: 'Require promises that have expectations in their chain to be valid'
|
|
209
|
+
},
|
|
210
|
+
messages: {
|
|
211
|
+
expectInFloatingPromise: 'This promise should either be returned or awaited to ensure the expects in its chain are called'
|
|
212
|
+
},
|
|
213
|
+
type: 'suggestion',
|
|
214
|
+
schema: []
|
|
215
|
+
},
|
|
216
|
+
defaultOptions: [],
|
|
217
|
+
create(context) {
|
|
218
|
+
let inTestCaseWithDoneCallback = false;
|
|
219
|
+
// an array of booleans representing each promise chain we enter, with the
|
|
220
|
+
// boolean value representing if we think a given chain contains an expect
|
|
221
|
+
// in it's body.
|
|
222
|
+
//
|
|
223
|
+
// since we only care about the inner-most chain, we represent the state in
|
|
224
|
+
// reverse with the inner-most being the first item, as that makes it
|
|
225
|
+
// slightly less code to assign to by not needing to know the length
|
|
226
|
+
const chains = [];
|
|
227
|
+
return {
|
|
228
|
+
CallExpression(node) {
|
|
229
|
+
// there are too many ways that the done argument could be used with
|
|
230
|
+
// promises that contain expect that would make the promise safe for us
|
|
231
|
+
if (isTestCaseCallWithCallbackArg(node, context)) {
|
|
232
|
+
inTestCaseWithDoneCallback = true;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// if this call expression is a promise chain, add it to the stack with
|
|
237
|
+
// value of "false", as we assume there are no expect calls initially
|
|
238
|
+
if (isPromiseChainCall(node)) {
|
|
239
|
+
chains.unshift(false);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// if we're within a promise chain, and this call expression looks like
|
|
244
|
+
// an expect call, mark the deepest chain as having an expect call
|
|
245
|
+
if (chains.length > 0 && (0, _utils2.isTypeOfJestFnCall)(node, context, ['expect'])) {
|
|
246
|
+
chains[0] = true;
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
'CallExpression:exit'(node) {
|
|
250
|
+
// there are too many ways that the "done" argument could be used to
|
|
251
|
+
// make promises containing expects safe in a test for us to be able to
|
|
252
|
+
// accurately check, so we just bail out completely if it's present
|
|
253
|
+
if (inTestCaseWithDoneCallback) {
|
|
254
|
+
if ((0, _utils2.isTypeOfJestFnCall)(node, context, ['test'])) {
|
|
255
|
+
inTestCaseWithDoneCallback = false;
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (!isPromiseChainCall(node)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// since we're exiting this call expression (which is a promise chain)
|
|
264
|
+
// we remove it from the stack of chains, since we're unwinding
|
|
265
|
+
const hasExpectCall = chains.shift();
|
|
266
|
+
|
|
267
|
+
// if the promise chain we're exiting doesn't contain an expect,
|
|
268
|
+
// then we don't need to check it for anything
|
|
269
|
+
if (!hasExpectCall) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const {
|
|
273
|
+
parent
|
|
274
|
+
} = (0, _utils2.findTopMostCallExpression)(node);
|
|
275
|
+
|
|
276
|
+
// if our parent is not directly within the test case, we stop checking
|
|
277
|
+
// because we're most likely in the body of a function being defined
|
|
278
|
+
// within the test, which we can't track
|
|
279
|
+
if (!isDirectlyWithinTestCaseCall(parent, context)) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
switch (parent.type) {
|
|
283
|
+
case _utils.AST_NODE_TYPES.VariableDeclarator:
|
|
284
|
+
{
|
|
285
|
+
if (isVariableAwaitedOrReturned(parent, context)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case _utils.AST_NODE_TYPES.AssignmentExpression:
|
|
291
|
+
{
|
|
292
|
+
if (parent.left.type === _utils.AST_NODE_TYPES.Identifier && isValueAwaitedOrReturned(parent.left, findFirstBlockBodyUp(parent), context)) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case _utils.AST_NODE_TYPES.ExpressionStatement:
|
|
298
|
+
break;
|
|
299
|
+
case _utils.AST_NODE_TYPES.ReturnStatement:
|
|
300
|
+
case _utils.AST_NODE_TYPES.AwaitExpression:
|
|
301
|
+
default:
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
context.report({
|
|
305
|
+
messageId: 'expectInFloatingPromise',
|
|
306
|
+
node: parent
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
10
|
+
name: __filename,
|
|
11
|
+
meta: {
|
|
12
|
+
docs: {
|
|
13
|
+
description: 'Require that `resolve` and `reject` modifiers are present (and only) for promise-like types',
|
|
14
|
+
requiresTypeChecking: true
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
poorlyExpectedPromise: 'Subject is a promise so resolve or reject should be used',
|
|
18
|
+
unneededRejectResolve: 'Subject is not a promise so {{ modifier }} is not needed'
|
|
19
|
+
},
|
|
20
|
+
type: 'suggestion',
|
|
21
|
+
schema: []
|
|
22
|
+
},
|
|
23
|
+
defaultOptions: [],
|
|
24
|
+
create(context) {
|
|
25
|
+
const services = _utils.ESLintUtils.getParserServices(context);
|
|
26
|
+
return {
|
|
27
|
+
CallExpression(node) {
|
|
28
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
29
|
+
if (jestFnCall?.type !== 'expect' || jestFnCall.head.node.parent.type !== _utils.AST_NODE_TYPES.CallExpression) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const [argument] = jestFnCall.head.node.parent.arguments;
|
|
33
|
+
const isPromiseLike = (0, _utils2.isBuiltinSymbolLike)(services.program, services.getTypeAtLocation(argument), 'Promise');
|
|
34
|
+
const promiseModifier = jestFnCall.modifiers.find(nod => (0, _utils2.getAccessorValue)(nod) !== 'not');
|
|
35
|
+
if (isPromiseLike && !promiseModifier) {
|
|
36
|
+
context.report({
|
|
37
|
+
messageId: 'poorlyExpectedPromise',
|
|
38
|
+
node
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!isPromiseLike && promiseModifier) {
|
|
43
|
+
context.report({
|
|
44
|
+
messageId: 'unneededRejectResolve',
|
|
45
|
+
data: {
|
|
46
|
+
modifier: (0, _utils2.getAccessorValue)(promiseModifier)
|
|
47
|
+
},
|
|
48
|
+
node: promiseModifier
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
});
|