@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,102 @@
|
|
|
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
|
+
/*
|
|
10
|
+
* This implementation is adapted from eslint-plugin-jasmine.
|
|
11
|
+
* MIT license, Remco Haszing.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if node names returned by getNodeName matches any of the given star patterns
|
|
16
|
+
* Pattern examples:
|
|
17
|
+
* request.*.expect
|
|
18
|
+
* request.**.expect
|
|
19
|
+
* request.**.expect*
|
|
20
|
+
*/
|
|
21
|
+
function matchesAssertFunctionName(nodeName, patterns) {
|
|
22
|
+
return patterns.some(p => new RegExp(`^${p.split('.').map(x => {
|
|
23
|
+
if (x === '**') {
|
|
24
|
+
return '[a-z\\d\\.]*';
|
|
25
|
+
}
|
|
26
|
+
return x.replace(/\*/gu, '[a-z\\d]*');
|
|
27
|
+
}).join('\\.')}(\\.|$)`, 'ui').test(nodeName));
|
|
28
|
+
}
|
|
29
|
+
function getLastStatement(fn) {
|
|
30
|
+
if (fn.body.type === _utils.AST_NODE_TYPES.BlockStatement) {
|
|
31
|
+
if (fn.body.body.length === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const lastStatement = fn.body.body[fn.body.body.length - 1];
|
|
35
|
+
if (lastStatement.type === _utils.AST_NODE_TYPES.ExpressionStatement) {
|
|
36
|
+
return lastStatement.expression;
|
|
37
|
+
}
|
|
38
|
+
return lastStatement;
|
|
39
|
+
}
|
|
40
|
+
return fn.body;
|
|
41
|
+
}
|
|
42
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
43
|
+
name: __filename,
|
|
44
|
+
meta: {
|
|
45
|
+
docs: {
|
|
46
|
+
description: 'Prefer having the last statement in a test be an assertion'
|
|
47
|
+
},
|
|
48
|
+
messages: {
|
|
49
|
+
mustEndWithExpect: 'Tests should end with an assertion'
|
|
50
|
+
},
|
|
51
|
+
schema: [{
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
assertFunctionNames: {
|
|
55
|
+
type: 'array',
|
|
56
|
+
items: {
|
|
57
|
+
type: 'string'
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
additionalTestBlockFunctions: {
|
|
61
|
+
type: 'array',
|
|
62
|
+
items: {
|
|
63
|
+
type: 'string'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
additionalProperties: false
|
|
68
|
+
}],
|
|
69
|
+
type: 'suggestion'
|
|
70
|
+
},
|
|
71
|
+
defaultOptions: [{
|
|
72
|
+
assertFunctionNames: ['expect'],
|
|
73
|
+
additionalTestBlockFunctions: []
|
|
74
|
+
}],
|
|
75
|
+
create(context, [{
|
|
76
|
+
assertFunctionNames = ['expect'],
|
|
77
|
+
additionalTestBlockFunctions = []
|
|
78
|
+
}]) {
|
|
79
|
+
return {
|
|
80
|
+
CallExpression(node) {
|
|
81
|
+
const name = (0, _utils2.getNodeName)(node.callee) ?? '';
|
|
82
|
+
if (!(0, _utils2.isTypeOfJestFnCall)(node, context, ['test']) && !additionalTestBlockFunctions.includes(name)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (node.arguments.length < 2 || !(0, _utils2.isFunction)(node.arguments[1])) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let lastStatement = getLastStatement(node.arguments[1]);
|
|
89
|
+
if (lastStatement?.type === _utils.AST_NODE_TYPES.AwaitExpression) {
|
|
90
|
+
lastStatement = lastStatement.argument;
|
|
91
|
+
}
|
|
92
|
+
if (lastStatement?.type === _utils.AST_NODE_TYPES.CallExpression && ((0, _utils2.isTypeOfJestFnCall)(lastStatement, context, ['expect']) || matchesAssertFunctionName((0, _utils2.getNodeName)(lastStatement.callee), assertFunctionNames))) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
context.report({
|
|
96
|
+
messageId: 'mustEndWithExpect',
|
|
97
|
+
node: node.callee
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
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: 'Suggest using the built-in equality matchers'
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
useEqualityMatcher: 'Prefer using one of the equality matchers instead',
|
|
17
|
+
suggestEqualityMatcher: 'Use `{{ equalityMatcher }}`'
|
|
18
|
+
},
|
|
19
|
+
hasSuggestions: true,
|
|
20
|
+
type: 'suggestion',
|
|
21
|
+
schema: []
|
|
22
|
+
},
|
|
23
|
+
defaultOptions: [],
|
|
24
|
+
create(context) {
|
|
25
|
+
return {
|
|
26
|
+
CallExpression(node) {
|
|
27
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
28
|
+
if (jestFnCall?.type !== 'expect' || jestFnCall.args.length === 0) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const {
|
|
32
|
+
parent: expect
|
|
33
|
+
} = jestFnCall.head.node;
|
|
34
|
+
if (expect.type !== _utils.AST_NODE_TYPES.CallExpression) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const {
|
|
38
|
+
arguments: [comparison],
|
|
39
|
+
range: [, expectCallEnd]
|
|
40
|
+
} = expect;
|
|
41
|
+
const {
|
|
42
|
+
matcher
|
|
43
|
+
} = jestFnCall;
|
|
44
|
+
const matcherArg = (0, _utils2.getFirstMatcherArg)(jestFnCall);
|
|
45
|
+
if (comparison.type !== _utils.AST_NODE_TYPES.BinaryExpression || comparison.operator !== '===' && comparison.operator !== '!==' || !_utils2.EqualityMatcher.hasOwnProperty((0, _utils2.getAccessorValue)(matcher)) || !(0, _utils2.isBooleanLiteral)(matcherArg)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const matcherValue = matcherArg.value;
|
|
49
|
+
const [modifier] = jestFnCall.modifiers;
|
|
50
|
+
const hasNot = jestFnCall.modifiers.some(nod => (0, _utils2.getAccessorValue)(nod) === 'not');
|
|
51
|
+
|
|
52
|
+
// we need to negate the expectation if the current expected
|
|
53
|
+
// value is itself negated by the "not" modifier
|
|
54
|
+
const addNotModifier = (comparison.operator === '!==' ? !matcherValue : matcherValue) === hasNot;
|
|
55
|
+
const buildFixer = equalityMatcher => fixer => {
|
|
56
|
+
const {
|
|
57
|
+
sourceCode
|
|
58
|
+
} = context;
|
|
59
|
+
|
|
60
|
+
// preserve the existing modifier if it's not a negation
|
|
61
|
+
let modifierText = modifier && (0, _utils2.getAccessorValue)(modifier) !== 'not' ? `.${(0, _utils2.getAccessorValue)(modifier)}` : '';
|
|
62
|
+
if (addNotModifier) {
|
|
63
|
+
modifierText += `.${_utils2.ModifierName.not}`;
|
|
64
|
+
}
|
|
65
|
+
return [
|
|
66
|
+
// replace the comparison argument with the left-hand side of the comparison
|
|
67
|
+
fixer.replaceText(comparison, sourceCode.getText(comparison.left)),
|
|
68
|
+
// replace the current matcher & modifier with the preferred matcher
|
|
69
|
+
fixer.replaceTextRange([expectCallEnd, matcher.parent.range[1]], `${modifierText}.${equalityMatcher}`),
|
|
70
|
+
// replace the matcher argument with the right-hand side of the comparison
|
|
71
|
+
fixer.replaceText(matcherArg, sourceCode.getText(comparison.right))];
|
|
72
|
+
};
|
|
73
|
+
context.report({
|
|
74
|
+
messageId: 'useEqualityMatcher',
|
|
75
|
+
suggest: ['toBe', 'toEqual', 'toStrictEqual'].map(equalityMatcher => ({
|
|
76
|
+
messageId: 'suggestEqualityMatcher',
|
|
77
|
+
data: {
|
|
78
|
+
equalityMatcher
|
|
79
|
+
},
|
|
80
|
+
fix: buildFixer(equalityMatcher)
|
|
81
|
+
})),
|
|
82
|
+
node: matcher
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
});
|
|
@@ -0,0 +1,237 @@
|
|
|
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 isFirstStatement = node => {
|
|
10
|
+
let parent = node;
|
|
11
|
+
while (parent) {
|
|
12
|
+
if (parent.parent?.type === _utils.AST_NODE_TYPES.BlockStatement) {
|
|
13
|
+
return parent.parent.body[0] === parent;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// if we've hit an arrow function, then it must have a single expression
|
|
17
|
+
// as its body, as otherwise we would have hit the block statement already
|
|
18
|
+
if (parent.parent?.type === _utils.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
parent = parent.parent;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* istanbul ignore next */
|
|
25
|
+
throw new Error(`Could not find BlockStatement - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
|
|
26
|
+
};
|
|
27
|
+
const suggestRemovingExtraArguments = (context, func, from) => ({
|
|
28
|
+
messageId: 'suggestRemovingExtraArguments',
|
|
29
|
+
fix: fixer => (0, _utils2.removeExtraArgumentsFixer)(fixer, context, func, from)
|
|
30
|
+
});
|
|
31
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
32
|
+
name: __filename,
|
|
33
|
+
meta: {
|
|
34
|
+
docs: {
|
|
35
|
+
description: 'Suggest using `expect.assertions()` OR `expect.hasAssertions()`'
|
|
36
|
+
},
|
|
37
|
+
messages: {
|
|
38
|
+
hasAssertionsTakesNoArguments: '`expect.hasAssertions` expects no arguments',
|
|
39
|
+
assertionsRequiresOneArgument: '`expect.assertions` expects a single argument of type number',
|
|
40
|
+
assertionsRequiresNumberArgument: 'This argument should be a number',
|
|
41
|
+
haveExpectAssertions: 'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression',
|
|
42
|
+
suggestAddingHasAssertions: 'Add `expect.hasAssertions()`',
|
|
43
|
+
suggestAddingAssertions: 'Add `expect.assertions(<number of assertions>)`',
|
|
44
|
+
suggestRemovingExtraArguments: 'Remove extra arguments'
|
|
45
|
+
},
|
|
46
|
+
type: 'suggestion',
|
|
47
|
+
hasSuggestions: true,
|
|
48
|
+
schema: [{
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
onlyFunctionsWithAsyncKeyword: {
|
|
52
|
+
type: 'boolean'
|
|
53
|
+
},
|
|
54
|
+
onlyFunctionsWithExpectInLoop: {
|
|
55
|
+
type: 'boolean'
|
|
56
|
+
},
|
|
57
|
+
onlyFunctionsWithExpectInCallback: {
|
|
58
|
+
type: 'boolean'
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
additionalProperties: false
|
|
62
|
+
}]
|
|
63
|
+
},
|
|
64
|
+
defaultOptions: [{
|
|
65
|
+
onlyFunctionsWithAsyncKeyword: false,
|
|
66
|
+
onlyFunctionsWithExpectInLoop: false,
|
|
67
|
+
onlyFunctionsWithExpectInCallback: false
|
|
68
|
+
}],
|
|
69
|
+
create(context, [options]) {
|
|
70
|
+
let expressionDepth = 0;
|
|
71
|
+
let describeDepth = 0;
|
|
72
|
+
let hasExpectInCallback = false;
|
|
73
|
+
let hasExpectInLoop = false;
|
|
74
|
+
let hasExpectAssertionsAsFirstStatement = false;
|
|
75
|
+
let inTestCaseCall = false;
|
|
76
|
+
let inForLoop = false;
|
|
77
|
+
const shouldCheckFunction = testFunction => {
|
|
78
|
+
if (!options.onlyFunctionsWithAsyncKeyword && !options.onlyFunctionsWithExpectInLoop && !options.onlyFunctionsWithExpectInCallback) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (options.onlyFunctionsWithAsyncKeyword) {
|
|
82
|
+
if (testFunction.async) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (options.onlyFunctionsWithExpectInLoop) {
|
|
87
|
+
if (hasExpectInLoop) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (options.onlyFunctionsWithExpectInCallback) {
|
|
92
|
+
if (hasExpectInCallback) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
};
|
|
98
|
+
const checkExpectHasAssertions = (expectFnCall, func) => {
|
|
99
|
+
if ((0, _utils2.getAccessorValue)(expectFnCall.members[0]) === 'hasAssertions') {
|
|
100
|
+
if (expectFnCall.args.length) {
|
|
101
|
+
context.report({
|
|
102
|
+
messageId: 'hasAssertionsTakesNoArguments',
|
|
103
|
+
node: expectFnCall.matcher,
|
|
104
|
+
suggest: [suggestRemovingExtraArguments(context, func, 0)]
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (expectFnCall.args.length !== 1) {
|
|
110
|
+
let {
|
|
111
|
+
loc
|
|
112
|
+
} = expectFnCall.matcher;
|
|
113
|
+
const suggest = [];
|
|
114
|
+
if (expectFnCall.args.length) {
|
|
115
|
+
loc = expectFnCall.args[1].loc;
|
|
116
|
+
suggest.push(suggestRemovingExtraArguments(context, func, 1));
|
|
117
|
+
}
|
|
118
|
+
context.report({
|
|
119
|
+
messageId: 'assertionsRequiresOneArgument',
|
|
120
|
+
suggest,
|
|
121
|
+
loc
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const [arg] = expectFnCall.args;
|
|
126
|
+
if (arg.type === _utils.AST_NODE_TYPES.Literal && Number.isInteger(arg.value)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
context.report({
|
|
130
|
+
messageId: 'assertionsRequiresNumberArgument',
|
|
131
|
+
node: arg
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
const enterExpression = () => inTestCaseCall && expressionDepth++;
|
|
135
|
+
const exitExpression = () => inTestCaseCall && expressionDepth--;
|
|
136
|
+
const enterForLoop = () => inForLoop = true;
|
|
137
|
+
const exitForLoop = () => inForLoop = false;
|
|
138
|
+
let inEachHook = false;
|
|
139
|
+
|
|
140
|
+
// when set to a non-negative value, all expect calls within describes whose depth
|
|
141
|
+
// are equal to or higher than this value are covered by an expect.hasAssertions set
|
|
142
|
+
// up within a beforeEach or afterEach hook
|
|
143
|
+
//
|
|
144
|
+
// when the describe depth is lower than the current value, it gets reset to -1
|
|
145
|
+
let coveredByHookAtDepth = -1;
|
|
146
|
+
return {
|
|
147
|
+
FunctionExpression: enterExpression,
|
|
148
|
+
'FunctionExpression:exit': exitExpression,
|
|
149
|
+
ArrowFunctionExpression: enterExpression,
|
|
150
|
+
'ArrowFunctionExpression:exit': exitExpression,
|
|
151
|
+
ForStatement: enterForLoop,
|
|
152
|
+
'ForStatement:exit': exitForLoop,
|
|
153
|
+
ForInStatement: enterForLoop,
|
|
154
|
+
'ForInStatement:exit': exitForLoop,
|
|
155
|
+
ForOfStatement: enterForLoop,
|
|
156
|
+
'ForOfStatement:exit': exitForLoop,
|
|
157
|
+
CallExpression(node) {
|
|
158
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
159
|
+
if (jestFnCall?.type === 'describe') {
|
|
160
|
+
describeDepth += 1;
|
|
161
|
+
}
|
|
162
|
+
if (jestFnCall?.type === 'hook' && jestFnCall.name.endsWith('Each')) {
|
|
163
|
+
inEachHook = true;
|
|
164
|
+
}
|
|
165
|
+
if (jestFnCall?.type === 'test') {
|
|
166
|
+
inTestCaseCall = true;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (jestFnCall?.type === 'expect' && inEachHook && (0, _utils2.getAccessorValue)(jestFnCall.members[0]) === 'hasAssertions') {
|
|
170
|
+
checkExpectHasAssertions(jestFnCall, node);
|
|
171
|
+
if (coveredByHookAtDepth < 0) {
|
|
172
|
+
coveredByHookAtDepth = describeDepth;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (jestFnCall?.type === 'expect' && inTestCaseCall) {
|
|
176
|
+
if (expressionDepth === 1 && isFirstStatement(node) && ['assertions', 'hasAssertions'].includes((0, _utils2.getAccessorValue)(jestFnCall.members[0]))) {
|
|
177
|
+
checkExpectHasAssertions(jestFnCall, node);
|
|
178
|
+
hasExpectAssertionsAsFirstStatement = true;
|
|
179
|
+
}
|
|
180
|
+
if (inForLoop) {
|
|
181
|
+
hasExpectInLoop = true;
|
|
182
|
+
}
|
|
183
|
+
if (expressionDepth > 1) {
|
|
184
|
+
hasExpectInCallback = true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
'CallExpression:exit'(node) {
|
|
189
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
190
|
+
if (jestFnCall?.type === 'describe') {
|
|
191
|
+
describeDepth -= 1;
|
|
192
|
+
|
|
193
|
+
// clear the "covered by each hook" flag if we have left the describe
|
|
194
|
+
// depth that it applies to
|
|
195
|
+
if (coveredByHookAtDepth > describeDepth) {
|
|
196
|
+
coveredByHookAtDepth = -1;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (jestFnCall?.type === 'hook' && jestFnCall.name.endsWith('Each')) {
|
|
200
|
+
inEachHook = false;
|
|
201
|
+
}
|
|
202
|
+
if (jestFnCall?.type !== 'test') {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
inTestCaseCall = false;
|
|
206
|
+
if (node.arguments.length < 2) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const [, testFn] = node.arguments;
|
|
210
|
+
if (!(0, _utils2.isFunction)(testFn) || !shouldCheckFunction(testFn)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
hasExpectInLoop = false;
|
|
214
|
+
hasExpectInCallback = false;
|
|
215
|
+
if (hasExpectAssertionsAsFirstStatement) {
|
|
216
|
+
hasExpectAssertionsAsFirstStatement = false;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (coveredByHookAtDepth >= 0 && coveredByHookAtDepth <= describeDepth) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const suggestions = [];
|
|
223
|
+
if (testFn.body.type === _utils.AST_NODE_TYPES.BlockStatement) {
|
|
224
|
+
suggestions.push(['suggestAddingHasAssertions', 'expect.hasAssertions();'], ['suggestAddingAssertions', 'expect.assertions();']);
|
|
225
|
+
}
|
|
226
|
+
context.report({
|
|
227
|
+
messageId: 'haveExpectAssertions',
|
|
228
|
+
node,
|
|
229
|
+
suggest: suggestions.map(([messageId, text]) => ({
|
|
230
|
+
messageId,
|
|
231
|
+
fix: fixer => fixer.insertTextBeforeRange([testFn.body.range[0] + 1, testFn.body.range[1]], text)
|
|
232
|
+
}))
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
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: 'Prefer `await expect(...).resolves` over `expect(await ...)` syntax'
|
|
14
|
+
},
|
|
15
|
+
fixable: 'code',
|
|
16
|
+
messages: {
|
|
17
|
+
expectResolves: 'Use `await expect(...).resolves instead'
|
|
18
|
+
},
|
|
19
|
+
schema: [],
|
|
20
|
+
type: 'suggestion'
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create: context => ({
|
|
24
|
+
CallExpression(node) {
|
|
25
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
26
|
+
if (jestFnCall?.type !== 'expect') {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const {
|
|
30
|
+
parent
|
|
31
|
+
} = jestFnCall.head.node;
|
|
32
|
+
if (parent.type !== _utils.AST_NODE_TYPES.CallExpression) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const [awaitNode] = parent.arguments;
|
|
36
|
+
if (awaitNode.type === _utils.AST_NODE_TYPES.AwaitExpression) {
|
|
37
|
+
context.report({
|
|
38
|
+
node: awaitNode,
|
|
39
|
+
messageId: 'expectResolves',
|
|
40
|
+
fix(fixer) {
|
|
41
|
+
return [fixer.insertTextBefore(parent, 'await '), fixer.removeRange([awaitNode.range[0], awaitNode.argument.range[0]]), fixer.insertTextAfter(parent, '.resolves')];
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _utils = require("./utils");
|
|
8
|
+
const HooksOrder = ['beforeAll', 'beforeEach', 'afterEach', 'afterAll'];
|
|
9
|
+
var _default = exports.default = (0, _utils.createRule)({
|
|
10
|
+
name: __filename,
|
|
11
|
+
meta: {
|
|
12
|
+
docs: {
|
|
13
|
+
description: 'Prefer having hooks in a consistent order'
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
reorderHooks: `\`{{ currentHook }}\` hooks should be before any \`{{ previousHook }}\` hooks`
|
|
17
|
+
},
|
|
18
|
+
schema: [],
|
|
19
|
+
type: 'suggestion'
|
|
20
|
+
},
|
|
21
|
+
defaultOptions: [],
|
|
22
|
+
create(context) {
|
|
23
|
+
let previousHookIndex = -1;
|
|
24
|
+
let inHook = false;
|
|
25
|
+
return {
|
|
26
|
+
CallExpression(node) {
|
|
27
|
+
if (inHook) {
|
|
28
|
+
// Ignore everything that is passed into a hook
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
|
|
32
|
+
if (jestFnCall?.type !== 'hook') {
|
|
33
|
+
// Reset the previousHookIndex when encountering something different from a hook
|
|
34
|
+
previousHookIndex = -1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
inHook = true;
|
|
38
|
+
const currentHook = jestFnCall.name;
|
|
39
|
+
const currentHookIndex = HooksOrder.indexOf(currentHook);
|
|
40
|
+
if (currentHookIndex < previousHookIndex) {
|
|
41
|
+
context.report({
|
|
42
|
+
messageId: 'reorderHooks',
|
|
43
|
+
node,
|
|
44
|
+
data: {
|
|
45
|
+
previousHook: HooksOrder[previousHookIndex],
|
|
46
|
+
currentHook
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
previousHookIndex = currentHookIndex;
|
|
52
|
+
},
|
|
53
|
+
'CallExpression:exit'(node) {
|
|
54
|
+
if ((0, _utils.isTypeOfJestFnCall)(node, context, ['hook'])) {
|
|
55
|
+
inHook = false;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (inHook) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Reset the previousHookIndex when encountering something different from a hook
|
|
63
|
+
previousHookIndex = -1;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _utils = require("./utils");
|
|
8
|
+
var _default = exports.default = (0, _utils.createRule)({
|
|
9
|
+
name: __filename,
|
|
10
|
+
meta: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'Suggest having hooks before any test cases'
|
|
13
|
+
},
|
|
14
|
+
messages: {
|
|
15
|
+
noHookOnTop: 'Hooks should come before test cases'
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
type: 'suggestion'
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
const hooksContext = [false];
|
|
23
|
+
return {
|
|
24
|
+
CallExpression(node) {
|
|
25
|
+
if ((0, _utils.isTypeOfJestFnCall)(node, context, ['test'])) {
|
|
26
|
+
hooksContext[hooksContext.length - 1] = true;
|
|
27
|
+
}
|
|
28
|
+
if (hooksContext[hooksContext.length - 1] && (0, _utils.isTypeOfJestFnCall)(node, context, ['hook'])) {
|
|
29
|
+
context.report({
|
|
30
|
+
messageId: 'noHookOnTop',
|
|
31
|
+
node
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
hooksContext.push(false);
|
|
35
|
+
},
|
|
36
|
+
'CallExpression:exit'() {
|
|
37
|
+
hooksContext.pop();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
});
|