@eslint-react/core 3.0.0-next.9 → 3.0.0-rc.1
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 +349 -326
- package/dist/index.js +728 -554
- package/package.json +13 -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) {
|
|
@@ -195,11 +396,11 @@ function isUseEffectLikeCall(node, additionalEffectHooks = { test: constFalse })
|
|
|
195
396
|
function isUseStateLikeCall(node, additionalStateHooks = { test: constFalse }) {
|
|
196
397
|
if (node == null) return false;
|
|
197
398
|
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
399
|
+
switch (true) {
|
|
400
|
+
case node.callee.type === AST_NODE_TYPES.Identifier: return node.callee.name === "useState" || additionalStateHooks.test(node.callee.name);
|
|
401
|
+
case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier: return ast.getPropertyName(node.callee.property) === "useState" || additionalStateHooks.test(node.callee.property.name);
|
|
402
|
+
}
|
|
403
|
+
return false;
|
|
203
404
|
}
|
|
204
405
|
const isUseCall = flip(isHookCallWithName)("use");
|
|
205
406
|
const isUseActionStateCall = flip(isHookCallWithName)("useActionState");
|
|
@@ -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,130 +640,444 @@ 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
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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;
|
|
721
|
+
}
|
|
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);
|
|
716
984
|
}
|
|
717
985
|
}
|
|
718
986
|
return false;
|
|
719
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
|
+
}
|
|
720
1081
|
|
|
721
1082
|
//#endregion
|
|
722
1083
|
//#region src/component/component-wrapper.ts
|
|
@@ -748,7 +1109,8 @@ function isComponentWrapperCallLoose(context, node) {
|
|
|
748
1109
|
*/
|
|
749
1110
|
function isComponentWrapperCallback(context, node) {
|
|
750
1111
|
if (!ast.isFunction(node)) return false;
|
|
751
|
-
|
|
1112
|
+
let parent = node.parent;
|
|
1113
|
+
while (ast.isTypeExpression(parent)) parent = parent.parent;
|
|
752
1114
|
if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
753
1115
|
return isComponentWrapperCall(context, parent);
|
|
754
1116
|
}
|
|
@@ -760,7 +1122,8 @@ function isComponentWrapperCallback(context, node) {
|
|
|
760
1122
|
*/
|
|
761
1123
|
function isComponentWrapperCallbackLoose(context, node) {
|
|
762
1124
|
if (!ast.isFunction(node)) return false;
|
|
763
|
-
|
|
1125
|
+
let parent = node.parent;
|
|
1126
|
+
while (ast.isTypeExpression(parent)) parent = parent.parent;
|
|
764
1127
|
if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
765
1128
|
return isComponentWrapperCallLoose(context, parent);
|
|
766
1129
|
}
|
|
@@ -771,15 +1134,16 @@ function isComponentWrapperCallbackLoose(context, node) {
|
|
|
771
1134
|
* Get function component identifier from `const Component = memo(() => {});`
|
|
772
1135
|
* @param context The rule context
|
|
773
1136
|
* @param node The function node to analyze
|
|
774
|
-
* @returns The function identifier or `
|
|
1137
|
+
* @returns The function identifier or `null` if not found
|
|
775
1138
|
*/
|
|
776
1139
|
function getFunctionComponentId(context, node) {
|
|
777
1140
|
const functionId = ast.getFunctionId(node);
|
|
778
1141
|
if (functionId != null) return functionId;
|
|
779
|
-
|
|
1142
|
+
let parent = node.parent;
|
|
1143
|
+
while (ast.isTypeExpression(parent)) parent = parent.parent;
|
|
780
1144
|
if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.id;
|
|
781
1145
|
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
|
|
1146
|
+
return null;
|
|
783
1147
|
}
|
|
784
1148
|
|
|
785
1149
|
//#endregion
|
|
@@ -814,75 +1178,25 @@ function isFunctionWithLooseComponentName(context, fn, allowNone = false) {
|
|
|
814
1178
|
}
|
|
815
1179
|
|
|
816
1180
|
//#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
|
|
1181
|
+
//#region src/component/component-detection.ts
|
|
836
1182
|
/**
|
|
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
|
|
1183
|
+
* Hints for component collector
|
|
860
1184
|
*/
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
}
|
|
1185
|
+
const ComponentDetectionHint = {
|
|
1186
|
+
...JsxDetectionHint,
|
|
1187
|
+
DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback: 1n << 18n,
|
|
1188
|
+
DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: 1n << 17n,
|
|
1189
|
+
DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n << 16n,
|
|
1190
|
+
DoNotIncludeFunctionDefinedInArrayExpression: 1n << 15n,
|
|
1191
|
+
DoNotIncludeFunctionDefinedInArrayPattern: 1n << 14n,
|
|
1192
|
+
DoNotIncludeFunctionDefinedOnClassMethod: 1n << 12n,
|
|
1193
|
+
DoNotIncludeFunctionDefinedOnClassProperty: 1n << 13n,
|
|
1194
|
+
DoNotIncludeFunctionDefinedOnObjectMethod: 1n << 11n
|
|
1195
|
+
};
|
|
873
1196
|
/**
|
|
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`
|
|
1197
|
+
* Default component detection hint
|
|
879
1198
|
*/
|
|
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
|
-
}
|
|
1199
|
+
const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | ComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | ComponentDetectionHint.DoNotIncludeJsxWithNumberValue | ComponentDetectionHint.DoNotIncludeJsxWithStringValue | ComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern | ComponentDetectionHint.RequireAllArrayElementsToBeJsx | ComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | ComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx;
|
|
886
1200
|
/**
|
|
887
1201
|
* Determine if a function node represents a valid React component definition
|
|
888
1202
|
*
|
|
@@ -893,8 +1207,38 @@ function isChildrenOfCreateElement(context, node) {
|
|
|
893
1207
|
*/
|
|
894
1208
|
function isComponentDefinition(context, node, hint) {
|
|
895
1209
|
if (!isFunctionWithLooseComponentName(context, node, true)) return false;
|
|
896
|
-
|
|
897
|
-
|
|
1210
|
+
switch (true) {
|
|
1211
|
+
case node.parent.type === AST_NODE_TYPES.CallExpression && isCreateElementCall(context, node.parent) && node.parent.arguments.slice(2).some((arg) => arg === node): return false;
|
|
1212
|
+
case isRenderMethodCallback(node): return false;
|
|
1213
|
+
}
|
|
1214
|
+
let parent = node.parent;
|
|
1215
|
+
while (ast.isTypeExpression(parent)) parent = parent.parent;
|
|
1216
|
+
switch (true) {
|
|
1217
|
+
case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.Property && parent.parent.type === AST_NODE_TYPES.ObjectExpression:
|
|
1218
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnObjectMethod) return false;
|
|
1219
|
+
break;
|
|
1220
|
+
case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.MethodDefinition:
|
|
1221
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassMethod) return false;
|
|
1222
|
+
break;
|
|
1223
|
+
case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.Property:
|
|
1224
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassProperty) return false;
|
|
1225
|
+
break;
|
|
1226
|
+
case parent.type === AST_NODE_TYPES.ArrayPattern:
|
|
1227
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern) return false;
|
|
1228
|
+
break;
|
|
1229
|
+
case parent.type === AST_NODE_TYPES.ArrayExpression:
|
|
1230
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression) return false;
|
|
1231
|
+
break;
|
|
1232
|
+
case parent.type === AST_NODE_TYPES.CallExpression && parent.callee.type === AST_NODE_TYPES.MemberExpression && parent.callee.property.type === AST_NODE_TYPES.Identifier && parent.callee.property.name === "map":
|
|
1233
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback) return false;
|
|
1234
|
+
break;
|
|
1235
|
+
case parent.type === AST_NODE_TYPES.CallExpression && parent.callee.type === AST_NODE_TYPES.MemberExpression && parent.callee.property.type === AST_NODE_TYPES.Identifier && parent.callee.property.name === "flatMap":
|
|
1236
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback) return false;
|
|
1237
|
+
break;
|
|
1238
|
+
case parent.type === AST_NODE_TYPES.CallExpression && ast.getFunctionId(node) == null && !isComponentWrapperCallLoose(context, parent) && !isCreateElementCall(context, parent):
|
|
1239
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback) return false;
|
|
1240
|
+
break;
|
|
1241
|
+
}
|
|
898
1242
|
const significantParent = ast.findParentNode(node, ast.isOneOf([
|
|
899
1243
|
AST_NODE_TYPES.JSXExpressionContainer,
|
|
900
1244
|
AST_NODE_TYPES.ArrowFunctionExpression,
|
|
@@ -913,15 +1257,12 @@ function isComponentDefinition(context, node, hint) {
|
|
|
913
1257
|
* Component flag constants
|
|
914
1258
|
*/
|
|
915
1259
|
const ComponentFlag = {
|
|
916
|
-
None: 0n,
|
|
917
|
-
PureComponent: 1n << 0n,
|
|
918
1260
|
CreateElement: 1n << 1n,
|
|
1261
|
+
ForwardRef: 1n << 3n,
|
|
919
1262
|
Memo: 1n << 2n,
|
|
920
|
-
|
|
1263
|
+
None: 0n,
|
|
1264
|
+
PureComponent: 1n << 0n
|
|
921
1265
|
};
|
|
922
|
-
|
|
923
|
-
//#endregion
|
|
924
|
-
//#region src/component/component-init-path.ts
|
|
925
1266
|
/**
|
|
926
1267
|
* Get component flag from init path
|
|
927
1268
|
* @param initPath The init path of the function component
|
|
@@ -936,7 +1277,7 @@ function getComponentFlagFromInitPath(initPath) {
|
|
|
936
1277
|
|
|
937
1278
|
//#endregion
|
|
938
1279
|
//#region src/component/component-collector.ts
|
|
939
|
-
const idGen$1 = new IdGenerator("
|
|
1280
|
+
const idGen$1 = new IdGenerator("function-component:");
|
|
940
1281
|
/**
|
|
941
1282
|
* Get a ctx and visitor object for the rule to collect function components
|
|
942
1283
|
* @param context The ESLint rule context
|
|
@@ -948,14 +1289,14 @@ function useComponentCollector(context, options = {}) {
|
|
|
948
1289
|
const functionEntries = [];
|
|
949
1290
|
const components = /* @__PURE__ */ new Map();
|
|
950
1291
|
const getText = (n) => context.sourceCode.getText(n);
|
|
951
|
-
const getCurrentEntry = () => functionEntries.at(-1);
|
|
1292
|
+
const getCurrentEntry = () => functionEntries.at(-1) ?? null;
|
|
952
1293
|
const onFunctionEnter = (node) => {
|
|
953
1294
|
const key = idGen$1.next();
|
|
954
1295
|
const exp = ast.findParentNode(node, (n) => n.type === AST_NODE_TYPES.ExportDefaultDeclaration);
|
|
955
1296
|
const isExportDefault = exp != null;
|
|
956
1297
|
const isExportDefaultDeclaration = exp != null && ast.getUnderlyingExpression(exp.declaration) === node;
|
|
957
1298
|
const id = getFunctionComponentId(context, node);
|
|
958
|
-
const name = id == null ?
|
|
1299
|
+
const name = id == null ? null : ast.getFullyQualifiedName(id, getText);
|
|
959
1300
|
const initPath = ast.getFunctionInitPath(node);
|
|
960
1301
|
const directives = ast.getFunctionDirectives(node);
|
|
961
1302
|
const entry = {
|
|
@@ -963,9 +1304,8 @@ function useComponentCollector(context, options = {}) {
|
|
|
963
1304
|
key,
|
|
964
1305
|
kind: "function-component",
|
|
965
1306
|
name,
|
|
966
|
-
node,
|
|
967
1307
|
directives,
|
|
968
|
-
displayName:
|
|
1308
|
+
displayName: null,
|
|
969
1309
|
flag: getComponentFlagFromInitPath(initPath),
|
|
970
1310
|
hint,
|
|
971
1311
|
hookCalls: [],
|
|
@@ -973,6 +1313,7 @@ function useComponentCollector(context, options = {}) {
|
|
|
973
1313
|
isComponentDefinition: isComponentDefinition(context, node, hint),
|
|
974
1314
|
isExportDefault,
|
|
975
1315
|
isExportDefaultDeclaration,
|
|
1316
|
+
node,
|
|
976
1317
|
rets: []
|
|
977
1318
|
};
|
|
978
1319
|
functionEntries.push(entry);
|
|
@@ -1002,13 +1343,13 @@ function useComponentCollector(context, options = {}) {
|
|
|
1002
1343
|
if (body.type === AST_NODE_TYPES.BlockStatement) return;
|
|
1003
1344
|
entry.rets.push(body);
|
|
1004
1345
|
if (!entry.isComponentDefinition) return;
|
|
1005
|
-
if (!components.has(entry.key) && !isJsxLike(context
|
|
1346
|
+
if (!components.has(entry.key) && !isJsxLike(context, body, hint)) return;
|
|
1006
1347
|
components.set(entry.key, entry);
|
|
1007
1348
|
},
|
|
1008
1349
|
...collectDisplayName ? { [ast.SEL_DISPLAY_NAME_ASSIGNMENT_EXPRESSION](node) {
|
|
1009
1350
|
const { left, right } = node;
|
|
1010
1351
|
if (left.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1011
|
-
const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name :
|
|
1352
|
+
const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name : null;
|
|
1012
1353
|
const component = [...components.values()].findLast(({ name }) => name != null && name === componentName);
|
|
1013
1354
|
if (component == null) return;
|
|
1014
1355
|
component.displayName = right;
|
|
@@ -1027,7 +1368,7 @@ function useComponentCollector(context, options = {}) {
|
|
|
1027
1368
|
entry.rets.push(node.argument);
|
|
1028
1369
|
if (!entry.isComponentDefinition) return;
|
|
1029
1370
|
const { argument } = node;
|
|
1030
|
-
if (!components.has(entry.key) && !isJsxLike(context
|
|
1371
|
+
if (!components.has(entry.key) && !isJsxLike(context, argument, hint)) return;
|
|
1031
1372
|
components.set(entry.key, entry);
|
|
1032
1373
|
}
|
|
1033
1374
|
}
|
|
@@ -1036,7 +1377,7 @@ function useComponentCollector(context, options = {}) {
|
|
|
1036
1377
|
|
|
1037
1378
|
//#endregion
|
|
1038
1379
|
//#region src/component/component-collector-legacy.ts
|
|
1039
|
-
const idGen = new IdGenerator("
|
|
1380
|
+
const idGen = new IdGenerator("class-component:");
|
|
1040
1381
|
/**
|
|
1041
1382
|
* Get a ctx and visitor object for the rule to collect class componentss
|
|
1042
1383
|
* @param context The ESLint rule context
|
|
@@ -1052,18 +1393,18 @@ function useComponentCollectorLegacy(context) {
|
|
|
1052
1393
|
if (!isClassComponent(node)) return;
|
|
1053
1394
|
const id = ast.getClassId(node);
|
|
1054
1395
|
const key = idGen.next();
|
|
1055
|
-
const name = id == null ?
|
|
1396
|
+
const name = id == null ? null : ast.getFullyQualifiedName(id, getText);
|
|
1056
1397
|
const flag = isPureComponent(node) ? ComponentFlag.PureComponent : ComponentFlag.None;
|
|
1057
1398
|
components.set(key, {
|
|
1058
1399
|
id,
|
|
1059
1400
|
key,
|
|
1060
1401
|
kind: "class-component",
|
|
1061
1402
|
name,
|
|
1062
|
-
|
|
1063
|
-
displayName: unit,
|
|
1403
|
+
displayName: null,
|
|
1064
1404
|
flag,
|
|
1065
1405
|
hint: 0n,
|
|
1066
|
-
methods: []
|
|
1406
|
+
methods: [],
|
|
1407
|
+
node
|
|
1067
1408
|
});
|
|
1068
1409
|
};
|
|
1069
1410
|
return {
|
|
@@ -1074,179 +1415,6 @@ function useComponentCollectorLegacy(context) {
|
|
|
1074
1415
|
}
|
|
1075
1416
|
};
|
|
1076
1417
|
}
|
|
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
1418
|
|
|
1251
1419
|
//#endregion
|
|
1252
1420
|
//#region src/ref/ref-name.ts
|
|
@@ -1255,12 +1423,18 @@ function isInsideComponentOrHook(node) {
|
|
|
1255
1423
|
* @param name The name to check
|
|
1256
1424
|
* @returns True if the name is "ref" or ends with "Ref"
|
|
1257
1425
|
*/
|
|
1258
|
-
function
|
|
1426
|
+
function isRefLikeName(name) {
|
|
1259
1427
|
return name === "ref" || name.endsWith("Ref");
|
|
1260
1428
|
}
|
|
1261
1429
|
|
|
1262
1430
|
//#endregion
|
|
1263
|
-
//#region src/ref/
|
|
1431
|
+
//#region src/ref/ref-id.ts
|
|
1432
|
+
function isRefId(node) {
|
|
1433
|
+
return node.type === AST_NODE_TYPES.Identifier && isRefLikeName(node.name);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
//#endregion
|
|
1437
|
+
//#region src/ref/ref-init.ts
|
|
1264
1438
|
/**
|
|
1265
1439
|
* Check if the variable with the given name is initialized or derived from a ref
|
|
1266
1440
|
* @param name The variable name
|
|
@@ -1274,20 +1448,20 @@ function isInitializedFromRef(name, initialScope) {
|
|
|
1274
1448
|
* Get the init expression of a ref variable
|
|
1275
1449
|
* @param name The variable name
|
|
1276
1450
|
* @param initialScope The initial scope
|
|
1277
|
-
* @returns The init expression node if the variable is derived from a ref, or
|
|
1451
|
+
* @returns The init expression node if the variable is derived from a ref, or null otherwise
|
|
1278
1452
|
*/
|
|
1279
1453
|
function getRefInit(name, initialScope) {
|
|
1280
|
-
for (const { node } of findVariable(initialScope
|
|
1454
|
+
for (const { node } of findVariable(initialScope, name)?.defs ?? []) {
|
|
1281
1455
|
if (node.type !== AST_NODE_TYPES$1.VariableDeclarator) continue;
|
|
1282
1456
|
const init = node.init;
|
|
1283
1457
|
if (init == null) continue;
|
|
1284
1458
|
switch (true) {
|
|
1285
|
-
case init.type === AST_NODE_TYPES$1.MemberExpression && init.object.type === AST_NODE_TYPES$1.Identifier &&
|
|
1459
|
+
case init.type === AST_NODE_TYPES$1.MemberExpression && init.object.type === AST_NODE_TYPES$1.Identifier && isRefLikeName(init.object.name): return init;
|
|
1286
1460
|
case init.type === AST_NODE_TYPES$1.CallExpression && isUseRefCall(init): return init;
|
|
1287
1461
|
}
|
|
1288
1462
|
}
|
|
1289
|
-
return
|
|
1463
|
+
return null;
|
|
1290
1464
|
}
|
|
1291
1465
|
|
|
1292
1466
|
//#endregion
|
|
1293
|
-
export { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit,
|
|
1467
|
+
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 };
|