@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
@@ -8,6 +8,7 @@ const NON_TRANSACTIONAL_CLASSES = new Set([
8
8
  'FirestoreDocFetcher',
9
9
  'FirestoreFetcher',
10
10
  ]);
11
+ const FETCHER_CLASSES = new Set(['FirestoreDocFetcher', 'FirestoreFetcher']);
11
12
  exports.noMixedFirestoreTransactions = (0, createRule_1.createRule)({
12
13
  name: 'no-mixed-firestore-transactions',
13
14
  meta: {
@@ -18,19 +19,26 @@ exports.noMixedFirestoreTransactions = (0, createRule_1.createRule)({
18
19
  },
19
20
  schema: [],
20
21
  messages: {
21
- noMixedTransactions: 'Do not use non-transactional Firestore operations ({{ className }}) inside a transaction. Use {{ transactionalClass }} instead.',
22
+ noMixedTransactions: 'Non-transactional Firestore helper "{{ className }}" is instantiated inside a transaction callback, so its reads and writes bypass the transaction context. That breaks Firestore\'s atomicity guarantees and can commit partial updates. Use the transaction-safe "{{ transactionalClass }}" and pass the provided transaction so every operation participates in the same commit.',
23
+ noMixedTransactionsFetcher: 'Non-transactional Firestore helper "{{ className }}" is instantiated inside a transaction callback, so its reads and writes bypass the transaction context. That breaks Firestore\'s atomicity guarantees and can commit partial updates. Pass the provided transaction to the constructor (e.g., new {{ className }}(ref, { transaction: tx })) or to fetch() (e.g., fetch({ transaction: tx })) so the operation participates in the transaction.',
22
24
  },
23
25
  },
24
26
  defaultOptions: [],
25
27
  create(context) {
26
28
  const transactionScopes = new Set();
29
+ /**
30
+ * Tracks fetcher lifetimes per transaction scope and binding name.
31
+ * The outer map keys by the transaction scope node so nested transactions keep
32
+ * independent state. Each scope maps variable names to an ordered list of
33
+ * instances, preserving reassignment history. Arrays keep the latest instance
34
+ * easy to read for fetch calls while still retaining older instances for
35
+ * Program:exit validation, which must flag any overwritten fetcher that never
36
+ * received a transaction.
37
+ */
38
+ const fetcherInstances = new Map();
27
39
  function getTransactionalClassName(className) {
28
40
  if (className === 'DocSetter')
29
41
  return 'DocSetterTransaction';
30
- if (className === 'FirestoreDocFetcher')
31
- return 'FirestoreDocFetcherTransaction';
32
- if (className === 'FirestoreFetcher')
33
- return 'FirestoreFetcherTransaction';
34
42
  return className;
35
43
  }
36
44
  function isFirestoreTransaction(node) {
@@ -65,53 +73,307 @@ exports.noMixedFirestoreTransactions = (0, createRule_1.createRule)({
65
73
  typeName.right.type === utils_1.AST_NODE_TYPES.Identifier &&
66
74
  typeName.right.name === 'Transaction');
67
75
  }
68
- function isInTransactionScope(node) {
76
+ function isFunctionLike(node) {
77
+ return (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
78
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
79
+ node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression);
80
+ }
81
+ function identifyTransactionScope(node) {
82
+ if (transactionScopes.has(node)) {
83
+ return node;
84
+ }
85
+ if (isFunctionLike(node) && node.params.some(isTransactionParameter)) {
86
+ return node.body;
87
+ }
88
+ return null;
89
+ }
90
+ function getTransactionScopeChain(node) {
91
+ const scopes = [];
69
92
  let current = node;
70
93
  while (current) {
71
- if (transactionScopes.has(current)) {
72
- return true;
73
- }
74
- if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
75
- current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
76
- current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
77
- const params = current.params;
78
- if (params.some(isTransactionParameter)) {
79
- return true;
80
- }
94
+ const scope = identifyTransactionScope(current);
95
+ if (scope && scopes[scopes.length - 1] !== scope) {
96
+ scopes.push(scope);
81
97
  }
82
98
  current = current.parent;
83
99
  }
100
+ return scopes;
101
+ }
102
+ function getTransactionScope(node) {
103
+ return getTransactionScopeChain(node)[0] ?? null;
104
+ }
105
+ function getVariableName(node) {
106
+ const parent = node.parent;
107
+ if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
108
+ parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
109
+ return parent.id.name;
110
+ }
111
+ if (parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
112
+ parent.left.type === utils_1.AST_NODE_TYPES.Identifier) {
113
+ return parent.left.name;
114
+ }
115
+ return null;
116
+ }
117
+ function isFetcherClass(className) {
118
+ return FETCHER_CLASSES.has(className);
119
+ }
120
+ function isTransactionKey(key) {
121
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
122
+ return key.name === 'transaction';
123
+ }
124
+ if (key.type === utils_1.AST_NODE_TYPES.Literal) {
125
+ return key.value === 'transaction';
126
+ }
127
+ if (key.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
128
+ return (key.expressions.length === 0 &&
129
+ key.quasis.length === 1 &&
130
+ key.quasis[0].value.cooked === 'transaction');
131
+ }
84
132
  return false;
85
133
  }
134
+ function isTransactionOptionObject(arg) {
135
+ if (arg.type !== utils_1.AST_NODE_TYPES.ObjectExpression)
136
+ return false;
137
+ return arg.properties.some((property) => {
138
+ if (property.type !== utils_1.AST_NODE_TYPES.Property) {
139
+ return false;
140
+ }
141
+ const key = property.key;
142
+ return isTransactionKey(key);
143
+ });
144
+ }
145
+ function hasTransactionOption(args) {
146
+ return args.some(isTransactionOptionObject);
147
+ }
148
+ function getFetchCallContext(node) {
149
+ const transactionScopesForCall = getTransactionScopeChain(node);
150
+ const transactionScope = transactionScopesForCall[0];
151
+ if (!transactionScope)
152
+ return null;
153
+ return {
154
+ transactionScope,
155
+ transactionScopesForCall,
156
+ callHasTransaction: hasTransactionOption(node.arguments),
157
+ };
158
+ }
159
+ function ensureFetcherScope(scope) {
160
+ if (!fetcherInstances.has(scope)) {
161
+ fetcherInstances.set(scope, new Map());
162
+ }
163
+ return fetcherInstances.get(scope);
164
+ }
165
+ function addFetcherInstance(scope, variableName, instance) {
166
+ const scopeMap = ensureFetcherScope(scope);
167
+ if (!scopeMap.has(variableName)) {
168
+ scopeMap.set(variableName, []);
169
+ }
170
+ scopeMap.get(variableName).push(instance);
171
+ }
172
+ function findFetcherInstance(scopes, variableName) {
173
+ for (const scope of scopes) {
174
+ const scopeInstances = fetcherInstances.get(scope);
175
+ const instances = scopeInstances?.get(variableName);
176
+ if (instances?.length) {
177
+ return instances[instances.length - 1];
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+ /**
183
+ * Determines whether a fetcher instance should be reported for mixed or unsafe
184
+ * usage.
185
+ *
186
+ * Reporting occurs in three scenarios:
187
+ * 1. The constructor lacks a transaction AND any call occurs without a transaction.
188
+ * 2. The constructor lacks a transaction AND no calls occur at all (suspicious
189
+ * unused instance in a transaction scope).
190
+ * 3. Constructor-level transactions make the instance always safe.
191
+ *
192
+ * If the constructor lacked a transaction but at least one call provided it, we
193
+ * skip reporting because the caller opted into explicit transactional use.
194
+ */
195
+ function shouldReportFetcher(instance) {
196
+ if (instance.constructorHasTransaction)
197
+ return false;
198
+ if (instance.hasCallWithoutTransaction)
199
+ return true;
200
+ if (instance.hasTransactionCall)
201
+ return false;
202
+ return true;
203
+ }
204
+ function isFetchCall(callee) {
205
+ return (callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
206
+ callee.property.name === 'fetch');
207
+ }
208
+ function trackTransactionScope(node) {
209
+ if (!isFirestoreTransaction(node))
210
+ return;
211
+ const callback = node.arguments[0];
212
+ if (!callback)
213
+ return;
214
+ if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
215
+ callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
216
+ transactionScopes.add(callback.body);
217
+ }
218
+ }
219
+ function updateInstanceTransactionState(instance, callHasTransaction) {
220
+ if (callHasTransaction || instance.constructorHasTransaction) {
221
+ instance.hasTransactionCall =
222
+ instance.hasTransactionCall || callHasTransaction;
223
+ }
224
+ else {
225
+ instance.hasCallWithoutTransaction = true;
226
+ }
227
+ }
228
+ function handleIdentifierFetchCall(identifier, transactionScopesForCall, callHasTransaction) {
229
+ const instance = findFetcherInstance(transactionScopesForCall, identifier.name);
230
+ if (!instance)
231
+ return;
232
+ updateInstanceTransactionState(instance, callHasTransaction);
233
+ }
234
+ function isInlineFetcherNew(object) {
235
+ return (object.type === utils_1.AST_NODE_TYPES.NewExpression &&
236
+ object.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
237
+ isFetcherClass(object.callee.name));
238
+ }
239
+ function handleInlineNewFetchCall(object, callHasTransaction) {
240
+ const constructorHasTransaction = hasTransactionOption(object.arguments);
241
+ if (constructorHasTransaction || callHasTransaction) {
242
+ return;
243
+ }
244
+ const className = object.callee.name;
245
+ context.report({
246
+ node: object,
247
+ messageId: 'noMixedTransactionsFetcher',
248
+ data: {
249
+ className,
250
+ },
251
+ });
252
+ }
253
+ function handleFetchCall(node, callee) {
254
+ const contextForCall = getFetchCallContext(node);
255
+ if (!contextForCall)
256
+ return;
257
+ const { transactionScopesForCall, callHasTransaction } = contextForCall;
258
+ const object = callee.object;
259
+ if (object.type === utils_1.AST_NODE_TYPES.Identifier) {
260
+ handleIdentifierFetchCall(object, transactionScopesForCall, callHasTransaction);
261
+ }
262
+ else if (isInlineFetcherNew(object)) {
263
+ handleInlineNewFetchCall(object, callHasTransaction);
264
+ }
265
+ }
266
+ function reportNonFetcherInTransaction(node, className) {
267
+ context.report({
268
+ node,
269
+ messageId: 'noMixedTransactions',
270
+ data: {
271
+ className,
272
+ transactionalClass: getTransactionalClassName(className),
273
+ },
274
+ });
275
+ }
276
+ function isNewExpressionUsedForFetchCall(node) {
277
+ return (node.parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
278
+ node.parent.object === node &&
279
+ isFetchCall(node.parent) &&
280
+ node.parent.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
281
+ node.parent.parent.callee === node.parent);
282
+ }
283
+ function handleFetcherWithoutVariable(node, className, constructorHasTransaction) {
284
+ if (constructorHasTransaction)
285
+ return;
286
+ if (isNewExpressionUsedForFetchCall(node))
287
+ return;
288
+ context.report({
289
+ node,
290
+ messageId: 'noMixedTransactionsFetcher',
291
+ data: {
292
+ className,
293
+ },
294
+ });
295
+ }
296
+ function handleFetcherWithVariable(node, className, constructorHasTransaction, variableName, transactionScope) {
297
+ addFetcherInstance(transactionScope, variableName, {
298
+ node,
299
+ className,
300
+ constructorHasTransaction,
301
+ hasTransactionCall: constructorHasTransaction,
302
+ hasCallWithoutTransaction: false,
303
+ });
304
+ }
305
+ function handleFetcherClass(node, className, transactionScope) {
306
+ const constructorHasTransaction = hasTransactionOption(node.arguments);
307
+ const variableName = getVariableName(node);
308
+ if (!variableName) {
309
+ handleFetcherWithoutVariable(node, className, constructorHasTransaction);
310
+ return;
311
+ }
312
+ handleFetcherWithVariable(node, className, constructorHasTransaction, variableName, transactionScope);
313
+ }
314
+ function handleNewExpression(node) {
315
+ if (!isNonTransactionalClass(node))
316
+ return;
317
+ const transactionScope = getTransactionScope(node);
318
+ if (!transactionScope)
319
+ return;
320
+ const className = node.callee.name;
321
+ if (!isFetcherClass(className)) {
322
+ reportNonFetcherInTransaction(node, className);
323
+ return;
324
+ }
325
+ handleFetcherClass(node, className, transactionScope);
326
+ }
327
+ function reportFetcherInstance(instance) {
328
+ if (!shouldReportFetcher(instance))
329
+ return;
330
+ context.report({
331
+ node: instance.node,
332
+ messageId: 'noMixedTransactionsFetcher',
333
+ data: {
334
+ className: instance.className,
335
+ },
336
+ });
337
+ }
338
+ function reportFetcherInstances(instances) {
339
+ for (const instance of instances) {
340
+ reportFetcherInstance(instance);
341
+ }
342
+ }
343
+ function reportFetcherScope(scope) {
344
+ for (const instances of scope.values()) {
345
+ reportFetcherInstances(instances);
346
+ }
347
+ }
348
+ function reportAllFetcherScopes() {
349
+ for (const scope of fetcherInstances.values()) {
350
+ reportFetcherScope(scope);
351
+ }
352
+ }
86
353
  return {
87
- 'CallExpression[callee.property.name="runTransaction"]'(node) {
88
- if (!isFirestoreTransaction(node))
354
+ NewExpression: handleNewExpression,
355
+ CallExpression(node) {
356
+ const callee = node.callee;
357
+ if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
89
358
  return;
90
- const callback = node.arguments[0];
91
- if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
92
- callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
93
- transactionScopes.add(callback.body);
94
359
  }
360
+ if (!isFetchCall(callee)) {
361
+ trackTransactionScope(node);
362
+ return;
363
+ }
364
+ handleFetchCall(node, callee);
95
365
  },
96
366
  'CallExpression[callee.property.name="runTransaction"]:exit'(node) {
97
367
  const callback = node.arguments[0];
368
+ if (!callback)
369
+ return;
98
370
  if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
99
371
  callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
100
372
  transactionScopes.delete(callback.body);
101
373
  }
102
374
  },
103
- NewExpression(node) {
104
- if (!isInTransactionScope(node) || !isNonTransactionalClass(node))
105
- return;
106
- const className = node.callee.name;
107
- context.report({
108
- node,
109
- messageId: 'noMixedTransactions',
110
- data: {
111
- className,
112
- transactionalClass: getTransactionalClassName(className),
113
- },
114
- });
375
+ 'Program:exit'() {
376
+ reportAllFetcherScopes();
115
377
  },
116
378
  };
117
379
  },
@@ -14,12 +14,12 @@ exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
14
14
  meta: {
15
15
  type: 'problem',
16
16
  docs: {
17
- description: 'Prevent mocking of functions/src/config/firebaseAdmin',
17
+ description: 'Prevent direct mocking of firebaseAdmin; use shared test helpers instead',
18
18
  recommended: 'error',
19
19
  },
20
20
  schema: [],
21
21
  messages: {
22
- noMockFirebaseAdmin: 'Do not mock firebaseAdmin directly. Use mockFirestore from __test-utils__/mockFirestore instead.',
22
+ noMockFirebaseAdmin: 'Do not mock firebaseAdmin module "{{modulePath}}". The project already ships a stable mock in jest.setup.node.js; overriding it creates divergent Firestore/Auth state and brittle test fixtures. Keep the shared mock and use __test-utils__/mockFirestore to seed data instead of replacing the module.',
23
23
  },
24
24
  },
25
25
  defaultOptions: [],
@@ -58,6 +58,9 @@ exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
58
58
  context.report({
59
59
  node,
60
60
  messageId: 'noMockFirebaseAdmin',
61
+ data: {
62
+ modulePath: mockPath,
63
+ },
61
64
  });
62
65
  }
63
66
  }
@@ -29,7 +29,7 @@ const utils_1 = require("@typescript-eslint/utils");
29
29
  const ts = __importStar(require("typescript"));
30
30
  exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
31
31
  create(context) {
32
- const sourceCode = context.getSourceCode();
32
+ const sourceCode = context.sourceCode;
33
33
  const parserServices = sourceCode.parserServices;
34
34
  // If TypeScript parser services are not available, return an empty object
35
35
  if (!parserServices ||
@@ -191,11 +191,13 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
191
191
  // Check if the call is Object.values()
192
192
  if (isObjectValuesCall(node)) {
193
193
  const argument = node.arguments[0];
194
+ const argumentText = sourceCode.getText(argument);
194
195
  // Quick check for string literals and template literals
195
196
  if (isStringLiteral(argument)) {
196
197
  context.report({
197
198
  node,
198
199
  messageId: 'unexpected',
200
+ data: { argument: argumentText },
199
201
  });
200
202
  return;
201
203
  }
@@ -204,6 +206,7 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
204
206
  context.report({
205
207
  node,
206
208
  messageId: 'unexpected',
209
+ data: { argument: argumentText },
207
210
  });
208
211
  return;
209
212
  }
@@ -220,6 +223,7 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
220
223
  context.report({
221
224
  node,
222
225
  messageId: 'unexpected',
226
+ data: { argument: argumentText },
223
227
  });
224
228
  return;
225
229
  }
@@ -234,6 +238,7 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
234
238
  context.report({
235
239
  node,
236
240
  messageId: 'unexpected',
241
+ data: { argument: argumentText },
237
242
  });
238
243
  return;
239
244
  }
@@ -243,6 +248,7 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
243
248
  context.report({
244
249
  node,
245
250
  messageId: 'unexpected',
251
+ data: { argument: argumentText },
246
252
  });
247
253
  return;
248
254
  }
@@ -284,9 +290,11 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
284
290
  .filter((p) => p.type === utils_1.AST_NODE_TYPES.Identifier)
285
291
  .map((p) => p.name);
286
292
  if (paramNames.includes(argument.name)) {
293
+ const argumentText = sourceCode.getText(argument);
287
294
  context.report({
288
295
  node: call,
289
296
  messageId: 'unexpected',
297
+ data: { argument: argumentText },
290
298
  });
291
299
  }
292
300
  }
@@ -304,7 +312,7 @@ exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
304
312
  },
305
313
  schema: [],
306
314
  messages: {
307
- unexpected: 'Object.values() should not be used on strings. It treats strings as arrays of characters, which is likely unintended. Use Object.values() only on objects.',
315
+ unexpected: 'Object.values() receives string-like argument {{argument}}. Strings are iterable, so Object.values returns one entry per character instead of object properties, which signals the argument is not an object. Pass an object (for example, a Record or Object.fromEntries(map)) to Object.values or use string helpers (split, spread, Array.from) to work with text.',
308
316
  },
309
317
  },
310
318
  defaultOptions: [],
@@ -0,0 +1 @@
1
+ export declare const noOverridableMethodCallsInConstructor: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noOverridableMethodCallsInConstructor", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;