@blumintinc/eslint-plugin-blumint 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +170 -139
  2. package/lib/index.js +190 -1
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  5. package/lib/rules/consistent-callback-naming.js +1 -1
  6. package/lib/rules/dynamic-https-errors.js +3 -4
  7. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  8. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
  9. package/lib/rules/enforce-callback-memo.js +1 -1
  10. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  11. package/lib/rules/enforce-console-error.js +35 -7
  12. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  13. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  14. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  15. package/lib/rules/enforce-early-destructuring.js +980 -0
  16. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  17. package/lib/rules/enforce-empty-object-check.js +502 -0
  18. package/lib/rules/enforce-exported-function-types.js +1 -1
  19. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  20. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  21. package/lib/rules/enforce-firestore-facade.js +4 -2
  22. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  23. package/lib/rules/enforce-global-constants.js +1 -1
  24. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  25. package/lib/rules/enforce-memoize-async.js +2 -6
  26. package/lib/rules/enforce-memoize-getters.js +39 -15
  27. package/lib/rules/enforce-microdiff.js +1 -1
  28. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  29. package/lib/rules/enforce-positive-naming.js +4 -0
  30. package/lib/rules/enforce-props-argument-name.js +2 -2
  31. package/lib/rules/enforce-safe-stringify.js +1 -1
  32. package/lib/rules/enforce-serializable-params.js +3 -3
  33. package/lib/rules/enforce-singular-type-names.js +1 -1
  34. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  35. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  36. package/lib/rules/enforce-storage-context.d.ts +9 -0
  37. package/lib/rules/enforce-storage-context.js +646 -0
  38. package/lib/rules/enforce-timestamp-now.js +23 -7
  39. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  40. package/lib/rules/enforce-transform-memoization.js +416 -0
  41. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  42. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  43. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  44. package/lib/rules/enforce-verb-noun-naming.js +5 -4
  45. package/lib/rules/ensure-pointer-events-none.js +28 -2
  46. package/lib/rules/export-if-in-doubt.js +27 -4
  47. package/lib/rules/extract-global-constants.js +40 -13
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  49. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  50. package/lib/rules/flatten-push-calls.d.ts +2 -0
  51. package/lib/rules/flatten-push-calls.js +428 -0
  52. package/lib/rules/global-const-style.js +50 -19
  53. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  54. package/lib/rules/jsdoc-above-field.js +208 -0
  55. package/lib/rules/key-only-outermost-element.js +21 -3
  56. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  57. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  58. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  59. package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
  60. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  61. package/lib/rules/memo-nested-react-components.js +410 -0
  62. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  63. package/lib/rules/no-always-true-false-conditions.js +68 -8
  64. package/lib/rules/no-array-length-in-deps.js +9 -3
  65. package/lib/rules/no-async-foreach.js +210 -12
  66. package/lib/rules/no-circular-references.js +18 -17
  67. package/lib/rules/no-class-instance-destructuring.js +55 -12
  68. package/lib/rules/no-complex-cloud-params.js +7 -3
  69. package/lib/rules/no-compositing-layer-props.js +2 -2
  70. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  71. package/lib/rules/no-console-error.d.ts +9 -0
  72. package/lib/rules/no-console-error.js +525 -0
  73. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  74. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  75. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  76. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  77. package/lib/rules/no-entire-object-hook-deps.js +4 -6
  78. package/lib/rules/no-excessive-parent-chain.js +1 -1
  79. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  80. package/lib/rules/no-explicit-return-type.js +215 -26
  81. package/lib/rules/no-filter-without-return.js +5 -1
  82. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  83. package/lib/rules/no-firestore-jest-mock.js +126 -8
  84. package/lib/rules/no-firestore-object-arrays.js +67 -12
  85. package/lib/rules/no-handler-suffix.d.ts +12 -0
  86. package/lib/rules/no-handler-suffix.js +305 -0
  87. package/lib/rules/no-hungarian.js +1 -1
  88. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  89. package/lib/rules/no-inline-component-prop.js +456 -0
  90. package/lib/rules/no-jsx-in-hooks.js +6 -1
  91. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  92. package/lib/rules/no-margin-properties.js +6 -6
  93. package/lib/rules/no-memoize-on-static.js +9 -1
  94. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  95. package/lib/rules/no-misused-switch-case.js +22 -1
  96. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  97. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  98. package/lib/rules/no-mock-firebase-admin.js +5 -2
  99. package/lib/rules/no-object-values-on-strings.js +10 -2
  100. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  101. package/lib/rules/no-passthrough-getters.js +38 -2
  102. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  103. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  104. package/lib/rules/no-redundant-param-types.js +16 -6
  105. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  106. package/lib/rules/no-redundant-this-params.js +459 -0
  107. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  108. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  109. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  110. package/lib/rules/no-restricted-properties-fix.js +11 -10
  111. package/lib/rules/no-separate-loading-state.js +7 -16
  112. package/lib/rules/no-stale-state-across-await.js +1 -1
  113. package/lib/rules/no-type-assertion-returns.js +43 -49
  114. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  115. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  116. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  117. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  118. package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
  119. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  120. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  121. package/lib/rules/no-unpinned-dependencies.js +36 -5
  122. package/lib/rules/no-unused-props.d.ts +2 -2
  123. package/lib/rules/no-unused-props.js +295 -91
  124. package/lib/rules/no-unused-usestate.js +6 -2
  125. package/lib/rules/no-useless-fragment.js +28 -2
  126. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  127. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  128. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  129. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  130. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  131. package/lib/rules/omit-index-html.d.ts +2 -1
  132. package/lib/rules/omit-index-html.js +62 -7
  133. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  134. package/lib/rules/parallelize-async-operations.js +24 -5
  135. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  136. package/lib/rules/prefer-batch-operations.js +32 -5
  137. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  138. package/lib/rules/prefer-clone-deep.js +3 -3
  139. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  140. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  141. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  142. package/lib/rules/prefer-docsetter-setall.js +243 -0
  143. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  144. package/lib/rules/prefer-fragment-component.js +1 -1
  145. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  146. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  147. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  148. package/lib/rules/prefer-global-router-state-key.js +88 -13
  149. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  150. package/lib/rules/prefer-memoized-props.js +445 -0
  151. package/lib/rules/prefer-next-dynamic.js +60 -69
  152. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
  153. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  154. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  155. package/lib/rules/prefer-params-over-parent-id.js +220 -171
  156. package/lib/rules/prefer-settings-object.js +2 -2
  157. package/lib/rules/prefer-type-over-interface.js +7 -2
  158. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  159. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  160. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  161. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  162. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  163. package/lib/rules/prevent-children-clobber.js +536 -0
  164. package/lib/rules/react-memoize-literals.d.ts +4 -0
  165. package/lib/rules/react-memoize-literals.js +495 -0
  166. package/lib/rules/react-usememo-should-be-component.js +2 -2
  167. package/lib/rules/require-hooks-default-params.js +17 -16
  168. package/lib/rules/require-https-error-cause.d.ts +4 -0
  169. package/lib/rules/require-https-error-cause.js +136 -0
  170. package/lib/rules/require-https-error.js +43 -21
  171. package/lib/rules/require-image-optimized.js +1 -1
  172. package/lib/rules/require-memo.js +1 -1
  173. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  174. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  175. package/lib/rules/require-usememo-object-literals.js +2 -3
  176. package/lib/rules/test-file-location-enforcement.js +1 -1
  177. package/lib/rules/use-latest-callback.js +6 -5
  178. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  179. package/lib/rules/vertically-group-related-functions.js +480 -0
  180. package/lib/utils/ASTHelpers.d.ts +13 -1
  181. package/lib/utils/ASTHelpers.js +24 -0
  182. package/lib/utils/getMethodName.d.ts +27 -0
  183. package/lib/utils/getMethodName.js +35 -0
  184. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  185. package/lib/utils/tsTypeClassifier.js +149 -0
  186. package/package.json +3 -1
@@ -104,7 +104,7 @@ exports.preferDestructuringNoClass = (0, createRule_1.createRule)({
104
104
  },
105
105
  ],
106
106
  messages: {
107
- preferDestructuring: 'Use destructuring instead of accessing the property directly.',
107
+ preferDestructuring: 'Property "{{property}}" from "{{object}}" is assigned via dot access{{targetNote}}. Destructure the property so the dependency is declared once and stays aligned with the source object. Use destructuring{{renamingHint}} (e.g., {{example}}).',
108
108
  },
109
109
  },
110
110
  defaultOptions,
@@ -114,6 +114,7 @@ exports.preferDestructuringNoClass = (0, createRule_1.createRule)({
114
114
  enforceForRenamedProperties: defaultOptions[0].enforceForRenamedProperties,
115
115
  ...context.options[0],
116
116
  };
117
+ const sourceCode = context.getSourceCode();
117
118
  /**
118
119
  * Check if we're inside a class method
119
120
  */
@@ -128,32 +129,228 @@ exports.preferDestructuringNoClass = (0, createRule_1.createRule)({
128
129
  }
129
130
  return false;
130
131
  }
132
+ function isIdentifierTarget(node) {
133
+ return node.type === utils_1.AST_NODE_TYPES.Identifier;
134
+ }
135
+ function isSkippedClassMemberAccess(memberExpression) {
136
+ return (isClassInstance(memberExpression, context) ||
137
+ isStaticClassMember(memberExpression, context));
138
+ }
139
+ function isThisMemberInClassMethod(memberExpression) {
140
+ return (memberExpression.object.type === utils_1.AST_NODE_TYPES.ThisExpression &&
141
+ isInsideClassMethod(memberExpression));
142
+ }
143
+ function findEnclosingMethodDefinition(node) {
144
+ let current = node;
145
+ while (current) {
146
+ if (current.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
147
+ return current;
148
+ }
149
+ current = current.parent ?? undefined;
150
+ }
151
+ return null;
152
+ }
153
+ function isInsideConstructorBody(node) {
154
+ let current = node;
155
+ while (current) {
156
+ if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
157
+ current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
158
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
159
+ const enclosingMethod = findEnclosingMethodDefinition(current);
160
+ return (!!enclosingMethod &&
161
+ enclosingMethod.kind === 'constructor' &&
162
+ enclosingMethod.value === current);
163
+ }
164
+ current = current.parent ?? undefined;
165
+ }
166
+ return false;
167
+ }
168
+ function isPrivateIdentifierProperty(property) {
169
+ return property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier;
170
+ }
171
+ function canDestructureObjectProperty(memberExpression, identifier) {
172
+ if (isPrivateIdentifierProperty(memberExpression.property)) {
173
+ return false;
174
+ }
175
+ if (!options.object) {
176
+ return false;
177
+ }
178
+ if (options.enforceForRenamedProperties) {
179
+ return true;
180
+ }
181
+ return isMatchingPropertyName(memberExpression.property, identifier.name);
182
+ }
183
+ function getPatternKeyText(memberExpression, propertyText) {
184
+ if (memberExpression.computed) {
185
+ return `[${sourceCode.getText(memberExpression.property)}]`;
186
+ }
187
+ return propertyText;
188
+ }
189
+ function getDestructuringBindingText(memberExpression, propertyText, targetName) {
190
+ if (isPrivateIdentifierProperty(memberExpression.property)) {
191
+ return null;
192
+ }
193
+ const patternKeyText = getPatternKeyText(memberExpression, propertyText);
194
+ if (memberExpression.computed ||
195
+ (options.enforceForRenamedProperties &&
196
+ !isMatchingPropertyName(memberExpression.property, targetName))) {
197
+ return `${patternKeyText}: ${targetName}`;
198
+ }
199
+ return patternKeyText;
200
+ }
131
201
  /**
132
202
  * Check if destructuring should be used for this node
133
203
  */
134
204
  function shouldUseDestructuring(node, leftNode) {
135
- // Skip if this is a class instance or static class member
136
- if (isClassInstance(node, context) ||
137
- isStaticClassMember(node, context)) {
205
+ if (!isIdentifierTarget(leftNode)) {
138
206
  return false;
139
207
  }
140
- // Skip if the object is 'this' and we're inside a class method
141
- if (node.object.type === utils_1.AST_NODE_TYPES.ThisExpression &&
142
- isInsideClassMethod(node)) {
143
- return false;
208
+ return (!isSkippedClassMemberAccess(node) &&
209
+ !isThisMemberInClassMethod(node) &&
210
+ canDestructureObjectProperty(node, leftNode));
211
+ }
212
+ /**
213
+ * Extracts the property name from a MemberExpression when it can be safely compared.
214
+ */
215
+ function getMemberExpressionPropertyName(memberExpression) {
216
+ if (isPrivateIdentifierProperty(memberExpression.property)) {
217
+ return null;
144
218
  }
145
- // Check object destructuring
146
- if (options.object) {
147
- if (options.enforceForRenamedProperties) {
148
- return true;
219
+ if (!memberExpression.computed) {
220
+ if (memberExpression.property.type === utils_1.AST_NODE_TYPES.Identifier) {
221
+ return memberExpression.property.name;
149
222
  }
150
- // Only suggest destructuring when property name matches variable name
151
- if (leftNode.type === utils_1.AST_NODE_TYPES.Identifier) {
152
- return isMatchingPropertyName(node.property, leftNode.name);
223
+ return null;
224
+ }
225
+ if (memberExpression.property.type === utils_1.AST_NODE_TYPES.Literal &&
226
+ typeof memberExpression.property.value === 'string') {
227
+ return memberExpression.property.value;
228
+ }
229
+ return null;
230
+ }
231
+ /**
232
+ * Look up a variable by name within a scope.
233
+ */
234
+ function findVariableInScope(scope, name) {
235
+ return scope?.variables.find((variable) => variable.name === name);
236
+ }
237
+ /**
238
+ * Check whether a variable definition originates from a parameter.
239
+ */
240
+ function isParameterDefinition(variable) {
241
+ if (!variable || !Array.isArray(variable.defs)) {
242
+ return false;
243
+ }
244
+ return variable.defs.some((definition) => definition.type === 'Parameter');
245
+ }
246
+ /**
247
+ * Determine whether an identifier refers to a function or method parameter.
248
+ */
249
+ function isFunctionParameter(identifier) {
250
+ let scope = context.getScope();
251
+ while (scope) {
252
+ const variable = findVariableInScope(scope, identifier.name);
253
+ if (variable) {
254
+ return isParameterDefinition(variable);
153
255
  }
256
+ scope = scope.upper;
154
257
  }
155
258
  return false;
156
259
  }
260
+ function buildReportDetails(memberExpr, targetName, examplePrefix, exampleSuffix) {
261
+ const objectText = sourceCode.getText(memberExpr.object);
262
+ const propertyText = getPropertyText(memberExpr.property, memberExpr.computed, sourceCode);
263
+ const usesRenaming = options.enforceForRenamedProperties &&
264
+ !!targetName &&
265
+ !isMatchingPropertyName(memberExpr.property, targetName);
266
+ const aliasName = targetName ?? propertyText;
267
+ const patternKeyText = getPatternKeyText(memberExpr, propertyText);
268
+ const destructuringBinding = getDestructuringBindingText(memberExpr, propertyText, aliasName) ??
269
+ patternKeyText;
270
+ return {
271
+ propertyText,
272
+ objectText,
273
+ destructuringBinding,
274
+ data: {
275
+ property: propertyText,
276
+ object: objectText,
277
+ targetNote: usesRenaming && targetName ? ` to "${targetName}"` : '',
278
+ renamingHint: usesRenaming ? ' with renaming' : '',
279
+ example: usesRenaming
280
+ ? `${examplePrefix}{ ${destructuringBinding} } = ${objectText}${exampleSuffix}`
281
+ : `${examplePrefix}{ ${destructuringBinding} } = ${objectText}${exampleSuffix}`,
282
+ },
283
+ };
284
+ }
285
+ function generateVariableDeclaratorFix(fixer, node, propertyText, objectText, memberExpr) {
286
+ const parentNode = node.parent;
287
+ if (!parentNode ||
288
+ parentNode.type !== utils_1.AST_NODE_TYPES.VariableDeclaration) {
289
+ return null;
290
+ }
291
+ if (parentNode.declarations.length > 1) {
292
+ return null;
293
+ }
294
+ const kind = parentNode.kind;
295
+ if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
296
+ return null;
297
+ }
298
+ const destructuringBinding = getDestructuringBindingText(memberExpr, propertyText, node.id.name);
299
+ if (!destructuringBinding) {
300
+ return null;
301
+ }
302
+ return fixer.replaceText(parentNode, `${kind} { ${destructuringBinding} } = ${objectText};`);
303
+ }
304
+ function generateAssignmentExpressionFix(fixer, node, propertyText, objectText, memberExpr) {
305
+ if (node.left.type !== utils_1.AST_NODE_TYPES.Identifier) {
306
+ return null;
307
+ }
308
+ const destructuringBinding = getDestructuringBindingText(memberExpr, propertyText, node.left.name);
309
+ if (!destructuringBinding) {
310
+ return null;
311
+ }
312
+ return fixer.replaceText(node, `({ ${destructuringBinding} } = ${objectText})`);
313
+ }
314
+ /**
315
+ * Report assignments that copy properties from parameter objects to class fields.
316
+ * These are reported without a fixer to avoid changing function signatures.
317
+ */
318
+ function handleClassPropertyAssignment(node) {
319
+ // Caller ensures node.right is a MemberExpression.
320
+ if (!options.object ||
321
+ node.left.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
322
+ node.left.object.type !== utils_1.AST_NODE_TYPES.ThisExpression) {
323
+ return;
324
+ }
325
+ if (!isInsideConstructorBody(node)) {
326
+ return;
327
+ }
328
+ const rightObject = node.right.object;
329
+ if (rightObject.type !== utils_1.AST_NODE_TYPES.Identifier ||
330
+ !isFunctionParameter(rightObject)) {
331
+ return;
332
+ }
333
+ const leftPropertyName = getMemberExpressionPropertyName(node.left);
334
+ const rightPropertyName = getMemberExpressionPropertyName(node.right);
335
+ if (!leftPropertyName || !rightPropertyName) {
336
+ return;
337
+ }
338
+ if (!options.enforceForRenamedProperties &&
339
+ leftPropertyName !== rightPropertyName) {
340
+ return;
341
+ }
342
+ if (isClassInstance(node.right, context) ||
343
+ isStaticClassMember(node.right, context)) {
344
+ return;
345
+ }
346
+ const { data } = buildReportDetails(node.right, leftPropertyName, '(', ')');
347
+ // No fixer here because destructuring parameters changes the function signature and must stay manual.
348
+ context.report({
349
+ node,
350
+ messageId: 'preferDestructuring',
351
+ data,
352
+ });
353
+ }
157
354
  return {
158
355
  VariableDeclarator(node) {
159
356
  // Skip if variable is declared without assignment or if init is not a MemberExpression
@@ -161,57 +358,45 @@ exports.preferDestructuringNoClass = (0, createRule_1.createRule)({
161
358
  return;
162
359
  if (node.init.type !== utils_1.AST_NODE_TYPES.MemberExpression)
163
360
  return;
164
- if (shouldUseDestructuring(node.init, node.id)) {
165
- const sourceCode = context.getSourceCode();
166
- const objectText = sourceCode.getText(node.init.object);
167
- const propertyText = getPropertyText(node.init.property, node.init.computed, sourceCode);
361
+ const memberInit = node.init;
362
+ if (!shouldUseDestructuring(memberInit, node.id)) {
363
+ return;
364
+ }
365
+ const targetName = node.id.type === utils_1.AST_NODE_TYPES.Identifier ? node.id.name : null;
366
+ const { propertyText, objectText, data } = buildReportDetails(memberInit, targetName, `${node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclaration
367
+ ? node.parent.kind
368
+ : 'const'} `, ';');
369
+ context.report({
370
+ node,
371
+ messageId: 'preferDestructuring',
372
+ data,
373
+ fix(fixer) {
374
+ return generateVariableDeclaratorFix(fixer, node, propertyText, objectText, memberInit);
375
+ },
376
+ });
377
+ },
378
+ AssignmentExpression(node) {
379
+ if (node.operator !== '=' ||
380
+ node.right.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
381
+ return;
382
+ }
383
+ const memberRight = node.right;
384
+ if (shouldUseDestructuring(memberRight, node.left)) {
385
+ const targetName = node.left.type === utils_1.AST_NODE_TYPES.Identifier
386
+ ? node.left.name
387
+ : null;
388
+ const { propertyText, objectText, data } = buildReportDetails(memberRight, targetName, '(', ')');
168
389
  context.report({
169
390
  node,
170
391
  messageId: 'preferDestructuring',
392
+ data,
171
393
  fix(fixer) {
172
- // Get the variable declaration kind (const, let, var)
173
- const parentNode = node.parent;
174
- if (!parentNode ||
175
- parentNode.type !== utils_1.AST_NODE_TYPES.VariableDeclaration) {
176
- return null;
177
- }
178
- const kind = parentNode.kind;
179
- // Handle renamed properties
180
- if (options.enforceForRenamedProperties &&
181
- node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
182
- node.init &&
183
- node.init.type === utils_1.AST_NODE_TYPES.MemberExpression &&
184
- !isMatchingPropertyName(node.init.property, node.id.name)) {
185
- return fixer.replaceText(parentNode, `${kind} { ${propertyText}: ${node.id.name} } = ${objectText};`);
186
- }
187
- return fixer.replaceText(parentNode, `${kind} { ${propertyText} } = ${objectText};`);
394
+ return generateAssignmentExpressionFix(fixer, node, propertyText, objectText, memberRight);
188
395
  },
189
396
  });
397
+ return;
190
398
  }
191
- },
192
- AssignmentExpression(node) {
193
- if (node.operator === '=' &&
194
- node.right.type === utils_1.AST_NODE_TYPES.MemberExpression) {
195
- if (shouldUseDestructuring(node.right, node.left)) {
196
- const sourceCode = context.getSourceCode();
197
- const objectText = sourceCode.getText(node.right.object);
198
- const propertyText = getPropertyText(node.right.property, node.right.computed, sourceCode);
199
- context.report({
200
- node,
201
- messageId: 'preferDestructuring',
202
- fix(fixer) {
203
- // Handle renamed properties
204
- if (options.enforceForRenamedProperties &&
205
- node.left.type === utils_1.AST_NODE_TYPES.Identifier &&
206
- node.right.type === utils_1.AST_NODE_TYPES.MemberExpression &&
207
- !isMatchingPropertyName(node.right.property, node.left.name)) {
208
- return fixer.replaceText(node, `({ ${propertyText}: ${node.left.name} } = ${objectText})`);
209
- }
210
- return fixer.replaceText(node, `({ ${propertyText} } = ${objectText})`);
211
- },
212
- });
213
- }
214
- }
399
+ handleClassPropertyAssignment(node);
215
400
  },
216
401
  };
217
402
  },
@@ -0,0 +1,2 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const preferDocSetterSetAll: TSESLint.RuleModule<"preferSetAll", [], TSESLint.RuleListener>;
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preferDocSetterSetAll = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const ITERATING_METHODS = new Set(['map', 'forEach']);
7
+ const DOC_SETTER_NAMES = new Set([
8
+ 'DocSetter',
9
+ 'DocSetterTransaction',
10
+ ]);
11
+ function unwrapCallee(callee) {
12
+ return callee.type === utils_1.AST_NODE_TYPES.ChainExpression
13
+ ? callee.expression
14
+ : callee;
15
+ }
16
+ function getEntityIdentifier(entity) {
17
+ if (entity.type === utils_1.AST_NODE_TYPES.Identifier) {
18
+ return entity.name;
19
+ }
20
+ return getEntityIdentifier(entity.right);
21
+ }
22
+ function getTypeName(typeNode) {
23
+ if (!typeNode || typeNode.type !== utils_1.AST_NODE_TYPES.TSTypeReference) {
24
+ return null;
25
+ }
26
+ return getEntityIdentifier(typeNode.typeName);
27
+ }
28
+ function isDocSetterName(name) {
29
+ return Boolean(name && DOC_SETTER_NAMES.has(name));
30
+ }
31
+ function getNewExpressionKind(expression) {
32
+ if (expression?.type !== utils_1.AST_NODE_TYPES.NewExpression) {
33
+ return null;
34
+ }
35
+ if (expression.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
36
+ isDocSetterName(expression.callee.name)) {
37
+ return expression.callee.name;
38
+ }
39
+ return null;
40
+ }
41
+ function getTypeAnnotationKind(identifier) {
42
+ const typeName = getTypeName(identifier.typeAnnotation?.typeAnnotation);
43
+ if (isDocSetterName(typeName)) {
44
+ return typeName;
45
+ }
46
+ return null;
47
+ }
48
+ function extractKindFromDefinition(definition) {
49
+ const nameNode = definition.name;
50
+ if (!nameNode || nameNode.type !== utils_1.AST_NODE_TYPES.Identifier)
51
+ return null;
52
+ const parent = nameNode.parent;
53
+ if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
54
+ const ctorKind = getNewExpressionKind(parent.init);
55
+ if (ctorKind)
56
+ return ctorKind;
57
+ }
58
+ const annotationKind = getTypeAnnotationKind(nameNode);
59
+ if (annotationKind)
60
+ return annotationKind;
61
+ return null;
62
+ }
63
+ function resolveDocSetterFromScope(identifier, context) {
64
+ let scope = context.getScope();
65
+ while (scope) {
66
+ const variable = scope.set.get(identifier.name);
67
+ if (variable) {
68
+ for (const def of variable.defs) {
69
+ const kind = extractKindFromDefinition(def);
70
+ if (kind) {
71
+ return { setterName: identifier.name, setterKind: kind };
72
+ }
73
+ }
74
+ }
75
+ scope = scope.upper;
76
+ }
77
+ return null;
78
+ }
79
+ function resolveDocSetterFromClassProperty(propertyName, startNode) {
80
+ let current = startNode;
81
+ while (current) {
82
+ if (current.type === utils_1.AST_NODE_TYPES.ClassBody) {
83
+ for (const element of current.body) {
84
+ if (element.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
85
+ element.key.type === utils_1.AST_NODE_TYPES.Identifier &&
86
+ element.key.name === propertyName) {
87
+ const ctorKind = getNewExpressionKind(element.value);
88
+ if (ctorKind)
89
+ return ctorKind;
90
+ const annotationKind = getTypeName(element.typeAnnotation?.typeAnnotation);
91
+ if (isDocSetterName(annotationKind)) {
92
+ return annotationKind;
93
+ }
94
+ }
95
+ }
96
+ const classNode = current.parent;
97
+ if (classNode &&
98
+ (classNode.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
99
+ classNode.type === utils_1.AST_NODE_TYPES.ClassExpression)) {
100
+ const constructor = classNode.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.MethodDefinition &&
101
+ member.kind === 'constructor');
102
+ if (constructor) {
103
+ for (const param of constructor.value.params) {
104
+ if (param.type === utils_1.AST_NODE_TYPES.TSParameterProperty) {
105
+ const parameter = param.parameter;
106
+ if (parameter.type === utils_1.AST_NODE_TYPES.Identifier &&
107
+ parameter.name === propertyName) {
108
+ const annotationKind = getTypeAnnotationKind(parameter);
109
+ if (annotationKind)
110
+ return annotationKind;
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+ current = current.parent;
119
+ }
120
+ return null;
121
+ }
122
+ function isAncestor(ancestor, node) {
123
+ let current = node;
124
+ while (current) {
125
+ if (current === ancestor)
126
+ return true;
127
+ current = current.parent;
128
+ }
129
+ return false;
130
+ }
131
+ function findIterationContext(node) {
132
+ let current = node.parent;
133
+ while (current) {
134
+ switch (current.type) {
135
+ case utils_1.AST_NODE_TYPES.ForStatement:
136
+ case utils_1.AST_NODE_TYPES.ForInStatement:
137
+ case utils_1.AST_NODE_TYPES.ForOfStatement:
138
+ case utils_1.AST_NODE_TYPES.WhileStatement:
139
+ case utils_1.AST_NODE_TYPES.DoWhileStatement:
140
+ return { kind: 'loop', loopType: current.type };
141
+ case utils_1.AST_NODE_TYPES.CallExpression: {
142
+ const callee = unwrapCallee(current.callee);
143
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
144
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
145
+ ITERATING_METHODS.has(callee.property.name)) {
146
+ const callback = current.arguments[0];
147
+ if (callback &&
148
+ (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
149
+ callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
150
+ isAncestor(callback, node)) {
151
+ return { kind: 'array-callback', methodName: callee.property.name };
152
+ }
153
+ }
154
+ break;
155
+ }
156
+ case utils_1.AST_NODE_TYPES.Program:
157
+ return null;
158
+ default:
159
+ break;
160
+ }
161
+ current = current.parent;
162
+ }
163
+ return null;
164
+ }
165
+ function formatContext(iteration) {
166
+ if (iteration.kind === 'array-callback') {
167
+ return `${iteration.methodName} callback`;
168
+ }
169
+ switch (iteration.loopType) {
170
+ case utils_1.AST_NODE_TYPES.ForOfStatement:
171
+ return 'for...of loop';
172
+ case utils_1.AST_NODE_TYPES.ForInStatement:
173
+ return 'for...in loop';
174
+ case utils_1.AST_NODE_TYPES.ForStatement:
175
+ return 'for loop';
176
+ case utils_1.AST_NODE_TYPES.WhileStatement:
177
+ return 'while loop';
178
+ case utils_1.AST_NODE_TYPES.DoWhileStatement:
179
+ return 'do...while loop';
180
+ default:
181
+ return 'loop';
182
+ }
183
+ }
184
+ function resolveDocSetterInfo(callee, context) {
185
+ if (callee.property.type !== utils_1.AST_NODE_TYPES.Identifier)
186
+ return null;
187
+ const object = callee.object;
188
+ if (object.type === utils_1.AST_NODE_TYPES.Identifier) {
189
+ return resolveDocSetterFromScope(object, context);
190
+ }
191
+ if (object.type === utils_1.AST_NODE_TYPES.MemberExpression &&
192
+ object.object.type === utils_1.AST_NODE_TYPES.ThisExpression &&
193
+ object.property.type === utils_1.AST_NODE_TYPES.Identifier) {
194
+ const setterKind = resolveDocSetterFromClassProperty(object.property.name, callee);
195
+ if (setterKind) {
196
+ return { setterName: object.property.name, setterKind };
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ exports.preferDocSetterSetAll = (0, createRule_1.createRule)({
202
+ name: 'prefer-docsetter-setall',
203
+ meta: {
204
+ type: 'suggestion',
205
+ docs: {
206
+ description: 'Enforce batching DocSetter and DocSetterTransaction writes by using setAll instead of set inside loops or array callbacks.',
207
+ recommended: 'error',
208
+ },
209
+ schema: [],
210
+ messages: {
211
+ preferSetAll: '{{setterName}}.set() runs inside a {{context}}, which sends one Firestore write per iteration. Collect the document payloads and call setAll() once to batch writes, cut round-trips, and keep payload types narrow (build an updates array and mark entries as const when needed).',
212
+ },
213
+ },
214
+ defaultOptions: [],
215
+ create(context) {
216
+ return {
217
+ CallExpression(node) {
218
+ const callee = unwrapCallee(node.callee);
219
+ if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
220
+ callee.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
221
+ callee.property.name !== 'set') {
222
+ return;
223
+ }
224
+ const setterInfo = resolveDocSetterInfo(callee, context);
225
+ if (!setterInfo)
226
+ return;
227
+ const iterationContext = findIterationContext(node);
228
+ if (!iterationContext)
229
+ return;
230
+ const contextLabel = formatContext(iterationContext);
231
+ context.report({
232
+ node: callee.property,
233
+ messageId: 'preferSetAll',
234
+ data: {
235
+ setterName: setterInfo.setterName,
236
+ context: contextLabel,
237
+ },
238
+ });
239
+ },
240
+ };
241
+ },
242
+ });
243
+ //# sourceMappingURL=prefer-docsetter-setall.js.map
@@ -74,8 +74,9 @@ function isBoundToName(fn, name) {
74
74
  if (parent.type === utils_1.AST_NODE_TYPES.Property ||
75
75
  parent.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
76
76
  parent.type === utils_1.AST_NODE_TYPES.PropertyDefinition) {
77
- const key = parent.key;
78
- if (parent.computed) {
77
+ const narrowedParent = parent;
78
+ const key = narrowedParent.key;
79
+ if (narrowedParent.computed) {
79
80
  return key.type === utils_1.AST_NODE_TYPES.Literal && key.value === name;
80
81
  }
81
82
  if (key.type === utils_1.AST_NODE_TYPES.Identifier)
@@ -84,7 +85,7 @@ function isBoundToName(fn, name) {
84
85
  return key.value === name;
85
86
  }
86
87
  if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
87
- return parent.id.type === utils_1.AST_NODE_TYPES.Identifier && parent.id.name === name;
88
+ return (parent.id.type === utils_1.AST_NODE_TYPES.Identifier && parent.id.name === name);
88
89
  }
89
90
  if (parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression) {
90
91
  const left = parent.left;
@@ -20,7 +20,7 @@ exports.preferFragmentComponent = (0, createRule_1.createRule)({
20
20
  },
21
21
  defaultOptions: [],
22
22
  create(context) {
23
- const sourceCode = context.getSourceCode();
23
+ const sourceCode = context.sourceCode;
24
24
  let hasFragmentImport = false;
25
25
  let reactImportNode = null;
26
26
  let defaultReactImportNode = null;
@@ -14,6 +14,7 @@ exports.preferFragmentShorthand = {
14
14
  context.report({
15
15
  node,
16
16
  messageId: 'preferShorthand',
17
+ data: { fragmentName: 'React.Fragment' },
17
18
  fix: (fixer) => [
18
19
  fixer.replaceTextRange(openingElement.range, '<>'),
19
20
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -31,7 +32,7 @@ exports.preferFragmentShorthand = {
31
32
  recommended: 'error',
32
33
  },
33
34
  messages: {
34
- preferShorthand: 'Use <> shorthand for <React.Fragment>, unless a key is required for an iterator',
35
+ preferShorthand: 'Fragment "{{fragmentName}}" is written with the long <React.Fragment> syntax. That form adds React namespace noise and is only necessary when you need a key or other attributes on the fragment. Prefer the <>...</> shorthand to keep JSX concise, and keep the long form only when fragment attributes are required.',
35
36
  },
36
37
  schema: [],
37
38
  fixable: 'code',
@@ -0,0 +1,13 @@
1
+ type OptionShape = {
2
+ stripPrefixes?: string[];
3
+ ignoredMethods?: string[];
4
+ ignoreAsync?: boolean;
5
+ ignoreVoidReturn?: boolean;
6
+ ignoreAbstract?: boolean;
7
+ respectJsDocSideEffects?: boolean;
8
+ minBodyLines?: number;
9
+ };
10
+ type Options = [OptionShape];
11
+ type MessageIds = 'preferGetter' | 'preferGetterSideEffect';
12
+ export declare const preferGetterOverParameterlessMethod: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
13
+ export default preferGetterOverParameterlessMethod;