@blumintinc/eslint-plugin-blumint 1.12.6 → 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 (258) hide show
  1. package/README.md +171 -99
  2. package/lib/index.js +300 -17
  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/class-methods-read-top-to-bottom.js +24 -5
  6. package/lib/rules/consistent-callback-naming.js +1 -1
  7. package/lib/rules/dynamic-https-errors.d.ts +3 -1
  8. package/lib/rules/dynamic-https-errors.js +48 -11
  9. package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
  10. package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +52 -96
  11. package/lib/rules/enforce-assert-throws.d.ts +3 -1
  12. package/lib/rules/enforce-assert-throws.js +493 -51
  13. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
  14. package/lib/rules/enforce-boolean-naming-prefixes.js +820 -39
  15. package/lib/rules/enforce-callable-types.js +4 -4
  16. package/lib/rules/enforce-callback-memo.js +143 -18
  17. package/lib/rules/enforce-centralized-mock-firestore.js +5 -2
  18. package/lib/rules/enforce-console-error.d.ts +3 -0
  19. package/lib/rules/enforce-console-error.js +278 -0
  20. package/lib/rules/enforce-css-media-queries.js +24 -46
  21. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  22. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  23. package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
  24. package/lib/rules/enforce-dynamic-imports.js +1 -1
  25. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  26. package/lib/rules/enforce-early-destructuring.js +980 -0
  27. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  28. package/lib/rules/enforce-empty-object-check.js +502 -0
  29. package/lib/rules/enforce-exported-function-types.js +4 -4
  30. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
  31. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +262 -0
  32. package/lib/rules/enforce-firestore-doc-ref-generic.js +350 -3
  33. package/lib/rules/enforce-firestore-facade.js +349 -157
  34. package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
  35. package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
  36. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  37. package/lib/rules/enforce-global-constants.d.ts +4 -1
  38. package/lib/rules/enforce-global-constants.js +240 -12
  39. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  40. package/lib/rules/enforce-memoize-async.d.ts +2 -1
  41. package/lib/rules/enforce-memoize-async.js +92 -27
  42. package/lib/rules/enforce-memoize-getters.d.ts +3 -0
  43. package/lib/rules/enforce-memoize-getters.js +147 -0
  44. package/lib/rules/enforce-microdiff.js +3 -2
  45. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  46. package/lib/rules/enforce-positive-naming.js +9 -7
  47. package/lib/rules/enforce-props-argument-name.d.ts +2 -7
  48. package/lib/rules/enforce-props-argument-name.js +165 -151
  49. package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
  50. package/lib/rules/enforce-props-naming-consistency.js +145 -0
  51. package/lib/rules/enforce-querykey-ts.d.ts +9 -0
  52. package/lib/rules/enforce-querykey-ts.js +274 -0
  53. package/lib/rules/enforce-safe-stringify.js +1 -1
  54. package/lib/rules/enforce-serializable-params.js +3 -3
  55. package/lib/rules/enforce-singular-type-names.js +10 -6
  56. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  57. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  58. package/lib/rules/enforce-storage-context.d.ts +9 -0
  59. package/lib/rules/enforce-storage-context.js +646 -0
  60. package/lib/rules/enforce-timestamp-now.js +23 -7
  61. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  62. package/lib/rules/enforce-transform-memoization.js +416 -0
  63. package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
  64. package/lib/rules/enforce-typescript-markdown-code-blocks.js +100 -0
  65. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  66. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  67. package/lib/rules/enforce-verb-noun-naming.js +90 -14
  68. package/lib/rules/ensure-pointer-events-none.js +28 -2
  69. package/lib/rules/export-if-in-doubt.js +27 -4
  70. package/lib/rules/extract-global-constants.js +92 -53
  71. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
  72. package/lib/rules/fast-deep-equal-over-microdiff.js +351 -83
  73. package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
  74. package/lib/rules/firestore-transaction-reads-before-writes.js +277 -0
  75. package/lib/rules/flatten-push-calls.d.ts +2 -0
  76. package/lib/rules/flatten-push-calls.js +428 -0
  77. package/lib/rules/generic-starts-with-t.js +8 -2
  78. package/lib/rules/global-const-style.js +50 -19
  79. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  80. package/lib/rules/jsdoc-above-field.js +208 -0
  81. package/lib/rules/key-only-outermost-element.js +21 -3
  82. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  83. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  84. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  85. package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
  86. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  87. package/lib/rules/memo-nested-react-components.js +410 -0
  88. package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
  89. package/lib/rules/memoize-root-level-hocs.js +242 -0
  90. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  91. package/lib/rules/no-always-true-false-conditions.js +68 -8
  92. package/lib/rules/no-array-length-in-deps.d.ts +12 -0
  93. package/lib/rules/no-array-length-in-deps.js +314 -0
  94. package/lib/rules/no-async-array-filter.js +5 -2
  95. package/lib/rules/no-async-foreach.js +210 -12
  96. package/lib/rules/no-circular-references.js +18 -17
  97. package/lib/rules/no-class-instance-destructuring.js +55 -12
  98. package/lib/rules/no-complex-cloud-params.js +22 -5
  99. package/lib/rules/no-compositing-layer-props.js +6 -4
  100. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  101. package/lib/rules/no-console-error.d.ts +9 -0
  102. package/lib/rules/no-console-error.js +525 -0
  103. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  104. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  105. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  106. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  107. package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
  108. package/lib/rules/no-entire-object-hook-deps.js +320 -68
  109. package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
  110. package/lib/rules/no-excessive-parent-chain.js +273 -0
  111. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  112. package/lib/rules/no-explicit-return-type.js +215 -26
  113. package/lib/rules/no-filter-without-return.js +5 -1
  114. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  115. package/lib/rules/no-firestore-jest-mock.js +126 -8
  116. package/lib/rules/no-firestore-object-arrays.js +398 -28
  117. package/lib/rules/no-handler-suffix.d.ts +12 -0
  118. package/lib/rules/no-handler-suffix.js +305 -0
  119. package/lib/rules/no-hungarian.js +14 -14
  120. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  121. package/lib/rules/no-inline-component-prop.js +456 -0
  122. package/lib/rules/no-jsx-in-hooks.js +6 -1
  123. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  124. package/lib/rules/no-margin-properties.d.ts +7 -1
  125. package/lib/rules/no-margin-properties.js +22 -9
  126. package/lib/rules/no-memoize-on-static.js +9 -1
  127. package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
  128. package/lib/rules/no-misleading-boolean-prefixes.js +331 -0
  129. package/lib/rules/no-misused-switch-case.js +22 -1
  130. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  131. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  132. package/lib/rules/no-mock-firebase-admin.js +5 -2
  133. package/lib/rules/no-object-values-on-strings.js +10 -2
  134. package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
  135. package/lib/rules/no-overridable-method-calls-in-constructor.js +272 -0
  136. package/lib/rules/no-passthrough-getters.d.ts +4 -0
  137. package/lib/rules/no-passthrough-getters.js +206 -0
  138. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  139. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  140. package/lib/rules/no-redundant-param-types.js +16 -6
  141. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  142. package/lib/rules/no-redundant-this-params.js +459 -0
  143. package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
  144. package/lib/rules/no-redundant-usecallback-wrapper.js +328 -0
  145. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  146. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  147. package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
  148. package/lib/rules/no-restricted-properties-fix.js +173 -0
  149. package/lib/rules/no-separate-loading-state.d.ts +8 -0
  150. package/lib/rules/no-separate-loading-state.js +133 -0
  151. package/lib/rules/no-stale-state-across-await.d.ts +1 -0
  152. package/lib/rules/no-stale-state-across-await.js +185 -0
  153. package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
  154. package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
  155. package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
  156. package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
  157. package/lib/rules/no-type-assertion-returns.js +49 -49
  158. package/lib/rules/no-undefined-null-passthrough.js +217 -40
  159. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  160. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  161. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  162. package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
  163. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  164. package/lib/rules/no-unnecessary-verb-suffix.js +2 -3
  165. package/lib/rules/no-unpinned-dependencies.js +36 -5
  166. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  167. package/lib/rules/no-unused-props.d.ts +5 -2
  168. package/lib/rules/no-unused-props.js +451 -112
  169. package/lib/rules/no-unused-usestate.js +6 -2
  170. package/lib/rules/no-useless-fragment.js +28 -2
  171. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  172. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  173. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  174. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  175. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  176. package/lib/rules/omit-index-html.d.ts +2 -1
  177. package/lib/rules/omit-index-html.js +62 -7
  178. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  179. package/lib/rules/optimize-object-boolean-conditions.js +236 -0
  180. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  181. package/lib/rules/parallelize-async-operations.js +394 -0
  182. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  183. package/lib/rules/prefer-batch-operations.js +228 -35
  184. package/lib/rules/prefer-block-comments-for-declarations.js +22 -7
  185. package/lib/rules/prefer-clone-deep.js +3 -3
  186. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  187. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  188. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  189. package/lib/rules/prefer-docsetter-setall.js +243 -0
  190. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  191. package/lib/rules/prefer-document-flattening.js +239 -0
  192. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  193. package/lib/rules/prefer-field-paths-in-transforms.js +251 -0
  194. package/lib/rules/prefer-fragment-component.js +4 -4
  195. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  196. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  197. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  198. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  199. package/lib/rules/prefer-global-router-state-key.js +305 -31
  200. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  201. package/lib/rules/prefer-memoized-props.js +445 -0
  202. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  203. package/lib/rules/prefer-next-dynamic.js +357 -0
  204. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  205. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +435 -0
  206. package/lib/rules/prefer-nullish-coalescing-override.d.ts +7 -0
  207. package/lib/rules/prefer-nullish-coalescing-override.js +188 -0
  208. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  209. package/lib/rules/prefer-params-over-parent-id.js +277 -0
  210. package/lib/rules/prefer-settings-object.js +23 -15
  211. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  212. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  213. package/lib/rules/prefer-type-over-interface.js +7 -2
  214. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  215. package/lib/rules/prefer-url-tostring-over-tojson.js +169 -0
  216. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  217. package/lib/rules/prefer-use-deep-compare-memo.js +431 -0
  218. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +25 -2
  219. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  220. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  221. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  222. package/lib/rules/prevent-children-clobber.js +536 -0
  223. package/lib/rules/react-memoize-literals.d.ts +4 -0
  224. package/lib/rules/react-memoize-literals.js +495 -0
  225. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  226. package/lib/rules/react-usememo-should-be-component.js +144 -78
  227. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  228. package/lib/rules/require-hooks-default-params.js +17 -16
  229. package/lib/rules/require-https-error-cause.d.ts +4 -0
  230. package/lib/rules/require-https-error-cause.js +136 -0
  231. package/lib/rules/require-https-error.js +43 -21
  232. package/lib/rules/require-image-optimized.js +2 -2
  233. package/lib/rules/require-memo.js +12 -2
  234. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  235. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  236. package/lib/rules/require-usememo-object-literals.js +9 -4
  237. package/lib/rules/semantic-function-prefixes.js +38 -18
  238. package/lib/rules/sync-onwrite-name-func.js +5 -1
  239. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  240. package/lib/rules/test-file-location-enforcement.js +57 -0
  241. package/lib/rules/use-custom-link.js +6 -1
  242. package/lib/rules/use-custom-router.js +6 -1
  243. package/lib/rules/use-latest-callback.d.ts +3 -0
  244. package/lib/rules/use-latest-callback.js +271 -0
  245. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  246. package/lib/rules/vertically-group-related-functions.js +480 -0
  247. package/lib/utils/ASTHelpers.d.ts +13 -1
  248. package/lib/utils/ASTHelpers.js +34 -0
  249. package/lib/utils/createRule.js +1 -1
  250. package/lib/utils/getMethodName.d.ts +27 -0
  251. package/lib/utils/getMethodName.js +35 -0
  252. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  253. package/lib/utils/ruleTester.d.ts +1 -0
  254. package/lib/utils/ruleTester.js +4 -1
  255. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  256. package/lib/utils/tsTypeClassifier.js +149 -0
  257. package/package.json +23 -14
  258. package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enforceFieldPathSyntaxInDocSetter = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const getMethodName_1 = require("../utils/getMethodName");
7
+ exports.enforceFieldPathSyntaxInDocSetter = (0, createRule_1.createRule)({
8
+ name: 'enforce-fieldpath-syntax-in-docsetter',
9
+ meta: {
10
+ type: 'suggestion',
11
+ docs: {
12
+ description: 'Enforce the use of Firestore FieldPath syntax when passing documentData into DocSetter. Instead of using nested object syntax, developers should use dot notation for deeply nested fields.',
13
+ recommended: 'error',
14
+ requiresTypeChecking: false,
15
+ extendsBaseRule: false,
16
+ },
17
+ fixable: 'code',
18
+ schema: [],
19
+ messages: {
20
+ enforceFieldPathSyntax: 'What’s wrong: DocSetter {{methodName}} receives nested object data under "{{topLevelKey}}". → Why it matters: Firestore treats that nested map as a whole sub-document write, so partial updates can overwrite sibling fields you did not include. → How to fix: Flatten nested properties into FieldPath (dot) keys before passing documentData (e.g., "{{exampleFieldPath}}") so only the intended leaves are written.',
21
+ },
22
+ },
23
+ defaultOptions: [],
24
+ create(context) {
25
+ const sourceCode = context.getSourceCode();
26
+ // Track DocSetter variables
27
+ const docSetterVariables = new Set();
28
+ // Helper function to check if a node is a DocSetter method call
29
+ function isDocSetterMethodCall(node) {
30
+ if (node.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
31
+ return false;
32
+ }
33
+ const { object, property } = node.callee;
34
+ // Only enforce for set/updateIfExists; skip overwrite (full-document replacement)
35
+ if (property.type !== utils_1.AST_NODE_TYPES.Identifier ||
36
+ !['set', 'updateIfExists'].includes(property.name)) {
37
+ return false;
38
+ }
39
+ // Check if the object is a DocSetter instance
40
+ if (object.type === utils_1.AST_NODE_TYPES.Identifier) {
41
+ return docSetterVariables.has(object.name);
42
+ }
43
+ // Support chained instantiation: new DocSetter(...).set(...)
44
+ if (object.type === utils_1.AST_NODE_TYPES.NewExpression &&
45
+ object.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
46
+ object.callee.name === 'DocSetter') {
47
+ return true;
48
+ }
49
+ return false;
50
+ }
51
+ // Helper: detect spread or computed properties in an object literal
52
+ function isSpreadOrComputed(prop) {
53
+ return (prop.type === utils_1.AST_NODE_TYPES.SpreadElement ||
54
+ (prop.type === utils_1.AST_NODE_TYPES.Property && prop.computed === true));
55
+ }
56
+ function isNumericKey(property) {
57
+ return (property.key.type === utils_1.AST_NODE_TYPES.Literal &&
58
+ (typeof property.key.value === 'number' ||
59
+ (typeof property.key.value === 'string' &&
60
+ /^\d+$/.test(property.key.value))));
61
+ }
62
+ function hasRootNumericKey(node) {
63
+ return node.properties.some((property) => property.type === utils_1.AST_NODE_TYPES.Property && isNumericKey(property));
64
+ }
65
+ // Helper function to flatten nested objects into FieldPath syntax
66
+ function flattenObject(obj, sourceCode, prefix = '') {
67
+ const result = {};
68
+ for (const property of obj.properties) {
69
+ // Skip spread elements
70
+ if (property.type === utils_1.AST_NODE_TYPES.SpreadElement) {
71
+ continue;
72
+ }
73
+ if (property.type !== utils_1.AST_NODE_TYPES.Property) {
74
+ continue;
75
+ }
76
+ // Skip computed properties (dynamic keys)
77
+ if (property.computed) {
78
+ continue;
79
+ }
80
+ let key;
81
+ if (property.key.type === utils_1.AST_NODE_TYPES.Identifier) {
82
+ key = property.key.name;
83
+ }
84
+ else if (property.key.type === utils_1.AST_NODE_TYPES.Literal) {
85
+ // Handle both string and numeric literal keys
86
+ if (typeof property.key.value === 'string') {
87
+ key = property.key.value;
88
+ }
89
+ else if (typeof property.key.value === 'number') {
90
+ key = String(property.key.value);
91
+ }
92
+ else {
93
+ // Skip other literal types
94
+ continue;
95
+ }
96
+ }
97
+ else {
98
+ // Skip other key types
99
+ continue;
100
+ }
101
+ const numericKey = isNumericKey(property);
102
+ if (prefix === '' && numericKey) {
103
+ continue;
104
+ }
105
+ const fullKey = prefix ? `${prefix}.${key}` : key;
106
+ // If the value is a nested object, recursively flatten it
107
+ if (property.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
108
+ const nestedResult = flattenObject(property.value, sourceCode, fullKey);
109
+ Object.assign(result, nestedResult);
110
+ }
111
+ else {
112
+ // For non-object values, use the key as is
113
+ result[fullKey] = sourceCode.getText(property.value);
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ // Helper to decide if a key needs quoting (contains dot or is not IdentifierName)
119
+ function needsQuoting(key) {
120
+ return key.includes('.') || !/^(?:[$_A-Za-z][$\w]*)$/u.test(key);
121
+ }
122
+ // Helper function to convert an object to FieldPath syntax
123
+ function convertToFieldPathSyntax(node, sourceCode, preflattened) {
124
+ const idProperty = node.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
125
+ prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
126
+ prop.key.name === 'id');
127
+ const flattenedProperties = {
128
+ ...(preflattened ?? flattenObject(node, sourceCode)),
129
+ };
130
+ if (idProperty && idProperty.type === utils_1.AST_NODE_TYPES.Property) {
131
+ delete flattenedProperties['id'];
132
+ }
133
+ const entries = Object.entries(flattenedProperties);
134
+ const propertyComma = ',';
135
+ // Start with the id property if it exists
136
+ let result = '{\n';
137
+ if (idProperty && idProperty.type === utils_1.AST_NODE_TYPES.Property) {
138
+ result += ` id: ${sourceCode.getText(idProperty.value)}${propertyComma}\n`;
139
+ }
140
+ // Add the flattened properties (always include trailing commas for multiline objects)
141
+ entries.forEach(([key, value]) => {
142
+ const printedKey = needsQuoting(key) ? `'${key}'` : key;
143
+ result += ` ${printedKey}: ${value}${propertyComma}\n`;
144
+ });
145
+ result += '}';
146
+ return result;
147
+ }
148
+ function getPropertyKeyText(property) {
149
+ if (property.key.type === utils_1.AST_NODE_TYPES.Identifier) {
150
+ return property.key.name;
151
+ }
152
+ if (property.key.type === utils_1.AST_NODE_TYPES.Literal &&
153
+ (typeof property.key.value === 'string' ||
154
+ typeof property.key.value === 'number')) {
155
+ return String(property.key.value);
156
+ }
157
+ return undefined;
158
+ }
159
+ function getFirstNestedObjectProperty(node, keyPredicate) {
160
+ for (const property of node.properties) {
161
+ if (isSpreadOrComputed(property)) {
162
+ continue;
163
+ }
164
+ if (property.type !== utils_1.AST_NODE_TYPES.Property) {
165
+ continue;
166
+ }
167
+ const propertyKeyText = getPropertyKeyText(property);
168
+ if (!propertyKeyText) {
169
+ continue;
170
+ }
171
+ if (keyPredicate && !keyPredicate(propertyKeyText)) {
172
+ continue;
173
+ }
174
+ // Root-level numeric keys typically model array-style buckets rather than
175
+ // Firestore document fields, so ignore them when identifying nested objects
176
+ if (node.parent?.type !== utils_1.AST_NODE_TYPES.Property &&
177
+ isNumericKey(property)) {
178
+ continue;
179
+ }
180
+ if (property.value.type !== utils_1.AST_NODE_TYPES.ObjectExpression) {
181
+ continue;
182
+ }
183
+ const hasSpreadOrComputed = property.value.properties.some((prop) => isSpreadOrComputed(prop));
184
+ if (hasSpreadOrComputed) {
185
+ continue;
186
+ }
187
+ return property;
188
+ }
189
+ return undefined;
190
+ }
191
+ function extractViolationDetails(firstArg, sourceCode) {
192
+ const firstNestedPropertyWithoutDots = getFirstNestedObjectProperty(firstArg, (key) => !key.includes('.'));
193
+ const firstNestedProperty = firstNestedPropertyWithoutDots ||
194
+ getFirstNestedObjectProperty(firstArg);
195
+ if (!firstNestedProperty) {
196
+ return null;
197
+ }
198
+ const propertyKeyText = getPropertyKeyText(firstNestedProperty);
199
+ const exampleFieldPathFromProperty = propertyKeyText &&
200
+ flattenObject(firstNestedProperty.value, sourceCode, propertyKeyText);
201
+ const flattenedProperties = flattenObject(firstArg, sourceCode);
202
+ const exampleFieldPath = (exampleFieldPathFromProperty &&
203
+ Object.keys(exampleFieldPathFromProperty).find((key) => key.includes('.'))) ??
204
+ (propertyKeyText &&
205
+ Object.keys(flattenedProperties).find((key) => key.startsWith(`${propertyKeyText}.`))) ??
206
+ Object.keys(flattenedProperties).find((key) => key.includes('.')) ??
207
+ 'field.nested';
208
+ return {
209
+ topLevelKey: propertyKeyText ?? 'nested field',
210
+ exampleFieldPath,
211
+ flattenedProperties,
212
+ };
213
+ }
214
+ return {
215
+ // Track DocSetter variable declarations
216
+ VariableDeclarator(node) {
217
+ if (node.init?.type === utils_1.AST_NODE_TYPES.NewExpression &&
218
+ node.init.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
219
+ node.init.callee.name === 'DocSetter' &&
220
+ node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
221
+ docSetterVariables.add(node.id.name);
222
+ }
223
+ },
224
+ // Check DocSetter method calls
225
+ CallExpression(node) {
226
+ if (!isDocSetterMethodCall(node)) {
227
+ return;
228
+ }
229
+ // Check if the first argument is an object literal
230
+ const firstArg = node.arguments[0];
231
+ if (firstArg?.type !== utils_1.AST_NODE_TYPES.ObjectExpression) {
232
+ return;
233
+ }
234
+ if (hasRootNumericKey(firstArg)) {
235
+ return;
236
+ }
237
+ const violationDetails = extractViolationDetails(firstArg, sourceCode);
238
+ if (!violationDetails) {
239
+ return;
240
+ }
241
+ // Report and fix the issue
242
+ const callee = node.callee;
243
+ context.report({
244
+ node: firstArg,
245
+ messageId: 'enforceFieldPathSyntax',
246
+ data: {
247
+ methodName: `${(0, getMethodName_1.getMemberExpressionName)(callee, sourceCode, {
248
+ computedFallbackToText: false,
249
+ }) || 'set'}()`,
250
+ topLevelKey: violationDetails.topLevelKey,
251
+ exampleFieldPath: violationDetails.exampleFieldPath,
252
+ },
253
+ fix(fixer) {
254
+ const newText = convertToFieldPathSyntax(firstArg, sourceCode, violationDetails.flattenedProperties);
255
+ return fixer.replaceText(firstArg, newText);
256
+ },
257
+ });
258
+ },
259
+ };
260
+ },
261
+ });
262
+ //# sourceMappingURL=enforce-fieldpath-syntax-in-docsetter.js.map
@@ -24,8 +24,20 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
24
24
  },
25
25
  schema: [],
26
26
  messages: {
27
- missingGeneric: '{{ type }} must specify a generic type argument for type safety. Instead of `const docRef = doc(collection)`, use `const docRef = doc<YourType>(collection)`.',
28
- invalidGeneric: '{{ type }} must not use "any" or "{}" as generic type argument. Define a proper interface/type for your document: `interface UserDoc { name: string; age: number; }` and use it: `const docRef = doc<UserDoc>(collection)`.',
27
+ missingGeneric: [
28
+ "What's wrong: {{ type }} is missing its document schema generic (the document data type).",
29
+ '',
30
+ 'Why it matters: Without the generic, Firestore references fall back to loose DocumentData, so TypeScript cannot catch field typos or missing required properties before they reach Firestore.',
31
+ '',
32
+ 'How to fix: Add the document interface/type as the generic (e.g., const ref: {{ type }}<UserDoc> = ... or doc<UserDoc>(collection)).',
33
+ ].join('\n'),
34
+ invalidGeneric: [
35
+ 'What\'s wrong: {{ type }} uses "any" or an empty object ({}) in its schema generic.',
36
+ '',
37
+ 'Why it matters: This erases the document schema and disables TypeScript checks on Firestore reads and writes, so malformed payloads and missing fields can pass silently.',
38
+ '',
39
+ 'How to fix: Define a concrete interface/type for the document (e.g., interface UserDoc { name: string }) and use it as the generic instead of "any" or {}.',
40
+ ].join('\n'),
29
41
  },
30
42
  },
31
43
  defaultOptions: [],
@@ -61,7 +73,7 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
61
73
  }
62
74
  // Prevent infinite recursion
63
75
  typeCache.set(typeName, false);
64
- const program = context.getSourceCode().ast;
76
+ const program = context.sourceCode.ast;
65
77
  const interfaceDecl = program.body.find((n) => n.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration &&
66
78
  n.id.name === typeName);
67
79
  if (interfaceDecl) {
@@ -194,6 +206,308 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
194
206
  }
195
207
  return false;
196
208
  }
209
+ const isTypedCollectionReferenceCache = new Map();
210
+ function isTypedCollectionReference(node) {
211
+ if (!node)
212
+ return false;
213
+ if (isTypedCollectionReferenceCache.has(node)) {
214
+ return isTypedCollectionReferenceCache.get(node);
215
+ }
216
+ let result = false;
217
+ if (node.type === utils_1.AST_NODE_TYPES.CallExpression &&
218
+ node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
219
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
220
+ node.callee.property.name === 'collection' &&
221
+ node.typeParameters &&
222
+ node.typeParameters.params.length > 0) {
223
+ result = true;
224
+ }
225
+ else if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
226
+ result = checkMemberExpressionForCollectionReference(node);
227
+ }
228
+ else if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
229
+ result = checkIdentifierForCollectionReference(node);
230
+ }
231
+ else if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
232
+ result = checkCallExpressionForCollectionReference(node);
233
+ }
234
+ isTypedCollectionReferenceCache.set(node, result);
235
+ return result;
236
+ }
237
+ function checkMemberExpressionForCollectionReference(node) {
238
+ const obj = node.object;
239
+ const property = node.property;
240
+ // Handle this.property access
241
+ if (obj.type === utils_1.AST_NODE_TYPES.ThisExpression &&
242
+ property.type === utils_1.AST_NODE_TYPES.Identifier) {
243
+ const classNode = findParentClass(node);
244
+ if (classNode) {
245
+ const classProp = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
246
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
247
+ member.key.name === property.name);
248
+ if (classProp?.typeAnnotation) {
249
+ return hasCollectionReferenceType(classProp.typeAnnotation.typeAnnotation);
250
+ }
251
+ }
252
+ }
253
+ // Handle nested property access like this.collections.tasks
254
+ if (obj.type === utils_1.AST_NODE_TYPES.MemberExpression) {
255
+ const parentType = getTypeOfMemberExpression(obj);
256
+ if (parentType && property.type === utils_1.AST_NODE_TYPES.Identifier) {
257
+ // Check if the parent object has a property with CollectionReference type
258
+ return checkObjectPropertyForCollectionReference(parentType, property.name);
259
+ }
260
+ }
261
+ // Handle identifier.property access
262
+ if (obj.type === utils_1.AST_NODE_TYPES.Identifier &&
263
+ property.type === utils_1.AST_NODE_TYPES.Identifier) {
264
+ const objType = getTypeOfIdentifier(obj);
265
+ if (objType) {
266
+ return checkObjectPropertyForCollectionReference(objType, property.name);
267
+ }
268
+ }
269
+ // Handle getter methods like this.collection where collection is a getter
270
+ if (obj.type === utils_1.AST_NODE_TYPES.ThisExpression &&
271
+ property.type === utils_1.AST_NODE_TYPES.Identifier) {
272
+ const classNode = findParentClass(node);
273
+ if (classNode) {
274
+ const getter = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.MethodDefinition &&
275
+ member.kind === 'get' &&
276
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
277
+ member.key.name === property.name);
278
+ if (getter) {
279
+ // Check explicit return type
280
+ if (getter.value.returnType) {
281
+ return hasCollectionReferenceType(getter.value.returnType.typeAnnotation);
282
+ }
283
+ // Check return statement to infer type
284
+ if (getter.value.body &&
285
+ getter.value.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
286
+ const returnStmt = getter.value.body.body.find((stmt) => stmt.type === utils_1.AST_NODE_TYPES.ReturnStatement);
287
+ if (returnStmt?.argument?.type === utils_1.AST_NODE_TYPES.MemberExpression) {
288
+ return checkMemberExpressionForCollectionReference(returnStmt.argument);
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+ return false;
295
+ }
296
+ function checkIdentifierForCollectionReference(node) {
297
+ // Check function parameters
298
+ const functionParam = findFunctionParameter(node);
299
+ if (functionParam &&
300
+ 'typeAnnotation' in functionParam &&
301
+ functionParam.typeAnnotation) {
302
+ return hasCollectionReferenceType(functionParam.typeAnnotation.typeAnnotation);
303
+ }
304
+ // Check variable declarations in current scope and parent scopes
305
+ if (findVariableDeclaration(node)) {
306
+ return true;
307
+ }
308
+ return false;
309
+ }
310
+ function checkCallExpressionForCollectionReference(node) {
311
+ // Check if this is a method call that returns CollectionReference
312
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
313
+ const obj = node.callee.object;
314
+ const property = node.callee.property;
315
+ if (property.type === utils_1.AST_NODE_TYPES.Identifier) {
316
+ // Check if this is a getter or method that returns CollectionReference
317
+ if (obj.type === utils_1.AST_NODE_TYPES.ThisExpression) {
318
+ const classNode = findParentClass(node);
319
+ if (classNode) {
320
+ const method = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.MethodDefinition &&
321
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
322
+ member.key.name === property.name &&
323
+ !!member.value.returnType);
324
+ if (method?.value.returnType) {
325
+ return hasCollectionReferenceType(method.value.returnType.typeAnnotation);
326
+ }
327
+ }
328
+ }
329
+ }
330
+ }
331
+ return false;
332
+ }
333
+ function getTypeOfMemberExpression(node) {
334
+ if (node.object.type === utils_1.AST_NODE_TYPES.ThisExpression &&
335
+ node.property.type === utils_1.AST_NODE_TYPES.Identifier) {
336
+ const classNode = findParentClass(node);
337
+ if (classNode) {
338
+ const classProp = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
339
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
340
+ 'name' in node.property &&
341
+ member.key.name === node.property.name);
342
+ return classProp?.typeAnnotation?.typeAnnotation || null;
343
+ }
344
+ }
345
+ return null;
346
+ }
347
+ function getTypeOfIdentifier(node) {
348
+ const functionParam = findFunctionParameter(node);
349
+ if (functionParam &&
350
+ 'typeAnnotation' in functionParam &&
351
+ functionParam.typeAnnotation) {
352
+ return functionParam.typeAnnotation.typeAnnotation;
353
+ }
354
+ const varDecl = findVariableDeclarationNode(node);
355
+ if (varDecl?.typeAnnotation) {
356
+ return varDecl.typeAnnotation.typeAnnotation;
357
+ }
358
+ return null;
359
+ }
360
+ function checkObjectPropertyForCollectionReference(objectType, propertyName) {
361
+ if (objectType.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
362
+ const property = objectType.members.find((member) => member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
363
+ member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
364
+ member.key.name === propertyName &&
365
+ !!member.typeAnnotation);
366
+ if (property?.typeAnnotation) {
367
+ return hasCollectionReferenceType(property.typeAnnotation.typeAnnotation);
368
+ }
369
+ }
370
+ // Handle Record<string, CollectionReference<T>> types
371
+ if (objectType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
372
+ objectType.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
373
+ objectType.typeName.name === 'Record' &&
374
+ objectType.typeParameters &&
375
+ objectType.typeParameters.params.length === 2) {
376
+ const valueType = objectType.typeParameters.params[1];
377
+ return hasCollectionReferenceType(valueType);
378
+ }
379
+ // Handle array types like CollectionReference<T>[]
380
+ if (objectType.type === utils_1.AST_NODE_TYPES.TSArrayType) {
381
+ return hasCollectionReferenceType(objectType.elementType);
382
+ }
383
+ return false;
384
+ }
385
+ function findFunctionParameter(node) {
386
+ const isFunctionNode = (n) => n.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
387
+ n.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
388
+ n.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression;
389
+ const findParamInFunction = (func) => {
390
+ const param = func.params.find((p) => p.type === utils_1.AST_NODE_TYPES.Identifier &&
391
+ p.name === node.name &&
392
+ 'typeAnnotation' in p &&
393
+ p.typeAnnotation !== undefined);
394
+ return param || null;
395
+ };
396
+ const functionScopes = [];
397
+ let current = node;
398
+ while (current) {
399
+ if (isFunctionNode(current)) {
400
+ functionScopes.push(current);
401
+ }
402
+ current = current.parent;
403
+ }
404
+ for (const func of functionScopes) {
405
+ const param = findParamInFunction(func);
406
+ if (param) {
407
+ return param;
408
+ }
409
+ }
410
+ return null;
411
+ }
412
+ function findVariableDeclaration(node) {
413
+ let current = node;
414
+ while (current) {
415
+ // Check in current scope
416
+ if (current.type === utils_1.AST_NODE_TYPES.Program ||
417
+ current.type === utils_1.AST_NODE_TYPES.BlockStatement ||
418
+ current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
419
+ current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
420
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
421
+ const varDecl = findVariableInScope(current, node.name);
422
+ if (varDecl && varDecl.typeAnnotation) {
423
+ return hasCollectionReferenceType(varDecl.typeAnnotation.typeAnnotation);
424
+ }
425
+ }
426
+ current = current.parent;
427
+ }
428
+ return false;
429
+ }
430
+ function findVariableDeclarationNode(node) {
431
+ let current = node;
432
+ while (current) {
433
+ if (current.type === utils_1.AST_NODE_TYPES.Program ||
434
+ current.type === utils_1.AST_NODE_TYPES.BlockStatement ||
435
+ current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
436
+ current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
437
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
438
+ const varDecl = findVariableInScope(current, node.name);
439
+ if (varDecl) {
440
+ return varDecl;
441
+ }
442
+ }
443
+ current = current.parent;
444
+ }
445
+ return null;
446
+ }
447
+ function findVariableInScope(scope, varName) {
448
+ const body = getNodeBody(scope);
449
+ if (!body)
450
+ return null;
451
+ for (const stmt of body) {
452
+ if (stmt.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
453
+ for (const decl of stmt.declarations) {
454
+ if (decl.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
455
+ decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
456
+ decl.id.name === varName &&
457
+ decl.id.typeAnnotation) {
458
+ return decl.id;
459
+ }
460
+ }
461
+ }
462
+ }
463
+ return null;
464
+ }
465
+ function getNodeBody(node) {
466
+ if (node.type === utils_1.AST_NODE_TYPES.Program) {
467
+ return node.body;
468
+ }
469
+ if (node.type === utils_1.AST_NODE_TYPES.BlockStatement) {
470
+ return node.body;
471
+ }
472
+ if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
473
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
474
+ return node.body?.body || null;
475
+ }
476
+ if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
477
+ return node.body.type === utils_1.AST_NODE_TYPES.BlockStatement
478
+ ? node.body.body
479
+ : null;
480
+ }
481
+ return null;
482
+ }
483
+ function hasCollectionReferenceType(typeNode) {
484
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
485
+ ((typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
486
+ typeNode.typeName.name === 'CollectionReference') ||
487
+ (typeNode.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName &&
488
+ typeNode.typeName.right.type === utils_1.AST_NODE_TYPES.Identifier &&
489
+ typeNode.typeName.right.name === 'CollectionReference')) &&
490
+ typeNode.typeParameters &&
491
+ typeNode.typeParameters.params.length > 0) {
492
+ return true;
493
+ }
494
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSUnionType) {
495
+ return typeNode.types.some(hasCollectionReferenceType);
496
+ }
497
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
498
+ return typeNode.types.some(hasCollectionReferenceType);
499
+ }
500
+ // TSParenthesizedType may appear even though AST_NODE_TYPES omits it
501
+ if (typeNode.type === 'TSParenthesizedType') {
502
+ const inner = typeNode
503
+ .typeAnnotation;
504
+ return hasCollectionReferenceType(inner);
505
+ }
506
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSArrayType) {
507
+ return hasCollectionReferenceType(typeNode.elementType);
508
+ }
509
+ return false;
510
+ }
197
511
  return {
198
512
  TSTypeReference(node) {
199
513
  if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
@@ -231,6 +545,39 @@ exports.enforceFirestoreDocRefGeneric = (0, createRule_1.createRule)({
231
545
  node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
232
546
  node.callee.property.name === 'doc') {
233
547
  const typeAnnotation = node.typeParameters;
548
+ const isOnTypedCollection = isTypedCollectionReference(node.callee.object);
549
+ // If this is a .doc() call on a typed CollectionReference,
550
+ // only check for invalid generics, not missing generics
551
+ if (isOnTypedCollection) {
552
+ if (typeAnnotation && hasInvalidType(typeAnnotation.params[0])) {
553
+ context.report({
554
+ node,
555
+ messageId: 'invalidGeneric',
556
+ data: { type: 'DocumentReference' },
557
+ });
558
+ }
559
+ return; // Skip the missing generic check for typed CollectionReference.doc() calls
560
+ }
561
+ // For standalone doc() calls or calls on untyped collections
562
+ if (!typeAnnotation) {
563
+ context.report({
564
+ node,
565
+ messageId: 'missingGeneric',
566
+ data: { type: 'DocumentReference' },
567
+ });
568
+ }
569
+ else if (hasInvalidType(typeAnnotation.params[0])) {
570
+ context.report({
571
+ node,
572
+ messageId: 'invalidGeneric',
573
+ data: { type: 'DocumentReference' },
574
+ });
575
+ }
576
+ }
577
+ // Check for standalone doc() function calls
578
+ else if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
579
+ node.callee.name === 'doc') {
580
+ const typeAnnotation = node.typeParameters;
234
581
  if (!typeAnnotation) {
235
582
  context.report({
236
583
  node,