@blumintinc/eslint-plugin-blumint 1.13.0 → 1.14.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 (186) hide show
  1. package/README.md +170 -139
  2. package/lib/index.js +190 -1
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  5. package/lib/rules/consistent-callback-naming.js +1 -1
  6. package/lib/rules/dynamic-https-errors.js +3 -4
  7. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  8. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
  9. package/lib/rules/enforce-callback-memo.js +1 -1
  10. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  11. package/lib/rules/enforce-console-error.js +35 -7
  12. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  13. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  14. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  15. package/lib/rules/enforce-early-destructuring.js +980 -0
  16. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  17. package/lib/rules/enforce-empty-object-check.js +502 -0
  18. package/lib/rules/enforce-exported-function-types.js +1 -1
  19. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  20. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  21. package/lib/rules/enforce-firestore-facade.js +4 -2
  22. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  23. package/lib/rules/enforce-global-constants.js +1 -1
  24. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  25. package/lib/rules/enforce-memoize-async.js +2 -6
  26. package/lib/rules/enforce-memoize-getters.js +39 -15
  27. package/lib/rules/enforce-microdiff.js +1 -1
  28. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  29. package/lib/rules/enforce-positive-naming.js +4 -0
  30. package/lib/rules/enforce-props-argument-name.js +2 -2
  31. package/lib/rules/enforce-safe-stringify.js +1 -1
  32. package/lib/rules/enforce-serializable-params.js +3 -3
  33. package/lib/rules/enforce-singular-type-names.js +1 -1
  34. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  35. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  36. package/lib/rules/enforce-storage-context.d.ts +9 -0
  37. package/lib/rules/enforce-storage-context.js +646 -0
  38. package/lib/rules/enforce-timestamp-now.js +23 -7
  39. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  40. package/lib/rules/enforce-transform-memoization.js +416 -0
  41. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  42. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  43. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  44. package/lib/rules/enforce-verb-noun-naming.js +5 -4
  45. package/lib/rules/ensure-pointer-events-none.js +28 -2
  46. package/lib/rules/export-if-in-doubt.js +27 -4
  47. package/lib/rules/extract-global-constants.js +40 -13
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  49. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  50. package/lib/rules/flatten-push-calls.d.ts +2 -0
  51. package/lib/rules/flatten-push-calls.js +428 -0
  52. package/lib/rules/global-const-style.js +50 -19
  53. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  54. package/lib/rules/jsdoc-above-field.js +208 -0
  55. package/lib/rules/key-only-outermost-element.js +21 -3
  56. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  57. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  58. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  59. package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
  60. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  61. package/lib/rules/memo-nested-react-components.js +410 -0
  62. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  63. package/lib/rules/no-always-true-false-conditions.js +68 -8
  64. package/lib/rules/no-array-length-in-deps.js +9 -3
  65. package/lib/rules/no-async-foreach.js +210 -12
  66. package/lib/rules/no-circular-references.js +18 -17
  67. package/lib/rules/no-class-instance-destructuring.js +55 -12
  68. package/lib/rules/no-complex-cloud-params.js +7 -3
  69. package/lib/rules/no-compositing-layer-props.js +2 -2
  70. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  71. package/lib/rules/no-console-error.d.ts +9 -0
  72. package/lib/rules/no-console-error.js +525 -0
  73. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  74. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  75. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  76. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  77. package/lib/rules/no-entire-object-hook-deps.js +4 -6
  78. package/lib/rules/no-excessive-parent-chain.js +1 -1
  79. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  80. package/lib/rules/no-explicit-return-type.js +215 -26
  81. package/lib/rules/no-filter-without-return.js +5 -1
  82. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  83. package/lib/rules/no-firestore-jest-mock.js +126 -8
  84. package/lib/rules/no-firestore-object-arrays.js +67 -12
  85. package/lib/rules/no-handler-suffix.d.ts +12 -0
  86. package/lib/rules/no-handler-suffix.js +305 -0
  87. package/lib/rules/no-hungarian.js +1 -1
  88. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  89. package/lib/rules/no-inline-component-prop.js +456 -0
  90. package/lib/rules/no-jsx-in-hooks.js +6 -1
  91. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  92. package/lib/rules/no-margin-properties.js +6 -6
  93. package/lib/rules/no-memoize-on-static.js +9 -1
  94. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  95. package/lib/rules/no-misused-switch-case.js +22 -1
  96. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  97. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  98. package/lib/rules/no-mock-firebase-admin.js +5 -2
  99. package/lib/rules/no-object-values-on-strings.js +10 -2
  100. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  101. package/lib/rules/no-passthrough-getters.js +38 -2
  102. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  103. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  104. package/lib/rules/no-redundant-param-types.js +16 -6
  105. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  106. package/lib/rules/no-redundant-this-params.js +459 -0
  107. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  108. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  109. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  110. package/lib/rules/no-restricted-properties-fix.js +11 -10
  111. package/lib/rules/no-separate-loading-state.js +7 -16
  112. package/lib/rules/no-stale-state-across-await.js +1 -1
  113. package/lib/rules/no-type-assertion-returns.js +43 -49
  114. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  115. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  116. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  117. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  118. package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
  119. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  120. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  121. package/lib/rules/no-unpinned-dependencies.js +36 -5
  122. package/lib/rules/no-unused-props.d.ts +2 -2
  123. package/lib/rules/no-unused-props.js +295 -91
  124. package/lib/rules/no-unused-usestate.js +6 -2
  125. package/lib/rules/no-useless-fragment.js +28 -2
  126. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  127. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  128. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  129. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  130. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  131. package/lib/rules/omit-index-html.d.ts +2 -1
  132. package/lib/rules/omit-index-html.js +62 -7
  133. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  134. package/lib/rules/parallelize-async-operations.js +24 -5
  135. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  136. package/lib/rules/prefer-batch-operations.js +32 -5
  137. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  138. package/lib/rules/prefer-clone-deep.js +3 -3
  139. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  140. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  141. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  142. package/lib/rules/prefer-docsetter-setall.js +243 -0
  143. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  144. package/lib/rules/prefer-fragment-component.js +1 -1
  145. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  146. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  147. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  148. package/lib/rules/prefer-global-router-state-key.js +88 -13
  149. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  150. package/lib/rules/prefer-memoized-props.js +445 -0
  151. package/lib/rules/prefer-next-dynamic.js +60 -69
  152. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
  153. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  154. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  155. package/lib/rules/prefer-params-over-parent-id.js +220 -171
  156. package/lib/rules/prefer-settings-object.js +2 -2
  157. package/lib/rules/prefer-type-over-interface.js +7 -2
  158. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  159. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  160. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  161. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  162. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  163. package/lib/rules/prevent-children-clobber.js +536 -0
  164. package/lib/rules/react-memoize-literals.d.ts +4 -0
  165. package/lib/rules/react-memoize-literals.js +495 -0
  166. package/lib/rules/react-usememo-should-be-component.js +2 -2
  167. package/lib/rules/require-hooks-default-params.js +17 -16
  168. package/lib/rules/require-https-error-cause.d.ts +4 -0
  169. package/lib/rules/require-https-error-cause.js +136 -0
  170. package/lib/rules/require-https-error.js +43 -21
  171. package/lib/rules/require-image-optimized.js +1 -1
  172. package/lib/rules/require-memo.js +1 -1
  173. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  174. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  175. package/lib/rules/require-usememo-object-literals.js +2 -3
  176. package/lib/rules/test-file-location-enforcement.js +1 -1
  177. package/lib/rules/use-latest-callback.js +6 -5
  178. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  179. package/lib/rules/vertically-group-related-functions.js +480 -0
  180. package/lib/utils/ASTHelpers.d.ts +13 -1
  181. package/lib/utils/ASTHelpers.js +24 -0
  182. package/lib/utils/getMethodName.d.ts +27 -0
  183. package/lib/utils/getMethodName.js +35 -0
  184. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  185. package/lib/utils/tsTypeClassifier.js +149 -0
  186. package/package.json +3 -1
@@ -11,6 +11,98 @@ const defaultOptions = {
11
11
  allowDtsFiles: true,
12
12
  allowFirestoreFunctionFiles: true,
13
13
  };
14
+ function getNameFromIdentifierOrLiteral(key) {
15
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
16
+ return key.name;
17
+ }
18
+ if (typeof key.value === 'string') {
19
+ return key.value;
20
+ }
21
+ return undefined;
22
+ }
23
+ function describeClassMethod(node) {
24
+ if (!node.computed &&
25
+ (node.key.type === utils_1.AST_NODE_TYPES.Identifier ||
26
+ (node.key.type === utils_1.AST_NODE_TYPES.Literal &&
27
+ typeof node.key.value === 'string'))) {
28
+ const name = getNameFromIdentifierOrLiteral(node.key);
29
+ if (name) {
30
+ return `class method "${name}"`;
31
+ }
32
+ }
33
+ return 'class method';
34
+ }
35
+ function describeMethodSignature(node) {
36
+ if (!node.computed &&
37
+ (node.key.type === utils_1.AST_NODE_TYPES.Identifier ||
38
+ (node.key.type === utils_1.AST_NODE_TYPES.Literal &&
39
+ typeof node.key.value === 'string'))) {
40
+ const name = getNameFromIdentifierOrLiteral(node.key);
41
+ if (name) {
42
+ return `interface method "${name}"`;
43
+ }
44
+ }
45
+ return 'interface method';
46
+ }
47
+ function describeFunctionDeclaration(node) {
48
+ if (node.id?.name) {
49
+ return `function "${node.id.name}"`;
50
+ }
51
+ return 'function';
52
+ }
53
+ function describeTSDeclareFunction(node) {
54
+ if (node.id?.name) {
55
+ return `function "${node.id.name}"`;
56
+ }
57
+ return 'function';
58
+ }
59
+ function describeFunctionExpression(node) {
60
+ if (node.id?.name) {
61
+ return `function "${node.id.name}"`;
62
+ }
63
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
64
+ node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
65
+ return `function "${node.parent.id.name}"`;
66
+ }
67
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.Property &&
68
+ !node.parent.computed &&
69
+ (node.parent.key.type === utils_1.AST_NODE_TYPES.Identifier ||
70
+ (node.parent.key.type === utils_1.AST_NODE_TYPES.Literal &&
71
+ typeof node.parent.key.value === 'string'))) {
72
+ const name = getNameFromIdentifierOrLiteral(node.parent.key);
73
+ if (name) {
74
+ return `object method "${name}"`;
75
+ }
76
+ }
77
+ return 'function expression';
78
+ }
79
+ function describeArrowFunction(node) {
80
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
81
+ node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
82
+ return `arrow function "${node.parent.id.name}"`;
83
+ }
84
+ return 'arrow function';
85
+ }
86
+ function describeFunctionKind(node) {
87
+ switch (node.type) {
88
+ case utils_1.AST_NODE_TYPES.MethodDefinition:
89
+ return describeClassMethod(node);
90
+ case utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition:
91
+ return describeClassMethod(node);
92
+ case utils_1.AST_NODE_TYPES.TSMethodSignature:
93
+ return describeMethodSignature(node);
94
+ case utils_1.AST_NODE_TYPES.TSDeclareFunction:
95
+ return describeTSDeclareFunction(node);
96
+ case utils_1.AST_NODE_TYPES.FunctionDeclaration:
97
+ return describeFunctionDeclaration(node);
98
+ case utils_1.AST_NODE_TYPES.FunctionExpression:
99
+ return describeFunctionExpression(node);
100
+ case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
101
+ return describeArrowFunction(node);
102
+ default:
103
+ return 'function';
104
+ }
105
+ }
14
106
  function isRecursiveFunction(node) {
15
107
  const functionName = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration
16
108
  ? node.id?.name
@@ -63,16 +155,52 @@ function isOverloadedFunction(node) {
63
155
  const interfaceBody = node.parent;
64
156
  if (interfaceBody.type !== utils_1.AST_NODE_TYPES.TSInterfaceBody)
65
157
  return false;
66
- const methodName = node.key.type === utils_1.AST_NODE_TYPES.Identifier ? node.key.name : undefined;
158
+ if (node.computed)
159
+ return false;
160
+ const methodName = node.key.type === utils_1.AST_NODE_TYPES.Identifier ||
161
+ node.key.type === utils_1.AST_NODE_TYPES.Literal
162
+ ? getNameFromIdentifierOrLiteral(node.key)
163
+ : undefined;
67
164
  if (!methodName)
68
165
  return false;
69
166
  return (interfaceBody.body.filter((member) => member.type === utils_1.AST_NODE_TYPES.TSMethodSignature &&
70
- member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
71
- member.key.name === methodName).length > 1);
167
+ !member.computed &&
168
+ (member.key.type === utils_1.AST_NODE_TYPES.Identifier ||
169
+ member.key.type === utils_1.AST_NODE_TYPES.Literal) &&
170
+ getNameFromIdentifierOrLiteral(member.key) === methodName).length > 1);
171
+ }
172
+ return false;
173
+ }
174
+ function isOverloadedTsDeclareFunction(node) {
175
+ const functionName = node.id?.name;
176
+ if (!functionName)
177
+ return false;
178
+ let container = node.parent;
179
+ while (container) {
180
+ if (container.type === utils_1.AST_NODE_TYPES.Program ||
181
+ container.type === utils_1.AST_NODE_TYPES.TSModuleBlock) {
182
+ const declarations = container.body
183
+ .map((statement) => {
184
+ if (statement.type === utils_1.AST_NODE_TYPES.TSDeclareFunction) {
185
+ return statement;
186
+ }
187
+ if (statement.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration &&
188
+ statement.declaration?.type === utils_1.AST_NODE_TYPES.TSDeclareFunction) {
189
+ return statement.declaration;
190
+ }
191
+ return undefined;
192
+ })
193
+ .filter((value) => Boolean(value?.id?.name))
194
+ .filter((decl) => decl.id.name === functionName);
195
+ return declarations.length > 1;
196
+ }
197
+ container = container.parent;
72
198
  }
73
199
  return false;
74
200
  }
75
201
  function isInterfaceOrAbstractMethodSignature(node) {
202
+ if (node.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition)
203
+ return true;
76
204
  if (node.type === utils_1.AST_NODE_TYPES.TSMethodSignature)
77
205
  return true;
78
206
  if (node.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
@@ -133,7 +261,8 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
133
261
  },
134
262
  ],
135
263
  messages: {
136
- noExplicitReturnType: 'Explicit return type is not allowed. Let TypeScript infer it.',
264
+ noExplicitReturnTypeInferable: "What's wrong: {{functionKind}} has an explicit return type annotation. \u2192 Why it matters: it must be updated manually and can drift from what the implementation actually returns, hiding bugs behind a stale type. \u2192 How to fix: remove the return type annotation so TypeScript infers it from the implementation.",
265
+ noExplicitReturnTypeNonInferable: "What's wrong: {{functionKind}} has an explicit return type annotation but no implementation body. \u2192 Why it matters: TypeScript cannot infer the return type here; removing it widens the return type to `any`. \u2192 How to fix: keep the annotation, or provide an implementation body where inference can succeed.",
137
266
  },
138
267
  },
139
268
  defaultOptions: [defaultOptions],
@@ -141,58 +270,77 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
141
270
  const mergedOptions = { ...defaultOptions, ...options };
142
271
  const filename = context.getFilename();
143
272
  if ((mergedOptions.allowDtsFiles && filename.endsWith('.d.ts')) ||
144
- (mergedOptions.allowFirestoreFunctionFiles && filename.endsWith('.f.ts'))) {
273
+ (mergedOptions.allowFirestoreFunctionFiles &&
274
+ filename.endsWith('.f.ts'))) {
145
275
  return {};
146
276
  }
147
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
277
  function fixReturnType(fixer, node) {
149
- const returnType = node.returnType || node.value?.returnType;
278
+ // Some nodes expose returnType directly while others nest it under value.
279
+ const returnType = 'returnType' in node
280
+ ? node.returnType
281
+ : 'value' in node
282
+ ? node.value.returnType
283
+ : null;
150
284
  if (!returnType)
151
285
  return null;
152
- // Create a fix that removes the return type annotation
153
286
  return fixer.remove(returnType);
154
287
  }
155
288
  return {
156
289
  FunctionDeclaration(node) {
157
- if (!node.returnType)
290
+ const returnType = node.returnType;
291
+ if (!returnType)
158
292
  return;
159
293
  if (isTypeGuardFunction(node) ||
160
294
  (mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node))) {
161
295
  return;
162
296
  }
297
+ const isInferable = Boolean(node.body);
163
298
  context.report({
164
- node: node.returnType,
165
- messageId: 'noExplicitReturnType',
166
- fix: (fixer) => fixReturnType(fixer, node),
299
+ node: returnType,
300
+ messageId: isInferable
301
+ ? 'noExplicitReturnTypeInferable'
302
+ : 'noExplicitReturnTypeNonInferable',
303
+ data: { functionKind: describeFunctionKind(node) },
304
+ ...(isInferable
305
+ ? { fix: (fixer) => fixReturnType(fixer, node) }
306
+ : {}),
167
307
  });
168
308
  },
169
309
  FunctionExpression(node) {
170
- if (!node.returnType)
310
+ const returnType = node.returnType;
311
+ if (!returnType)
312
+ return;
313
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
171
314
  return;
315
+ }
172
316
  if (isTypeGuardFunction(node) ||
173
317
  (mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node))) {
174
318
  return;
175
319
  }
176
320
  context.report({
177
- node: node.returnType,
178
- messageId: 'noExplicitReturnType',
321
+ node: returnType,
322
+ messageId: 'noExplicitReturnTypeInferable',
323
+ data: { functionKind: describeFunctionKind(node) },
179
324
  fix: (fixer) => fixReturnType(fixer, node),
180
325
  });
181
326
  },
182
327
  ArrowFunctionExpression(node) {
183
- if (!node.returnType)
328
+ const returnType = node.returnType;
329
+ if (!returnType)
184
330
  return;
185
331
  if (isTypeGuardFunction(node)) {
186
332
  return;
187
333
  }
188
334
  context.report({
189
- node: node.returnType,
190
- messageId: 'noExplicitReturnType',
335
+ node: returnType,
336
+ messageId: 'noExplicitReturnTypeInferable',
337
+ data: { functionKind: describeFunctionKind(node) },
191
338
  fix: (fixer) => fixReturnType(fixer, node),
192
339
  });
193
340
  },
194
341
  TSMethodSignature(node) {
195
- if (!node.returnType)
342
+ const returnType = node.returnType;
343
+ if (!returnType)
196
344
  return;
197
345
  if (mergedOptions.allowInterfaceMethodSignatures) {
198
346
  return;
@@ -202,23 +350,64 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
202
350
  return;
203
351
  }
204
352
  context.report({
205
- node: node.returnType,
206
- messageId: 'noExplicitReturnType',
207
- fix: (fixer) => fixReturnType(fixer, node),
353
+ node: returnType,
354
+ messageId: 'noExplicitReturnTypeNonInferable',
355
+ data: { functionKind: describeFunctionKind(node) },
208
356
  });
209
357
  },
210
358
  MethodDefinition(node) {
211
- if (!node.value.returnType)
359
+ const returnType = node.value.returnType;
360
+ if (!returnType)
212
361
  return;
213
362
  if (isTypeGuardFunction(node.value) ||
214
363
  (mergedOptions.allowAbstractMethodSignatures &&
215
364
  isInterfaceOrAbstractMethodSignature(node))) {
216
365
  return;
217
366
  }
367
+ const isInferable = Boolean(node.value.body);
218
368
  context.report({
219
- node: node.value.returnType,
220
- messageId: 'noExplicitReturnType',
221
- fix: (fixer) => fixReturnType(fixer, node),
369
+ node: returnType,
370
+ messageId: isInferable
371
+ ? 'noExplicitReturnTypeInferable'
372
+ : 'noExplicitReturnTypeNonInferable',
373
+ data: { functionKind: describeFunctionKind(node) },
374
+ ...(isInferable
375
+ ? { fix: (fixer) => fixReturnType(fixer, node) }
376
+ : {}),
377
+ });
378
+ },
379
+ TSAbstractMethodDefinition(node) {
380
+ const returnType = node.value.returnType;
381
+ if (!returnType)
382
+ return;
383
+ if (isTypeGuardFunction(node.value) ||
384
+ (mergedOptions.allowAbstractMethodSignatures &&
385
+ isInterfaceOrAbstractMethodSignature(node))) {
386
+ return;
387
+ }
388
+ // Abstract methods never have bodies; they are always non-inferable and
389
+ // intentionally have no fixer.
390
+ context.report({
391
+ node: returnType,
392
+ messageId: 'noExplicitReturnTypeNonInferable',
393
+ data: { functionKind: describeFunctionKind(node) },
394
+ });
395
+ },
396
+ TSDeclareFunction(node) {
397
+ const returnType = node.returnType;
398
+ if (!returnType)
399
+ return;
400
+ if (isTypeGuardFunction(node)) {
401
+ return;
402
+ }
403
+ if (mergedOptions.allowOverloadedFunctions &&
404
+ isOverloadedTsDeclareFunction(node)) {
405
+ return;
406
+ }
407
+ context.report({
408
+ node: returnType,
409
+ messageId: 'noExplicitReturnTypeNonInferable',
410
+ data: { functionKind: describeFunctionKind(node) },
222
411
  });
223
412
  },
224
413
  };
@@ -15,9 +15,13 @@ exports.noFilterWithoutReturn = (0, createRule_1.createRule)({
15
15
  return;
16
16
  }
17
17
  if (!ASTHelpers_1.ASTHelpers.hasReturnStatement(body)) {
18
+ const filterCall = context.getSourceCode().getText(node.callee);
18
19
  context.report({
19
20
  node,
20
21
  messageId: 'unexpected',
22
+ data: {
23
+ filterCall,
24
+ },
21
25
  });
22
26
  }
23
27
  }
@@ -33,7 +37,7 @@ exports.noFilterWithoutReturn = (0, createRule_1.createRule)({
33
37
  },
34
38
  schema: [],
35
39
  messages: {
36
- unexpected: 'Array.filter callbacks with block statements must contain a return statement. Instead of `array.filter(x => { doSomething(x); })`, use `array.filter(x => { doSomething(x); return someCondition; })` or use implicit return `array.filter(x => someCondition)`.',
40
+ unexpected: 'Callback for {{filterCall}} uses braces but never returns a value, so filter receives undefined for every element and silently drops them all. Return the predicate result from inside the block (e.g., "return matches(item);") or use a concise arrow like {{filterCall}}((item) => matches(item)) to make the keep/remove condition explicit.',
37
41
  },
38
42
  },
39
43
  defaultOptions: [],
@@ -1 +1,2 @@
1
- export declare const noFirestoreJestMock: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noFirestoreJestMock", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const noFirestoreJestMock: TSESLint.RuleModule<"noFirestoreJestMock", [], TSESLint.RuleListener>;
@@ -1,8 +1,90 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.noFirestoreJestMock = void 0;
7
+ const path_1 = __importDefault(require("path"));
4
8
  const utils_1 = require("@typescript-eslint/utils");
5
9
  const createRule_1 = require("../utils/createRule");
10
+ const FIRESTORE_JEST_MOCK = 'firestore-jest-mock';
11
+ const MOCK_FIRESTORE_TARGET = '__test-utils__/mockFirestore';
12
+ const toPosixPath = (filePath) => filePath.replace(/\\/g, '/');
13
+ const ensureRelativeSpecifier = (specifier) => specifier.startsWith('.') ? specifier : `./${specifier}`;
14
+ const isWindowsDrivePath = (filePath) => /^[A-Za-z]:[\\/]/.test(filePath);
15
+ const isValidRelativePath = (relativePath) => !path_1.default.isAbsolute(relativePath) && !isWindowsDrivePath(relativePath);
16
+ const buildReplacementPath = (sourceFilePath, cwd) => {
17
+ const absoluteFilename = path_1.default.isAbsolute(sourceFilePath)
18
+ ? sourceFilePath
19
+ : path_1.default.join(cwd, sourceFilePath);
20
+ const targetPath = path_1.default.join(cwd, MOCK_FIRESTORE_TARGET);
21
+ const relativePath = path_1.default.relative(path_1.default.dirname(absoluteFilename), targetPath);
22
+ if (!isValidRelativePath(relativePath)) {
23
+ return '';
24
+ }
25
+ return ensureRelativeSpecifier(toPosixPath(relativePath));
26
+ };
27
+ const findVariableDeclarator = (node) => {
28
+ const { parent } = node;
29
+ if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
30
+ return parent;
31
+ }
32
+ if (parent?.type === utils_1.AST_NODE_TYPES.AwaitExpression &&
33
+ parent.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
34
+ return parent.parent;
35
+ }
36
+ return null;
37
+ };
38
+ const buildDestructuringFix = (fixer, pattern) => {
39
+ if (pattern.properties.length !== 1) {
40
+ return null;
41
+ }
42
+ const [property] = pattern.properties;
43
+ if (property.type !== utils_1.AST_NODE_TYPES.Property) {
44
+ return null;
45
+ }
46
+ if (property.value.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
47
+ return null;
48
+ }
49
+ if (property.value.type !== utils_1.AST_NODE_TYPES.Identifier) {
50
+ return null;
51
+ }
52
+ const localName = property.value.name;
53
+ const replacement = localName === 'mockFirestore'
54
+ ? '{ mockFirestore }'
55
+ : `{ mockFirestore: ${localName} }`;
56
+ return fixer.replaceText(pattern, replacement);
57
+ };
58
+ const constructImportStatement = (localName, replacementPath) => {
59
+ const binding = localName === 'mockFirestore'
60
+ ? 'mockFirestore'
61
+ : `mockFirestore as ${localName}`;
62
+ return `import { ${binding} } from '${replacementPath}';`;
63
+ };
64
+ const getSingleValueImportSpecifier = (node) => {
65
+ const valueSpecifiers = node.specifiers.filter((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
66
+ specifier.importKind !== 'type');
67
+ const hasTypeSpecifiers = node.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
68
+ specifier.importKind === 'type');
69
+ const hasUnsupportedSpecifier = node.specifiers.some((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
70
+ specifier.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier);
71
+ if (valueSpecifiers.length !== 1 ||
72
+ hasTypeSpecifiers ||
73
+ hasUnsupportedSpecifier) {
74
+ return null;
75
+ }
76
+ return valueSpecifiers[0];
77
+ };
78
+ const buildImportDeclarationFix = (node, replacementPath) => {
79
+ if (!replacementPath) {
80
+ return null;
81
+ }
82
+ const specifier = getSingleValueImportSpecifier(node);
83
+ if (!specifier) {
84
+ return null;
85
+ }
86
+ return constructImportStatement(specifier.local.name, replacementPath);
87
+ };
6
88
  exports.noFirestoreJestMock = (0, createRule_1.createRule)({
7
89
  name: 'no-firestore-jest-mock',
8
90
  meta: {
@@ -14,38 +96,72 @@ exports.noFirestoreJestMock = (0, createRule_1.createRule)({
14
96
  fixable: 'code',
15
97
  schema: [],
16
98
  messages: {
17
- noFirestoreJestMock: 'Do not import from firestore-jest-mock. Use mockFirestore from the centralized mock utility instead.',
99
+ noFirestoreJestMock: 'What\'s wrong: This test imports "{{moduleName}}" directly Why it matters: it bypasses the centralized mockFirestore helper that mirrors production schema and keeps seeding/cleanup consistent, which leads to inconsistent data and flaky tests → How to fix: import { mockFirestore } from "{{replacementPath}}" instead.',
18
100
  },
19
101
  },
20
102
  defaultOptions: [],
21
103
  create(context) {
22
- const filename = context.getFilename();
104
+ const sourceFilePath = context.getFilename();
23
105
  // Only apply rule to test files
24
- if (!filename.endsWith('.test.ts')) {
106
+ if (!sourceFilePath.endsWith('.test.ts')) {
25
107
  return {};
26
108
  }
109
+ const cwd = typeof context.getCwd === 'function' ? context.getCwd() : process.cwd();
110
+ const replacementPath = buildReplacementPath(sourceFilePath, cwd);
111
+ const messageReplacementPath = replacementPath ||
112
+ ensureRelativeSpecifier(toPosixPath(MOCK_FIRESTORE_TARGET));
113
+ const reportData = {
114
+ moduleName: FIRESTORE_JEST_MOCK,
115
+ replacementPath: messageReplacementPath,
116
+ };
27
117
  return {
28
118
  ImportDeclaration(node) {
29
119
  // Skip type imports completely
30
120
  if (node.importKind === 'type') {
31
121
  return;
32
122
  }
33
- if (node.source.value === 'firestore-jest-mock') {
123
+ if (node.source.value === FIRESTORE_JEST_MOCK) {
34
124
  context.report({
35
125
  node,
36
126
  messageId: 'noFirestoreJestMock',
127
+ data: reportData,
37
128
  fix: (fixer) => {
38
- return fixer.replaceText(node, `import { mockFirestore } from '../../../../../__test-utils__/mockFirestore';`);
129
+ const replacementImport = buildImportDeclarationFix(node, replacementPath);
130
+ if (!replacementImport) {
131
+ return null;
132
+ }
133
+ return fixer.replaceText(node, replacementImport);
39
134
  },
40
135
  });
41
136
  }
42
137
  },
43
138
  ImportExpression(node) {
44
139
  if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
45
- node.source.value === 'firestore-jest-mock') {
140
+ node.source.value === FIRESTORE_JEST_MOCK) {
141
+ const variableDeclarator = findVariableDeclarator(node);
46
142
  context.report({
47
143
  node,
48
144
  messageId: 'noFirestoreJestMock',
145
+ data: reportData,
146
+ fix: (fixer) => {
147
+ if (!replacementPath) {
148
+ return null;
149
+ }
150
+ if (!variableDeclarator) {
151
+ return null;
152
+ }
153
+ if (variableDeclarator.id.type !== utils_1.AST_NODE_TYPES.ObjectPattern) {
154
+ return null;
155
+ }
156
+ const destructuringFix = buildDestructuringFix(fixer, variableDeclarator.id);
157
+ if (!destructuringFix) {
158
+ return null;
159
+ }
160
+ return [
161
+ fixer.replaceText(node.source, `'${replacementPath}'`),
162
+ destructuringFix,
163
+ ];
164
+ },
49
165
  });
50
166
  }
51
167
  },
@@ -58,10 +174,11 @@ exports.noFirestoreJestMock = (0, createRule_1.createRule)({
58
174
  node.callee.property.name === 'mock' &&
59
175
  node.arguments.length > 0 &&
60
176
  node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
61
- node.arguments[0].value === 'firestore-jest-mock') {
177
+ node.arguments[0].value === FIRESTORE_JEST_MOCK) {
62
178
  context.report({
63
179
  node,
64
180
  messageId: 'noFirestoreJestMock',
181
+ data: reportData,
65
182
  });
66
183
  }
67
184
  // Check for require('firestore-jest-mock')
@@ -69,10 +186,11 @@ exports.noFirestoreJestMock = (0, createRule_1.createRule)({
69
186
  node.callee.name === 'require' &&
70
187
  node.arguments.length > 0 &&
71
188
  node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal &&
72
- node.arguments[0].value === 'firestore-jest-mock') {
189
+ node.arguments[0].value === FIRESTORE_JEST_MOCK) {
73
190
  context.report({
74
191
  node,
75
192
  messageId: 'noFirestoreJestMock',
193
+ data: reportData,
76
194
  });
77
195
  }
78
196
  },