@eslint-react/core 4.2.5-beta.0 → 5.0.0-next.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 (3) hide show
  1. package/dist/index.d.ts +441 -383
  2. package/dist/index.js +538 -536
  3. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -1,22 +1,15 @@
1
1
  import * as ast from "@eslint-react/ast";
2
+ import { resolveImportSource } from "@eslint-react/var";
2
3
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
3
- import { findVariable } from "@typescript-eslint/utils/ast-utils";
4
- import { P, match } from "ts-pattern";
5
- import { JsxDetectionHint, isJsxLike } from "@eslint-react/jsx";
4
+ import { isAPIFromReact as isAPIFromReact$1 } from "@eslint-react/core";
6
5
  import { IdGenerator, RE_COMPONENT_NAME, RE_COMPONENT_NAME_LOOSE } from "@eslint-react/shared";
6
+ import { JsxDetectionHint, isJsxLike } from "@eslint-react/jsx";
7
7
  import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from "@typescript-eslint/utils";
8
+ import { P, isMatching, match } from "ts-pattern";
9
+ import ts from "typescript";
8
10
 
9
11
  //#region ../../.pkgs/eff/dist/index.js
10
12
  /**
11
- * Returns its argument.
12
- *
13
- * @param x - The value to return.
14
- * @returns The input value unchanged.
15
- */
16
- function identity(x) {
17
- return x;
18
- }
19
- /**
20
13
  * Creates a function that can be used in a data-last (aka `pipe`able) or
21
14
  * data-first style.
22
15
  *
@@ -119,24 +112,6 @@ function constFalse() {
119
112
  return false;
120
113
  }
121
114
  /**
122
- * Reverses the order of arguments for a curried function.
123
- *
124
- * @param f - The function to flip.
125
- * @returns A new function with the argument order reversed.
126
- * @example
127
- * ```ts
128
- * import * as assert from "node:assert"
129
- * import { flip } from "effect/Function"
130
- *
131
- * const f = (a: number) => (b: string) => a - b.length
132
- *
133
- * assert.deepStrictEqual(flip(f)('aaa')(2), -1)
134
- * ```
135
- *
136
- * @since 1.0.0
137
- */
138
- const flip = (f) => (...b) => (...a) => f(...a)(...b);
139
- /**
140
115
  * Composes two functions, `ab` and `bc` into a single function that takes in an argument `a` of type `A` and returns a result of type `C`.
141
116
  * The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`.
142
117
  *
@@ -159,91 +134,13 @@ const flip = (f) => (...b) => (...a) => f(...a)(...b);
159
134
  const compose = dual(2, (ab, bc) => (a) => bc(ab(a)));
160
135
 
161
136
  //#endregion
162
- //#region src/api/find-import-source.ts
163
- /**
164
- * Get the arguments of a require expression
165
- * @param node The node to match
166
- * @returns The require expression arguments or null if the node is not a require expression
167
- * @internal
168
- */
169
- function getRequireExpressionArguments(node) {
170
- return match(node).with({
171
- type: AST_NODE_TYPES.CallExpression,
172
- arguments: P.select(),
173
- callee: {
174
- type: AST_NODE_TYPES.Identifier,
175
- name: "require"
176
- }
177
- }, identity).with({
178
- type: AST_NODE_TYPES.MemberExpression,
179
- object: P.select()
180
- }, getRequireExpressionArguments).otherwise(() => null);
181
- }
182
- /**
183
- * Find the import source of a variable
184
- * @param name The variable name
185
- * @param initialScope The initial scope to search
186
- * @returns The import source or null if not found
187
- */
188
- function findImportSource(name, initialScope) {
189
- return findImportSourceImpl(name, initialScope, /* @__PURE__ */ new Set());
190
- }
191
- function findImportSourceImpl(name, initialScope, visited) {
192
- if (visited.has(name)) return null;
193
- visited.add(name);
194
- const latestDef = findVariable(initialScope, name)?.defs.at(-1);
195
- if (latestDef == null) return null;
196
- const { node, parent } = latestDef;
197
- if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
198
- const { init } = node;
199
- if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) return findImportSourceImpl(init.object.name, initialScope, visited);
200
- if (init.type === AST_NODE_TYPES.Identifier) return findImportSourceImpl(init.name, initialScope, visited);
201
- const arg0 = getRequireExpressionArguments(init)?.[0];
202
- if (arg0 == null || !ast.isLiteral(arg0, "string")) return null;
203
- return arg0.value;
204
- }
205
- if (parent?.type === AST_NODE_TYPES.ImportDeclaration) return parent.source.value;
206
- return null;
207
- }
208
-
209
- //#endregion
210
- //#region src/api/is-from-react.ts
211
- /**
212
- * Check if a variable is initialized from React import
213
- * @param name The variable name
214
- * @param initialScope The initial scope
215
- * @param importSource Alternative import source of React (ex: "preact/compat")
216
- * @returns True if the variable is initialized or derived from React import
217
- */
218
- function isInitializedFromReact(name, initialScope, importSource = "react") {
219
- return name.toLowerCase() === "react" || Boolean(findImportSource(name, initialScope)?.startsWith(importSource));
220
- }
221
-
222
- //#endregion
223
- //#region src/api/is-from-react-native.ts
224
- /**
225
- * if a variable is initialized from React Native import
226
- * @param name The variable name
227
- * @param initialScope The initial scope
228
- * @param importSource Alternative import source of React Native (ex: "react-native-web")
229
- * @returns True if the variable is initialized from React Native import
230
- */
231
- function isInitializedFromReactNative(name, initialScope, importSource = "react-native") {
232
- return [
233
- "react_native",
234
- "reactnative",
235
- "rn"
236
- ].includes(name.toLowerCase()) || Boolean(findImportSource(name, initialScope)?.startsWith(importSource));
237
- }
238
-
239
- //#endregion
240
- //#region src/api/is-react-api.ts
137
+ //#region src/api.ts
241
138
  /**
242
139
  * Check if the node is a React API identifier or member expression
243
140
  * @param api The React API name to check against (ex: "useState", "React.memo")
244
141
  * @returns A predicate function to check if a node matches the API
245
142
  */
246
- function isReactAPI(api) {
143
+ function isAPI(api) {
247
144
  const func = (context, node) => {
248
145
  if (node == null) return false;
249
146
  const getText = (n) => context.sourceCode.getText(n);
@@ -259,272 +156,105 @@ function isReactAPI(api) {
259
156
  * @param api The React API name to check against
260
157
  * @returns A predicate function to check if a node is a call to the API
261
158
  */
262
- function isReactAPICall(api) {
159
+ function isAPICall(api) {
263
160
  const func = (context, node) => {
264
161
  if (node == null) return false;
265
162
  if (node.type !== AST_NODE_TYPES.CallExpression) return false;
266
- return isReactAPI(api)(context, ast.getUnderlyingExpression(node.callee));
163
+ return isAPI(api)(context, ast.getUnderlyingExpression(node.callee));
267
164
  };
268
165
  return dual(2, func);
269
166
  }
270
- const isCaptureOwnerStack = isReactAPI("captureOwnerStack");
271
- const isChildrenCount = isReactAPI("Children.count");
272
- const isChildrenForEach = isReactAPI("Children.forEach");
273
- const isChildrenMap = isReactAPI("Children.map");
274
- const isChildrenOnly = isReactAPI("Children.only");
275
- const isChildrenToArray = isReactAPI("Children.toArray");
276
- const isCloneElement = isReactAPI("cloneElement");
277
- const isCreateContext = isReactAPI("createContext");
278
- const isCreateElement = isReactAPI("createElement");
279
- const isCreateRef = isReactAPI("createRef");
280
- const isForwardRef = isReactAPI("forwardRef");
281
- const isMemo = isReactAPI("memo");
282
- const isLazy = isReactAPI("lazy");
283
- const isCaptureOwnerStackCall = isReactAPICall("captureOwnerStack");
284
- const isChildrenCountCall = isReactAPICall("Children.count");
285
- const isChildrenForEachCall = isReactAPICall("Children.forEach");
286
- const isChildrenMapCall = isReactAPICall("Children.map");
287
- const isChildrenOnlyCall = isReactAPICall("Children.only");
288
- const isChildrenToArrayCall = isReactAPICall("Children.toArray");
289
- const isCloneElementCall = isReactAPICall("cloneElement");
290
- const isCreateContextCall = isReactAPICall("createContext");
291
- const isCreateElementCall = isReactAPICall("createElement");
292
- const isCreateRefCall = isReactAPICall("createRef");
293
- const isForwardRefCall = isReactAPICall("forwardRef");
294
- const isMemoCall = isReactAPICall("memo");
295
- const isLazyCall = isReactAPICall("lazy");
296
-
297
- //#endregion
298
- //#region src/hook/hook-name.ts
299
- const REACT_BUILTIN_HOOK_NAMES = [
300
- "use",
301
- "useActionState",
302
- "useCallback",
303
- "useContext",
304
- "useDebugValue",
305
- "useDeferredValue",
306
- "useEffect",
307
- "useFormStatus",
308
- "useId",
309
- "useImperativeHandle",
310
- "useInsertionEffect",
311
- "useLayoutEffect",
312
- "useMemo",
313
- "useOptimistic",
314
- "useReducer",
315
- "useRef",
316
- "useState",
317
- "useSyncExternalStore",
318
- "useTransition"
319
- ];
320
- /**
321
- * Catch all identifiers that begin with "use" followed by an uppercase Latin
322
- * character to exclude identifiers like "user".
323
- * @param name The name of the identifier to check.
324
- * @see https://github.com/facebook/react/blob/1d6c8168db1d82713202e842df3167787ffa00ed/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts#L16
325
- */
326
- function isHookName(name) {
327
- return name === "use" || /^use[A-Z0-9]/.test(name);
328
- }
329
-
330
- //#endregion
331
- //#region src/hook/hook-is.ts
332
- /**
333
- * Determine if a function node is a React Hook based on its name.
334
- * @param node The function node to check
335
- * @returns True if the function is a React Hook, false otherwise
336
- */
337
- function isHookDefinition(node) {
338
- if (node == null) return false;
339
- const id = ast.getFunctionId(node);
340
- switch (id?.type) {
341
- case AST_NODE_TYPES.Identifier: return isHookName(id.name);
342
- case AST_NODE_TYPES.MemberExpression: return "name" in id.property && isHookName(id.property.name);
343
- default: return false;
344
- }
345
- }
346
- /**
347
- * Check if the given node is a React Hook call by its name.
348
- * @param node The node to check.
349
- * @returns `true` if the node is a React Hook call, `false` otherwise.
350
- */
351
- function isHookCall(node) {
352
- if (node == null) return false;
353
- if (node.type !== AST_NODE_TYPES.CallExpression) return false;
354
- if (node.callee.type === AST_NODE_TYPES.Identifier) return isHookName(node.callee.name);
355
- if (node.callee.type === AST_NODE_TYPES.MemberExpression) return node.callee.property.type === AST_NODE_TYPES.Identifier && isHookName(node.callee.property.name);
356
- return false;
357
- }
358
- /**
359
- * Check if a node is a call to a specific React hook.
360
- * Returns a function that accepts a hook name to check against.
361
- * @param node The AST node to check
362
- * @returns A function that takes a hook name and returns boolean
363
- */
364
- function isHookCallWithName(node) {
365
- if (node == null || node.type !== AST_NODE_TYPES.CallExpression) return constFalse;
366
- return (name) => {
367
- switch (node.callee.type) {
368
- case AST_NODE_TYPES.Identifier: return node.callee.name === name;
369
- case AST_NODE_TYPES.MemberExpression: return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === name;
370
- default: return false;
371
- }
372
- };
373
- }
374
- /**
375
- * Detect useEffect calls and variations (useLayoutEffect, etc.) using a regex pattern
376
- * @param node The AST node to check
377
- * @param additionalEffectHooks Regex pattern matching custom hooks that should be treated as effect hooks
378
- * @returns True if the node is a useEffect-like call
379
- */
380
- function isUseEffectLikeCall(node, additionalEffectHooks = { test: constFalse }) {
381
- if (node == null) return false;
382
- if (node.type !== AST_NODE_TYPES.CallExpression) return false;
383
- return [/^use\w*Effect$/u, additionalEffectHooks].some((regexp) => {
384
- if (node.callee.type === AST_NODE_TYPES.Identifier) return regexp.test(node.callee.name);
385
- if (node.callee.type === AST_NODE_TYPES.MemberExpression) return node.callee.property.type === AST_NODE_TYPES.Identifier && regexp.test(node.callee.property.name);
386
- return false;
387
- });
388
- }
389
- /**
390
- * Detect useState calls and variations (useCustomState, etc.) using a regex pattern
391
- * @param node The AST node to check
392
- * @param additionalStateHooks Regex pattern matching custom hooks that should be treated as state hooks
393
- * @returns True if the node is a useState-like call
394
- */
395
- function isUseStateLikeCall(node, additionalStateHooks = { test: constFalse }) {
396
- if (node == null) return false;
397
- if (node.type !== AST_NODE_TYPES.CallExpression) return false;
398
- switch (true) {
399
- case node.callee.type === AST_NODE_TYPES.Identifier: return node.callee.name === "useState" || additionalStateHooks.test(node.callee.name);
400
- case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier: return ast.getPropertyName(node.callee.property) === "useState" || additionalStateHooks.test(node.callee.property.name);
401
- }
402
- return false;
403
- }
404
- const isUseCall = flip(isHookCallWithName)("use");
405
- const isUseActionStateCall = flip(isHookCallWithName)("useActionState");
406
- const isUseCallbackCall = flip(isHookCallWithName)("useCallback");
407
- const isUseContextCall = flip(isHookCallWithName)("useContext");
408
- const isUseDebugValueCall = flip(isHookCallWithName)("useDebugValue");
409
- const isUseDeferredValueCall = flip(isHookCallWithName)("useDeferredValue");
410
- const isUseEffectCall = flip(isHookCallWithName)("useEffect");
411
- const isUseFormStatusCall = flip(isHookCallWithName)("useFormStatus");
412
- const isUseIdCall = flip(isHookCallWithName)("useId");
413
- const isUseImperativeHandleCall = flip(isHookCallWithName)("useImperativeHandle");
414
- const isUseInsertionEffectCall = flip(isHookCallWithName)("useInsertionEffect");
415
- const isUseLayoutEffectCall = flip(isHookCallWithName)("useLayoutEffect");
416
- const isUseMemoCall = flip(isHookCallWithName)("useMemo");
417
- const isUseOptimisticCall = flip(isHookCallWithName)("useOptimistic");
418
- const isUseReducerCall = flip(isHookCallWithName)("useReducer");
419
- const isUseRefCall = flip(isHookCallWithName)("useRef");
420
- const isUseStateCall = flip(isHookCallWithName)("useState");
421
- const isUseSyncExternalStoreCall = flip(isHookCallWithName)("useSyncExternalStore");
422
- const isUseTransitionCall = flip(isHookCallWithName)("useTransition");
423
-
424
- //#endregion
425
- //#region src/hook/hook-callback.ts
426
- /**
427
- * Determine if a node is the setup function passed to a useEffect-like hook
428
- * @param node The AST node to check
429
- */
430
- function isUseEffectSetupCallback(node) {
431
- if (node == null) return false;
432
- return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.arguments.at(0) === node && isUseEffectLikeCall(node.parent);
433
- }
434
- /**
435
- * Determine if a node is the cleanup function returned by a useEffect-like hook's setup function
436
- * @param node The AST node to check
437
- */
438
- function isUseEffectCleanupCallback(node) {
439
- if (node == null) return false;
440
- const returnStatement = ast.findParent(node, ast.is(AST_NODE_TYPES.ReturnStatement));
441
- const enclosingFunction = ast.findParent(node, ast.isFunction);
442
- if (enclosingFunction !== ast.findParent(returnStatement, ast.isFunction)) return false;
443
- return isUseEffectSetupCallback(enclosingFunction);
444
- }
445
-
446
- //#endregion
447
- //#region src/hook/hook-id.ts
448
167
  /**
449
- * Checks if the given node is a hook identifier
450
- * @param id The AST node to check
451
- * @returns `true` if the node is a hook identifier or member expression with hook name, `false` otherwise
168
+ * Check if a variable is initialized from React import
169
+ * @param name The variable name
170
+ * @param initialScope The initial scope
171
+ * @param importSource Alternative import source of React (ex: "preact/compat")
172
+ * @returns True if the variable is initialized or derived from React import
452
173
  */
453
- function isHookId(id) {
454
- switch (id.type) {
455
- case AST_NODE_TYPES.Identifier: return isHookName(id.name);
456
- case AST_NODE_TYPES.MemberExpression: return "name" in id.property && isHookName(id.property.name);
457
- default: return false;
458
- }
174
+ function isAPIFromReact(name, initialScope, importSource = "react") {
175
+ return name.toLowerCase() === "react" || Boolean(resolveImportSource(name, initialScope)?.startsWith(importSource));
459
176
  }
460
-
461
- //#endregion
462
- //#region src/hook/hook-collector.ts
463
- const idGen$2 = new IdGenerator("hook:");
464
177
  /**
465
- * Get an api and visitor object for the rule to collect hooks
466
- * @param context The ESLint rule context
467
- * @returns The api and visitor of the collector
178
+ * if a variable is initialized from React Native import
179
+ * @param name The variable name
180
+ * @param initialScope The initial scope
181
+ * @param importSource Alternative import source of React Native (ex: "react-native-web")
182
+ * @returns True if the variable is initialized from React Native import
468
183
  */
469
- function getHookCollector(context) {
470
- const hooks = /* @__PURE__ */ new Map();
471
- const functionEntries = [];
472
- const getText = (n) => context.sourceCode.getText(n);
473
- const getCurrentEntry = () => functionEntries.at(-1) ?? null;
474
- const onFunctionEnter = (node) => {
475
- const id = ast.getFunctionId(node);
476
- const key = idGen$2.next();
477
- const entry = {
478
- id,
479
- key,
480
- kind: "hook",
481
- name: id == null ? null : ast.getFullyQualifiedName(id, getText),
482
- directives: [],
483
- flag: 0n,
484
- hint: 0n,
485
- hookCalls: [],
486
- isHookDefinition: id != null && isHookId(id),
487
- node,
488
- rets: []
489
- };
490
- functionEntries.push(entry);
491
- if (!entry.isHookDefinition) return;
492
- hooks.set(key, entry);
493
- };
494
- const onFunctionExit = () => {
495
- functionEntries.pop();
496
- };
497
- return {
498
- api: { getAllHooks(node) {
499
- return [...hooks.values()];
500
- } },
501
- visitor: {
502
- ":function": onFunctionEnter,
503
- ":function:exit": onFunctionExit,
504
- "ArrowFunctionExpression[body.type!='BlockStatement']"() {
505
- const entry = getCurrentEntry();
506
- if (entry == null) return;
507
- const { body } = entry.node;
508
- if (body.type === AST_NODE_TYPES$1.BlockStatement) return;
509
- entry.rets.push(body);
510
- },
511
- CallExpression(node) {
512
- if (!isHookCall(node)) return;
513
- const entry = getCurrentEntry();
514
- if (entry == null) return;
515
- entry.hookCalls.push(node);
516
- },
517
- ReturnStatement(node) {
518
- const entry = getCurrentEntry();
519
- if (entry == null) return;
520
- entry.rets.push(node.argument);
521
- }
522
- }
523
- };
524
- }
184
+ function isAPIFromReactNative(name, initialScope, importSource = "react-native") {
185
+ return [
186
+ "react_native",
187
+ "reactnative",
188
+ "rn"
189
+ ].includes(name.toLowerCase()) || Boolean(resolveImportSource(name, initialScope)?.startsWith(importSource));
190
+ }
191
+ const isCaptureOwnerStack = isAPI("captureOwnerStack");
192
+ const isChildrenCount = isAPI("Children.count");
193
+ const isChildrenForEach = isAPI("Children.forEach");
194
+ const isChildrenMap = isAPI("Children.map");
195
+ const isChildrenOnly = isAPI("Children.only");
196
+ const isChildrenToArray = isAPI("Children.toArray");
197
+ const isCloneElement = isAPI("cloneElement");
198
+ const isCreateContext = isAPI("createContext");
199
+ const isCreateElement = isAPI("createElement");
200
+ const isCreateRef = isAPI("createRef");
201
+ const isForwardRef = isAPI("forwardRef");
202
+ const isMemo = isAPI("memo");
203
+ const isLazy = isAPI("lazy");
204
+ const isCaptureOwnerStackCall = isAPICall("captureOwnerStack");
205
+ const isChildrenCountCall = isAPICall("Children.count");
206
+ const isChildrenForEachCall = isAPICall("Children.forEach");
207
+ const isChildrenMapCall = isAPICall("Children.map");
208
+ const isChildrenOnlyCall = isAPICall("Children.only");
209
+ const isChildrenToArrayCall = isAPICall("Children.toArray");
210
+ const isCloneElementCall = isAPICall("cloneElement");
211
+ const isCreateContextCall = isAPICall("createContext");
212
+ const isCreateElementCall = isAPICall("createElement");
213
+ const isCreateRefCall = isAPICall("createRef");
214
+ const isForwardRefCall = isAPICall("forwardRef");
215
+ const isMemoCall = isAPICall("memo");
216
+ const isLazyCall = isAPICall("lazy");
217
+ const isUse = isAPI("use");
218
+ const isUseActionState = isAPI("useActionState");
219
+ const isUseCallback = isAPI("useCallback");
220
+ const isUseContext = isAPI("useContext");
221
+ const isUseDebugValue = isAPI("useDebugValue");
222
+ const isUseDeferredValue = isAPI("useDeferredValue");
223
+ const isUseEffect = isAPI("useEffect");
224
+ const isUseFormStatus = isAPI("useFormStatus");
225
+ const isUseId = isAPI("useId");
226
+ const isUseImperativeHandle = isAPI("useImperativeHandle");
227
+ const isUseInsertionEffect = isAPI("useInsertionEffect");
228
+ const isUseLayoutEffect = isAPI("useLayoutEffect");
229
+ const isUseMemo = isAPI("useMemo");
230
+ const isUseOptimistic = isAPI("useOptimistic");
231
+ const isUseReducer = isAPI("useReducer");
232
+ const isUseRef = isAPI("useRef");
233
+ const isUseState = isAPI("useState");
234
+ const isUseSyncExternalStore = isAPI("useSyncExternalStore");
235
+ const isUseTransition = isAPI("useTransition");
236
+ const isUseCall = isAPICall("use");
237
+ const isUseActionStateCall = isAPICall("useActionState");
238
+ const isUseCallbackCall = isAPICall("useCallback");
239
+ const isUseContextCall = isAPICall("useContext");
240
+ const isUseDebugValueCall = isAPICall("useDebugValue");
241
+ const isUseDeferredValueCall = isAPICall("useDeferredValue");
242
+ const isUseEffectCall = isAPICall("useEffect");
243
+ const isUseFormStatusCall = isAPICall("useFormStatus");
244
+ const isUseIdCall = isAPICall("useId");
245
+ const isUseImperativeHandleCall = isAPICall("useImperativeHandle");
246
+ const isUseInsertionEffectCall = isAPICall("useInsertionEffect");
247
+ const isUseLayoutEffectCall = isAPICall("useLayoutEffect");
248
+ const isUseMemoCall = isAPICall("useMemo");
249
+ const isUseOptimisticCall = isAPICall("useOptimistic");
250
+ const isUseReducerCall = isAPICall("useReducer");
251
+ const isUseRefCall = isAPICall("useRef");
252
+ const isUseStateCall = isAPICall("useState");
253
+ const isUseSyncExternalStoreCall = isAPICall("useSyncExternalStore");
254
+ const isUseTransitionCall = isAPICall("useTransition");
525
255
 
526
256
  //#endregion
527
- //#region src/component/component-detection-legacy.ts
257
+ //#region src/class-component.ts
528
258
  function isClassComponent(node, context) {
529
259
  if ("superClass" in node && node.superClass != null) {
530
260
  const re = /^(?:Pure)?Component$/u;
@@ -532,20 +262,29 @@ function isClassComponent(node, context) {
532
262
  case node.superClass.type === AST_NODE_TYPES.Identifier:
533
263
  if (!re.test(node.superClass.name)) return false;
534
264
  if (context == null) return true;
535
- return isInitializedFromReact(node.superClass.name, context.sourceCode.getScope(node), "react");
265
+ return isAPIFromReact$1(node.superClass.name, context.sourceCode.getScope(node), "react");
536
266
  case node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier:
537
267
  if (!re.test(node.superClass.property.name)) return false;
538
268
  if (context == null) return true;
539
- if (node.superClass.object.type === AST_NODE_TYPES.Identifier) return isInitializedFromReact(node.superClass.object.name, context.sourceCode.getScope(node), "react");
269
+ if (node.superClass.object.type === AST_NODE_TYPES.Identifier) return isAPIFromReact$1(node.superClass.object.name, context.sourceCode.getScope(node), "react");
540
270
  return true;
541
271
  }
542
272
  }
543
273
  return false;
544
274
  }
275
+ function isClassComponentLoose(node) {
276
+ if ("superClass" in node && node.superClass != null) {
277
+ const re = /^(?:Pure)?Component$/u;
278
+ switch (true) {
279
+ case node.superClass.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.name);
280
+ case node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.property.name);
281
+ }
282
+ }
283
+ return false;
284
+ }
545
285
  /**
546
- * Check if a node is a React PureComponent
547
- * @param node The AST node to check
548
- * @returns `true` if the node is a PureComponent, `false` otherwise
286
+ * @param node The AST node to check.
287
+ * @deprecated Class components are legacy. This function exists only to support legacy rules.
549
288
  */
550
289
  function isPureComponent(node) {
551
290
  if ("superClass" in node && node.superClass != null) {
@@ -557,109 +296,144 @@ function isPureComponent(node) {
557
296
  }
558
297
  return false;
559
298
  }
560
- /**
561
- * Create a lifecycle method checker function
562
- * @param methodName The lifecycle method name
563
- * @param isStatic Whether the method is static
564
- */
565
299
  function createLifecycleChecker(methodName, isStatic = false) {
566
300
  return (node) => ast.isMethodOrProperty(node) && node.static === isStatic && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === methodName;
567
301
  }
302
+ /** @deprecated Class components are legacy. */
568
303
  const isRender = createLifecycleChecker("render");
304
+ /** @deprecated Class components are legacy. */
569
305
  const isComponentDidCatch = createLifecycleChecker("componentDidCatch");
306
+ /** @deprecated Class components are legacy. */
570
307
  const isComponentDidMount = createLifecycleChecker("componentDidMount");
308
+ /** @deprecated Class components are legacy. */
571
309
  const isComponentDidUpdate = createLifecycleChecker("componentDidUpdate");
310
+ /** @deprecated Class components are legacy. */
572
311
  const isComponentWillMount = createLifecycleChecker("componentWillMount");
312
+ /** @deprecated Class components are legacy. */
573
313
  const isComponentWillReceiveProps = createLifecycleChecker("componentWillReceiveProps");
314
+ /** @deprecated Class components are legacy. */
574
315
  const isComponentWillUnmount = createLifecycleChecker("componentWillUnmount");
316
+ /** @deprecated Class components are legacy. */
575
317
  const isComponentWillUpdate = createLifecycleChecker("componentWillUpdate");
318
+ /** @deprecated Class components are legacy. */
576
319
  const isGetChildContext = createLifecycleChecker("getChildContext");
320
+ /** @deprecated Class components are legacy. */
577
321
  const isGetInitialState = createLifecycleChecker("getInitialState");
322
+ /** @deprecated Class components are legacy. */
578
323
  const isGetSnapshotBeforeUpdate = createLifecycleChecker("getSnapshotBeforeUpdate");
324
+ /** @deprecated Class components are legacy. */
579
325
  const isShouldComponentUpdate = createLifecycleChecker("shouldComponentUpdate");
326
+ /** @deprecated Class components are legacy. */
580
327
  const isUnsafeComponentWillMount = createLifecycleChecker("UNSAFE_componentWillMount");
328
+ /** @deprecated Class components are legacy. */
581
329
  const isUnsafeComponentWillReceiveProps = createLifecycleChecker("UNSAFE_componentWillReceiveProps");
330
+ /** @deprecated Class components are legacy. */
582
331
  const isUnsafeComponentWillUpdate = createLifecycleChecker("UNSAFE_componentWillUpdate");
332
+ /** @deprecated Class components are legacy. */
583
333
  const isGetDefaultProps = createLifecycleChecker("getDefaultProps", true);
334
+ /** @deprecated Class components are legacy. */
584
335
  const isGetDerivedStateFromProps = createLifecycleChecker("getDerivedStateFromProps", true);
336
+ /** @deprecated Class components are legacy. */
585
337
  const isGetDerivedStateFromError = createLifecycleChecker("getDerivedStateFromError", true);
586
338
  /**
587
- * Check if the given node is a componentDidMount callback
588
- * @param node The node to check
589
- * @returns True if the node is a componentDidMount callback, false otherwise
339
+ * @param node The AST node to check.
340
+ * @deprecated Class components are legacy. This function exists only to support legacy rules.
590
341
  */
591
- function isComponentDidMountCallback(node) {
592
- return ast.isFunction(node) && isComponentDidMount(node.parent) && node.parent.value === node;
342
+ function isRenderMethodLike(node) {
343
+ return ast.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name.startsWith("render") && ast.isOneOf([AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.ClassExpression])(node.parent.parent);
344
+ }
345
+ function isRenderMethodCallback(node) {
346
+ const parent = node.parent;
347
+ const greatGrandparent = parent.parent?.parent;
348
+ return greatGrandparent != null && isRenderMethodLike(parent) && isClassComponentLoose(greatGrandparent);
593
349
  }
594
350
  /**
595
- * Check if the given node is a componentWillUnmount callback
596
- * @param node The node to check
597
- * @returns True if the node is a componentWillUnmount callback, false otherwise
351
+ * @param node The call expression node to check.
352
+ * @deprecated Class components are legacy. This function exists only to support legacy rules.
598
353
  */
599
- function isComponentWillUnmountCallback(node) {
600
- return ast.isFunction(node) && isComponentWillUnmount(node.parent) && node.parent.value === node;
354
+ function isThisSetStateCall(node) {
355
+ const { callee } = node;
356
+ return callee.type === AST_NODE_TYPES.MemberExpression && ast.isThisExpressionLoose(callee.object) && callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "setState";
601
357
  }
602
358
  /**
603
- * Check whether given node is a render method of a class component
604
- * @example
605
- * ```tsx
606
- * class Component extends React.Component {
607
- * renderHeader = () => <div />;
608
- * renderFooter = () => <div />;
609
- * }
610
- * ```
611
- * @param node The AST node to check
612
- * @returns `true` if node is a render function, `false` if not
359
+ * @param node The assignment expression node to check.
360
+ * @deprecated Class components are legacy. This function exists only to support legacy rules.
613
361
  */
614
- function isRenderMethodLike(node) {
615
- return ast.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name.startsWith("render") && ast.isOneOf([AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.ClassExpression])(node.parent.parent);
362
+ function isAssignmentToThisState(node) {
363
+ const { left } = node;
364
+ return left.type === AST_NODE_TYPES.MemberExpression && ast.isThisExpressionLoose(left.object) && ast.getPropertyName(left.property) === "state";
616
365
  }
366
+
367
+ //#endregion
368
+ //#region src/class-component-collector.ts
369
+ const idGen$2 = new IdGenerator("class-component:");
617
370
  /**
618
- * Check if the given node is a function within a render method of a class component
619
- *
620
- * @param node The AST node to check
621
- * @returns `true` if the node is a render function inside a class component
622
- *
623
- * @example
624
- * ```tsx
625
- * class Component extends React.Component {
626
- * renderHeader = () => <div />; // Returns true
627
- * }
628
- * ```
371
+ * @param context The rule context.
372
+ * @deprecated Class components are legacy. This function exists only to support legacy rules.
629
373
  */
630
- function isRenderMethodCallback(node) {
631
- const parent = node.parent;
632
- const greatGrandparent = parent.parent?.parent;
633
- return greatGrandparent != null && isRenderMethodLike(parent) && isClassComponent(greatGrandparent);
374
+ function getClassComponentCollector(context) {
375
+ const components = /* @__PURE__ */ new Map();
376
+ const api = { getAllComponents(node) {
377
+ return [...components.values()];
378
+ } };
379
+ const getText = (n) => context.sourceCode.getText(n);
380
+ const collect = (node) => {
381
+ if (!isClassComponent(node)) return;
382
+ const id = ast.getClassId(node);
383
+ const key = idGen$2.next();
384
+ const name = id == null ? null : ast.getFullyQualifiedName(id, getText);
385
+ components.set(key, {
386
+ id,
387
+ key,
388
+ kind: "class-component",
389
+ name,
390
+ displayName: null,
391
+ flag: 0n,
392
+ hint: 0n,
393
+ methods: [],
394
+ node
395
+ });
396
+ };
397
+ return {
398
+ api,
399
+ visitor: {
400
+ ClassDeclaration: collect,
401
+ ClassExpression: collect
402
+ }
403
+ };
634
404
  }
405
+
406
+ //#endregion
407
+ //#region src/function-component.ts
635
408
  /**
636
- * Check whether the given node is a this.setState() call
637
- * @param node The node to check
638
- * @internal
409
+ * Component flag constants
639
410
  */
640
- function isThisSetStateCall(node) {
641
- const { callee } = node;
642
- return callee.type === AST_NODE_TYPES.MemberExpression && ast.isThisExpressionLoose(callee.object) && callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "setState";
643
- }
411
+ const FunctionComponentFlag = {
412
+ None: 0n,
413
+ PureComponent: 1n << 0n,
414
+ CreateElement: 1n << 1n,
415
+ Memo: 1n << 2n,
416
+ ForwardRef: 1n << 3n
417
+ };
644
418
  /**
645
- * Check whether the given node is an assignment to this.state
646
- * @param node The node to check
419
+ * Get component flag from init path
420
+ * @param initPath The init path of the function component
421
+ * @returns The component flag
647
422
  * @internal
648
423
  */
649
- function isAssignmentToThisState(node) {
650
- const { left } = node;
651
- return left.type === AST_NODE_TYPES.MemberExpression && ast.isThisExpressionLoose(left.object) && ast.getPropertyName(left.property) === "state";
424
+ function getFunctionComponentFlagFromInitPath(initPath) {
425
+ let flag = FunctionComponentFlag.None;
426
+ if (initPath != null && ast.hasCallInFunctionInitPath("memo", initPath)) flag |= FunctionComponentFlag.Memo;
427
+ if (initPath != null && ast.hasCallInFunctionInitPath("forwardRef", initPath)) flag |= FunctionComponentFlag.ForwardRef;
428
+ return flag;
652
429
  }
653
-
654
- //#endregion
655
- //#region src/component/component-wrapper.ts
656
430
  /**
657
431
  * Check if the node is a call expression for a component wrapper
658
432
  * @param context The ESLint rule context
659
433
  * @param node The node to check
660
434
  * @returns `true` if the node is a call expression for a component wrapper
661
435
  */
662
- function isComponentWrapperCall(context, node) {
436
+ function isFunctionComponentWrapperCall(context, node) {
663
437
  if (node.type !== AST_NODE_TYPES.CallExpression) return false;
664
438
  return isMemoCall(context, node) || isForwardRefCall(context, node);
665
439
  }
@@ -669,75 +443,40 @@ function isComponentWrapperCall(context, node) {
669
443
  * @param node The node to check
670
444
  * @returns `true` if the node is a callback function passed to a component wrapper
671
445
  */
672
- /**
673
- * Check if the node is a call expression for a component wrapper loosely
674
- * @param context The ESLint rule context
675
- * @param node The node to check
676
- * @returns `true` if the node is a call expression for a component wrapper loosely
677
- */
678
- function isComponentWrapperCallLoose(context, node) {
679
- if (node.type !== AST_NODE_TYPES.CallExpression) return false;
680
- return isComponentWrapperCall(context, node) || isUseCallbackCall(node);
681
- }
682
- /**
683
- * Check if the node is a callback function passed to a component wrapper
684
- * @param context The ESLint rule context
685
- * @param node The node to check
686
- * @returns `true` if the node is a callback function passed to a component wrapper
687
- */
688
- function isComponentWrapperCallback(context, node) {
689
- if (!ast.isFunction(node)) return false;
690
- let parent = node.parent;
691
- while (ast.isTypeExpression(parent)) parent = parent.parent;
692
- if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
693
- return isComponentWrapperCall(context, parent);
694
- }
695
- /**
696
- * Check if the node is a callback function passed to a component wrapper loosely
697
- * @param context The ESLint rule context
698
- * @param node The node to check
699
- * @returns `true` if the node is a callback function passed to a component wrapper loosely
700
- */
701
- function isComponentWrapperCallbackLoose(context, node) {
446
+ function isFunctionComponentWrapperCallback(context, node) {
702
447
  if (!ast.isFunction(node)) return false;
703
448
  let parent = node.parent;
704
449
  while (ast.isTypeExpression(parent)) parent = parent.parent;
705
450
  if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
706
- return isComponentWrapperCallLoose(context, parent);
451
+ return isFunctionComponentWrapperCall(context, parent);
707
452
  }
708
-
709
- //#endregion
710
- //#region src/component/component-id.ts
711
453
  /**
712
454
  * Get function component identifier from `const Component = memo(() => {});`
713
455
  * @param context The rule context
714
- * @param node The function node to analyze
715
- * @returns The function identifier or `null` if not found
456
+ * @param node The AST node to get the function component identifier from
457
+ * @internal
716
458
  */
717
459
  function getFunctionComponentId(context, node) {
718
460
  const functionId = ast.getFunctionId(node);
719
461
  if (functionId != null) return functionId;
720
462
  let parent = node.parent;
721
463
  while (ast.isTypeExpression(parent)) parent = parent.parent;
722
- if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.id;
723
- if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent.parent) && parent.parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.parent.id;
464
+ if (parent.type === AST_NODE_TYPES.CallExpression && isFunctionComponentWrapperCall(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.id;
465
+ if (parent.type === AST_NODE_TYPES.CallExpression && isFunctionComponentWrapperCall(context, parent) && parent.parent.type === AST_NODE_TYPES.CallExpression && isFunctionComponentWrapperCall(context, parent.parent) && parent.parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.parent.id;
724
466
  return null;
725
467
  }
726
-
727
- //#endregion
728
- //#region src/component/component-name.ts
729
468
  /**
730
469
  * Check if a string matches the strict component name pattern
731
470
  * @param name The name to check
732
471
  */
733
- function isComponentName(name) {
472
+ function isFunctionComponentName(name) {
734
473
  return RE_COMPONENT_NAME.test(name);
735
474
  }
736
475
  /**
737
476
  * Check if a string matches the loose component name pattern
738
477
  * @param name The name to check
739
478
  */
740
- function isComponentNameLoose(name) {
479
+ function isFunctionComponentNameLoose(name) {
741
480
  return RE_COMPONENT_NAME_LOOSE.test(name);
742
481
  }
743
482
  /**
@@ -750,17 +489,14 @@ function isComponentNameLoose(name) {
750
489
  function isFunctionWithLooseComponentName(context, fn, allowNone = false) {
751
490
  const id = getFunctionComponentId(context, fn);
752
491
  if (id == null) return allowNone;
753
- if (id.type === AST_NODE_TYPES.Identifier) return isComponentNameLoose(id.name);
754
- if (id.type === AST_NODE_TYPES.MemberExpression && id.property.type === AST_NODE_TYPES.Identifier) return isComponentNameLoose(id.property.name);
492
+ if (id.type === AST_NODE_TYPES.Identifier) return isFunctionComponentNameLoose(id.name);
493
+ if (id.type === AST_NODE_TYPES.MemberExpression && id.property.type === AST_NODE_TYPES.Identifier) return isFunctionComponentNameLoose(id.property.name);
755
494
  return false;
756
495
  }
757
-
758
- //#endregion
759
- //#region src/component/component-detection.ts
760
496
  /**
761
497
  * Hints for component collector
762
498
  */
763
- const ComponentDetectionHint = {
499
+ const FunctionComponentDetectionHint = {
764
500
  ...JsxDetectionHint,
765
501
  DoNotIncludeFunctionDefinedAsClassMethod: 1n << 11n,
766
502
  DoNotIncludeFunctionDefinedAsClassProperty: 1n << 12n,
@@ -774,7 +510,7 @@ const ComponentDetectionHint = {
774
510
  /**
775
511
  * Default component detection hint
776
512
  */
777
- const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | ComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | ComponentDetectionHint.DoNotIncludeJsxWithNumberValue | ComponentDetectionHint.DoNotIncludeJsxWithStringValue | ComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | ComponentDetectionHint.RequireAllArrayElementsToBeJsx | ComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | ComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx;
513
+ const DEFAULT_COMPONENT_DETECTION_HINT = 0n | FunctionComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | FunctionComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | FunctionComponentDetectionHint.DoNotIncludeJsxWithNumberValue | FunctionComponentDetectionHint.DoNotIncludeJsxWithStringValue | FunctionComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback | FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | FunctionComponentDetectionHint.RequireAllArrayElementsToBeJsx | FunctionComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | FunctionComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx;
778
514
  /**
779
515
  * Determine if a function node represents a valid React component definition
780
516
  *
@@ -783,7 +519,7 @@ const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.DoNotInclud
783
519
  * @param hint Component detection hints (bit flags) to customize detection logic
784
520
  * @returns `true` if the node is considered a component definition
785
521
  */
786
- function isComponentDefinition(context, node, hint) {
522
+ function isFunctionComponentDefinition(context, node, hint) {
787
523
  if (!isFunctionWithLooseComponentName(context, node, true)) return false;
788
524
  switch (true) {
789
525
  case node.parent.type === AST_NODE_TYPES.CallExpression && isCreateElementCall(context, node.parent) && node.parent.arguments.slice(2).some((arg) => arg === node): return false;
@@ -793,28 +529,28 @@ function isComponentDefinition(context, node, hint) {
793
529
  while (ast.isTypeExpression(parent)) parent = parent.parent;
794
530
  switch (true) {
795
531
  case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.Property && parent.parent.type === AST_NODE_TYPES.ObjectExpression:
796
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsObjectMethod) return false;
532
+ if (hint & FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsObjectMethod) return false;
797
533
  break;
798
534
  case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.MethodDefinition:
799
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsClassMethod) return false;
535
+ if (hint & FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsClassMethod) return false;
800
536
  break;
801
537
  case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.Property:
802
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsClassProperty) return false;
538
+ if (hint & FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsClassProperty) return false;
803
539
  break;
804
540
  case parent.type === AST_NODE_TYPES.ArrayPattern:
805
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement) return false;
541
+ if (hint & FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement) return false;
806
542
  break;
807
543
  case parent.type === AST_NODE_TYPES.ArrayExpression:
808
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement) return false;
544
+ if (hint & FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement) return false;
809
545
  break;
810
546
  case parent.type === AST_NODE_TYPES.CallExpression && parent.callee.type === AST_NODE_TYPES.MemberExpression && parent.callee.property.type === AST_NODE_TYPES.Identifier && parent.callee.property.name === "map":
811
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback) return false;
547
+ if (hint & FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback) return false;
812
548
  break;
813
549
  case parent.type === AST_NODE_TYPES.CallExpression && parent.callee.type === AST_NODE_TYPES.MemberExpression && parent.callee.property.type === AST_NODE_TYPES.Identifier && parent.callee.property.name === "flatMap":
814
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback) return false;
550
+ if (hint & FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback) return false;
815
551
  break;
816
- case parent.type === AST_NODE_TYPES.CallExpression && ast.getFunctionId(node) == null && !isComponentWrapperCallLoose(context, parent) && !isCreateElementCall(context, parent):
817
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback) return false;
552
+ case parent.type === AST_NODE_TYPES.CallExpression && ast.getFunctionId(node) == null && !isFunctionComponentWrapperCall(context, parent) && !isCreateElementCall(context, parent):
553
+ if (hint & FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback) return false;
818
554
  break;
819
555
  }
820
556
  const significantParent = ast.findParent(node, ast.isOneOf([
@@ -830,31 +566,127 @@ function isComponentDefinition(context, node, hint) {
830
566
  }
831
567
 
832
568
  //#endregion
833
- //#region src/component/component-flag.ts
569
+ //#region src/hook.ts
570
+ const REACT_BUILTIN_HOOK_NAMES = [
571
+ "use",
572
+ "useActionState",
573
+ "useCallback",
574
+ "useContext",
575
+ "useDebugValue",
576
+ "useDeferredValue",
577
+ "useEffect",
578
+ "useFormStatus",
579
+ "useId",
580
+ "useImperativeHandle",
581
+ "useInsertionEffect",
582
+ "useLayoutEffect",
583
+ "useMemo",
584
+ "useOptimistic",
585
+ "useReducer",
586
+ "useRef",
587
+ "useState",
588
+ "useSyncExternalStore",
589
+ "useTransition"
590
+ ];
834
591
  /**
835
- * Component flag constants
592
+ * Catch all identifiers that begin with "use" followed by an uppercase Latin
593
+ * character to exclude identifiers like "user".
594
+ * @param name The name of the identifier to check.
595
+ * @see https://github.com/facebook/react/blob/1d6c8168db1d82713202e842df3167787ffa00ed/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts#L16
836
596
  */
837
- const ComponentFlag = {
838
- None: 0n,
839
- PureComponent: 1n << 0n,
840
- CreateElement: 1n << 1n,
841
- Memo: 1n << 2n,
842
- ForwardRef: 1n << 3n
843
- };
597
+ function isHookName(name) {
598
+ return name === "use" || /^use[A-Z0-9]/.test(name);
599
+ }
844
600
  /**
845
- * Get component flag from init path
846
- * @param initPath The init path of the function component
847
- * @returns The component flag
601
+ * Checks if the given node is a hook identifier
602
+ * @param id The AST node to check
603
+ * @returns `true` if the node is a hook identifier or member expression with hook name, `false` otherwise
848
604
  */
849
- function getComponentFlagFromInitPath(initPath) {
850
- let flag = ComponentFlag.None;
851
- if (initPath != null && ast.hasCallInFunctionInitPath("memo", initPath)) flag |= ComponentFlag.Memo;
852
- if (initPath != null && ast.hasCallInFunctionInitPath("forwardRef", initPath)) flag |= ComponentFlag.ForwardRef;
853
- return flag;
605
+ function isHookId(id) {
606
+ switch (id.type) {
607
+ case AST_NODE_TYPES.Identifier: return isHookName(id.name);
608
+ case AST_NODE_TYPES.MemberExpression: return "name" in id.property && isHookName(id.property.name);
609
+ default: return false;
610
+ }
611
+ }
612
+ /**
613
+ * Determine if a function node is a React Hook based on its name.
614
+ * @param node The function node to check
615
+ * @returns True if the function is a React Hook, false otherwise
616
+ */
617
+ function isHookDefinition(node) {
618
+ if (node == null) return false;
619
+ const id = ast.getFunctionId(node);
620
+ switch (id?.type) {
621
+ case AST_NODE_TYPES.Identifier: return isHookName(id.name);
622
+ case AST_NODE_TYPES.MemberExpression: return "name" in id.property && isHookName(id.property.name);
623
+ default: return false;
624
+ }
625
+ }
626
+ /**
627
+ * Check if the given node is a React Hook call by its name.
628
+ * @param node The node to check.
629
+ * @returns `true` if the node is a React Hook call, `false` otherwise.
630
+ */
631
+ function isHookCall(node) {
632
+ if (node == null) return false;
633
+ if (node.type !== AST_NODE_TYPES.CallExpression) return false;
634
+ if (node.callee.type === AST_NODE_TYPES.Identifier) return isHookName(node.callee.name);
635
+ if (node.callee.type === AST_NODE_TYPES.MemberExpression) return node.callee.property.type === AST_NODE_TYPES.Identifier && isHookName(node.callee.property.name);
636
+ return false;
637
+ }
638
+ /**
639
+ * Detect useEffect calls and variations (useLayoutEffect, etc.) using a regex pattern
640
+ * @param node The AST node to check
641
+ * @param additionalEffectHooks Regex pattern matching custom hooks that should be treated as effect hooks
642
+ * @returns True if the node is a useEffect-like call
643
+ */
644
+ function isUseEffectLikeCall(node, additionalEffectHooks = { test: constFalse }) {
645
+ if (node == null) return false;
646
+ if (node.type !== AST_NODE_TYPES.CallExpression) return false;
647
+ return [/^use\w*Effect$/u, additionalEffectHooks].some((regexp) => {
648
+ if (node.callee.type === AST_NODE_TYPES.Identifier) return regexp.test(node.callee.name);
649
+ if (node.callee.type === AST_NODE_TYPES.MemberExpression) return node.callee.property.type === AST_NODE_TYPES.Identifier && regexp.test(node.callee.property.name);
650
+ return false;
651
+ });
652
+ }
653
+ /**
654
+ * Detect useState calls and variations using a regex pattern
655
+ * @param node The AST node to check
656
+ * @param additionalStateHooks Regex pattern matching custom hooks that should be treated as state hooks
657
+ * @returns True if the node is a useState-like call
658
+ */
659
+ function isUseStateLikeCall(node, additionalStateHooks = { test: constFalse }) {
660
+ if (node == null) return false;
661
+ if (node.type !== AST_NODE_TYPES.CallExpression) return false;
662
+ switch (true) {
663
+ case node.callee.type === AST_NODE_TYPES.Identifier: return node.callee.name === "useState" || additionalStateHooks.test(node.callee.name);
664
+ case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier: return ast.getPropertyName(node.callee.property) === "useState" || additionalStateHooks.test(node.callee.property.name);
665
+ }
666
+ return false;
667
+ }
668
+ /**
669
+ * Determine if a node is the setup function passed to a useEffect-like hook
670
+ * @param node The AST node to check
671
+ */
672
+ function isUseEffectSetupCallback(node) {
673
+ if (node == null) return false;
674
+ return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.arguments.at(0) === node && isUseEffectLikeCall(node.parent);
675
+ }
676
+ /**
677
+ * Determine if a node is the cleanup function returned by a useEffect-like hook's setup function
678
+ * @param node The AST node to check
679
+ */
680
+ function isUseEffectCleanupCallback(node) {
681
+ if (node == null) return false;
682
+ const returnStatement = ast.findParent(node, ast.is(AST_NODE_TYPES.ReturnStatement));
683
+ const enclosingFunction = ast.findParent(node, ast.isFunction);
684
+ if (enclosingFunction !== ast.findParent(returnStatement, ast.isFunction)) return false;
685
+ return isUseEffectSetupCallback(enclosingFunction);
854
686
  }
855
687
 
856
688
  //#endregion
857
- //#region src/component/component-collector.ts
689
+ //#region src/function-component-collector.ts
858
690
  const idGen$1 = new IdGenerator("component:");
859
691
  /**
860
692
  * Get an api and visitor object for the rule to collect function components
@@ -862,7 +694,7 @@ const idGen$1 = new IdGenerator("component:");
862
694
  * @param options The options to use
863
695
  * @returns The api and visitor of the collector
864
696
  */
865
- function getComponentCollector(context, options = {}) {
697
+ function getFunctionComponentCollector(context, options = {}) {
866
698
  const { collectDisplayName = false, hint = DEFAULT_COMPONENT_DETECTION_HINT } = options;
867
699
  const functionEntries = [];
868
700
  const components = /* @__PURE__ */ new Map();
@@ -884,18 +716,18 @@ function getComponentCollector(context, options = {}) {
884
716
  name,
885
717
  directives,
886
718
  displayName: null,
887
- flag: getComponentFlagFromInitPath(initPath),
719
+ flag: getFunctionComponentFlagFromInitPath(initPath),
888
720
  hint,
889
721
  hookCalls: [],
890
722
  initPath,
891
- isComponentDefinition: isComponentDefinition(context, node, hint),
723
+ isFunctionComponentDefinition: isFunctionComponentDefinition(context, node, hint),
892
724
  isExportDefault,
893
725
  isExportDefaultDeclaration,
894
726
  node,
895
727
  rets: []
896
728
  };
897
729
  functionEntries.push(entry);
898
- if (!entry.isComponentDefinition || !isFunctionWithLooseComponentName(context, node, false)) return;
730
+ if (!entry.isFunctionComponentDefinition || !isFunctionWithLooseComponentName(context, node, false)) return;
899
731
  if (directives.some((d) => d.directive === "use memo" || d.directive === "use no memo")) components.set(entry.key, entry);
900
732
  };
901
733
  const onFunctionExit = () => {
@@ -914,7 +746,7 @@ function getComponentCollector(context, options = {}) {
914
746
  const { body } = entry.node;
915
747
  if (body.type === AST_NODE_TYPES.BlockStatement) return;
916
748
  entry.rets.push(body);
917
- if (!entry.isComponentDefinition) return;
749
+ if (!entry.isFunctionComponentDefinition) return;
918
750
  if (!components.has(entry.key) && !isJsxLike(context, body, hint)) return;
919
751
  components.set(entry.key, entry);
920
752
  },
@@ -931,14 +763,14 @@ function getComponentCollector(context, options = {}) {
931
763
  const entry = getCurrentEntry();
932
764
  if (entry == null) return;
933
765
  entry.hookCalls.push(node);
934
- if (!entry.isComponentDefinition) return;
766
+ if (!entry.isFunctionComponentDefinition) return;
935
767
  components.set(entry.key, entry);
936
768
  },
937
769
  ReturnStatement(node) {
938
770
  const entry = getCurrentEntry();
939
771
  if (entry == null) return;
940
772
  entry.rets.push(node.argument);
941
- if (!entry.isComponentDefinition) return;
773
+ if (!entry.isFunctionComponentDefinition) return;
942
774
  const { argument } = node;
943
775
  if (!components.has(entry.key) && !isJsxLike(context, argument, hint)) return;
944
776
  components.set(entry.key, entry);
@@ -948,45 +780,215 @@ function getComponentCollector(context, options = {}) {
948
780
  }
949
781
 
950
782
  //#endregion
951
- //#region src/component/component-collector-legacy.ts
952
- const idGen = new IdGenerator("class-component:");
783
+ //#region src/hook-collector.ts
784
+ const idGen = new IdGenerator("hook:");
953
785
  /**
954
- * Get an api and visitor object for the rule to collect class components
786
+ * Get an api and visitor object for the rule to collect hooks
955
787
  * @param context The ESLint rule context
956
788
  * @returns The api and visitor of the collector
957
789
  */
958
- function getComponentCollectorLegacy(context) {
959
- const components = /* @__PURE__ */ new Map();
960
- const api = { getAllComponents(node) {
961
- return [...components.values()];
962
- } };
790
+ function getHookCollector(context) {
791
+ const hooks = /* @__PURE__ */ new Map();
792
+ const functionEntries = [];
963
793
  const getText = (n) => context.sourceCode.getText(n);
964
- const collect = (node) => {
965
- if (!isClassComponent(node)) return;
966
- const id = ast.getClassId(node);
794
+ const getCurrentEntry = () => functionEntries.at(-1) ?? null;
795
+ const onFunctionEnter = (node) => {
796
+ const id = ast.getFunctionId(node);
967
797
  const key = idGen.next();
968
- const name = id == null ? null : ast.getFullyQualifiedName(id, getText);
969
- const flag = isPureComponent(node) ? ComponentFlag.PureComponent : ComponentFlag.None;
970
- components.set(key, {
798
+ const entry = {
971
799
  id,
972
800
  key,
973
- kind: "class-component",
974
- name,
975
- displayName: null,
976
- flag,
801
+ kind: "hook",
802
+ name: id == null ? null : ast.getFullyQualifiedName(id, getText),
803
+ directives: [],
804
+ flag: 0n,
977
805
  hint: 0n,
978
- methods: [],
979
- node
980
- });
806
+ hookCalls: [],
807
+ isHookDefinition: id != null && isHookId(id),
808
+ node,
809
+ rets: []
810
+ };
811
+ functionEntries.push(entry);
812
+ if (!entry.isHookDefinition) return;
813
+ hooks.set(key, entry);
814
+ };
815
+ const onFunctionExit = () => {
816
+ functionEntries.pop();
981
817
  };
982
818
  return {
983
- api,
819
+ api: { getAllHooks(node) {
820
+ return [...hooks.values()];
821
+ } },
984
822
  visitor: {
985
- ClassDeclaration: collect,
986
- ClassExpression: collect
823
+ ":function": onFunctionEnter,
824
+ ":function:exit": onFunctionExit,
825
+ "ArrowFunctionExpression[body.type!='BlockStatement']"() {
826
+ const entry = getCurrentEntry();
827
+ if (entry == null) return;
828
+ const { body } = entry.node;
829
+ if (body.type === AST_NODE_TYPES$1.BlockStatement) return;
830
+ entry.rets.push(body);
831
+ },
832
+ CallExpression(node) {
833
+ if (!isHookCall(node)) return;
834
+ const entry = getCurrentEntry();
835
+ if (entry == null) return;
836
+ entry.hookCalls.push(node);
837
+ },
838
+ ReturnStatement(node) {
839
+ const entry = getCurrentEntry();
840
+ if (entry == null) return;
841
+ entry.rets.push(node.argument);
842
+ }
987
843
  }
988
844
  };
989
845
  }
990
846
 
991
847
  //#endregion
992
- export { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, REACT_BUILTIN_HOOK_NAMES, findImportSource, getComponentCollector, getComponentCollectorLegacy, getComponentFlagFromInitPath, getFunctionComponentId, getHookCollector, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidMountCallback, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUnmountCallback, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isComponentWrapperCallback, isComponentWrapperCallbackLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isForwardRef, isForwardRefCall, isFunctionWithLooseComponentName, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isHookCall, isHookDefinition, isHookId, isHookName, isInitializedFromReact, isInitializedFromReactNative, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isRender, isRenderMethodCallback, isRenderMethodLike, isShouldComponentUpdate, isThisSetStateCall, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectCleanupCallback, isUseEffectLikeCall, isUseEffectSetupCallback, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseStateLikeCall, isUseSyncExternalStoreCall, isUseTransitionCall };
848
+ //#region src/type-is.ts
849
+ function isFlagSet(allFlags, flag) {
850
+ return (allFlags & flag) !== 0;
851
+ }
852
+ function isFlagSetOnObject(obj, flag) {
853
+ return isFlagSet(obj.flags, flag);
854
+ }
855
+ const isTypeFlagSet = isFlagSetOnObject;
856
+ function isBooleanLiteralType(type) {
857
+ return isTypeFlagSet(type, ts.TypeFlags.BooleanLiteral);
858
+ }
859
+ /** @internal */
860
+ const isFalseLiteralType = (type) => isBooleanLiteralType(type) && type.intrinsicName === "false";
861
+ /** @internal */
862
+ const isTrueLiteralType = (type) => isBooleanLiteralType(type) && type.intrinsicName === "true";
863
+ /** @internal */
864
+ const isAnyType = (type) => isTypeFlagSet(type, ts.TypeFlags.TypeParameter | ts.TypeFlags.Any);
865
+ /** @internal */
866
+ const isBigIntType = (type) => isTypeFlagSet(type, ts.TypeFlags.BigIntLike);
867
+ /** @internal */
868
+ const isBooleanType = (type) => isTypeFlagSet(type, ts.TypeFlags.BooleanLike);
869
+ /** @internal */
870
+ const isEnumType = (type) => isTypeFlagSet(type, ts.TypeFlags.EnumLike);
871
+ /** @internal */
872
+ const isFalsyBigIntType = (type) => type.isLiteral() && isMatching({ value: { base10Value: "0" } }, type);
873
+ /** @internal */
874
+ const isFalsyNumberType = (type) => type.isNumberLiteral() && type.value === 0;
875
+ /** @internal */
876
+ const isFalsyStringType = (type) => type.isStringLiteral() && type.value === "";
877
+ /** @internal */
878
+ const isNeverType = (type) => isTypeFlagSet(type, ts.TypeFlags.Never);
879
+ /** @internal */
880
+ const isNullishType = (type) => isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike);
881
+ /** @internal */
882
+ const isNumberType = (type) => isTypeFlagSet(type, ts.TypeFlags.NumberLike);
883
+ /** @internal */
884
+ const isObjectType = (type) => !isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike | ts.TypeFlags.BooleanLike | ts.TypeFlags.StringLike | ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike | ts.TypeFlags.TypeParameter | ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never);
885
+ /** @internal */
886
+ const isStringType = (type) => isTypeFlagSet(type, ts.TypeFlags.StringLike);
887
+ /** @internal */
888
+ const isTruthyBigIntType = (type) => type.isLiteral() && isMatching({ value: { base10Value: P.not("0") } }, type);
889
+ /** @internal */
890
+ const isTruthyNumberType = (type) => type.isNumberLiteral() && type.value !== 0;
891
+ /** @internal */
892
+ const isTruthyStringType = (type) => type.isStringLiteral() && type.value !== "";
893
+ /** @internal */
894
+ const isUnknownType = (type) => isTypeFlagSet(type, ts.TypeFlags.Unknown);
895
+
896
+ //#endregion
897
+ //#region src/type-name.ts
898
+ /**
899
+ * An enhanced version of getFullyQualifiedName that handles cases that original function does not handle
900
+ * @param checker TypeScript type checker
901
+ * @param symbol Symbol to get fully qualified name for
902
+ * @returns Fully qualified name of the symbol
903
+ */
904
+ function getFullyQualifiedNameEx(checker, symbol) {
905
+ let name = symbol.name;
906
+ let parent = symbol.declarations?.at(0)?.parent;
907
+ if (parent == null) return checker.getFullyQualifiedName(symbol);
908
+ while (parent.kind !== ts.SyntaxKind.SourceFile) {
909
+ switch (true) {
910
+ case ts.isInterfaceDeclaration(parent):
911
+ case ts.isTypeAliasDeclaration(parent):
912
+ case ts.isEnumDeclaration(parent):
913
+ case ts.isModuleDeclaration(parent):
914
+ case ts.isNamespaceImport(parent):
915
+ case ts.isNamespaceExport(parent):
916
+ case ts.isNamespaceExportDeclaration(parent):
917
+ name = `${parent.name.text}.${name}`;
918
+ break;
919
+ case ts.isPropertySignature(parent) && ts.isIdentifier(parent.name):
920
+ case ts.isPropertyDeclaration(parent) && ts.isIdentifier(parent.name):
921
+ case ts.isMethodDeclaration(parent) && ts.isIdentifier(parent.name):
922
+ case ts.isMethodSignature(parent) && ts.isIdentifier(parent.name):
923
+ case ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name):
924
+ name = `${parent.name.text}.${name}`;
925
+ break;
926
+ case ts.isFunctionDeclaration(parent) && parent.name != null:
927
+ case ts.isClassExpression(parent) && parent.name != null:
928
+ case ts.isClassDeclaration(parent) && parent.name != null:
929
+ name = `${parent.name.text}.${name}`;
930
+ break;
931
+ case ts.isEnumMember(parent):
932
+ name = `${parent.name.getText()}.${name}`;
933
+ break;
934
+ case ts.isTypeLiteralNode(parent):
935
+ case ts.isMappedTypeNode(parent):
936
+ case ts.isObjectLiteralExpression(parent):
937
+ case ts.isIntersectionTypeNode(parent):
938
+ case ts.isUnionTypeNode(parent): break;
939
+ default: break;
940
+ }
941
+ parent = parent.parent;
942
+ }
943
+ const namespace = parent.getSourceFile().statements.find((n) => ts.isNamespaceExportDeclaration(n))?.name.text;
944
+ if (namespace == null) return name;
945
+ if (name.startsWith(`${namespace}.`)) return name;
946
+ return `${namespace}.${name}`;
947
+ }
948
+
949
+ //#endregion
950
+ //#region src/type-variant.ts
951
+ /**
952
+ * Ported from https://github.com/typescript-eslint/typescript-eslint/blob/eb736bbfc22554694400e6a4f97051d845d32e0b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts#L826 with some enhancements
953
+ * Get the variants of an array of types.
954
+ * @param types The types to get the variants of
955
+ * @returns The variants of the types
956
+ * @internal
957
+ */
958
+ function getTypeVariants(types) {
959
+ const variants = /* @__PURE__ */ new Set();
960
+ if (types.some(isUnknownType)) {
961
+ variants.add("unknown");
962
+ return variants;
963
+ }
964
+ if (types.some(isNullishType)) variants.add("nullish");
965
+ const booleans = types.filter(isBooleanType);
966
+ const boolean0 = booleans[0];
967
+ if (booleans.length === 1 && boolean0 != null) {
968
+ if (isFalseLiteralType(boolean0)) variants.add("falsy boolean");
969
+ else if (isTrueLiteralType(boolean0)) variants.add("truthy boolean");
970
+ } else if (booleans.length === 2) variants.add("boolean");
971
+ const strings = types.filter(isStringType);
972
+ if (strings.length > 0) {
973
+ const evaluated = match(strings).when((types) => types.every(isTruthyStringType), () => "truthy string").when((types) => types.every(isFalsyStringType), () => "falsy string").otherwise(() => "string");
974
+ variants.add(evaluated);
975
+ }
976
+ const bigints = types.filter(isBigIntType);
977
+ if (bigints.length > 0) {
978
+ const evaluated = match(bigints).when((types) => types.every(isTruthyBigIntType), () => "truthy bigint").when((types) => types.every(isFalsyBigIntType), () => "falsy bigint").otherwise(() => "bigint");
979
+ variants.add(evaluated);
980
+ }
981
+ const numbers = types.filter(isNumberType);
982
+ if (numbers.length > 0) {
983
+ const evaluated = match(numbers).when((types) => types.every(isTruthyNumberType), () => "truthy number").when((types) => types.every(isFalsyNumberType), () => "falsy number").otherwise(() => "number");
984
+ variants.add(evaluated);
985
+ }
986
+ if (types.some(isEnumType)) variants.add("enum");
987
+ if (types.some(isObjectType)) variants.add("object");
988
+ if (types.some(isAnyType)) variants.add("any");
989
+ if (types.some(isNeverType)) variants.add("never");
990
+ return variants;
991
+ }
992
+
993
+ //#endregion
994
+ export { DEFAULT_COMPONENT_DETECTION_HINT, FunctionComponentDetectionHint, FunctionComponentFlag, REACT_BUILTIN_HOOK_NAMES, getClassComponentCollector, getFullyQualifiedNameEx, getFunctionComponentCollector, getFunctionComponentFlagFromInitPath, getFunctionComponentId, getHookCollector, getTypeVariants, isAPI, isAPICall, isAPIFromReact, isAPIFromReactNative, isAnyType, isAssignmentToThisState, isBigIntType, isBooleanLiteralType, isBooleanType, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isClassComponentLoose, isCloneElement, isCloneElementCall, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUpdate, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isEnumType, isFalseLiteralType, isFalsyBigIntType, isFalsyNumberType, isFalsyStringType, isForwardRef, isForwardRefCall, isFunctionComponentDefinition, isFunctionComponentName, isFunctionComponentNameLoose, isFunctionComponentWrapperCall, isFunctionComponentWrapperCallback, isFunctionWithLooseComponentName, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isHookCall, isHookDefinition, isHookId, isHookName, isLazy, isLazyCall, isMemo, isMemoCall, isNeverType, isNullishType, isNumberType, isObjectType, isPureComponent, isRender, isRenderMethodCallback, isRenderMethodLike, isShouldComponentUpdate, isStringType, isThisSetStateCall, isTrueLiteralType, isTruthyBigIntType, isTruthyNumberType, isTruthyStringType, isUnknownType, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUse, isUseActionState, isUseActionStateCall, isUseCall, isUseCallback, isUseCallbackCall, isUseContext, isUseContextCall, isUseDebugValue, isUseDebugValueCall, isUseDeferredValue, isUseDeferredValueCall, isUseEffect, isUseEffectCall, isUseEffectCleanupCallback, isUseEffectLikeCall, isUseEffectSetupCallback, isUseFormStatus, isUseFormStatusCall, isUseId, isUseIdCall, isUseImperativeHandle, isUseImperativeHandleCall, isUseInsertionEffect, isUseInsertionEffectCall, isUseLayoutEffect, isUseLayoutEffectCall, isUseMemo, isUseMemoCall, isUseOptimistic, isUseOptimisticCall, isUseReducer, isUseReducerCall, isUseRef, isUseRefCall, isUseState, isUseStateCall, isUseStateLikeCall, isUseSyncExternalStore, isUseSyncExternalStoreCall, isUseTransition, isUseTransitionCall };