@eslint-react/core 2.0.0-next.6 → 2.0.0-next.61

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