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