@blumintinc/eslint-plugin-blumint 1.12.5 → 1.13.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 (142) hide show
  1. package/README.md +140 -99
  2. package/lib/index.js +111 -20
  3. package/lib/rules/array-methods-this-context.js +20 -2
  4. package/lib/rules/avoid-utils-directory.js +11 -2
  5. package/lib/rules/dynamic-https-errors.d.ts +3 -1
  6. package/lib/rules/dynamic-https-errors.js +48 -10
  7. package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
  8. package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +51 -95
  9. package/lib/rules/enforce-assert-throws.d.ts +3 -1
  10. package/lib/rules/enforce-assert-throws.js +493 -51
  11. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
  12. package/lib/rules/enforce-boolean-naming-prefixes.js +832 -39
  13. package/lib/rules/enforce-callable-types.js +4 -4
  14. package/lib/rules/enforce-callback-memo.js +143 -18
  15. package/lib/rules/enforce-centralized-mock-firestore.js +4 -1
  16. package/lib/rules/enforce-console-error.d.ts +3 -0
  17. package/lib/rules/enforce-console-error.js +250 -0
  18. package/lib/rules/enforce-css-media-queries.js +24 -46
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
  20. package/lib/rules/enforce-dynamic-imports.js +1 -1
  21. package/lib/rules/enforce-exported-function-types.js +3 -3
  22. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
  23. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
  24. package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
  25. package/lib/rules/enforce-firestore-facade.js +347 -157
  26. package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
  27. package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
  28. package/lib/rules/enforce-global-constants.d.ts +4 -1
  29. package/lib/rules/enforce-global-constants.js +240 -12
  30. package/lib/rules/enforce-memoize-async.d.ts +2 -1
  31. package/lib/rules/enforce-memoize-async.js +96 -27
  32. package/lib/rules/enforce-memoize-getters.d.ts +3 -0
  33. package/lib/rules/enforce-memoize-getters.js +123 -0
  34. package/lib/rules/enforce-microdiff.js +2 -1
  35. package/lib/rules/enforce-positive-naming.js +5 -7
  36. package/lib/rules/enforce-props-argument-name.d.ts +2 -7
  37. package/lib/rules/enforce-props-argument-name.js +165 -151
  38. package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
  39. package/lib/rules/enforce-props-naming-consistency.js +145 -0
  40. package/lib/rules/enforce-querykey-ts.d.ts +9 -0
  41. package/lib/rules/enforce-querykey-ts.js +274 -0
  42. package/lib/rules/enforce-singular-type-names.js +9 -5
  43. package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
  44. package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
  45. package/lib/rules/enforce-verb-noun-naming.js +85 -10
  46. package/lib/rules/extract-global-constants.js +57 -45
  47. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
  49. package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
  50. package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
  51. package/lib/rules/generic-starts-with-t.js +8 -2
  52. package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
  53. package/lib/rules/memoize-root-level-hocs.js +242 -0
  54. package/lib/rules/no-array-length-in-deps.d.ts +12 -0
  55. package/lib/rules/no-array-length-in-deps.js +308 -0
  56. package/lib/rules/no-async-array-filter.js +5 -2
  57. package/lib/rules/no-complex-cloud-params.js +15 -2
  58. package/lib/rules/no-compositing-layer-props.js +4 -2
  59. package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
  60. package/lib/rules/no-entire-object-hook-deps.js +317 -63
  61. package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
  62. package/lib/rules/no-excessive-parent-chain.js +273 -0
  63. package/lib/rules/no-firestore-object-arrays.js +345 -30
  64. package/lib/rules/no-hungarian.js +13 -13
  65. package/lib/rules/no-margin-properties.d.ts +7 -1
  66. package/lib/rules/no-margin-properties.js +16 -3
  67. package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
  68. package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
  69. package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
  70. package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
  71. package/lib/rules/no-passthrough-getters.d.ts +4 -0
  72. package/lib/rules/no-passthrough-getters.js +170 -0
  73. package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
  74. package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
  75. package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
  76. package/lib/rules/no-restricted-properties-fix.js +172 -0
  77. package/lib/rules/no-separate-loading-state.d.ts +8 -0
  78. package/lib/rules/no-separate-loading-state.js +142 -0
  79. package/lib/rules/no-stale-state-across-await.d.ts +1 -0
  80. package/lib/rules/no-stale-state-across-await.js +185 -0
  81. package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
  82. package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
  83. package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
  84. package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
  85. package/lib/rules/no-undefined-null-passthrough.js +166 -19
  86. package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
  87. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  88. package/lib/rules/no-unused-props.d.ts +3 -0
  89. package/lib/rules/no-unused-props.js +213 -78
  90. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  91. package/lib/rules/optimize-object-boolean-conditions.js +234 -0
  92. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  93. package/lib/rules/parallelize-async-operations.js +375 -0
  94. package/lib/rules/prefer-batch-operations.js +199 -33
  95. package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
  96. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  97. package/lib/rules/prefer-document-flattening.js +239 -0
  98. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  99. package/lib/rules/prefer-field-paths-in-transforms.js +250 -0
  100. package/lib/rules/prefer-fragment-component.js +3 -3
  101. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  102. package/lib/rules/prefer-global-router-state-key.js +230 -31
  103. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  104. package/lib/rules/prefer-next-dynamic.js +366 -0
  105. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  106. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
  107. package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
  108. package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
  109. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  110. package/lib/rules/prefer-params-over-parent-id.js +228 -0
  111. package/lib/rules/prefer-settings-object.js +23 -15
  112. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  113. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  114. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  115. package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
  116. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  117. package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
  118. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
  119. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  120. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  121. package/lib/rules/react-usememo-should-be-component.js +144 -78
  122. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  123. package/lib/rules/require-image-optimized.js +1 -1
  124. package/lib/rules/require-memo.js +11 -1
  125. package/lib/rules/require-usememo-object-literals.js +7 -1
  126. package/lib/rules/semantic-function-prefixes.js +38 -18
  127. package/lib/rules/sync-onwrite-name-func.js +5 -1
  128. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  129. package/lib/rules/test-file-location-enforcement.js +57 -0
  130. package/lib/rules/use-custom-link.js +6 -1
  131. package/lib/rules/use-custom-router.js +6 -1
  132. package/lib/rules/use-latest-callback.d.ts +3 -0
  133. package/lib/rules/use-latest-callback.js +270 -0
  134. package/lib/utils/ASTHelpers.js +10 -0
  135. package/lib/utils/createRule.js +1 -1
  136. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  137. package/lib/utils/ruleTester.d.ts +1 -0
  138. package/lib/utils/ruleTester.js +4 -1
  139. package/package.json +21 -14
  140. package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
  141. package/lib/rules/no-restricted-imports-dynamic.d.ts +0 -25
  142. package/lib/rules/no-restricted-imports-dynamic.js +0 -213
@@ -194,6 +194,308 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
194
194
  }
195
195
  return false;
196
196
  }
197
+ const isTypedCollectionReferenceCache = new Map();
198
+ function isTypedCollectionReference(node) {
199
+ if (!node)
200
+ return false;
201
+ if (isTypedCollectionReferenceCache.has(node)) {
202
+ return isTypedCollectionReferenceCache.get(node);
203
+ }
204
+ let result = false;
205
+ if (node.type === utils_1.AST_NODE_TYPES.CallExpression &&
206
+ node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
207
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
208
+ node.callee.property.name === 'collection' &&
209
+ node.typeParameters &&
210
+ node.typeParameters.params.length > 0) {
211
+ result = true;
212
+ }
213
+ else if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
214
+ result = checkMemberExpressionForCollectionReference(node);
215
+ }
216
+ else if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
217
+ result = checkIdentifierForCollectionReference(node);
218
+ }
219
+ else if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
220
+ result = checkCallExpressionForCollectionReference(node);
221
+ }
222
+ isTypedCollectionReferenceCache.set(node, result);
223
+ return result;
224
+ }
225
+ function checkMemberExpressionForCollectionReference(node) {
226
+ const obj = node.object;
227
+ const property = node.property;
228
+ // Handle this.property access
229
+ if (obj.type === utils_1.AST_NODE_TYPES.ThisExpression &&
230
+ property.type === utils_1.AST_NODE_TYPES.Identifier) {
231
+ const classNode = findParentClass(node);
232
+ if (classNode) {
233
+ const classProp = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
234
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
235
+ member.key.name === property.name);
236
+ if (classProp?.typeAnnotation) {
237
+ return hasCollectionReferenceType(classProp.typeAnnotation.typeAnnotation);
238
+ }
239
+ }
240
+ }
241
+ // Handle nested property access like this.collections.tasks
242
+ if (obj.type === utils_1.AST_NODE_TYPES.MemberExpression) {
243
+ const parentType = getTypeOfMemberExpression(obj);
244
+ if (parentType && property.type === utils_1.AST_NODE_TYPES.Identifier) {
245
+ // Check if the parent object has a property with CollectionReference type
246
+ return checkObjectPropertyForCollectionReference(parentType, property.name);
247
+ }
248
+ }
249
+ // Handle identifier.property access
250
+ if (obj.type === utils_1.AST_NODE_TYPES.Identifier &&
251
+ property.type === utils_1.AST_NODE_TYPES.Identifier) {
252
+ const objType = getTypeOfIdentifier(obj);
253
+ if (objType) {
254
+ return checkObjectPropertyForCollectionReference(objType, property.name);
255
+ }
256
+ }
257
+ // Handle getter methods like this.collection where collection is a getter
258
+ if (obj.type === utils_1.AST_NODE_TYPES.ThisExpression &&
259
+ property.type === utils_1.AST_NODE_TYPES.Identifier) {
260
+ const classNode = findParentClass(node);
261
+ if (classNode) {
262
+ const getter = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.MethodDefinition &&
263
+ member.kind === 'get' &&
264
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
265
+ member.key.name === property.name);
266
+ if (getter) {
267
+ // Check explicit return type
268
+ if (getter.value.returnType) {
269
+ return hasCollectionReferenceType(getter.value.returnType.typeAnnotation);
270
+ }
271
+ // Check return statement to infer type
272
+ if (getter.value.body &&
273
+ getter.value.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
274
+ const returnStmt = getter.value.body.body.find((stmt) => stmt.type === utils_1.AST_NODE_TYPES.ReturnStatement);
275
+ if (returnStmt?.argument?.type === utils_1.AST_NODE_TYPES.MemberExpression) {
276
+ return checkMemberExpressionForCollectionReference(returnStmt.argument);
277
+ }
278
+ }
279
+ }
280
+ }
281
+ }
282
+ return false;
283
+ }
284
+ function checkIdentifierForCollectionReference(node) {
285
+ // Check function parameters
286
+ const functionParam = findFunctionParameter(node);
287
+ if (functionParam &&
288
+ 'typeAnnotation' in functionParam &&
289
+ functionParam.typeAnnotation) {
290
+ return hasCollectionReferenceType(functionParam.typeAnnotation.typeAnnotation);
291
+ }
292
+ // Check variable declarations in current scope and parent scopes
293
+ if (findVariableDeclaration(node)) {
294
+ return true;
295
+ }
296
+ return false;
297
+ }
298
+ function checkCallExpressionForCollectionReference(node) {
299
+ // Check if this is a method call that returns CollectionReference
300
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
301
+ const obj = node.callee.object;
302
+ const property = node.callee.property;
303
+ if (property.type === utils_1.AST_NODE_TYPES.Identifier) {
304
+ // Check if this is a getter or method that returns CollectionReference
305
+ if (obj.type === utils_1.AST_NODE_TYPES.ThisExpression) {
306
+ const classNode = findParentClass(node);
307
+ if (classNode) {
308
+ const method = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.MethodDefinition &&
309
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
310
+ member.key.name === property.name &&
311
+ !!member.value.returnType);
312
+ if (method?.value.returnType) {
313
+ return hasCollectionReferenceType(method.value.returnType.typeAnnotation);
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+ return false;
320
+ }
321
+ function getTypeOfMemberExpression(node) {
322
+ if (node.object.type === utils_1.AST_NODE_TYPES.ThisExpression &&
323
+ node.property.type === utils_1.AST_NODE_TYPES.Identifier) {
324
+ const classNode = findParentClass(node);
325
+ if (classNode) {
326
+ const classProp = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
327
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
328
+ 'name' in node.property &&
329
+ member.key.name === node.property.name);
330
+ return classProp?.typeAnnotation?.typeAnnotation || null;
331
+ }
332
+ }
333
+ return null;
334
+ }
335
+ function getTypeOfIdentifier(node) {
336
+ const functionParam = findFunctionParameter(node);
337
+ if (functionParam &&
338
+ 'typeAnnotation' in functionParam &&
339
+ functionParam.typeAnnotation) {
340
+ return functionParam.typeAnnotation.typeAnnotation;
341
+ }
342
+ const varDecl = findVariableDeclarationNode(node);
343
+ if (varDecl?.typeAnnotation) {
344
+ return varDecl.typeAnnotation.typeAnnotation;
345
+ }
346
+ return null;
347
+ }
348
+ function checkObjectPropertyForCollectionReference(objectType, propertyName) {
349
+ if (objectType.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
350
+ const property = objectType.members.find((member) => member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
351
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
352
+ member.key.name === propertyName &&
353
+ !!member.typeAnnotation);
354
+ if (property?.typeAnnotation) {
355
+ return hasCollectionReferenceType(property.typeAnnotation.typeAnnotation);
356
+ }
357
+ }
358
+ // Handle Record<string, CollectionReference<T>> types
359
+ if (objectType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
360
+ objectType.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
361
+ objectType.typeName.name === 'Record' &&
362
+ objectType.typeParameters &&
363
+ objectType.typeParameters.params.length === 2) {
364
+ const valueType = objectType.typeParameters.params[1];
365
+ return hasCollectionReferenceType(valueType);
366
+ }
367
+ // Handle array types like CollectionReference<T>[]
368
+ if (objectType.type === utils_1.AST_NODE_TYPES.TSArrayType) {
369
+ return hasCollectionReferenceType(objectType.elementType);
370
+ }
371
+ return false;
372
+ }
373
+ function findFunctionParameter(node) {
374
+ const isFunctionNode = (n) => n.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
375
+ n.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
376
+ n.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression;
377
+ const findParamInFunction = (func) => {
378
+ const param = func.params.find((p) => p.type === utils_1.AST_NODE_TYPES.Identifier &&
379
+ p.name === node.name &&
380
+ 'typeAnnotation' in p &&
381
+ p.typeAnnotation !== undefined);
382
+ return param || null;
383
+ };
384
+ const functionScopes = [];
385
+ let current = node;
386
+ while (current) {
387
+ if (isFunctionNode(current)) {
388
+ functionScopes.push(current);
389
+ }
390
+ current = current.parent;
391
+ }
392
+ for (const func of functionScopes) {
393
+ const param = findParamInFunction(func);
394
+ if (param) {
395
+ return param;
396
+ }
397
+ }
398
+ return null;
399
+ }
400
+ function findVariableDeclaration(node) {
401
+ let current = node;
402
+ while (current) {
403
+ // Check in current scope
404
+ if (current.type === utils_1.AST_NODE_TYPES.Program ||
405
+ current.type === utils_1.AST_NODE_TYPES.BlockStatement ||
406
+ current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
407
+ current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
408
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
409
+ const varDecl = findVariableInScope(current, node.name);
410
+ if (varDecl && varDecl.typeAnnotation) {
411
+ return hasCollectionReferenceType(varDecl.typeAnnotation.typeAnnotation);
412
+ }
413
+ }
414
+ current = current.parent;
415
+ }
416
+ return false;
417
+ }
418
+ function findVariableDeclarationNode(node) {
419
+ let current = node;
420
+ while (current) {
421
+ if (current.type === utils_1.AST_NODE_TYPES.Program ||
422
+ current.type === utils_1.AST_NODE_TYPES.BlockStatement ||
423
+ current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
424
+ current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
425
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
426
+ const varDecl = findVariableInScope(current, node.name);
427
+ if (varDecl) {
428
+ return varDecl;
429
+ }
430
+ }
431
+ current = current.parent;
432
+ }
433
+ return null;
434
+ }
435
+ function findVariableInScope(scope, varName) {
436
+ const body = getNodeBody(scope);
437
+ if (!body)
438
+ return null;
439
+ for (const stmt of body) {
440
+ if (stmt.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
441
+ for (const decl of stmt.declarations) {
442
+ if (decl.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
443
+ decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
444
+ decl.id.name === varName &&
445
+ decl.id.typeAnnotation) {
446
+ return decl.id;
447
+ }
448
+ }
449
+ }
450
+ }
451
+ return null;
452
+ }
453
+ function getNodeBody(node) {
454
+ if (node.type === utils_1.AST_NODE_TYPES.Program) {
455
+ return node.body;
456
+ }
457
+ if (node.type === utils_1.AST_NODE_TYPES.BlockStatement) {
458
+ return node.body;
459
+ }
460
+ if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
461
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
462
+ return node.body?.body || null;
463
+ }
464
+ if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
465
+ return node.body.type === utils_1.AST_NODE_TYPES.BlockStatement
466
+ ? node.body.body
467
+ : null;
468
+ }
469
+ return null;
470
+ }
471
+ function hasCollectionReferenceType(typeNode) {
472
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
473
+ ((typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
474
+ typeNode.typeName.name === 'CollectionReference') ||
475
+ (typeNode.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName &&
476
+ typeNode.typeName.right.type === utils_1.AST_NODE_TYPES.Identifier &&
477
+ typeNode.typeName.right.name === 'CollectionReference')) &&
478
+ typeNode.typeParameters &&
479
+ typeNode.typeParameters.params.length > 0) {
480
+ return true;
481
+ }
482
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSUnionType) {
483
+ return typeNode.types.some(hasCollectionReferenceType);
484
+ }
485
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
486
+ return typeNode.types.some(hasCollectionReferenceType);
487
+ }
488
+ // TSParenthesizedType may appear even though AST_NODE_TYPES omits it
489
+ if (typeNode.type === 'TSParenthesizedType') {
490
+ const inner = typeNode
491
+ .typeAnnotation;
492
+ return hasCollectionReferenceType(inner);
493
+ }
494
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSArrayType) {
495
+ return hasCollectionReferenceType(typeNode.elementType);
496
+ }
497
+ return false;
498
+ }
197
499
  return {
198
500
  TSTypeReference(node) {
199
501
  if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
@@ -231,6 +533,39 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
231
533
  node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
232
534
  node.callee.property.name === 'doc') {
233
535
  const typeAnnotation = node.typeParameters;
536
+ const isOnTypedCollection = isTypedCollectionReference(node.callee.object);
537
+ // If this is a .doc() call on a typed CollectionReference,
538
+ // only check for invalid generics, not missing generics
539
+ if (isOnTypedCollection) {
540
+ if (typeAnnotation && hasInvalidType(typeAnnotation.params[0])) {
541
+ context.report({
542
+ node,
543
+ messageId: 'invalidGeneric',
544
+ data: { type: 'DocumentReference' },
545
+ });
546
+ }
547
+ return; // Skip the missing generic check for typed CollectionReference.doc() calls
548
+ }
549
+ // For standalone doc() calls or calls on untyped collections
550
+ if (!typeAnnotation) {
551
+ context.report({
552
+ node,
553
+ messageId: 'missingGeneric',
554
+ data: { type: 'DocumentReference' },
555
+ });
556
+ }
557
+ else if (hasInvalidType(typeAnnotation.params[0])) {
558
+ context.report({
559
+ node,
560
+ messageId: 'invalidGeneric',
561
+ data: { type: 'DocumentReference' },
562
+ });
563
+ }
564
+ }
565
+ // Check for standalone doc() function calls
566
+ else if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
567
+ node.callee.name === 'doc') {
568
+ const typeAnnotation = node.typeParameters;
234
569
  if (!typeAnnotation) {
235
570
  context.report({
236
571
  node,