@blumintinc/eslint-plugin-blumint 1.13.0 → 1.15.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 (199) hide show
  1. package/README.md +169 -139
  2. package/lib/index.js +200 -9
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/avoid-utils-directory.js +0 -4
  5. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  6. package/lib/rules/consistent-callback-naming.js +43 -4
  7. package/lib/rules/dynamic-https-errors.d.ts +1 -1
  8. package/lib/rules/dynamic-https-errors.js +134 -44
  9. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  10. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -235
  11. package/lib/rules/enforce-callback-memo.js +1 -1
  12. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  13. package/lib/rules/enforce-console-error.js +35 -7
  14. package/lib/rules/enforce-date-ttime.d.ts +1 -0
  15. package/lib/rules/enforce-date-ttime.js +156 -0
  16. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  17. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  18. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
  19. package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
  20. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  21. package/lib/rules/enforce-early-destructuring.js +980 -0
  22. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  23. package/lib/rules/enforce-empty-object-check.js +502 -0
  24. package/lib/rules/enforce-exported-function-types.js +1 -1
  25. package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
  26. package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
  27. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  28. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  29. package/lib/rules/enforce-firestore-facade.js +4 -2
  30. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  31. package/lib/rules/enforce-global-constants.js +4 -4
  32. package/lib/rules/enforce-id-capitalization.js +1 -1
  33. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  34. package/lib/rules/enforce-memoize-async.js +71 -21
  35. package/lib/rules/enforce-memoize-getters.js +39 -15
  36. package/lib/rules/enforce-microdiff.js +1 -1
  37. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  38. package/lib/rules/enforce-positive-naming.js +4 -0
  39. package/lib/rules/enforce-props-argument-name.js +44 -18
  40. package/lib/rules/enforce-safe-stringify.js +1 -1
  41. package/lib/rules/enforce-serializable-params.js +3 -3
  42. package/lib/rules/enforce-singular-type-names.js +1 -1
  43. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  44. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  45. package/lib/rules/enforce-storage-context.d.ts +9 -0
  46. package/lib/rules/enforce-storage-context.js +646 -0
  47. package/lib/rules/enforce-timestamp-now.js +23 -7
  48. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  49. package/lib/rules/enforce-transform-memoization.js +416 -0
  50. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  51. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  52. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  53. package/lib/rules/enforce-verb-noun-naming.js +3818 -4644
  54. package/lib/rules/ensure-pointer-events-none.js +28 -2
  55. package/lib/rules/export-if-in-doubt.js +27 -4
  56. package/lib/rules/extract-global-constants.js +40 -13
  57. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  58. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  59. package/lib/rules/flatten-push-calls.d.ts +2 -0
  60. package/lib/rules/flatten-push-calls.js +428 -0
  61. package/lib/rules/global-const-style.js +66 -23
  62. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  63. package/lib/rules/jsdoc-above-field.js +208 -0
  64. package/lib/rules/key-only-outermost-element.js +21 -3
  65. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  66. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  67. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  68. package/lib/rules/memo-compare-deeply-complex-props.js +801 -0
  69. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  70. package/lib/rules/memo-nested-react-components.js +415 -0
  71. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  72. package/lib/rules/no-always-true-false-conditions.js +68 -8
  73. package/lib/rules/no-array-length-in-deps.js +9 -3
  74. package/lib/rules/no-async-foreach.js +215 -12
  75. package/lib/rules/no-circular-references.d.ts +2 -1
  76. package/lib/rules/no-circular-references.js +31 -32
  77. package/lib/rules/no-class-instance-destructuring.js +55 -12
  78. package/lib/rules/no-complex-cloud-params.js +7 -3
  79. package/lib/rules/no-compositing-layer-props.js +2 -2
  80. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  81. package/lib/rules/no-console-error.d.ts +9 -0
  82. package/lib/rules/no-console-error.js +527 -0
  83. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  84. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  85. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  86. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  87. package/lib/rules/no-entire-object-hook-deps.js +52 -7
  88. package/lib/rules/no-excessive-parent-chain.js +4 -1
  89. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  90. package/lib/rules/no-explicit-return-type.js +215 -26
  91. package/lib/rules/no-filter-without-return.js +5 -1
  92. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  93. package/lib/rules/no-firestore-jest-mock.js +126 -8
  94. package/lib/rules/no-firestore-object-arrays.js +67 -12
  95. package/lib/rules/no-handler-suffix.d.ts +12 -0
  96. package/lib/rules/no-handler-suffix.js +305 -0
  97. package/lib/rules/no-hungarian.js +1 -1
  98. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  99. package/lib/rules/no-inline-component-prop.js +465 -0
  100. package/lib/rules/no-jsx-in-hooks.js +6 -1
  101. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  102. package/lib/rules/no-margin-properties.js +6 -6
  103. package/lib/rules/no-memoize-on-static.js +9 -1
  104. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  105. package/lib/rules/no-misused-switch-case.js +22 -1
  106. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  107. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  108. package/lib/rules/no-mock-firebase-admin.js +5 -2
  109. package/lib/rules/no-object-values-on-strings.js +10 -2
  110. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  111. package/lib/rules/no-passthrough-getters.d.ts +2 -2
  112. package/lib/rules/no-passthrough-getters.js +120 -2
  113. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  114. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  115. package/lib/rules/no-redundant-param-types.js +16 -6
  116. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  117. package/lib/rules/no-redundant-this-params.js +508 -0
  118. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  119. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  120. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  121. package/lib/rules/no-restricted-properties-fix.js +11 -10
  122. package/lib/rules/no-separate-loading-state.js +7 -16
  123. package/lib/rules/no-stale-state-across-await.js +1 -1
  124. package/lib/rules/no-type-assertion-returns.js +43 -49
  125. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  126. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  127. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  128. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  129. package/lib/rules/no-unnecessary-destructuring-rename.js +344 -0
  130. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  131. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  132. package/lib/rules/no-unpinned-dependencies.js +36 -5
  133. package/lib/rules/no-unused-props.d.ts +2 -2
  134. package/lib/rules/no-unused-props.js +295 -91
  135. package/lib/rules/no-unused-usestate.js +6 -2
  136. package/lib/rules/no-useless-fragment.js +28 -2
  137. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  138. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  139. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  140. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  141. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  142. package/lib/rules/omit-index-html.d.ts +2 -1
  143. package/lib/rules/omit-index-html.js +62 -7
  144. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  145. package/lib/rules/parallelize-async-operations.js +143 -59
  146. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  147. package/lib/rules/prefer-batch-operations.js +32 -5
  148. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  149. package/lib/rules/prefer-clone-deep.js +3 -3
  150. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  151. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  152. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  153. package/lib/rules/prefer-docsetter-setall.js +243 -0
  154. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  155. package/lib/rules/prefer-fragment-component.js +1 -1
  156. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  157. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  158. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  159. package/lib/rules/prefer-global-router-state-key.js +88 -13
  160. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  161. package/lib/rules/prefer-memoized-props.js +445 -0
  162. package/lib/rules/prefer-next-dynamic.js +60 -69
  163. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +118 -8
  164. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  165. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  166. package/lib/rules/prefer-params-over-parent-id.js +221 -172
  167. package/lib/rules/prefer-settings-object.js +27 -10
  168. package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
  169. package/lib/rules/prefer-type-over-interface.js +7 -2
  170. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  171. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  172. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  173. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
  174. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  175. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  176. package/lib/rules/prevent-children-clobber.js +540 -0
  177. package/lib/rules/react-memoize-literals.d.ts +4 -0
  178. package/lib/rules/react-memoize-literals.js +614 -0
  179. package/lib/rules/react-usememo-should-be-component.js +2 -2
  180. package/lib/rules/require-hooks-default-params.js +17 -16
  181. package/lib/rules/require-https-error-cause.d.ts +4 -0
  182. package/lib/rules/require-https-error-cause.js +155 -0
  183. package/lib/rules/require-https-error.js +43 -21
  184. package/lib/rules/require-image-optimized.js +1 -1
  185. package/lib/rules/require-memo.js +18 -10
  186. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  187. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  188. package/lib/rules/require-usememo-object-literals.js +2 -3
  189. package/lib/rules/test-file-location-enforcement.js +1 -1
  190. package/lib/rules/use-latest-callback.js +6 -5
  191. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  192. package/lib/rules/vertically-group-related-functions.js +480 -0
  193. package/lib/utils/ASTHelpers.d.ts +47 -2
  194. package/lib/utils/ASTHelpers.js +370 -112
  195. package/lib/utils/getMethodName.d.ts +27 -0
  196. package/lib/utils/getMethodName.js +35 -0
  197. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  198. package/lib/utils/tsTypeClassifier.js +149 -0
  199. package/package.json +3 -1
@@ -224,6 +224,27 @@ function findLoopNode(node) {
224
224
  }
225
225
  return undefined;
226
226
  }
227
+ function describeLoopContext(loopInfo) {
228
+ if (loopInfo.isPromiseAll)
229
+ return 'Promise.all()';
230
+ if (loopInfo.isArrayMethod) {
231
+ return `${loopInfo.isArrayMethod}() callback`;
232
+ }
233
+ switch (loopInfo.node.type) {
234
+ case utils_1.AST_NODE_TYPES.ForStatement:
235
+ return 'for loop';
236
+ case utils_1.AST_NODE_TYPES.ForInStatement:
237
+ return 'for...in loop';
238
+ case utils_1.AST_NODE_TYPES.ForOfStatement:
239
+ return 'for...of loop';
240
+ case utils_1.AST_NODE_TYPES.WhileStatement:
241
+ return 'while loop';
242
+ case utils_1.AST_NODE_TYPES.DoWhileStatement:
243
+ return 'do...while loop';
244
+ default:
245
+ return 'this control flow';
246
+ }
247
+ }
227
248
  function isFirestoreSetterInstance(node) {
228
249
  // Check if it's a DocSetter instance
229
250
  if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
@@ -301,8 +322,7 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
301
322
  fixable: 'code',
302
323
  schema: [],
303
324
  messages: {
304
- preferSetAll: 'Use setAll() instead of multiple set() calls for better performance',
305
- preferOverwriteAll: 'Use overwriteAll() instead of multiple overwrite() calls for better performance',
325
+ preferBatch: 'DocSetter.{{setterMethod}} is invoked repeatedly inside {{contextDescription}}, which issues separate Firestore writes per document and can leave partial updates when later calls fail. Batch the documents with DocSetter.{{batchMethod}} so the writes stay grouped and latency stays predictable.',
306
326
  },
307
327
  },
308
328
  defaultOptions: [],
@@ -321,6 +341,13 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
321
341
  const loopInfo = findLoopNode(node);
322
342
  if (!loopInfo)
323
343
  return;
344
+ const batchMethod = methodName === 'set' ? 'setAll()' : 'overwriteAll()';
345
+ const messageData = {
346
+ setterMethod: `${methodName}()`,
347
+ batchMethod,
348
+ contextDescription: describeLoopContext(loopInfo),
349
+ };
350
+ const messageId = 'preferBatch';
324
351
  // Get or create the setter calls map for this loop
325
352
  let setterCalls = loopSetterCalls.get(loopInfo.node);
326
353
  if (!setterCalls) {
@@ -348,10 +375,10 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
348
375
  existing.count === 2 &&
349
376
  !reportedLoops.has(loopInfo.node)) {
350
377
  reportedLoops.add(loopInfo.node);
351
- const messageId = methodName === 'set' ? 'preferSetAll' : 'preferOverwriteAll';
352
378
  context.report({
353
379
  node: existing.firstNode,
354
380
  messageId,
381
+ data: messageData,
355
382
  fix: () => null, // We can't provide a fix because we don't know the array structure
356
383
  });
357
384
  }
@@ -371,10 +398,10 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
371
398
  // For traditional loops, report on the first occurrence since we know it's in a loop
372
399
  if (!reportedLoops.has(loopInfo.node)) {
373
400
  reportedLoops.add(loopInfo.node);
374
- const messageId = methodName === 'set' ? 'preferSetAll' : 'preferOverwriteAll';
375
401
  context.report({
376
402
  node,
377
403
  messageId,
404
+ data: messageData,
378
405
  fix: () => null, // We can't provide a fix because we don't know the array structure
379
406
  });
380
407
  }
@@ -383,10 +410,10 @@ exports.preferBatchOperations = (0, createRule_1.createRule)({
383
410
  else if (loopInfo.isArrayMethod) {
384
411
  if (!reportedLoops.has(loopInfo.node)) {
385
412
  reportedLoops.add(loopInfo.node);
386
- const messageId = methodName === 'set' ? 'preferSetAll' : 'preferOverwriteAll';
387
413
  context.report({
388
414
  node,
389
415
  messageId,
416
+ data: messageData,
390
417
  fix: () => null, // We can't provide a fix because we don't know the array structure
391
418
  });
392
419
  }
@@ -65,17 +65,22 @@ exports.preferBlockCommentsForDeclarations = (0, createRule_1.createRule)({
65
65
  if (isInsideFunctionBody(node)) {
66
66
  return;
67
67
  }
68
- const sourceCode = context.getSourceCode();
68
+ const sourceCode = context.sourceCode;
69
69
  const comments = sourceCode.getCommentsBefore(node);
70
70
  // Find the closest comment to the node
71
71
  const lastComment = comments[comments.length - 1];
72
72
  if (lastComment && isLineCommentBeforeDeclaration(lastComment, node)) {
73
+ const commentText = lastComment.value.trim();
74
+ const commentLabel = commentText || 'declaration comment';
73
75
  context.report({
74
76
  loc: lastComment.loc,
75
77
  messageId: 'preferBlockComment',
78
+ data: { commentText: commentLabel },
76
79
  fix: (fixer) => {
77
- const commentText = lastComment.value.trim();
78
- return fixer.replaceText(lastComment, `/** ${commentText} */`);
80
+ if (commentLabel.includes('*/')) {
81
+ return null;
82
+ }
83
+ return fixer.replaceText(lastComment, `/** ${commentLabel} */`);
79
84
  },
80
85
  });
81
86
  }
@@ -129,7 +134,7 @@ exports.preferBlockCommentsForDeclarations = (0, createRule_1.createRule)({
129
134
  fixable: 'code',
130
135
  schema: [],
131
136
  messages: {
132
- preferBlockComment: 'Use block comments (/** */) instead of line comments (//) for declarations to improve IDE support.',
137
+ preferBlockComment: 'Line comment "{{commentText}}" sits on a declaration, and IDE hovers and TypeScript docs ignore it as documentation. Rewrite it as a block comment (/** ... */) so the text stays attached to the declaration and tooling surfaces it.',
133
138
  },
134
139
  },
135
140
  defaultOptions: [],
@@ -14,7 +14,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
14
14
  fixable: 'code',
15
15
  schema: [],
16
16
  messages: {
17
- preferCloneDeep: 'Use cloneDeep from functions/src/util/cloneDeep.ts instead of nested spread operators for deep object copying',
17
+ preferCloneDeep: 'Nested spread copies only clone one level, so inner objects still point at the original and later mutations leak back. Use cloneDeep from functions/src/util/cloneDeep.ts and pass overrides as the second argument so the base object is deeply cloned before applying updates.',
18
18
  },
19
19
  },
20
20
  defaultOptions: [],
@@ -74,7 +74,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
74
74
  !hasSymbol);
75
75
  }
76
76
  function generateCloneDeepFix(node) {
77
- const sourceCode = context.getSourceCode();
77
+ const sourceCode = context.sourceCode;
78
78
  // Find the base object (first spread element)
79
79
  let baseObj = null;
80
80
  // Extract the base object from the first spread element
@@ -248,7 +248,7 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
248
248
  }
249
249
  // Helper function to extract nested overrides without spread elements
250
250
  function extractNestedOverrides(node) {
251
- const sourceCode = context.getSourceCode();
251
+ const sourceCode = context.sourceCode;
252
252
  const overrides = [];
253
253
  for (const prop of node.properties) {
254
254
  if (prop.type === utils_1.AST_NODE_TYPES.Property) {
@@ -1,8 +1,9 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
1
2
  type Options = [
2
3
  {
3
4
  object?: boolean;
4
5
  enforceForRenamedProperties?: boolean;
5
6
  }
6
7
  ];
7
- export declare const preferDestructuringNoClass: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferDestructuring", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
8
+ export declare const preferDestructuringNoClass: TSESLint.RuleModule<"preferDestructuring", Options, TSESLint.RuleListener>;
8
9
  export {};
@@ -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>;