@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,302 @@
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 ported from from eslint-plugin-jasmine.
11
+ * MIT license, Tom Vincent.
12
+ */
13
+
14
+ /**
15
+ * Async assertions might be called in Promise
16
+ * methods like `Promise.x(expect1)` or `Promise.x([expect1, expect2])`.
17
+ * If that's the case, Promise node have to be awaited or returned.
18
+ *
19
+ * @Returns CallExpressionNode
20
+ */
21
+ const getPromiseCallExpressionNode = node => {
22
+ if (node.type === _utils.AST_NODE_TYPES.ArrayExpression && node.parent.type === _utils.AST_NODE_TYPES.CallExpression) {
23
+ node = node.parent;
24
+ }
25
+ if (node.type === _utils.AST_NODE_TYPES.CallExpression && node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.object, 'Promise')) {
26
+ return node;
27
+ }
28
+ return null;
29
+ };
30
+ const findPromiseCallExpressionNode = node => node.parent?.parent && [_utils.AST_NODE_TYPES.CallExpression, _utils.AST_NODE_TYPES.ArrayExpression].includes(node.parent.type) ? getPromiseCallExpressionNode(node.parent) : null;
31
+ const findFirstFunctionExpression = ({
32
+ parent
33
+ }) => {
34
+ if (!parent) {
35
+ return null;
36
+ }
37
+ return (0, _utils2.isFunction)(parent) ? parent : findFirstFunctionExpression(parent);
38
+ };
39
+ const getNormalizeFunctionExpression = functionExpression => {
40
+ if (functionExpression.parent.type === _utils.AST_NODE_TYPES.Property && functionExpression.type === _utils.AST_NODE_TYPES.FunctionExpression) {
41
+ return functionExpression.parent;
42
+ }
43
+ return functionExpression;
44
+ };
45
+ const getParentIfThenified = node => {
46
+ const grandParentNode = node.parent?.parent;
47
+ if (grandParentNode && grandParentNode.type === _utils.AST_NODE_TYPES.CallExpression && grandParentNode.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(grandParentNode.callee.property) && ['then', 'catch'].includes((0, _utils2.getAccessorValue)(grandParentNode.callee.property))) {
48
+ // Just in case `then`s are chained look one above.
49
+ return getParentIfThenified(grandParentNode);
50
+ }
51
+ return node;
52
+ };
53
+ const isAcceptableReturnNode = (node, allowReturn) => {
54
+ if (allowReturn && node.type === _utils.AST_NODE_TYPES.ReturnStatement) {
55
+ return true;
56
+ }
57
+ if (node.type === _utils.AST_NODE_TYPES.ConditionalExpression) {
58
+ return isAcceptableReturnNode(node.parent, allowReturn);
59
+ }
60
+ return [_utils.AST_NODE_TYPES.ArrowFunctionExpression, _utils.AST_NODE_TYPES.AwaitExpression].includes(node.type);
61
+ };
62
+ const promiseArrayExceptionKey = ({
63
+ start,
64
+ end
65
+ }) => `${start.line}:${start.column}-${end.line}:${end.column}`;
66
+ const defaultAsyncMatchers = ['toReject', 'toResolve'];
67
+ var _default = exports.default = (0, _utils2.createRule)({
68
+ name: __filename,
69
+ meta: {
70
+ docs: {
71
+ description: 'Enforce valid `expect()` usage'
72
+ },
73
+ messages: {
74
+ tooManyArgs: 'Expect takes at most {{ amount }} argument{{ s }}',
75
+ notEnoughArgs: 'Expect requires at least {{ amount }} argument{{ s }}',
76
+ modifierUnknown: 'Expect has an unknown modifier',
77
+ matcherNotFound: 'Expect must have a corresponding matcher call',
78
+ matcherNotCalled: 'Matchers must be called to assert',
79
+ asyncMustBeAwaited: 'Async assertions must be awaited{{ orReturned }}',
80
+ promisesWithAsyncAssertionsMustBeAwaited: 'Promises which return async assertions must be awaited{{ orReturned }}'
81
+ },
82
+ fixable: 'code',
83
+ type: 'suggestion',
84
+ schema: [{
85
+ type: 'object',
86
+ properties: {
87
+ alwaysAwait: {
88
+ type: 'boolean',
89
+ default: false
90
+ },
91
+ asyncMatchers: {
92
+ type: 'array',
93
+ items: {
94
+ type: 'string'
95
+ }
96
+ },
97
+ minArgs: {
98
+ type: 'number',
99
+ minimum: 0
100
+ },
101
+ maxArgs: {
102
+ type: 'number',
103
+ minimum: 1
104
+ }
105
+ },
106
+ additionalProperties: false
107
+ }]
108
+ },
109
+ defaultOptions: [{
110
+ alwaysAwait: false,
111
+ asyncMatchers: defaultAsyncMatchers,
112
+ minArgs: 1,
113
+ maxArgs: 1
114
+ }],
115
+ create(context, [{
116
+ alwaysAwait,
117
+ asyncMatchers = defaultAsyncMatchers,
118
+ minArgs = 1,
119
+ maxArgs = 1
120
+ }]) {
121
+ // Context state
122
+ const arrayExceptions = new Set();
123
+ const descriptors = [];
124
+ const pushPromiseArrayException = loc => arrayExceptions.add(promiseArrayExceptionKey(loc));
125
+
126
+ /**
127
+ * Promise method that accepts an array of promises,
128
+ * (eg. Promise.all), will throw warnings for the each
129
+ * unawaited or non-returned promise. To avoid throwing
130
+ * multiple warnings, we check if there is a warning in
131
+ * the given location.
132
+ */
133
+ const promiseArrayExceptionExists = loc => arrayExceptions.has(promiseArrayExceptionKey(loc));
134
+ const findTopMostMemberExpression = node => {
135
+ let topMostMemberExpression = node;
136
+ let {
137
+ parent
138
+ } = node;
139
+ while (parent) {
140
+ if (parent.type !== _utils.AST_NODE_TYPES.MemberExpression) {
141
+ break;
142
+ }
143
+ topMostMemberExpression = parent;
144
+ parent = parent.parent;
145
+ }
146
+ return topMostMemberExpression;
147
+ };
148
+ return {
149
+ CallExpression(node) {
150
+ const jestFnCall = (0, _utils2.parseJestFnCallWithReason)(node, context);
151
+ if (typeof jestFnCall === 'string') {
152
+ const reportingNode = node.parent.type === _utils.AST_NODE_TYPES.MemberExpression ? findTopMostMemberExpression(node.parent).property : node;
153
+ if (jestFnCall === 'matcher-not-found') {
154
+ context.report({
155
+ messageId: 'matcherNotFound',
156
+ node: reportingNode
157
+ });
158
+ return;
159
+ }
160
+ if (jestFnCall === 'matcher-not-called') {
161
+ context.report({
162
+ messageId: (0, _utils2.isSupportedAccessor)(reportingNode) && _utils2.ModifierName.hasOwnProperty((0, _utils2.getAccessorValue)(reportingNode)) ? 'matcherNotFound' : 'matcherNotCalled',
163
+ node: reportingNode
164
+ });
165
+ }
166
+ if (jestFnCall === 'modifier-unknown') {
167
+ context.report({
168
+ messageId: 'modifierUnknown',
169
+ node: reportingNode
170
+ });
171
+ return;
172
+ }
173
+ return;
174
+ } else if (jestFnCall?.type !== 'expect') {
175
+ return;
176
+ }
177
+ const {
178
+ parent: expect
179
+ } = jestFnCall.head.node;
180
+ if (expect.type !== _utils.AST_NODE_TYPES.CallExpression) {
181
+ return;
182
+ }
183
+ if (expect.arguments.length < minArgs) {
184
+ const expectLength = (0, _utils2.getAccessorValue)(jestFnCall.head.node).length;
185
+ const loc = {
186
+ start: {
187
+ column: expect.loc.start.column + expectLength,
188
+ line: expect.loc.start.line
189
+ },
190
+ end: {
191
+ column: expect.loc.start.column + expectLength + 1,
192
+ line: expect.loc.start.line
193
+ }
194
+ };
195
+ context.report({
196
+ messageId: 'notEnoughArgs',
197
+ data: {
198
+ amount: minArgs,
199
+ s: minArgs === 1 ? '' : 's'
200
+ },
201
+ node: expect,
202
+ loc
203
+ });
204
+ }
205
+ if (expect.arguments.length > maxArgs) {
206
+ const {
207
+ start
208
+ } = expect.arguments[maxArgs].loc;
209
+ const {
210
+ end
211
+ } = expect.arguments[expect.arguments.length - 1].loc;
212
+ const loc = {
213
+ start,
214
+ end: {
215
+ column: end.column - 1,
216
+ line: end.line
217
+ }
218
+ };
219
+ context.report({
220
+ messageId: 'tooManyArgs',
221
+ data: {
222
+ amount: maxArgs,
223
+ s: maxArgs === 1 ? '' : 's'
224
+ },
225
+ node: expect,
226
+ loc
227
+ });
228
+ }
229
+ const {
230
+ matcher
231
+ } = jestFnCall;
232
+ const parentNode = matcher.parent.parent;
233
+ const shouldBeAwaited = jestFnCall.modifiers.some(nod => (0, _utils2.getAccessorValue)(nod) !== 'not') || asyncMatchers.includes((0, _utils2.getAccessorValue)(matcher));
234
+ if (!parentNode.parent || !shouldBeAwaited) {
235
+ return;
236
+ }
237
+ /**
238
+ * If parent node is an array expression, we'll report the warning,
239
+ * for the array object, not for each individual assertion.
240
+ */
241
+ const isParentArrayExpression = parentNode.parent.type === _utils.AST_NODE_TYPES.ArrayExpression;
242
+ /**
243
+ * An async assertion can be chained with `then` or `catch` statements.
244
+ * In that case our target CallExpression node is the one with
245
+ * the last `then` or `catch` statement.
246
+ */
247
+ const targetNode = getParentIfThenified(parentNode);
248
+ const finalNode = findPromiseCallExpressionNode(targetNode) || targetNode;
249
+ if (finalNode.parent &&
250
+ // If node is not awaited or returned
251
+ !isAcceptableReturnNode(finalNode.parent, !alwaysAwait) &&
252
+ // if we didn't warn user already
253
+ !promiseArrayExceptionExists(finalNode.loc)) {
254
+ descriptors.push({
255
+ node: finalNode,
256
+ messageId: targetNode === finalNode ? 'asyncMustBeAwaited' : 'promisesWithAsyncAssertionsMustBeAwaited'
257
+ });
258
+ }
259
+ if (isParentArrayExpression) {
260
+ pushPromiseArrayException(finalNode.loc);
261
+ }
262
+ },
263
+ 'Program:exit'() {
264
+ const fixes = [];
265
+ descriptors.forEach(({
266
+ node,
267
+ messageId
268
+ }, index) => {
269
+ const orReturned = alwaysAwait ? '' : ' or returned';
270
+ context.report({
271
+ loc: node.loc,
272
+ data: {
273
+ orReturned
274
+ },
275
+ messageId,
276
+ node,
277
+ fix(fixer) {
278
+ const functionExpression = findFirstFunctionExpression(node);
279
+ if (!functionExpression) {
280
+ return null;
281
+ }
282
+ const foundAsyncFixer = fixes.some(fix => fix.text === 'async ');
283
+ if (!functionExpression.async && !foundAsyncFixer) {
284
+ const targetFunction = getNormalizeFunctionExpression(functionExpression);
285
+ fixes.push(fixer.insertTextBefore(targetFunction, 'async '));
286
+ }
287
+ const returnStatement = node.parent?.type === _utils.AST_NODE_TYPES.ReturnStatement ? node.parent : null;
288
+ if (alwaysAwait && returnStatement) {
289
+ const sourceCodeText = context.sourceCode.getText(returnStatement);
290
+ const replacedText = sourceCodeText.replace('return', 'await');
291
+ fixes.push(fixer.replaceText(returnStatement, replacedText));
292
+ } else {
293
+ fixes.push(fixer.insertTextBefore(node, 'await '));
294
+ }
295
+ return index === descriptors.length - 1 ? fixes : null;
296
+ }
297
+ });
298
+ });
299
+ }
300
+ };
301
+ }
302
+ });
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _fs = require("fs");
8
+ var _path = _interopRequireDefault(require("path"));
9
+ var _utils = require("@typescript-eslint/utils");
10
+ var _utils2 = require("./utils");
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ var _default = exports.default = (0, _utils2.createRule)({
13
+ name: __filename,
14
+ meta: {
15
+ type: 'problem',
16
+ docs: {
17
+ description: 'Disallow mocking of non-existing module paths'
18
+ },
19
+ messages: {
20
+ invalidMockModulePath: 'Module path {{ moduleName }} does not exist or is not exported'
21
+ },
22
+ schema: [{
23
+ type: 'object',
24
+ properties: {
25
+ moduleFileExtensions: {
26
+ type: 'array',
27
+ items: {
28
+ type: 'string'
29
+ },
30
+ additionalItems: false
31
+ }
32
+ },
33
+ additionalProperties: false
34
+ }]
35
+ },
36
+ defaultOptions: [{
37
+ moduleFileExtensions: ['.js', '.ts', '.tsx', '.jsx', '.json']
38
+ }],
39
+ create(context, [{
40
+ moduleFileExtensions = ['.js', '.ts', '.tsx', '.jsx', '.json']
41
+ }]) {
42
+ return {
43
+ CallExpression(node) {
44
+ if (node.callee.type !== _utils.AST_NODE_TYPES.MemberExpression) {
45
+ return;
46
+ }
47
+ if (!node.arguments.length || !(0, _utils2.isTypeOfJestFnCall)(node, context, ['jest']) || !((0, _utils2.isSupportedAccessor)(node.callee.property) && ['mock', 'doMock'].includes((0, _utils2.getAccessorValue)(node.callee.property)))) {
48
+ return;
49
+ }
50
+ const moduleName = (0, _utils2.findModuleName)(node.arguments[0]);
51
+ if (!moduleName) {
52
+ return;
53
+ }
54
+ try {
55
+ if (!moduleName.value.startsWith('.')) {
56
+ require.resolve(moduleName.value);
57
+ return;
58
+ }
59
+ const resolvedModulePath = _path.default.resolve(_path.default.dirname(context.filename), moduleName.value);
60
+ const hasPossiblyModulePaths = ['', ...moduleFileExtensions].some(ext => {
61
+ try {
62
+ (0, _fs.statSync)(`${resolvedModulePath}${ext}`);
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ });
68
+ if (hasPossiblyModulePaths) {
69
+ return;
70
+ }
71
+ } catch (err) {
72
+ const castedErr = err;
73
+
74
+ // Reports unexpected issues when attempt to verify mocked module path.
75
+ // The list of possible errors is non-exhaustive.
76
+ if (castedErr.code !== 'MODULE_NOT_FOUND' && castedErr.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
77
+ throw new Error(`Error when trying to validate mock module path from \`jest.mock\`: ${err}`);
78
+ }
79
+ }
80
+ context.report({
81
+ messageId: 'invalidMockModulePath',
82
+ data: {
83
+ moduleName: moduleName.raw
84
+ },
85
+ node
86
+ });
87
+ }
88
+ };
89
+ }
90
+ });
@@ -0,0 +1,252 @@
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 trimFXprefix = word => ['f', 'x'].includes(word.charAt(0)) ? word.substring(1) : word;
10
+ const doesBinaryExpressionContainStringNode = binaryExp => {
11
+ if ((0, _utils2.isStringNode)(binaryExp.right)) {
12
+ return true;
13
+ }
14
+ if (binaryExp.left.type === _utils.AST_NODE_TYPES.BinaryExpression) {
15
+ return doesBinaryExpressionContainStringNode(binaryExp.left);
16
+ }
17
+ return (0, _utils2.isStringNode)(binaryExp.left);
18
+ };
19
+ const quoteStringValue = node => node.type === _utils.AST_NODE_TYPES.TemplateLiteral ? `\`${node.quasis[0].value.raw}\`` : node.raw;
20
+ const compileMatcherPattern = matcherMaybeWithMessage => {
21
+ const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage];
22
+ return [new RegExp(matcher, 'u'), message];
23
+ };
24
+ const compileMatcherPatterns = matchers => {
25
+ if (typeof matchers === 'string' || Array.isArray(matchers)) {
26
+ const compiledMatcher = compileMatcherPattern(matchers);
27
+ return {
28
+ describe: compiledMatcher,
29
+ test: compiledMatcher,
30
+ it: compiledMatcher
31
+ };
32
+ }
33
+ return {
34
+ describe: matchers.describe ? compileMatcherPattern(matchers.describe) : null,
35
+ test: matchers.test ? compileMatcherPattern(matchers.test) : null,
36
+ it: matchers.it ? compileMatcherPattern(matchers.it) : null
37
+ };
38
+ };
39
+
40
+ // %p - pretty-format.
41
+ // %s- String.
42
+ // %d- Number.
43
+ // %i - Integer.
44
+ // %f - Floating point value.
45
+ // %j - JSON.
46
+ // %o - Object.
47
+ // %# - Index of the test case.
48
+ // %$ - Number of the test case.
49
+ // %% - single percent sign ('%'). This does not consume an argument.
50
+
51
+ // const re = /%([^psdifjo#$%])/u;
52
+
53
+ const MatcherAndMessageSchema = {
54
+ type: 'array',
55
+ items: {
56
+ type: 'string'
57
+ },
58
+ minItems: 1,
59
+ maxItems: 2,
60
+ additionalItems: false
61
+ };
62
+ var _default = exports.default = (0, _utils2.createRule)({
63
+ name: __filename,
64
+ meta: {
65
+ docs: {
66
+ description: 'Enforce valid titles'
67
+ },
68
+ messages: {
69
+ titleMustBeString: 'Title must be a string',
70
+ emptyTitle: '{{ jestFunctionName }} should not have an empty title',
71
+ duplicatePrefix: 'should not have duplicate prefix',
72
+ accidentalSpace: 'should not have leading or trailing spaces',
73
+ disallowedWord: '"{{ word }}" is not allowed in test titles',
74
+ mustNotMatch: '{{ jestFunctionName }} should not match {{ pattern }}',
75
+ mustMatch: '{{ jestFunctionName }} should match {{ pattern }}',
76
+ mustNotMatchCustom: '{{ message }}',
77
+ mustMatchCustom: '{{ message }}',
78
+ invalidEachSpecifier: '"{{ specifier }}" is not a valid format specifier'
79
+ },
80
+ type: 'suggestion',
81
+ schema: [{
82
+ type: 'object',
83
+ properties: {
84
+ ignoreSpaces: {
85
+ type: 'boolean',
86
+ default: false
87
+ },
88
+ ignoreTypeOfDescribeName: {
89
+ type: 'boolean',
90
+ default: false
91
+ },
92
+ ignoreTypeOfTestName: {
93
+ type: 'boolean',
94
+ default: false
95
+ },
96
+ disallowedWords: {
97
+ type: 'array',
98
+ items: {
99
+ type: 'string'
100
+ }
101
+ }
102
+ },
103
+ patternProperties: {
104
+ [/^must(?:Not)?Match$/u.source]: {
105
+ oneOf: [{
106
+ type: 'string'
107
+ }, MatcherAndMessageSchema, {
108
+ type: 'object',
109
+ // @ts-expect-error https://github.com/eslint/eslint/discussions/17573
110
+ propertyNames: {
111
+ enum: ['describe', 'test', 'it']
112
+ },
113
+ additionalProperties: {
114
+ oneOf: [{
115
+ type: 'string'
116
+ }, MatcherAndMessageSchema]
117
+ }
118
+ }]
119
+ }
120
+ },
121
+ additionalProperties: false
122
+ }],
123
+ fixable: 'code'
124
+ },
125
+ defaultOptions: [{
126
+ ignoreSpaces: false,
127
+ ignoreTypeOfDescribeName: false,
128
+ ignoreTypeOfTestName: false,
129
+ disallowedWords: []
130
+ }],
131
+ create(context, [{
132
+ ignoreSpaces,
133
+ ignoreTypeOfDescribeName,
134
+ ignoreTypeOfTestName,
135
+ disallowedWords = [],
136
+ mustNotMatch,
137
+ mustMatch
138
+ }]) {
139
+ const disallowedWordsRegexp = new RegExp(`\\b(${disallowedWords.join('|')})\\b`, 'iu');
140
+ const mustNotMatchPatterns = compileMatcherPatterns(mustNotMatch ?? {});
141
+ const mustMatchPatterns = compileMatcherPatterns(mustMatch ?? {});
142
+ return {
143
+ CallExpression(node) {
144
+ const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
145
+ if (jestFnCall?.type !== 'describe' && jestFnCall?.type !== 'test') {
146
+ return;
147
+ }
148
+ const [argument] = node.arguments;
149
+ if (!argument) {
150
+ return;
151
+ }
152
+ if (!(0, _utils2.isStringNode)(argument)) {
153
+ if (argument.type === _utils.AST_NODE_TYPES.BinaryExpression && doesBinaryExpressionContainStringNode(argument)) {
154
+ return;
155
+ }
156
+ if (!(jestFnCall.type === 'describe' && ignoreTypeOfDescribeName || jestFnCall.type === 'test' && ignoreTypeOfTestName) && argument.type !== _utils.AST_NODE_TYPES.TemplateLiteral) {
157
+ context.report({
158
+ messageId: 'titleMustBeString',
159
+ loc: argument.loc
160
+ });
161
+ }
162
+ return;
163
+ }
164
+ const title = (0, _utils2.getStringValue)(argument);
165
+ if (!title) {
166
+ context.report({
167
+ messageId: 'emptyTitle',
168
+ data: {
169
+ jestFunctionName: jestFnCall.type === 'describe' ? _utils2.DescribeAlias.describe : _utils2.TestCaseName.test
170
+ },
171
+ node
172
+ });
173
+ return;
174
+ }
175
+
176
+ // check array-based .each titles for invalid printf specifiers
177
+ if (jestFnCall.members.find(s => (0, _utils2.isSupportedAccessor)(s, 'each'))?.parent.parent.type === _utils.AST_NODE_TYPES.CallExpression) {
178
+ const [unknownSpecifier] = /%[^psdifjo#$%]/u.exec(title.replaceAll('%%', '')) ?? [];
179
+ if (unknownSpecifier) {
180
+ context.report({
181
+ messageId: 'invalidEachSpecifier',
182
+ data: {
183
+ specifier: unknownSpecifier
184
+ },
185
+ node: argument
186
+ });
187
+ }
188
+ }
189
+ if (disallowedWords.length > 0) {
190
+ const disallowedMatch = disallowedWordsRegexp.exec(title);
191
+ if (disallowedMatch) {
192
+ context.report({
193
+ data: {
194
+ word: disallowedMatch[1]
195
+ },
196
+ messageId: 'disallowedWord',
197
+ node: argument
198
+ });
199
+ return;
200
+ }
201
+ }
202
+ if (ignoreSpaces === false && title.trim().length !== title.length) {
203
+ context.report({
204
+ messageId: 'accidentalSpace',
205
+ node: argument,
206
+ fix: fixer => [fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]) +?/u, '$1').replace(/ +?([`'"])$/u, '$1'))]
207
+ });
208
+ }
209
+ const unprefixedName = trimFXprefix(jestFnCall.name);
210
+ const [firstWord] = title.split(' ');
211
+ if (firstWord.toLowerCase() === unprefixedName) {
212
+ context.report({
213
+ messageId: 'duplicatePrefix',
214
+ node: argument,
215
+ fix: fixer => [fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]).+? /u, '$1'))]
216
+ });
217
+ }
218
+ const jestFunctionName = unprefixedName;
219
+ const [mustNotMatchPattern, mustNotMatchMessage] = mustNotMatchPatterns[jestFunctionName] ?? [];
220
+ if (mustNotMatchPattern) {
221
+ if (mustNotMatchPattern.test(title)) {
222
+ context.report({
223
+ messageId: mustNotMatchMessage ? 'mustNotMatchCustom' : 'mustNotMatch',
224
+ node: argument,
225
+ data: {
226
+ jestFunctionName,
227
+ pattern: mustNotMatchPattern,
228
+ message: mustNotMatchMessage
229
+ }
230
+ });
231
+ return;
232
+ }
233
+ }
234
+ const [mustMatchPattern, mustMatchMessage] = mustMatchPatterns[jestFunctionName] ?? [];
235
+ if (mustMatchPattern) {
236
+ if (!mustMatchPattern.test(title)) {
237
+ context.report({
238
+ messageId: mustMatchMessage ? 'mustMatchCustom' : 'mustMatch',
239
+ node: argument,
240
+ data: {
241
+ jestFunctionName,
242
+ pattern: mustMatchPattern,
243
+ message: mustMatchMessage
244
+ }
245
+ });
246
+ return;
247
+ }
248
+ }
249
+ }
250
+ };
251
+ }
252
+ });