@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.
Files changed (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/changes.json +10 -0
  4. package/docs/rules/consistent-test-it.md +96 -0
  5. package/docs/rules/expect-expect.md +152 -0
  6. package/docs/rules/max-expects.md +78 -0
  7. package/docs/rules/max-nested-describe.md +134 -0
  8. package/docs/rules/no-alias-methods.md +57 -0
  9. package/docs/rules/no-commented-out-tests.md +68 -0
  10. package/docs/rules/no-conditional-expect.md +142 -0
  11. package/docs/rules/no-conditional-in-test.md +123 -0
  12. package/docs/rules/no-confusing-set-timeout.md +64 -0
  13. package/docs/rules/no-deprecated-functions.md +59 -0
  14. package/docs/rules/no-disabled-tests.md +72 -0
  15. package/docs/rules/no-done-callback.md +100 -0
  16. package/docs/rules/no-duplicate-hooks.md +79 -0
  17. package/docs/rules/no-error-equal.md +37 -0
  18. package/docs/rules/no-export.md +52 -0
  19. package/docs/rules/no-focused-tests.md +67 -0
  20. package/docs/rules/no-hooks.md +178 -0
  21. package/docs/rules/no-identical-title.md +59 -0
  22. package/docs/rules/no-interpolation-in-snapshots.md +67 -0
  23. package/docs/rules/no-jasmine-globals.md +69 -0
  24. package/docs/rules/no-large-snapshots.md +180 -0
  25. package/docs/rules/no-mocks-import.md +34 -0
  26. package/docs/rules/no-restricted-jest-methods.md +53 -0
  27. package/docs/rules/no-restricted-matchers.md +64 -0
  28. package/docs/rules/no-standalone-expect.md +106 -0
  29. package/docs/rules/no-test-prefixes.md +42 -0
  30. package/docs/rules/no-test-return-statement.md +50 -0
  31. package/docs/rules/no-unnecessary-assertion.md +41 -0
  32. package/docs/rules/no-unneeded-async-expect-function.md +41 -0
  33. package/docs/rules/no-untyped-mock-factory.md +74 -0
  34. package/docs/rules/padding-around-after-all-blocks.md +34 -0
  35. package/docs/rules/padding-around-after-each-blocks.md +38 -0
  36. package/docs/rules/padding-around-all.md +20 -0
  37. package/docs/rules/padding-around-before-all-blocks.md +37 -0
  38. package/docs/rules/padding-around-before-each-blocks.md +38 -0
  39. package/docs/rules/padding-around-describe-blocks.md +42 -0
  40. package/docs/rules/padding-around-expect-groups.md +44 -0
  41. package/docs/rules/padding-around-test-blocks.md +48 -0
  42. package/docs/rules/prefer-called-with.md +39 -0
  43. package/docs/rules/prefer-comparison-matcher.md +62 -0
  44. package/docs/rules/prefer-each.md +58 -0
  45. package/docs/rules/prefer-ending-with-an-expect.md +170 -0
  46. package/docs/rules/prefer-equality-matcher.md +36 -0
  47. package/docs/rules/prefer-expect-assertions.md +230 -0
  48. package/docs/rules/prefer-expect-resolves.md +62 -0
  49. package/docs/rules/prefer-hooks-in-order.md +137 -0
  50. package/docs/rules/prefer-hooks-on-top.md +124 -0
  51. package/docs/rules/prefer-importing-jest-globals.md +85 -0
  52. package/docs/rules/prefer-jest-mocked.md +40 -0
  53. package/docs/rules/prefer-lowercase-title.md +128 -0
  54. package/docs/rules/prefer-mock-promise-shorthand.md +41 -0
  55. package/docs/rules/prefer-mock-return-shorthand.md +50 -0
  56. package/docs/rules/prefer-snapshot-hint.md +192 -0
  57. package/docs/rules/prefer-spy-on.md +46 -0
  58. package/docs/rules/prefer-strict-equal.md +29 -0
  59. package/docs/rules/prefer-to-be.md +63 -0
  60. package/docs/rules/prefer-to-contain.md +53 -0
  61. package/docs/rules/prefer-to-have-been-called-times.md +43 -0
  62. package/docs/rules/prefer-to-have-been-called.md +35 -0
  63. package/docs/rules/prefer-to-have-length.md +41 -0
  64. package/docs/rules/prefer-todo.md +33 -0
  65. package/docs/rules/require-hook.md +187 -0
  66. package/docs/rules/require-to-throw-message.md +43 -0
  67. package/docs/rules/require-top-level-describe.md +86 -0
  68. package/docs/rules/unbound-method.md +61 -0
  69. package/docs/rules/valid-describe-callback.md +69 -0
  70. package/docs/rules/valid-expect-in-promise.md +79 -0
  71. package/docs/rules/valid-expect-with-promise.md +43 -0
  72. package/docs/rules/valid-expect.md +155 -0
  73. package/docs/rules/valid-mock-module-path.md +72 -0
  74. package/docs/rules/valid-title.md +319 -0
  75. package/index.d.ts +28 -0
  76. package/lib/globals.json +15 -0
  77. package/lib/index.js +85 -0
  78. package/lib/rules/consistent-test-it.js +101 -0
  79. package/lib/rules/expect-expect.js +102 -0
  80. package/lib/rules/max-expects.js +70 -0
  81. package/lib/rules/max-nested-describe.js +59 -0
  82. package/lib/rules/no-alias-methods.js +62 -0
  83. package/lib/rules/no-commented-out-tests.js +44 -0
  84. package/lib/rules/no-conditional-expect.js +80 -0
  85. package/lib/rules/no-conditional-in-test.js +63 -0
  86. package/lib/rules/no-confusing-set-timeout.js +61 -0
  87. package/lib/rules/no-deprecated-functions.js +81 -0
  88. package/lib/rules/no-disabled-tests.js +57 -0
  89. package/lib/rules/no-done-callback.js +120 -0
  90. package/lib/rules/no-duplicate-hooks.js +52 -0
  91. package/lib/rules/no-error-equal.js +41 -0
  92. package/lib/rules/no-export.js +61 -0
  93. package/lib/rules/no-focused-tests.js +63 -0
  94. package/lib/rules/no-hooks.js +51 -0
  95. package/lib/rules/no-identical-title.js +73 -0
  96. package/lib/rules/no-interpolation-in-snapshots.js +43 -0
  97. package/lib/rules/no-jasmine-globals.js +140 -0
  98. package/lib/rules/no-large-snapshots.js +100 -0
  99. package/lib/rules/no-mocks-import.js +46 -0
  100. package/lib/rules/no-restricted-jest-methods.js +53 -0
  101. package/lib/rules/no-restricted-matchers.js +60 -0
  102. package/lib/rules/no-standalone-expect.js +114 -0
  103. package/lib/rules/no-test-prefixes.js +48 -0
  104. package/lib/rules/no-test-return-statement.js +62 -0
  105. package/lib/rules/no-unnecessary-assertion.js +87 -0
  106. package/lib/rules/no-unneeded-async-expect-function.js +57 -0
  107. package/lib/rules/no-untyped-mock-factory.js +61 -0
  108. package/lib/rules/padding-around-after-all-blocks.js +17 -0
  109. package/lib/rules/padding-around-after-each-blocks.js +17 -0
  110. package/lib/rules/padding-around-all.js +15 -0
  111. package/lib/rules/padding-around-before-all-blocks.js +17 -0
  112. package/lib/rules/padding-around-before-each-blocks.js +17 -0
  113. package/lib/rules/padding-around-describe-blocks.js +17 -0
  114. package/lib/rules/padding-around-expect-groups.js +21 -0
  115. package/lib/rules/padding-around-test-blocks.js +17 -0
  116. package/lib/rules/prefer-called-with.js +47 -0
  117. package/lib/rules/prefer-comparison-matcher.js +111 -0
  118. package/lib/rules/prefer-each.js +77 -0
  119. package/lib/rules/prefer-ending-with-an-expect.js +102 -0
  120. package/lib/rules/prefer-equality-matcher.js +87 -0
  121. package/lib/rules/prefer-expect-assertions.js +237 -0
  122. package/lib/rules/prefer-expect-resolves.js +47 -0
  123. package/lib/rules/prefer-hooks-in-order.js +67 -0
  124. package/lib/rules/prefer-hooks-on-top.js +41 -0
  125. package/lib/rules/prefer-importing-jest-globals.js +126 -0
  126. package/lib/rules/prefer-jest-mocked.js +66 -0
  127. package/lib/rules/prefer-lowercase-title.js +128 -0
  128. package/lib/rules/prefer-mock-promise-shorthand.js +88 -0
  129. package/lib/rules/prefer-mock-return-shorthand.js +122 -0
  130. package/lib/rules/prefer-snapshot-hint.js +111 -0
  131. package/lib/rules/prefer-spy-on.js +80 -0
  132. package/lib/rules/prefer-strict-equal.js +46 -0
  133. package/lib/rules/prefer-to-be.js +98 -0
  134. package/lib/rules/prefer-to-contain.js +82 -0
  135. package/lib/rules/prefer-to-have-been-called-times.js +70 -0
  136. package/lib/rules/prefer-to-have-been-called.js +59 -0
  137. package/lib/rules/prefer-to-have-length.js +57 -0
  138. package/lib/rules/prefer-todo.js +72 -0
  139. package/lib/rules/require-hook.js +93 -0
  140. package/lib/rules/require-to-throw-message.js +45 -0
  141. package/lib/rules/require-top-level-describe.js +86 -0
  142. package/lib/rules/unbound-method.js +86 -0
  143. package/lib/rules/utils/accessors.js +127 -0
  144. package/lib/rules/utils/ast-utils.js +59 -0
  145. package/lib/rules/utils/detectJestVersion.js +24 -0
  146. package/lib/rules/utils/followTypeAssertionChain.js +10 -0
  147. package/lib/rules/utils/index.js +71 -0
  148. package/lib/rules/utils/misc.js +168 -0
  149. package/lib/rules/utils/padding.js +308 -0
  150. package/lib/rules/utils/parseJestFnCall.js +325 -0
  151. package/lib/rules/utils/ts.js +73 -0
  152. package/lib/rules/valid-describe-callback.js +94 -0
  153. package/lib/rules/valid-expect-in-promise.js +311 -0
  154. package/lib/rules/valid-expect-with-promise.js +54 -0
  155. package/lib/rules/valid-expect.js +302 -0
  156. package/lib/rules/valid-mock-module-path.js +90 -0
  157. package/lib/rules/valid-title.js +252 -0
  158. 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
+ });