@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
@@ -14,61 +14,280 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
14
14
  fixable: 'code',
15
15
  schema: [],
16
16
  messages: {
17
- useFastDeepEqual: 'Use fast-deep-equal for equality checks instead of microdiff.length === 0',
18
- addFastDeepEqualImport: 'Import isEqual from fast-deep-equal for equality checks',
17
+ useFastDeepEqual: "What's wrong: This code uses `{{diffName}}(...).length` as a deep equality check.\n" +
18
+ 'Why it matters: `{{diffName}}` allocates a full change list (paths, types, values) before you compare it to zero, which hides the boolean intent and wastes memory/time.\n' +
19
+ 'How to fix: Call `{{fastEqualName}}(left, right)` for equality (or prefix with `!` for inequality) using the same two arguments instead of counting diff length.',
20
+ addFastDeepEqualImport: "What's wrong: This file checks equality via `{{diffName}}(...).length` but does not import `fast-deep-equal`.\n" +
21
+ 'Why it matters: Without `fast-deep-equal`, equality checks keep building diff entries just to count them, adding overhead and obscuring intent.\n' +
22
+ 'How to fix: Add a default import for `fast-deep-equal` as `{{fastEqualName}}` and use `{{fastEqualName}}(a, b)` for equality checks.',
19
23
  },
20
24
  },
21
25
  defaultOptions: [],
22
26
  create(context) {
23
- const sourceCode = context.getSourceCode();
27
+ const sourceCode = context.sourceCode;
24
28
  let hasFastDeepEqualImport = false;
29
+ let hasMicrodiffImport = false;
25
30
  let microdiffImportName = 'diff';
26
31
  let fastDeepEqualImportName = 'isEqual';
27
32
  const reportedNodes = new Set();
33
+ let plannedFastDeepEqualImport = false;
34
+ const isChainExpression = (node) => node.type === utils_1.AST_NODE_TYPES.ChainExpression;
35
+ function isMicrodiffCallee(callee) {
36
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
37
+ callee.name === microdiffImportName) {
38
+ return true;
39
+ }
40
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
41
+ !callee.computed &&
42
+ callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
43
+ callee.object.name === microdiffImportName &&
44
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
45
+ (callee.property.name === 'diff' || callee.property.name === 'default')) {
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+ /**
51
+ * Resolve an identifier to its variable and return the initializer CallExpression if it's a microdiff call.
52
+ */
53
+ function findVariableInScopeChain(identifier) {
54
+ // Use scopeManager to resolve variable from the identifier's reference
55
+ const scopeManager = sourceCode.scopeManager;
56
+ if (!scopeManager)
57
+ return undefined;
58
+ // Find the scope for this identifier
59
+ let scope = null;
60
+ let currentNode = identifier;
61
+ while (currentNode) {
62
+ scope = scopeManager.acquire(currentNode, true) || null;
63
+ if (scope)
64
+ break;
65
+ currentNode = currentNode.parent;
66
+ }
67
+ if (!scope) {
68
+ scope = scopeManager.globalScope;
69
+ }
70
+ if (!scope)
71
+ return undefined;
72
+ // Look up variable in the scope chain
73
+ let currentScope = scope;
74
+ while (currentScope) {
75
+ const variable = currentScope.variables.find((v) => v.name === identifier.name);
76
+ if (variable)
77
+ return variable;
78
+ currentScope = currentScope.upper;
79
+ }
80
+ return undefined;
81
+ }
82
+ function resolveIdentifierToMicrodiffCall(identifier) {
83
+ // Look for a reference in the current scope chain
84
+ const variable = findVariableInScopeChain(identifier);
85
+ if (!variable)
86
+ return undefined;
87
+ const defNode = variable.defs[0]?.node;
88
+ if (!defNode)
89
+ return undefined;
90
+ // Variable declarator with initializer call
91
+ if (defNode.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
92
+ const { init } = defNode;
93
+ if (init &&
94
+ init.type === utils_1.AST_NODE_TYPES.CallExpression &&
95
+ init.callee &&
96
+ init.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
97
+ init.callee.name === microdiffImportName) {
98
+ return init;
99
+ }
100
+ }
101
+ return undefined;
102
+ }
103
+ /**
104
+ * Determine if the given variable is used only for `.length` property accesses.
105
+ * If there is any non-length usage (e.g., indexing, iteration, method calls), return false.
106
+ */
107
+ function isVariableOnlyUsedForLength(identifier) {
108
+ // Walk up scope chain to find a reference for this identifier
109
+ const variable = findVariableInScopeChain(identifier);
110
+ if (!variable)
111
+ return false;
112
+ // All references must be strictly of the form <id>.length, except the variable's own declaration
113
+ return variable.references.every((reference) => {
114
+ const idNode = reference.identifier;
115
+ const parent = idNode.parent;
116
+ if (!parent)
117
+ return false;
118
+ // Allow the declaration id (not considered a read)
119
+ if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
120
+ parent.id.type === utils_1.AST_NODE_TYPES.Identifier &&
121
+ parent.id === idNode) {
122
+ return true;
123
+ }
124
+ // Accept only the specific pattern: Identifier used as object in MemberExpression with property `length`
125
+ if (parent.type === utils_1.AST_NODE_TYPES.MemberExpression &&
126
+ parent.object === idNode &&
127
+ !parent.computed &&
128
+ parent.property.type === utils_1.AST_NODE_TYPES.Identifier &&
129
+ parent.property.name === 'length') {
130
+ return true;
131
+ }
132
+ // Accept optional chaining: identifier?.length represented as ChainExpression
133
+ if (parent.type === utils_1.AST_NODE_TYPES.ChainExpression &&
134
+ parent.expression.type === utils_1.AST_NODE_TYPES.MemberExpression &&
135
+ parent.expression.object === idNode &&
136
+ !parent.expression.computed &&
137
+ parent.expression.property.type === utils_1.AST_NODE_TYPES.Identifier &&
138
+ parent.expression.property.name === 'length') {
139
+ return true;
140
+ }
141
+ return false;
142
+ });
143
+ }
144
+ /**
145
+ * If a MemberExpression is `<something>.length`, returns the underlying microdiff call if applicable.
146
+ * Supports direct `diff(a,b).length` and `changes.length` where `changes` is initialized to a microdiff call.
147
+ */
148
+ function getMicrodiffCallFromLengthAccess(member) {
149
+ if (!hasMicrodiffImport)
150
+ return { viaIdentifier: false };
151
+ if (member.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
152
+ member.property.name !== 'length') {
153
+ return { viaIdentifier: false };
154
+ }
155
+ const obj = member.object;
156
+ if (obj.type === utils_1.AST_NODE_TYPES.CallExpression &&
157
+ isMicrodiffCallee(obj.callee)) {
158
+ return { diffCall: obj, viaIdentifier: false };
159
+ }
160
+ if (obj.type === utils_1.AST_NODE_TYPES.Identifier) {
161
+ const resolved = resolveIdentifierToMicrodiffCall(obj);
162
+ if (!resolved)
163
+ return { viaIdentifier: true };
164
+ // Ensure the identifier is only used for `.length` accesses (no content-based usage)
165
+ if (!isVariableOnlyUsedForLength(obj)) {
166
+ return { viaIdentifier: true };
167
+ }
168
+ return { diffCall: resolved, viaIdentifier: true };
169
+ }
170
+ return { viaIdentifier: false };
171
+ }
28
172
  /**
29
173
  * Check if a node is a microdiff equality check pattern
30
174
  * Looks for patterns like:
31
175
  * - diff(a, b).length === 0
32
- * - diff(a, b).length !== 0
176
+ * - 0 === diff(a, b).length
177
+ * - changes.length === 0 (where `changes = diff(a,b)` and changes is only used for length checks)
178
+ * - changes.length !== 0
33
179
  * - !diff(a, b).length
180
+ * - !changes.length
34
181
  */
35
182
  function isMicrodiffEqualityCheck(node) {
36
- // Check for binary expressions like diff(a, b).length === 0
37
- if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
38
- (node.operator === '===' ||
39
- node.operator === '==' ||
40
- node.operator === '!==' ||
41
- node.operator === '!=') &&
42
- node.right.type === utils_1.AST_NODE_TYPES.Literal &&
43
- node.right.value === 0 &&
44
- node.left.type === utils_1.AST_NODE_TYPES.MemberExpression &&
45
- node.left.property.type === utils_1.AST_NODE_TYPES.Identifier &&
46
- node.left.property.name === 'length' &&
47
- node.left.object.type === utils_1.AST_NODE_TYPES.CallExpression &&
48
- node.left.object.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
49
- node.left.object.callee.name === microdiffImportName) {
50
- return {
51
- isEquality: node.operator === '===' || node.operator === '==',
52
- diffCall: node.left.object,
53
- };
54
- }
55
- // Check for unary expressions like !diff(a, b).length
183
+ // Check for binary expressions comparing length to 0 (both directions)
184
+ if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
185
+ const operators = [
186
+ '===',
187
+ '==',
188
+ '!==',
189
+ '!=',
190
+ ];
191
+ if (operators.includes(node.operator)) {
192
+ // side A: MemberExpression .length, side B: 0
193
+ if (node.right.type === utils_1.AST_NODE_TYPES.Literal &&
194
+ node.right.value === 0 &&
195
+ node.left.type === utils_1.AST_NODE_TYPES.MemberExpression) {
196
+ const { diffCall } = getMicrodiffCallFromLengthAccess(node.left);
197
+ if (diffCall) {
198
+ return {
199
+ isEquality: node.operator === '===' || node.operator === '==',
200
+ diffCall,
201
+ };
202
+ }
203
+ }
204
+ // side A: 0, side B: MemberExpression .length
205
+ if (node.left.type === utils_1.AST_NODE_TYPES.Literal &&
206
+ node.left.value === 0 &&
207
+ node.right.type === utils_1.AST_NODE_TYPES.MemberExpression) {
208
+ const { diffCall } = getMicrodiffCallFromLengthAccess(node.right);
209
+ if (diffCall) {
210
+ return {
211
+ isEquality: node.operator === '===' || node.operator === '==',
212
+ diffCall,
213
+ };
214
+ }
215
+ }
216
+ }
217
+ }
218
+ // Check for unary expressions like !diff(a, b).length or !changes.length (including optional chaining)
56
219
  if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
57
- node.operator === '!' &&
58
- node.argument.type === utils_1.AST_NODE_TYPES.MemberExpression &&
59
- node.argument.property.type === utils_1.AST_NODE_TYPES.Identifier &&
60
- node.argument.property.name === 'length' &&
61
- node.argument.object.type === utils_1.AST_NODE_TYPES.CallExpression &&
62
- node.argument.object.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
63
- node.argument.object.callee.name === microdiffImportName) {
64
- return {
65
- isEquality: true,
66
- diffCall: node.argument.object,
67
- };
220
+ node.operator === '!') {
221
+ const argumentNode = node.argument;
222
+ const target = isChainExpression(argumentNode)
223
+ ? argumentNode.expression
224
+ : argumentNode;
225
+ if (target.type === utils_1.AST_NODE_TYPES.MemberExpression) {
226
+ const { diffCall } = getMicrodiffCallFromLengthAccess(target);
227
+ if (diffCall) {
228
+ return {
229
+ isEquality: true,
230
+ diffCall,
231
+ };
232
+ }
233
+ }
68
234
  }
69
235
  // Not a microdiff equality check
70
236
  return { isEquality: false };
71
237
  }
238
+ /**
239
+ * Try to find the identifier used as `<id>.length` for the given equality node
240
+ */
241
+ function getLengthIdentifierFromNode(node) {
242
+ if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
243
+ const left = node.left;
244
+ const right = node.right;
245
+ const isLengthMember = (n) => n.type === utils_1.AST_NODE_TYPES.MemberExpression &&
246
+ !n.computed &&
247
+ n.property.type === utils_1.AST_NODE_TYPES.Identifier &&
248
+ n.property.name === 'length';
249
+ if (isLengthMember(left) &&
250
+ left.object.type === utils_1.AST_NODE_TYPES.Identifier) {
251
+ return left.object;
252
+ }
253
+ if (isLengthMember(right) &&
254
+ right.object.type === utils_1.AST_NODE_TYPES.Identifier) {
255
+ return right.object;
256
+ }
257
+ }
258
+ if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression) {
259
+ const arg = node.argument;
260
+ if (arg.type === utils_1.AST_NODE_TYPES.MemberExpression &&
261
+ !arg.computed &&
262
+ arg.property.type === utils_1.AST_NODE_TYPES.Identifier &&
263
+ arg.property.name === 'length' &&
264
+ arg.object.type === utils_1.AST_NODE_TYPES.Identifier) {
265
+ return arg.object;
266
+ }
267
+ }
268
+ return undefined;
269
+ }
270
+ /**
271
+ * Get the indentation at the start of the current line for a node
272
+ */
273
+ function getLineBaseIndent(node) {
274
+ const text = sourceCode.text;
275
+ const start = node.range?.[0] ?? sourceCode.getIndexFromLoc(node.loc.start);
276
+ const lineStart = text.lastIndexOf('\n', start - 1) + 1;
277
+ let i = lineStart;
278
+ let indent = '';
279
+ while (i < text.length) {
280
+ const ch = text[i];
281
+ if (ch === ' ' || ch === '\t') {
282
+ indent += ch;
283
+ i++;
284
+ }
285
+ else {
286
+ break;
287
+ }
288
+ }
289
+ return indent;
290
+ }
72
291
  /**
73
292
  * Create a fix for replacing microdiff equality check with fast-deep-equal
74
293
  */
@@ -79,21 +298,90 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
79
298
  }
80
299
  const arg1 = sourceCode.getText(args[0]);
81
300
  const arg2 = sourceCode.getText(args[1]);
82
- // If fast-deep-equal is not imported, we need to add the import after the microdiff import
83
- if (!hasFastDeepEqualImport) {
84
- // Find the end of the microdiff import statement
301
+ const fixes = [];
302
+ // Add import if needed (only once across all fixes in this file)
303
+ if (!hasFastDeepEqualImport && !plannedFastDeepEqualImport) {
85
304
  const importDeclarations = sourceCode.ast.body.filter((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
86
305
  const microdiffImport = importDeclarations.find((node) => node.source.value === 'microdiff');
87
- const importFix = fixer.insertTextAfter(microdiffImport, `\nimport isEqual from 'fast-deep-equal';`);
88
- const replaceFix = fixer.replaceText(node, isEquality
89
- ? `isEqual(${arg1}, ${arg2})`
90
- : `!isEqual(${arg1}, ${arg2})`);
91
- return [importFix, replaceFix];
92
- }
93
- // Otherwise just replace the expression
94
- return fixer.replaceText(node, isEquality
95
- ? `${fastDeepEqualImportName}(${arg1}, ${arg2})`
96
- : `!${fastDeepEqualImportName}(${arg1}, ${arg2})`);
306
+ if (microdiffImport) {
307
+ fixes.push(fixer.insertTextAfter(microdiffImport, `\nimport ${fastDeepEqualImportName} from 'fast-deep-equal';`));
308
+ plannedFastDeepEqualImport = true;
309
+ }
310
+ else {
311
+ const lastImport = importDeclarations[importDeclarations.length - 1];
312
+ if (lastImport) {
313
+ fixes.push(fixer.insertTextAfter(lastImport, `\nimport ${fastDeepEqualImportName} from 'fast-deep-equal';`));
314
+ }
315
+ else {
316
+ fixes.push(fixer.insertTextBeforeRange([0, 0], `import ${fastDeepEqualImportName} from 'fast-deep-equal';\n`));
317
+ }
318
+ plannedFastDeepEqualImport = true;
319
+ }
320
+ }
321
+ // If the equality was via an identifier like `changes.length`, and that identifier
322
+ // is declared as `const changes = diff(...);` and used ONLY for `.length` checks,
323
+ // remove the redundant variable declaration.
324
+ const maybeIdentifier = getLengthIdentifierFromNode(node);
325
+ if (maybeIdentifier && isVariableOnlyUsedForLength(maybeIdentifier)) {
326
+ const variable = findVariableInScopeChain(maybeIdentifier);
327
+ const defNode = variable?.defs?.[0]?.node;
328
+ if (defNode &&
329
+ defNode.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
330
+ defNode.parent &&
331
+ defNode.parent.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
332
+ const declaration = defNode.parent;
333
+ // Only remove if it's the only declaration in the const/let statement
334
+ if (declaration.declarations.length === 1) {
335
+ const text = sourceCode.text;
336
+ const startOfLine = text.lastIndexOf('\n', declaration.range[0] - 1) + 1;
337
+ const nextNewline = text.indexOf('\n', declaration.range[1]);
338
+ const endOfLine = nextNewline === -1 ? declaration.range[1] : nextNewline + 1;
339
+ fixes.push(fixer.removeRange([startOfLine, endOfLine]));
340
+ }
341
+ }
342
+ }
343
+ // Replace the equality expression with isEqual call
344
+ const isMultilineCall = diffCall.loc &&
345
+ args[0].loc &&
346
+ (diffCall.loc.start.line !== args[0].loc.start.line ||
347
+ (args[1] && args[0].loc.start.line !== args[1].loc.start.line));
348
+ let replacement;
349
+ if (isMultilineCall) {
350
+ const baseIndent = getLineBaseIndent(node);
351
+ const innerIndentPlus = baseIndent + ' ';
352
+ replacement =
353
+ `${fastDeepEqualImportName}(` +
354
+ `\n${innerIndentPlus}${arg1},` +
355
+ `\n${innerIndentPlus}${arg2},` +
356
+ `\n${baseIndent})`;
357
+ }
358
+ else {
359
+ replacement = `${fastDeepEqualImportName}(${arg1}, ${arg2})`;
360
+ }
361
+ if (!isEquality) {
362
+ replacement = `!${replacement}`;
363
+ }
364
+ fixes.push(fixer.replaceText(node, replacement));
365
+ return fixes;
366
+ }
367
+ function reportEqualityCheck(node, result) {
368
+ const { diffCall, isEquality } = result;
369
+ if (!diffCall)
370
+ return;
371
+ if (reportedNodes.has(node))
372
+ return;
373
+ reportedNodes.add(node);
374
+ context.report({
375
+ node,
376
+ messageId: 'useFastDeepEqual',
377
+ data: {
378
+ diffName: microdiffImportName,
379
+ fastEqualName: fastDeepEqualImportName,
380
+ },
381
+ fix(fixer) {
382
+ return createFix(fixer, node, diffCall, isEquality);
383
+ },
384
+ });
97
385
  }
98
386
  return {
99
387
  // Track imports of microdiff and fast-deep-equal
@@ -101,12 +389,16 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
101
389
  const importSource = node.source.value;
102
390
  // Check for microdiff import
103
391
  if (importSource === 'microdiff') {
392
+ hasMicrodiffImport = true;
104
393
  // Get the local name of the imported diff function
105
394
  node.specifiers.forEach((specifier) => {
106
395
  if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
107
396
  specifier.imported.name === 'diff') {
108
397
  microdiffImportName = specifier.local.name;
109
398
  }
399
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
400
+ microdiffImportName = specifier.local.name;
401
+ }
110
402
  });
111
403
  }
112
404
  // Check for fast-deep-equal import
@@ -123,58 +415,34 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
123
415
  },
124
416
  // Check expressions for microdiff equality patterns
125
417
  ['BinaryExpression, UnaryExpression'](node) {
418
+ if (!hasMicrodiffImport)
419
+ return;
126
420
  // Skip if we've already reported this node
127
421
  if (reportedNodes.has(node)) {
128
422
  return;
129
423
  }
130
- const result = isMicrodiffEqualityCheck(node);
131
- if (result.isEquality !== undefined && result.diffCall) {
132
- reportedNodes.add(node);
133
- context.report({
134
- node,
135
- messageId: 'useFastDeepEqual',
136
- fix(fixer) {
137
- return createFix(fixer, node, result.diffCall, result.isEquality);
138
- },
139
- });
140
- }
424
+ reportEqualityCheck(node, isMicrodiffEqualityCheck(node));
141
425
  },
142
426
  // Check if statements for microdiff equality patterns
143
427
  IfStatement(node) {
428
+ if (!hasMicrodiffImport)
429
+ return;
144
430
  // Skip if we've already reported this node
145
431
  if (reportedNodes.has(node.test)) {
146
432
  return;
147
433
  }
148
- const result = isMicrodiffEqualityCheck(node.test);
149
- if (result.isEquality !== undefined && result.diffCall) {
150
- reportedNodes.add(node.test);
151
- context.report({
152
- node: node.test,
153
- messageId: 'useFastDeepEqual',
154
- fix(fixer) {
155
- return createFix(fixer, node.test, result.diffCall, result.isEquality);
156
- },
157
- });
158
- }
434
+ reportEqualityCheck(node.test, isMicrodiffEqualityCheck(node.test));
159
435
  },
160
436
  // Check return statements for microdiff equality patterns
161
437
  ReturnStatement(node) {
438
+ if (!hasMicrodiffImport)
439
+ return;
162
440
  // Skip if we've already reported this node or if there's no argument
163
- if (!node.argument || reportedNodes.has(node.argument)) {
441
+ const argument = node.argument;
442
+ if (!argument || reportedNodes.has(argument)) {
164
443
  return;
165
444
  }
166
- const result = isMicrodiffEqualityCheck(node.argument);
167
- if (result.isEquality !== undefined && result.diffCall) {
168
- reportedNodes.add(node.argument);
169
- context.report({
170
- node: node.argument,
171
- messageId: 'useFastDeepEqual',
172
- fix(fixer) {
173
- // We already checked that node.argument is not null above
174
- return createFix(fixer, node.argument, result.diffCall, result.isEquality);
175
- },
176
- });
177
- }
445
+ reportEqualityCheck(argument, isMicrodiffEqualityCheck(argument));
178
446
  },
179
447
  };
180
448
  },
@@ -0,0 +1 @@
1
+ export declare const firestoreTransactionReadsBeforeWrites: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"readsAfterWrites", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;