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