@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
@@ -1,9 +1,73 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.preferNullishCoalescingBooleanProps = void 0;
4
27
  const utils_1 = require("@typescript-eslint/utils");
28
+ const ts = __importStar(require("typescript"));
5
29
  const createRule_1 = require("../utils/createRule");
6
30
  const BOOLEAN_PROP_REGEX = /^(is|has|should|can|will|do|does|did|was|were|enable|disable)/;
31
+ function isBooleanType(type, checker) {
32
+ if (type.isUnion()) {
33
+ return type.types.every((t) => isBooleanType(t, checker));
34
+ }
35
+ // For type parameters, check if the constraint is a boolean type
36
+ if (type.getFlags() & ts.TypeFlags.TypeParameter) {
37
+ if (checker) {
38
+ const constraint = checker.getBaseConstraintOfType(type);
39
+ if (constraint && constraint !== type) {
40
+ return isBooleanType(constraint, checker);
41
+ }
42
+ }
43
+ return false;
44
+ }
45
+ return ((type.getFlags() & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral)) !==
46
+ 0);
47
+ }
48
+ function isPossiblyNullish(type, checker) {
49
+ if (type.isUnion()) {
50
+ return type.types.some((t) => isPossiblyNullish(t, checker));
51
+ }
52
+ // For type parameters, check if the constraint excludes nullish
53
+ if (type.getFlags() & ts.TypeFlags.TypeParameter) {
54
+ if (checker) {
55
+ const constraint = checker.getBaseConstraintOfType(type);
56
+ if (constraint && constraint !== type) {
57
+ return isPossiblyNullish(constraint, checker);
58
+ }
59
+ }
60
+ // If we can't determine the constraint, assume it could be nullish
61
+ return true;
62
+ }
63
+ return ((type.getFlags() &
64
+ (ts.TypeFlags.Null |
65
+ ts.TypeFlags.Undefined |
66
+ ts.TypeFlags.Void |
67
+ ts.TypeFlags.Any |
68
+ ts.TypeFlags.Unknown)) !==
69
+ 0);
70
+ }
7
71
  function isInJSXBooleanAttribute(node) {
8
72
  const parent = node.parent;
9
73
  if (parent?.type !== utils_1.AST_NODE_TYPES.JSXAttribute)
@@ -57,7 +121,34 @@ function isInConditionalContext(node) {
57
121
  /**
58
122
  * Determines if a node is within a boolean context in JSX props or other boolean contexts
59
123
  */
60
- function isInBooleanContext(node) {
124
+ function isInBooleanContext(node, checker, parserServices) {
125
+ if (checker && parserServices) {
126
+ try {
127
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
128
+ const contextualType = checker.getContextualType(tsNode);
129
+ if (contextualType && isBooleanType(contextualType, checker)) {
130
+ return true;
131
+ }
132
+ // Also check if the expression itself is a boolean type
133
+ const actualType = checker.getTypeAtLocation(tsNode);
134
+ if (actualType && isBooleanType(actualType, checker)) {
135
+ return true;
136
+ }
137
+ // If this is a logical expression, also check if the left operand is a boolean
138
+ if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
139
+ const leftTSNode = parserServices.esTreeNodeToTSNodeMap.get(node.left);
140
+ const leftType = checker.getTypeAtLocation(leftTSNode);
141
+ if (leftType && isBooleanType(leftType, checker)) {
142
+ return true;
143
+ }
144
+ }
145
+ }
146
+ catch {
147
+ // esTreeNodeToTSNodeMap may fail for synthetic nodes or nodes without
148
+ // source positions; getContextualType may return undefined for non-expression
149
+ // contexts. Fall back to AST-based heuristics in these cases.
150
+ }
151
+ }
61
152
  let current = node;
62
153
  // Traverse up the AST to find if we're in a boolean context
63
154
  while (current && current.parent) {
@@ -365,7 +456,19 @@ function isInBooleanContext(node) {
365
456
  /**
366
457
  * Checks if the left operand could be nullish (null or undefined)
367
458
  */
368
- function couldBeNullish(node) {
459
+ function couldBeNullish(node, checker, parserServices) {
460
+ if (checker && parserServices) {
461
+ try {
462
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
463
+ const type = checker.getTypeAtLocation(tsNode);
464
+ return isPossiblyNullish(type, checker);
465
+ }
466
+ catch {
467
+ // esTreeNodeToTSNodeMap may fail for synthetic nodes or nodes without
468
+ // source positions; getTypeAtLocation may throw for nodes without
469
+ // type information. Fall back to manual check.
470
+ }
471
+ }
369
472
  // For literals, check the actual value
370
473
  if (node.type === utils_1.AST_NODE_TYPES.Literal) {
371
474
  return node.value === null || node.value === undefined;
@@ -402,16 +505,18 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
402
505
  },
403
506
  defaultOptions: [],
404
507
  create(context) {
508
+ const parserServices = utils_1.ESLintUtils.getParserServices(context, true);
509
+ const checker = parserServices?.program?.getTypeChecker();
405
510
  return {
406
511
  LogicalExpression(node) {
407
512
  if (node.operator === '||') {
408
513
  // If the node is in a boolean context, we allow logical OR
409
- if (isInBooleanContext(node)) {
514
+ if (isInBooleanContext(node, checker, parserServices)) {
410
515
  return;
411
516
  }
412
517
  // Check if this could benefit from nullish coalescing
413
518
  // We only suggest nullish coalescing when the left operand could be nullish
414
- if (couldBeNullish(node.left)) {
519
+ if (couldBeNullish(node.left, checker, parserServices)) {
415
520
  const sourceCode = context.getSourceCode();
416
521
  const leftText = sourceCode.getText(node.left);
417
522
  const rightText = sourceCode.getText(node.right);
@@ -202,7 +202,7 @@ exports.preferParamsOverParentId = (0, createRule_1.createRule)({
202
202
  meta: {
203
203
  type: 'suggestion',
204
204
  docs: {
205
- description: 'Prefer handler params for parent IDs instead of traversing ref.parent.id so Firebase triggers stay aligned with path templates and type-safe.',
205
+ description: 'Prefer event.params over ref.parent.id for type-safe Firebase trigger paths.',
206
206
  recommended: 'error',
207
207
  },
208
208
  fixable: 'code',
@@ -47,6 +47,7 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
47
47
  defaultOptions: [defaultOptions],
48
48
  create(context, [options]) {
49
49
  const finalOptions = { ...defaultOptions, ...options };
50
+ const { sourceCode } = context;
50
51
  function getParameterType(param) {
51
52
  if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
52
53
  return getParameterType(param.left);
@@ -56,13 +57,10 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
56
57
  const typeNode = param.typeAnnotation.typeAnnotation;
57
58
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
58
59
  // Include type parameters in the type signature to differentiate generic types
59
- const typeName = typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier
60
- ? typeNode.typeName.name
61
- : 'unknown';
60
+ const typeName = sourceCode.getText(typeNode.typeName);
62
61
  // If there are type parameters, include them in the type signature
63
62
  if (typeNode.typeParameters &&
64
63
  typeNode.typeParameters.params.length > 0) {
65
- const sourceCode = context.sourceCode;
66
64
  const typeParams = typeNode.typeParameters.params
67
65
  .map((param) => sourceCode.getText(param))
68
66
  .join(', ');
@@ -76,13 +74,10 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
76
74
  const typeNode = param.typeAnnotation.typeAnnotation;
77
75
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
78
76
  // Include type parameters in the type signature to differentiate generic types
79
- const typeName = typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier
80
- ? typeNode.typeName.name
81
- : 'unknown';
77
+ const typeName = sourceCode.getText(typeNode.typeName);
82
78
  // If there are type parameters, include them in the type signature
83
79
  if (typeNode.typeParameters &&
84
80
  typeNode.typeParameters.params.length > 0) {
85
- const sourceCode = context.sourceCode;
86
81
  const typeParams = typeNode.typeParameters.params
87
82
  .map((param) => sourceCode.getText(param))
88
83
  .join(', ');
@@ -98,12 +93,14 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
98
93
  return 'boolean';
99
94
  return typeNode.type;
100
95
  }
101
- return 'unknown';
96
+ return null;
102
97
  }
103
98
  function findDuplicateParameterType(params) {
104
99
  const typeMap = new Map();
105
100
  for (const param of params) {
106
101
  const type = getParameterType(param);
102
+ if (type === null)
103
+ continue;
107
104
  const count = (typeMap.get(type) || 0) + 1;
108
105
  typeMap.set(type, count);
109
106
  if (count > 1) {
@@ -248,6 +245,24 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
248
245
  }
249
246
  return false;
250
247
  }
248
+ function hasImplicitlyTypedParams(params) {
249
+ return params.some((param) => {
250
+ let current = param;
251
+ if (current.type === utils_1.AST_NODE_TYPES.TSParameterProperty) {
252
+ current = current.parameter;
253
+ }
254
+ if (current.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
255
+ current = current.left;
256
+ }
257
+ if (current.type === utils_1.AST_NODE_TYPES.Identifier ||
258
+ current.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
259
+ current.type === utils_1.AST_NODE_TYPES.ArrayPattern ||
260
+ current.type === utils_1.AST_NODE_TYPES.RestElement) {
261
+ return !current.typeAnnotation;
262
+ }
263
+ return true;
264
+ });
265
+ }
251
266
  function shouldIgnoreNode(node) {
252
267
  // Ignore built-in objects and third-party modules
253
268
  if (isBuiltInOrThirdParty(node))
@@ -270,7 +285,7 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
270
285
  parent = parent.parent;
271
286
  }
272
287
  }
273
- // Ignore functions with A/B pattern parameters
288
+ // Ignore functions with A/B pattern parameters or missing type annotations
274
289
  if ((node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
275
290
  node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
276
291
  node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
@@ -279,6 +294,8 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
279
294
  Array.isArray(node.params)) {
280
295
  if (hasABPattern(node.params))
281
296
  return true;
297
+ if (hasImplicitlyTypedParams(node.params))
298
+ return true;
282
299
  }
283
300
  // Check if the function is a handler with transaction parameter
284
301
  // This is a common pattern in Firebase/Firestore handlers
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.preferTypeAliasOverTypeofConstant = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
6
7
  const PAREN_TYPE = utils_1.AST_NODE_TYPES.TSParenthesizedType ??
7
8
  'TSParenthesizedType';
8
9
  const isParenthesizedType = (node) => {
@@ -87,6 +88,71 @@ function collectReferencedTypeNames(node, acc = new Set()) {
87
88
  collectReferencedTypeNames(e, acc);
88
89
  break;
89
90
  }
91
+ case utils_1.AST_NODE_TYPES.TSIndexedAccessType: {
92
+ const idx = node;
93
+ collectReferencedTypeNames(idx.objectType, acc);
94
+ collectReferencedTypeNames(idx.indexType, acc);
95
+ break;
96
+ }
97
+ case utils_1.AST_NODE_TYPES.TSMappedType: {
98
+ const mapped = node;
99
+ if (mapped.typeAnnotation)
100
+ collectReferencedTypeNames(mapped.typeAnnotation, acc);
101
+ if (mapped.nameType)
102
+ collectReferencedTypeNames(mapped.nameType, acc);
103
+ if (mapped.typeParameter && mapped.typeParameter.constraint) {
104
+ collectReferencedTypeNames(mapped.typeParameter.constraint, acc);
105
+ }
106
+ break;
107
+ }
108
+ case utils_1.AST_NODE_TYPES.TSConditionalType: {
109
+ const cond = node;
110
+ collectReferencedTypeNames(cond.checkType, acc);
111
+ collectReferencedTypeNames(cond.extendsType, acc);
112
+ collectReferencedTypeNames(cond.trueType, acc);
113
+ collectReferencedTypeNames(cond.falseType, acc);
114
+ break;
115
+ }
116
+ case utils_1.AST_NODE_TYPES.TSTypeLiteral: {
117
+ const lit = node;
118
+ for (const m of lit.members) {
119
+ if (m.type === utils_1.AST_NODE_TYPES.TSPropertySignature && m.typeAnnotation) {
120
+ collectReferencedTypeNames(m.typeAnnotation.typeAnnotation, acc);
121
+ }
122
+ else if (m.type === utils_1.AST_NODE_TYPES.TSMethodSignature) {
123
+ if (m.returnType) {
124
+ collectReferencedTypeNames(m.returnType.typeAnnotation, acc);
125
+ }
126
+ for (const p of m.params) {
127
+ if (p.typeAnnotation) {
128
+ collectReferencedTypeNames(p.typeAnnotation.typeAnnotation, acc);
129
+ }
130
+ }
131
+ }
132
+ else if (m.type === utils_1.AST_NODE_TYPES.TSIndexSignature) {
133
+ if (m.typeAnnotation) {
134
+ collectReferencedTypeNames(m.typeAnnotation.typeAnnotation, acc);
135
+ }
136
+ for (const p of m.parameters) {
137
+ if (p.typeAnnotation) {
138
+ collectReferencedTypeNames(p.typeAnnotation.typeAnnotation, acc);
139
+ }
140
+ }
141
+ }
142
+ else if (m.type === utils_1.AST_NODE_TYPES.TSCallSignatureDeclaration ||
143
+ m.type === utils_1.AST_NODE_TYPES.TSConstructSignatureDeclaration) {
144
+ if (m.returnType) {
145
+ collectReferencedTypeNames(m.returnType.typeAnnotation, acc);
146
+ }
147
+ for (const p of m.params) {
148
+ if (p.typeAnnotation) {
149
+ collectReferencedTypeNames(p.typeAnnotation.typeAnnotation, acc);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ break;
155
+ }
90
156
  default: {
91
157
  if (isParenthesizedType(node)) {
92
158
  collectReferencedTypeNames(node.typeAnnotation, acc);
@@ -225,10 +291,15 @@ exports.preferTypeAliasOverTypeofConstant = (0, createRule_1.createRule)({
225
291
  TSTypeQuery(node) {
226
292
  if (!collected)
227
293
  return;
228
- // Skip `keyof typeof X`
229
- if (node.parent &&
230
- node.parent.type === utils_1.AST_NODE_TYPES.TSTypeOperator &&
231
- node.parent.operator === 'keyof') {
294
+ const ancestors = ASTHelpers_1.ASTHelpers.getAncestors(context, node);
295
+ // Skip if inside a type alias declaration (Issue #1117, #1175)
296
+ // This allows 'type T = typeof CONST' as the canonical way to define the alias.
297
+ if (ancestors.some((a) => a.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration)) {
298
+ return;
299
+ }
300
+ // Skip `keyof typeof X` as it's a canonical way to derive a union of keys from a constant object.
301
+ if (ancestors.some((a) => a.type === utils_1.AST_NODE_TYPES.TSTypeOperator &&
302
+ a.operator === 'keyof')) {
232
303
  return;
233
304
  }
234
305
  if (node.exprName.type !== utils_1.AST_NODE_TYPES.Identifier) {
@@ -60,8 +60,6 @@ function isNonPrimitiveWithoutTypes(expr) {
60
60
  switch (expr.type) {
61
61
  case utils_1.AST_NODE_TYPES.ArrayExpression:
62
62
  case utils_1.AST_NODE_TYPES.ObjectExpression:
63
- case utils_1.AST_NODE_TYPES.FunctionExpression:
64
- case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
65
63
  case utils_1.AST_NODE_TYPES.NewExpression:
66
64
  case utils_1.AST_NODE_TYPES.ClassExpression:
67
65
  return true;
@@ -74,6 +72,8 @@ function isNonPrimitiveWithoutTypes(expr) {
74
72
  case utils_1.AST_NODE_TYPES.MemberExpression:
75
73
  case utils_1.AST_NODE_TYPES.ChainExpression:
76
74
  case utils_1.AST_NODE_TYPES.CallExpression:
75
+ case utils_1.AST_NODE_TYPES.FunctionExpression:
76
+ case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
77
77
  default:
78
78
  return false;
79
79
  }
@@ -92,9 +92,9 @@ function isNonPrimitiveWithTypes(context, expr) {
92
92
  if (!tsNode)
93
93
  return false;
94
94
  const type = checker.getTypeAtLocation(tsNode);
95
- // Only consider explicit functions as non-primitive here
95
+ // Explicitly ignore functions even though they are non-primitive
96
96
  if (type.getCallSignatures().length > 0)
97
- return true;
97
+ return false;
98
98
  // Avoid guessing for unknown/any or non-function types
99
99
  if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown))
100
100
  return false;
@@ -243,7 +243,7 @@ function isImportedIdentifier(context, name) {
243
243
  }
244
244
  return false;
245
245
  }
246
- function identifierUsedAsObjectOrFunction(callback, name) {
246
+ function identifierUsedAsObjectOrArray(callback, name) {
247
247
  const stack = [callback.body];
248
248
  while (stack.length) {
249
249
  const node = stack.pop();
@@ -268,12 +268,6 @@ function identifierUsedAsObjectOrFunction(callback, name) {
268
268
  }
269
269
  }
270
270
  }
271
- if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
272
- const callee = node.callee;
273
- if (callee.type === utils_1.AST_NODE_TYPES.Identifier && callee.name === name) {
274
- return true; // function usage
275
- }
276
- }
277
271
  if (node.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute) {
278
272
  if (node.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
279
273
  node.argument.name === name) {
@@ -313,13 +307,13 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
313
307
  meta: {
314
308
  type: 'suggestion',
315
309
  docs: {
316
- description: 'Enforce using useDeepCompareMemo when dependency array contains non-primitive values (objects, arrays, functions) that are not already memoized. This prevents unnecessary re-renders due to reference changes.',
310
+ description: 'Enforce using useDeepCompareMemo when dependency array contains non-primitive values (objects, arrays) that are not already memoized. This prevents unnecessary re-renders due to reference changes.',
317
311
  recommended: 'error',
318
312
  },
319
313
  fixable: 'code',
320
314
  schema: [],
321
315
  messages: {
322
- preferUseDeepCompareMemo: 'Dependency array for "{{hook}}" includes objects/arrays/functions that change identity each render, so React treats them as changed and reruns the memoized computation, triggering avoidable renders. Use useDeepCompareMemo (or memoize those dependencies first) so comparisons use deep equality and the memo stays stable.',
316
+ preferUseDeepCompareMemo: 'Dependency array for "{{hook}}" includes objects/arrays that change identity each render, so React treats them as changed and reruns the memoized computation, triggering avoidable renders. Use useDeepCompareMemo (or memoize those dependencies first) so comparisons use deep equality and the memo stays stable.',
323
317
  },
324
318
  },
325
319
  defaultOptions: [],
@@ -366,7 +360,7 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
366
360
  if (isImportedIdentifier(context, expr.name)) {
367
361
  isNonPrimitive = false;
368
362
  }
369
- else if (identifierUsedAsObjectOrFunction(callback, expr.name) &&
363
+ else if (identifierUsedAsObjectOrArray(callback, expr.name) &&
370
364
  !isIdentifierMemoizedAbove(expr.name, memoizedIds)) {
371
365
  isNonPrimitive = true;
372
366
  }
@@ -11,7 +11,7 @@ exports.preferUseMemoOverUseEffectUseState = (0, createRule_1.createRule)({
11
11
  recommended: 'error',
12
12
  },
13
13
  messages: {
14
- preferUseMemo: 'Derived state "{{stateName}}" is computed inside useEffect and copied into React state even though the value comes from a pure calculation. That extra render cycle and state indirection make components re-render more and risk stale snapshots when dependencies change. Compute the value with useMemo (or inline in render) and read it directly instead of mirroring it into state.',
14
+ preferUseMemo: 'Use useMemo to compute derived state "{{stateName}}" instead of useEffect + useState to avoid extra render cycles and stale snapshots.',
15
15
  },
16
16
  schema: [],
17
17
  },
@@ -44,12 +44,12 @@ function resolveFunctionName(node) {
44
44
  }
45
45
  return null;
46
46
  }
47
- function isComponentLike(node) {
47
+ function isComponentLike(node, context) {
48
48
  const name = resolveFunctionName(node);
49
49
  if (name && /^[A-Z]/.test(name)) {
50
50
  return true;
51
51
  }
52
- return ASTHelpers_1.ASTHelpers.returnsJSX(node.body);
52
+ return ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context);
53
53
  }
54
54
  function patternHasChildrenProperty(pattern) {
55
55
  return pattern.properties.some((prop) => {
@@ -266,7 +266,8 @@ function bindingMayContainChildren(binding, context) {
266
266
  return false;
267
267
  if (binding.typeAnnotationExcludesProperty)
268
268
  return false;
269
- const services = context.sourceCode?.parserServices ?? context.parserServices;
269
+ const services = context.sourceCode?.parserServices ??
270
+ context.parserServices;
270
271
  if (!services?.program || !services?.esTreeNodeToTSNodeMap) {
271
272
  return true;
272
273
  }
@@ -410,7 +411,7 @@ exports.preventChildrenClobber = (0, createRule_1.createRule)({
410
411
  return {
411
412
  ':function'(node) {
412
413
  const ctx = {
413
- isComponent: isComponentLike(node),
414
+ isComponent: isComponentLike(node, context),
414
415
  bindings: new Map(),
415
416
  propsLikeIdentifiers: new Set(),
416
417
  childrenValueSourceIds: new Map(),
@@ -510,7 +511,10 @@ exports.preventChildrenClobber = (0, createRule_1.createRule)({
510
511
  if (!bindingMayContainChildren(binding, context)) {
511
512
  continue;
512
513
  }
513
- offendingSpreads.push({ name, childrenSourceId: binding.childrenSourceId });
514
+ offendingSpreads.push({
515
+ name,
516
+ childrenSourceId: binding.childrenSourceId,
517
+ });
514
518
  }
515
519
  if (offendingSpreads.length === 0)
516
520
  return;