@blumintinc/eslint-plugin-blumint 1.14.0 → 1.16.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 (68) hide show
  1. package/README.md +36 -14
  2. package/lib/index.js +20 -12
  3. package/lib/rules/avoid-utils-directory.js +0 -4
  4. package/lib/rules/consistent-callback-naming.js +68 -3
  5. package/lib/rules/dynamic-https-errors.d.ts +1 -1
  6. package/lib/rules/dynamic-https-errors.js +119 -49
  7. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +1 -0
  8. package/lib/rules/enforce-boolean-naming-prefixes.js +86 -331
  9. package/lib/rules/enforce-date-ttime.d.ts +1 -0
  10. package/lib/rules/enforce-date-ttime.js +156 -0
  11. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
  12. package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
  13. package/lib/rules/enforce-dynamic-imports.d.ts +2 -1
  14. package/lib/rules/enforce-dynamic-imports.js +42 -21
  15. package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
  16. package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
  17. package/lib/rules/enforce-global-constants.js +3 -3
  18. package/lib/rules/enforce-id-capitalization.js +1 -1
  19. package/lib/rules/enforce-memoize-async.js +66 -15
  20. package/lib/rules/enforce-mui-rounded-icons.js +42 -1
  21. package/lib/rules/enforce-props-argument-name.js +42 -16
  22. package/lib/rules/enforce-stable-hash-spread-props.js +3 -3
  23. package/lib/rules/enforce-transform-memoization.js +1 -1
  24. package/lib/rules/enforce-verb-noun-naming.js +3817 -4641
  25. package/lib/rules/global-const-style.js +25 -4
  26. package/lib/rules/logical-top-to-bottom-grouping.js +80 -2
  27. package/lib/rules/memo-compare-deeply-complex-props.js +183 -6
  28. package/lib/rules/memo-nested-react-components.js +243 -103
  29. package/lib/rules/no-array-length-in-deps.js +74 -3
  30. package/lib/rules/no-async-foreach.js +7 -2
  31. package/lib/rules/no-circular-references.d.ts +2 -1
  32. package/lib/rules/no-circular-references.js +150 -489
  33. package/lib/rules/no-compositing-layer-props.js +31 -0
  34. package/lib/rules/no-console-error.js +12 -10
  35. package/lib/rules/no-empty-dependency-use-callbacks.js +1 -1
  36. package/lib/rules/no-entire-object-hook-deps.js +147 -65
  37. package/lib/rules/no-excessive-parent-chain.js +3 -0
  38. package/lib/rules/no-explicit-return-type.js +6 -0
  39. package/lib/rules/no-hungarian.js +119 -24
  40. package/lib/rules/no-inline-component-prop.js +16 -7
  41. package/lib/rules/no-margin-properties.js +7 -38
  42. package/lib/rules/no-passthrough-getters.d.ts +2 -2
  43. package/lib/rules/no-passthrough-getters.js +83 -1
  44. package/lib/rules/no-redundant-this-params.js +50 -1
  45. package/lib/rules/no-unmemoized-memo-without-props.js +1 -1
  46. package/lib/rules/no-unnecessary-destructuring-rename.js +2 -5
  47. package/lib/rules/no-unnecessary-verb-suffix.js +79 -0
  48. package/lib/rules/no-unused-props.js +215 -37
  49. package/lib/rules/no-useless-fragment.js +10 -2
  50. package/lib/rules/parallelize-async-operations.js +117 -54
  51. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +109 -4
  52. package/lib/rules/prefer-params-over-parent-id.js +1 -1
  53. package/lib/rules/prefer-settings-object.js +27 -10
  54. package/lib/rules/prefer-type-alias-over-typeof-constant.js +75 -4
  55. package/lib/rules/prefer-use-deep-compare-memo.js +8 -14
  56. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
  57. package/lib/rules/prevent-children-clobber.js +9 -5
  58. package/lib/rules/react-memoize-literals.js +218 -13
  59. package/lib/rules/require-https-error-cause.js +30 -11
  60. package/lib/rules/require-memo.js +17 -9
  61. package/lib/rules/require-migration-script-metadata.d.ts +9 -0
  62. package/lib/rules/require-migration-script-metadata.js +206 -0
  63. package/lib/rules/warn-https-error-message-user-friendly.d.ts +1 -0
  64. package/lib/rules/warn-https-error-message-user-friendly.js +239 -0
  65. package/lib/utils/ASTHelpers.d.ts +49 -1
  66. package/lib/utils/ASTHelpers.js +394 -112
  67. package/package.json +7 -6
  68. package/release-manifest.json +166 -0
@@ -32,6 +32,21 @@ const COMPOSITING_VALUES = new Set([
32
32
  'translateZ',
33
33
  'transparent',
34
34
  ]);
35
+ // CSS reset/identity values that explicitly DON'T promote a layer for a given
36
+ // property. These are the opt-out counterparts to COMPOSITING_VALUES: `none`
37
+ // removes the effect, `auto`/global keywords disable the hint, and the default
38
+ // keyword leaves the element un-promoted. Keyed by normalized property name so
39
+ // the allowlist stays property-specific (e.g. `none` clears `transform` but is
40
+ // not a valid no-op for `opacity`).
41
+ const NON_COMPOSITING_VALUES = {
42
+ filter: new Set(['none']),
43
+ 'backdrop-filter': new Set(['none']),
44
+ transform: new Set(['none']),
45
+ contain: new Set(['none']),
46
+ perspective: new Set(['none']),
47
+ 'will-change': new Set(['auto', 'unset', 'initial', 'inherit', 'revert']),
48
+ 'backface-visibility': new Set(['visible']),
49
+ };
35
50
  exports.noCompositingLayerProps = (0, createRule_1.createRule)({
36
51
  name: 'no-compositing-layer-props',
37
52
  meta: {
@@ -54,6 +69,14 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
54
69
  value.includes('scale3d') ||
55
70
  value.includes('translateZ'));
56
71
  }
72
+ // Strip `!important` and normalize casing so reset/identity values written
73
+ // as e.g. `none !important` are still recognized as non-promoting.
74
+ function normalizePropertyValue(value) {
75
+ return value
76
+ .replace(/\s*!important\s*$/i, '')
77
+ .trim()
78
+ .toLowerCase();
79
+ }
57
80
  function checkProperty(propertyName, propertyValue) {
58
81
  const normalizedName = normalizePropertyName(propertyName);
59
82
  if (COMPOSITING_PROPERTIES.has(normalizedName)) {
@@ -66,6 +89,14 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
66
89
  return false;
67
90
  return numValue > 0 && numValue < 1;
68
91
  }
92
+ // CSS reset/identity values can't create a compositing layer, so don't
93
+ // flag them (mirrors the opacity value-awareness for the other props).
94
+ const allowedValues = NON_COMPOSITING_VALUES[normalizedName];
95
+ if (allowedValues &&
96
+ propertyValue &&
97
+ allowedValues.has(normalizePropertyValue(propertyValue))) {
98
+ return false;
99
+ }
69
100
  return true;
70
101
  }
71
102
  if (propertyValue && checkPropertyValue(propertyValue)) {
@@ -4,6 +4,7 @@ exports.noConsoleError = void 0;
4
4
  const minimatch_1 = require("minimatch");
5
5
  const utils_1 = require("@typescript-eslint/utils");
6
6
  const createRule_1 = require("../utils/createRule");
7
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
7
8
  const defaultIgnorePatterns = [
8
9
  '**/__tests__/**',
9
10
  '**/__mocks__/**',
@@ -69,19 +70,20 @@ const getResolvedVariable = (scope, identifier) => {
69
70
  return findVariable(scope, identifier.name);
70
71
  };
71
72
  const getScopeForNode = (context, node) => {
72
- const sourceCode = context.getSourceCode();
73
- const sourceCodeWithScope = sourceCode;
74
- if (typeof sourceCodeWithScope.getScope === 'function') {
75
- return sourceCodeWithScope.getScope(node) ?? context.getScope();
73
+ try {
74
+ const sourceCode = context.getSourceCode();
75
+ const sourceCodeWithScope = sourceCode;
76
+ if (typeof sourceCodeWithScope.getScope === 'function') {
77
+ return sourceCodeWithScope.getScope(node) ?? context.getScope();
78
+ }
79
+ return context.getScope();
80
+ }
81
+ catch {
82
+ return context.getScope();
76
83
  }
77
- return context.getScope();
78
84
  };
79
85
  const getDeclaredVariablesForNode = (context, node) => {
80
- const sourceCodeWithDeclaredVariables = context.getSourceCode();
81
- if (typeof sourceCodeWithDeclaredVariables.getDeclaredVariables === 'function') {
82
- return sourceCodeWithDeclaredVariables.getDeclaredVariables(node);
83
- }
84
- return context.getDeclaredVariables(node);
86
+ return ASTHelpers_1.ASTHelpers.getDeclaredVariables(context, node);
85
87
  };
86
88
  const isErrorKey = (key, computed) => {
87
89
  if (!computed && key.type === utils_1.AST_NODE_TYPES.Identifier) {
@@ -515,7 +515,7 @@ exports.noEmptyDependencyUseCallbacks = (0, createRule_1.createRule)({
515
515
  const callback = getCallbackArg(callExpression);
516
516
  if (!callback)
517
517
  return;
518
- if (ASTHelpers_1.ASTHelpers.returnsJSX(callback.body))
518
+ if (ASTHelpers_1.ASTHelpers.returnsJSX(callback.body, context))
519
519
  return;
520
520
  const extraTypeRoots = [];
521
521
  if (callExpression.parent &&
@@ -55,12 +55,23 @@ function isArrayOrPrimitive(checker, esTreeNode, nodeMap) {
55
55
  return false;
56
56
  }
57
57
  }
58
+ function unwrapExpression(expr) {
59
+ let current = expr;
60
+ while (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
61
+ current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
62
+ current.type === utils_1.AST_NODE_TYPES.ChainExpression ||
63
+ current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
64
+ current = current.expression;
65
+ }
66
+ return current;
67
+ }
58
68
  function getObjectUsagesInHook(hookBody, objectName,
59
69
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
70
  context) {
61
71
  const usages = new Map(); // Track usage and its position
62
72
  const visited = new Set();
63
73
  let needsEntireObject = false;
74
+ let isUsed = false;
64
75
  // Built-in array methods that indicate usage of the entire array
65
76
  const ARRAY_METHODS = new Set([
66
77
  'map',
@@ -125,12 +136,12 @@ context) {
125
136
  // For computed properties with variables (like user[key]), we need the entire object
126
137
  if (memberExpr.property.type === utils_1.AST_NODE_TYPES.Identifier) {
127
138
  // Check if this is accessing our target object
128
- let current = memberExpr.object;
129
- while (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
130
- current = current.object;
139
+ let currentBase = unwrapExpression(memberExpr.object);
140
+ while (currentBase.type === utils_1.AST_NODE_TYPES.MemberExpression) {
141
+ currentBase = unwrapExpression(currentBase.object);
131
142
  }
132
- if (current.type === utils_1.AST_NODE_TYPES.Identifier &&
133
- current.name === objectName) {
143
+ if (currentBase.type === utils_1.AST_NODE_TYPES.Identifier &&
144
+ currentBase.name === objectName) {
134
145
  // This is a computed property access on our target object, so we need the entire object
135
146
  needsEntireObject = true;
136
147
  }
@@ -172,29 +183,29 @@ context) {
172
183
  (ARRAY_METHODS.has(memberExpr.property.name) ||
173
184
  STRING_METHODS.has(memberExpr.property.name))) {
174
185
  // Check if this is accessing our target object or a property of it
175
- let current = memberExpr.object;
186
+ let currentBase = unwrapExpression(memberExpr.object);
176
187
  let pathParts = [];
177
- let hasOptionalChaining = false;
188
+ let hasOptionalChainingInMethod = false;
178
189
  // Build the path to the array/string being accessed
179
- while (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
180
- const currentMember = current;
190
+ while (currentBase.type === utils_1.AST_NODE_TYPES.MemberExpression) {
191
+ const currentMember = currentBase;
181
192
  if (currentMember.property.type === utils_1.AST_NODE_TYPES.Identifier) {
182
193
  pathParts.unshift(currentMember.property.name);
183
194
  }
184
195
  if (currentMember.optional) {
185
- hasOptionalChaining = true;
196
+ hasOptionalChainingInMethod = true;
186
197
  }
187
- current = currentMember.object;
198
+ currentBase = unwrapExpression(currentMember.object);
188
199
  }
189
- if (current.type === utils_1.AST_NODE_TYPES.Identifier &&
190
- current.name === objectName) {
200
+ if (currentBase.type === utils_1.AST_NODE_TYPES.Identifier &&
201
+ currentBase.name === objectName) {
191
202
  if (pathParts.length === 0) {
192
203
  // Direct method call on the object (e.g., userData.map(...))
193
204
  needsEntireObject = true;
194
205
  }
195
206
  else {
196
207
  // Method call on a property (e.g., userData.items.map(...) or userData?.items?.map(...))
197
- let path = objectName + (hasOptionalChaining ? '?' : '');
208
+ let path = objectName + (hasOptionalChainingInMethod ? '?' : '');
198
209
  path += '.' + pathParts.join('.');
199
210
  usages.set(path, memberExpr.range?.[0] || 0);
200
211
  }
@@ -206,11 +217,11 @@ context) {
206
217
  if (memberExpr.optional) {
207
218
  hasOptionalChaining = true;
208
219
  }
209
- current = memberExpr.object;
220
+ current = unwrapExpression(memberExpr.object);
210
221
  }
211
222
  // Check if we reached the target identifier
212
- if (current.type === utils_1.AST_NODE_TYPES.Identifier &&
213
- current.name === objectName) {
223
+ const base = unwrapExpression(current);
224
+ if (base.type === utils_1.AST_NODE_TYPES.Identifier && base.name === objectName) {
214
225
  // Build the path with optional chaining
215
226
  let path = objectName + (hasOptionalChaining ? '?' : '');
216
227
  // Add each part with proper formatting (dot notation or bracket notation)
@@ -230,15 +241,74 @@ context) {
230
241
  if (!node || visited.has(node))
231
242
  return;
232
243
  visited.add(node);
244
+ if (node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === objectName) {
245
+ // Skip TS type assertions, ChainExpression and TSNonNullExpression wrappers
246
+ // (used around the Identifier) so we can attribute the Identifier's usage
247
+ // to its actual parent context (e.g., call/member/assignment) and avoid
248
+ // misclassifying the dependency when determining if the whole object is referenced.
249
+ let wrapperNode = node;
250
+ let effectiveParent = node.parent;
251
+ while (effectiveParent &&
252
+ (effectiveParent.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
253
+ effectiveParent.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
254
+ effectiveParent.type === utils_1.AST_NODE_TYPES.ChainExpression ||
255
+ effectiveParent.type === utils_1.AST_NODE_TYPES.TSNonNullExpression)) {
256
+ wrapperNode = effectiveParent;
257
+ effectiveParent = effectiveParent.parent;
258
+ }
259
+ // Exclude: property name in `other.objectName` (not our target object)
260
+ const isMemberProperty = effectiveParent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
261
+ effectiveParent.property === wrapperNode &&
262
+ !effectiveParent.computed;
263
+ // Exclude: object in `objectName.prop` (handled by MemberExpression visitor for field tracking)
264
+ const isMemberObject = effectiveParent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
265
+ effectiveParent.object === wrapperNode;
266
+ // Exclude: key in `{ objectName: value }` (not usage, just a label)
267
+ // Include: shorthand `{ objectName }` (actual usage)
268
+ const isPropertyKey = effectiveParent?.type === utils_1.AST_NODE_TYPES.Property &&
269
+ effectiveParent.key === wrapperNode &&
270
+ !effectiveParent.computed &&
271
+ !effectiveParent.shorthand;
272
+ if (!isMemberProperty && !isMemberObject && !isPropertyKey) {
273
+ isUsed = true;
274
+ // Patterns that require the entire object (cannot refactor to specific fields)
275
+ const isTypeAUsage = effectiveParent?.type === utils_1.AST_NODE_TYPES.ReturnStatement ||
276
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
277
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.BinaryExpression ||
278
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.LogicalExpression ||
279
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.ConditionalExpression ||
280
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.UnaryExpression ||
281
+ (effectiveParent?.type === utils_1.AST_NODE_TYPES.Property &&
282
+ (effectiveParent.value === wrapperNode ||
283
+ effectiveParent.shorthand ||
284
+ (effectiveParent.key === wrapperNode &&
285
+ effectiveParent.computed))) ||
286
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.TemplateLiteral ||
287
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
288
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression ||
289
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer ||
290
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute ||
291
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.SpreadElement ||
292
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.ForInStatement ||
293
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
294
+ effectiveParent?.type === utils_1.AST_NODE_TYPES.CallExpression;
295
+ if (isTypeAUsage) {
296
+ needsEntireObject = true;
297
+ }
298
+ }
299
+ }
233
300
  if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
234
301
  // Check if the object is being called as a function
235
- if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
236
- node.callee.name === objectName) {
302
+ const callee = unwrapExpression(node.callee);
303
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
304
+ callee.name === objectName) {
237
305
  needsEntireObject = true;
238
306
  }
239
307
  // Check if the object is directly passed as an argument
240
308
  node.arguments.forEach((arg) => {
241
- if (arg.type === utils_1.AST_NODE_TYPES.Identifier && arg.name === objectName) {
309
+ const unwrappedArg = unwrapExpression(arg);
310
+ if (unwrappedArg.type === utils_1.AST_NODE_TYPES.Identifier &&
311
+ unwrappedArg.name === objectName) {
242
312
  needsEntireObject = true;
243
313
  }
244
314
  });
@@ -248,18 +318,21 @@ context) {
248
318
  // If we find a JSX element, check its attributes for spread operator
249
319
  if (node.type === utils_1.AST_NODE_TYPES.JSXElement) {
250
320
  node.openingElement.attributes.forEach((attr) => {
251
- if (attr.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute &&
252
- attr.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
253
- attr.argument.name === objectName) {
254
- needsEntireObject = true;
321
+ if (attr.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute) {
322
+ const argument = unwrapExpression(attr.argument);
323
+ if (argument.type === utils_1.AST_NODE_TYPES.Identifier &&
324
+ argument.name === objectName) {
325
+ needsEntireObject = true;
326
+ }
255
327
  }
256
328
  });
257
329
  }
258
330
  }
259
331
  else if (node.type === utils_1.AST_NODE_TYPES.SpreadElement) {
260
332
  // If we find a spread operator with our target object, consider it as accessing all properties
261
- if (node.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
262
- node.argument.name === objectName) {
333
+ const argument = unwrapExpression(node.argument);
334
+ if (argument.type === utils_1.AST_NODE_TYPES.Identifier &&
335
+ argument.name === objectName) {
263
336
  needsEntireObject = true;
264
337
  return;
265
338
  }
@@ -267,43 +340,51 @@ context) {
267
340
  else if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
268
341
  // Check if this is accessing a property of our target object
269
342
  const memberExpr = node;
270
- // Only process if this is the outermost member expression in a chain
271
- // (i.e., its parent is not also a member expression)
272
- const parent = memberExpr.parent;
273
- if (parent && parent.type === utils_1.AST_NODE_TYPES.MemberExpression) {
274
- // This is an intermediate member expression, skip it
275
- return;
343
+ // Skip TS type assertions, ChainExpression and TSNonNullExpression wrappers
344
+ // so we can attribute the MemberExpression's usage to its actual parent
345
+ // context and avoid misclassifying the dependency.
346
+ // We only process if this is the outermost member expression in a chain.
347
+ let effectiveParent = memberExpr.parent;
348
+ while (effectiveParent &&
349
+ (effectiveParent.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
350
+ effectiveParent.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
351
+ effectiveParent.type === utils_1.AST_NODE_TYPES.ChainExpression ||
352
+ effectiveParent.type === utils_1.AST_NODE_TYPES.TSNonNullExpression)) {
353
+ effectiveParent = effectiveParent.parent;
276
354
  }
277
- // Check if this member expression involves our target object
278
- let current = memberExpr;
279
- let foundTargetObject = false;
280
- let hasDynamicComputed = false;
281
- // Walk up the member expression chain to see if it involves our target object
282
- while (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
283
- const currentMember = current;
284
- // Check if this level uses dynamic computed property access
285
- if (currentMember.computed &&
286
- currentMember.property.type === utils_1.AST_NODE_TYPES.Identifier) {
287
- hasDynamicComputed = true;
355
+ const isIntermediate = effectiveParent && effectiveParent.type === utils_1.AST_NODE_TYPES.MemberExpression;
356
+ if (!isIntermediate) {
357
+ // Check if this member expression involves our target object
358
+ let current = memberExpr;
359
+ let foundTargetObject = false;
360
+ let hasDynamicComputed = false;
361
+ // Walk up the member expression chain to see if it involves our target object
362
+ while (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
363
+ const currentMember = current;
364
+ // Check if this level uses dynamic computed property access
365
+ if (currentMember.computed &&
366
+ currentMember.property.type === utils_1.AST_NODE_TYPES.Identifier) {
367
+ hasDynamicComputed = true;
368
+ }
369
+ current = unwrapExpression(currentMember.object);
288
370
  }
289
- current = currentMember.object;
290
- }
291
- // Check if we reached our target object
292
- if (current.type === utils_1.AST_NODE_TYPES.Identifier &&
293
- current.name === objectName) {
294
- foundTargetObject = true;
295
- }
296
- if (foundTargetObject) {
297
- if (hasDynamicComputed) {
298
- // Dynamic computed property access means we need the entire object
299
- needsEntireObject = true;
300
- return;
371
+ // Check if we reached our target object
372
+ const base = unwrapExpression(current);
373
+ if (base.type === utils_1.AST_NODE_TYPES.Identifier &&
374
+ base.name === objectName) {
375
+ foundTargetObject = true;
301
376
  }
302
- else {
303
- // Static property access - add to usages
304
- const path = buildAccessPath(memberExpr);
305
- if (path) {
306
- usages.set(path, memberExpr.range?.[0] || 0);
377
+ if (foundTargetObject) {
378
+ if (hasDynamicComputed) {
379
+ // Dynamic computed property access means we need the entire object
380
+ needsEntireObject = true;
381
+ }
382
+ else {
383
+ // Static property access - add to usages
384
+ const path = buildAccessPath(memberExpr);
385
+ if (path) {
386
+ usages.set(path, memberExpr.range?.[0] || 0);
387
+ }
307
388
  }
308
389
  }
309
390
  }
@@ -431,7 +512,7 @@ context) {
431
512
  return posA - posB;
432
513
  });
433
514
  const filteredUsages = new Set(sortedPaths);
434
- const notUsed = !needsEntireObject && filteredUsages.size === 0;
515
+ const notUsed = !needsEntireObject && !isUsed && filteredUsages.size === 0;
435
516
  return {
436
517
  usages: filteredUsages,
437
518
  needsEntireObject,
@@ -486,16 +567,17 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
486
567
  }
487
568
  // Check each dependency in the array
488
569
  depsArg.elements.forEach((element) => {
489
- if (!element)
570
+ const unwrappedElement = element ? unwrapExpression(element) : null;
571
+ if (!unwrappedElement)
490
572
  return; // Skip null elements (holes in the array)
491
- if (element.type === utils_1.AST_NODE_TYPES.Identifier) {
492
- const objectName = element.name;
573
+ if (unwrappedElement.type === utils_1.AST_NODE_TYPES.Identifier) {
574
+ const objectName = unwrappedElement.name;
493
575
  // Skip type checking if we don't have TypeScript services
494
576
  if (hasFullTypeChecking && parserServices) {
495
577
  const checker = parserServices.program.getTypeChecker();
496
578
  const nodeMap = parserServices.esTreeNodeToTSNodeMap;
497
579
  // Skip if the dependency is an array or primitive type
498
- if (isArrayOrPrimitive(checker, element, nodeMap)) {
580
+ if (isArrayOrPrimitive(checker, unwrappedElement, nodeMap)) {
499
581
  return;
500
582
  }
501
583
  }
@@ -260,6 +260,9 @@ exports.noExcessiveParentChain = (0, createRule_1.createRule)({
260
260
  suggest: [
261
261
  {
262
262
  messageId: 'excessiveParentChain',
263
+ data: {
264
+ count: parentCount,
265
+ },
263
266
  fix(fixer) {
264
267
  return fixer.replaceText(node, 'event.params');
265
268
  },
@@ -225,6 +225,12 @@ function isTypeGuardFunction(node) {
225
225
  // Check for type predicates (is keyword)
226
226
  if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypePredicate)
227
227
  return true;
228
+ // `never` is never inferred: TypeScript infers `void` for a function whose
229
+ // every path throws, so an explicit `: never` always carries more information
230
+ // than inference (callers rely on it for control-flow narrowing and
231
+ // exhaustiveness). Removing it would silently widen the type to `void`.
232
+ if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSNeverKeyword)
233
+ return true;
228
234
  // Check for assertion functions (asserts keyword)
229
235
  if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
230
236
  const typeName = typeAnnotation.typeName;