@eslint-react/core 3.0.0-next.8 → 3.0.0-next.80
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 +348 -326
- package/dist/index.js +715 -550
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -1,18 +1,219 @@
|
|
|
1
|
-
import { findImportSource, findProperty, findVariable, getVariableDefinitionNode } from "@eslint-react/var";
|
|
2
1
|
import * as ast from "@eslint-react/ast";
|
|
3
|
-
import { constFalse, constTrue, dual, flip, getOrElseUpdate, identity, unit } from "@eslint-react/eff";
|
|
4
2
|
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
5
|
-
import {
|
|
6
|
-
import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
3
|
+
import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
7
4
|
import { P, match } from "ts-pattern";
|
|
5
|
+
import { IdGenerator, RE_ANNOTATION_JSX, RE_ANNOTATION_JSX_FRAG, RE_ANNOTATION_JSX_IMPORT_SOURCE, RE_ANNOTATION_JSX_RUNTIME, RE_COMPONENT_NAME, RE_COMPONENT_NAME_LOOSE } from "@eslint-react/shared";
|
|
6
|
+
import { resolve } from "@eslint-react/var";
|
|
8
7
|
import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from "@typescript-eslint/utils";
|
|
9
8
|
|
|
9
|
+
//#region ../../.pkgs/eff/dist/index.js
|
|
10
|
+
/**
|
|
11
|
+
* Returns its argument.
|
|
12
|
+
*
|
|
13
|
+
* @param x - The value to return.
|
|
14
|
+
* @returns The input value unchanged.
|
|
15
|
+
*/
|
|
16
|
+
function identity(x) {
|
|
17
|
+
return x;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates a function that can be used in a data-last (aka `pipe`able) or
|
|
21
|
+
* data-first style.
|
|
22
|
+
*
|
|
23
|
+
* The first parameter to `dual` is either the arity of the uncurried function
|
|
24
|
+
* or a predicate that determines if the function is being used in a data-first
|
|
25
|
+
* or data-last style.
|
|
26
|
+
*
|
|
27
|
+
* Using the arity is the most common use case, but there are some cases where
|
|
28
|
+
* you may want to use a predicate. For example, if you have a function that
|
|
29
|
+
* takes an optional argument, you can use a predicate to determine if the
|
|
30
|
+
* function is being used in a data-first or data-last style.
|
|
31
|
+
*
|
|
32
|
+
* You can pass either the arity of the uncurried function or a predicate
|
|
33
|
+
* which determines if the function is being used in a data-first or
|
|
34
|
+
* data-last style.
|
|
35
|
+
*
|
|
36
|
+
* **Example** (Using arity to determine data-first or data-last style)
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { dual, pipe } from "effect/Function"
|
|
40
|
+
*
|
|
41
|
+
* const sum = dual<
|
|
42
|
+
* (that: number) => (self: number) => number,
|
|
43
|
+
* (self: number, that: number) => number
|
|
44
|
+
* >(2, (self, that) => self + that)
|
|
45
|
+
*
|
|
46
|
+
* console.log(sum(2, 3)) // 5
|
|
47
|
+
* console.log(pipe(2, sum(3))) // 5
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* **Example** (Using call signatures to define the overloads)
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { dual, pipe } from "effect/Function"
|
|
54
|
+
*
|
|
55
|
+
* const sum: {
|
|
56
|
+
* (that: number): (self: number) => number
|
|
57
|
+
* (self: number, that: number): number
|
|
58
|
+
* } = dual(2, (self: number, that: number): number => self + that)
|
|
59
|
+
*
|
|
60
|
+
* console.log(sum(2, 3)) // 5
|
|
61
|
+
* console.log(pipe(2, sum(3))) // 5
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* **Example** (Using a predicate to determine data-first or data-last style)
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { dual, pipe } from "effect/Function"
|
|
68
|
+
*
|
|
69
|
+
* const sum = dual<
|
|
70
|
+
* (that: number) => (self: number) => number,
|
|
71
|
+
* (self: number, that: number) => number
|
|
72
|
+
* >(
|
|
73
|
+
* (args) => args.length === 2,
|
|
74
|
+
* (self, that) => self + that
|
|
75
|
+
* )
|
|
76
|
+
*
|
|
77
|
+
* console.log(sum(2, 3)) // 5
|
|
78
|
+
* console.log(pipe(2, sum(3))) // 5
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @param arity - The arity of the uncurried function or a predicate that determines if the function is being used in a data-first or data-last style.
|
|
82
|
+
* @param body - The function to be curried.
|
|
83
|
+
* @since 1.0.0
|
|
84
|
+
*/
|
|
85
|
+
const dual = function(arity, body) {
|
|
86
|
+
if (typeof arity === "function") return function() {
|
|
87
|
+
return arity(arguments) ? body.apply(this, arguments) : ((self) => body(self, ...arguments));
|
|
88
|
+
};
|
|
89
|
+
switch (arity) {
|
|
90
|
+
case 0:
|
|
91
|
+
case 1: throw new RangeError(`Invalid arity ${arity}`);
|
|
92
|
+
case 2: return function(a, b) {
|
|
93
|
+
if (arguments.length >= 2) return body(a, b);
|
|
94
|
+
return function(self) {
|
|
95
|
+
return body(self, a);
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
case 3: return function(a, b, c) {
|
|
99
|
+
if (arguments.length >= 3) return body(a, b, c);
|
|
100
|
+
return function(self) {
|
|
101
|
+
return body(self, a, b);
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
default: return function() {
|
|
105
|
+
if (arguments.length >= arity) return body.apply(this, arguments);
|
|
106
|
+
const args = arguments;
|
|
107
|
+
return function(self) {
|
|
108
|
+
return body(self, ...args);
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Do nothing and return `false`.
|
|
115
|
+
*
|
|
116
|
+
* @returns false
|
|
117
|
+
*/
|
|
118
|
+
function constFalse() {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Reverses the order of arguments for a curried function.
|
|
123
|
+
*
|
|
124
|
+
* @param f - The function to flip.
|
|
125
|
+
* @returns A new function with the argument order reversed.
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* import * as assert from "node:assert"
|
|
129
|
+
* import { flip } from "effect/Function"
|
|
130
|
+
*
|
|
131
|
+
* const f = (a: number) => (b: string) => a - b.length
|
|
132
|
+
*
|
|
133
|
+
* assert.deepStrictEqual(flip(f)('aaa')(2), -1)
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @since 1.0.0
|
|
137
|
+
*/
|
|
138
|
+
const flip = (f) => (...b) => (...a) => f(...a)(...b);
|
|
139
|
+
/**
|
|
140
|
+
* Composes two functions, `ab` and `bc` into a single function that takes in an argument `a` of type `A` and returns a result of type `C`.
|
|
141
|
+
* The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`.
|
|
142
|
+
*
|
|
143
|
+
* @param self - The first function to apply (or the composed function in data-last style).
|
|
144
|
+
* @param bc - The second function to apply.
|
|
145
|
+
* @returns A composed function that applies both functions in sequence.
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* import * as assert from "node:assert"
|
|
149
|
+
* import { compose } from "effect/Function"
|
|
150
|
+
*
|
|
151
|
+
* const increment = (n: number) => n + 1;
|
|
152
|
+
* const square = (n: number) => n * n;
|
|
153
|
+
*
|
|
154
|
+
* assert.strictEqual(compose(increment, square)(2), 9);
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* @since 1.0.0
|
|
158
|
+
*/
|
|
159
|
+
const compose = dual(2, (ab, bc) => (a) => bc(ab(a)));
|
|
160
|
+
function getOrElseUpdate(map, key, callback) {
|
|
161
|
+
if (map.has(key)) return map.get(key);
|
|
162
|
+
const value = callback();
|
|
163
|
+
map.set(key, value);
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/api/find-import-source.ts
|
|
169
|
+
/**
|
|
170
|
+
* Get the arguments of a require expression
|
|
171
|
+
* @param node The node to match
|
|
172
|
+
* @returns The require expression arguments or null if the node is not a require expression
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
function getRequireExpressionArguments(node) {
|
|
176
|
+
return match(node).with({
|
|
177
|
+
type: AST_NODE_TYPES.CallExpression,
|
|
178
|
+
arguments: P.select(),
|
|
179
|
+
callee: {
|
|
180
|
+
type: AST_NODE_TYPES.Identifier,
|
|
181
|
+
name: "require"
|
|
182
|
+
}
|
|
183
|
+
}, identity).with({
|
|
184
|
+
type: AST_NODE_TYPES.MemberExpression,
|
|
185
|
+
object: P.select()
|
|
186
|
+
}, getRequireExpressionArguments).otherwise(() => null);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Find the import source of a variable
|
|
190
|
+
* @param name The variable name
|
|
191
|
+
* @param initialScope The initial scope to search
|
|
192
|
+
* @returns The import source or null if not found
|
|
193
|
+
*/
|
|
194
|
+
function findImportSource(name, initialScope) {
|
|
195
|
+
const latestDef = findVariable(initialScope, name)?.defs.at(-1);
|
|
196
|
+
if (latestDef == null) return null;
|
|
197
|
+
const { node, parent } = latestDef;
|
|
198
|
+
if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
|
|
199
|
+
const { init } = node;
|
|
200
|
+
if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) return findImportSource(init.object.name, initialScope);
|
|
201
|
+
if (init.type === AST_NODE_TYPES.Identifier) return findImportSource(init.name, initialScope);
|
|
202
|
+
const arg0 = getRequireExpressionArguments(init)?.[0];
|
|
203
|
+
if (arg0 == null || !ast.isLiteral(arg0, "string")) return null;
|
|
204
|
+
return arg0.value;
|
|
205
|
+
}
|
|
206
|
+
if (parent?.type === AST_NODE_TYPES.ImportDeclaration) return parent.source.value;
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//#endregion
|
|
10
211
|
//#region src/api/is-from-react.ts
|
|
11
212
|
/**
|
|
12
213
|
* Check if a variable is initialized from React import
|
|
13
214
|
* @param name The variable name
|
|
14
215
|
* @param initialScope The initial scope
|
|
15
|
-
* @param importSource Alternative import source of React (
|
|
216
|
+
* @param importSource Alternative import source of React (ex: "preact/compat")
|
|
16
217
|
* @returns True if the variable is initialized or derived from React import
|
|
17
218
|
*/
|
|
18
219
|
function isInitializedFromReact(name, initialScope, importSource = "react") {
|
|
@@ -25,7 +226,7 @@ function isInitializedFromReact(name, initialScope, importSource = "react") {
|
|
|
25
226
|
* if a variable is initialized from React Native import
|
|
26
227
|
* @param name The variable name
|
|
27
228
|
* @param initialScope The initial scope
|
|
28
|
-
* @param importSource Alternative import source of React Native (
|
|
229
|
+
* @param importSource Alternative import source of React Native (ex: "react-native-web")
|
|
29
230
|
* @returns True if the variable is initialized from React Native import
|
|
30
231
|
*/
|
|
31
232
|
function isInitializedFromReactNative(name, initialScope, importSource = "react-native") {
|
|
@@ -40,7 +241,7 @@ function isInitializedFromReactNative(name, initialScope, importSource = "react-
|
|
|
40
241
|
//#region src/api/is-react-api.ts
|
|
41
242
|
/**
|
|
42
243
|
* Check if the node is a React API identifier or member expression
|
|
43
|
-
* @param api The React API name to check against (
|
|
244
|
+
* @param api The React API name to check against (ex: "useState", "React.memo")
|
|
44
245
|
* @returns A predicate function to check if a node matches the API
|
|
45
246
|
*/
|
|
46
247
|
function isReactAPI(api) {
|
|
@@ -260,7 +461,7 @@ function isHookId(id) {
|
|
|
260
461
|
|
|
261
462
|
//#endregion
|
|
262
463
|
//#region src/hook/hook-collector.ts
|
|
263
|
-
const idGen$2 = new IdGenerator("
|
|
464
|
+
const idGen$2 = new IdGenerator("hook:");
|
|
264
465
|
/**
|
|
265
466
|
* Get a ctx and visitor object for the rule to collect hooks
|
|
266
467
|
* @param context The ESLint rule context
|
|
@@ -270,7 +471,7 @@ function useHookCollector(context) {
|
|
|
270
471
|
const hooks = /* @__PURE__ */ new Map();
|
|
271
472
|
const functionEntries = [];
|
|
272
473
|
const getText = (n) => context.sourceCode.getText(n);
|
|
273
|
-
const getCurrentEntry = () => functionEntries.at(-1);
|
|
474
|
+
const getCurrentEntry = () => functionEntries.at(-1) ?? null;
|
|
274
475
|
const onFunctionEnter = (node) => {
|
|
275
476
|
const id = ast.getFunctionId(node);
|
|
276
477
|
const key = idGen$2.next();
|
|
@@ -284,11 +485,11 @@ function useHookCollector(context) {
|
|
|
284
485
|
key,
|
|
285
486
|
kind: "function",
|
|
286
487
|
name: ast.getFullyQualifiedName(id, getText),
|
|
287
|
-
node,
|
|
288
488
|
directives: [],
|
|
289
489
|
flag: 0n,
|
|
290
490
|
hint: 0n,
|
|
291
|
-
hookCalls: []
|
|
491
|
+
hookCalls: [],
|
|
492
|
+
node
|
|
292
493
|
});
|
|
293
494
|
};
|
|
294
495
|
const onFunctionExit = () => {
|
|
@@ -315,150 +516,6 @@ function useHookCollector(context) {
|
|
|
315
516
|
};
|
|
316
517
|
}
|
|
317
518
|
|
|
318
|
-
//#endregion
|
|
319
|
-
//#region src/jsx/jsx-stringify.ts
|
|
320
|
-
/**
|
|
321
|
-
* Incomplete but sufficient stringification of JSX nodes for common use cases
|
|
322
|
-
*
|
|
323
|
-
* @param node JSX node from TypeScript ESTree
|
|
324
|
-
* @returns String representation of the JSX node
|
|
325
|
-
*/
|
|
326
|
-
function stringifyJsx(node) {
|
|
327
|
-
switch (node.type) {
|
|
328
|
-
case AST_NODE_TYPES.JSXIdentifier: return node.name;
|
|
329
|
-
case AST_NODE_TYPES.JSXNamespacedName: return `${node.namespace.name}:${node.name.name}`;
|
|
330
|
-
case AST_NODE_TYPES.JSXMemberExpression: return `${stringifyJsx(node.object)}.${stringifyJsx(node.property)}`;
|
|
331
|
-
case AST_NODE_TYPES.JSXText: return node.value;
|
|
332
|
-
case AST_NODE_TYPES.JSXOpeningElement: return `<${stringifyJsx(node.name)}>`;
|
|
333
|
-
case AST_NODE_TYPES.JSXClosingElement: return `</${stringifyJsx(node.name)}>`;
|
|
334
|
-
case AST_NODE_TYPES.JSXOpeningFragment: return "<>";
|
|
335
|
-
case AST_NODE_TYPES.JSXClosingFragment: return "</>";
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
//#endregion
|
|
340
|
-
//#region src/jsx/jsx-attribute-name.ts
|
|
341
|
-
/**
|
|
342
|
-
* Get the stringified name of a JSX attribute
|
|
343
|
-
* @param context The ESLint rule context
|
|
344
|
-
* @param node The JSX attribute node
|
|
345
|
-
* @returns The name of the attribute
|
|
346
|
-
*/
|
|
347
|
-
function getJsxAttributeName(context, node) {
|
|
348
|
-
return stringifyJsx(node.name);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
//#endregion
|
|
352
|
-
//#region src/jsx/jsx-attribute.ts
|
|
353
|
-
/**
|
|
354
|
-
* Creates a helper function to find a specific JSX attribute by name
|
|
355
|
-
* Handles direct attributes and spread attributes (variables or object literals)
|
|
356
|
-
* @param context The ESLint rule context
|
|
357
|
-
* @param node The JSX element node
|
|
358
|
-
* @param initialScope (Optional) The initial scope to use for variable resolution
|
|
359
|
-
*/
|
|
360
|
-
function getJsxAttribute(context, node, initialScope) {
|
|
361
|
-
const scope = initialScope ?? context.sourceCode.getScope(node);
|
|
362
|
-
const attributes = node.openingElement.attributes;
|
|
363
|
-
/**
|
|
364
|
-
* Finds the last occurrence of a specific attribute
|
|
365
|
-
* @param name The attribute name to search for
|
|
366
|
-
*/
|
|
367
|
-
return (name) => {
|
|
368
|
-
return attributes.findLast((attr) => {
|
|
369
|
-
if (attr.type === AST_NODE_TYPES.JSXAttribute) return getJsxAttributeName(context, attr) === name;
|
|
370
|
-
switch (attr.argument.type) {
|
|
371
|
-
case AST_NODE_TYPES.Identifier: {
|
|
372
|
-
const variableNode = getVariableDefinitionNode(findVariable(attr.argument.name, scope), 0);
|
|
373
|
-
if (variableNode?.type === AST_NODE_TYPES.ObjectExpression) return findProperty(name, variableNode.properties, scope) != null;
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
case AST_NODE_TYPES.ObjectExpression: return findProperty(name, attr.argument.properties, scope) != null;
|
|
377
|
-
}
|
|
378
|
-
return false;
|
|
379
|
-
});
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
//#endregion
|
|
384
|
-
//#region src/jsx/jsx-attribute-value.ts
|
|
385
|
-
/**
|
|
386
|
-
* Resolve the static value of a JSX attribute or spread attribute
|
|
387
|
-
*
|
|
388
|
-
* @param context - The ESLint rule context
|
|
389
|
-
* @param attribute - The JSX attribute node to resolve
|
|
390
|
-
* @returns An object containing the value kind, the node (if applicable), and a `toStatic` helper
|
|
391
|
-
*/
|
|
392
|
-
function resolveJsxAttributeValue(context, attribute) {
|
|
393
|
-
const initialScope = context.sourceCode.getScope(attribute);
|
|
394
|
-
/**
|
|
395
|
-
* Handles standard JSX attributes (e.g., prop="value", prop={value}, prop)
|
|
396
|
-
* @param node The JSX attribute node
|
|
397
|
-
*/
|
|
398
|
-
function handleJsxAttribute(node) {
|
|
399
|
-
if (node.value == null) return {
|
|
400
|
-
kind: "boolean",
|
|
401
|
-
toStatic() {
|
|
402
|
-
return true;
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
switch (node.value.type) {
|
|
406
|
-
case AST_NODE_TYPES.Literal: {
|
|
407
|
-
const staticValue = node.value.value;
|
|
408
|
-
return {
|
|
409
|
-
kind: "literal",
|
|
410
|
-
node: node.value,
|
|
411
|
-
toStatic() {
|
|
412
|
-
return staticValue;
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
case AST_NODE_TYPES.JSXExpressionContainer: {
|
|
417
|
-
const expr = node.value.expression;
|
|
418
|
-
return {
|
|
419
|
-
kind: "expression",
|
|
420
|
-
node: expr,
|
|
421
|
-
toStatic() {
|
|
422
|
-
return getStaticValue(expr, initialScope)?.value;
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
case AST_NODE_TYPES.JSXElement: return {
|
|
427
|
-
kind: "element",
|
|
428
|
-
node: node.value,
|
|
429
|
-
toStatic() {
|
|
430
|
-
return unit;
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
case AST_NODE_TYPES.JSXSpreadChild: return {
|
|
434
|
-
kind: "spreadChild",
|
|
435
|
-
node: node.value.expression,
|
|
436
|
-
toStatic() {
|
|
437
|
-
return unit;
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Handles JSX spread attributes (e.g., {...props})
|
|
444
|
-
* @param node The JSX spread attribute node
|
|
445
|
-
*/
|
|
446
|
-
function handleJsxSpreadAttribute(node) {
|
|
447
|
-
return {
|
|
448
|
-
kind: "spreadProps",
|
|
449
|
-
node: node.argument,
|
|
450
|
-
toStatic(name) {
|
|
451
|
-
if (name == null) return unit;
|
|
452
|
-
return match(getStaticValue(node.argument, initialScope)?.value).with({ [name]: P.select(P.any) }, identity).otherwise(() => unit);
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
switch (attribute.type) {
|
|
457
|
-
case AST_NODE_TYPES.JSXAttribute: return handleJsxAttribute(attribute);
|
|
458
|
-
case AST_NODE_TYPES.JSXSpreadAttribute: return handleJsxSpreadAttribute(attribute);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
519
|
//#endregion
|
|
463
520
|
//#region src/jsx/jsx-config.ts
|
|
464
521
|
const JsxEmit = {
|
|
@@ -531,25 +588,15 @@ const JsxDetectionHint = {
|
|
|
531
588
|
*/
|
|
532
589
|
const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue;
|
|
533
590
|
/**
|
|
534
|
-
* Check if a node is a `JSXText` or a `Literal` node
|
|
535
|
-
* @param node The AST node to check
|
|
536
|
-
* @returns `true` if the node is a `JSXText` or a `Literal` node
|
|
537
|
-
*/
|
|
538
|
-
function isJsxText(node) {
|
|
539
|
-
if (node == null) return false;
|
|
540
|
-
return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal;
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
591
|
* Determine if a node represents JSX-like content based on heuristics
|
|
544
592
|
* Supports configuration through hint flags to customize detection behavior
|
|
545
593
|
*
|
|
546
|
-
* @param
|
|
547
|
-
* @param code.getScope The function to get the scope of a node
|
|
594
|
+
* @param context The rule context with scope lookup capability
|
|
548
595
|
* @param node The AST node to analyze
|
|
549
596
|
* @param hint The configuration flags to adjust detection behavior
|
|
550
597
|
* @returns boolean Whether the node is considered JSX-like
|
|
551
598
|
*/
|
|
552
|
-
function isJsxLike(
|
|
599
|
+
function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
553
600
|
if (node == null) return false;
|
|
554
601
|
if (ast.isJSX(node)) return true;
|
|
555
602
|
switch (node.type) {
|
|
@@ -565,27 +612,27 @@ function isJsxLike(code, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
|
565
612
|
case AST_NODE_TYPES.TemplateLiteral: return !(hint & JsxDetectionHint.DoNotIncludeJsxWithStringValue);
|
|
566
613
|
case AST_NODE_TYPES.ArrayExpression:
|
|
567
614
|
if (node.elements.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue);
|
|
568
|
-
if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.elements.every((n) => isJsxLike(
|
|
569
|
-
return node.elements.some((n) => isJsxLike(
|
|
615
|
+
if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.elements.every((n) => isJsxLike(context, n, hint));
|
|
616
|
+
return node.elements.some((n) => isJsxLike(context, n, hint));
|
|
570
617
|
case AST_NODE_TYPES.LogicalExpression:
|
|
571
|
-
if (hint & JsxDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx) return isJsxLike(
|
|
572
|
-
return isJsxLike(
|
|
618
|
+
if (hint & JsxDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx) return isJsxLike(context, node.left, hint) && isJsxLike(context, node.right, hint);
|
|
619
|
+
return isJsxLike(context, node.left, hint) || isJsxLike(context, node.right, hint);
|
|
573
620
|
case AST_NODE_TYPES.ConditionalExpression: {
|
|
574
621
|
function leftHasJSX(node) {
|
|
575
622
|
if (Array.isArray(node.consequent)) {
|
|
576
623
|
if (node.consequent.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue);
|
|
577
|
-
if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.consequent.every((n) => isJsxLike(
|
|
578
|
-
return node.consequent.some((n) => isJsxLike(
|
|
624
|
+
if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.consequent.every((n) => isJsxLike(context, n, hint));
|
|
625
|
+
return node.consequent.some((n) => isJsxLike(context, n, hint));
|
|
579
626
|
}
|
|
580
|
-
return isJsxLike(
|
|
627
|
+
return isJsxLike(context, node.consequent, hint);
|
|
581
628
|
}
|
|
582
629
|
function rightHasJSX(node) {
|
|
583
|
-
return isJsxLike(
|
|
630
|
+
return isJsxLike(context, node.alternate, hint);
|
|
584
631
|
}
|
|
585
632
|
if (hint & JsxDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx) return leftHasJSX(node) && rightHasJSX(node);
|
|
586
633
|
return leftHasJSX(node) || rightHasJSX(node);
|
|
587
634
|
}
|
|
588
|
-
case AST_NODE_TYPES.SequenceExpression: return isJsxLike(
|
|
635
|
+
case AST_NODE_TYPES.SequenceExpression: return isJsxLike(context, node.expressions.at(-1) ?? null, hint);
|
|
589
636
|
case AST_NODE_TYPES.CallExpression:
|
|
590
637
|
if (hint & JsxDetectionHint.DoNotIncludeJsxWithCreateElementValue) return false;
|
|
591
638
|
switch (node.callee.type) {
|
|
@@ -593,131 +640,445 @@ function isJsxLike(code, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
|
593
640
|
case AST_NODE_TYPES.MemberExpression: return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "createElement";
|
|
594
641
|
}
|
|
595
642
|
return false;
|
|
596
|
-
case AST_NODE_TYPES.Identifier:
|
|
597
|
-
|
|
598
|
-
if (name === "undefined") return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue);
|
|
643
|
+
case AST_NODE_TYPES.Identifier:
|
|
644
|
+
if (node.name === "undefined") return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue);
|
|
599
645
|
if (ast.isJSXTagNameExpression(node)) return true;
|
|
600
|
-
return isJsxLike(
|
|
601
|
-
}
|
|
646
|
+
return isJsxLike(context, resolve(context, node), hint);
|
|
602
647
|
}
|
|
603
648
|
return false;
|
|
604
649
|
}
|
|
605
650
|
|
|
606
651
|
//#endregion
|
|
607
|
-
//#region src/jsx/jsx-
|
|
652
|
+
//#region src/jsx/jsx-stringify.ts
|
|
608
653
|
/**
|
|
609
|
-
*
|
|
610
|
-
* For JSX elements, returns the stringified name (e.g., "div", "Button", "React.Fragment")
|
|
611
|
-
* For JSX fragments, returns an empty string
|
|
654
|
+
* Incomplete but sufficient stringification of JSX nodes for common use cases
|
|
612
655
|
*
|
|
613
|
-
* @param
|
|
614
|
-
* @
|
|
615
|
-
* @returns String representation of the element type
|
|
656
|
+
* @param node JSX node from TypeScript ESTree
|
|
657
|
+
* @returns String representation of the JSX node
|
|
616
658
|
*/
|
|
617
|
-
function
|
|
618
|
-
|
|
619
|
-
|
|
659
|
+
function stringifyJsx(node) {
|
|
660
|
+
switch (node.type) {
|
|
661
|
+
case AST_NODE_TYPES.JSXIdentifier: return node.name;
|
|
662
|
+
case AST_NODE_TYPES.JSXNamespacedName: return `${node.namespace.name}:${node.name.name}`;
|
|
663
|
+
case AST_NODE_TYPES.JSXMemberExpression: return `${stringifyJsx(node.object)}.${stringifyJsx(node.property)}`;
|
|
664
|
+
case AST_NODE_TYPES.JSXText: return node.value;
|
|
665
|
+
case AST_NODE_TYPES.JSXOpeningElement: return `<${stringifyJsx(node.name)}>`;
|
|
666
|
+
case AST_NODE_TYPES.JSXClosingElement: return `</${stringifyJsx(node.name)}>`;
|
|
667
|
+
case AST_NODE_TYPES.JSXOpeningFragment: return "<>";
|
|
668
|
+
case AST_NODE_TYPES.JSXClosingFragment: return "</>";
|
|
669
|
+
}
|
|
620
670
|
}
|
|
621
671
|
|
|
622
672
|
//#endregion
|
|
623
|
-
//#region src/jsx/jsx-
|
|
673
|
+
//#region src/jsx/jsx-inspector.ts
|
|
624
674
|
/**
|
|
625
|
-
*
|
|
626
|
-
*
|
|
675
|
+
* A stateful helper that binds an ESLint `RuleContext` once and exposes
|
|
676
|
+
* ergonomic methods for the most common JSX inspection tasks that rules need.
|
|
627
677
|
*
|
|
628
|
-
*
|
|
629
|
-
* @param node AST node to check
|
|
630
|
-
* @returns boolean indicating if the element is a host element
|
|
631
|
-
*/
|
|
632
|
-
function isJsxHostElement(context, node) {
|
|
633
|
-
return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Determine if a JSX element is a React Fragment
|
|
637
|
-
* Fragments can be imported from React and used like <Fragment> or <React.Fragment>
|
|
678
|
+
* ### Typical usage inside a rule's `create` function
|
|
638
679
|
*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
* @param jsxConfig.jsxFragmentFactory Name of the fragment factory (e.g., React.Fragment)
|
|
643
|
-
* @returns boolean indicating if the element is a Fragment
|
|
644
|
-
*/
|
|
645
|
-
function isJsxFragmentElement(context, node, jsxConfig) {
|
|
646
|
-
if (node.type !== AST_NODE_TYPES.JSXElement) return false;
|
|
647
|
-
const fragment = jsxConfig?.jsxFragmentFactory?.split(".").at(-1) ?? "Fragment";
|
|
648
|
-
return getJsxElementType(context, node).split(".").at(-1) === fragment;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
//#endregion
|
|
652
|
-
//#region src/jsx/jsx-hierarchy.ts
|
|
653
|
-
/**
|
|
654
|
-
* Traverses up the AST to find a parent JSX attribute node that matches a given test
|
|
680
|
+
* ```ts
|
|
681
|
+
* export function create(context: RuleContext) {
|
|
682
|
+
* const jsx = JsxInspector.from(context);
|
|
655
683
|
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
*
|
|
672
|
-
|
|
673
|
-
const ComponentDetectionHint = {
|
|
674
|
-
...JsxDetectionHint,
|
|
675
|
-
DoNotIncludeFunctionDefinedOnObjectMethod: 1n << 64n,
|
|
676
|
-
DoNotIncludeFunctionDefinedOnClassMethod: 1n << 65n,
|
|
677
|
-
DoNotIncludeFunctionDefinedOnClassProperty: 1n << 66n,
|
|
678
|
-
DoNotIncludeFunctionDefinedInArrayPattern: 1n << 67n,
|
|
679
|
-
DoNotIncludeFunctionDefinedInArrayExpression: 1n << 68n,
|
|
680
|
-
DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n << 69n,
|
|
681
|
-
DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: 1n << 70n
|
|
682
|
-
};
|
|
683
|
-
/**
|
|
684
|
-
* Default component detection hint
|
|
685
|
-
*/
|
|
686
|
-
const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | ComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | ComponentDetectionHint.DoNotIncludeJsxWithNumberValue | ComponentDetectionHint.DoNotIncludeJsxWithStringValue | ComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern | ComponentDetectionHint.RequireAllArrayElementsToBeJsx | ComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | ComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx;
|
|
687
|
-
|
|
688
|
-
//#endregion
|
|
689
|
-
//#region src/component/component-is.ts
|
|
690
|
-
/**
|
|
691
|
-
* Check if a node is a React class component
|
|
692
|
-
* @param node The AST node to check
|
|
693
|
-
* @returns `true` if the node is a class component, `false` otherwise
|
|
684
|
+
* return defineRuleListener({
|
|
685
|
+
* JSXElement(node) {
|
|
686
|
+
* // element type
|
|
687
|
+
* const type = jsx.getElementType(node); // "div" | "React.Fragment" | …
|
|
688
|
+
*
|
|
689
|
+
* // attribute lookup + value resolution in one step
|
|
690
|
+
* const val = jsx.getAttributeValue(node, "sandbox");
|
|
691
|
+
* if (typeof val?.getStatic() === "string") { … }
|
|
692
|
+
*
|
|
693
|
+
* // simple boolean checks
|
|
694
|
+
* if (jsx.isHostElement(node)) { … }
|
|
695
|
+
* if (jsx.isFragmentElement(node)) { … }
|
|
696
|
+
* if (jsx.hasAttribute(node, "key")) { … }
|
|
697
|
+
* },
|
|
698
|
+
* });
|
|
699
|
+
* }
|
|
700
|
+
* ```
|
|
694
701
|
*/
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
+
var JsxInspector = class JsxInspector {
|
|
703
|
+
context;
|
|
704
|
+
/**
|
|
705
|
+
* Merged JSX configuration (tsconfig compiler options + pragma annotations).
|
|
706
|
+
* The result is lazily computed and cached for the lifetime of this inspector.
|
|
707
|
+
*/
|
|
708
|
+
get jsxConfig() {
|
|
709
|
+
return this.#jsxConfig ??= {
|
|
710
|
+
...getJsxConfigFromContext(this.context),
|
|
711
|
+
...getJsxConfigFromAnnotation(this.context)
|
|
712
|
+
};
|
|
702
713
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
function isPureComponent(node) {
|
|
711
|
-
if ("superClass" in node && node.superClass != null) {
|
|
712
|
-
const re = /^PureComponent$/u;
|
|
713
|
-
switch (true) {
|
|
714
|
-
case node.superClass.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.name);
|
|
715
|
-
case node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.property.name);
|
|
716
|
-
}
|
|
714
|
+
/**
|
|
715
|
+
* Lazily resolved & cached JSX configuration (merged from tsconfig +
|
|
716
|
+
* pragma annotations). Use {@link jsxConfig} to access.
|
|
717
|
+
*/
|
|
718
|
+
#jsxConfig;
|
|
719
|
+
constructor(context) {
|
|
720
|
+
this.context = context;
|
|
717
721
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
722
|
+
/**
|
|
723
|
+
* Walk **up** the AST from `node` to find the nearest ancestor that is a
|
|
724
|
+
* `JSXAttribute` and passes the optional `test` predicate.
|
|
725
|
+
* @param node The starting node for the search.
|
|
726
|
+
* @param test A predicate function to test each ancestor node.
|
|
727
|
+
*/
|
|
728
|
+
static findParentAttribute(node, test = () => true) {
|
|
729
|
+
const guard = (n) => {
|
|
730
|
+
return n.type === AST_NODE_TYPES.JSXAttribute && test(n);
|
|
731
|
+
};
|
|
732
|
+
return ast.findParentNode(node, guard);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Create a new `JsxInspector` bound to the given rule context.
|
|
736
|
+
* @param context The ESLint rule context to bind to this inspector instance.
|
|
737
|
+
*/
|
|
738
|
+
static from(context) {
|
|
739
|
+
return new JsxInspector(context);
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Whether the node is a `JSXText` or a `Literal` node.
|
|
743
|
+
* @param node The node to check.
|
|
744
|
+
*/
|
|
745
|
+
static isJsxText(node) {
|
|
746
|
+
if (node == null) return false;
|
|
747
|
+
return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Find a JSX attribute (or spread attribute containing the property) by name
|
|
751
|
+
* on a given element.
|
|
752
|
+
*
|
|
753
|
+
* Returns the **last** matching attribute (to mirror React's behaviour where
|
|
754
|
+
* later props win), or `undefined` if not found.
|
|
755
|
+
* @param node The JSX element to search for the attribute.
|
|
756
|
+
* @param name The name of the attribute to find (ex: `"className"`).
|
|
757
|
+
*/
|
|
758
|
+
findAttribute(node, name) {
|
|
759
|
+
return node.openingElement.attributes.findLast((attr) => {
|
|
760
|
+
if (attr.type === AST_NODE_TYPES.JSXAttribute) return stringifyJsx(attr.name) === name;
|
|
761
|
+
switch (attr.argument.type) {
|
|
762
|
+
case AST_NODE_TYPES.Identifier: {
|
|
763
|
+
const initNode = resolve(this.context, attr.argument);
|
|
764
|
+
if (initNode?.type === AST_NODE_TYPES.ObjectExpression) return ast.findProperty(initNode.properties, name) != null;
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
case AST_NODE_TYPES.ObjectExpression: return ast.findProperty(attr.argument.properties, name) != null;
|
|
768
|
+
}
|
|
769
|
+
return false;
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Get the stringified name of a `JSXAttribute` node
|
|
774
|
+
* (ex: `"className"`, `"aria-label"`, `"xml:space"`).
|
|
775
|
+
* @param node The `JSXAttribute` node to extract the name from.
|
|
776
|
+
* @returns The stringified name of the attribute.
|
|
777
|
+
*/
|
|
778
|
+
getAttributeName(node) {
|
|
779
|
+
return stringifyJsx(node.name);
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Resolve the static value of an attribute, automatically handling the
|
|
783
|
+
* `spreadProps` case by extracting the named property.
|
|
784
|
+
*
|
|
785
|
+
* This eliminates the repetitive pattern:
|
|
786
|
+
* ```ts
|
|
787
|
+
* const v = core.resolveJsxAttributeValue(ctx, attr);
|
|
788
|
+
* const s = v.kind === "spreadProps" ? v.getProperty(name) : v.toStatic();
|
|
789
|
+
* ```
|
|
790
|
+
*
|
|
791
|
+
* Returns `undefined` when the attribute is not present or its value
|
|
792
|
+
* cannot be statically determined.
|
|
793
|
+
* @param node The JSX element to search for the attribute.
|
|
794
|
+
* @param name The name of the attribute to resolve (ex: `"className"`).
|
|
795
|
+
* @returns The static value of the attribute, or `undefined` if not found or not statically resolvable.
|
|
796
|
+
*/
|
|
797
|
+
getAttributeStaticValue(node, name) {
|
|
798
|
+
const attr = this.findAttribute(node, name);
|
|
799
|
+
if (attr == null) return void 0;
|
|
800
|
+
const resolved = this.resolveAttributeValue(attr);
|
|
801
|
+
if (resolved.kind === "spreadProps") return resolved.getProperty(name);
|
|
802
|
+
return resolved.toStatic();
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* **All-in-one helper** – find an attribute by name on an element *and*
|
|
806
|
+
* resolve its value in a single call.
|
|
807
|
+
*
|
|
808
|
+
* Returns `undefined` when the attribute is not present.
|
|
809
|
+
* @param node The JSX element to search for the attribute.
|
|
810
|
+
* @param name The name of the attribute to find and resolve (ex: `"className"`).
|
|
811
|
+
* @returns A descriptor of the attribute's value that can be further inspected, or `undefined` if the attribute is not found.
|
|
812
|
+
*/
|
|
813
|
+
getAttributeValue(node, name) {
|
|
814
|
+
const attr = this.findAttribute(node, name);
|
|
815
|
+
if (attr == null) return void 0;
|
|
816
|
+
return this.resolveAttributeValue(attr);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Get the **self name** (last segment) of a JSX element type.
|
|
820
|
+
*
|
|
821
|
+
* - `<Foo.Bar.Baz>` → `"Baz"`
|
|
822
|
+
* - `<div>` → `"div"`
|
|
823
|
+
* - `<></>` → `""`
|
|
824
|
+
* @param node The JSX element or fragment to extract the self name from.
|
|
825
|
+
*/
|
|
826
|
+
getElementSelfName(node) {
|
|
827
|
+
return this.getElementType(node).split(".").at(-1) ?? "";
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Get the string representation of a JSX element's type.
|
|
831
|
+
*
|
|
832
|
+
* - `<div>` → `"div"`
|
|
833
|
+
* - `<Foo.Bar>` → `"Foo.Bar"`
|
|
834
|
+
* - `<React.Fragment>` → `"React.Fragment"`
|
|
835
|
+
* - `<></>` (JSXFragment) → `""`
|
|
836
|
+
* @param node The JSX element or fragment to extract the type from.
|
|
837
|
+
*/
|
|
838
|
+
getElementType(node) {
|
|
839
|
+
if (node.type === AST_NODE_TYPES.JSXFragment) return "";
|
|
840
|
+
return stringifyJsx(node.openingElement.name);
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Shorthand: check whether an attribute exists on the element.
|
|
844
|
+
* @param node The JSX element to check for the attribute.
|
|
845
|
+
* @param name The name of the attribute to check for (ex: `"className"`).
|
|
846
|
+
* @returns `true` if the attribute exists on the element, `false` otherwise.
|
|
847
|
+
*/
|
|
848
|
+
hasAttribute(node, name) {
|
|
849
|
+
return this.findAttribute(node, name) != null;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Whether the node is a React **Fragment** element (either `<Fragment>` /
|
|
853
|
+
* `<React.Fragment>` or the shorthand `<>` syntax).
|
|
854
|
+
*
|
|
855
|
+
* The check honours the configured `jsxFragmentFactory`.
|
|
856
|
+
* @param node The node to check.
|
|
857
|
+
*/
|
|
858
|
+
isFragmentElement(node) {
|
|
859
|
+
if (node.type === AST_NODE_TYPES.JSXFragment) return true;
|
|
860
|
+
if (node.type !== AST_NODE_TYPES.JSXElement) return false;
|
|
861
|
+
const fragment = this.jsxConfig.jsxFragmentFactory.split(".").at(-1) ?? "Fragment";
|
|
862
|
+
return this.getElementType(node).split(".").at(-1) === fragment;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Whether the node is a **host** (intrinsic / DOM) element – i.e. its tag
|
|
866
|
+
* name starts with a lowercase letter.
|
|
867
|
+
* @param node The node to check.
|
|
868
|
+
*/
|
|
869
|
+
isHostElement(node) {
|
|
870
|
+
return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Resolve the *value* of a JSX attribute (or spread attribute) into a
|
|
874
|
+
* descriptor that can be inspected further.
|
|
875
|
+
*
|
|
876
|
+
* See {@link JsxAttributeValue} for the full set of `kind` discriminants.
|
|
877
|
+
* @param attribute The attribute node to resolve the value of.
|
|
878
|
+
* @returns A descriptor of the attribute's value that can be further inspected.
|
|
879
|
+
*/
|
|
880
|
+
resolveAttributeValue(attribute) {
|
|
881
|
+
if (attribute.type === AST_NODE_TYPES.JSXAttribute) return this.#resolveJsxAttribute(attribute);
|
|
882
|
+
return this.#resolveJsxSpreadAttribute(attribute);
|
|
883
|
+
}
|
|
884
|
+
#resolveJsxAttribute(node) {
|
|
885
|
+
const scope = this.context.sourceCode.getScope(node);
|
|
886
|
+
if (node.value == null) return {
|
|
887
|
+
kind: "boolean",
|
|
888
|
+
toStatic() {
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
switch (node.value.type) {
|
|
893
|
+
case AST_NODE_TYPES.Literal: {
|
|
894
|
+
const staticValue = node.value.value;
|
|
895
|
+
return {
|
|
896
|
+
kind: "literal",
|
|
897
|
+
node: node.value,
|
|
898
|
+
toStatic() {
|
|
899
|
+
return staticValue;
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
case AST_NODE_TYPES.JSXExpressionContainer: {
|
|
904
|
+
const expr = node.value.expression;
|
|
905
|
+
if (expr.type === AST_NODE_TYPES.JSXEmptyExpression) return {
|
|
906
|
+
kind: "missing",
|
|
907
|
+
node: expr,
|
|
908
|
+
toStatic() {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
return {
|
|
913
|
+
kind: "expression",
|
|
914
|
+
node: expr,
|
|
915
|
+
toStatic() {
|
|
916
|
+
return getStaticValue(expr, scope)?.value;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
case AST_NODE_TYPES.JSXElement: return {
|
|
921
|
+
kind: "element",
|
|
922
|
+
node: node.value,
|
|
923
|
+
toStatic() {
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
case AST_NODE_TYPES.JSXSpreadChild:
|
|
928
|
+
node.value.expression;
|
|
929
|
+
return {
|
|
930
|
+
kind: "spreadChild",
|
|
931
|
+
node: node.value.expression,
|
|
932
|
+
toStatic() {
|
|
933
|
+
return null;
|
|
934
|
+
},
|
|
935
|
+
getChildren(_at) {
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
#resolveJsxSpreadAttribute(node) {
|
|
942
|
+
const scope = this.context.sourceCode.getScope(node);
|
|
943
|
+
return {
|
|
944
|
+
kind: "spreadProps",
|
|
945
|
+
node: node.argument,
|
|
946
|
+
toStatic() {
|
|
947
|
+
return null;
|
|
948
|
+
},
|
|
949
|
+
getProperty(name) {
|
|
950
|
+
return match(getStaticValue(node.argument, scope)?.value).with({ [name]: P.select(P.any) }, identity).otherwise(() => null);
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
//#endregion
|
|
957
|
+
//#region src/component/component-detection-legacy.ts
|
|
958
|
+
/**
|
|
959
|
+
* Check if a node is a React class component
|
|
960
|
+
* @param node The AST node to check
|
|
961
|
+
* @returns `true` if the node is a class component, `false` otherwise
|
|
962
|
+
*/
|
|
963
|
+
function isClassComponent(node) {
|
|
964
|
+
if ("superClass" in node && node.superClass != null) {
|
|
965
|
+
const re = /^(?:Pure)?Component$/u;
|
|
966
|
+
switch (true) {
|
|
967
|
+
case node.superClass.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.name);
|
|
968
|
+
case node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.property.name);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Check if a node is a React PureComponent
|
|
975
|
+
* @param node The AST node to check
|
|
976
|
+
* @returns `true` if the node is a PureComponent, `false` otherwise
|
|
977
|
+
*/
|
|
978
|
+
function isPureComponent(node) {
|
|
979
|
+
if ("superClass" in node && node.superClass != null) {
|
|
980
|
+
const re = /^PureComponent$/u;
|
|
981
|
+
switch (true) {
|
|
982
|
+
case node.superClass.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.name);
|
|
983
|
+
case node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.property.name);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Create a lifecycle method checker function
|
|
990
|
+
* @param methodName The lifecycle method name
|
|
991
|
+
* @param isStatic Whether the method is static
|
|
992
|
+
*/
|
|
993
|
+
function createLifecycleChecker(methodName, isStatic = false) {
|
|
994
|
+
return (node) => ast.isMethodOrProperty(node) && node.static === isStatic && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === methodName;
|
|
995
|
+
}
|
|
996
|
+
const isRender = createLifecycleChecker("render");
|
|
997
|
+
const isComponentDidCatch = createLifecycleChecker("componentDidCatch");
|
|
998
|
+
const isComponentDidMount = createLifecycleChecker("componentDidMount");
|
|
999
|
+
const isComponentDidUpdate = createLifecycleChecker("componentDidUpdate");
|
|
1000
|
+
const isComponentWillMount = createLifecycleChecker("componentWillMount");
|
|
1001
|
+
const isComponentWillReceiveProps = createLifecycleChecker("componentWillReceiveProps");
|
|
1002
|
+
const isComponentWillUnmount = createLifecycleChecker("componentWillUnmount");
|
|
1003
|
+
const isComponentWillUpdate = createLifecycleChecker("componentWillUpdate");
|
|
1004
|
+
const isGetChildContext = createLifecycleChecker("getChildContext");
|
|
1005
|
+
const isGetInitialState = createLifecycleChecker("getInitialState");
|
|
1006
|
+
const isGetSnapshotBeforeUpdate = createLifecycleChecker("getSnapshotBeforeUpdate");
|
|
1007
|
+
const isShouldComponentUpdate = createLifecycleChecker("shouldComponentUpdate");
|
|
1008
|
+
const isUnsafeComponentWillMount = createLifecycleChecker("UNSAFE_componentWillMount");
|
|
1009
|
+
const isUnsafeComponentWillReceiveProps = createLifecycleChecker("UNSAFE_componentWillReceiveProps");
|
|
1010
|
+
const isUnsafeComponentWillUpdate = createLifecycleChecker("UNSAFE_componentWillUpdate");
|
|
1011
|
+
const isGetDefaultProps = createLifecycleChecker("getDefaultProps", true);
|
|
1012
|
+
const isGetDerivedStateFromProps = createLifecycleChecker("getDerivedStateFromProps", true);
|
|
1013
|
+
const isGetDerivedStateFromError = createLifecycleChecker("getDerivedStateFromError", true);
|
|
1014
|
+
/**
|
|
1015
|
+
* Check if the given node is a componentDidMount callback
|
|
1016
|
+
* @param node The node to check
|
|
1017
|
+
* @returns True if the node is a componentDidMount callback, false otherwise
|
|
1018
|
+
*/
|
|
1019
|
+
function isComponentDidMountCallback(node) {
|
|
1020
|
+
return ast.isFunction(node) && isComponentDidMount(node.parent) && node.parent.value === node;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Check if the given node is a componentWillUnmount callback
|
|
1024
|
+
* @param node The node to check
|
|
1025
|
+
* @returns True if the node is a componentWillUnmount callback, false otherwise
|
|
1026
|
+
*/
|
|
1027
|
+
function isComponentWillUnmountCallback(node) {
|
|
1028
|
+
return ast.isFunction(node) && isComponentWillUnmount(node.parent) && node.parent.value === node;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Check whether given node is a render method of a class component
|
|
1032
|
+
* @example
|
|
1033
|
+
* ```tsx
|
|
1034
|
+
* class Component extends React.Component {
|
|
1035
|
+
* renderHeader = () => <div />;
|
|
1036
|
+
* renderFooter = () => <div />;
|
|
1037
|
+
* }
|
|
1038
|
+
* ```
|
|
1039
|
+
* @param node The AST node to check
|
|
1040
|
+
* @returns `true` if node is a render function, `false` if not
|
|
1041
|
+
*/
|
|
1042
|
+
function isRenderMethodLike(node) {
|
|
1043
|
+
return ast.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name.startsWith("render") && node.parent.parent.type === AST_NODE_TYPES.ClassDeclaration;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Check if the given node is a function within a render method of a class component
|
|
1047
|
+
*
|
|
1048
|
+
* @param node The AST node to check
|
|
1049
|
+
* @returns `true` if the node is a render function inside a class component
|
|
1050
|
+
*
|
|
1051
|
+
* @example
|
|
1052
|
+
* ```tsx
|
|
1053
|
+
* class Component extends React.Component {
|
|
1054
|
+
* renderHeader = () => <div />; // Returns true
|
|
1055
|
+
* }
|
|
1056
|
+
* ```
|
|
1057
|
+
*/
|
|
1058
|
+
function isRenderMethodCallback(node) {
|
|
1059
|
+
const parent = node.parent;
|
|
1060
|
+
const greatGrandparent = parent.parent?.parent;
|
|
1061
|
+
return greatGrandparent != null && isRenderMethodLike(parent) && isClassComponent(greatGrandparent);
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Check whether the given node is a this.setState() call
|
|
1065
|
+
* @param node The node to check
|
|
1066
|
+
* @internal
|
|
1067
|
+
*/
|
|
1068
|
+
function isThisSetState(node) {
|
|
1069
|
+
const { callee } = node;
|
|
1070
|
+
return callee.type === AST_NODE_TYPES.MemberExpression && ast.isThisExpressionLoose(callee.object) && callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "setState";
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Check whether the given node is an assignment to this.state
|
|
1074
|
+
* @param node The node to check
|
|
1075
|
+
* @internal
|
|
1076
|
+
*/
|
|
1077
|
+
function isAssignmentToThisState(node) {
|
|
1078
|
+
const { left } = node;
|
|
1079
|
+
return left.type === AST_NODE_TYPES.MemberExpression && ast.isThisExpressionLoose(left.object) && ast.getPropertyName(left.property) === "state";
|
|
1080
|
+
}
|
|
1081
|
+
|
|
721
1082
|
//#endregion
|
|
722
1083
|
//#region src/component/component-wrapper.ts
|
|
723
1084
|
/**
|
|
@@ -771,7 +1132,7 @@ function isComponentWrapperCallbackLoose(context, node) {
|
|
|
771
1132
|
* Get function component identifier from `const Component = memo(() => {});`
|
|
772
1133
|
* @param context The rule context
|
|
773
1134
|
* @param node The function node to analyze
|
|
774
|
-
* @returns The function identifier or `
|
|
1135
|
+
* @returns The function identifier or `null` if not found
|
|
775
1136
|
*/
|
|
776
1137
|
function getFunctionComponentId(context, node) {
|
|
777
1138
|
const functionId = ast.getFunctionId(node);
|
|
@@ -779,7 +1140,7 @@ function getFunctionComponentId(context, node) {
|
|
|
779
1140
|
const { parent } = node;
|
|
780
1141
|
if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.id;
|
|
781
1142
|
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) return parent.parent.parent.id;
|
|
782
|
-
return
|
|
1143
|
+
return null;
|
|
783
1144
|
}
|
|
784
1145
|
|
|
785
1146
|
//#endregion
|
|
@@ -814,75 +1175,24 @@ function isFunctionWithLooseComponentName(context, fn, allowNone = false) {
|
|
|
814
1175
|
}
|
|
815
1176
|
|
|
816
1177
|
//#endregion
|
|
817
|
-
//#region src/component/component-
|
|
818
|
-
/**
|
|
819
|
-
* Check whether given node is a render method of a class component
|
|
820
|
-
* @example
|
|
821
|
-
* ```tsx
|
|
822
|
-
* class Component extends React.Component {
|
|
823
|
-
* renderHeader = () => <div />;
|
|
824
|
-
* renderFooter = () => <div />;
|
|
825
|
-
* }
|
|
826
|
-
* ```
|
|
827
|
-
* @param node The AST node to check
|
|
828
|
-
* @returns `true` if node is a render function, `false` if not
|
|
829
|
-
*/
|
|
830
|
-
function isRenderMethodLike(node) {
|
|
831
|
-
return ast.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name.startsWith("render") && node.parent.parent.type === AST_NODE_TYPES.ClassDeclaration;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
//#endregion
|
|
835
|
-
//#region src/component/component-definition.ts
|
|
1178
|
+
//#region src/component/component-detection.ts
|
|
836
1179
|
/**
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
* @param node The AST node to check
|
|
840
|
-
* @returns `true` if the node is a render function inside a class component
|
|
841
|
-
*
|
|
842
|
-
* @example
|
|
843
|
-
* ```tsx
|
|
844
|
-
* class Component extends React.Component {
|
|
845
|
-
* renderHeader = () => <div />; // Returns true
|
|
846
|
-
* }
|
|
847
|
-
* ```
|
|
848
|
-
*/
|
|
849
|
-
function isRenderMethodCallback(node) {
|
|
850
|
-
const parent = node.parent;
|
|
851
|
-
const greatGrandparent = parent.parent?.parent;
|
|
852
|
-
return greatGrandparent != null && isRenderMethodLike(parent) && isClassComponent(greatGrandparent);
|
|
853
|
-
}
|
|
854
|
-
/**
|
|
855
|
-
* Check if a function node should be excluded based on provided detection hints
|
|
856
|
-
*
|
|
857
|
-
* @param node The function node to check
|
|
858
|
-
* @param hint Component detection hints as bit flags
|
|
859
|
-
* @returns `true` if the function matches an exclusion hint
|
|
1180
|
+
* Hints for component collector
|
|
860
1181
|
*/
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
return false;
|
|
872
|
-
}
|
|
1182
|
+
const ComponentDetectionHint = {
|
|
1183
|
+
...JsxDetectionHint,
|
|
1184
|
+
DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: 1n << 17n,
|
|
1185
|
+
DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n << 16n,
|
|
1186
|
+
DoNotIncludeFunctionDefinedInArrayExpression: 1n << 15n,
|
|
1187
|
+
DoNotIncludeFunctionDefinedInArrayPattern: 1n << 14n,
|
|
1188
|
+
DoNotIncludeFunctionDefinedOnClassMethod: 1n << 12n,
|
|
1189
|
+
DoNotIncludeFunctionDefinedOnClassProperty: 1n << 13n,
|
|
1190
|
+
DoNotIncludeFunctionDefinedOnObjectMethod: 1n << 11n
|
|
1191
|
+
};
|
|
873
1192
|
/**
|
|
874
|
-
*
|
|
875
|
-
*
|
|
876
|
-
* @param context The rule context
|
|
877
|
-
* @param node The AST node to check
|
|
878
|
-
* @returns `true` if the node is passed as a child to `createElement`
|
|
1193
|
+
* Default component detection hint
|
|
879
1194
|
*/
|
|
880
|
-
|
|
881
|
-
const parent = node.parent;
|
|
882
|
-
if (parent?.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
883
|
-
if (!isCreateElementCall(context, parent)) return false;
|
|
884
|
-
return parent.arguments.slice(2).some((arg) => arg === node);
|
|
885
|
-
}
|
|
1195
|
+
const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | ComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | ComponentDetectionHint.DoNotIncludeJsxWithNumberValue | ComponentDetectionHint.DoNotIncludeJsxWithStringValue | ComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern | ComponentDetectionHint.RequireAllArrayElementsToBeJsx | ComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | ComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx;
|
|
886
1196
|
/**
|
|
887
1197
|
* Determine if a function node represents a valid React component definition
|
|
888
1198
|
*
|
|
@@ -893,8 +1203,33 @@ function isChildrenOfCreateElement(context, node) {
|
|
|
893
1203
|
*/
|
|
894
1204
|
function isComponentDefinition(context, node, hint) {
|
|
895
1205
|
if (!isFunctionWithLooseComponentName(context, node, true)) return false;
|
|
896
|
-
|
|
897
|
-
|
|
1206
|
+
switch (true) {
|
|
1207
|
+
case node.parent.type === AST_NODE_TYPES.CallExpression && isCreateElementCall(context, node.parent) && node.parent.arguments.slice(2).some((arg) => arg === node): return false;
|
|
1208
|
+
case isRenderMethodCallback(node): return false;
|
|
1209
|
+
}
|
|
1210
|
+
switch (true) {
|
|
1211
|
+
case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && node.parent.type === AST_NODE_TYPES.Property && node.parent.parent.type === AST_NODE_TYPES.ObjectExpression:
|
|
1212
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnObjectMethod) return false;
|
|
1213
|
+
break;
|
|
1214
|
+
case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && node.parent.type === AST_NODE_TYPES.MethodDefinition:
|
|
1215
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassMethod) return false;
|
|
1216
|
+
break;
|
|
1217
|
+
case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && node.parent.type === AST_NODE_TYPES.Property:
|
|
1218
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassProperty) return false;
|
|
1219
|
+
break;
|
|
1220
|
+
case node.parent.type === AST_NODE_TYPES.ArrayPattern:
|
|
1221
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern) return false;
|
|
1222
|
+
break;
|
|
1223
|
+
case node.parent.type === AST_NODE_TYPES.ArrayExpression:
|
|
1224
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression) return false;
|
|
1225
|
+
break;
|
|
1226
|
+
case node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee.type === AST_NODE_TYPES.MemberExpression && node.parent.callee.property.type === AST_NODE_TYPES.Identifier && node.parent.callee.property.name === "map":
|
|
1227
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback) return false;
|
|
1228
|
+
break;
|
|
1229
|
+
case node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee.type === AST_NODE_TYPES.MemberExpression && node.parent.callee.property.type === AST_NODE_TYPES.Identifier && node.parent.callee.property.name === "flatMap":
|
|
1230
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback) return false;
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
898
1233
|
const significantParent = ast.findParentNode(node, ast.isOneOf([
|
|
899
1234
|
AST_NODE_TYPES.JSXExpressionContainer,
|
|
900
1235
|
AST_NODE_TYPES.ArrowFunctionExpression,
|
|
@@ -913,15 +1248,12 @@ function isComponentDefinition(context, node, hint) {
|
|
|
913
1248
|
* Component flag constants
|
|
914
1249
|
*/
|
|
915
1250
|
const ComponentFlag = {
|
|
916
|
-
None: 0n,
|
|
917
|
-
PureComponent: 1n << 0n,
|
|
918
1251
|
CreateElement: 1n << 1n,
|
|
1252
|
+
ForwardRef: 1n << 3n,
|
|
919
1253
|
Memo: 1n << 2n,
|
|
920
|
-
|
|
1254
|
+
None: 0n,
|
|
1255
|
+
PureComponent: 1n << 0n
|
|
921
1256
|
};
|
|
922
|
-
|
|
923
|
-
//#endregion
|
|
924
|
-
//#region src/component/component-init-path.ts
|
|
925
1257
|
/**
|
|
926
1258
|
* Get component flag from init path
|
|
927
1259
|
* @param initPath The init path of the function component
|
|
@@ -936,7 +1268,7 @@ function getComponentFlagFromInitPath(initPath) {
|
|
|
936
1268
|
|
|
937
1269
|
//#endregion
|
|
938
1270
|
//#region src/component/component-collector.ts
|
|
939
|
-
const idGen$1 = new IdGenerator("
|
|
1271
|
+
const idGen$1 = new IdGenerator("function-component:");
|
|
940
1272
|
/**
|
|
941
1273
|
* Get a ctx and visitor object for the rule to collect function components
|
|
942
1274
|
* @param context The ESLint rule context
|
|
@@ -948,14 +1280,14 @@ function useComponentCollector(context, options = {}) {
|
|
|
948
1280
|
const functionEntries = [];
|
|
949
1281
|
const components = /* @__PURE__ */ new Map();
|
|
950
1282
|
const getText = (n) => context.sourceCode.getText(n);
|
|
951
|
-
const getCurrentEntry = () => functionEntries.at(-1);
|
|
1283
|
+
const getCurrentEntry = () => functionEntries.at(-1) ?? null;
|
|
952
1284
|
const onFunctionEnter = (node) => {
|
|
953
1285
|
const key = idGen$1.next();
|
|
954
1286
|
const exp = ast.findParentNode(node, (n) => n.type === AST_NODE_TYPES.ExportDefaultDeclaration);
|
|
955
1287
|
const isExportDefault = exp != null;
|
|
956
1288
|
const isExportDefaultDeclaration = exp != null && ast.getUnderlyingExpression(exp.declaration) === node;
|
|
957
1289
|
const id = getFunctionComponentId(context, node);
|
|
958
|
-
const name = id == null ?
|
|
1290
|
+
const name = id == null ? null : ast.getFullyQualifiedName(id, getText);
|
|
959
1291
|
const initPath = ast.getFunctionInitPath(node);
|
|
960
1292
|
const directives = ast.getFunctionDirectives(node);
|
|
961
1293
|
const entry = {
|
|
@@ -963,9 +1295,8 @@ function useComponentCollector(context, options = {}) {
|
|
|
963
1295
|
key,
|
|
964
1296
|
kind: "function-component",
|
|
965
1297
|
name,
|
|
966
|
-
node,
|
|
967
1298
|
directives,
|
|
968
|
-
displayName:
|
|
1299
|
+
displayName: null,
|
|
969
1300
|
flag: getComponentFlagFromInitPath(initPath),
|
|
970
1301
|
hint,
|
|
971
1302
|
hookCalls: [],
|
|
@@ -973,6 +1304,7 @@ function useComponentCollector(context, options = {}) {
|
|
|
973
1304
|
isComponentDefinition: isComponentDefinition(context, node, hint),
|
|
974
1305
|
isExportDefault,
|
|
975
1306
|
isExportDefaultDeclaration,
|
|
1307
|
+
node,
|
|
976
1308
|
rets: []
|
|
977
1309
|
};
|
|
978
1310
|
functionEntries.push(entry);
|
|
@@ -1002,13 +1334,13 @@ function useComponentCollector(context, options = {}) {
|
|
|
1002
1334
|
if (body.type === AST_NODE_TYPES.BlockStatement) return;
|
|
1003
1335
|
entry.rets.push(body);
|
|
1004
1336
|
if (!entry.isComponentDefinition) return;
|
|
1005
|
-
if (!components.has(entry.key) && !isJsxLike(context
|
|
1337
|
+
if (!components.has(entry.key) && !isJsxLike(context, body, hint)) return;
|
|
1006
1338
|
components.set(entry.key, entry);
|
|
1007
1339
|
},
|
|
1008
1340
|
...collectDisplayName ? { [ast.SEL_DISPLAY_NAME_ASSIGNMENT_EXPRESSION](node) {
|
|
1009
1341
|
const { left, right } = node;
|
|
1010
1342
|
if (left.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1011
|
-
const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name :
|
|
1343
|
+
const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name : null;
|
|
1012
1344
|
const component = [...components.values()].findLast(({ name }) => name != null && name === componentName);
|
|
1013
1345
|
if (component == null) return;
|
|
1014
1346
|
component.displayName = right;
|
|
@@ -1027,7 +1359,7 @@ function useComponentCollector(context, options = {}) {
|
|
|
1027
1359
|
entry.rets.push(node.argument);
|
|
1028
1360
|
if (!entry.isComponentDefinition) return;
|
|
1029
1361
|
const { argument } = node;
|
|
1030
|
-
if (!components.has(entry.key) && !isJsxLike(context
|
|
1362
|
+
if (!components.has(entry.key) && !isJsxLike(context, argument, hint)) return;
|
|
1031
1363
|
components.set(entry.key, entry);
|
|
1032
1364
|
}
|
|
1033
1365
|
}
|
|
@@ -1036,7 +1368,7 @@ function useComponentCollector(context, options = {}) {
|
|
|
1036
1368
|
|
|
1037
1369
|
//#endregion
|
|
1038
1370
|
//#region src/component/component-collector-legacy.ts
|
|
1039
|
-
const idGen = new IdGenerator("
|
|
1371
|
+
const idGen = new IdGenerator("class-component:");
|
|
1040
1372
|
/**
|
|
1041
1373
|
* Get a ctx and visitor object for the rule to collect class componentss
|
|
1042
1374
|
* @param context The ESLint rule context
|
|
@@ -1052,18 +1384,18 @@ function useComponentCollectorLegacy(context) {
|
|
|
1052
1384
|
if (!isClassComponent(node)) return;
|
|
1053
1385
|
const id = ast.getClassId(node);
|
|
1054
1386
|
const key = idGen.next();
|
|
1055
|
-
const name = id == null ?
|
|
1387
|
+
const name = id == null ? null : ast.getFullyQualifiedName(id, getText);
|
|
1056
1388
|
const flag = isPureComponent(node) ? ComponentFlag.PureComponent : ComponentFlag.None;
|
|
1057
1389
|
components.set(key, {
|
|
1058
1390
|
id,
|
|
1059
1391
|
key,
|
|
1060
1392
|
kind: "class-component",
|
|
1061
1393
|
name,
|
|
1062
|
-
|
|
1063
|
-
displayName: unit,
|
|
1394
|
+
displayName: null,
|
|
1064
1395
|
flag,
|
|
1065
1396
|
hint: 0n,
|
|
1066
|
-
methods: []
|
|
1397
|
+
methods: [],
|
|
1398
|
+
node
|
|
1067
1399
|
});
|
|
1068
1400
|
};
|
|
1069
1401
|
return {
|
|
@@ -1074,179 +1406,6 @@ function useComponentCollectorLegacy(context) {
|
|
|
1074
1406
|
}
|
|
1075
1407
|
};
|
|
1076
1408
|
}
|
|
1077
|
-
/**
|
|
1078
|
-
* Check whether the given node is a this.setState() call
|
|
1079
|
-
* @param node The node to check
|
|
1080
|
-
* @internal
|
|
1081
|
-
*/
|
|
1082
|
-
function isThisSetState(node) {
|
|
1083
|
-
const { callee } = node;
|
|
1084
|
-
return callee.type === AST_NODE_TYPES$1.MemberExpression && ast.isThisExpressionLoose(callee.object) && callee.property.type === AST_NODE_TYPES$1.Identifier && callee.property.name === "setState";
|
|
1085
|
-
}
|
|
1086
|
-
/**
|
|
1087
|
-
* Check whether the given node is an assignment to this.state
|
|
1088
|
-
* @param node The node to check
|
|
1089
|
-
* @internal
|
|
1090
|
-
*/
|
|
1091
|
-
function isAssignmentToThisState(node) {
|
|
1092
|
-
const { left } = node;
|
|
1093
|
-
return left.type === AST_NODE_TYPES$1.MemberExpression && ast.isThisExpressionLoose(left.object) && ast.getPropertyName(left.property) === "state";
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
//#endregion
|
|
1097
|
-
//#region src/component/component-method-is.ts
|
|
1098
|
-
/**
|
|
1099
|
-
* Create a lifecycle method checker function
|
|
1100
|
-
* @param methodName The lifecycle method name
|
|
1101
|
-
* @param isStatic Whether the method is static
|
|
1102
|
-
*/
|
|
1103
|
-
function createLifecycleChecker(methodName, isStatic = false) {
|
|
1104
|
-
return (node) => ast.isMethodOrProperty(node) && node.static === isStatic && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === methodName;
|
|
1105
|
-
}
|
|
1106
|
-
const isRender = createLifecycleChecker("render");
|
|
1107
|
-
const isComponentDidCatch = createLifecycleChecker("componentDidCatch");
|
|
1108
|
-
const isComponentDidMount = createLifecycleChecker("componentDidMount");
|
|
1109
|
-
const isComponentDidUpdate = createLifecycleChecker("componentDidUpdate");
|
|
1110
|
-
const isComponentWillMount = createLifecycleChecker("componentWillMount");
|
|
1111
|
-
const isComponentWillReceiveProps = createLifecycleChecker("componentWillReceiveProps");
|
|
1112
|
-
const isComponentWillUnmount = createLifecycleChecker("componentWillUnmount");
|
|
1113
|
-
const isComponentWillUpdate = createLifecycleChecker("componentWillUpdate");
|
|
1114
|
-
const isGetChildContext = createLifecycleChecker("getChildContext");
|
|
1115
|
-
const isGetInitialState = createLifecycleChecker("getInitialState");
|
|
1116
|
-
const isGetSnapshotBeforeUpdate = createLifecycleChecker("getSnapshotBeforeUpdate");
|
|
1117
|
-
const isShouldComponentUpdate = createLifecycleChecker("shouldComponentUpdate");
|
|
1118
|
-
const isUnsafeComponentWillMount = createLifecycleChecker("UNSAFE_componentWillMount");
|
|
1119
|
-
const isUnsafeComponentWillReceiveProps = createLifecycleChecker("UNSAFE_componentWillReceiveProps");
|
|
1120
|
-
const isUnsafeComponentWillUpdate = createLifecycleChecker("UNSAFE_componentWillUpdate");
|
|
1121
|
-
const isGetDefaultProps = createLifecycleChecker("getDefaultProps", true);
|
|
1122
|
-
const isGetDerivedStateFromProps = createLifecycleChecker("getDerivedStateFromProps", true);
|
|
1123
|
-
const isGetDerivedStateFromError = createLifecycleChecker("getDerivedStateFromError", true);
|
|
1124
|
-
|
|
1125
|
-
//#endregion
|
|
1126
|
-
//#region src/component/component-method-callback.ts
|
|
1127
|
-
/**
|
|
1128
|
-
* Check if the given node is a componentDidMount callback
|
|
1129
|
-
* @param node The node to check
|
|
1130
|
-
* @returns True if the node is a componentDidMount callback, false otherwise
|
|
1131
|
-
*/
|
|
1132
|
-
function isComponentDidMountCallback(node) {
|
|
1133
|
-
return ast.isFunction(node) && isComponentDidMount(node.parent) && node.parent.value === node;
|
|
1134
|
-
}
|
|
1135
|
-
/**
|
|
1136
|
-
* Check if the given node is a componentWillUnmount callback
|
|
1137
|
-
* @param node The node to check
|
|
1138
|
-
* @returns True if the node is a componentWillUnmount callback, false otherwise
|
|
1139
|
-
*/
|
|
1140
|
-
function isComponentWillUnmountCallback(node) {
|
|
1141
|
-
return ast.isFunction(node) && isComponentWillUnmount(node.parent) && node.parent.value === node;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
//#endregion
|
|
1145
|
-
//#region src/component/component-render-prop.ts
|
|
1146
|
-
/**
|
|
1147
|
-
* Unsafe check whether given node is a render function
|
|
1148
|
-
* ```tsx
|
|
1149
|
-
* const renderRow = () => <div />
|
|
1150
|
-
* ` ^^^^^^^^^^^^`
|
|
1151
|
-
* _ = <Component renderRow={() => <div />} />
|
|
1152
|
-
* ` ^^^^^^^^^^^^^ `
|
|
1153
|
-
* ```
|
|
1154
|
-
* @param context The rule context
|
|
1155
|
-
* @param node The AST node to check
|
|
1156
|
-
* @returns `true` if node is a render function, `false` if not
|
|
1157
|
-
*/
|
|
1158
|
-
function isRenderFunctionLoose(context, node) {
|
|
1159
|
-
if (!ast.isFunction(node)) return false;
|
|
1160
|
-
const id = ast.getFunctionId(node);
|
|
1161
|
-
switch (true) {
|
|
1162
|
-
case id?.type === AST_NODE_TYPES.Identifier: return id.name.startsWith("render");
|
|
1163
|
-
case id?.type === AST_NODE_TYPES.MemberExpression && id.property.type === AST_NODE_TYPES.Identifier: return id.property.name.startsWith("render");
|
|
1164
|
-
case node.parent.type === AST_NODE_TYPES.JSXExpressionContainer && node.parent.parent.type === AST_NODE_TYPES.JSXAttribute && node.parent.parent.name.type === AST_NODE_TYPES.JSXIdentifier: return node.parent.parent.name.name.startsWith("render");
|
|
1165
|
-
}
|
|
1166
|
-
return false;
|
|
1167
|
-
}
|
|
1168
|
-
/**
|
|
1169
|
-
* Unsafe check whether given JSXAttribute is a render prop
|
|
1170
|
-
* ```tsx
|
|
1171
|
-
* _ = <Component renderRow={() => <div />} />
|
|
1172
|
-
* ` ^^^^^^^^^^^^^^^^^^^^^^^^^ `
|
|
1173
|
-
* ```
|
|
1174
|
-
* @param context The rule context
|
|
1175
|
-
* @param node The AST node to check
|
|
1176
|
-
* @returns `true` if node is a render prop, `false` if not
|
|
1177
|
-
*/
|
|
1178
|
-
function isRenderPropLoose(context, node) {
|
|
1179
|
-
if (node.name.type !== AST_NODE_TYPES.JSXIdentifier) return false;
|
|
1180
|
-
return node.name.name.startsWith("render") && node.value?.type === AST_NODE_TYPES.JSXExpressionContainer && isRenderFunctionLoose(context, node.value.expression);
|
|
1181
|
-
}
|
|
1182
|
-
/**
|
|
1183
|
-
* Unsafe check whether given node is declared directly inside a render property
|
|
1184
|
-
* ```tsx
|
|
1185
|
-
* const rows = { render: () => <div /> }
|
|
1186
|
-
* ` ^^^^^^^^^^^^^ `
|
|
1187
|
-
* _ = <Component rows={ [{ render: () => <div /> }] } />
|
|
1188
|
-
* ` ^^^^^^^^^^^^^ `
|
|
1189
|
-
* ```
|
|
1190
|
-
* @internal
|
|
1191
|
-
* @param node The AST node to check
|
|
1192
|
-
* @returns `true` if component is declared inside a render property, `false` if not
|
|
1193
|
-
*/
|
|
1194
|
-
function isDirectValueOfRenderPropertyLoose(node) {
|
|
1195
|
-
const matching = (node) => {
|
|
1196
|
-
return node.type === AST_NODE_TYPES.Property && node.key.type === AST_NODE_TYPES.Identifier && node.key.name.startsWith("render");
|
|
1197
|
-
};
|
|
1198
|
-
return matching(node) || node.parent != null && matching(node.parent);
|
|
1199
|
-
}
|
|
1200
|
-
/**
|
|
1201
|
-
* Unsafe check whether given node is declared inside a render prop
|
|
1202
|
-
* ```tsx
|
|
1203
|
-
* _ = <Component renderRow={"node"} />
|
|
1204
|
-
* ` ^^^^^^ `
|
|
1205
|
-
* _ = <Component rows={ [{ render: "node" }] } />
|
|
1206
|
-
* ` ^^^^^^ `
|
|
1207
|
-
* ```
|
|
1208
|
-
* @param node The AST node to check
|
|
1209
|
-
* @returns `true` if component is declared inside a render prop, `false` if not
|
|
1210
|
-
*/
|
|
1211
|
-
function isDeclaredInRenderPropLoose(node) {
|
|
1212
|
-
if (isDirectValueOfRenderPropertyLoose(node)) return true;
|
|
1213
|
-
const parent = ast.findParentNode(node, ast.is(AST_NODE_TYPES.JSXExpressionContainer))?.parent;
|
|
1214
|
-
if (parent?.type !== AST_NODE_TYPES.JSXAttribute) return false;
|
|
1215
|
-
return parent.name.type === AST_NODE_TYPES.JSXIdentifier && parent.name.name.startsWith("render");
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
//#endregion
|
|
1219
|
-
//#region src/hierarchy/find-enclosing-component-or-hook.ts
|
|
1220
|
-
/**
|
|
1221
|
-
* Find the enclosing React component or hook for a given AST node
|
|
1222
|
-
* @param node The AST node to start the search from
|
|
1223
|
-
* @param test Optional test function to customize component or hook identification
|
|
1224
|
-
* @returns The enclosing component or hook node, or `null` if none is ASAST.
|
|
1225
|
-
*/
|
|
1226
|
-
function findEnclosingComponentOrHook(node, test = (n, name) => {
|
|
1227
|
-
if (name == null) return false;
|
|
1228
|
-
return isComponentNameLoose(name) || isHookName(name);
|
|
1229
|
-
}) {
|
|
1230
|
-
const enclosingNode = ast.findParentNode(node, (n) => {
|
|
1231
|
-
if (!ast.isFunction(n)) return false;
|
|
1232
|
-
return test(n, match(ast.getFunctionId(n)).with({ type: AST_NODE_TYPES.Identifier }, (id) => id.name).with({
|
|
1233
|
-
type: AST_NODE_TYPES.MemberExpression,
|
|
1234
|
-
property: { type: AST_NODE_TYPES.Identifier }
|
|
1235
|
-
}, (me) => me.property.name).otherwise(() => null));
|
|
1236
|
-
});
|
|
1237
|
-
return ast.isFunction(enclosingNode) ? enclosingNode : unit;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
//#endregion
|
|
1241
|
-
//#region src/hierarchy/is-inside-component-or-hook.ts
|
|
1242
|
-
/**
|
|
1243
|
-
* Check if a given AST node is inside a React component or hook
|
|
1244
|
-
* @param node The AST node to check
|
|
1245
|
-
* @returns True if the node is inside a component or hook, false otherwise
|
|
1246
|
-
*/
|
|
1247
|
-
function isInsideComponentOrHook(node) {
|
|
1248
|
-
return findEnclosingComponentOrHook(node) != null;
|
|
1249
|
-
}
|
|
1250
1409
|
|
|
1251
1410
|
//#endregion
|
|
1252
1411
|
//#region src/ref/ref-name.ts
|
|
@@ -1255,12 +1414,18 @@ function isInsideComponentOrHook(node) {
|
|
|
1255
1414
|
* @param name The name to check
|
|
1256
1415
|
* @returns True if the name is "ref" or ends with "Ref"
|
|
1257
1416
|
*/
|
|
1258
|
-
function
|
|
1417
|
+
function isRefLikeName(name) {
|
|
1259
1418
|
return name === "ref" || name.endsWith("Ref");
|
|
1260
1419
|
}
|
|
1261
1420
|
|
|
1262
1421
|
//#endregion
|
|
1263
|
-
//#region src/ref/
|
|
1422
|
+
//#region src/ref/ref-id.ts
|
|
1423
|
+
function isRefId(node) {
|
|
1424
|
+
return node.type === AST_NODE_TYPES.Identifier && isRefLikeName(node.name);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
//#endregion
|
|
1428
|
+
//#region src/ref/ref-init.ts
|
|
1264
1429
|
/**
|
|
1265
1430
|
* Check if the variable with the given name is initialized or derived from a ref
|
|
1266
1431
|
* @param name The variable name
|
|
@@ -1274,20 +1439,20 @@ function isInitializedFromRef(name, initialScope) {
|
|
|
1274
1439
|
* Get the init expression of a ref variable
|
|
1275
1440
|
* @param name The variable name
|
|
1276
1441
|
* @param initialScope The initial scope
|
|
1277
|
-
* @returns The init expression node if the variable is derived from a ref, or
|
|
1442
|
+
* @returns The init expression node if the variable is derived from a ref, or null otherwise
|
|
1278
1443
|
*/
|
|
1279
1444
|
function getRefInit(name, initialScope) {
|
|
1280
|
-
for (const { node } of findVariable(initialScope
|
|
1445
|
+
for (const { node } of findVariable(initialScope, name)?.defs ?? []) {
|
|
1281
1446
|
if (node.type !== AST_NODE_TYPES$1.VariableDeclarator) continue;
|
|
1282
1447
|
const init = node.init;
|
|
1283
1448
|
if (init == null) continue;
|
|
1284
1449
|
switch (true) {
|
|
1285
|
-
case init.type === AST_NODE_TYPES$1.MemberExpression && init.object.type === AST_NODE_TYPES$1.Identifier &&
|
|
1450
|
+
case init.type === AST_NODE_TYPES$1.MemberExpression && init.object.type === AST_NODE_TYPES$1.Identifier && isRefLikeName(init.object.name): return init;
|
|
1286
1451
|
case init.type === AST_NODE_TYPES$1.CallExpression && isUseRefCall(init): return init;
|
|
1287
1452
|
}
|
|
1288
1453
|
}
|
|
1289
|
-
return
|
|
1454
|
+
return null;
|
|
1290
1455
|
}
|
|
1291
1456
|
|
|
1292
1457
|
//#endregion
|
|
1293
|
-
export { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit,
|
|
1458
|
+
export { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, JsxInspector, REACT_BUILTIN_HOOK_NAMES, findImportSource, getComponentFlagFromInitPath, getFunctionComponentId, getJsxConfigFromAnnotation, getJsxConfigFromContext, getRefInit, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidMountCallback, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUnmountCallback, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isComponentWrapperCallback, isComponentWrapperCallbackLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isForwardRef, isForwardRefCall, isFunctionWithLooseComponentName, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isHook, isHookCall, isHookCallWithName, isHookId, isHookName, isInitializedFromReact, isInitializedFromReactNative, isInitializedFromRef, isJsxLike, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isRefId, isRefLikeName, isRender, isRenderMethodCallback, isRenderMethodLike, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectCleanupCallback, isUseEffectLikeCall, isUseEffectSetupCallback, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseStateLikeCall, isUseSyncExternalStoreCall, isUseTransitionCall, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
|