@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,126 @@
|
|
|
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 createFixerImports = (isModule, functionsToImport) => {
|
|
10
|
+
const allImportsFormatted = Array.from(functionsToImport).sort().join(', ');
|
|
11
|
+
return isModule ? `import { ${allImportsFormatted} } from '@jest/globals';` : `const { ${allImportsFormatted} } = require('@jest/globals');`;
|
|
12
|
+
};
|
|
13
|
+
const allJestFnTypes = ['hook', 'describe', 'test', 'expect', 'jest', 'unknown'];
|
|
14
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
15
|
+
name: __filename,
|
|
16
|
+
meta: {
|
|
17
|
+
docs: {
|
|
18
|
+
description: 'Prefer importing Jest globals'
|
|
19
|
+
},
|
|
20
|
+
messages: {
|
|
21
|
+
preferImportingJestGlobal: `Import the following Jest functions from '@jest/globals': {{ jestFunctions }}`
|
|
22
|
+
},
|
|
23
|
+
fixable: 'code',
|
|
24
|
+
type: 'problem',
|
|
25
|
+
schema: [{
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
types: {
|
|
29
|
+
type: 'array',
|
|
30
|
+
items: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
enum: allJestFnTypes
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
additionalProperties: false
|
|
37
|
+
}]
|
|
38
|
+
},
|
|
39
|
+
defaultOptions: [{
|
|
40
|
+
types: allJestFnTypes
|
|
41
|
+
}],
|
|
42
|
+
create(context) {
|
|
43
|
+
const {
|
|
44
|
+
types = allJestFnTypes
|
|
45
|
+
} = context.options[0] || {};
|
|
46
|
+
const functionsToImport = new Set();
|
|
47
|
+
let reportingNode;
|
|
48
|
+
return {
|
|
49
|
+
CallExpression(node) {
|
|
50
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
51
|
+
if (!jestFnCall) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (jestFnCall.head.type !== 'import' && types.includes(jestFnCall.type)) {
|
|
55
|
+
functionsToImport.add(jestFnCall.name);
|
|
56
|
+
reportingNode ||= jestFnCall.head.node;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
'Program:exit'() {
|
|
60
|
+
// this means we found at least one function to import
|
|
61
|
+
if (!reportingNode) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const isModule = context.parserOptions?.sourceType === 'module' || context.languageOptions.sourceType === 'module';
|
|
65
|
+
context.report({
|
|
66
|
+
node: reportingNode,
|
|
67
|
+
messageId: 'preferImportingJestGlobal',
|
|
68
|
+
data: {
|
|
69
|
+
jestFunctions: Array.from(functionsToImport).join(', ')
|
|
70
|
+
},
|
|
71
|
+
fix(fixer) {
|
|
72
|
+
const {
|
|
73
|
+
sourceCode
|
|
74
|
+
} = context;
|
|
75
|
+
const [firstNode] = sourceCode.ast.body;
|
|
76
|
+
|
|
77
|
+
// check if "use strict" directive exists
|
|
78
|
+
if (firstNode.type === _utils.AST_NODE_TYPES.ExpressionStatement && (0, _utils2.isStringNode)(firstNode.expression, 'use strict')) {
|
|
79
|
+
return fixer.insertTextAfter(firstNode, `\n${createFixerImports(isModule, functionsToImport)}`);
|
|
80
|
+
}
|
|
81
|
+
const importNode = sourceCode.ast.body.find(node => node.type === _utils.AST_NODE_TYPES.ImportDeclaration && node.source.value === '@jest/globals');
|
|
82
|
+
if (importNode?.type === _utils.AST_NODE_TYPES.ImportDeclaration) {
|
|
83
|
+
for (const specifier of importNode.specifiers) {
|
|
84
|
+
if (specifier.type === _utils.AST_NODE_TYPES.ImportSpecifier) {
|
|
85
|
+
let importName = 'name' in specifier.imported ? specifier.imported.name : '';
|
|
86
|
+
const local = (0, _utils2.getAccessorValue)(specifier.local);
|
|
87
|
+
if (local !== importName) {
|
|
88
|
+
importName = `${importName} as ${local}`;
|
|
89
|
+
}
|
|
90
|
+
if ('value' in specifier.imported) {
|
|
91
|
+
importName = `'${specifier.imported.value}'${importName}`;
|
|
92
|
+
}
|
|
93
|
+
functionsToImport.add(importName);
|
|
94
|
+
}
|
|
95
|
+
if (specifier.type === _utils.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
96
|
+
functionsToImport.add(specifier.local.name);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return fixer.replaceText(importNode, createFixerImports(isModule, functionsToImport));
|
|
100
|
+
}
|
|
101
|
+
const requireNode = sourceCode.ast.body.find(node => node.type === _utils.AST_NODE_TYPES.VariableDeclaration && node.declarations.some(declaration => declaration.init?.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isIdentifier)(declaration.init.callee, 'require') && (0, _utils2.isStringNode)(declaration.init.arguments[0], '@jest/globals') && (declaration.id.type === _utils.AST_NODE_TYPES.Identifier || declaration.id.type === _utils.AST_NODE_TYPES.ObjectPattern)));
|
|
102
|
+
if (requireNode?.type !== _utils.AST_NODE_TYPES.VariableDeclaration) {
|
|
103
|
+
return fixer.insertTextBefore(firstNode, `${createFixerImports(isModule, functionsToImport)}\n`);
|
|
104
|
+
}
|
|
105
|
+
if (requireNode.declarations[0]?.id.type === _utils.AST_NODE_TYPES.ObjectPattern) {
|
|
106
|
+
for (const property of requireNode.declarations[0].id.properties) {
|
|
107
|
+
if (property.type !== _utils.AST_NODE_TYPES.Property || !(0, _utils2.isSupportedAccessor)(property.key)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let importName = (0, _utils2.getAccessorValue)(property.key);
|
|
111
|
+
if ((0, _utils2.isSupportedAccessor)(property.value)) {
|
|
112
|
+
const local = (0, _utils2.getAccessorValue)(property.value);
|
|
113
|
+
if (importName !== local) {
|
|
114
|
+
importName += `: ${local}`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
functionsToImport.add(importName);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return fixer.replaceText(requireNode, `${createFixerImports(isModule, functionsToImport)}`);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
});
|
|
@@ -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 = context.sourceCode.text.slice(...(0, _utils2.followTypeAssertionChain)(node.expression).range);
|
|
46
|
+
context.report({
|
|
47
|
+
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
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
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 hasStringAsFirstArgument = node => node.arguments[0] && (0, _utils.isStringNode)(node.arguments[0]);
|
|
9
|
+
const populateIgnores = ignore => {
|
|
10
|
+
const ignores = [];
|
|
11
|
+
if (ignore.includes(_utils.DescribeAlias.describe)) {
|
|
12
|
+
ignores.push(...Object.keys(_utils.DescribeAlias));
|
|
13
|
+
}
|
|
14
|
+
if (ignore.includes(_utils.TestCaseName.test)) {
|
|
15
|
+
ignores.push(...Object.keys(_utils.TestCaseName).filter(k => k.endsWith(_utils.TestCaseName.test)));
|
|
16
|
+
}
|
|
17
|
+
if (ignore.includes(_utils.TestCaseName.it)) {
|
|
18
|
+
ignores.push(...Object.keys(_utils.TestCaseName).filter(k => k.endsWith(_utils.TestCaseName.it)));
|
|
19
|
+
}
|
|
20
|
+
return ignores;
|
|
21
|
+
};
|
|
22
|
+
var _default = exports.default = (0, _utils.createRule)({
|
|
23
|
+
name: __filename,
|
|
24
|
+
meta: {
|
|
25
|
+
type: 'suggestion',
|
|
26
|
+
docs: {
|
|
27
|
+
description: 'Enforce lowercase test names'
|
|
28
|
+
},
|
|
29
|
+
fixable: 'code',
|
|
30
|
+
messages: {
|
|
31
|
+
unexpectedCase: '`{{ method }}`s should begin with lowercase'
|
|
32
|
+
},
|
|
33
|
+
schema: [{
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
ignore: {
|
|
37
|
+
type: 'array',
|
|
38
|
+
items: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
// for some reason TypeScript thinks this _must_ be a read-only
|
|
41
|
+
// array, so we have to explicitly cast it as a mutable array
|
|
42
|
+
enum: [_utils.DescribeAlias.describe, _utils.TestCaseName.test, _utils.TestCaseName.it]
|
|
43
|
+
},
|
|
44
|
+
additionalItems: false
|
|
45
|
+
},
|
|
46
|
+
allowedPrefixes: {
|
|
47
|
+
type: 'array',
|
|
48
|
+
items: {
|
|
49
|
+
type: 'string'
|
|
50
|
+
},
|
|
51
|
+
additionalItems: false
|
|
52
|
+
},
|
|
53
|
+
ignoreTopLevelDescribe: {
|
|
54
|
+
type: 'boolean',
|
|
55
|
+
default: false
|
|
56
|
+
},
|
|
57
|
+
ignoreTodos: {
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
default: false
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
additionalProperties: false
|
|
63
|
+
}]
|
|
64
|
+
},
|
|
65
|
+
defaultOptions: [{
|
|
66
|
+
ignore: [],
|
|
67
|
+
allowedPrefixes: [],
|
|
68
|
+
ignoreTopLevelDescribe: false,
|
|
69
|
+
ignoreTodos: false
|
|
70
|
+
}],
|
|
71
|
+
create(context, [{
|
|
72
|
+
ignore = [],
|
|
73
|
+
allowedPrefixes = [],
|
|
74
|
+
ignoreTopLevelDescribe,
|
|
75
|
+
ignoreTodos
|
|
76
|
+
}]) {
|
|
77
|
+
const ignores = populateIgnores(ignore);
|
|
78
|
+
let numberOfDescribeBlocks = 0;
|
|
79
|
+
return {
|
|
80
|
+
CallExpression(node) {
|
|
81
|
+
const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
|
|
82
|
+
if (!jestFnCall || !hasStringAsFirstArgument(node)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (jestFnCall.type === 'describe') {
|
|
86
|
+
numberOfDescribeBlocks++;
|
|
87
|
+
if (ignoreTopLevelDescribe && numberOfDescribeBlocks === 1) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
} else if (jestFnCall.type !== 'test') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Ignore *.todo test and/or test suites
|
|
95
|
+
if (ignoreTodos && jestFnCall.members.some(s => (0, _utils.getAccessorValue)(s) === 'todo')) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const [firstArg] = node.arguments;
|
|
99
|
+
const description = (0, _utils.getStringValue)(firstArg);
|
|
100
|
+
if (allowedPrefixes.some(name => description.startsWith(name))) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const firstCharacter = description.charAt(0);
|
|
104
|
+
if (!firstCharacter || firstCharacter === firstCharacter.toLowerCase() || ignores.includes(jestFnCall.name)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
context.report({
|
|
108
|
+
messageId: 'unexpectedCase',
|
|
109
|
+
node: node.arguments[0],
|
|
110
|
+
data: {
|
|
111
|
+
method: jestFnCall.name
|
|
112
|
+
},
|
|
113
|
+
fix(fixer) {
|
|
114
|
+
const description = (0, _utils.getStringValue)(firstArg);
|
|
115
|
+
const rangeIgnoringQuotes = [firstArg.range[0] + 1, firstArg.range[1] - 1];
|
|
116
|
+
const newDescription = description.substring(0, 1).toLowerCase() + description.substring(1);
|
|
117
|
+
return [fixer.replaceTextRange(rangeIgnoringQuotes, newDescription)];
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
'CallExpression:exit'(node) {
|
|
122
|
+
if ((0, _utils.isTypeOfJestFnCall)(node, context, ['describe'])) {
|
|
123
|
+
numberOfDescribeBlocks--;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
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 withOnce = (name, addOnce) => {
|
|
10
|
+
return `${name}${addOnce ? 'Once' : ''}`;
|
|
11
|
+
};
|
|
12
|
+
const findSingleReturnArgumentNode = fnNode => {
|
|
13
|
+
if (fnNode.body.type !== _utils.AST_NODE_TYPES.BlockStatement) {
|
|
14
|
+
return fnNode.body;
|
|
15
|
+
}
|
|
16
|
+
if (fnNode.body.body[0]?.type === _utils.AST_NODE_TYPES.ReturnStatement) {
|
|
17
|
+
return fnNode.body.body[0].argument;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
22
|
+
name: __filename,
|
|
23
|
+
meta: {
|
|
24
|
+
docs: {
|
|
25
|
+
description: 'Prefer mock resolved/rejected shorthands for promises'
|
|
26
|
+
},
|
|
27
|
+
messages: {
|
|
28
|
+
useMockShorthand: 'Prefer {{ replacement }}'
|
|
29
|
+
},
|
|
30
|
+
schema: [],
|
|
31
|
+
type: 'suggestion',
|
|
32
|
+
fixable: 'code'
|
|
33
|
+
},
|
|
34
|
+
defaultOptions: [],
|
|
35
|
+
create(context) {
|
|
36
|
+
const report = (property, isOnce, outerArgNode, innerArgNode = outerArgNode) => {
|
|
37
|
+
if (innerArgNode?.type !== _utils.AST_NODE_TYPES.CallExpression) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const argName = (0, _utils2.getNodeName)(innerArgNode);
|
|
41
|
+
if (argName !== 'Promise.resolve' && argName !== 'Promise.reject') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const replacement = withOnce(argName.endsWith('reject') ? 'mockRejectedValue' : 'mockResolvedValue', isOnce);
|
|
45
|
+
context.report({
|
|
46
|
+
node: property,
|
|
47
|
+
messageId: 'useMockShorthand',
|
|
48
|
+
data: {
|
|
49
|
+
replacement
|
|
50
|
+
},
|
|
51
|
+
fix(fixer) {
|
|
52
|
+
const {
|
|
53
|
+
sourceCode
|
|
54
|
+
} = context;
|
|
55
|
+
|
|
56
|
+
// there shouldn't be more than one argument, but if there is don't try
|
|
57
|
+
// fixing since we have no idea what to do with the extra arguments
|
|
58
|
+
if (innerArgNode.arguments.length > 1) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return [fixer.replaceText(property, replacement), fixer.replaceText(outerArgNode,
|
|
62
|
+
// the value argument for both Promise methods is optional,
|
|
63
|
+
// whereas for Jest they're required so use an explicit undefined
|
|
64
|
+
// if no argument is being passed to the call we're replacing
|
|
65
|
+
innerArgNode.arguments.length === 1 ? sourceCode.getText(innerArgNode.arguments[0]) : 'undefined')];
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
CallExpression(node) {
|
|
71
|
+
if (node.callee.type !== _utils.AST_NODE_TYPES.MemberExpression || !(0, _utils2.isSupportedAccessor)(node.callee.property) || node.arguments.length === 0) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const mockFnName = (0, _utils2.getAccessorValue)(node.callee.property);
|
|
75
|
+
const isOnce = mockFnName.endsWith('Once');
|
|
76
|
+
if (mockFnName === withOnce('mockReturnValue', isOnce)) {
|
|
77
|
+
report(node.callee.property, isOnce, node.arguments[0]);
|
|
78
|
+
} else if (mockFnName === withOnce('mockImplementation', isOnce)) {
|
|
79
|
+
const [arg] = node.arguments;
|
|
80
|
+
if (!(0, _utils2.isFunction)(arg) || arg.params.length !== 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
report(node.callee.property, isOnce, arg, findSingleReturnArgumentNode(arg));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
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 withOnce = (name, addOnce) => {
|
|
10
|
+
return `${name}${addOnce ? 'Once' : ''}`;
|
|
11
|
+
};
|
|
12
|
+
const findSingleReturnArgumentNode = fnNode => {
|
|
13
|
+
if (fnNode.body.type !== _utils.AST_NODE_TYPES.BlockStatement) {
|
|
14
|
+
return fnNode.body;
|
|
15
|
+
}
|
|
16
|
+
if (fnNode.body.body[0]?.type === _utils.AST_NODE_TYPES.ReturnStatement) {
|
|
17
|
+
return fnNode.body.body[0].argument;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
22
|
+
name: __filename,
|
|
23
|
+
meta: {
|
|
24
|
+
docs: {
|
|
25
|
+
description: 'Prefer mock return shorthands'
|
|
26
|
+
},
|
|
27
|
+
messages: {
|
|
28
|
+
useMockShorthand: 'Prefer {{ replacement }}'
|
|
29
|
+
},
|
|
30
|
+
schema: [],
|
|
31
|
+
type: 'suggestion',
|
|
32
|
+
fixable: 'code'
|
|
33
|
+
},
|
|
34
|
+
defaultOptions: [],
|
|
35
|
+
create(context) {
|
|
36
|
+
const isMutable = identifier => {
|
|
37
|
+
const scope = context.sourceCode.getScope(identifier);
|
|
38
|
+
return scope.through.some(v => v.resolved?.defs.some(n => n.type === 'Variable' && n.parent.kind !== 'const'));
|
|
39
|
+
};
|
|
40
|
+
const usesMutableIdentifier = node => {
|
|
41
|
+
switch (node.type) {
|
|
42
|
+
case _utils.AST_NODE_TYPES.Identifier:
|
|
43
|
+
return isMutable(node);
|
|
44
|
+
case _utils.AST_NODE_TYPES.ObjectExpression:
|
|
45
|
+
return node.properties.some(prop => usesMutableIdentifier(prop));
|
|
46
|
+
case _utils.AST_NODE_TYPES.Property:
|
|
47
|
+
if (node.computed && usesMutableIdentifier(node.key)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return usesMutableIdentifier(node.value);
|
|
51
|
+
case _utils.AST_NODE_TYPES.ArrayExpression:
|
|
52
|
+
return node.elements.some(el => el && usesMutableIdentifier(el));
|
|
53
|
+
case _utils.AST_NODE_TYPES.ChainExpression:
|
|
54
|
+
return usesMutableIdentifier(node.expression);
|
|
55
|
+
case _utils.AST_NODE_TYPES.SpreadElement:
|
|
56
|
+
case _utils.AST_NODE_TYPES.UnaryExpression:
|
|
57
|
+
return usesMutableIdentifier(node.argument);
|
|
58
|
+
case _utils.AST_NODE_TYPES.LogicalExpression:
|
|
59
|
+
case _utils.AST_NODE_TYPES.BinaryExpression:
|
|
60
|
+
return usesMutableIdentifier(node.left) || usesMutableIdentifier(node.right);
|
|
61
|
+
case _utils.AST_NODE_TYPES.MemberExpression:
|
|
62
|
+
if (node.computed && usesMutableIdentifier(node.property)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return node.object.type === _utils.AST_NODE_TYPES.CallExpression && usesMutableIdentifier(node.object);
|
|
66
|
+
case _utils.AST_NODE_TYPES.ConditionalExpression:
|
|
67
|
+
return usesMutableIdentifier(node.test) || usesMutableIdentifier(node.alternate) || usesMutableIdentifier(node.consequent);
|
|
68
|
+
case _utils.AST_NODE_TYPES.NewExpression:
|
|
69
|
+
case _utils.AST_NODE_TYPES.CallExpression:
|
|
70
|
+
return usesMutableIdentifier(node.callee) || node.arguments.some(arg => usesMutableIdentifier(arg));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// currently we assume a mutable identifier is not being used
|
|
74
|
+
// unless we can find one specifically, which is technically
|
|
75
|
+
// not safe but so far it has not seemed to cause issues.
|
|
76
|
+
//
|
|
77
|
+
// if it proves to be too troublesome, we should consider
|
|
78
|
+
// inverting this so we only report when we're completely
|
|
79
|
+
// sure it is safe
|
|
80
|
+
return false;
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
CallExpression(node) {
|
|
84
|
+
if (node.callee.type !== _utils.AST_NODE_TYPES.MemberExpression || !(0, _utils2.isSupportedAccessor)(node.callee.property) || node.arguments.length === 0) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const {
|
|
88
|
+
property
|
|
89
|
+
} = node.callee;
|
|
90
|
+
const mockFnName = (0, _utils2.getAccessorValue)(property);
|
|
91
|
+
const isOnce = mockFnName.endsWith('Once');
|
|
92
|
+
if (mockFnName !== withOnce('mockImplementation', isOnce)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const [arg] = node.arguments;
|
|
96
|
+
if (!(0, _utils2.isFunction)(arg) || arg.params.length !== 0 || arg.async) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const replacement = withOnce('mockReturnValue', isOnce);
|
|
100
|
+
const returnNode = findSingleReturnArgumentNode(arg);
|
|
101
|
+
if (!returnNode || returnNode.type === _utils.AST_NODE_TYPES.UpdateExpression) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// check if we're using a non-constant variable
|
|
106
|
+
if (usesMutableIdentifier(returnNode)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
context.report({
|
|
110
|
+
node: property,
|
|
111
|
+
messageId: 'useMockShorthand',
|
|
112
|
+
data: {
|
|
113
|
+
replacement
|
|
114
|
+
},
|
|
115
|
+
fix(fixer) {
|
|
116
|
+
return [fixer.replaceText(property, replacement), fixer.replaceText(arg, context.sourceCode.getText(returnNode))];
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
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 snapshotMatchers = ['toMatchSnapshot', 'toThrowErrorMatchingSnapshot'];
|
|
9
|
+
const snapshotMatcherNames = snapshotMatchers;
|
|
10
|
+
const isSnapshotMatcherWithoutHint = expectFnCall => {
|
|
11
|
+
if (expectFnCall.args.length === 0) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// this matcher only supports one argument which is the hint
|
|
16
|
+
if (!(0, _utils.isSupportedAccessor)(expectFnCall.matcher, 'toMatchSnapshot')) {
|
|
17
|
+
return expectFnCall.args.length !== 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// if we're being passed two arguments,
|
|
21
|
+
// the second one should be the hint
|
|
22
|
+
if (expectFnCall.args.length === 2) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const [arg] = expectFnCall.args;
|
|
26
|
+
|
|
27
|
+
// the first argument to `toMatchSnapshot` can be _either_ a snapshot hint or
|
|
28
|
+
// an object with asymmetric matchers, so we can't just assume that the first
|
|
29
|
+
// argument is a hint when it's by itself.
|
|
30
|
+
return !(0, _utils.isStringNode)(arg);
|
|
31
|
+
};
|
|
32
|
+
const messages = {
|
|
33
|
+
missingHint: 'You should provide a hint for this snapshot'
|
|
34
|
+
};
|
|
35
|
+
var _default = exports.default = (0, _utils.createRule)({
|
|
36
|
+
name: __filename,
|
|
37
|
+
meta: {
|
|
38
|
+
docs: {
|
|
39
|
+
description: 'Prefer including a hint with external snapshots'
|
|
40
|
+
},
|
|
41
|
+
messages,
|
|
42
|
+
type: 'suggestion',
|
|
43
|
+
schema: [{
|
|
44
|
+
type: 'string',
|
|
45
|
+
enum: ['always', 'multi']
|
|
46
|
+
}]
|
|
47
|
+
},
|
|
48
|
+
defaultOptions: ['multi'],
|
|
49
|
+
create(context, [mode]) {
|
|
50
|
+
const snapshotMatchers = [];
|
|
51
|
+
const depths = [];
|
|
52
|
+
let expressionDepth = 0;
|
|
53
|
+
const reportSnapshotMatchersWithoutHints = () => {
|
|
54
|
+
for (const snapshotMatcher of snapshotMatchers) {
|
|
55
|
+
if (isSnapshotMatcherWithoutHint(snapshotMatcher)) {
|
|
56
|
+
context.report({
|
|
57
|
+
messageId: 'missingHint',
|
|
58
|
+
node: snapshotMatcher.matcher
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const enterExpression = () => {
|
|
64
|
+
expressionDepth++;
|
|
65
|
+
};
|
|
66
|
+
const exitExpression = () => {
|
|
67
|
+
expressionDepth--;
|
|
68
|
+
if (mode === 'always') {
|
|
69
|
+
reportSnapshotMatchersWithoutHints();
|
|
70
|
+
snapshotMatchers.length = 0;
|
|
71
|
+
}
|
|
72
|
+
if (mode === 'multi' && expressionDepth === 0) {
|
|
73
|
+
if (snapshotMatchers.length > 1) {
|
|
74
|
+
reportSnapshotMatchersWithoutHints();
|
|
75
|
+
}
|
|
76
|
+
snapshotMatchers.length = 0;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
'Program:exit'() {
|
|
81
|
+
enterExpression();
|
|
82
|
+
exitExpression();
|
|
83
|
+
},
|
|
84
|
+
FunctionExpression: enterExpression,
|
|
85
|
+
'FunctionExpression:exit': exitExpression,
|
|
86
|
+
ArrowFunctionExpression: enterExpression,
|
|
87
|
+
'ArrowFunctionExpression:exit': exitExpression,
|
|
88
|
+
'CallExpression:exit'(node) {
|
|
89
|
+
if ((0, _utils.isTypeOfJestFnCall)(node, context, ['describe', 'test'])) {
|
|
90
|
+
/* istanbul ignore next */
|
|
91
|
+
expressionDepth = depths.pop() ?? 0;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
CallExpression(node) {
|
|
95
|
+
const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
|
|
96
|
+
if (jestFnCall?.type !== 'expect') {
|
|
97
|
+
if (jestFnCall?.type === 'describe' || jestFnCall?.type === 'test') {
|
|
98
|
+
depths.push(expressionDepth);
|
|
99
|
+
expressionDepth = 0;
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const matcherName = (0, _utils.getAccessorValue)(jestFnCall.matcher);
|
|
104
|
+
if (!snapshotMatcherNames.includes(matcherName)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
snapshotMatchers.push(jestFnCall);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
});
|