@blumintinc/eslint-plugin-blumint 1.14.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 (49) hide show
  1. package/README.md +10 -11
  2. package/lib/index.js +14 -12
  3. package/lib/rules/avoid-utils-directory.js +0 -4
  4. package/lib/rules/consistent-callback-naming.js +42 -3
  5. package/lib/rules/dynamic-https-errors.d.ts +1 -1
  6. package/lib/rules/dynamic-https-errors.js +132 -41
  7. package/lib/rules/enforce-boolean-naming-prefixes.js +0 -212
  8. package/lib/rules/enforce-date-ttime.d.ts +1 -0
  9. package/lib/rules/enforce-date-ttime.js +156 -0
  10. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
  11. package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
  12. package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
  13. package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
  14. package/lib/rules/enforce-global-constants.js +3 -3
  15. package/lib/rules/enforce-id-capitalization.js +1 -1
  16. package/lib/rules/enforce-memoize-async.js +69 -15
  17. package/lib/rules/enforce-props-argument-name.js +42 -16
  18. package/lib/rules/enforce-stable-hash-spread-props.js +3 -3
  19. package/lib/rules/enforce-transform-memoization.js +1 -1
  20. package/lib/rules/enforce-verb-noun-naming.js +3814 -4641
  21. package/lib/rules/global-const-style.js +16 -4
  22. package/lib/rules/memo-compare-deeply-complex-props.js +16 -3
  23. package/lib/rules/memo-nested-react-components.js +108 -103
  24. package/lib/rules/no-async-foreach.js +7 -2
  25. package/lib/rules/no-circular-references.d.ts +2 -1
  26. package/lib/rules/no-circular-references.js +13 -15
  27. package/lib/rules/no-console-error.js +12 -10
  28. package/lib/rules/no-empty-dependency-use-callbacks.js +1 -1
  29. package/lib/rules/no-entire-object-hook-deps.js +48 -1
  30. package/lib/rules/no-excessive-parent-chain.js +3 -0
  31. package/lib/rules/no-inline-component-prop.js +16 -7
  32. package/lib/rules/no-passthrough-getters.d.ts +2 -2
  33. package/lib/rules/no-passthrough-getters.js +83 -1
  34. package/lib/rules/no-redundant-this-params.js +50 -1
  35. package/lib/rules/no-unmemoized-memo-without-props.js +1 -1
  36. package/lib/rules/no-unnecessary-destructuring-rename.js +2 -5
  37. package/lib/rules/parallelize-async-operations.js +119 -54
  38. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +109 -4
  39. package/lib/rules/prefer-params-over-parent-id.js +1 -1
  40. package/lib/rules/prefer-settings-object.js +27 -10
  41. package/lib/rules/prefer-type-alias-over-typeof-constant.js +9 -0
  42. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
  43. package/lib/rules/prevent-children-clobber.js +9 -5
  44. package/lib/rules/react-memoize-literals.js +131 -12
  45. package/lib/rules/require-https-error-cause.js +30 -11
  46. package/lib/rules/require-memo.js +17 -9
  47. package/lib/utils/ASTHelpers.d.ts +34 -1
  48. package/lib/utils/ASTHelpers.js +346 -112
  49. package/package.json +1 -1
@@ -31,6 +31,21 @@ exports.default = (0, createRule_1.createRule)({
31
31
  }
32
32
  return target;
33
33
  };
34
+ const isDynamicValue = (node) => {
35
+ const target = unwrapAssertions(node);
36
+ if (target.type === utils_1.AST_NODE_TYPES.CallExpression ||
37
+ target.type === utils_1.AST_NODE_TYPES.NewExpression ||
38
+ target.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
39
+ return true;
40
+ }
41
+ if (target.type === utils_1.AST_NODE_TYPES.ChainExpression) {
42
+ return isDynamicValue(target.expression);
43
+ }
44
+ if (target.type === utils_1.AST_NODE_TYPES.MemberExpression) {
45
+ return isDynamicValue(target.object);
46
+ }
47
+ return false;
48
+ };
34
49
  const describeValueKind = (node) => {
35
50
  const target = unwrapAssertions(node);
36
51
  if (target.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
@@ -102,10 +117,7 @@ exports.default = (0, createRule_1.createRule)({
102
117
  const { name } = declaration.id;
103
118
  const init = declaration.init;
104
119
  // Skip if no initializer or if it's a dynamic value or class instance
105
- if (!init ||
106
- init.type === utils_1.AST_NODE_TYPES.CallExpression ||
107
- init.type === utils_1.AST_NODE_TYPES.BinaryExpression ||
108
- init.type === utils_1.AST_NODE_TYPES.NewExpression) {
120
+ if (!init || isDynamicValue(init)) {
109
121
  return;
110
122
  }
111
123
  const sourceCode = context.sourceCode;
@@ -6,6 +6,16 @@ const createRule_1 = require("../utils/createRule");
6
6
  function isUtilMemoModulePath(path) {
7
7
  return /(?:^|\/|\\)util\/memo$/.test(path);
8
8
  }
9
+ /**
10
+ * Checks if a property name is handled by the default deep equality logic
11
+ * in our custom memo implementation (blumintAreEqual).
12
+ */
13
+ function isDefaultDeepCompareProp(name) {
14
+ return (name === 'sx' ||
15
+ name.endsWith('Sx') ||
16
+ name === 'style' ||
17
+ name.endsWith('Style'));
18
+ }
9
19
  function unwrapExpression(expression) {
10
20
  let node = expression;
11
21
  // Unwrap harmless wrappers so detection treats casted/parenthesized expressions the same.
@@ -733,13 +743,16 @@ exports.memoCompareDeeplyComplexProps = (0, createRule_1.createRule)({
733
743
  const comparatorArg = node.arguments[1];
734
744
  return { memoCall, componentArg, comparatorArg };
735
745
  }
736
- function analyzeComponentAndProps(componentArg, comparatorArg, currentScope) {
746
+ function analyzeComponentAndProps(componentArg, comparatorArg, currentScope, memoSource) {
737
747
  const componentTargets = findComponentAnalysisTargets(componentArg, (name) => initializerTracking.getInitializer(name, currentScope));
738
748
  if (!componentTargets)
739
749
  return null;
740
750
  if (isComparatorProvided(comparatorArg, currentScope))
741
751
  return null;
742
- const complexProps = collectComplexPropsForTargets(componentTargets, (expr) => collectComplexProps(expr, sourceCode, complexPropsCache));
752
+ let complexProps = collectComplexPropsForTargets(componentTargets, (expr) => collectComplexProps(expr, sourceCode, complexPropsCache));
753
+ if (isUtilMemoModulePath(memoSource)) {
754
+ complexProps = complexProps.filter((prop) => !isDefaultDeepCompareProp(prop));
755
+ }
743
756
  if (complexProps.length === 0)
744
757
  return null;
745
758
  const componentName = resolveComponentName(componentTargets, componentArg);
@@ -763,7 +776,7 @@ exports.memoCompareDeeplyComplexProps = (0, createRule_1.createRule)({
763
776
  return;
764
777
  const { memoCall, componentArg, comparatorArg } = validationResult;
765
778
  const currentScope = context.getScope();
766
- const analysisResult = analyzeComponentAndProps(componentArg, comparatorArg, currentScope);
779
+ const analysisResult = analyzeComponentAndProps(componentArg, comparatorArg, currentScope, memoCall.source);
767
780
  if (!analysisResult)
768
781
  return;
769
782
  const { complexProps, componentName } = analysisResult;
@@ -4,12 +4,12 @@ exports.memoNestedReactComponents = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const minimatch_1 = require("minimatch");
6
6
  const createRule_1 = require("../utils/createRule");
7
- const ASTHelpers_1 = require("../utils/ASTHelpers");
8
- const CALLBACK_HOOKS = new Set(['useCallback', 'useDeepCompareCallback']);
9
- const HOOK_REPLACEMENT = {
10
- useCallback: 'useMemo',
11
- useDeepCompareCallback: 'useDeepCompareMemo',
12
- };
7
+ const CALLBACK_HOOKS = new Set([
8
+ 'useCallback',
9
+ 'useDeepCompareCallback',
10
+ 'useMemo',
11
+ 'useDeepCompareMemo',
12
+ ]);
13
13
  const collectReactImports = (sourceCode) => {
14
14
  const reactImports = { namespace: null, named: {} };
15
15
  for (const statement of sourceCode.ast.body) {
@@ -252,24 +252,26 @@ const getVariableName = (node) => {
252
252
  }
253
253
  return null;
254
254
  };
255
- const findMemoReference = (reactImports) => {
256
- if (reactImports.namespace) {
257
- return `${reactImports.namespace}.memo`;
255
+ const isInsideFunction = (node) => {
256
+ let parent = node.parent;
257
+ while (parent) {
258
+ if (parent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
259
+ parent.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
260
+ parent.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
261
+ return true;
262
+ }
263
+ parent = parent.parent;
258
264
  }
259
- return reactImports.named.memo ?? null;
260
- };
261
- const hasIdentifierInScope = (identifierName, scope) => {
262
- return !!ASTHelpers_1.ASTHelpers.findVariableInScope(scope, identifierName);
265
+ return false;
263
266
  };
264
267
  exports.memoNestedReactComponents = (0, createRule_1.createRule)({
265
268
  name: 'memo-nested-react-components',
266
269
  meta: {
267
270
  type: 'suggestion',
268
271
  docs: {
269
- description: 'Disallow React components defined in useCallback/useDeepCompareCallback',
272
+ description: 'Disallow React components defined in render bodies, hooks, or passed as props',
270
273
  recommended: 'error',
271
274
  },
272
- fixable: 'code',
273
275
  schema: [
274
276
  {
275
277
  type: 'object',
@@ -284,9 +286,19 @@ exports.memoNestedReactComponents = (0, createRule_1.createRule)({
284
286
  },
285
287
  ],
286
288
  messages: {
287
- memoizeNestedComponent: `What's wrong: React component "{{componentName}}" is created inside {{hookName}}.
288
- Why it matters: Components defined inside callbacks get new identities when the callback changes, causing React to remount them and drop their state and effects.
289
- How to fix: Create the component via {{replacementHook}} and wrap it in memo() so its identity stays stable across renders.`,
289
+ memoizeNestedComponent: `What's wrong: React component "{{componentName}}" is created inline inside {{locationDescription}}.
290
+
291
+ Why it matters: Inline components get new identities when their containing scope re-renders, causing React to unmount and remount them—dropping state, replaying animations, and causing UI flashes. Wrapping with memo() does NOT fix this—memo() only prevents re-renders when props change, not when the component identity itself changes.
292
+
293
+ How to fix:
294
+ 1. Define the component at MODULE SCOPE in its own file, wrapped with memo()
295
+ 2. Use React Context and/or directly provide props to supply any dynamic data the component needs
296
+ 3. Pass the stable, imported component reference to props like CatalogWrapper
297
+
298
+ Don't pass inline function components to component-type props (*Wrapper, *Component).
299
+ Render-prop callbacks (e.g., render={...}) are fine; this rule targets component-type props only.
300
+
301
+ See: https://react.dev/learn/your-first-component#nesting-and-organizing-components`,
290
302
  },
291
303
  },
292
304
  defaultOptions: [{}],
@@ -298,7 +310,16 @@ How to fix: Create the component via {{replacementHook}} and wrap it in memo() s
298
310
  }
299
311
  const sourceCode = context.sourceCode ?? context.getSourceCode();
300
312
  const reactImports = collectReactImports(sourceCode);
301
- const memoReference = findMemoReference(reactImports);
313
+ const reportNestedComponentViolation = (node, componentName, locationDescription) => {
314
+ context.report({
315
+ node,
316
+ messageId: 'memoizeNestedComponent',
317
+ data: {
318
+ componentName,
319
+ locationDescription,
320
+ },
321
+ });
322
+ };
302
323
  return {
303
324
  CallExpression(node) {
304
325
  const hook = isHookCall(node.callee);
@@ -314,95 +335,79 @@ How to fix: Create the component via {{replacementHook}} and wrap it in memo() s
314
335
  if (!componentMatch) {
315
336
  return;
316
337
  }
338
+ // For useMemo, it only counts as a component if it returns a function
339
+ if (hook.name.includes('useMemo') ||
340
+ hook.name.includes('useDeepCompareMemo')) {
341
+ if (componentMatch.componentIsCallback) {
342
+ // Returns JSX directly, so it's an element, not a component
343
+ return;
344
+ }
345
+ }
317
346
  const componentName = getVariableName(node) ??
318
347
  (isFunctionExpression(callback) &&
319
348
  callback.id?.type === utils_1.AST_NODE_TYPES.Identifier
320
349
  ? callback.id.name
321
350
  : 'component');
322
- const replacementHook = HOOK_REPLACEMENT[hook.name];
323
- context.report({
324
- node,
325
- messageId: 'memoizeNestedComponent',
326
- data: {
327
- componentName,
328
- hookName: `${hook.name}()`,
329
- replacementHook: `${replacementHook}()`,
330
- },
331
- fix: (fixer) => {
332
- if (!memoReference) {
333
- return null;
334
- }
335
- const memoNamespace = memoReference.endsWith('.memo')
336
- ? memoReference.slice(0, -'.memo'.length)
337
- : null;
338
- // Prefer SourceCode#getScope when available; fall back to RuleContext#getScope for ESLint compatibility.
339
- const sourceCodeWithScope = sourceCode;
340
- const scope = typeof sourceCodeWithScope.getScope === 'function'
341
- ? sourceCodeWithScope.getScope(node)
342
- : context.getScope();
343
- const replacementIdentifierAvailable = hook.name === 'useCallback'
344
- ? hasIdentifierInScope(HOOK_REPLACEMENT.useCallback, scope)
345
- : hasIdentifierInScope(HOOK_REPLACEMENT.useDeepCompareCallback, scope);
346
- if (!replacementIdentifierAvailable) {
347
- // When the standalone replacement hook (useMemo/useDeepCompareMemo) is not imported,
348
- // we can only fix member expressions that use the React namespace.
349
- if (node.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
350
- node.callee.object.type !== utils_1.AST_NODE_TYPES.Identifier) {
351
- return null;
352
- }
353
- // Ensure memo's namespace matches the hook's namespace (e.g., React.useCallback → React.useMemo + React.memo).
354
- if (memoReference &&
355
- memoReference.includes('.') &&
356
- memoReference !== `${node.callee.object.name}.memo`) {
357
- return null;
358
- }
359
- }
360
- // Component factories (functions returning functions that return JSX) require manual refactoring.
361
- if (!componentMatch.componentIsCallback) {
362
- return null;
363
- }
364
- // Ensure callback is a function expression before attempting to wrap it.
365
- if (!isFunctionExpression(callback)) {
366
- return null;
367
- }
368
- // React.memo() expects a synchronous component function. Wrapping async functions or
369
- // generators would produce a Promise/yielding component, which is not renderable.
370
- if (callback.async ||
371
- (callback.type === utils_1.AST_NODE_TYPES.FunctionExpression &&
372
- callback.generator)) {
373
- return null;
374
- }
375
- // Avoid rewriting non-React namespaces (e.g., Hooks.useCallback → Hooks.useMemo),
376
- // even when useMemo/memo are available as named imports.
377
- if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
378
- if (node.callee.object.type !== utils_1.AST_NODE_TYPES.Identifier ||
379
- !reactImports.namespace ||
380
- node.callee.object.name !== reactImports.namespace) {
381
- return null;
382
- }
383
- }
384
- // Prevent mixing namespaces: don't apply fix when hook and memo use different React imports.
385
- if (memoNamespace &&
386
- node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
387
- node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
388
- node.callee.object.name !== memoNamespace) {
389
- return null;
390
- }
391
- const callbackText = sourceCode.getText(callback);
392
- const callbackReplacement = `() => ${memoReference}(${callbackText})`;
393
- const fixes = [
394
- fixer.replaceText(callback, callbackReplacement),
395
- ];
396
- if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
397
- fixes.push(fixer.replaceText(node.callee, HOOK_REPLACEMENT[node.callee.name]));
398
- }
399
- else if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
400
- node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
401
- fixes.push(fixer.replaceText(node.callee.property, HOOK_REPLACEMENT[node.callee.property.name]));
402
- }
403
- return fixes;
404
- },
405
- });
351
+ reportNestedComponentViolation(node, componentName, `${hook.name}()`);
352
+ },
353
+ VariableDeclarator(node) {
354
+ if (!node.init)
355
+ return;
356
+ if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
357
+ return;
358
+ // Only check if name starts with uppercase (convention for components)
359
+ if (!/^[A-Z]/.test(node.id.name))
360
+ return;
361
+ if (!isInsideFunction(node))
362
+ return;
363
+ // Skip if it's already a hook call (handled by CallExpression visitor)
364
+ if (node.init.type === utils_1.AST_NODE_TYPES.CallExpression) {
365
+ const hook = isHookCall(node.init.callee);
366
+ if (hook)
367
+ return;
368
+ }
369
+ const componentMatch = expressionCreatesComponent(node.init, reactImports);
370
+ if (!componentMatch)
371
+ return;
372
+ // JSX elements assigned to variables are fine, only report function definitions
373
+ if (componentMatch.componentIsCallback)
374
+ return;
375
+ reportNestedComponentViolation(node, node.id.name, 'a render body');
376
+ },
377
+ FunctionDeclaration(node) {
378
+ if (!node.id || !/^[A-Z]/.test(node.id.name))
379
+ return;
380
+ if (!isInsideFunction(node))
381
+ return;
382
+ const componentMatch = functionCreatesComponent(node, reactImports);
383
+ if (!componentMatch)
384
+ return;
385
+ reportNestedComponentViolation(node.id, node.id.name, 'a render body');
386
+ },
387
+ JSXAttribute(node) {
388
+ if (node.name.type !== utils_1.AST_NODE_TYPES.JSXIdentifier)
389
+ return;
390
+ const attrName = node.name.name;
391
+ // Check if it's a component-type prop
392
+ if (!/(Wrapper|Component|Template|Header|Footer)$/.test(attrName)) {
393
+ return;
394
+ }
395
+ if (!node.value ||
396
+ node.value.type !== utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
397
+ return;
398
+ }
399
+ const expression = node.value.expression;
400
+ if (expression.type === utils_1.AST_NODE_TYPES.JSXEmptyExpression)
401
+ return;
402
+ const componentMatch = expressionCreatesComponent(expression, reactImports);
403
+ if (!componentMatch)
404
+ return;
405
+ // For props, we only report if it's a function (component definition)
406
+ if (componentMatch.componentIsCallback) {
407
+ // It's a JSX element passed directly, which is usually fine
408
+ return;
409
+ }
410
+ reportNestedComponentViolation(node, attrName, `the "${attrName}" prop`);
406
411
  },
407
412
  };
408
413
  },
@@ -82,8 +82,13 @@ const getSourceCode = (context) => {
82
82
  `getSourceCode=${typeof context.getSourceCode}.`);
83
83
  };
84
84
  const getScope = (context, sourceCode, node) => {
85
- const typedSourceCode = sourceCode;
86
- return typedSourceCode.getScope?.(node) ?? context.getScope?.() ?? null;
85
+ try {
86
+ const typedSourceCode = sourceCode;
87
+ return typedSourceCode.getScope?.(node) ?? context.getScope?.() ?? null;
88
+ }
89
+ catch {
90
+ return context.getScope?.() ?? null;
91
+ }
87
92
  };
88
93
  const analyzeInlineCallback = (callback) => {
89
94
  if (!callback.async) {
@@ -1 +1,2 @@
1
- export declare const noCircularReferences: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"circularReference", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const noCircularReferences: TSESLint.RuleModule<"circularReference", [], TSESLint.RuleListener>;
@@ -107,11 +107,20 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
107
107
  node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
108
108
  node.property.name === 'method'));
109
109
  }
110
+ function getVariable(name) {
111
+ let scope = context.getScope();
112
+ while (scope) {
113
+ const variable = scope.variables.find((v) => v.name === name);
114
+ if (variable) {
115
+ return variable;
116
+ }
117
+ scope = scope.upper;
118
+ }
119
+ return null;
120
+ }
110
121
  function getReferencedObject(node) {
111
122
  if (isIdentifier(node)) {
112
- const scope = context.getScope();
113
- const scopeId = getScopeId(scope);
114
- const variable = scope.variables.find((v) => v.name === node.name);
123
+ const variable = getVariable(node.name);
115
124
  if (variable?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
116
125
  const init = variable.defs[0].node.init;
117
126
  if (init) {
@@ -127,24 +136,13 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
127
136
  }
128
137
  }
129
138
  }
130
- // Check objects in the current scope
131
- const scopeObjects = scopeMap.get(scopeId);
132
- if (scopeObjects) {
133
- for (const obj of scopeObjects) {
134
- const info = objectMap.get(obj);
135
- if (info && info.scope === scopeId && !info.isCircular) {
136
- return obj;
137
- }
138
- }
139
- }
140
139
  }
141
140
  else if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
142
141
  const property = node.property;
143
142
  if (property.type === utils_1.AST_NODE_TYPES.Identifier) {
144
143
  const object = node.object;
145
144
  if (isIdentifier(object)) {
146
- const scope = context.getScope();
147
- const variable = scope.variables.find((v) => v.name === object.name);
145
+ const variable = getVariable(object.name);
148
146
  if (variable?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
149
147
  const init = variable.defs[0].node.init;
150
148
  if (init) {
@@ -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 &&
@@ -61,6 +61,7 @@ context) {
61
61
  const usages = new Map(); // Track usage and its position
62
62
  const visited = new Set();
63
63
  let needsEntireObject = false;
64
+ let isUsed = false;
64
65
  // Built-in array methods that indicate usage of the entire array
65
66
  const ARRAY_METHODS = new Set([
66
67
  'map',
@@ -230,6 +231,52 @@ context) {
230
231
  if (!node || visited.has(node))
231
232
  return;
232
233
  visited.add(node);
234
+ // Direct usage of the target identifier to distinguish between:
235
+ // 1. Never used (not even present) → suggest removal
236
+ // 2. Used in ways requiring entire object → no suggestion
237
+ // 3. Used only via specific fields → suggest replacing with fields
238
+ if (node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === objectName) {
239
+ const parent = node.parent;
240
+ // Exclude: property name in `other.objectName` (not our target object)
241
+ const isMemberProperty = parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
242
+ parent.property === node &&
243
+ !parent.computed;
244
+ // Exclude: object in `objectName.prop` (handled by MemberExpression visitor for field tracking)
245
+ const isMemberObject = parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
246
+ parent.object === node;
247
+ // Exclude: key in `{ objectName: value }` (not usage, just a label)
248
+ // Include: shorthand `{ objectName }` (actual usage)
249
+ const isPropertyKey = parent?.type === utils_1.AST_NODE_TYPES.Property &&
250
+ parent.key === node &&
251
+ !parent.computed &&
252
+ !parent.shorthand;
253
+ if (!isMemberProperty && !isMemberObject && !isPropertyKey) {
254
+ isUsed = true;
255
+ // Patterns that require the entire object (cannot refactor to specific fields)
256
+ const isTypeAUsage = parent?.type === utils_1.AST_NODE_TYPES.ReturnStatement ||
257
+ parent?.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
258
+ parent?.type === utils_1.AST_NODE_TYPES.BinaryExpression ||
259
+ parent?.type === utils_1.AST_NODE_TYPES.LogicalExpression ||
260
+ parent?.type === utils_1.AST_NODE_TYPES.ConditionalExpression ||
261
+ parent?.type === utils_1.AST_NODE_TYPES.UnaryExpression ||
262
+ (parent?.type === utils_1.AST_NODE_TYPES.Property &&
263
+ (parent.value === node ||
264
+ parent.shorthand ||
265
+ (parent.key === node && parent.computed))) ||
266
+ parent?.type === utils_1.AST_NODE_TYPES.TemplateLiteral ||
267
+ parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
268
+ parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression ||
269
+ parent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer ||
270
+ parent?.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute ||
271
+ parent?.type === utils_1.AST_NODE_TYPES.SpreadElement ||
272
+ parent?.type === utils_1.AST_NODE_TYPES.ForInStatement ||
273
+ parent?.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
274
+ parent?.type === utils_1.AST_NODE_TYPES.CallExpression;
275
+ if (isTypeAUsage) {
276
+ needsEntireObject = true;
277
+ }
278
+ }
279
+ }
233
280
  if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
234
281
  // Check if the object is being called as a function
235
282
  if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
@@ -431,7 +478,7 @@ context) {
431
478
  return posA - posB;
432
479
  });
433
480
  const filteredUsages = new Set(sortedPaths);
434
- const notUsed = !needsEntireObject && filteredUsages.size === 0;
481
+ const notUsed = !needsEntireObject && !isUsed && filteredUsages.size === 0;
435
482
  return {
436
483
  usages: filteredUsages,
437
484
  needsEntireObject,
@@ -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
  },
@@ -190,9 +190,9 @@ function isInModuleScope(node) {
190
190
  }
191
191
  return true;
192
192
  }
193
- function isComponentLikeFunction(fn, displayName) {
193
+ function isComponentLikeFunction(fn, context, displayName) {
194
194
  const body = fn.body;
195
- const hasJSX = !!body && ASTHelpers_1.ASTHelpers.returnsJSX(body);
195
+ const hasJSX = !!body && ASTHelpers_1.ASTHelpers.returnsJSX(body, context);
196
196
  const expressionBody = fn.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
197
197
  fn.body.type !== utils_1.AST_NODE_TYPES.BlockStatement
198
198
  ? fn.body
@@ -204,7 +204,15 @@ function isComponentLikeFunction(fn, displayName) {
204
204
  }
205
205
  function findVariableInScopes(context, identifier) {
206
206
  const sourceCode = context.sourceCode;
207
- let scope = sourceCode.getScope?.(identifier) ?? context.getScope();
207
+ let scope = null;
208
+ try {
209
+ // Tolerate ESLint API variations across versions/configurations.
210
+ // Some contexts may not provide getScope or may throw unexpectedly.
211
+ scope = sourceCode.getScope?.(identifier) ?? context.getScope();
212
+ }
213
+ catch {
214
+ scope = null;
215
+ }
208
216
  while (scope) {
209
217
  const variable = scope.variables.find((v) => v.name === identifier.name);
210
218
  if (variable)
@@ -340,7 +348,7 @@ exports.noInlineComponentProp = (0, createRule_1.createRule)({
340
348
  }
341
349
  if (!fnNode)
342
350
  return false;
343
- return isComponentLikeFunction(fnNode, displayName);
351
+ return isComponentLikeFunction(fnNode, context, displayName);
344
352
  }
345
353
  function report(node, propName, componentName) {
346
354
  context.report({
@@ -394,13 +402,14 @@ exports.noInlineComponentProp = (0, createRule_1.createRule)({
394
402
  return;
395
403
  }
396
404
  const fnNode = findObjectPropertyFunction(defNode.init, member.property.name);
397
- if (fnNode && isComponentLikeFunction(fnNode, member.property.name)) {
405
+ if (fnNode &&
406
+ isComponentLikeFunction(fnNode, context, member.property.name)) {
398
407
  report(member, propName, member.property.name);
399
408
  }
400
409
  }
401
410
  function handleInlineFunctionExpression(fn, propName) {
402
411
  const explicitName = fn.type === utils_1.AST_NODE_TYPES.FunctionExpression ? fn.id?.name : undefined;
403
- if (!isComponentLikeFunction(fn, explicitName)) {
412
+ if (!isComponentLikeFunction(fn, context, explicitName)) {
404
413
  return;
405
414
  }
406
415
  const displayName = (fn.type === utils_1.AST_NODE_TYPES.FunctionExpression && fn.id?.name) ||
@@ -411,7 +420,7 @@ exports.noInlineComponentProp = (0, createRule_1.createRule)({
411
420
  const fnNode = getFunctionFromCall(call);
412
421
  if (!fnNode)
413
422
  return;
414
- if (isComponentLikeFunction(fnNode)) {
423
+ if (isComponentLikeFunction(fnNode, context)) {
415
424
  report(call, propName, INLINE_COMPONENT_NAME);
416
425
  }
417
426
  }
@@ -1,4 +1,4 @@
1
- import { TSESTree } from '@typescript-eslint/utils';
2
- export declare const noPassthroughGetters: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noPassthroughGetter", never[], {
1
+ import { TSESTree, TSESLint } from '@typescript-eslint/utils';
2
+ export declare const noPassthroughGetters: TSESLint.RuleModule<"noPassthroughGetter", never[], {
3
3
  MethodDefinition(node: TSESTree.MethodDefinition): void;
4
4
  }>;