@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,7 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ASTHelpers = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
4
5
  class ASTHelpers {
6
+ /**
7
+ * AST node shapes vary across ESLint/typescript-eslint versions, with some
8
+ * node types (ParenthesizedExpression, TSSatisfiesExpression) not consistently
9
+ * available in type definitions. Type guards and runtime checks ensure correctness
10
+ * despite these discrepancies, trading compile-time safety for cross-version
11
+ * compatibility until type definitions stabilize.
12
+ *
13
+ * Semantics Contract:
14
+ * - Helpers like getScope and returnsJSX must be invoked from the active
15
+ * visitor traversal context (ESLint 8 compatible).
16
+ * - returnsJSX is a heuristic and does not perform a full control-flow proof.
17
+ * - Reliance on runtime type guards (isParenthesizedExpression,
18
+ * isLoopOrLabeledStatement) is intentional.
19
+ */
5
20
  /**
6
21
  * Finds a variable by name in the scope chain starting from the given scope.
7
22
  */
@@ -17,9 +32,30 @@ class ASTHelpers {
17
32
  }
18
33
  return null;
19
34
  }
35
+ /**
36
+ * Compatibility wrapper for getting the scope of a node across ESLint versions.
37
+ * ESLint 9 moves getScope onto sourceCode; ESLint 8 exposes context.getScope().
38
+ */
39
+ static getScope(context, node) {
40
+ const sourceCode = context.sourceCode;
41
+ const sourceGetScope = sourceCode?.getScope;
42
+ const contextGetScope = context.getScope;
43
+ if (typeof sourceGetScope === 'function') {
44
+ try {
45
+ return sourceGetScope.call(sourceCode, node);
46
+ }
47
+ catch {
48
+ // Fall through to context.getScope
49
+ }
50
+ }
51
+ if (typeof contextGetScope === 'function') {
52
+ return contextGetScope.call(context);
53
+ }
54
+ throw new Error('getScope is not available in this ESLint version.');
55
+ }
20
56
  static blockIncludesIdentifier(block) {
21
57
  for (const statement of block.body) {
22
- if (ASTHelpers.declarationIncludesIdentifier(statement)) {
58
+ if (this.declarationIncludesIdentifier(statement)) {
23
59
  return true;
24
60
  }
25
61
  }
@@ -30,7 +66,7 @@ class ASTHelpers {
30
66
  return false;
31
67
  }
32
68
  // Gracefully handle ParenthesizedExpression without widening AST node types
33
- if (node.type === 'ParenthesizedExpression') {
69
+ if (this.isParenthesizedExpression(node)) {
34
70
  return this.declarationIncludesIdentifier(node.expression);
35
71
  }
36
72
  switch (node.type) {
@@ -39,11 +75,11 @@ class ASTHelpers {
39
75
  case 'TSSatisfiesExpression':
40
76
  return this.declarationIncludesIdentifier(node.expression);
41
77
  case 'ArrayPattern':
42
- return node.elements.some((element) => ASTHelpers.declarationIncludesIdentifier(element));
78
+ return node.elements.some((element) => this.declarationIncludesIdentifier(element));
43
79
  case 'ObjectPattern':
44
- return node.properties.some((property) => this.declarationIncludesIdentifier(property.value || null));
80
+ return node.properties.some((property) => this.declarationIncludesIdentifier(property));
45
81
  case 'AssignmentPattern':
46
- return this.declarationIncludesIdentifier(node.left);
82
+ return this.declarationIncludesIdentifier(node.right);
47
83
  case 'RestElement':
48
84
  return this.declarationIncludesIdentifier(node.argument);
49
85
  case 'AwaitExpression':
@@ -52,8 +88,28 @@ class ASTHelpers {
52
88
  return (this.declarationIncludesIdentifier(node.left) ||
53
89
  this.declarationIncludesIdentifier(node.right));
54
90
  case 'BlockStatement':
55
- return node.body.some((statement) => statement.type === 'BlockStatement' &&
56
- ASTHelpers.blockIncludesIdentifier(statement));
91
+ return node.body.some((statement) => this.declarationIncludesIdentifier(statement));
92
+ case 'ExpressionStatement':
93
+ return this.declarationIncludesIdentifier(node.expression);
94
+ case 'TryStatement':
95
+ return (this.declarationIncludesIdentifier(node.block) ||
96
+ this.declarationIncludesIdentifier(node.handler) ||
97
+ this.declarationIncludesIdentifier(node.finalizer));
98
+ case 'CatchClause':
99
+ return (this.patternHasDependency(node.param) ||
100
+ this.declarationIncludesIdentifier(node.body));
101
+ case 'ReturnStatement':
102
+ case 'ThrowStatement':
103
+ return this.declarationIncludesIdentifier(node.argument);
104
+ case 'VariableDeclaration':
105
+ return node.declarations.some((decl) => this.declarationIncludesIdentifier(decl));
106
+ case 'VariableDeclarator':
107
+ return (this.patternHasDependency(node.id) ||
108
+ this.declarationIncludesIdentifier(node.init));
109
+ case 'FunctionDeclaration':
110
+ case 'FunctionExpression':
111
+ case 'ArrowFunctionExpression':
112
+ return (node.params.some((param) => this.patternHasDependency(param)) || this.declarationIncludesIdentifier(node.body));
57
113
  case 'IfStatement':
58
114
  return (this.declarationIncludesIdentifier(node.test) ||
59
115
  this.declarationIncludesIdentifier(node.consequent) ||
@@ -63,26 +119,30 @@ class ASTHelpers {
63
119
  case 'Identifier':
64
120
  return true;
65
121
  case 'SpreadElement':
66
- return ASTHelpers.declarationIncludesIdentifier(node.argument);
122
+ return this.declarationIncludesIdentifier(node.argument);
67
123
  case 'ChainExpression':
68
- return ASTHelpers.declarationIncludesIdentifier(node.expression);
124
+ return this.declarationIncludesIdentifier(node.expression);
69
125
  case 'ArrayExpression':
70
126
  return node.elements.some((element) => element &&
71
127
  (element.type === 'SpreadElement'
72
- ? ASTHelpers.declarationIncludesIdentifier(element.argument)
73
- : ASTHelpers.declarationIncludesIdentifier(element)));
128
+ ? this.declarationIncludesIdentifier(element.argument)
129
+ : this.declarationIncludesIdentifier(element)));
74
130
  case 'ObjectExpression':
75
131
  return node.properties.some((property) => {
76
132
  if (property.type === 'Property') {
77
- return ASTHelpers.declarationIncludesIdentifier(property.value);
133
+ return ((property.computed &&
134
+ this.declarationIncludesIdentifier(property.key)) ||
135
+ this.declarationIncludesIdentifier(property.value));
78
136
  }
79
137
  else if (property.type === 'SpreadElement') {
80
- return ASTHelpers.declarationIncludesIdentifier(property.argument);
138
+ return this.declarationIncludesIdentifier(property.argument);
81
139
  }
82
140
  return false;
83
141
  });
84
142
  case 'Property':
85
- return this.declarationIncludesIdentifier(node.value);
143
+ return ((node.computed &&
144
+ this.declarationIncludesIdentifier(node.key)) ||
145
+ this.declarationIncludesIdentifier(node.value));
86
146
  case 'BinaryExpression':
87
147
  case 'LogicalExpression':
88
148
  return (this.declarationIncludesIdentifier(node.left) ||
@@ -130,160 +190,207 @@ class ASTHelpers {
130
190
  return false;
131
191
  }
132
192
  }
193
+ /**
194
+ * Checks if a pattern (in a declaration or parameter) contains any dependencies.
195
+ * Patterns themselves define new bindings (Identifiers), but they can contain
196
+ * dependencies in computed keys or default values (AssignmentPattern).
197
+ */
198
+ static patternHasDependency(node) {
199
+ if (!node) {
200
+ return false;
201
+ }
202
+ switch (node.type) {
203
+ case 'Identifier':
204
+ return false; // Declaration site, not a reference
205
+ case 'AssignmentPattern':
206
+ return this.declarationIncludesIdentifier(node.right);
207
+ case 'ArrayPattern':
208
+ return node.elements.some((element) => this.patternHasDependency(element));
209
+ case 'ObjectPattern':
210
+ return node.properties.some((property) => this.patternHasDependency(property));
211
+ case 'Property':
212
+ return ((node.computed &&
213
+ this.declarationIncludesIdentifier(node.key)) ||
214
+ this.patternHasDependency(node.value));
215
+ case 'RestElement':
216
+ return this.patternHasDependency(node.argument);
217
+ default:
218
+ // For anything else (like nested expressions in computed keys),
219
+ // fall back to the general check.
220
+ return this.declarationIncludesIdentifier(node);
221
+ }
222
+ }
133
223
  static classMethodDependenciesOf(node, graph, className) {
134
224
  const dependencies = [];
135
225
  if (!node) {
136
226
  return dependencies;
137
227
  }
138
228
  // Gracefully handle ParenthesizedExpression without widening AST node types
139
- if (node.type === 'ParenthesizedExpression') {
140
- return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
229
+ if (this.isParenthesizedExpression(node)) {
230
+ return this.classMethodDependenciesOf(node.expression, graph, className);
141
231
  }
142
232
  switch (node.type) {
143
- case 'MethodDefinition':
233
+ case 'MethodDefinition': {
144
234
  const functionBody = node.value.body;
145
235
  return (functionBody?.body || [])
146
- .map((statement) => ASTHelpers.classMethodDependenciesOf(statement, graph, className))
236
+ .map((statement) => this.classMethodDependenciesOf(statement, graph, className))
147
237
  .flat();
238
+ }
148
239
  case 'Identifier':
149
240
  dependencies.push(node.name);
150
241
  break;
151
242
  case 'ExpressionStatement':
152
- return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
153
- case 'MemberExpression':
154
- if ((node.object.type === 'ThisExpression' &&
155
- node.property.type === 'Identifier') ||
156
- (node.object.type === 'Identifier' &&
157
- node.object.name === className &&
158
- node.property.type === 'Identifier')) {
159
- dependencies.push(node.property.name);
243
+ return this.classMethodDependenciesOf(node.expression, graph, className);
244
+ case 'MemberExpression': {
245
+ const memberExpr = node;
246
+ if ((memberExpr.object.type === 'ThisExpression' &&
247
+ memberExpr.property.type === 'Identifier') ||
248
+ (memberExpr.object.type === 'Identifier' &&
249
+ memberExpr.object.name === className &&
250
+ memberExpr.property.type === 'Identifier')) {
251
+ dependencies.push(memberExpr.property.name);
160
252
  }
161
253
  else {
162
254
  return [
163
- ...ASTHelpers.classMethodDependenciesOf(node.object, graph, className),
164
- ...ASTHelpers.classMethodDependenciesOf(node.property, graph, className),
255
+ ...this.classMethodDependenciesOf(memberExpr.object, graph, className),
256
+ ...this.classMethodDependenciesOf(memberExpr.property, graph, className),
165
257
  ];
166
258
  }
167
259
  break;
260
+ }
168
261
  case 'TSNonNullExpression':
169
- return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
262
+ return this.classMethodDependenciesOf(node.expression, graph, className);
170
263
  case 'ArrayPattern':
171
264
  return node.elements
172
- .map((element) => ASTHelpers.classMethodDependenciesOf(element, graph, className))
265
+ .map((element) => this.classMethodDependenciesOf(element, graph, className))
173
266
  .flat();
174
267
  case 'ObjectPattern':
175
268
  return node.properties
176
- .map((property) => ASTHelpers.classMethodDependenciesOf(property.value || null, graph, className))
269
+ .map((property) => this.classMethodDependenciesOf(property, graph, className))
177
270
  .flat();
178
271
  case 'AssignmentPattern':
179
- return ASTHelpers.classMethodDependenciesOf(node.left, graph, className);
272
+ return this.classMethodDependenciesOf(node.left, graph, className);
180
273
  case 'RestElement':
181
- return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
274
+ return this.classMethodDependenciesOf(node.argument, graph, className);
182
275
  case 'AwaitExpression':
183
- return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
184
- case 'AssignmentExpression':
276
+ return this.classMethodDependenciesOf(node.argument, graph, className);
277
+ case 'AssignmentExpression': {
278
+ const assignExpr = node;
185
279
  return [
186
- ...ASTHelpers.classMethodDependenciesOf(node.left, graph, className),
187
- ...ASTHelpers.classMethodDependenciesOf(node.right, graph, className),
280
+ ...this.classMethodDependenciesOf(assignExpr.left, graph, className),
281
+ ...this.classMethodDependenciesOf(assignExpr.right, graph, className),
188
282
  ];
283
+ }
189
284
  case 'BlockStatement':
190
285
  return node.body
191
- .map((statement) => ASTHelpers.classMethodDependenciesOf(statement, graph, className))
286
+ .map((statement) => this.classMethodDependenciesOf(statement, graph, className))
192
287
  .flat()
193
288
  .filter(Boolean);
194
- case 'IfStatement':
289
+ case 'IfStatement': {
290
+ const ifStmt = node;
195
291
  return [
196
- ...ASTHelpers.classMethodDependenciesOf(node.test, graph, className),
197
- ...ASTHelpers.classMethodDependenciesOf(node.consequent, graph, className),
198
- ...ASTHelpers.classMethodDependenciesOf(node.alternate, graph, className),
292
+ ...this.classMethodDependenciesOf(ifStmt.test, graph, className),
293
+ ...this.classMethodDependenciesOf(ifStmt.consequent, graph, className),
294
+ ...this.classMethodDependenciesOf(ifStmt.alternate, graph, className),
199
295
  ];
296
+ }
200
297
  case 'TSTypeAssertion':
201
- return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
202
- case 'Identifier':
203
- return dependencies;
298
+ return this.classMethodDependenciesOf(node.expression, graph, className);
204
299
  case 'SpreadElement':
205
- return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
300
+ return this.classMethodDependenciesOf(node.argument, graph, className);
206
301
  case 'ChainExpression':
207
- return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
302
+ return this.classMethodDependenciesOf(node.expression, graph, className);
208
303
  case 'ArrayExpression':
209
304
  return node.elements
210
305
  .map((element) => element &&
211
306
  (element.type === 'SpreadElement'
212
- ? ASTHelpers.classMethodDependenciesOf(element.argument, graph, className)
213
- : ASTHelpers.classMethodDependenciesOf(element, graph, className)))
307
+ ? this.classMethodDependenciesOf(element.argument, graph, className)
308
+ : this.classMethodDependenciesOf(element, graph, className)))
214
309
  .flat()
215
310
  .filter(Boolean);
216
311
  case 'ObjectExpression':
217
312
  return node.properties
218
313
  .map((property) => {
219
314
  if (property.type === 'Property') {
220
- return ASTHelpers.classMethodDependenciesOf(property.value, graph, className);
315
+ return this.classMethodDependenciesOf(property.value, graph, className);
221
316
  }
222
317
  else if (property.type === 'SpreadElement') {
223
- return ASTHelpers.classMethodDependenciesOf(property.argument, graph, className);
318
+ return this.classMethodDependenciesOf(property.argument, graph, className);
224
319
  }
225
320
  return false;
226
321
  })
227
322
  .flat()
228
323
  .filter(Boolean);
229
324
  case 'Property':
230
- return ASTHelpers.classMethodDependenciesOf(node.value, graph, className);
325
+ return this.classMethodDependenciesOf(node.value, graph, className);
231
326
  case 'BinaryExpression':
232
- case 'LogicalExpression':
327
+ case 'LogicalExpression': {
328
+ const binLogExpr = node;
233
329
  return [
234
- ...ASTHelpers.classMethodDependenciesOf(node.left, graph, className),
235
- ...ASTHelpers.classMethodDependenciesOf(node.right, graph, className),
330
+ ...this.classMethodDependenciesOf(binLogExpr.left, graph, className),
331
+ ...this.classMethodDependenciesOf(binLogExpr.right, graph, className),
236
332
  ];
333
+ }
237
334
  case 'UnaryExpression':
238
335
  case 'UpdateExpression':
239
- return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
336
+ return this.classMethodDependenciesOf(node.argument, graph, className);
240
337
  case 'CallExpression':
241
- case 'NewExpression':
338
+ case 'NewExpression': {
242
339
  // For function and constructor calls, we care about both the callee and the arguments.
340
+ const callNewExpr = node;
243
341
  return [
244
- ...ASTHelpers.classMethodDependenciesOf(node.callee, graph, className),
245
- ...node.arguments
246
- .map((arg) => ASTHelpers.classMethodDependenciesOf(arg, graph, className))
342
+ ...this.classMethodDependenciesOf(callNewExpr.callee, graph, className),
343
+ ...callNewExpr.arguments
344
+ .map((arg) => this.classMethodDependenciesOf(arg, graph, className))
247
345
  .flat(),
248
346
  ];
249
- case 'ConditionalExpression':
347
+ }
348
+ case 'ConditionalExpression': {
349
+ const condExpr = node;
250
350
  return [
251
- ...ASTHelpers.classMethodDependenciesOf(node.test, graph, className),
252
- ...ASTHelpers.classMethodDependenciesOf(node.consequent, graph, className),
253
- ...ASTHelpers.classMethodDependenciesOf(node.alternate, graph, className),
351
+ ...this.classMethodDependenciesOf(condExpr.test, graph, className),
352
+ ...this.classMethodDependenciesOf(condExpr.consequent, graph, className),
353
+ ...this.classMethodDependenciesOf(condExpr.alternate, graph, className),
254
354
  ];
355
+ }
255
356
  case 'TSAsExpression':
256
- return ASTHelpers.classMethodDependenciesOf(node.expression, graph, className);
357
+ return this.classMethodDependenciesOf(node.expression, graph, className);
257
358
  case 'VariableDeclaration':
258
359
  return node.declarations
259
- .map((declaration) => ASTHelpers.classMethodDependenciesOf(declaration, graph, className))
360
+ .map((declaration) => this.classMethodDependenciesOf(declaration, graph, className))
260
361
  .flat()
261
362
  .filter(Boolean);
262
363
  case 'VariableDeclarator':
263
- return ASTHelpers.classMethodDependenciesOf(node.init, graph, className);
264
- case 'ForOfStatement':
364
+ return this.classMethodDependenciesOf(node.init, graph, className);
365
+ case 'ForOfStatement': {
366
+ const forOfStmt = node;
265
367
  return [
266
- ...ASTHelpers.classMethodDependenciesOf(node.left, graph, className),
267
- ...ASTHelpers.classMethodDependenciesOf(node.body, graph, className),
268
- ...ASTHelpers.classMethodDependenciesOf(node.right, graph, className),
368
+ ...this.classMethodDependenciesOf(forOfStmt.left, graph, className),
369
+ ...this.classMethodDependenciesOf(forOfStmt.body, graph, className),
370
+ ...this.classMethodDependenciesOf(forOfStmt.right, graph, className),
269
371
  ];
270
- case 'ForStatement':
271
- return [node.body, node.init, node.test, node.update]
272
- .map((node) => ASTHelpers.classMethodDependenciesOf(node, graph, className))
372
+ }
373
+ case 'ForStatement': {
374
+ const forStmt = node;
375
+ return [forStmt.body, forStmt.init, forStmt.test, forStmt.update]
376
+ .map((node) => this.classMethodDependenciesOf(node, graph, className))
273
377
  .flat();
378
+ }
274
379
  case 'ThrowStatement':
275
- return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
380
+ return this.classMethodDependenciesOf(node.argument, graph, className);
276
381
  case 'TemplateLiteral':
277
382
  return node.expressions
278
- .map((expression) => ASTHelpers.classMethodDependenciesOf(expression, graph, className))
383
+ .map((expression) => this.classMethodDependenciesOf(expression, graph, className))
279
384
  .flat();
280
385
  case 'ReturnStatement':
281
- return ASTHelpers.classMethodDependenciesOf(node.argument, graph, className);
282
- case 'ArrowFunctionExpression':
386
+ return this.classMethodDependenciesOf(node.argument, graph, className);
387
+ case 'ArrowFunctionExpression': {
388
+ const arrowFunc = node;
283
389
  return [
284
- ...node.params.flatMap((param) => ASTHelpers.classMethodDependenciesOf(param, graph, className)),
285
- ...ASTHelpers.classMethodDependenciesOf(node.body, graph, className),
390
+ ...(arrowFunc.params || []).flatMap((param) => this.classMethodDependenciesOf(param, graph, className)),
391
+ ...this.classMethodDependenciesOf(arrowFunc.body, graph, className),
286
392
  ];
393
+ }
287
394
  default:
288
395
  break;
289
396
  }
@@ -300,17 +407,19 @@ class ASTHelpers {
300
407
  return typeof value === 'object' && value !== null && 'type' in value;
301
408
  }
302
409
  static hasReturnStatement(node) {
303
- if (node.type === 'ReturnStatement') {
410
+ if (node.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
304
411
  return true;
305
412
  }
306
- if (node.type === 'IfStatement') {
307
- const consequentHasReturn = ASTHelpers.hasReturnStatement(node.consequent);
308
- const alternateHasReturn = !!node.alternate && ASTHelpers.hasReturnStatement(node.alternate);
413
+ if (node.type === utils_1.AST_NODE_TYPES.IfStatement) {
414
+ const ifStmt = node;
415
+ const consequentHasReturn = this.hasReturnStatement(ifStmt.consequent);
416
+ const alternateHasReturn = !!ifStmt.alternate && this.hasReturnStatement(ifStmt.alternate);
309
417
  return consequentHasReturn && alternateHasReturn;
310
418
  }
311
- if (node.type === 'BlockStatement') {
312
- for (const statement of node.body) {
313
- if (ASTHelpers.hasReturnStatement(statement)) {
419
+ if (node.type === utils_1.AST_NODE_TYPES.BlockStatement) {
420
+ const blockStmt = node;
421
+ for (const statement of blockStmt.body) {
422
+ if (this.hasReturnStatement(statement)) {
314
423
  return true;
315
424
  }
316
425
  }
@@ -320,8 +429,8 @@ class ASTHelpers {
320
429
  continue; // Ignore the parent property
321
430
  }
322
431
  const value = node[key];
323
- if (ASTHelpers.isNode(value)) {
324
- if (ASTHelpers.hasReturnStatement(value)) {
432
+ if (this.isNode(value)) {
433
+ if (this.hasReturnStatement(value)) {
325
434
  return true;
326
435
  }
327
436
  }
@@ -330,55 +439,228 @@ class ASTHelpers {
330
439
  }
331
440
  static isNodeExported(node) {
332
441
  // Checking if the node is exported as a named export.
333
- if (node.parent && node.parent.type === 'ExportNamedDeclaration') {
442
+ if (node.parent &&
443
+ node.parent.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
334
444
  return true;
335
445
  }
336
446
  // Checking if the node is exported as default.
337
447
  if (node.parent &&
338
448
  node.parent.parent &&
339
- node.parent.parent.type === 'ExportDefaultDeclaration') {
449
+ node.parent.parent.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
340
450
  return true;
341
451
  }
342
452
  // Checking if the node is exported in a list of exports.
343
453
  if (node.parent &&
344
454
  node.parent.parent &&
345
- node.parent.parent.type === 'ExportSpecifier' &&
346
- node.parent.parent.exported.name === node.name) {
455
+ node.parent.parent.type === utils_1.AST_NODE_TYPES.ExportSpecifier &&
456
+ node.parent.parent.exported?.name ===
457
+ node.name) {
347
458
  return true;
348
459
  }
349
460
  return false;
350
461
  }
351
- static returnsJSX(node) {
352
- if (node.type === 'JSXElement' || node.type === 'JSXFragment') {
462
+ static isLoopOrLabeledStatement(node) {
463
+ return (node.type === utils_1.AST_NODE_TYPES.WhileStatement ||
464
+ node.type === utils_1.AST_NODE_TYPES.DoWhileStatement ||
465
+ node.type === utils_1.AST_NODE_TYPES.ForStatement ||
466
+ node.type === utils_1.AST_NODE_TYPES.ForInStatement ||
467
+ node.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
468
+ node.type === utils_1.AST_NODE_TYPES.LabeledStatement);
469
+ }
470
+ static isParenthesizedExpression(node) {
471
+ // ParenthesizedExpression is not in AST_NODE_TYPES across all ESLint versions
472
+ // so we check the string literal directly for cross-version compatibility.
473
+ return node?.type === 'ParenthesizedExpression';
474
+ }
475
+ static returnsJSXValue(node) {
476
+ if (!node) {
477
+ return false;
478
+ }
479
+ if (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
480
+ node.type === utils_1.AST_NODE_TYPES.JSXFragment) {
353
481
  return true;
354
482
  }
355
- if (node.type === 'BlockStatement') {
356
- for (const statement of node.body) {
357
- if (statement.type === 'ReturnStatement' &&
358
- (statement.argument?.type === 'JSXElement' ||
359
- statement.argument?.type === 'JSXFragment')) {
360
- return true;
361
- }
362
- // Handle conditional returns
363
- if (statement.type === 'ReturnStatement' &&
364
- statement.argument?.type === 'ConditionalExpression') {
365
- const conditionalExpr = statement.argument;
366
- if (ASTHelpers.returnsJSX(conditionalExpr.consequent) ||
367
- ASTHelpers.returnsJSX(conditionalExpr.alternate)) {
368
- return true;
483
+ if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
484
+ return (this.returnsJSXValue(node.left) ||
485
+ this.returnsJSXValue(node.right));
486
+ }
487
+ if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
488
+ return (this.returnsJSXValue(node.consequent) ||
489
+ this.returnsJSXValue(node.alternate));
490
+ }
491
+ if (node.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
492
+ node.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
493
+ node.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
494
+ node.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
495
+ return this.returnsJSXValue(node.expression);
496
+ }
497
+ if (this.isParenthesizedExpression(node)) {
498
+ return this.returnsJSXValue(node.expression);
499
+ }
500
+ // Function/class values are not JSX values.
501
+ return false;
502
+ }
503
+ static returnsJSXFromStatement(node, context) {
504
+ if (!node) {
505
+ return false;
506
+ }
507
+ if (node.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
508
+ const arg = node.argument;
509
+ if (arg?.type === utils_1.AST_NODE_TYPES.Identifier && context) {
510
+ // Resolve variable to its initializer if possible
511
+ const scope = this.getScope(context, arg);
512
+ if (scope) {
513
+ const variable = this.findVariableInScope(scope, arg.name);
514
+ if (variable && variable.defs.length === 1) {
515
+ const def = variable.defs[0];
516
+ // Check if the variable is reassigned after initialization.
517
+ // We only follow variables that are defined once and never reassigned
518
+ // to ensure we're following a deterministic JSX-returning value.
519
+ // This is intentionally conservative to avoid ambiguous multi-write cases,
520
+ // which affects React component detection accuracy.
521
+ const isReassigned = variable.references.some((ref) => ref.isWrite() && !ref.init);
522
+ if (isReassigned) {
523
+ return this.returnsJSXValue(arg);
524
+ }
525
+ if (def.type === 'Variable' &&
526
+ def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
527
+ def.node.init) {
528
+ // ReturnStatement returns a value; treat function/class initializers as non-JSX values.
529
+ return this.returnsJSXValue(def.node.init);
530
+ }
369
531
  }
370
532
  }
533
+ else {
534
+ return this.returnsJSXValue(arg);
535
+ }
371
536
  }
537
+ return this.returnsJSXValue(arg);
538
+ }
539
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
540
+ // Variable declarations don't return values from the enclosing function.
541
+ return false;
542
+ }
543
+ if (node.type === utils_1.AST_NODE_TYPES.BlockStatement) {
544
+ return node.body.some((stmt) => this.returnsJSXFromStatement(stmt, context));
545
+ }
546
+ if (node.type === utils_1.AST_NODE_TYPES.IfStatement) {
547
+ return (this.returnsJSXFromStatement(node.consequent, context) ||
548
+ this.returnsJSXFromStatement(node.alternate, context));
549
+ }
550
+ if (node.type === utils_1.AST_NODE_TYPES.SwitchStatement) {
551
+ return node.cases.some((c) => c.consequent.some((stmt) => this.returnsJSXFromStatement(stmt, context)));
372
552
  }
373
- if (node.type === 'ConditionalExpression') {
374
- return (ASTHelpers.returnsJSX(node.consequent) ||
375
- ASTHelpers.returnsJSX(node.alternate));
553
+ if (node.type === utils_1.AST_NODE_TYPES.TryStatement) {
554
+ return (this.returnsJSXFromStatement(node.block, context) ||
555
+ this.returnsJSXFromStatement(node.handler?.body, context) ||
556
+ this.returnsJSXFromStatement(node.finalizer, context));
557
+ }
558
+ if (this.isLoopOrLabeledStatement(node)) {
559
+ return this.returnsJSXFromStatement(node.body, context);
376
560
  }
377
561
  return false;
378
562
  }
563
+ static returnsJSX(node, context) {
564
+ if (!node) {
565
+ return false;
566
+ }
567
+ if (node.type === utils_1.AST_NODE_TYPES.ExpressionStatement) {
568
+ // ExpressionStatement does not produce a return value for the surrounding function, so treat as non-returning.
569
+ return false;
570
+ }
571
+ if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
572
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
573
+ node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
574
+ const func = node;
575
+ if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
576
+ return func.body.type === utils_1.AST_NODE_TYPES.BlockStatement
577
+ ? this.returnsJSXFromStatement(func.body, context)
578
+ : this.returnsJSXValue(func.body);
579
+ }
580
+ return this.returnsJSXFromStatement(func.body, context);
581
+ }
582
+ if (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
583
+ node.type === utils_1.AST_NODE_TYPES.JSXFragment) {
584
+ return true;
585
+ }
586
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
587
+ // Detects `const Component = () => <div />`-style declarations.
588
+ return node.declarations.some((decl) => this.returnsJSX(decl.init, context));
589
+ }
590
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
591
+ return this.returnsJSX(node.init, context);
592
+ }
593
+ // Treat remaining nodes as statement-path or value checks.
594
+ return (this.returnsJSXFromStatement(node, context) || this.returnsJSXValue(node));
595
+ }
379
596
  static hasParameters(node) {
380
597
  return node.params && node.params.length > 0;
381
598
  }
599
+ /**
600
+ * Compatibility wrapper for getting declared variables across ESLint versions.
601
+ */
602
+ static getDeclaredVariables(context, node) {
603
+ const sourceCode = context.sourceCode;
604
+ const sourceCodeWithDeclaredVariables = sourceCode;
605
+ const fn = typeof sourceCodeWithDeclaredVariables?.getDeclaredVariables ===
606
+ 'function'
607
+ ? sourceCodeWithDeclaredVariables.getDeclaredVariables.bind(sourceCodeWithDeclaredVariables)
608
+ : typeof context.getDeclaredVariables === 'function'
609
+ ? context.getDeclaredVariables.bind(context)
610
+ : null;
611
+ if (!fn) {
612
+ throw new Error('getDeclaredVariables is not available in this ESLint version.');
613
+ }
614
+ return fn(node);
615
+ }
616
+ /**
617
+ * Checks if a call expression or new expression is a call to HttpsError.
618
+ * Handles both 'HttpsError' and 'https.HttpsError'.
619
+ */
620
+ static isHttpsErrorCall(callee) {
621
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
622
+ return (callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
623
+ callee.object.name === 'https' &&
624
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
625
+ callee.property.name === 'HttpsError');
626
+ }
627
+ else if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
628
+ return callee.name === 'HttpsError';
629
+ }
630
+ return false;
631
+ }
632
+ /**
633
+ * Checks if a call expression is a call to toHttpsError.
634
+ * Handles both 'toHttpsError' and 'https.toHttpsError'.
635
+ */
636
+ static isToHttpsErrorCall(callee) {
637
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
638
+ return (callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
639
+ callee.object.name === 'https' &&
640
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
641
+ callee.property.name === 'toHttpsError');
642
+ }
643
+ else if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
644
+ return callee.name === 'toHttpsError';
645
+ }
646
+ return false;
647
+ }
648
+ /**
649
+ * Unwraps TypeScript-specific nodes (assertions, non-null, satisfies) and
650
+ * parenthesized expressions to get to the underlying expression.
651
+ */
652
+ static unwrapTSAssertions(node) {
653
+ let inner = node;
654
+ while (inner &&
655
+ (inner.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
656
+ inner.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
657
+ inner.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
658
+ inner.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
659
+ inner.type === 'ParenthesizedExpression')) {
660
+ inner = inner.expression;
661
+ }
662
+ return inner;
663
+ }
382
664
  /**
383
665
  * Helper to get ancestors of a node in a way that is compatible with both ESLint v8 and v9.
384
666
  * In ESLint v9, context.getAncestors() is deprecated and moved to context.sourceCode.getAncestors(node).