@eslint-react/core 2.0.0-next.17 → 2.0.0-next.170

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 +533 -268
  2. package/dist/index.js +1161 -877
  3. package/package.json +14 -14
package/dist/index.js CHANGED
@@ -1,958 +1,1242 @@
1
- import { AST_NODE_TYPES } from '@typescript-eslint/types';
2
- import * as AST14 from '@eslint-react/ast';
3
- import { flip, dual, constFalse, identity, constTrue, _ } from '@eslint-react/eff';
4
- import { RegExp, Selector } from '@eslint-react/kit';
5
- import { coerceSettings, DEFAULT_ESLINT_REACT_SETTINGS, getId } from '@eslint-react/shared';
6
- import * as VAR3 from '@eslint-react/var';
7
- import { isMatching, P, match } from 'ts-pattern';
8
- import birecord from 'birecord';
9
-
10
- // src/component/component-children.ts
1
+ import { AST_NODE_TYPES } from "@typescript-eslint/types";
2
+ import * as AST from "@eslint-react/ast";
3
+ import { constFalse, constTrue, dual, flip, identity, unit } from "@eslint-react/eff";
4
+ import { RegExp, Selector } from "@eslint-react/kit";
5
+ import { DEFAULT_ESLINT_REACT_SETTINGS, coerceSettings, getId } from "@eslint-react/shared";
6
+ import * as VAR from "@eslint-react/var";
7
+ import { P, isMatching, match } from "ts-pattern";
8
+ import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
9
+ import birecord from "birecord";
10
+ import { isFalseLiteralType, isTrueLiteralType, isTypeFlagSet } from "ts-api-utils";
11
+ import ts from "typescript";
12
+
13
+ //#region src/utils/is-react-api.ts
11
14
  function isReactAPI(api) {
12
- const func = (context, node) => {
13
- if (node == null) return false;
14
- const getText = (n) => context.sourceCode.getText(n);
15
- const name = AST14.toStringFormat(node, getText);
16
- if (name === api) return true;
17
- if (name.substring(name.indexOf(".") + 1) === api) return true;
18
- return false;
19
- };
20
- return dual(2, func);
15
+ const func = (context, node) => {
16
+ if (node == null) return false;
17
+ const getText = (n) => context.sourceCode.getText(n);
18
+ const name = AST.toStringFormat(node, getText);
19
+ if (name === api) return true;
20
+ if (name.substring(name.indexOf(".") + 1) === api) return true;
21
+ return false;
22
+ };
23
+ return dual(2, func);
21
24
  }
22
25
  function isReactAPICall(api) {
23
- const func = (context, node) => {
24
- if (node == null) return false;
25
- if (node.type !== AST_NODE_TYPES.CallExpression) return false;
26
- return isReactAPI(api)(context, node.callee);
27
- };
28
- return dual(2, func);
29
- }
30
- var isCaptureOwnerStack = isReactAPI("captureOwnerStack");
31
- var isChildrenCount = isReactAPI("Children.count");
32
- var isChildrenForEach = isReactAPI("Children.forEach");
33
- var isChildrenMap = isReactAPI("Children.map");
34
- var isChildrenOnly = isReactAPI("Children.only");
35
- var isChildrenToArray = isReactAPI("Children.toArray");
36
- var isCloneElement = isReactAPI("cloneElement");
37
- var isCreateContext = isReactAPI("createContext");
38
- var isCreateElement = isReactAPI("createElement");
39
- var isCreateRef = isReactAPI("createRef");
40
- var isForwardRef = isReactAPI("forwardRef");
41
- var isMemo = isReactAPI("memo");
42
- var isLazy = isReactAPI("lazy");
43
- var isCaptureOwnerStackCall = isReactAPICall("captureOwnerStack");
44
- var isChildrenCountCall = isReactAPICall("Children.count");
45
- var isChildrenForEachCall = isReactAPICall("Children.forEach");
46
- var isChildrenMapCall = isReactAPICall("Children.map");
47
- var isChildrenOnlyCall = isReactAPICall("Children.only");
48
- var isChildrenToArrayCall = isReactAPICall("Children.toArray");
49
- var isCloneElementCall = isReactAPICall("cloneElement");
50
- var isCreateContextCall = isReactAPICall("createContext");
51
- var isCreateElementCall = isReactAPICall("createElement");
52
- var isCreateRefCall = isReactAPICall("createRef");
53
- var isForwardRefCall = isReactAPICall("forwardRef");
54
- var isMemoCall = isReactAPICall("memo");
55
- var isLazyCall = isReactAPICall("lazy");
56
-
57
- // src/component/component-children.ts
26
+ const func = (context, node) => {
27
+ if (node == null) return false;
28
+ if (node.type !== AST_NODE_TYPES.CallExpression) return false;
29
+ return isReactAPI(api)(context, node.callee);
30
+ };
31
+ return dual(2, func);
32
+ }
33
+ const isCaptureOwnerStack = isReactAPI("captureOwnerStack");
34
+ const isChildrenCount = isReactAPI("Children.count");
35
+ const isChildrenForEach = isReactAPI("Children.forEach");
36
+ const isChildrenMap = isReactAPI("Children.map");
37
+ const isChildrenOnly = isReactAPI("Children.only");
38
+ const isChildrenToArray = isReactAPI("Children.toArray");
39
+ const isCloneElement = isReactAPI("cloneElement");
40
+ const isCreateContext = isReactAPI("createContext");
41
+ const isCreateElement = isReactAPI("createElement");
42
+ const isCreateRef = isReactAPI("createRef");
43
+ const isForwardRef = isReactAPI("forwardRef");
44
+ const isMemo = isReactAPI("memo");
45
+ const isLazy = isReactAPI("lazy");
46
+ const isCaptureOwnerStackCall = isReactAPICall("captureOwnerStack");
47
+ const isChildrenCountCall = isReactAPICall("Children.count");
48
+ const isChildrenForEachCall = isReactAPICall("Children.forEach");
49
+ const isChildrenMapCall = isReactAPICall("Children.map");
50
+ const isChildrenOnlyCall = isReactAPICall("Children.only");
51
+ const isChildrenToArrayCall = isReactAPICall("Children.toArray");
52
+ const isCloneElementCall = isReactAPICall("cloneElement");
53
+ const isCreateContextCall = isReactAPICall("createContext");
54
+ const isCreateElementCall = isReactAPICall("createElement");
55
+ const isCreateRefCall = isReactAPICall("createRef");
56
+ const isForwardRefCall = isReactAPICall("forwardRef");
57
+ const isMemoCall = isReactAPICall("memo");
58
+ const isLazyCall = isReactAPICall("lazy");
59
+
60
+ //#endregion
61
+ //#region src/component/component-children.ts
62
+ /**
63
+ * Determines whether inside `createElement`'s children.
64
+ * @param context The rule context
65
+ * @param node The AST node to check
66
+ * @returns `true` if the node is inside createElement's children
67
+ */
58
68
  function isChildrenOfCreateElement(context, node) {
59
- const parent = node.parent;
60
- if (parent == null || parent.type !== AST_NODE_TYPES.CallExpression) return false;
61
- if (!isCreateElementCall(context, parent)) return false;
62
- return parent.arguments.slice(2).some((arg) => arg === node);
69
+ const parent = node.parent;
70
+ if (parent == null || parent.type !== AST_NODE_TYPES.CallExpression) return false;
71
+ if (!isCreateElementCall(context, parent)) return false;
72
+ return parent.arguments.slice(2).some((arg) => arg === node);
63
73
  }
74
+
75
+ //#endregion
76
+ //#region src/utils/get-instance-id.ts
77
+ /**
78
+ * Gets the identifier node of an instance based on AST node relationships.
79
+ * Used for tracking where hooks or components are being assigned in the code.
80
+ * @param node The current AST node to evaluate
81
+ * @param prev The previous AST node in the traversal (used for context)
82
+ * @internal
83
+ */
64
84
  function getInstanceId(node, prev) {
65
- switch (true) {
66
- case (node.type === AST_NODE_TYPES.VariableDeclarator && node.init === prev):
67
- return node.id;
68
- case (node.type === AST_NODE_TYPES.AssignmentExpression && node.right === prev):
69
- return node.left;
70
- case (node.type === AST_NODE_TYPES.PropertyDefinition && node.value === prev):
71
- return node.key;
72
- case (node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node):
73
- return _;
74
- default:
75
- return getInstanceId(node.parent, node);
76
- }
85
+ switch (true) {
86
+ case node.type === AST_NODE_TYPES.VariableDeclarator && node.init === prev: return node.id;
87
+ case node.type === AST_NODE_TYPES.AssignmentExpression && node.right === prev: return node.left;
88
+ case node.type === AST_NODE_TYPES.PropertyDefinition && node.value === prev: return node.key;
89
+ case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node: return unit;
90
+ default: return getInstanceId(node.parent, node);
91
+ }
77
92
  }
93
+
94
+ //#endregion
95
+ //#region src/utils/is-from-react.ts
96
+ /**
97
+ * Get the arguments of a require expression
98
+ * @param node The node to match
99
+ * @returns The require expression arguments or undefined if the node is not a require expression
100
+ */
78
101
  function getRequireExpressionArguments(node) {
79
- return match(node).with({ type: AST_NODE_TYPES.CallExpression, arguments: P.select(), callee: { type: AST_NODE_TYPES.Identifier, name: "require" } }, identity).with({ type: AST_NODE_TYPES.MemberExpression, object: P.select() }, getRequireExpressionArguments).otherwise(() => null);
80
- }
102
+ return match(node).with({
103
+ type: AST_NODE_TYPES.CallExpression,
104
+ arguments: P.select(),
105
+ callee: {
106
+ type: AST_NODE_TYPES.Identifier,
107
+ name: "require"
108
+ }
109
+ }, identity).with({
110
+ type: AST_NODE_TYPES.MemberExpression,
111
+ object: P.select()
112
+ }, getRequireExpressionArguments).otherwise(() => null);
113
+ }
114
+ /**
115
+ * Check if an identifier name is initialized from react
116
+ * @param name The top-level identifier's name
117
+ * @param importSource The import source to check against
118
+ * @param initialScope Initial scope to search for the identifier
119
+ * @returns Whether the identifier name is initialized from react
120
+ * @internal
121
+ */
81
122
  function isInitializedFromReact(name, importSource, initialScope) {
82
- if (name.toLowerCase() === "react") return true;
83
- const latestDef = VAR3.findVariable(name, initialScope)?.defs.at(-1);
84
- if (latestDef == null) return false;
85
- const { node, parent } = latestDef;
86
- if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
87
- const { init } = node;
88
- if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) {
89
- return isInitializedFromReact(init.object.name, importSource, initialScope);
90
- }
91
- if (init.type === AST_NODE_TYPES.Identifier) {
92
- return isInitializedFromReact(init.name, importSource, initialScope);
93
- }
94
- const args = getRequireExpressionArguments(init);
95
- const arg0 = args?.[0];
96
- if (arg0 == null || !AST14.isLiteral(arg0, "string")) {
97
- return false;
98
- }
99
- return arg0.value === importSource || arg0.value.startsWith(`${importSource}/`);
100
- }
101
- return parent?.type === AST_NODE_TYPES.ImportDeclaration && parent.source.value === importSource;
123
+ if (name.toLowerCase() === "react") return true;
124
+ const latestDef = VAR.findVariable(name, initialScope)?.defs.at(-1);
125
+ if (latestDef == null) return false;
126
+ const { node, parent } = latestDef;
127
+ if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
128
+ const { init } = node;
129
+ if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) return isInitializedFromReact(init.object.name, importSource, initialScope);
130
+ if (init.type === AST_NODE_TYPES.Identifier) return isInitializedFromReact(init.name, importSource, initialScope);
131
+ const args = getRequireExpressionArguments(init);
132
+ const arg0 = args?.[0];
133
+ if (arg0 == null || !AST.isLiteral(arg0, "string")) return false;
134
+ return arg0.value === importSource || arg0.value.startsWith(`${importSource}/`);
135
+ }
136
+ return parent?.type === AST_NODE_TYPES.ImportDeclaration && parent.source.value === importSource;
102
137
  }
138
+
139
+ //#endregion
140
+ //#region src/utils/is-instance-id-equal.ts
141
+ /** @internal */
103
142
  function isInstanceIdEqual(context, a, b) {
104
- return AST14.isNodeEqual(a, b) || VAR3.isNodeValueEqual(a, b, [
105
- context.sourceCode.getScope(a),
106
- context.sourceCode.getScope(b)
107
- ]);
108
- }
109
-
110
- // src/hook/hook-name.ts
111
- var REACT_BUILTIN_HOOK_NAMES = [
112
- "use",
113
- "useActionState",
114
- "useCallback",
115
- "useContext",
116
- "useDebugValue",
117
- "useDeferredValue",
118
- "useEffect",
119
- "useFormStatus",
120
- "useId",
121
- "useImperativeHandle",
122
- "useInsertionEffect",
123
- "useLayoutEffect",
124
- "useMemo",
125
- "useOptimistic",
126
- "useReducer",
127
- "useRef",
128
- "useState",
129
- "useSyncExternalStore",
130
- "useTransition"
143
+ return AST.isNodeEqual(a, b) || VAR.isNodeValueEqual(a, b, [context.sourceCode.getScope(a), context.sourceCode.getScope(b)]);
144
+ }
145
+
146
+ //#endregion
147
+ //#region src/hook/hook-name.ts
148
+ const REACT_BUILTIN_HOOK_NAMES = [
149
+ "use",
150
+ "useActionState",
151
+ "useCallback",
152
+ "useContext",
153
+ "useDebugValue",
154
+ "useDeferredValue",
155
+ "useEffect",
156
+ "useFormStatus",
157
+ "useId",
158
+ "useImperativeHandle",
159
+ "useInsertionEffect",
160
+ "useLayoutEffect",
161
+ "useMemo",
162
+ "useOptimistic",
163
+ "useReducer",
164
+ "useRef",
165
+ "useState",
166
+ "useSyncExternalStore",
167
+ "useTransition"
131
168
  ];
169
+ /**
170
+ * Catch all identifiers that begin with "use" followed by an uppercase Latin
171
+ * character to exclude identifiers like "user".
172
+ * @param name The name of the identifier to check.
173
+ * @see https://github.com/facebook/react/blob/1d6c8168db1d82713202e842df3167787ffa00ed/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts#L16
174
+ */
132
175
  function isReactHookName(name) {
133
- return name === "use" || /^use[A-Z0-9]/.test(name);
176
+ return name === "use" || /^use[A-Z0-9]/.test(name);
134
177
  }
135
178
 
136
- // src/hook/hook-is.ts
179
+ //#endregion
180
+ //#region src/hook/hook-is.ts
181
+ /**
182
+ * Determines if a function node is a React Hook based on its name.
183
+ * @param node The function node to check
184
+ * @returns True if the function is a React Hook, false otherwise
185
+ */
137
186
  function isReactHook(node) {
138
- if (node == null) return false;
139
- const id = AST14.getFunctionId(node);
140
- return id?.name != null && isReactHookName(id.name);
141
- }
187
+ if (node == null) return false;
188
+ const id = AST.getFunctionId(node);
189
+ return id?.name != null && isReactHookName(id.name);
190
+ }
191
+ /**
192
+ * Check if the given node is a React Hook call by its name.
193
+ * @param node The node to check.
194
+ * @returns `true` if the node is a React Hook call, `false` otherwise.
195
+ */
142
196
  function isReactHookCall(node) {
143
- if (node == null) return false;
144
- if (node.type !== AST_NODE_TYPES.CallExpression) {
145
- return false;
146
- }
147
- if (node.callee.type === AST_NODE_TYPES.Identifier) {
148
- return isReactHookName(node.callee.name);
149
- }
150
- if (node.callee.type === AST_NODE_TYPES.MemberExpression) {
151
- return node.callee.property.type === AST_NODE_TYPES.Identifier && isReactHookName(node.callee.property.name);
152
- }
153
- return false;
154
- }
197
+ if (node == null) return false;
198
+ if (node.type !== AST_NODE_TYPES.CallExpression) return false;
199
+ if (node.callee.type === AST_NODE_TYPES.Identifier) return isReactHookName(node.callee.name);
200
+ if (node.callee.type === AST_NODE_TYPES.MemberExpression) return node.callee.property.type === AST_NODE_TYPES.Identifier && isReactHookName(node.callee.property.name);
201
+ return false;
202
+ }
203
+ /**
204
+ * Checks if a node is a call to a specific React hook, with React import validation.
205
+ * Returns a function that accepts a hook name to check against.
206
+ * @param context The rule context
207
+ * @param node The AST node to check
208
+ * @returns A function that takes a hook name and returns boolean
209
+ */
155
210
  function isReactHookCallWithName(context, node) {
156
- if (node == null || node.type !== AST_NODE_TYPES.CallExpression) return constFalse;
157
- const {
158
- importSource = DEFAULT_ESLINT_REACT_SETTINGS.importSource,
159
- skipImportCheck = true
160
- } = coerceSettings(context.settings);
161
- const initialScope = context.sourceCode.getScope(node);
162
- return (name) => {
163
- switch (true) {
164
- case (node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === name):
165
- return skipImportCheck || isInitializedFromReact(name, importSource, initialScope);
166
- case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === name && "name" in node.callee.object):
167
- return skipImportCheck || isInitializedFromReact(node.callee.object.name, importSource, initialScope);
168
- default:
169
- return false;
170
- }
171
- };
172
- }
211
+ if (node == null || node.type !== AST_NODE_TYPES.CallExpression) return constFalse;
212
+ const { importSource = DEFAULT_ESLINT_REACT_SETTINGS.importSource, skipImportCheck = true } = coerceSettings(context.settings);
213
+ const initialScope = context.sourceCode.getScope(node);
214
+ return (name) => {
215
+ switch (true) {
216
+ case node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === name: return skipImportCheck || isInitializedFromReact(name, importSource, initialScope);
217
+ case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === name && "name" in node.callee.object: return skipImportCheck || isInitializedFromReact(node.callee.object.name, importSource, initialScope);
218
+ default: return false;
219
+ }
220
+ };
221
+ }
222
+ /**
223
+ * Lightweight version of isReactHookCallWithName that doesn't check imports.
224
+ * @param node The AST node to check
225
+ * @returns A function that takes a hook name and returns boolean
226
+ */
173
227
  function isReactHookCallWithNameLoose(node) {
174
- if (node == null || node.type !== AST_NODE_TYPES.CallExpression) return constFalse;
175
- return (name) => {
176
- switch (node.callee.type) {
177
- case AST_NODE_TYPES.Identifier:
178
- return node.callee.name === name;
179
- case AST_NODE_TYPES.MemberExpression:
180
- return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === name;
181
- default:
182
- return false;
183
- }
184
- };
185
- }
228
+ if (node == null || node.type !== AST_NODE_TYPES.CallExpression) return constFalse;
229
+ return (name) => {
230
+ switch (node.callee.type) {
231
+ case AST_NODE_TYPES.Identifier: return node.callee.name === name;
232
+ case AST_NODE_TYPES.MemberExpression: return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === name;
233
+ default: return false;
234
+ }
235
+ };
236
+ }
237
+ /**
238
+ * Checks if a node is a call to a specific React hook or one of its aliases.
239
+ * @param context The rule context
240
+ * @param name The primary hook name to check
241
+ * @param alias Optional array of alias names to also accept
242
+ * @returns Function that checks if a node matches the hook name or aliases
243
+ */
186
244
  function isReactHookCallWithNameAlias(context, name, alias = []) {
187
- const {
188
- importSource = DEFAULT_ESLINT_REACT_SETTINGS.importSource,
189
- skipImportCheck = true
190
- } = coerceSettings(context.settings);
191
- return (node) => {
192
- const initialScope = context.sourceCode.getScope(node);
193
- switch (true) {
194
- case (node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === name):
195
- return skipImportCheck || isInitializedFromReact(name, importSource, initialScope);
196
- case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === name && "name" in node.callee.object):
197
- return skipImportCheck || isInitializedFromReact(node.callee.object.name, importSource, initialScope);
198
- default:
199
- return alias.some(isReactHookCallWithNameLoose(node));
200
- }
201
- };
202
- }
245
+ const { importSource = DEFAULT_ESLINT_REACT_SETTINGS.importSource, skipImportCheck = true } = coerceSettings(context.settings);
246
+ return (node) => {
247
+ const initialScope = context.sourceCode.getScope(node);
248
+ switch (true) {
249
+ case node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === name: return skipImportCheck || isInitializedFromReact(name, importSource, initialScope);
250
+ case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === name && "name" in node.callee.object: return skipImportCheck || isInitializedFromReact(node.callee.object.name, importSource, initialScope);
251
+ default: return alias.some(isReactHookCallWithNameLoose(node));
252
+ }
253
+ };
254
+ }
255
+ /**
256
+ * Detects useEffect calls and variations (useLayoutEffect, etc.) using regex pattern.
257
+ * @param node The AST node to check
258
+ * @returns True if the node is a useEffect-like call
259
+ */
203
260
  function isUseEffectCallLoose(node) {
204
- if (node == null) return false;
205
- if (node.type !== AST_NODE_TYPES.CallExpression) {
206
- return false;
207
- }
208
- switch (node.callee.type) {
209
- case AST_NODE_TYPES.Identifier:
210
- return /^use\w*Effect$/u.test(node.callee.name);
211
- case AST_NODE_TYPES.MemberExpression:
212
- return node.callee.property.type === AST_NODE_TYPES.Identifier && /^use\w*Effect$/u.test(node.callee.property.name);
213
- default:
214
- return false;
215
- }
216
- }
217
- var isUseCall = flip(isReactHookCallWithName)("use");
218
- var isUseActionStateCall = flip(isReactHookCallWithName)("useActionState");
219
- var isUseCallbackCall = flip(isReactHookCallWithName)("useCallback");
220
- var isUseContextCall = flip(isReactHookCallWithName)("useContext");
221
- var isUseDebugValueCall = flip(isReactHookCallWithName)("useDebugValue");
222
- var isUseDeferredValueCall = flip(isReactHookCallWithName)("useDeferredValue");
223
- var isUseEffectCall = flip(isReactHookCallWithName)("useEffect");
224
- var isUseFormStatusCall = flip(isReactHookCallWithName)("useFormStatus");
225
- var isUseIdCall = flip(isReactHookCallWithName)("useId");
226
- var isUseImperativeHandleCall = flip(isReactHookCallWithName)("useImperativeHandle");
227
- var isUseInsertionEffectCall = flip(isReactHookCallWithName)("useInsertionEffect");
228
- var isUseLayoutEffectCall = flip(isReactHookCallWithName)("useLayoutEffect");
229
- var isUseMemoCall = flip(isReactHookCallWithName)("useMemo");
230
- var isUseOptimisticCall = flip(isReactHookCallWithName)("useOptimistic");
231
- var isUseReducerCall = flip(isReactHookCallWithName)("useReducer");
232
- var isUseRefCall = flip(isReactHookCallWithName)("useRef");
233
- var isUseStateCall = flip(isReactHookCallWithName)("useState");
234
- var isUseSyncExternalStoreCall = flip(isReactHookCallWithName)("useSyncExternalStore");
235
- var isUseTransitionCall = flip(isReactHookCallWithName)("useTransition");
236
-
237
- // src/hook/hook-collector.ts
261
+ if (node == null) return false;
262
+ if (node.type !== AST_NODE_TYPES.CallExpression) return false;
263
+ switch (node.callee.type) {
264
+ case AST_NODE_TYPES.Identifier: return /^use\w*Effect$/u.test(node.callee.name);
265
+ case AST_NODE_TYPES.MemberExpression: return node.callee.property.type === AST_NODE_TYPES.Identifier && /^use\w*Effect$/u.test(node.callee.property.name);
266
+ default: return false;
267
+ }
268
+ }
269
+ const isUseCall = flip(isReactHookCallWithName)("use");
270
+ const isUseActionStateCall = flip(isReactHookCallWithName)("useActionState");
271
+ const isUseCallbackCall = flip(isReactHookCallWithName)("useCallback");
272
+ const isUseContextCall = flip(isReactHookCallWithName)("useContext");
273
+ const isUseDebugValueCall = flip(isReactHookCallWithName)("useDebugValue");
274
+ const isUseDeferredValueCall = flip(isReactHookCallWithName)("useDeferredValue");
275
+ const isUseEffectCall = flip(isReactHookCallWithName)("useEffect");
276
+ const isUseFormStatusCall = flip(isReactHookCallWithName)("useFormStatus");
277
+ const isUseIdCall = flip(isReactHookCallWithName)("useId");
278
+ const isUseImperativeHandleCall = flip(isReactHookCallWithName)("useImperativeHandle");
279
+ const isUseInsertionEffectCall = flip(isReactHookCallWithName)("useInsertionEffect");
280
+ const isUseLayoutEffectCall = flip(isReactHookCallWithName)("useLayoutEffect");
281
+ const isUseMemoCall = flip(isReactHookCallWithName)("useMemo");
282
+ const isUseOptimisticCall = flip(isReactHookCallWithName)("useOptimistic");
283
+ const isUseReducerCall = flip(isReactHookCallWithName)("useReducer");
284
+ const isUseRefCall = flip(isReactHookCallWithName)("useRef");
285
+ const isUseStateCall = flip(isReactHookCallWithName)("useState");
286
+ const isUseSyncExternalStoreCall = flip(isReactHookCallWithName)("useSyncExternalStore");
287
+ const isUseTransitionCall = flip(isReactHookCallWithName)("useTransition");
288
+
289
+ //#endregion
290
+ //#region src/hook/hook-collector.ts
238
291
  function useHookCollector() {
239
- const hooks = /* @__PURE__ */ new Map();
240
- const functionEntries = [];
241
- const onFunctionEnter = (node) => {
242
- const id = AST14.getFunctionId(node);
243
- const key = getId();
244
- const name = id?.name;
245
- if (name != null && isReactHookName(name)) {
246
- functionEntries.push({ key, node, isHook: true });
247
- hooks.set(key, {
248
- id,
249
- key,
250
- kind: "function",
251
- name,
252
- node,
253
- flag: 0n,
254
- hint: 0n,
255
- hookCalls: []
256
- });
257
- return;
258
- }
259
- functionEntries.push({ key, node, isHook: false });
260
- };
261
- const onFunctionExit = () => {
262
- functionEntries.pop();
263
- };
264
- const ctx = {
265
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
266
- getAllHooks(node) {
267
- return hooks;
268
- }
269
- };
270
- const listeners = {
271
- ":function[type]": onFunctionEnter,
272
- ":function[type]:exit": onFunctionExit,
273
- "CallExpression[type]"(node) {
274
- if (!isReactHookCall(node)) {
275
- return;
276
- }
277
- const fEntry = functionEntries.at(-1);
278
- if (fEntry?.key == null) {
279
- return;
280
- }
281
- const hook = hooks.get(fEntry.key);
282
- if (hook == null) {
283
- return;
284
- }
285
- hook.hookCalls.push(node);
286
- }
287
- };
288
- return { ctx, listeners };
292
+ const hooks = /* @__PURE__ */ new Map();
293
+ const functionEntries = [];
294
+ const onFunctionEnter = (node) => {
295
+ const id = AST.getFunctionId(node);
296
+ const key = getId();
297
+ const name = id?.name;
298
+ if (name != null && isReactHookName(name)) {
299
+ functionEntries.push({
300
+ key,
301
+ node,
302
+ isHook: true
303
+ });
304
+ hooks.set(key, {
305
+ id,
306
+ key,
307
+ kind: "function",
308
+ name,
309
+ node,
310
+ flag: 0n,
311
+ hint: 0n,
312
+ hookCalls: []
313
+ });
314
+ return;
315
+ }
316
+ functionEntries.push({
317
+ key,
318
+ node,
319
+ isHook: false
320
+ });
321
+ };
322
+ const onFunctionExit = () => {
323
+ functionEntries.pop();
324
+ };
325
+ const ctx = { getAllHooks(node) {
326
+ return hooks;
327
+ } };
328
+ const listeners = {
329
+ ":function[type]": onFunctionEnter,
330
+ ":function[type]:exit": onFunctionExit,
331
+ "CallExpression[type]"(node) {
332
+ if (!isReactHookCall(node)) return;
333
+ const fEntry = functionEntries.at(-1);
334
+ if (fEntry?.key == null) return;
335
+ const hook = hooks.get(fEntry.key);
336
+ if (hook == null) return;
337
+ hook.hookCalls.push(node);
338
+ }
339
+ };
340
+ return {
341
+ ctx,
342
+ listeners
343
+ };
289
344
  }
345
+
346
+ //#endregion
347
+ //#region src/hook/hook-id.ts
290
348
  function isReactHookId(id) {
291
- switch (id.type) {
292
- case AST_NODE_TYPES.Identifier:
293
- return isReactHookName(id.name);
294
- case AST_NODE_TYPES.MemberExpression:
295
- return "name" in id.property && isReactHookName(id.property.name);
296
- default:
297
- return false;
298
- }
349
+ switch (id.type) {
350
+ case AST_NODE_TYPES.Identifier: return isReactHookName(id.name);
351
+ case AST_NODE_TYPES.MemberExpression: return "name" in id.property && isReactHookName(id.property.name);
352
+ default: return false;
353
+ }
299
354
  }
355
+
356
+ //#endregion
357
+ //#region src/jsx/jsx-stringify.ts
358
+ /**
359
+ * Incomplete but sufficient stringification of JSX nodes for common use cases
360
+ *
361
+ * @param node - JSX node from TypeScript ESTree
362
+ * @returns String representation of the JSX node
363
+ */
300
364
  function stringifyJsx(node) {
301
- switch (node.type) {
302
- case AST_NODE_TYPES.JSXIdentifier:
303
- return node.name;
304
- case AST_NODE_TYPES.JSXNamespacedName:
305
- return `${node.namespace.name}:${node.name.name}`;
306
- case AST_NODE_TYPES.JSXMemberExpression:
307
- return `${stringifyJsx(node.object)}.${stringifyJsx(node.property)}`;
308
- case AST_NODE_TYPES.JSXText:
309
- return node.value;
310
- case AST_NODE_TYPES.JSXOpeningElement:
311
- return `<${stringifyJsx(node.name)}>`;
312
- case AST_NODE_TYPES.JSXClosingElement:
313
- return `</${stringifyJsx(node.name)}>`;
314
- case AST_NODE_TYPES.JSXOpeningFragment:
315
- return "<>";
316
- case AST_NODE_TYPES.JSXClosingFragment:
317
- return "</>";
318
- }
319
- }
320
-
321
- // src/jsx/jsx-attribute-name.ts
365
+ switch (node.type) {
366
+ case AST_NODE_TYPES.JSXIdentifier: return node.name;
367
+ case AST_NODE_TYPES.JSXNamespacedName: return `${node.namespace.name}:${node.name.name}`;
368
+ case AST_NODE_TYPES.JSXMemberExpression: return `${stringifyJsx(node.object)}.${stringifyJsx(node.property)}`;
369
+ case AST_NODE_TYPES.JSXText: return node.value;
370
+ case AST_NODE_TYPES.JSXOpeningElement: return `<${stringifyJsx(node.name)}>`;
371
+ case AST_NODE_TYPES.JSXClosingElement: return `</${stringifyJsx(node.name)}>`;
372
+ case AST_NODE_TYPES.JSXOpeningFragment: return "<>";
373
+ case AST_NODE_TYPES.JSXClosingFragment: return "</>";
374
+ }
375
+ }
376
+
377
+ //#endregion
378
+ //#region src/jsx/jsx-attribute-name.ts
379
+ /**
380
+ * Get the stringified name of a JSX attribute
381
+ * @param context The ESLint rule context
382
+ * @param node The JSX attribute node
383
+ * @returns The name of the attribute
384
+ */
322
385
  function getAttributeName(context, node) {
323
- return stringifyJsx(node.name);
324
- }
325
-
326
- // src/jsx/jsx-attribute.ts
327
- function getAttribute(context, name, attributes, initialScope) {
328
- return attributes.findLast((attr) => {
329
- if (attr.type === AST_NODE_TYPES.JSXAttribute) {
330
- return getAttributeName(context, attr) === name;
331
- }
332
- if (initialScope == null) return false;
333
- switch (attr.argument.type) {
334
- case AST_NODE_TYPES.Identifier: {
335
- const variable = VAR3.findVariable(attr.argument.name, initialScope);
336
- const variableNode = VAR3.getVariableInitNode(variable, 0);
337
- if (variableNode?.type === AST_NODE_TYPES.ObjectExpression) {
338
- return VAR3.findPropertyInProperties(name, variableNode.properties, initialScope) != null;
339
- }
340
- return false;
341
- }
342
- case AST_NODE_TYPES.ObjectExpression:
343
- return VAR3.findPropertyInProperties(name, attr.argument.properties, initialScope) != null;
344
- }
345
- return false;
346
- });
347
- }
348
- function getAttributeValue(context, node, name) {
349
- const initialScope = context.sourceCode.getScope(node);
350
- switch (node.type) {
351
- case AST_NODE_TYPES.JSXAttribute:
352
- if (node.value?.type === AST_NODE_TYPES.Literal) {
353
- return {
354
- kind: "some",
355
- node: node.value,
356
- initialScope,
357
- value: node.value.value
358
- };
359
- }
360
- if (node.value?.type === AST_NODE_TYPES.JSXExpressionContainer) {
361
- return VAR3.toStaticValue({
362
- kind: "lazy",
363
- node: node.value.expression,
364
- initialScope
365
- });
366
- }
367
- return { kind: "none", node, initialScope };
368
- case AST_NODE_TYPES.JSXSpreadAttribute: {
369
- const staticValue = VAR3.toStaticValue({
370
- kind: "lazy",
371
- node: node.argument,
372
- initialScope
373
- });
374
- if (staticValue.kind === "none") {
375
- return staticValue;
376
- }
377
- return match(staticValue.value).with({ [name]: P.select(P.any) }, (value) => ({
378
- kind: "some",
379
- node: node.argument,
380
- initialScope,
381
- value
382
- })).otherwise(() => ({ kind: "none", node, initialScope }));
383
- }
384
- default:
385
- return { kind: "none", node, initialScope };
386
- }
387
- }
388
-
389
- // src/jsx/jsx-detection-hint.ts
390
- var JSXDetectionHint = {
391
- None: 0n,
392
- SkipUndefined: 1n << 0n,
393
- SkipNullLiteral: 1n << 1n,
394
- SkipBooleanLiteral: 1n << 2n,
395
- SkipStringLiteral: 1n << 3n,
396
- SkipNumberLiteral: 1n << 4n,
397
- SkipBigIntLiteral: 1n << 5n,
398
- SkipEmptyArray: 1n << 6n,
399
- SkipCreateElement: 1n << 7n,
400
- StrictArray: 1n << 8n,
401
- StrictLogical: 1n << 9n,
402
- StrictConditional: 1n << 10n
403
- };
404
- var DEFAULT_JSX_DETECTION_HINT = 0n | JSXDetectionHint.SkipUndefined | JSXDetectionHint.SkipBooleanLiteral;
386
+ return stringifyJsx(node.name);
387
+ }
405
388
 
406
- // src/jsx/jsx-detection.ts
407
- function isJsxText(node) {
408
- if (node == null) return false;
409
- return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal;
389
+ //#endregion
390
+ //#region src/jsx/jsx-attribute.ts
391
+ function getAttribute(context, attributes, initialScope) {
392
+ return (name) => {
393
+ return attributes.findLast((attr) => {
394
+ if (attr.type === AST_NODE_TYPES.JSXAttribute) return getAttributeName(context, attr) === name;
395
+ if (initialScope == null) return false;
396
+ switch (attr.argument.type) {
397
+ case AST_NODE_TYPES.Identifier: {
398
+ const variable = VAR.findVariable(attr.argument.name, initialScope);
399
+ const variableNode = VAR.getVariableDefinitionNode(variable, 0);
400
+ if (variableNode?.type === AST_NODE_TYPES.ObjectExpression) return VAR.findProperty(name, variableNode.properties, initialScope) != null;
401
+ return false;
402
+ }
403
+ case AST_NODE_TYPES.ObjectExpression: return VAR.findProperty(name, attr.argument.properties, initialScope) != null;
404
+ }
405
+ return false;
406
+ });
407
+ };
408
+ }
409
+
410
+ //#endregion
411
+ //#region src/jsx/jsx-attribute-value.ts
412
+ function resolveAttributeValue(context, attribute) {
413
+ const initialScope = context.sourceCode.getScope(attribute);
414
+ function handleJsxAttribute(node) {
415
+ if (node.value == null) return {
416
+ kind: "boolean",
417
+ toStatic() {
418
+ return true;
419
+ }
420
+ };
421
+ switch (node.value.type) {
422
+ case AST_NODE_TYPES.Literal: {
423
+ const staticValue = node.value.value;
424
+ return {
425
+ kind: "literal",
426
+ node: node.value,
427
+ toStatic() {
428
+ return staticValue;
429
+ }
430
+ };
431
+ }
432
+ case AST_NODE_TYPES.JSXExpressionContainer: {
433
+ const expr = node.value.expression;
434
+ return {
435
+ kind: "expression",
436
+ node: expr,
437
+ toStatic() {
438
+ return getStaticValue(expr, initialScope)?.value;
439
+ }
440
+ };
441
+ }
442
+ case AST_NODE_TYPES.JSXElement: return {
443
+ kind: "element",
444
+ node: node.value,
445
+ toStatic() {
446
+ return unit;
447
+ }
448
+ };
449
+ case AST_NODE_TYPES.JSXSpreadChild: return {
450
+ kind: "spreadChild",
451
+ node: node.value.expression,
452
+ toStatic() {
453
+ return unit;
454
+ }
455
+ };
456
+ }
457
+ }
458
+ function handleJsxSpreadAttribute(node) {
459
+ return {
460
+ kind: "spreadProps",
461
+ node: node.argument,
462
+ toStatic(name) {
463
+ if (name == null) return unit;
464
+ return match(getStaticValue(node.argument, initialScope)?.value).with({ [name]: P.select(P.any) }, identity).otherwise(() => unit);
465
+ }
466
+ };
467
+ }
468
+ switch (attribute.type) {
469
+ case AST_NODE_TYPES.JSXAttribute: return handleJsxAttribute(attribute);
470
+ case AST_NODE_TYPES.JSXSpreadAttribute: return handleJsxSpreadAttribute(attribute);
471
+ }
410
472
  }
473
+
474
+ //#endregion
475
+ //#region src/jsx/jsx-detection.ts
476
+ /**
477
+ * Flags to control JSX detection behavior:
478
+ * - Skip* flags: Ignore specific node types when detecting JSX
479
+ * - Strict* flags: Enforce stricter rules for container types
480
+ */
481
+ const JSXDetectionHint = {
482
+ None: 0n,
483
+ SkipUndefined: 1n << 0n,
484
+ SkipNullLiteral: 1n << 1n,
485
+ SkipBooleanLiteral: 1n << 2n,
486
+ SkipStringLiteral: 1n << 3n,
487
+ SkipNumberLiteral: 1n << 4n,
488
+ SkipBigIntLiteral: 1n << 5n,
489
+ SkipEmptyArray: 1n << 6n,
490
+ SkipCreateElement: 1n << 7n,
491
+ StrictArray: 1n << 8n,
492
+ StrictLogical: 1n << 9n,
493
+ StrictConditional: 1n << 10n
494
+ };
495
+ /**
496
+ * Default JSX detection configuration
497
+ * Skips undefined and boolean literals (common in React)
498
+ */
499
+ const DEFAULT_JSX_DETECTION_HINT = 0n | JSXDetectionHint.SkipUndefined | JSXDetectionHint.SkipBooleanLiteral;
500
+ /**
501
+ * Checks if a node is a `JSXText` or a `Literal` node
502
+ * @param node The AST node to check
503
+ * @returns `true` if the node is a `JSXText` or a `Literal` node
504
+ */
505
+ function isJsxText(node) {
506
+ if (node == null) return false;
507
+ return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal;
508
+ }
509
+ /**
510
+ * Determines if a node represents JSX-like content based on heuristics
511
+ * Supports configuration through hint flags to customize detection behavior
512
+ *
513
+ * @param code The source code with scope lookup capability
514
+ * @param code.getScope The function to get the scope of a node
515
+ * @param node The AST node to analyze
516
+ * @param hint The configuration flags to adjust detection behavior
517
+ * @returns boolean Whether the node is considered JSX-like
518
+ */
411
519
  function isJsxLike(code, node, hint = DEFAULT_JSX_DETECTION_HINT) {
412
- if (node == null) return false;
413
- if (AST14.isJSX(node)) return true;
414
- switch (node.type) {
415
- case AST_NODE_TYPES.Literal: {
416
- switch (typeof node.value) {
417
- case "boolean":
418
- return !(hint & JSXDetectionHint.SkipBooleanLiteral);
419
- case "string":
420
- return !(hint & JSXDetectionHint.SkipStringLiteral);
421
- case "number":
422
- return !(hint & JSXDetectionHint.SkipNumberLiteral);
423
- case "bigint":
424
- return !(hint & JSXDetectionHint.SkipBigIntLiteral);
425
- }
426
- if (node.value == null) {
427
- return !(hint & JSXDetectionHint.SkipNullLiteral);
428
- }
429
- return false;
430
- }
431
- case AST_NODE_TYPES.TemplateLiteral: {
432
- return !(hint & JSXDetectionHint.SkipStringLiteral);
433
- }
434
- case AST_NODE_TYPES.ArrayExpression: {
435
- if (hint & JSXDetectionHint.StrictArray) {
436
- return node.elements.every((n) => isJsxLike(code, n, hint));
437
- }
438
- return node.elements.some((n) => isJsxLike(code, n, hint));
439
- }
440
- case AST_NODE_TYPES.LogicalExpression: {
441
- if (hint & JSXDetectionHint.StrictLogical) {
442
- return isJsxLike(code, node.left, hint) && isJsxLike(code, node.right, hint);
443
- }
444
- return isJsxLike(code, node.left, hint) || isJsxLike(code, node.right, hint);
445
- }
446
- case AST_NODE_TYPES.ConditionalExpression: {
447
- let leftHasJSX2 = function(node2) {
448
- if (Array.isArray(node2.consequent)) {
449
- if (node2.consequent.length === 0) {
450
- return !(hint & JSXDetectionHint.SkipEmptyArray);
451
- }
452
- if (hint & JSXDetectionHint.StrictArray) {
453
- return node2.consequent.every((n) => isJsxLike(code, n, hint));
454
- }
455
- return node2.consequent.some((n) => isJsxLike(code, n, hint));
456
- }
457
- return isJsxLike(code, node2.consequent, hint);
458
- }, rightHasJSX2 = function(node2) {
459
- return isJsxLike(code, node2.alternate, hint);
460
- };
461
- if (hint & JSXDetectionHint.StrictConditional) {
462
- return leftHasJSX2(node) && rightHasJSX2(node);
463
- }
464
- return leftHasJSX2(node) || rightHasJSX2(node);
465
- }
466
- case AST_NODE_TYPES.SequenceExpression: {
467
- const exp = node.expressions.at(-1);
468
- return isJsxLike(code, exp, hint);
469
- }
470
- case AST_NODE_TYPES.CallExpression: {
471
- if (hint & JSXDetectionHint.SkipCreateElement) {
472
- return false;
473
- }
474
- switch (node.callee.type) {
475
- case AST_NODE_TYPES.Identifier:
476
- return node.callee.name === "createElement";
477
- case AST_NODE_TYPES.MemberExpression:
478
- return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "createElement";
479
- }
480
- return false;
481
- }
482
- case AST_NODE_TYPES.Identifier: {
483
- const { name } = node;
484
- if (name === "undefined") {
485
- return !(hint & JSXDetectionHint.SkipUndefined);
486
- }
487
- if (AST14.isJSXTagNameExpression(node)) {
488
- return true;
489
- }
490
- const variable = VAR3.findVariable(name, code.getScope(node));
491
- const variableNode = variable && VAR3.getVariableInitNode(variable, 0);
492
- return !!variableNode && isJsxLike(code, variableNode, hint);
493
- }
494
- }
495
- return false;
520
+ if (node == null) return false;
521
+ if (AST.isJSX(node)) return true;
522
+ switch (node.type) {
523
+ case AST_NODE_TYPES.Literal:
524
+ switch (typeof node.value) {
525
+ case "boolean": return !(hint & JSXDetectionHint.SkipBooleanLiteral);
526
+ case "string": return !(hint & JSXDetectionHint.SkipStringLiteral);
527
+ case "number": return !(hint & JSXDetectionHint.SkipNumberLiteral);
528
+ case "bigint": return !(hint & JSXDetectionHint.SkipBigIntLiteral);
529
+ }
530
+ if (node.value == null) return !(hint & JSXDetectionHint.SkipNullLiteral);
531
+ return false;
532
+ case AST_NODE_TYPES.TemplateLiteral: return !(hint & JSXDetectionHint.SkipStringLiteral);
533
+ case AST_NODE_TYPES.ArrayExpression:
534
+ if (node.elements.length === 0) return !(hint & JSXDetectionHint.SkipEmptyArray);
535
+ if (hint & JSXDetectionHint.StrictArray) return node.elements.every((n) => isJsxLike(code, n, hint));
536
+ return node.elements.some((n) => isJsxLike(code, n, hint));
537
+ case AST_NODE_TYPES.LogicalExpression:
538
+ if (hint & JSXDetectionHint.StrictLogical) return isJsxLike(code, node.left, hint) && isJsxLike(code, node.right, hint);
539
+ return isJsxLike(code, node.left, hint) || isJsxLike(code, node.right, hint);
540
+ case AST_NODE_TYPES.ConditionalExpression: {
541
+ function leftHasJSX(node$1) {
542
+ if (Array.isArray(node$1.consequent)) {
543
+ if (node$1.consequent.length === 0) return !(hint & JSXDetectionHint.SkipEmptyArray);
544
+ if (hint & JSXDetectionHint.StrictArray) return node$1.consequent.every((n) => isJsxLike(code, n, hint));
545
+ return node$1.consequent.some((n) => isJsxLike(code, n, hint));
546
+ }
547
+ return isJsxLike(code, node$1.consequent, hint);
548
+ }
549
+ function rightHasJSX(node$1) {
550
+ return isJsxLike(code, node$1.alternate, hint);
551
+ }
552
+ if (hint & JSXDetectionHint.StrictConditional) return leftHasJSX(node) && rightHasJSX(node);
553
+ return leftHasJSX(node) || rightHasJSX(node);
554
+ }
555
+ case AST_NODE_TYPES.SequenceExpression: {
556
+ const exp = node.expressions.at(-1);
557
+ return isJsxLike(code, exp, hint);
558
+ }
559
+ case AST_NODE_TYPES.CallExpression:
560
+ if (hint & JSXDetectionHint.SkipCreateElement) return false;
561
+ switch (node.callee.type) {
562
+ case AST_NODE_TYPES.Identifier: return node.callee.name === "createElement";
563
+ case AST_NODE_TYPES.MemberExpression: return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "createElement";
564
+ }
565
+ return false;
566
+ case AST_NODE_TYPES.Identifier: {
567
+ const { name } = node;
568
+ if (name === "undefined") return !(hint & JSXDetectionHint.SkipUndefined);
569
+ if (AST.isJSXTagNameExpression(node)) return true;
570
+ const variable = VAR.findVariable(name, code.getScope(node));
571
+ const variableNode = variable && VAR.getVariableDefinitionNode(variable, 0);
572
+ return !!variableNode && isJsxLike(code, variableNode, hint);
573
+ }
574
+ }
575
+ return false;
496
576
  }
577
+
578
+ //#endregion
579
+ //#region src/jsx/jsx-element-type.ts
580
+ /**
581
+ * Extracts the element type name from a JSX element or fragment
582
+ * For JSX elements, returns the stringified name (e.g., "div", "Button", "React.Fragment")
583
+ * For JSX fragments, returns an empty string
584
+ *
585
+ * @param context - ESLint rule context
586
+ * @param node - JSX element or fragment node
587
+ * @returns String representation of the element type
588
+ */
497
589
  function getElementType(context, node) {
498
- if (node.type === AST_NODE_TYPES.JSXFragment) {
499
- return "";
500
- }
501
- return stringifyJsx(node.openingElement.name);
590
+ if (node.type === AST_NODE_TYPES.JSXFragment) return "";
591
+ return stringifyJsx(node.openingElement.name);
502
592
  }
503
593
 
504
- // src/jsx/jsx-has.ts
505
- function hasAttribute(context, name, attributes, initialScope) {
506
- return getAttribute(context, name, attributes, initialScope) != null;
594
+ //#endregion
595
+ //#region src/jsx/jsx-element-is.ts
596
+ /**
597
+ * Determines if a JSX element is a host element
598
+ * Host elements in React start with lowercase letters (e.g., div, span)
599
+ *
600
+ * @param context - ESLint rule context
601
+ * @param node - AST node to check
602
+ * @returns boolean indicating if the element is a host element
603
+ */
604
+ function isHostElement(context, node) {
605
+ return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
606
+ }
607
+ /**
608
+ * Determines if a JSX element is a React Fragment
609
+ * Fragments can be imported from React and used like <Fragment> or <React.Fragment>
610
+ *
611
+ * @param context - ESLint rule context
612
+ * @param node - AST node to check
613
+ * @returns boolean indicating if the element is a Fragment with type narrowing
614
+ */
615
+ function isFragmentElement(context, node) {
616
+ if (node.type !== AST_NODE_TYPES.JSXElement) return false;
617
+ return getElementType(context, node).split(".").at(-1) === "Fragment";
507
618
  }
619
+
620
+ //#endregion
621
+ //#region src/jsx/jsx-has.ts
622
+ /**
623
+ * Checks if a JSX element has a specific attribute
624
+ *
625
+ * @param context - ESLint rule context
626
+ * @param name - Name of the attribute to check for
627
+ * @param attributes - List of JSX attributes from opening element
628
+ * @param initialScope - Optional scope for resolving variables in spread attributes
629
+ * @returns boolean indicating whether the attribute exists
630
+ */
631
+ function hasAttribute(context, name, attributes, initialScope) {
632
+ return getAttribute(context, attributes, initialScope)(name) != null;
633
+ }
634
+ /**
635
+ * Checks if a JSX element has at least one of the specified attributes
636
+ *
637
+ * @param context - ESLint rule context
638
+ * @param names - Array of attribute names to check for
639
+ * @param attributes - List of JSX attributes from opening element
640
+ * @param initialScope - Optional scope for resolving variables in spread attributes
641
+ * @returns boolean indicating whether any of the attributes exist
642
+ */
508
643
  function hasAnyAttribute(context, names, attributes, initialScope) {
509
- return names.some((n) => hasAttribute(context, n, attributes, initialScope));
510
- }
644
+ return names.some((n) => hasAttribute(context, n, attributes, initialScope));
645
+ }
646
+ /**
647
+ * Checks if a JSX element has all of the specified attributes
648
+ *
649
+ * @param context - ESLint rule context
650
+ * @param names - Array of attribute names to check for
651
+ * @param attributes - List of JSX attributes from opening element
652
+ * @param initialScope - Optional scope for resolving variables in spread attributes
653
+ * @returns boolean indicating whether all of the attributes exist
654
+ */
511
655
  function hasEveryAttribute(context, names, attributes, initialScope) {
512
- return names.every((n) => hasAttribute(context, n, attributes, initialScope));
656
+ return names.every((n) => hasAttribute(context, n, attributes, initialScope));
513
657
  }
658
+
659
+ //#endregion
660
+ //#region src/jsx/jsx-hierarchy.ts
661
+ /**
662
+ * Traverses up the AST to find a parent JSX attribute node that matches a given test
663
+ *
664
+ * @param node - The starting AST node
665
+ * @param test - Optional predicate function to test if the attribute meets criteria
666
+ * Defaults to always returning true (matches any attribute)
667
+ * @returns The first matching JSX attribute node found when traversing upwards, or undefined
668
+ */
514
669
  function findParentAttribute(node, test = constTrue) {
515
- const guard = (node2) => {
516
- return node2.type === AST_NODE_TYPES.JSXAttribute && test(node2);
517
- };
518
- return AST14.findParentNode(node, guard);
670
+ const guard = (node$1) => {
671
+ return node$1.type === AST_NODE_TYPES.JSXAttribute && test(node$1);
672
+ };
673
+ return AST.findParentNode(node, guard);
519
674
  }
520
- function isHostElement(context, node) {
521
- return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
522
- }
523
- function isKeyedElement(context, node, initialScope) {
524
- return node.type === AST_NODE_TYPES.JSXElement && hasAttribute(context, "key", node.openingElement.attributes, initialScope);
525
- }
526
- function isFragmentElement(context, node, allowJSXFragment = false) {
527
- if (node == null) return false;
528
- if (node.type !== AST_NODE_TYPES.JSXElement && node.type !== AST_NODE_TYPES.JSXFragment) return false;
529
- if (node.type === AST_NODE_TYPES.JSXFragment) return allowJSXFragment;
530
- return getElementType(context, node).split(".").at(-1) === "Fragment";
531
- }
532
-
533
- // src/component/component-detection-hint.ts
534
- var ComponentDetectionHint = {
535
- /**
536
- * 1n << 0n - 1n << 63n are reserved for JSXDetectionHint
537
- */
538
- ...JSXDetectionHint,
539
- /**
540
- * Skip function component created by React.memo
541
- */
542
- SkipMemo: 1n << 64n,
543
- /**
544
- * Skip function component created by React.forwardRef
545
- */
546
- SkipForwardRef: 1n << 65n,
547
- /**
548
- * Skip function component defined as array map argument
549
- */
550
- SkipArrayMapArgument: 1n << 66n,
551
- /**
552
- * Skip function component defined on object method
553
- */
554
- SkipObjectMethod: 1n << 67n,
555
- /**
556
- * Skip function component defined on class method
557
- */
558
- SkipClassMethod: 1n << 68n,
559
- /**
560
- * Skip function component defined on class property
561
- */
562
- SkipClassProperty: 1n << 69n
675
+
676
+ //#endregion
677
+ //#region src/component/component-detection-hint.ts
678
+ /**
679
+ * Hints for component collector
680
+ */
681
+ const ComponentDetectionHint = {
682
+ ...JSXDetectionHint,
683
+ SkipMemo: 1n << 64n,
684
+ SkipForwardRef: 1n << 65n,
685
+ SkipArrayMapArgument: 1n << 66n,
686
+ SkipObjectMethod: 1n << 67n,
687
+ SkipClassMethod: 1n << 68n,
688
+ SkipClassProperty: 1n << 69n
563
689
  };
564
- var DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.SkipBooleanLiteral | ComponentDetectionHint.SkipEmptyArray | ComponentDetectionHint.SkipArrayMapArgument | ComponentDetectionHint.SkipNumberLiteral | ComponentDetectionHint.SkipStringLiteral | ComponentDetectionHint.SkipUndefined | ComponentDetectionHint.StrictArray | ComponentDetectionHint.StrictConditional | ComponentDetectionHint.StrictLogical;
690
+ /**
691
+ * Default component detection hint
692
+ */
693
+ const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.SkipBooleanLiteral | ComponentDetectionHint.SkipEmptyArray | ComponentDetectionHint.SkipArrayMapArgument | ComponentDetectionHint.SkipNumberLiteral | ComponentDetectionHint.SkipStringLiteral | ComponentDetectionHint.SkipUndefined | ComponentDetectionHint.StrictArray | ComponentDetectionHint.StrictConditional | ComponentDetectionHint.StrictLogical;
694
+
695
+ //#endregion
696
+ //#region src/component/component-is.ts
697
+ /**
698
+ * Check if a node is a React class component
699
+ * @param node The AST node to check
700
+ * @returns `true` if the node is a class component, `false` otherwise
701
+ */
565
702
  function isClassComponent(node) {
566
- if ("superClass" in node && node.superClass != null) {
567
- const re = /^(?:Pure)?Component$/u;
568
- switch (true) {
569
- case node.superClass.type === AST_NODE_TYPES.Identifier:
570
- return re.test(node.superClass.name);
571
- case (node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier):
572
- return re.test(node.superClass.property.name);
573
- }
574
- }
575
- return false;
576
- }
703
+ if ("superClass" in node && node.superClass != null) {
704
+ const re = /^(?:Pure)?Component$/u;
705
+ switch (true) {
706
+ case node.superClass.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.name);
707
+ case node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.property.name);
708
+ }
709
+ }
710
+ return false;
711
+ }
712
+ /**
713
+ * Check if a node is a React PureComponent
714
+ * @param node The AST node to check
715
+ * @returns `true` if the node is a pure component, `false` otherwise
716
+ */
577
717
  function isPureComponent(node) {
578
- if ("superClass" in node && node.superClass != null) {
579
- const re = /^PureComponent$/u;
580
- switch (true) {
581
- case node.superClass.type === AST_NODE_TYPES.Identifier:
582
- return re.test(node.superClass.name);
583
- case (node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier):
584
- return re.test(node.superClass.property.name);
585
- }
586
- }
587
- return false;
718
+ if ("superClass" in node && node.superClass != null) {
719
+ const re = /^PureComponent$/u;
720
+ switch (true) {
721
+ case node.superClass.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.name);
722
+ case node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.property.name);
723
+ }
724
+ }
725
+ return false;
588
726
  }
727
+
728
+ //#endregion
729
+ //#region src/component/component-render-method.ts
730
+ /**
731
+ * Check whether given node is a render method of a class component
732
+ * @example
733
+ * ```tsx
734
+ * class Component extends React.Component {
735
+ * renderHeader = () => <div />;
736
+ * renderFooter = () => <div />;
737
+ * }
738
+ * ```
739
+ * @param node The AST node to check
740
+ * @returns `true` if node is a render function, `false` if not
741
+ */
589
742
  function isRenderMethodLike(node) {
590
- return AST14.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name.startsWith("render") && node.parent.parent.type === AST_NODE_TYPES.ClassDeclaration;
743
+ return AST.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name.startsWith("render") && node.parent.parent.type === AST_NODE_TYPES.ClassDeclaration;
591
744
  }
592
745
 
593
- // src/component/component-definition.ts
594
- var isFunctionOfClassMethod = isMatching({
595
- type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
596
- parent: AST_NODE_TYPES.MethodDefinition
597
- });
598
- var isFunctionOfClassProperty = isMatching({
599
- type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
600
- parent: AST_NODE_TYPES.Property
601
- });
602
- var isFunctionOfObjectMethod = isMatching({
603
- type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
604
- parent: {
605
- type: AST_NODE_TYPES.Property,
606
- parent: {
607
- type: AST_NODE_TYPES.ObjectExpression
608
- }
609
- }
610
- });
746
+ //#endregion
747
+ //#region src/component/component-definition.ts
748
+ /**
749
+ * Function pattern matchers for different contexts
750
+ */
751
+ const functionPatterns = {
752
+ classMethod: {
753
+ type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
754
+ parent: AST_NODE_TYPES.MethodDefinition
755
+ },
756
+ classProperty: {
757
+ type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
758
+ parent: AST_NODE_TYPES.Property
759
+ },
760
+ objectMethod: {
761
+ type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
762
+ parent: {
763
+ type: AST_NODE_TYPES.Property,
764
+ parent: { type: AST_NODE_TYPES.ObjectExpression }
765
+ }
766
+ }
767
+ };
768
+ /**
769
+ * Check whether given node is a function of a render method of a class component
770
+ * @example
771
+ * ```tsx
772
+ * class Component extends React.Component {
773
+ * renderHeader = () => <div />;
774
+ * renderFooter = () => <div />;
775
+ * }
776
+ * ```
777
+ * @param node The AST node to check
778
+ * @returns `true` if node is a render function, `false` if not
779
+ */
611
780
  function isFunctionOfRenderMethod(node) {
612
- if (!isRenderMethodLike(node.parent)) {
613
- return false;
614
- }
615
- return isClassComponent(node.parent.parent.parent);
616
- }
617
- function isValidComponentDefinition(context, node, hint) {
618
- if (isChildrenOfCreateElement(context, node) || isFunctionOfRenderMethod(node)) {
619
- return false;
620
- }
621
- if (hint & ComponentDetectionHint.SkipObjectMethod && isFunctionOfObjectMethod(node.parent)) {
622
- return false;
623
- }
624
- if (hint & ComponentDetectionHint.SkipClassMethod && isFunctionOfClassMethod(node.parent)) {
625
- return false;
626
- }
627
- if (hint & ComponentDetectionHint.SkipClassProperty && isFunctionOfClassProperty(node.parent)) {
628
- return false;
629
- }
630
- if (hint & ComponentDetectionHint.SkipArrayMapArgument && AST14.isArrayMapCall(node.parent)) {
631
- return false;
632
- }
633
- const significantParent = AST14.findParentNode(
634
- node,
635
- AST14.isOneOf([
636
- AST_NODE_TYPES.JSXExpressionContainer,
637
- AST_NODE_TYPES.ArrowFunctionExpression,
638
- AST_NODE_TYPES.FunctionExpression,
639
- AST_NODE_TYPES.Property,
640
- AST_NODE_TYPES.ClassBody
641
- ])
642
- );
643
- return significantParent == null || significantParent.type !== AST_NODE_TYPES.JSXExpressionContainer;
781
+ return isRenderMethodLike(node.parent) && isClassComponent(node.parent.parent.parent);
782
+ }
783
+ /**
784
+ * Checks if a function node should be excluded based on detection hints
785
+ * @param node The function node to check
786
+ * @param hint Component detection hints as bit flags
787
+ * @returns `true` if the function should be excluded, `false` otherwise
788
+ */
789
+ function shouldExcludeBasedOnHint(node, hint) {
790
+ if (hint & ComponentDetectionHint.SkipObjectMethod && isMatching(functionPatterns.objectMethod)(node)) return true;
791
+ if (hint & ComponentDetectionHint.SkipClassMethod && isMatching(functionPatterns.classMethod)(node)) return true;
792
+ if (hint & ComponentDetectionHint.SkipClassProperty && isMatching(functionPatterns.classProperty)(node)) return true;
793
+ if (hint & ComponentDetectionHint.SkipArrayMapArgument && AST.isArrayMapCall(node.parent)) return true;
794
+ return false;
795
+ }
796
+ /**
797
+ * Determines if a function node represents a valid React component definition
798
+ * @param context The rule context
799
+ * @param node The function node to check
800
+ * @param hint Component detection hints as bit flags
801
+ * @returns `true` if the node is a valid component definition, `false` otherwise
802
+ */
803
+ function isComponentDefinition(context, node, hint) {
804
+ if (isChildrenOfCreateElement(context, node) || isFunctionOfRenderMethod(node)) return false;
805
+ if (shouldExcludeBasedOnHint(node, hint)) return false;
806
+ const significantParent = AST.findParentNode(node, AST.isOneOf([
807
+ AST_NODE_TYPES.JSXExpressionContainer,
808
+ AST_NODE_TYPES.ArrowFunctionExpression,
809
+ AST_NODE_TYPES.FunctionExpression,
810
+ AST_NODE_TYPES.Property,
811
+ AST_NODE_TYPES.ClassBody
812
+ ]));
813
+ return significantParent == null || significantParent.type !== AST_NODE_TYPES.JSXExpressionContainer;
644
814
  }
815
+
816
+ //#endregion
817
+ //#region src/component/component-wrapper.ts
818
+ /**
819
+ * Check if the node is a call expression for a component wrapper
820
+ * @param context The ESLint rule context
821
+ * @param node The node to check
822
+ * @returns `true` if the node is a call expression for a component wrapper
823
+ */
645
824
  function isComponentWrapperCall(context, node) {
646
- if (node.type !== AST_NODE_TYPES.CallExpression) return false;
647
- return isMemoCall(context, node) || isForwardRefCall(context, node);
648
- }
825
+ if (node.type !== AST_NODE_TYPES.CallExpression) return false;
826
+ return isMemoCall(context, node) || isForwardRefCall(context, node);
827
+ }
828
+ /**
829
+ * Check if the node is a call expression for a component wrapper loosely
830
+ * @param context The ESLint rule context
831
+ * @param node The node to check
832
+ * @returns `true` if the node is a call expression for a component wrapper loosely
833
+ */
649
834
  function isComponentWrapperCallLoose(context, node) {
650
- if (node.type !== AST_NODE_TYPES.CallExpression) return false;
651
- return isComponentWrapperCall(context, node) || isUseCallbackCall(context, node);
835
+ if (node.type !== AST_NODE_TYPES.CallExpression) return false;
836
+ return isComponentWrapperCall(context, node) || isUseCallbackCall(context, node);
652
837
  }
653
838
 
654
- // src/component/component-id.ts
839
+ //#endregion
840
+ //#region src/component/component-id.ts
655
841
  function getFunctionComponentId(context, node) {
656
- const functionId = AST14.getFunctionId(node);
657
- if (functionId != null) {
658
- return functionId;
659
- }
660
- const { parent } = node;
661
- if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator && parent.parent.id.type === AST_NODE_TYPES.Identifier) {
662
- return parent.parent.id;
663
- }
664
- 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 && parent.parent.parent.id.type === AST_NODE_TYPES.Identifier) {
665
- return parent.parent.parent.id;
666
- }
667
- return _;
668
- }
669
-
670
- // src/component/component-flag.ts
671
- var ComponentFlag = {
672
- None: 0n,
673
- PureComponent: 1n << 0n,
674
- CreateElement: 1n << 1n,
675
- Memo: 1n << 2n,
676
- ForwardRef: 1n << 3n,
677
- Async: 1n << 4n
842
+ const functionId = AST.getFunctionId(node);
843
+ if (functionId != null) return functionId;
844
+ const { parent } = node;
845
+ if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator && parent.parent.id.type === AST_NODE_TYPES.Identifier) return parent.parent.id;
846
+ 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 && parent.parent.parent.id.type === AST_NODE_TYPES.Identifier) return parent.parent.parent.id;
847
+ return unit;
848
+ }
849
+
850
+ //#endregion
851
+ //#region src/component/component-flag.ts
852
+ const ComponentFlag = {
853
+ None: 0n,
854
+ PureComponent: 1n << 0n,
855
+ CreateElement: 1n << 1n,
856
+ Memo: 1n << 2n,
857
+ ForwardRef: 1n << 3n,
858
+ Async: 1n << 4n
678
859
  };
679
860
 
680
- // src/component/component-init-path.ts
861
+ //#endregion
862
+ //#region src/component/component-init-path.ts
681
863
  function getComponentFlagFromInitPath(initPath) {
682
- let flag = ComponentFlag.None;
683
- if (initPath != null && AST14.hasCallInFunctionInitPath("memo", initPath)) {
684
- flag |= ComponentFlag.Memo;
685
- }
686
- if (initPath != null && AST14.hasCallInFunctionInitPath("forwardRef", initPath)) {
687
- flag |= ComponentFlag.ForwardRef;
688
- }
689
- return flag;
864
+ let flag = ComponentFlag.None;
865
+ if (initPath != null && AST.hasCallInFunctionInitPath("memo", initPath)) flag |= ComponentFlag.Memo;
866
+ if (initPath != null && AST.hasCallInFunctionInitPath("forwardRef", initPath)) flag |= ComponentFlag.ForwardRef;
867
+ return flag;
690
868
  }
869
+
870
+ //#endregion
871
+ //#region src/component/component-name.ts
691
872
  function isComponentName(name) {
692
- return RegExp.COMPONENT_NAME.test(name);
873
+ return RegExp.COMPONENT_NAME.test(name);
693
874
  }
694
875
  function isComponentNameLoose(name) {
695
- return RegExp.COMPONENT_NAME_LOOSE.test(name);
876
+ return RegExp.COMPONENT_NAME_LOOSE.test(name);
696
877
  }
697
878
  function getComponentNameFromId(id) {
698
- if (id == null) return _;
699
- return Array.isArray(id) ? id.map((n) => n.name).join(".") : id.name;
879
+ if (id == null) return unit;
880
+ return Array.isArray(id) ? id.map((n) => n.name).join(".") : id.name;
700
881
  }
701
882
  function hasNoneOrLooseComponentName(context, fn) {
702
- const id = getFunctionComponentId(context, fn);
703
- if (id == null) return true;
704
- const name = Array.isArray(id) ? id.at(-1)?.name : id.name;
705
- return name != null && isComponentNameLoose(name);
883
+ const id = getFunctionComponentId(context, fn);
884
+ if (id == null) return true;
885
+ const name = Array.isArray(id) ? id.at(-1)?.name : id.name;
886
+ return name != null && isComponentNameLoose(name);
706
887
  }
707
888
 
708
- // src/component/component-collector.ts
889
+ //#endregion
890
+ //#region src/component/component-collector.ts
891
+ /**
892
+ * Get a ctx and listeners for the rule to collect function components
893
+ * @param context The ESLint rule context
894
+ * @param options The options to use
895
+ * @returns The component collector
896
+ */
709
897
  function useComponentCollector(context, options = {}) {
710
- const {
711
- collectDisplayName = false,
712
- collectHookCalls = false,
713
- hint = DEFAULT_COMPONENT_DETECTION_HINT
714
- } = options;
715
- const components = /* @__PURE__ */ new Map();
716
- const functionEntries = [];
717
- const getCurrentEntry = () => functionEntries.at(-1);
718
- const onFunctionEnter = (node) => {
719
- const key = getId();
720
- functionEntries.push({ key, node, hookCalls: [], isComponent: false });
721
- };
722
- const onFunctionExit = () => {
723
- const entry = functionEntries.at(-1);
724
- if (entry == null) return;
725
- if (!entry.isComponent) return functionEntries.pop();
726
- const shouldDrop = AST14.getNestedReturnStatements(entry.node.body).slice().reverse().some((r) => {
727
- return context.sourceCode.getScope(r).block === entry.node && r.argument != null && !isJsxLike(context.sourceCode, r.argument, hint);
728
- });
729
- if (shouldDrop) {
730
- components.delete(entry.key);
731
- }
732
- return functionEntries.pop();
733
- };
734
- const ctx = {
735
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
736
- getAllComponents(node) {
737
- return components;
738
- },
739
- getCurrentEntries() {
740
- return [...functionEntries];
741
- },
742
- getCurrentEntry
743
- };
744
- const listeners = {
745
- ":function[type]": onFunctionEnter,
746
- ":function[type]:exit": onFunctionExit,
747
- "ArrowFunctionExpression[body.type!='BlockStatement']"() {
748
- const entry = getCurrentEntry();
749
- if (entry == null) return;
750
- const { body } = entry.node;
751
- const isComponent = hasNoneOrLooseComponentName(context, entry.node) && isJsxLike(context.sourceCode, body, hint) && isValidComponentDefinition(context, entry.node, hint);
752
- if (!isComponent) return;
753
- const initPath = AST14.getFunctionInitPath(entry.node);
754
- const id = getFunctionComponentId(context, entry.node);
755
- const name = getComponentNameFromId(id);
756
- const key = getId();
757
- components.set(key, {
758
- id,
759
- key,
760
- kind: "function",
761
- name,
762
- node: entry.node,
763
- displayName: _,
764
- flag: getComponentFlagFromInitPath(initPath),
765
- hint,
766
- hookCalls: entry.hookCalls,
767
- initPath
768
- });
769
- },
770
- ...collectDisplayName ? {
771
- [Selector.DISPLAY_NAME_ASSIGNMENT_EXPRESSION](node) {
772
- const { left, right } = node;
773
- if (left.type !== AST_NODE_TYPES.MemberExpression) return;
774
- const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name : _;
775
- const component = [...components.values()].findLast(({ name }) => name != null && name === componentName);
776
- if (component == null) return;
777
- component.displayName = right;
778
- }
779
- } : {},
780
- ...collectHookCalls ? {
781
- "CallExpression[type]:exit"(node) {
782
- if (!isReactHookCall(node)) return;
783
- const entry = getCurrentEntry();
784
- if (entry == null) return;
785
- entry.hookCalls.push(node);
786
- }
787
- } : {},
788
- "ReturnStatement[type]"(node) {
789
- const entry = getCurrentEntry();
790
- if (entry == null) return;
791
- const isComponent = hasNoneOrLooseComponentName(context, entry.node) && isJsxLike(context.sourceCode, node.argument, hint) && isValidComponentDefinition(context, entry.node, hint);
792
- if (!isComponent) return;
793
- entry.isComponent = true;
794
- const initPath = AST14.getFunctionInitPath(entry.node);
795
- const id = getFunctionComponentId(context, entry.node);
796
- const name = getComponentNameFromId(id);
797
- components.set(entry.key, {
798
- id,
799
- key: entry.key,
800
- kind: "function",
801
- name,
802
- node: entry.node,
803
- displayName: _,
804
- flag: getComponentFlagFromInitPath(initPath),
805
- hint,
806
- hookCalls: entry.hookCalls,
807
- initPath
808
- });
809
- }
810
- };
811
- return { ctx, listeners };
898
+ const { collectDisplayName = false, collectHookCalls = false, hint = DEFAULT_COMPONENT_DETECTION_HINT } = options;
899
+ const components = /* @__PURE__ */ new Map();
900
+ const functionEntries = [];
901
+ const getCurrentEntry = () => functionEntries.at(-1);
902
+ const onFunctionEnter = (node) => {
903
+ const key = getId();
904
+ functionEntries.push({
905
+ key,
906
+ node,
907
+ hookCalls: [],
908
+ isComponent: false
909
+ });
910
+ };
911
+ const onFunctionExit = () => {
912
+ const entry = functionEntries.at(-1);
913
+ if (entry == null) return;
914
+ if (!entry.isComponent) return functionEntries.pop();
915
+ const rets = AST.getNestedReturnStatements(entry.node.body);
916
+ for (let i = rets.length - 1; i >= 0; i--) {
917
+ const ret = rets[i];
918
+ if (ret == null) continue;
919
+ const shouldDrop = context.sourceCode.getScope(ret).block === entry.node && ret.argument != null && !isJsxLike(context.sourceCode, ret.argument, hint);
920
+ if (shouldDrop) {
921
+ components.delete(entry.key);
922
+ break;
923
+ }
924
+ }
925
+ return functionEntries.pop();
926
+ };
927
+ const ctx = {
928
+ getAllComponents(node) {
929
+ return components;
930
+ },
931
+ getCurrentEntries() {
932
+ return [...functionEntries];
933
+ },
934
+ getCurrentEntry
935
+ };
936
+ const listeners = {
937
+ ":function[type]": onFunctionEnter,
938
+ ":function[type]:exit": onFunctionExit,
939
+ "ArrowFunctionExpression[body.type!='BlockStatement']"() {
940
+ const entry = getCurrentEntry();
941
+ if (entry == null) return;
942
+ const { body } = entry.node;
943
+ const isComponent = hasNoneOrLooseComponentName(context, entry.node) && isJsxLike(context.sourceCode, body, hint) && isComponentDefinition(context, entry.node, hint);
944
+ if (!isComponent) return;
945
+ const initPath = AST.getFunctionInitPath(entry.node);
946
+ const id = getFunctionComponentId(context, entry.node);
947
+ const name = getComponentNameFromId(id);
948
+ const key = getId();
949
+ components.set(key, {
950
+ id,
951
+ key,
952
+ kind: "function",
953
+ name,
954
+ node: entry.node,
955
+ displayName: unit,
956
+ flag: getComponentFlagFromInitPath(initPath),
957
+ hint,
958
+ hookCalls: entry.hookCalls,
959
+ initPath
960
+ });
961
+ },
962
+ ...collectDisplayName ? { [Selector.DISPLAY_NAME_ASSIGNMENT_EXPRESSION](node) {
963
+ const { left, right } = node;
964
+ if (left.type !== AST_NODE_TYPES.MemberExpression) return;
965
+ const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name : unit;
966
+ const component = [...components.values()].findLast(({ name }) => name != null && name === componentName);
967
+ if (component == null) return;
968
+ component.displayName = right;
969
+ } } : {},
970
+ ...collectHookCalls ? { "CallExpression[type]:exit"(node) {
971
+ if (!isReactHookCall(node)) return;
972
+ const entry = getCurrentEntry();
973
+ if (entry == null) return;
974
+ entry.hookCalls.push(node);
975
+ } } : {},
976
+ "ReturnStatement[type]"(node) {
977
+ const entry = getCurrentEntry();
978
+ if (entry == null) return;
979
+ const isComponent = hasNoneOrLooseComponentName(context, entry.node) && isJsxLike(context.sourceCode, node.argument, hint) && isComponentDefinition(context, entry.node, hint);
980
+ if (!isComponent) return;
981
+ entry.isComponent = true;
982
+ const initPath = AST.getFunctionInitPath(entry.node);
983
+ const id = getFunctionComponentId(context, entry.node);
984
+ const name = getComponentNameFromId(id);
985
+ components.set(entry.key, {
986
+ id,
987
+ key: entry.key,
988
+ kind: "function",
989
+ name,
990
+ node: entry.node,
991
+ displayName: unit,
992
+ flag: getComponentFlagFromInitPath(initPath),
993
+ hint,
994
+ hookCalls: entry.hookCalls,
995
+ initPath
996
+ });
997
+ }
998
+ };
999
+ return {
1000
+ ctx,
1001
+ listeners
1002
+ };
812
1003
  }
1004
+
1005
+ //#endregion
1006
+ //#region src/component/component-collector-legacy.ts
1007
+ /**
1008
+ * Get a ctx and listeners object for the rule to collect class components
1009
+ * @returns The context and listeners for the rule
1010
+ */
813
1011
  function useComponentCollectorLegacy() {
814
- const components = /* @__PURE__ */ new Map();
815
- const ctx = {
816
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
817
- getAllComponents(node) {
818
- return components;
819
- }
820
- };
821
- const collect = (node) => {
822
- if (!isClassComponent(node)) {
823
- return;
824
- }
825
- const id = AST14.getClassId(node);
826
- const key = getId();
827
- const flag = isPureComponent(node) ? ComponentFlag.PureComponent : ComponentFlag.None;
828
- components.set(
829
- key,
830
- {
831
- id,
832
- key,
833
- kind: "class",
834
- name: id?.name,
835
- node,
836
- // TODO: Get displayName of class component
837
- displayName: _,
838
- flag,
839
- hint: 0n,
840
- // TODO: Get methods of class component
841
- methods: []
842
- }
843
- );
844
- };
845
- const listeners = {
846
- "ClassDeclaration[type]": collect,
847
- "ClassExpression[type]": collect
848
- };
849
- return { ctx, listeners };
850
- }
851
- function isComponentDidCatch(node) {
852
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "componentDidCatch";
853
- }
854
- function isComponentDidMount(node) {
855
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "componentDidMount";
856
- }
857
- function isComponentDidUpdate(node) {
858
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "componentDidUpdate";
859
- }
860
- function isComponentWillMount(node) {
861
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "componentWillMount";
862
- }
863
- function isComponentWillReceiveProps(node) {
864
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "componentWillReceiveProps";
865
- }
866
- function isComponentWillUnmount(node) {
867
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "componentWillUnmount";
868
- }
869
- function isComponentWillUpdate(node) {
870
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "componentWillUpdate";
871
- }
872
- function isGetChildContext(node) {
873
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "getChildContext";
874
- }
875
- function isGetDefaultProps(node) {
876
- return AST14.isMethodOrProperty(node) && node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "getDefaultProps";
877
- }
878
- function isGetInitialState(node) {
879
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "getInitialState";
880
- }
881
- function isGetSnapshotBeforeUpdate(node) {
882
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "getSnapshotBeforeUpdate";
883
- }
884
- function isShouldComponentUpdate(node) {
885
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "shouldComponentUpdate";
886
- }
887
- function isUnsafeComponentWillMount(node) {
888
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "UNSAFE_componentWillMount";
889
- }
890
- function isUnsafeComponentWillReceiveProps(node) {
891
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "UNSAFE_componentWillReceiveProps";
892
- }
893
- function isUnsafeComponentWillUpdate(node) {
894
- return AST14.isMethodOrProperty(node) && !node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "UNSAFE_componentWillUpdate";
895
- }
896
- function isGetDerivedStateFromProps(node) {
897
- return AST14.isMethodOrProperty(node) && node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "getDerivedStateFromProps";
898
- }
899
- function isGetDerivedStateFromError(node) {
900
- return AST14.isMethodOrProperty(node) && node.static && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "getDerivedStateFromError";
901
- }
902
- var ComponentPhaseRelevance = birecord({
903
- mount: "unmount",
904
- setup: "cleanup"
905
- });
906
- var isInversePhase = dual(2, (a, b) => ComponentPhaseRelevance.get(a) === b);
907
- function isRenderLike(node) {
908
- return AST14.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "render" && node.parent.parent.type === AST_NODE_TYPES.ClassDeclaration;
909
- }
910
- function isFunctionOfRender(node) {
911
- if (!isRenderLike(node.parent)) {
912
- return false;
913
- }
914
- return isClassComponent(node.parent.parent.parent);
1012
+ const components = /* @__PURE__ */ new Map();
1013
+ const ctx = { getAllComponents(node) {
1014
+ return components;
1015
+ } };
1016
+ const collect = (node) => {
1017
+ if (!isClassComponent(node)) return;
1018
+ const id = AST.getClassId(node);
1019
+ const key = getId();
1020
+ const flag = isPureComponent(node) ? ComponentFlag.PureComponent : ComponentFlag.None;
1021
+ components.set(key, {
1022
+ id,
1023
+ key,
1024
+ kind: "class",
1025
+ name: id?.name,
1026
+ node,
1027
+ displayName: unit,
1028
+ flag,
1029
+ hint: 0n,
1030
+ methods: []
1031
+ });
1032
+ };
1033
+ const listeners = {
1034
+ "ClassDeclaration[type]": collect,
1035
+ "ClassExpression[type]": collect
1036
+ };
1037
+ return {
1038
+ ctx,
1039
+ listeners
1040
+ };
915
1041
  }
1042
+
1043
+ //#endregion
1044
+ //#region src/component/component-method.ts
1045
+ /**
1046
+ * Create a lifecycle method checker function
1047
+ * @param methodName The lifecycle method name
1048
+ * @param isStatic Whether the method is static
1049
+ */
1050
+ function createLifecycleChecker(methodName, isStatic) {
1051
+ return function(node) {
1052
+ return AST.isMethodOrProperty(node) && node.static === isStatic && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === methodName;
1053
+ };
1054
+ }
1055
+ const isRender = createLifecycleChecker("render", false);
1056
+ const isComponentDidCatch = createLifecycleChecker("componentDidCatch", false);
1057
+ const isComponentDidMount = createLifecycleChecker("componentDidMount", false);
1058
+ const isComponentDidUpdate = createLifecycleChecker("componentDidUpdate", false);
1059
+ const isComponentWillMount = createLifecycleChecker("componentWillMount", false);
1060
+ const isComponentWillReceiveProps = createLifecycleChecker("componentWillReceiveProps", false);
1061
+ const isComponentWillUnmount = createLifecycleChecker("componentWillUnmount", false);
1062
+ const isComponentWillUpdate = createLifecycleChecker("componentWillUpdate", false);
1063
+ const isGetChildContext = createLifecycleChecker("getChildContext", false);
1064
+ const isGetInitialState = createLifecycleChecker("getInitialState", false);
1065
+ const isGetSnapshotBeforeUpdate = createLifecycleChecker("getSnapshotBeforeUpdate", false);
1066
+ const isShouldComponentUpdate = createLifecycleChecker("shouldComponentUpdate", false);
1067
+ const isUnsafeComponentWillMount = createLifecycleChecker("UNSAFE_componentWillMount", false);
1068
+ const isUnsafeComponentWillReceiveProps = createLifecycleChecker("UNSAFE_componentWillReceiveProps", false);
1069
+ const isUnsafeComponentWillUpdate = createLifecycleChecker("UNSAFE_componentWillUpdate", false);
1070
+ const isGetDefaultProps = createLifecycleChecker("getDefaultProps", true);
1071
+ const isGetDerivedStateFromProps = createLifecycleChecker("getDerivedStateFromProps", true);
1072
+ const isGetDerivedStateFromError = createLifecycleChecker("getDerivedStateFromError", true);
1073
+
1074
+ //#endregion
1075
+ //#region src/component/component-phase.ts
1076
+ const ComponentPhaseRelevance = birecord({
1077
+ mount: "unmount",
1078
+ setup: "cleanup"
1079
+ });
1080
+ const isInversePhase = dual(2, (a, b) => ComponentPhaseRelevance.get(a) === b);
1081
+
1082
+ //#endregion
1083
+ //#region src/component/component-render-prop.ts
1084
+ /**
1085
+ * Unsafe check whether given node is a render function
1086
+ * ```tsx
1087
+ * const renderRow = () => <div />
1088
+ * ` ^^^^^^^^^^^^`
1089
+ * _ = <Component renderRow={() => <div />} />
1090
+ * ` ^^^^^^^^^^^^^ `
1091
+ * ```
1092
+ * @param context The rule context
1093
+ * @param node The AST node to check
1094
+ * @returns `true` if node is a render function, `false` if not
1095
+ */
916
1096
  function isRenderFunctionLoose(context, node) {
917
- const { body, parent } = node;
918
- if (AST14.getFunctionId(node)?.name.startsWith("render")) {
919
- return parent.type === AST_NODE_TYPES.JSXExpressionContainer && parent.parent.type === AST_NODE_TYPES.JSXAttribute && parent.parent.name.type === AST_NODE_TYPES.JSXIdentifier && parent.parent.name.name.startsWith("render");
920
- }
921
- return isJsxLike(
922
- context.sourceCode,
923
- body,
924
- JSXDetectionHint.SkipNullLiteral | JSXDetectionHint.SkipUndefined | JSXDetectionHint.StrictLogical | JSXDetectionHint.StrictConditional
925
- );
926
- }
1097
+ const { body, parent } = node;
1098
+ if (AST.getFunctionId(node)?.name.startsWith("render")) return parent.type === AST_NODE_TYPES.JSXExpressionContainer && parent.parent.type === AST_NODE_TYPES.JSXAttribute && parent.parent.name.type === AST_NODE_TYPES.JSXIdentifier && parent.parent.name.name.startsWith("render");
1099
+ return isJsxLike(context.sourceCode, body, JSXDetectionHint.SkipNullLiteral | JSXDetectionHint.SkipUndefined | JSXDetectionHint.StrictLogical | JSXDetectionHint.StrictConditional);
1100
+ }
1101
+ /**
1102
+ * Unsafe check whether given JSXAttribute is a render prop
1103
+ * ```tsx
1104
+ * _ = <Component renderRow={() => <div />} />
1105
+ * ` ^^^^^^^^^^^^^^^^^^^^^^^^^ `
1106
+ * ```
1107
+ * @param context The rule context
1108
+ * @param node The AST node to check
1109
+ * @returns `true` if node is a render prop, `false` if not
1110
+ */
927
1111
  function isRenderPropLoose(context, node) {
928
- if (node.name.type !== AST_NODE_TYPES.JSXIdentifier) {
929
- return false;
930
- }
931
- return node.name.name.startsWith("render") && node.value?.type === AST_NODE_TYPES.JSXExpressionContainer && AST14.isFunction(node.value.expression) && isRenderFunctionLoose(context, node.value.expression);
932
- }
1112
+ if (node.name.type !== AST_NODE_TYPES.JSXIdentifier) return false;
1113
+ return node.name.name.startsWith("render") && node.value?.type === AST_NODE_TYPES.JSXExpressionContainer && AST.isFunction(node.value.expression) && isRenderFunctionLoose(context, node.value.expression);
1114
+ }
1115
+ /**
1116
+ * Unsafe check whether given node is declared directly inside a render property
1117
+ * ```tsx
1118
+ * const rows = { render: () => <div /> }
1119
+ * ` ^^^^^^^^^^^^^ `
1120
+ * _ = <Component rows={ [{ render: () => <div /> }] } />
1121
+ * ` ^^^^^^^^^^^^^ `
1122
+ * ```
1123
+ * @internal
1124
+ * @param node The AST node to check
1125
+ * @returns `true` if component is declared inside a render property, `false` if not
1126
+ */
933
1127
  function isDirectValueOfRenderPropertyLoose(node) {
934
- const matching = (node2) => {
935
- return node2.type === AST_NODE_TYPES.Property && node2.key.type === AST_NODE_TYPES.Identifier && node2.key.name.startsWith("render");
936
- };
937
- return matching(node) || node.parent != null && matching(node.parent);
938
- }
1128
+ const matching = (node$1) => {
1129
+ return node$1.type === AST_NODE_TYPES.Property && node$1.key.type === AST_NODE_TYPES.Identifier && node$1.key.name.startsWith("render");
1130
+ };
1131
+ return matching(node) || node.parent != null && matching(node.parent);
1132
+ }
1133
+ /**
1134
+ * Unsafe check whether given node is declared inside a render prop
1135
+ * ```tsx
1136
+ * _ = <Component renderRow={"node"} />
1137
+ * ` ^^^^^^ `
1138
+ * _ = <Component rows={ [{ render: "node" }] } />
1139
+ * ` ^^^^^^ `
1140
+ * ```
1141
+ * @param node The AST node to check
1142
+ * @returns `true` if component is declared inside a render prop, `false` if not
1143
+ */
939
1144
  function isDeclaredInRenderPropLoose(node) {
940
- if (isDirectValueOfRenderPropertyLoose(node)) {
941
- return true;
942
- }
943
- const parent = AST14.findParentNode(node, AST14.is(AST_NODE_TYPES.JSXExpressionContainer))?.parent;
944
- if (parent?.type !== AST_NODE_TYPES.JSXAttribute) {
945
- return false;
946
- }
947
- return parent.name.type === AST_NODE_TYPES.JSXIdentifier && parent.name.name.startsWith("render");
1145
+ if (isDirectValueOfRenderPropertyLoose(node)) return true;
1146
+ const parent = AST.findParentNode(node, AST.is(AST_NODE_TYPES.JSXExpressionContainer))?.parent;
1147
+ if (parent?.type !== AST_NODE_TYPES.JSXAttribute) return false;
1148
+ return parent.name.type === AST_NODE_TYPES.JSXIdentifier && parent.name.name.startsWith("render");
948
1149
  }
1150
+
1151
+ //#endregion
1152
+ //#region src/component/component-state.ts
949
1153
  function isThisSetState(node) {
950
- const { callee } = node;
951
- return callee.type === AST_NODE_TYPES.MemberExpression && AST14.isThisExpression(callee.object) && callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "setState";
1154
+ const { callee } = node;
1155
+ return callee.type === AST_NODE_TYPES.MemberExpression && AST.isThisExpression(callee.object) && callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "setState";
952
1156
  }
953
1157
  function isAssignmentToThisState(node) {
954
- const { left } = node;
955
- return left.type === AST_NODE_TYPES.MemberExpression && AST14.isThisExpression(left.object) && AST14.getPropertyName(left.property) === "state";
1158
+ const { left } = node;
1159
+ return left.type === AST_NODE_TYPES.MemberExpression && AST.isThisExpression(left.object) && AST.getPropertyName(left.property) === "state";
1160
+ }
1161
+
1162
+ //#endregion
1163
+ //#region src/type/type-is.ts
1164
+ /** @internal */
1165
+ const isAnyType = (type) => isTypeFlagSet(type, ts.TypeFlags.TypeParameter | ts.TypeFlags.Any);
1166
+ /** @internal */
1167
+ const isBigIntType = (type) => isTypeFlagSet(type, ts.TypeFlags.BigIntLike);
1168
+ /** @internal */
1169
+ const isBooleanType = (type) => isTypeFlagSet(type, ts.TypeFlags.BooleanLike);
1170
+ /** @internal */
1171
+ const isEnumType = (type) => isTypeFlagSet(type, ts.TypeFlags.EnumLike);
1172
+ /** @internal */
1173
+ const isFalsyBigIntType = (type) => type.isLiteral() && isMatching({ value: { base10Value: "0" } }, type);
1174
+ /** @internal */
1175
+ const isFalsyNumberType = (type) => type.isNumberLiteral() && type.value === 0;
1176
+ /** @internal */
1177
+ const isFalsyStringType = (type) => type.isStringLiteral() && type.value === "";
1178
+ /** @internal */
1179
+ const isNeverType = (type) => isTypeFlagSet(type, ts.TypeFlags.Never);
1180
+ /** @internal */
1181
+ const isNullishType = (type) => isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.VoidLike);
1182
+ /** @internal */
1183
+ const isNumberType = (type) => isTypeFlagSet(type, ts.TypeFlags.NumberLike);
1184
+ /** @internal */
1185
+ 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);
1186
+ /** @internal */
1187
+ const isStringType = (type) => isTypeFlagSet(type, ts.TypeFlags.StringLike);
1188
+ /** @internal */
1189
+ const isTruthyBigIntType = (type) => type.isLiteral() && isMatching({ value: { base10Value: P.not("0") } }, type);
1190
+ /** @internal */
1191
+ const isTruthyNumberType = (type) => type.isNumberLiteral() && type.value !== 0;
1192
+ /** @internal */
1193
+ const isTruthyStringType = (type) => type.isStringLiteral() && type.value !== "";
1194
+ /** @internal */
1195
+ const isUnknownType = (type) => isTypeFlagSet(type, ts.TypeFlags.Unknown);
1196
+
1197
+ //#endregion
1198
+ //#region src/type/type-variant.ts
1199
+ /**
1200
+ * Ported from https://github.com/typescript-eslint/typescript-eslint/blob/eb736bbfc22554694400e6a4f97051d845d32e0b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts#L826 with some enhancements
1201
+ * Get the variants of an array of types.
1202
+ * @param types The types to get the variants of
1203
+ * @returns The variants of the types
1204
+ * @internal
1205
+ */
1206
+ function getTypeVariants(types) {
1207
+ const variants = /* @__PURE__ */ new Set();
1208
+ if (types.some(isUnknownType)) {
1209
+ variants.add("unknown");
1210
+ return variants;
1211
+ }
1212
+ if (types.some(isNullishType)) variants.add("nullish");
1213
+ const booleans = types.filter(isBooleanType);
1214
+ const boolean0 = booleans[0];
1215
+ if (booleans.length === 1 && boolean0 != null) {
1216
+ if (isFalseLiteralType(boolean0)) variants.add("falsy boolean");
1217
+ else if (isTrueLiteralType(boolean0)) variants.add("truthy boolean");
1218
+ } else if (booleans.length === 2) variants.add("boolean");
1219
+ const strings = types.filter(isStringType);
1220
+ if (strings.length > 0) {
1221
+ const evaluated = match(strings).when((types$1) => types$1.every(isTruthyStringType), () => "truthy string").when((types$1) => types$1.every(isFalsyStringType), () => "falsy string").otherwise(() => "string");
1222
+ variants.add(evaluated);
1223
+ }
1224
+ const bigints = types.filter(isBigIntType);
1225
+ if (bigints.length > 0) {
1226
+ const evaluated = match(bigints).when((types$1) => types$1.every(isTruthyBigIntType), () => "truthy bigint").when((types$1) => types$1.every(isFalsyBigIntType), () => "falsy bigint").otherwise(() => "bigint");
1227
+ variants.add(evaluated);
1228
+ }
1229
+ const numbers = types.filter(isNumberType);
1230
+ if (numbers.length > 0) {
1231
+ const evaluated = match(numbers).when((types$1) => types$1.every(isTruthyNumberType), () => "truthy number").when((types$1) => types$1.every(isFalsyNumberType), () => "falsy number").otherwise(() => "number");
1232
+ variants.add(evaluated);
1233
+ }
1234
+ if (types.some(isEnumType)) variants.add("enum");
1235
+ if (types.some(isObjectType)) variants.add("object");
1236
+ if (types.some(isAnyType)) variants.add("any");
1237
+ if (types.some(isNeverType)) variants.add("never");
1238
+ return variants;
956
1239
  }
957
1240
 
958
- export { ComponentDetectionHint, ComponentFlag, ComponentPhaseRelevance, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JSXDetectionHint, REACT_BUILTIN_HOOK_NAMES, findParentAttribute, getAttribute, getAttributeName, getAttributeValue, getComponentFlagFromInitPath, getComponentNameFromId, getElementType, getFunctionComponentId, getInstanceId, hasAnyAttribute, hasAttribute, hasEveryAttribute, hasNoneOrLooseComponentName, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOfCreateElement, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFragmentElement, isFunctionOfRender, isFunctionOfRenderMethod, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isHostElement, isInitializedFromReact, isInstanceIdEqual, isInversePhase, isJsxLike, isJsxText, isKeyedElement, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isReactHook, isReactHookCall, isReactHookCallWithName, isReactHookCallWithNameAlias, isReactHookCallWithNameLoose, isReactHookId, isReactHookName, isRenderFunctionLoose, isRenderLike, isRenderMethodLike, isRenderPropLoose, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectCallLoose, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseSyncExternalStoreCall, isUseTransitionCall, isValidComponentDefinition, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
1241
+ //#endregion
1242
+ export { ComponentDetectionHint, ComponentFlag, ComponentPhaseRelevance, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JSXDetectionHint, REACT_BUILTIN_HOOK_NAMES, findParentAttribute, getAttribute, getAttributeName, getComponentFlagFromInitPath, getComponentNameFromId, getElementType, getFunctionComponentId, getInstanceId, getTypeVariants, hasAnyAttribute, hasAttribute, hasEveryAttribute, hasNoneOrLooseComponentName, isAnyType, isAssignmentToThisState, isBigIntType, isBooleanType, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOfCreateElement, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isEnumType, isFalseLiteralType, isFalsyBigIntType, isFalsyNumberType, isFalsyStringType, isForwardRef, isForwardRefCall, isFragmentElement, isFunctionOfRenderMethod, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isHostElement, isInitializedFromReact, isInstanceIdEqual, isInversePhase, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isNeverType, isNullishType, isNumberType, isObjectType, isPureComponent, isReactAPI, isReactAPICall, isReactHook, isReactHookCall, isReactHookCallWithName, isReactHookCallWithNameAlias, isReactHookCallWithNameLoose, isReactHookId, isReactHookName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, isShouldComponentUpdate, isStringType, isThisSetState, isTrueLiteralType, isTruthyBigIntType, isTruthyNumberType, isTruthyStringType, isUnknownType, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectCallLoose, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseSyncExternalStoreCall, isUseTransitionCall, resolveAttributeValue, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };