@eslint-react/core 2.0.0-beta.20 → 2.0.0-beta.200

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