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