@eslint-react/core 2.7.5-next.0 → 2.7.5-next.10
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 +105 -85
- package/dist/index.js +205 -243
- package/package.json +8 -9
package/dist/index.d.ts
CHANGED
|
@@ -3,13 +3,12 @@ import { unit } from "@eslint-react/eff";
|
|
|
3
3
|
import { TSESTree } from "@typescript-eslint/types";
|
|
4
4
|
import { RegExpLike, RuleContext } from "@eslint-react/shared";
|
|
5
5
|
import { ESLintUtils, TSESTree as TSESTree$1 } from "@typescript-eslint/utils";
|
|
6
|
-
import * as birecord0 from "birecord";
|
|
7
6
|
import { Scope } from "@typescript-eslint/scope-manager";
|
|
8
7
|
import * as typescript0 from "typescript";
|
|
9
8
|
|
|
10
9
|
//#region src/api/is-from-react.d.ts
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
11
|
+
* Check if a variable is initialized from React import
|
|
13
12
|
* @param name The variable name
|
|
14
13
|
* @param initialScope The initial scope
|
|
15
14
|
* @param importSource Alternative import source of React (e.g., "preact/compat")
|
|
@@ -19,7 +18,7 @@ declare function isInitializedFromReact(name: string, initialScope: Scope, impor
|
|
|
19
18
|
//#endregion
|
|
20
19
|
//#region src/api/is-from-react-native.d.ts
|
|
21
20
|
/**
|
|
22
|
-
*
|
|
21
|
+
* if a variable is initialized from React Native import
|
|
23
22
|
* @param name The variable name
|
|
24
23
|
* @param initialScope The initial scope
|
|
25
24
|
* @param importSource Alternative import source of React Native (e.g., "react-native-web")
|
|
@@ -35,7 +34,7 @@ declare namespace isReactAPI {
|
|
|
35
34
|
};
|
|
36
35
|
}
|
|
37
36
|
/**
|
|
38
|
-
*
|
|
37
|
+
* Check if the node is a React API identifier or member expression
|
|
39
38
|
* @param api The React API name to check against (e.g., "useState", "React.memo")
|
|
40
39
|
* @returns A predicate function to check if a node matches the API
|
|
41
40
|
*/
|
|
@@ -47,7 +46,7 @@ declare namespace isReactAPICall {
|
|
|
47
46
|
};
|
|
48
47
|
}
|
|
49
48
|
/**
|
|
50
|
-
*
|
|
49
|
+
* Check if the node is a call expression to a specific React API
|
|
51
50
|
* @param api The React API name to check against
|
|
52
51
|
* @returns A predicate function to check if a node is a call to the API
|
|
53
52
|
*/
|
|
@@ -127,21 +126,6 @@ declare const ComponentDetectionHint: {
|
|
|
127
126
|
*/
|
|
128
127
|
declare const DEFAULT_COMPONENT_DETECTION_HINT: bigint;
|
|
129
128
|
//#endregion
|
|
130
|
-
//#region src/component/component-phase.d.ts
|
|
131
|
-
type ComponentEffectPhaseKind = "cleanup" | "setup";
|
|
132
|
-
type ComponentLifecyclePhaseKind = "mount" | "unmount";
|
|
133
|
-
type ComponentPhaseKind = ComponentEffectPhaseKind | ComponentLifecyclePhaseKind;
|
|
134
|
-
declare const ComponentPhaseRelevance: birecord0.BiRecord<{
|
|
135
|
-
readonly mount: "unmount";
|
|
136
|
-
readonly setup: "cleanup";
|
|
137
|
-
}>;
|
|
138
|
-
//#endregion
|
|
139
|
-
//#region src/semantic/semantic-entry.d.ts
|
|
140
|
-
interface SemanticEntry {
|
|
141
|
-
node: TSESTree.Node;
|
|
142
|
-
phase: ComponentPhaseKind;
|
|
143
|
-
}
|
|
144
|
-
//#endregion
|
|
145
129
|
//#region src/semantic/semantic-node.d.ts
|
|
146
130
|
interface SemanticNode {
|
|
147
131
|
id: unit | TSESTree.Node;
|
|
@@ -153,22 +137,36 @@ interface SemanticNode {
|
|
|
153
137
|
hint: bigint;
|
|
154
138
|
}
|
|
155
139
|
//#endregion
|
|
140
|
+
//#region src/semantic/semantic-func.d.ts
|
|
141
|
+
interface SemanticFunc extends SemanticNode {
|
|
142
|
+
id: AST.FunctionID | unit;
|
|
143
|
+
node: AST.TSESTreeFunction;
|
|
144
|
+
name: string | unit;
|
|
145
|
+
type: TSESTree.TSTypeAnnotation | unit;
|
|
146
|
+
body: TSESTree.BlockStatement | TSESTree.Expression;
|
|
147
|
+
directives: TSESTree.StringLiteral[];
|
|
148
|
+
parameters: TSESTree.Parameter[];
|
|
149
|
+
typeParameters: TSESTree.TSTypeParameterDeclaration | unit;
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
156
152
|
//#region src/component/component-flag.d.ts
|
|
157
153
|
type ComponentFlag = bigint;
|
|
154
|
+
/**
|
|
155
|
+
* Component flag constants
|
|
156
|
+
*/
|
|
158
157
|
declare const ComponentFlag: {
|
|
159
158
|
/** No flags set */None: bigint; /** Indicates the component is a pure component (e.g., extends PureComponent) */
|
|
160
159
|
PureComponent: bigint; /** Indicates the component creates elements using `createElement` instead of JSX */
|
|
161
160
|
CreateElement: bigint; /** Indicates the component is memoized (e.g., React.memo) */
|
|
162
161
|
Memo: bigint; /** Indicates the component forwards a ref (e.g., React.forwardRef) */
|
|
163
|
-
ForwardRef: bigint;
|
|
164
|
-
Async: bigint;
|
|
162
|
+
ForwardRef: bigint;
|
|
165
163
|
};
|
|
166
164
|
//#endregion
|
|
167
165
|
//#region src/component/component-semantic-node.d.ts
|
|
168
166
|
/**
|
|
169
|
-
* Represents a React
|
|
167
|
+
* Represents a React Function Component
|
|
170
168
|
*/
|
|
171
|
-
interface
|
|
169
|
+
interface FunctionComponentSemanticNode extends SemanticNode {
|
|
172
170
|
/**
|
|
173
171
|
* The identifier or identifier sequence of the component
|
|
174
172
|
*/
|
|
@@ -215,9 +213,9 @@ interface FunctionComponent extends SemanticNode {
|
|
|
215
213
|
displayName: unit | TSESTree.Expression;
|
|
216
214
|
}
|
|
217
215
|
/**
|
|
218
|
-
* Represents a React
|
|
216
|
+
* Represents a React Class Component
|
|
219
217
|
*/
|
|
220
|
-
interface
|
|
218
|
+
interface ClassComponentSemanticNode extends SemanticNode {
|
|
221
219
|
/**
|
|
222
220
|
* The identifier of the component
|
|
223
221
|
*/
|
|
@@ -248,30 +246,22 @@ interface ClassComponent extends SemanticNode {
|
|
|
248
246
|
displayName: unit | TSESTree.Expression;
|
|
249
247
|
}
|
|
250
248
|
/**
|
|
251
|
-
*
|
|
249
|
+
* Represents a React Component
|
|
252
250
|
*/
|
|
253
|
-
type
|
|
251
|
+
type ComponentSemanticNode = ClassComponentSemanticNode | FunctionComponentSemanticNode;
|
|
254
252
|
//#endregion
|
|
255
253
|
//#region src/component/component-collector.d.ts
|
|
256
|
-
|
|
257
|
-
key: string;
|
|
258
|
-
node: AST.TSESTreeFunction;
|
|
259
|
-
hookCalls: TSESTree.CallExpression[];
|
|
260
|
-
isComponent: boolean;
|
|
254
|
+
interface FunctionEntry$1 extends FunctionComponentSemanticNode {
|
|
261
255
|
isComponentDefinition: boolean;
|
|
262
|
-
|
|
263
|
-
isExportDefaultDeclaration: boolean;
|
|
264
|
-
rets: TSESTree.ReturnStatement["argument"][];
|
|
265
|
-
};
|
|
256
|
+
}
|
|
266
257
|
declare namespace useComponentCollector {
|
|
267
258
|
type Options = {
|
|
268
259
|
collectDisplayName?: boolean;
|
|
269
|
-
collectHookCalls?: boolean;
|
|
270
260
|
hint?: ComponentDetectionHint;
|
|
271
261
|
};
|
|
272
262
|
type ReturnType = {
|
|
273
263
|
ctx: {
|
|
274
|
-
getAllComponents: (node: TSESTree.Program) =>
|
|
264
|
+
getAllComponents: (node: TSESTree.Program) => FunctionComponentSemanticNode[];
|
|
275
265
|
getCurrentEntries: () => FunctionEntry$1[];
|
|
276
266
|
getCurrentEntry: () => FunctionEntry$1 | unit;
|
|
277
267
|
};
|
|
@@ -279,7 +269,7 @@ declare namespace useComponentCollector {
|
|
|
279
269
|
};
|
|
280
270
|
}
|
|
281
271
|
/**
|
|
282
|
-
* Get a ctx and visitor for the rule to collect function components
|
|
272
|
+
* Get a ctx and visitor object for the rule to collect function components
|
|
283
273
|
* @param context The ESLint rule context
|
|
284
274
|
* @param options The options to use
|
|
285
275
|
* @returns The ctx and visitor of the collector
|
|
@@ -290,13 +280,13 @@ declare function useComponentCollector(context: RuleContext, options?: useCompon
|
|
|
290
280
|
declare namespace useComponentCollectorLegacy {
|
|
291
281
|
type ReturnType = {
|
|
292
282
|
ctx: {
|
|
293
|
-
getAllComponents: (node: TSESTree$1.Program) =>
|
|
283
|
+
getAllComponents: (node: TSESTree$1.Program) => ClassComponentSemanticNode[];
|
|
294
284
|
};
|
|
295
285
|
visitor: ESLintUtils.RuleListener;
|
|
296
286
|
};
|
|
297
287
|
}
|
|
298
288
|
/**
|
|
299
|
-
* Get a ctx and visitor object for the rule to collect class
|
|
289
|
+
* Get a ctx and visitor object for the rule to collect class componentss
|
|
300
290
|
* @param context The ESLint rule context
|
|
301
291
|
* @returns The ctx and visitor of the collector
|
|
302
292
|
*/
|
|
@@ -316,7 +306,7 @@ declare function isAssignmentToThisState(node: TSESTree$1.AssignmentExpression):
|
|
|
316
306
|
//#endregion
|
|
317
307
|
//#region src/component/component-definition.d.ts
|
|
318
308
|
/**
|
|
319
|
-
*
|
|
309
|
+
* Determine if a function node represents a valid React component definition
|
|
320
310
|
*
|
|
321
311
|
* @param context The rule context
|
|
322
312
|
* @param node The function node to analyze
|
|
@@ -326,10 +316,21 @@ declare function isAssignmentToThisState(node: TSESTree$1.AssignmentExpression):
|
|
|
326
316
|
declare function isComponentDefinition(context: RuleContext, node: AST.TSESTreeFunction, hint: bigint): boolean;
|
|
327
317
|
//#endregion
|
|
328
318
|
//#region src/component/component-id.d.ts
|
|
319
|
+
/**
|
|
320
|
+
* Get function component identifier from `const Component = memo(() => {});`
|
|
321
|
+
* @param context The rule context
|
|
322
|
+
* @param node The function node to analyze
|
|
323
|
+
* @returns The function identifier or `unit` if not found
|
|
324
|
+
*/
|
|
329
325
|
declare function getFunctionComponentId(context: RuleContext, node: AST.TSESTreeFunction): AST.FunctionID | unit;
|
|
330
326
|
//#endregion
|
|
331
327
|
//#region src/component/component-init-path.d.ts
|
|
332
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Get component flag from init path
|
|
330
|
+
* @param initPath The init path of the function component
|
|
331
|
+
* @returns The component flag
|
|
332
|
+
*/
|
|
333
|
+
declare function getComponentFlagFromInitPath(initPath: FunctionComponentSemanticNode["initPath"]): bigint;
|
|
333
334
|
//#endregion
|
|
334
335
|
//#region src/component/component-is.d.ts
|
|
335
336
|
/**
|
|
@@ -341,24 +342,27 @@ declare function isClassComponent(node: TSESTree.Node): node is AST.TSESTreeClas
|
|
|
341
342
|
/**
|
|
342
343
|
* Check if a node is a React PureComponent
|
|
343
344
|
* @param node The AST node to check
|
|
344
|
-
* @returns `true` if the node is a
|
|
345
|
+
* @returns `true` if the node is a PureComponent, `false` otherwise
|
|
345
346
|
*/
|
|
346
347
|
declare function isPureComponent(node: TSESTree.Node): boolean;
|
|
347
348
|
//#endregion
|
|
348
349
|
//#region src/component/component-kind.d.ts
|
|
350
|
+
/**
|
|
351
|
+
* Represents the kind of a React component
|
|
352
|
+
*/
|
|
349
353
|
type ComponentKind = "classComponent" | "functionComponent";
|
|
350
354
|
//#endregion
|
|
351
355
|
//#region src/component/component-method-callback.d.ts
|
|
352
356
|
/**
|
|
353
|
-
*
|
|
354
|
-
* @param node The
|
|
355
|
-
* @returns
|
|
357
|
+
* Check if the given node is a componentDidMount callback
|
|
358
|
+
* @param node The node to check
|
|
359
|
+
* @returns True if the node is a componentDidMount callback, false otherwise
|
|
356
360
|
*/
|
|
357
361
|
declare function isComponentDidMountCallback(node: TSESTree.Node): boolean;
|
|
358
362
|
/**
|
|
359
|
-
*
|
|
360
|
-
* @param node The
|
|
361
|
-
* @returns
|
|
363
|
+
* Check if the given node is a componentWillUnmount callback
|
|
364
|
+
* @param node The node to check
|
|
365
|
+
* @returns True if the node is a componentWillUnmount callback, false otherwise
|
|
362
366
|
*/
|
|
363
367
|
declare function isComponentWillUnmountCallback(node: TSESTree.Node): boolean;
|
|
364
368
|
//#endregion
|
|
@@ -400,13 +404,6 @@ declare function isComponentNameLoose(name: string): boolean;
|
|
|
400
404
|
*/
|
|
401
405
|
declare function hasNoneOrLooseComponentName(context: RuleContext, fn: AST.TSESTreeFunction): boolean;
|
|
402
406
|
//#endregion
|
|
403
|
-
//#region src/component/component-phase-helpers.d.ts
|
|
404
|
-
declare const isInversePhase: {
|
|
405
|
-
(a: ComponentPhaseKind): (b: ComponentPhaseKind) => boolean;
|
|
406
|
-
(a: ComponentPhaseKind, b: ComponentPhaseKind): boolean;
|
|
407
|
-
};
|
|
408
|
-
declare function getPhaseKindOfFunction(node: AST.TSESTreeFunction): ComponentPhaseKind | null;
|
|
409
|
-
//#endregion
|
|
410
407
|
//#region src/component/component-render-method.d.ts
|
|
411
408
|
/**
|
|
412
409
|
* Check whether given node is a render method of a class component
|
|
@@ -503,6 +500,30 @@ declare function isComponentWrapperCallback(context: RuleContext, node: TSESTree
|
|
|
503
500
|
*/
|
|
504
501
|
declare function isComponentWrapperCallbackLoose(context: RuleContext, node: TSESTree.Node): boolean;
|
|
505
502
|
//#endregion
|
|
503
|
+
//#region src/function/function-semantic-node.d.ts
|
|
504
|
+
/**
|
|
505
|
+
* Represents a React Client Function
|
|
506
|
+
*/
|
|
507
|
+
interface ClientFunctionSemanticNode extends SemanticFunc {
|
|
508
|
+
/**
|
|
509
|
+
* The kind of function
|
|
510
|
+
*/
|
|
511
|
+
kind: "client-function";
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Represents a React Server Function
|
|
515
|
+
*/
|
|
516
|
+
interface ServerFunctionSemanticNode extends SemanticFunc {
|
|
517
|
+
/**
|
|
518
|
+
* The kind of function
|
|
519
|
+
*/
|
|
520
|
+
kind: "server-function";
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Represents a React Function
|
|
524
|
+
*/
|
|
525
|
+
type FunctionSemanticNode = ClientFunctionSemanticNode | ServerFunctionSemanticNode;
|
|
526
|
+
//#endregion
|
|
506
527
|
//#region src/hierarchy/find-enclosing-component-or-hook.d.ts
|
|
507
528
|
type FindEnclosingComponentOrHookFilter = (n: TSESTree.Node, name: string | null) => boolean;
|
|
508
529
|
/**
|
|
@@ -511,11 +532,11 @@ type FindEnclosingComponentOrHookFilter = (n: TSESTree.Node, name: string | null
|
|
|
511
532
|
* @param test Optional test function to customize component or hook identification
|
|
512
533
|
* @returns The enclosing component or hook node, or `null` if none is found
|
|
513
534
|
*/
|
|
514
|
-
declare function findEnclosingComponentOrHook(node: TSESTree.Node | unit, test?: FindEnclosingComponentOrHookFilter): TSESTree.ArrowFunctionExpression | TSESTree.
|
|
535
|
+
declare function findEnclosingComponentOrHook(node: TSESTree.Node | unit, test?: FindEnclosingComponentOrHookFilter): TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclarationWithName | TSESTree.FunctionDeclarationWithOptionalName | TSESTree.FunctionExpression | undefined;
|
|
515
536
|
//#endregion
|
|
516
537
|
//#region src/hierarchy/is-inside-component-or-hook.d.ts
|
|
517
538
|
/**
|
|
518
|
-
*
|
|
539
|
+
* Check if a given AST node is inside a React component or hook
|
|
519
540
|
* @param node The AST node to check
|
|
520
541
|
* @returns True if the node is inside a component or hook, false otherwise
|
|
521
542
|
*/
|
|
@@ -523,18 +544,18 @@ declare function isInsideComponentOrHook(node: TSESTree.Node | unit): boolean;
|
|
|
523
544
|
//#endregion
|
|
524
545
|
//#region src/hook/hook-callback.d.ts
|
|
525
546
|
/**
|
|
526
|
-
*
|
|
547
|
+
* Determine if a node is the setup function passed to a useEffect-like hook
|
|
527
548
|
* @param node The AST node to check
|
|
528
549
|
*/
|
|
529
550
|
declare function isUseEffectSetupCallback(node: TSESTree.Node | unit): boolean;
|
|
530
551
|
/**
|
|
531
|
-
*
|
|
552
|
+
* Determine if a node is the cleanup function returned by a useEffect-like hook's setup function.
|
|
532
553
|
* @param node The AST node to check
|
|
533
554
|
*/
|
|
534
555
|
declare function isUseEffectCleanupCallback(node: TSESTree.Node | unit): boolean;
|
|
535
556
|
//#endregion
|
|
536
557
|
//#region src/hook/hook-semantic-node.d.ts
|
|
537
|
-
interface
|
|
558
|
+
interface HookSemanticNode extends SemanticNode {
|
|
538
559
|
id: AST.FunctionID | unit;
|
|
539
560
|
node: AST.TSESTreeFunction;
|
|
540
561
|
name: string;
|
|
@@ -545,12 +566,11 @@ interface Hook extends SemanticNode {
|
|
|
545
566
|
type FunctionEntry = {
|
|
546
567
|
key: string;
|
|
547
568
|
node: AST.TSESTreeFunction;
|
|
548
|
-
isHook: boolean;
|
|
549
569
|
};
|
|
550
570
|
declare namespace useHookCollector {
|
|
551
571
|
type ReturnType = {
|
|
552
572
|
ctx: {
|
|
553
|
-
getAllHooks(node: TSESTree$1.Program):
|
|
573
|
+
getAllHooks(node: TSESTree$1.Program): HookSemanticNode[];
|
|
554
574
|
getCurrentEntries(): FunctionEntry[];
|
|
555
575
|
getCurrentEntry(): FunctionEntry | unit;
|
|
556
576
|
};
|
|
@@ -558,44 +578,44 @@ declare namespace useHookCollector {
|
|
|
558
578
|
};
|
|
559
579
|
}
|
|
560
580
|
/**
|
|
561
|
-
* Get a ctx and visitor for the rule to collect hooks
|
|
581
|
+
* Get a ctx and visitor object for the rule to collect hooks
|
|
562
582
|
* @param context The ESLint rule context
|
|
563
583
|
* @returns The ctx and visitor of the collector
|
|
564
584
|
*/
|
|
565
585
|
declare function useHookCollector(context: RuleContext): useHookCollector.ReturnType;
|
|
566
586
|
//#endregion
|
|
567
587
|
//#region src/hook/hook-id.d.ts
|
|
568
|
-
declare function
|
|
588
|
+
declare function isHookId(id: TSESTree.Node): id is TSESTree.Identifier | TSESTree.MemberExpression;
|
|
569
589
|
//#endregion
|
|
570
590
|
//#region src/hook/hook-is.d.ts
|
|
571
591
|
/**
|
|
572
|
-
*
|
|
592
|
+
* Determine if a function node is a React Hook based on its name.
|
|
573
593
|
* @param node The function node to check
|
|
574
594
|
* @returns True if the function is a React Hook, false otherwise
|
|
575
595
|
*/
|
|
576
|
-
declare function
|
|
596
|
+
declare function isHook(node: AST.TSESTreeFunction | unit): boolean;
|
|
577
597
|
/**
|
|
578
598
|
* Check if the given node is a React Hook call by its name.
|
|
579
599
|
* @param node The node to check.
|
|
580
600
|
* @returns `true` if the node is a React Hook call, `false` otherwise.
|
|
581
601
|
*/
|
|
582
|
-
declare function
|
|
602
|
+
declare function isHookCall(node: TSESTree.Node | unit): node is TSESTree.CallExpression;
|
|
583
603
|
/**
|
|
584
|
-
*
|
|
604
|
+
* Check if a node is a call to a specific React hook.
|
|
585
605
|
* Returns a function that accepts a hook name to check against.
|
|
586
606
|
* @param node The AST node to check
|
|
587
607
|
* @returns A function that takes a hook name and returns boolean
|
|
588
608
|
*/
|
|
589
|
-
declare function
|
|
609
|
+
declare function isHookCallWithName(node: TSESTree.Node | unit): (name: string) => boolean;
|
|
590
610
|
/**
|
|
591
|
-
*
|
|
611
|
+
* Detect useEffect calls and variations (useLayoutEffect, etc.) using a regex pattern
|
|
592
612
|
* @param node The AST node to check
|
|
593
613
|
* @param additionalEffectHooks Regex pattern matching custom hooks that should be treated as effect hooks
|
|
594
614
|
* @returns True if the node is a useEffect-like call
|
|
595
615
|
*/
|
|
596
616
|
declare function isUseEffectLikeCall(node: TSESTree.Node | unit, additionalEffectHooks?: RegExpLike): node is TSESTree.CallExpression;
|
|
597
617
|
/**
|
|
598
|
-
*
|
|
618
|
+
* Detect useState calls and variations (useCustomState, etc.) using a regex pattern
|
|
599
619
|
* @param node The AST node to check
|
|
600
620
|
* @param additionalStateHooks Regex pattern matching custom hooks that should be treated as state hooks
|
|
601
621
|
* @returns True if the node is a useState-like call
|
|
@@ -629,7 +649,7 @@ declare const REACT_BUILTIN_HOOK_NAMES: readonly ["use", "useActionState", "useC
|
|
|
629
649
|
* @param name The name of the identifier to check.
|
|
630
650
|
* @see https://github.com/facebook/react/blob/1d6c8168db1d82713202e842df3167787ffa00ed/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts#L16
|
|
631
651
|
*/
|
|
632
|
-
declare function
|
|
652
|
+
declare function isHookName(name: string): boolean;
|
|
633
653
|
//#endregion
|
|
634
654
|
//#region src/jsx/jsx-attribute.d.ts
|
|
635
655
|
/**
|
|
@@ -679,7 +699,7 @@ type JsxAttributeValue = {
|
|
|
679
699
|
toStatic(): unknown;
|
|
680
700
|
};
|
|
681
701
|
/**
|
|
682
|
-
*
|
|
702
|
+
* Resolve the static value of a JSX attribute or spread attribute
|
|
683
703
|
*
|
|
684
704
|
* @param context - The ESLint rule context
|
|
685
705
|
* @param attribute - The JSX attribute node to resolve
|
|
@@ -695,7 +715,7 @@ declare function resolveJsxAttributeValue(context: RuleContext, attribute: AST.T
|
|
|
695
715
|
readonly toStatic: () => string | number | bigint | boolean | RegExp | null;
|
|
696
716
|
} | {
|
|
697
717
|
readonly kind: "expression";
|
|
698
|
-
readonly node: TSESTree.
|
|
718
|
+
readonly node: TSESTree.JSXEmptyExpression | TSESTree.Expression;
|
|
699
719
|
readonly toStatic: () => unknown;
|
|
700
720
|
} | {
|
|
701
721
|
readonly kind: "element";
|
|
@@ -703,7 +723,7 @@ declare function resolveJsxAttributeValue(context: RuleContext, attribute: AST.T
|
|
|
703
723
|
readonly toStatic: () => undefined;
|
|
704
724
|
} | {
|
|
705
725
|
readonly kind: "spreadChild";
|
|
706
|
-
readonly node: TSESTree.
|
|
726
|
+
readonly node: TSESTree.JSXEmptyExpression | TSESTree.Expression;
|
|
707
727
|
readonly toStatic: () => undefined;
|
|
708
728
|
} | {
|
|
709
729
|
readonly kind: "spreadProps";
|
|
@@ -769,13 +789,13 @@ declare const JsxDetectionHint: {
|
|
|
769
789
|
*/
|
|
770
790
|
declare const DEFAULT_JSX_DETECTION_HINT: bigint;
|
|
771
791
|
/**
|
|
772
|
-
*
|
|
792
|
+
* Check if a node is a `JSXText` or a `Literal` node
|
|
773
793
|
* @param node The AST node to check
|
|
774
794
|
* @returns `true` if the node is a `JSXText` or a `Literal` node
|
|
775
795
|
*/
|
|
776
796
|
declare function isJsxText(node: TSESTree$1.Node | null | unit): node is TSESTree$1.JSXText | TSESTree$1.Literal;
|
|
777
797
|
/**
|
|
778
|
-
*
|
|
798
|
+
* Determine if a node represents JSX-like content based on heuristics
|
|
779
799
|
* Supports configuration through hint flags to customize detection behavior
|
|
780
800
|
*
|
|
781
801
|
* @param code The source code with scope lookup capability
|
|
@@ -790,7 +810,7 @@ declare function isJsxLike(code: {
|
|
|
790
810
|
//#endregion
|
|
791
811
|
//#region src/jsx/jsx-element-is.d.ts
|
|
792
812
|
/**
|
|
793
|
-
*
|
|
813
|
+
* Determine if a JSX element is a host element
|
|
794
814
|
* Host elements in React start with lowercase letters (e.g., div, span)
|
|
795
815
|
*
|
|
796
816
|
* @param context ESLint rule context
|
|
@@ -799,7 +819,7 @@ declare function isJsxLike(code: {
|
|
|
799
819
|
*/
|
|
800
820
|
declare function isJsxHostElement(context: RuleContext, node: TSESTree.Node): boolean;
|
|
801
821
|
/**
|
|
802
|
-
*
|
|
822
|
+
* Determine if a JSX element is a React Fragment
|
|
803
823
|
* Fragments can be imported from React and used like <Fragment> or <React.Fragment>
|
|
804
824
|
*
|
|
805
825
|
* @param context ESLint rule context
|
|
@@ -844,7 +864,7 @@ declare function stringifyJsx(node: TSESTree$1.JSXIdentifier | TSESTree$1.JSXNam
|
|
|
844
864
|
//#endregion
|
|
845
865
|
//#region src/ref/is-from-ref.d.ts
|
|
846
866
|
/**
|
|
847
|
-
*
|
|
867
|
+
* Check if the variable with the given name is initialized or derived from a ref
|
|
848
868
|
* @param name The variable name
|
|
849
869
|
* @param initialScope The initial scope
|
|
850
870
|
* @returns True if the variable is derived from a ref, false otherwise
|
|
@@ -853,10 +873,10 @@ declare function isInitializedFromRef(name: string, initialScope: Scope): boolea
|
|
|
853
873
|
//#endregion
|
|
854
874
|
//#region src/ref/ref-name.d.ts
|
|
855
875
|
/**
|
|
856
|
-
*
|
|
876
|
+
* Check if a given name corresponds to a ref name
|
|
857
877
|
* @param name The name to check
|
|
858
878
|
* @returns True if the name is "ref" or ends with "Ref"
|
|
859
879
|
*/
|
|
860
880
|
declare function isRefName(name: string): boolean;
|
|
861
881
|
//#endregion
|
|
862
|
-
export {
|
|
882
|
+
export { ClassComponentSemanticNode, ClientFunctionSemanticNode, ComponentDetectionHint, ComponentFlag, ComponentKind, ComponentSemanticNode, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, FindEnclosingComponentOrHookFilter, FunctionComponentSemanticNode, FunctionSemanticNode, HookSemanticNode, JsxAttributeValue, JsxConfig, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, SemanticFunc, SemanticNode, ServerFunctionSemanticNode, findEnclosingComponentOrHook, findParentJsxAttribute, getComponentFlagFromInitPath, getFunctionComponentId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, hasNoneOrLooseComponentName, 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, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isHook, isHookCall, isHookCallWithName, isHookId, isHookName, isInitializedFromReact, isInitializedFromReactNative, isInitializedFromRef, isInsideComponentOrHook, isJsxFragmentElement, isJsxHostElement, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isRefName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, 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, resolveJsxAttributeValue, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
|
package/dist/index.js
CHANGED
|
@@ -6,11 +6,10 @@ import { IdGenerator, RE_ANNOTATION_JSX, RE_ANNOTATION_JSX_FRAG, RE_ANNOTATION_J
|
|
|
6
6
|
import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
7
7
|
import { P, match } from "ts-pattern";
|
|
8
8
|
import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from "@typescript-eslint/utils";
|
|
9
|
-
import birecord from "birecord";
|
|
10
9
|
|
|
11
10
|
//#region src/api/is-from-react.ts
|
|
12
11
|
/**
|
|
13
|
-
*
|
|
12
|
+
* Check if a variable is initialized from React import
|
|
14
13
|
* @param name The variable name
|
|
15
14
|
* @param initialScope The initial scope
|
|
16
15
|
* @param importSource Alternative import source of React (e.g., "preact/compat")
|
|
@@ -23,7 +22,7 @@ function isInitializedFromReact(name, initialScope, importSource = "react") {
|
|
|
23
22
|
//#endregion
|
|
24
23
|
//#region src/api/is-from-react-native.ts
|
|
25
24
|
/**
|
|
26
|
-
*
|
|
25
|
+
* if a variable is initialized from React Native import
|
|
27
26
|
* @param name The variable name
|
|
28
27
|
* @param initialScope The initial scope
|
|
29
28
|
* @param importSource Alternative import source of React Native (e.g., "react-native-web")
|
|
@@ -40,7 +39,7 @@ function isInitializedFromReactNative(name, initialScope, importSource = "react-
|
|
|
40
39
|
//#endregion
|
|
41
40
|
//#region src/api/is-react-api.ts
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
42
|
+
* Check if the node is a React API identifier or member expression
|
|
44
43
|
* @param api The React API name to check against (e.g., "useState", "React.memo")
|
|
45
44
|
* @returns A predicate function to check if a node matches the API
|
|
46
45
|
*/
|
|
@@ -56,7 +55,7 @@ function isReactAPI(api) {
|
|
|
56
55
|
return dual(2, func);
|
|
57
56
|
}
|
|
58
57
|
/**
|
|
59
|
-
*
|
|
58
|
+
* Check if the node is a call expression to a specific React API
|
|
60
59
|
* @param api The React API name to check against
|
|
61
60
|
* @returns A predicate function to check if a node is a call to the API
|
|
62
61
|
*/
|
|
@@ -124,23 +123,23 @@ const REACT_BUILTIN_HOOK_NAMES = [
|
|
|
124
123
|
* @param name The name of the identifier to check.
|
|
125
124
|
* @see https://github.com/facebook/react/blob/1d6c8168db1d82713202e842df3167787ffa00ed/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts#L16
|
|
126
125
|
*/
|
|
127
|
-
function
|
|
126
|
+
function isHookName(name) {
|
|
128
127
|
return name === "use" || /^use[A-Z0-9]/.test(name);
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
//#endregion
|
|
132
131
|
//#region src/hook/hook-is.ts
|
|
133
132
|
/**
|
|
134
|
-
*
|
|
133
|
+
* Determine if a function node is a React Hook based on its name.
|
|
135
134
|
* @param node The function node to check
|
|
136
135
|
* @returns True if the function is a React Hook, false otherwise
|
|
137
136
|
*/
|
|
138
|
-
function
|
|
137
|
+
function isHook(node) {
|
|
139
138
|
if (node == null) return false;
|
|
140
139
|
const id = AST.getFunctionId(node);
|
|
141
140
|
switch (id?.type) {
|
|
142
|
-
case AST_NODE_TYPES.Identifier: return
|
|
143
|
-
case AST_NODE_TYPES.MemberExpression: return "name" in id.property &&
|
|
141
|
+
case AST_NODE_TYPES.Identifier: return isHookName(id.name);
|
|
142
|
+
case AST_NODE_TYPES.MemberExpression: return "name" in id.property && isHookName(id.property.name);
|
|
144
143
|
default: return false;
|
|
145
144
|
}
|
|
146
145
|
}
|
|
@@ -149,20 +148,20 @@ function isReactHook(node) {
|
|
|
149
148
|
* @param node The node to check.
|
|
150
149
|
* @returns `true` if the node is a React Hook call, `false` otherwise.
|
|
151
150
|
*/
|
|
152
|
-
function
|
|
151
|
+
function isHookCall(node) {
|
|
153
152
|
if (node == null) return false;
|
|
154
153
|
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
155
|
-
if (node.callee.type === AST_NODE_TYPES.Identifier) return
|
|
156
|
-
if (node.callee.type === AST_NODE_TYPES.MemberExpression) return node.callee.property.type === AST_NODE_TYPES.Identifier &&
|
|
154
|
+
if (node.callee.type === AST_NODE_TYPES.Identifier) return isHookName(node.callee.name);
|
|
155
|
+
if (node.callee.type === AST_NODE_TYPES.MemberExpression) return node.callee.property.type === AST_NODE_TYPES.Identifier && isHookName(node.callee.property.name);
|
|
157
156
|
return false;
|
|
158
157
|
}
|
|
159
158
|
/**
|
|
160
|
-
*
|
|
159
|
+
* Check if a node is a call to a specific React hook.
|
|
161
160
|
* Returns a function that accepts a hook name to check against.
|
|
162
161
|
* @param node The AST node to check
|
|
163
162
|
* @returns A function that takes a hook name and returns boolean
|
|
164
163
|
*/
|
|
165
|
-
function
|
|
164
|
+
function isHookCallWithName(node) {
|
|
166
165
|
if (node == null || node.type !== AST_NODE_TYPES.CallExpression) return constFalse;
|
|
167
166
|
return (name) => {
|
|
168
167
|
switch (node.callee.type) {
|
|
@@ -173,7 +172,7 @@ function isReactHookCallWithName(node) {
|
|
|
173
172
|
};
|
|
174
173
|
}
|
|
175
174
|
/**
|
|
176
|
-
*
|
|
175
|
+
* Detect useEffect calls and variations (useLayoutEffect, etc.) using a regex pattern
|
|
177
176
|
* @param node The AST node to check
|
|
178
177
|
* @param additionalEffectHooks Regex pattern matching custom hooks that should be treated as effect hooks
|
|
179
178
|
* @returns True if the node is a useEffect-like call
|
|
@@ -188,7 +187,7 @@ function isUseEffectLikeCall(node, additionalEffectHooks = { test: constFalse })
|
|
|
188
187
|
});
|
|
189
188
|
}
|
|
190
189
|
/**
|
|
191
|
-
*
|
|
190
|
+
* Detect useState calls and variations (useCustomState, etc.) using a regex pattern
|
|
192
191
|
* @param node The AST node to check
|
|
193
192
|
* @param additionalStateHooks Regex pattern matching custom hooks that should be treated as state hooks
|
|
194
193
|
* @returns True if the node is a useState-like call
|
|
@@ -202,30 +201,30 @@ function isUseStateLikeCall(node, additionalStateHooks = { test: constFalse }) {
|
|
|
202
201
|
return false;
|
|
203
202
|
});
|
|
204
203
|
}
|
|
205
|
-
const isUseCall = flip(
|
|
206
|
-
const isUseActionStateCall = flip(
|
|
207
|
-
const isUseCallbackCall = flip(
|
|
208
|
-
const isUseContextCall = flip(
|
|
209
|
-
const isUseDebugValueCall = flip(
|
|
210
|
-
const isUseDeferredValueCall = flip(
|
|
211
|
-
const isUseEffectCall = flip(
|
|
212
|
-
const isUseFormStatusCall = flip(
|
|
213
|
-
const isUseIdCall = flip(
|
|
214
|
-
const isUseImperativeHandleCall = flip(
|
|
215
|
-
const isUseInsertionEffectCall = flip(
|
|
216
|
-
const isUseLayoutEffectCall = flip(
|
|
217
|
-
const isUseMemoCall = flip(
|
|
218
|
-
const isUseOptimisticCall = flip(
|
|
219
|
-
const isUseReducerCall = flip(
|
|
220
|
-
const isUseRefCall = flip(
|
|
221
|
-
const isUseStateCall = flip(
|
|
222
|
-
const isUseSyncExternalStoreCall = flip(
|
|
223
|
-
const isUseTransitionCall = flip(
|
|
204
|
+
const isUseCall = flip(isHookCallWithName)("use");
|
|
205
|
+
const isUseActionStateCall = flip(isHookCallWithName)("useActionState");
|
|
206
|
+
const isUseCallbackCall = flip(isHookCallWithName)("useCallback");
|
|
207
|
+
const isUseContextCall = flip(isHookCallWithName)("useContext");
|
|
208
|
+
const isUseDebugValueCall = flip(isHookCallWithName)("useDebugValue");
|
|
209
|
+
const isUseDeferredValueCall = flip(isHookCallWithName)("useDeferredValue");
|
|
210
|
+
const isUseEffectCall = flip(isHookCallWithName)("useEffect");
|
|
211
|
+
const isUseFormStatusCall = flip(isHookCallWithName)("useFormStatus");
|
|
212
|
+
const isUseIdCall = flip(isHookCallWithName)("useId");
|
|
213
|
+
const isUseImperativeHandleCall = flip(isHookCallWithName)("useImperativeHandle");
|
|
214
|
+
const isUseInsertionEffectCall = flip(isHookCallWithName)("useInsertionEffect");
|
|
215
|
+
const isUseLayoutEffectCall = flip(isHookCallWithName)("useLayoutEffect");
|
|
216
|
+
const isUseMemoCall = flip(isHookCallWithName)("useMemo");
|
|
217
|
+
const isUseOptimisticCall = flip(isHookCallWithName)("useOptimistic");
|
|
218
|
+
const isUseReducerCall = flip(isHookCallWithName)("useReducer");
|
|
219
|
+
const isUseRefCall = flip(isHookCallWithName)("useRef");
|
|
220
|
+
const isUseStateCall = flip(isHookCallWithName)("useState");
|
|
221
|
+
const isUseSyncExternalStoreCall = flip(isHookCallWithName)("useSyncExternalStore");
|
|
222
|
+
const isUseTransitionCall = flip(isHookCallWithName)("useTransition");
|
|
224
223
|
|
|
225
224
|
//#endregion
|
|
226
225
|
//#region src/hook/hook-callback.ts
|
|
227
226
|
/**
|
|
228
|
-
*
|
|
227
|
+
* Determine if a node is the setup function passed to a useEffect-like hook
|
|
229
228
|
* @param node The AST node to check
|
|
230
229
|
*/
|
|
231
230
|
function isUseEffectSetupCallback(node) {
|
|
@@ -233,7 +232,7 @@ function isUseEffectSetupCallback(node) {
|
|
|
233
232
|
return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.arguments.at(0) === node && isUseEffectLikeCall(node.parent);
|
|
234
233
|
}
|
|
235
234
|
/**
|
|
236
|
-
*
|
|
235
|
+
* Determine if a node is the cleanup function returned by a useEffect-like hook's setup function.
|
|
237
236
|
* @param node The AST node to check
|
|
238
237
|
*/
|
|
239
238
|
function isUseEffectCleanupCallback(node) {
|
|
@@ -246,10 +245,10 @@ function isUseEffectCleanupCallback(node) {
|
|
|
246
245
|
|
|
247
246
|
//#endregion
|
|
248
247
|
//#region src/hook/hook-id.ts
|
|
249
|
-
function
|
|
248
|
+
function isHookId(id) {
|
|
250
249
|
switch (id.type) {
|
|
251
|
-
case AST_NODE_TYPES.Identifier: return
|
|
252
|
-
case AST_NODE_TYPES.MemberExpression: return "name" in id.property &&
|
|
250
|
+
case AST_NODE_TYPES.Identifier: return isHookName(id.name);
|
|
251
|
+
case AST_NODE_TYPES.MemberExpression: return "name" in id.property && isHookName(id.property.name);
|
|
253
252
|
default: return false;
|
|
254
253
|
}
|
|
255
254
|
}
|
|
@@ -258,7 +257,7 @@ function isReactHookId(id) {
|
|
|
258
257
|
//#region src/hook/hook-collector.ts
|
|
259
258
|
const idGen$2 = new IdGenerator("hook_");
|
|
260
259
|
/**
|
|
261
|
-
* Get a ctx and visitor for the rule to collect hooks
|
|
260
|
+
* Get a ctx and visitor object for the rule to collect hooks
|
|
262
261
|
* @param context The ESLint rule context
|
|
263
262
|
* @returns The ctx and visitor of the collector
|
|
264
263
|
*/
|
|
@@ -270,28 +269,20 @@ function useHookCollector(context) {
|
|
|
270
269
|
const onFunctionEnter = (node) => {
|
|
271
270
|
const id = AST.getFunctionId(node);
|
|
272
271
|
const key = idGen$2.next();
|
|
273
|
-
if (id != null && isReactHookId(id)) {
|
|
274
|
-
functionEntries.push({
|
|
275
|
-
key,
|
|
276
|
-
node,
|
|
277
|
-
isHook: true
|
|
278
|
-
});
|
|
279
|
-
hooks.set(key, {
|
|
280
|
-
id,
|
|
281
|
-
key,
|
|
282
|
-
kind: "function",
|
|
283
|
-
name: AST.toStringFormat(id, getText),
|
|
284
|
-
node,
|
|
285
|
-
flag: 0n,
|
|
286
|
-
hint: 0n,
|
|
287
|
-
hookCalls: []
|
|
288
|
-
});
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
272
|
functionEntries.push({
|
|
292
273
|
key,
|
|
274
|
+
node
|
|
275
|
+
});
|
|
276
|
+
if (id == null || !isHookId(id)) return;
|
|
277
|
+
hooks.set(key, {
|
|
278
|
+
id,
|
|
279
|
+
key,
|
|
280
|
+
kind: "function",
|
|
281
|
+
name: AST.toStringFormat(id, getText),
|
|
293
282
|
node,
|
|
294
|
-
|
|
283
|
+
flag: 0n,
|
|
284
|
+
hint: 0n,
|
|
285
|
+
hookCalls: []
|
|
295
286
|
});
|
|
296
287
|
};
|
|
297
288
|
const onFunctionExit = () => {
|
|
@@ -309,12 +300,10 @@ function useHookCollector(context) {
|
|
|
309
300
|
":function": onFunctionEnter,
|
|
310
301
|
":function:exit": onFunctionExit,
|
|
311
302
|
CallExpression(node) {
|
|
312
|
-
if (!
|
|
313
|
-
const
|
|
314
|
-
if (
|
|
315
|
-
|
|
316
|
-
if (hook == null) return;
|
|
317
|
-
hook.hookCalls.push(node);
|
|
303
|
+
if (!isHookCall(node)) return;
|
|
304
|
+
const entry = getCurrentEntry();
|
|
305
|
+
if (entry == null) return;
|
|
306
|
+
hooks.get(entry.key)?.hookCalls.push(node);
|
|
318
307
|
}
|
|
319
308
|
}
|
|
320
309
|
};
|
|
@@ -388,7 +377,7 @@ function getJsxAttribute(context, node, initialScope) {
|
|
|
388
377
|
//#endregion
|
|
389
378
|
//#region src/jsx/jsx-attribute-value.ts
|
|
390
379
|
/**
|
|
391
|
-
*
|
|
380
|
+
* Resolve the static value of a JSX attribute or spread attribute
|
|
392
381
|
*
|
|
393
382
|
* @param context - The ESLint rule context
|
|
394
383
|
* @param attribute - The JSX attribute node to resolve
|
|
@@ -536,7 +525,7 @@ const JsxDetectionHint = {
|
|
|
536
525
|
*/
|
|
537
526
|
const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.SkipUndefined | JsxDetectionHint.SkipBooleanLiteral;
|
|
538
527
|
/**
|
|
539
|
-
*
|
|
528
|
+
* Check if a node is a `JSXText` or a `Literal` node
|
|
540
529
|
* @param node The AST node to check
|
|
541
530
|
* @returns `true` if the node is a `JSXText` or a `Literal` node
|
|
542
531
|
*/
|
|
@@ -545,7 +534,7 @@ function isJsxText(node) {
|
|
|
545
534
|
return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal;
|
|
546
535
|
}
|
|
547
536
|
/**
|
|
548
|
-
*
|
|
537
|
+
* Determine if a node represents JSX-like content based on heuristics
|
|
549
538
|
* Supports configuration through hint flags to customize detection behavior
|
|
550
539
|
*
|
|
551
540
|
* @param code The source code with scope lookup capability
|
|
@@ -627,7 +616,7 @@ function getJsxElementType(context, node) {
|
|
|
627
616
|
//#endregion
|
|
628
617
|
//#region src/jsx/jsx-element-is.ts
|
|
629
618
|
/**
|
|
630
|
-
*
|
|
619
|
+
* Determine if a JSX element is a host element
|
|
631
620
|
* Host elements in React start with lowercase letters (e.g., div, span)
|
|
632
621
|
*
|
|
633
622
|
* @param context ESLint rule context
|
|
@@ -638,7 +627,7 @@ function isJsxHostElement(context, node) {
|
|
|
638
627
|
return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
|
|
639
628
|
}
|
|
640
629
|
/**
|
|
641
|
-
*
|
|
630
|
+
* Determine if a JSX element is a React Fragment
|
|
642
631
|
* Fragments can be imported from React and used like <Fragment> or <React.Fragment>
|
|
643
632
|
*
|
|
644
633
|
* @param context ESLint rule context
|
|
@@ -709,7 +698,7 @@ function isClassComponent(node) {
|
|
|
709
698
|
/**
|
|
710
699
|
* Check if a node is a React PureComponent
|
|
711
700
|
* @param node The AST node to check
|
|
712
|
-
* @returns `true` if the node is a
|
|
701
|
+
* @returns `true` if the node is a PureComponent, `false` otherwise
|
|
713
702
|
*/
|
|
714
703
|
function isPureComponent(node) {
|
|
715
704
|
if ("superClass" in node && node.superClass != null) {
|
|
@@ -722,6 +711,99 @@ function isPureComponent(node) {
|
|
|
722
711
|
return false;
|
|
723
712
|
}
|
|
724
713
|
|
|
714
|
+
//#endregion
|
|
715
|
+
//#region src/component/component-wrapper.ts
|
|
716
|
+
/**
|
|
717
|
+
* Check if the node is a call expression for a component wrapper
|
|
718
|
+
* @param context The ESLint rule context
|
|
719
|
+
* @param node The node to check
|
|
720
|
+
* @returns `true` if the node is a call expression for a component wrapper
|
|
721
|
+
*/
|
|
722
|
+
function isComponentWrapperCall(context, node) {
|
|
723
|
+
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
724
|
+
return isMemoCall(context, node) || isForwardRefCall(context, node);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Check if the node is a call expression for a component wrapper loosely
|
|
728
|
+
* @param context The ESLint rule context
|
|
729
|
+
* @param node The node to check
|
|
730
|
+
* @returns `true` if the node is a call expression for a component wrapper loosely
|
|
731
|
+
*/
|
|
732
|
+
function isComponentWrapperCallLoose(context, node) {
|
|
733
|
+
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
734
|
+
return isComponentWrapperCall(context, node) || isUseCallbackCall(node);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Check if the node is a callback function passed to a component wrapper
|
|
738
|
+
* @param context The ESLint rule context
|
|
739
|
+
* @param node The node to check
|
|
740
|
+
* @returns `true` if the node is a callback function passed to a component wrapper
|
|
741
|
+
*/
|
|
742
|
+
function isComponentWrapperCallback(context, node) {
|
|
743
|
+
if (!AST.isFunction(node)) return false;
|
|
744
|
+
const parent = node.parent;
|
|
745
|
+
if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
746
|
+
return isComponentWrapperCall(context, parent);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Check if the node is a callback function passed to a component wrapper loosely
|
|
750
|
+
* @param context The ESLint rule context
|
|
751
|
+
* @param node The node to check
|
|
752
|
+
* @returns `true` if the node is a callback function passed to a component wrapper loosely
|
|
753
|
+
*/
|
|
754
|
+
function isComponentWrapperCallbackLoose(context, node) {
|
|
755
|
+
if (!AST.isFunction(node)) return false;
|
|
756
|
+
const parent = node.parent;
|
|
757
|
+
if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
758
|
+
return isComponentWrapperCallLoose(context, parent);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
//#endregion
|
|
762
|
+
//#region src/component/component-id.ts
|
|
763
|
+
/**
|
|
764
|
+
* Get function component identifier from `const Component = memo(() => {});`
|
|
765
|
+
* @param context The rule context
|
|
766
|
+
* @param node The function node to analyze
|
|
767
|
+
* @returns The function identifier or `unit` if not found
|
|
768
|
+
*/
|
|
769
|
+
function getFunctionComponentId(context, node) {
|
|
770
|
+
const functionId = AST.getFunctionId(node);
|
|
771
|
+
if (functionId != null) return functionId;
|
|
772
|
+
const { parent } = node;
|
|
773
|
+
if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.id;
|
|
774
|
+
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;
|
|
775
|
+
return unit;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
//#endregion
|
|
779
|
+
//#region src/component/component-name.ts
|
|
780
|
+
/**
|
|
781
|
+
* Check if a string matches the strict component name pattern
|
|
782
|
+
* @param name The name to check
|
|
783
|
+
*/
|
|
784
|
+
function isComponentName(name) {
|
|
785
|
+
return RE_COMPONENT_NAME.test(name);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Check if a string matches the loose component name pattern
|
|
789
|
+
* @param name The name to check
|
|
790
|
+
*/
|
|
791
|
+
function isComponentNameLoose(name) {
|
|
792
|
+
return RE_COMPONENT_NAME_LOOSE.test(name);
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Check if the function has no name or a loose component name
|
|
796
|
+
* @param context The rule context
|
|
797
|
+
* @param fn The function node
|
|
798
|
+
*/
|
|
799
|
+
function hasNoneOrLooseComponentName(context, fn) {
|
|
800
|
+
const id = getFunctionComponentId(context, fn);
|
|
801
|
+
if (id == null) return true;
|
|
802
|
+
if (id.type === AST_NODE_TYPES.Identifier) return isComponentNameLoose(id.name);
|
|
803
|
+
if (id.type === AST_NODE_TYPES.MemberExpression && id.property.type === AST_NODE_TYPES.Identifier) return isComponentNameLoose(id.property.name);
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
|
|
725
807
|
//#endregion
|
|
726
808
|
//#region src/component/component-render-method.ts
|
|
727
809
|
/**
|
|
@@ -743,7 +825,7 @@ function isRenderMethodLike(node) {
|
|
|
743
825
|
//#endregion
|
|
744
826
|
//#region src/component/component-definition.ts
|
|
745
827
|
/**
|
|
746
|
-
*
|
|
828
|
+
* Check if the given node is a function within a render method of a class component.
|
|
747
829
|
*
|
|
748
830
|
* @param node The AST node to check
|
|
749
831
|
* @returns `true` if the node is a render function inside a class component
|
|
@@ -761,7 +843,7 @@ function isRenderMethodCallback(node) {
|
|
|
761
843
|
return greatGrandparent != null && isRenderMethodLike(parent) && isClassComponent(greatGrandparent);
|
|
762
844
|
}
|
|
763
845
|
/**
|
|
764
|
-
*
|
|
846
|
+
* Check if a function node should be excluded based on provided detection hints
|
|
765
847
|
*
|
|
766
848
|
* @param node The function node to check
|
|
767
849
|
* @param hint Component detection hints as bit flags
|
|
@@ -779,7 +861,7 @@ function shouldExcludeBasedOnHint(node, hint) {
|
|
|
779
861
|
return false;
|
|
780
862
|
}
|
|
781
863
|
/**
|
|
782
|
-
*
|
|
864
|
+
* Determine if the node is an argument within `createElement`'s children list (3rd argument onwards)
|
|
783
865
|
*
|
|
784
866
|
* @param context The rule context
|
|
785
867
|
* @param node The AST node to check
|
|
@@ -792,7 +874,7 @@ function isChildrenOfCreateElement(context, node) {
|
|
|
792
874
|
return parent.arguments.slice(2).some((arg) => arg === node);
|
|
793
875
|
}
|
|
794
876
|
/**
|
|
795
|
-
*
|
|
877
|
+
* Determine if a function node represents a valid React component definition
|
|
796
878
|
*
|
|
797
879
|
* @param context The rule context
|
|
798
880
|
* @param node The function node to analyze
|
|
@@ -800,6 +882,7 @@ function isChildrenOfCreateElement(context, node) {
|
|
|
800
882
|
* @returns `true` if the node is considered a component definition
|
|
801
883
|
*/
|
|
802
884
|
function isComponentDefinition(context, node, hint) {
|
|
885
|
+
if (!hasNoneOrLooseComponentName(context, node)) return false;
|
|
803
886
|
if (isChildrenOfCreateElement(context, node) || isRenderMethodCallback(node)) return false;
|
|
804
887
|
if (shouldExcludeBasedOnHint(node, hint)) return false;
|
|
805
888
|
const significantParent = AST.findParentNode(node, AST.isOneOf([
|
|
@@ -815,76 +898,25 @@ function isComponentDefinition(context, node, hint) {
|
|
|
815
898
|
}
|
|
816
899
|
|
|
817
900
|
//#endregion
|
|
818
|
-
//#region src/component/component-
|
|
819
|
-
/**
|
|
820
|
-
* Check if the node is a call expression for a component wrapper
|
|
821
|
-
* @param context The ESLint rule context
|
|
822
|
-
* @param node The node to check
|
|
823
|
-
* @returns `true` if the node is a call expression for a component wrapper
|
|
824
|
-
*/
|
|
825
|
-
function isComponentWrapperCall(context, node) {
|
|
826
|
-
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
827
|
-
return isMemoCall(context, node) || isForwardRefCall(context, node);
|
|
828
|
-
}
|
|
829
|
-
/**
|
|
830
|
-
* Check if the node is a call expression for a component wrapper loosely
|
|
831
|
-
* @param context The ESLint rule context
|
|
832
|
-
* @param node The node to check
|
|
833
|
-
* @returns `true` if the node is a call expression for a component wrapper loosely
|
|
834
|
-
*/
|
|
835
|
-
function isComponentWrapperCallLoose(context, node) {
|
|
836
|
-
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
837
|
-
return isComponentWrapperCall(context, node) || isUseCallbackCall(node);
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
|
-
* Check if the node is a callback function passed to a component wrapper
|
|
841
|
-
* @param context The ESLint rule context
|
|
842
|
-
* @param node The node to check
|
|
843
|
-
* @returns `true` if the node is a callback function passed to a component wrapper
|
|
844
|
-
*/
|
|
845
|
-
function isComponentWrapperCallback(context, node) {
|
|
846
|
-
if (!AST.isFunction(node)) return false;
|
|
847
|
-
const parent = node.parent;
|
|
848
|
-
if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
849
|
-
return isComponentWrapperCall(context, parent);
|
|
850
|
-
}
|
|
901
|
+
//#region src/component/component-flag.ts
|
|
851
902
|
/**
|
|
852
|
-
*
|
|
853
|
-
* @param context The ESLint rule context
|
|
854
|
-
* @param node The node to check
|
|
855
|
-
* @returns `true` if the node is a callback function passed to a component wrapper loosely
|
|
903
|
+
* Component flag constants
|
|
856
904
|
*/
|
|
857
|
-
function isComponentWrapperCallbackLoose(context, node) {
|
|
858
|
-
if (!AST.isFunction(node)) return false;
|
|
859
|
-
const parent = node.parent;
|
|
860
|
-
if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
861
|
-
return isComponentWrapperCallLoose(context, parent);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
//#endregion
|
|
865
|
-
//#region src/component/component-id.ts
|
|
866
|
-
function getFunctionComponentId(context, node) {
|
|
867
|
-
const functionId = AST.getFunctionId(node);
|
|
868
|
-
if (functionId != null) return functionId;
|
|
869
|
-
const { parent } = node;
|
|
870
|
-
if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.id;
|
|
871
|
-
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;
|
|
872
|
-
return unit;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
//#endregion
|
|
876
|
-
//#region src/component/component-flag.ts
|
|
877
905
|
const ComponentFlag = {
|
|
878
906
|
None: 0n,
|
|
879
907
|
PureComponent: 1n << 0n,
|
|
880
908
|
CreateElement: 1n << 1n,
|
|
881
909
|
Memo: 1n << 2n,
|
|
882
|
-
ForwardRef: 1n << 3n
|
|
883
|
-
Async: 1n << 4n
|
|
910
|
+
ForwardRef: 1n << 3n
|
|
884
911
|
};
|
|
885
912
|
|
|
886
913
|
//#endregion
|
|
887
914
|
//#region src/component/component-init-path.ts
|
|
915
|
+
/**
|
|
916
|
+
* Get component flag from init path
|
|
917
|
+
* @param initPath The init path of the function component
|
|
918
|
+
* @returns The component flag
|
|
919
|
+
*/
|
|
888
920
|
function getComponentFlagFromInitPath(initPath) {
|
|
889
921
|
let flag = ComponentFlag.None;
|
|
890
922
|
if (initPath != null && AST.hasCallInFunctionInitPath("memo", initPath)) flag |= ComponentFlag.Memo;
|
|
@@ -892,46 +924,17 @@ function getComponentFlagFromInitPath(initPath) {
|
|
|
892
924
|
return flag;
|
|
893
925
|
}
|
|
894
926
|
|
|
895
|
-
//#endregion
|
|
896
|
-
//#region src/component/component-name.ts
|
|
897
|
-
/**
|
|
898
|
-
* Check if a string matches the strict component name pattern
|
|
899
|
-
* @param name The name to check
|
|
900
|
-
*/
|
|
901
|
-
function isComponentName(name) {
|
|
902
|
-
return RE_COMPONENT_NAME.test(name);
|
|
903
|
-
}
|
|
904
|
-
/**
|
|
905
|
-
* Check if a string matches the loose component name pattern
|
|
906
|
-
* @param name The name to check
|
|
907
|
-
*/
|
|
908
|
-
function isComponentNameLoose(name) {
|
|
909
|
-
return RE_COMPONENT_NAME_LOOSE.test(name);
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* Check if the function has no name or a loose component name
|
|
913
|
-
* @param context The rule context
|
|
914
|
-
* @param fn The function node
|
|
915
|
-
*/
|
|
916
|
-
function hasNoneOrLooseComponentName(context, fn) {
|
|
917
|
-
const id = getFunctionComponentId(context, fn);
|
|
918
|
-
if (id == null) return true;
|
|
919
|
-
if (id.type === AST_NODE_TYPES.Identifier) return isComponentNameLoose(id.name);
|
|
920
|
-
if (id.type === AST_NODE_TYPES.MemberExpression && id.property.type === AST_NODE_TYPES.Identifier) return isComponentNameLoose(id.property.name);
|
|
921
|
-
return false;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
927
|
//#endregion
|
|
925
928
|
//#region src/component/component-collector.ts
|
|
926
929
|
const idGen$1 = new IdGenerator("function_component_");
|
|
927
930
|
/**
|
|
928
|
-
* Get a ctx and visitor for the rule to collect function components
|
|
931
|
+
* Get a ctx and visitor object for the rule to collect function components
|
|
929
932
|
* @param context The ESLint rule context
|
|
930
933
|
* @param options The options to use
|
|
931
934
|
* @returns The ctx and visitor of the collector
|
|
932
935
|
*/
|
|
933
936
|
function useComponentCollector(context, options = {}) {
|
|
934
|
-
const { collectDisplayName = false,
|
|
937
|
+
const { collectDisplayName = false, hint = DEFAULT_COMPONENT_DETECTION_HINT } = options;
|
|
935
938
|
const functionEntries = [];
|
|
936
939
|
const components = /* @__PURE__ */ new Map();
|
|
937
940
|
const getText = (n) => context.sourceCode.getText(n);
|
|
@@ -941,11 +944,20 @@ function useComponentCollector(context, options = {}) {
|
|
|
941
944
|
const exp = AST.findParentNode(node, (n) => n.type === AST_NODE_TYPES.ExportDefaultDeclaration);
|
|
942
945
|
const isExportDefault = exp != null;
|
|
943
946
|
const isExportDefaultDeclaration = exp != null && AST.getUnderlyingExpression(exp.declaration) === node;
|
|
947
|
+
const id = getFunctionComponentId(context, node);
|
|
948
|
+
const name = id == null ? unit : AST.toStringFormat(id, getText);
|
|
949
|
+
const initPath = AST.getFunctionInitPath(node);
|
|
944
950
|
functionEntries.push({
|
|
951
|
+
id: getFunctionComponentId(context, node),
|
|
945
952
|
key,
|
|
953
|
+
kind: "function",
|
|
954
|
+
name,
|
|
946
955
|
node,
|
|
956
|
+
displayName: unit,
|
|
957
|
+
flag: getComponentFlagFromInitPath(initPath),
|
|
958
|
+
hint,
|
|
947
959
|
hookCalls: [],
|
|
948
|
-
|
|
960
|
+
initPath,
|
|
949
961
|
isComponentDefinition: isComponentDefinition(context, node, hint),
|
|
950
962
|
isExportDefault,
|
|
951
963
|
isExportDefaultDeclaration,
|
|
@@ -971,29 +983,12 @@ function useComponentCollector(context, options = {}) {
|
|
|
971
983
|
"ArrowFunctionExpression[body.type!='BlockStatement']"() {
|
|
972
984
|
const entry = getCurrentEntry();
|
|
973
985
|
if (entry == null) return;
|
|
974
|
-
if (!entry.isComponentDefinition) return;
|
|
975
986
|
const { body } = entry.node;
|
|
976
987
|
if (body.type === AST_NODE_TYPES.BlockStatement) return;
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
const name = id == null ? unit : AST.toStringFormat(id, getText);
|
|
982
|
-
components.set(key, {
|
|
983
|
-
id,
|
|
984
|
-
key,
|
|
985
|
-
kind: "function",
|
|
986
|
-
name,
|
|
987
|
-
node: entry.node,
|
|
988
|
-
displayName: unit,
|
|
989
|
-
flag: getComponentFlagFromInitPath(initPath),
|
|
990
|
-
hint,
|
|
991
|
-
hookCalls: entry.hookCalls,
|
|
992
|
-
initPath,
|
|
993
|
-
isExportDefault: entry.isExportDefault,
|
|
994
|
-
isExportDefaultDeclaration: entry.isExportDefaultDeclaration,
|
|
995
|
-
rets: [body]
|
|
996
|
-
});
|
|
988
|
+
entry.rets.push(body);
|
|
989
|
+
if (!entry.isComponentDefinition) return;
|
|
990
|
+
if (!components.has(entry.key) && !isJsxLike(context.sourceCode, body, hint)) return;
|
|
991
|
+
components.set(entry.key, entry);
|
|
997
992
|
},
|
|
998
993
|
...collectDisplayName ? { [AST.SEL_DISPLAY_NAME_ASSIGNMENT_EXPRESSION](node) {
|
|
999
994
|
const { left, right } = node;
|
|
@@ -1003,39 +998,22 @@ function useComponentCollector(context, options = {}) {
|
|
|
1003
998
|
if (component == null) return;
|
|
1004
999
|
component.displayName = right;
|
|
1005
1000
|
} } : {},
|
|
1006
|
-
|
|
1007
|
-
if (!
|
|
1001
|
+
CallExpression(node) {
|
|
1002
|
+
if (!isHookCall(node)) return;
|
|
1008
1003
|
const entry = getCurrentEntry();
|
|
1009
1004
|
if (entry == null) return;
|
|
1010
1005
|
entry.hookCalls.push(node);
|
|
1011
|
-
|
|
1006
|
+
if (!entry.isComponentDefinition) return;
|
|
1007
|
+
components.set(entry.key, entry);
|
|
1008
|
+
},
|
|
1012
1009
|
ReturnStatement(node) {
|
|
1013
1010
|
const entry = getCurrentEntry();
|
|
1014
1011
|
if (entry == null) return;
|
|
1012
|
+
entry.rets.push(node.argument);
|
|
1015
1013
|
if (!entry.isComponentDefinition) return;
|
|
1016
1014
|
const { argument } = node;
|
|
1017
|
-
entry.
|
|
1018
|
-
|
|
1019
|
-
entry.isComponent = true;
|
|
1020
|
-
const initPath = AST.getFunctionInitPath(entry.node);
|
|
1021
|
-
const id = getFunctionComponentId(context, entry.node);
|
|
1022
|
-
const key = entry.key;
|
|
1023
|
-
const name = id == null ? unit : AST.toStringFormat(id, getText);
|
|
1024
|
-
components.set(key, {
|
|
1025
|
-
id,
|
|
1026
|
-
key,
|
|
1027
|
-
kind: "function",
|
|
1028
|
-
name,
|
|
1029
|
-
node: entry.node,
|
|
1030
|
-
displayName: unit,
|
|
1031
|
-
flag: getComponentFlagFromInitPath(initPath),
|
|
1032
|
-
hint,
|
|
1033
|
-
hookCalls: entry.hookCalls,
|
|
1034
|
-
initPath,
|
|
1035
|
-
isExportDefault: entry.isExportDefault,
|
|
1036
|
-
isExportDefaultDeclaration: entry.isExportDefaultDeclaration,
|
|
1037
|
-
rets: entry.rets
|
|
1038
|
-
});
|
|
1015
|
+
if (!components.has(entry.key) && !isJsxLike(context.sourceCode, argument, hint)) return;
|
|
1016
|
+
components.set(entry.key, entry);
|
|
1039
1017
|
}
|
|
1040
1018
|
}
|
|
1041
1019
|
};
|
|
@@ -1045,7 +1023,7 @@ function useComponentCollector(context, options = {}) {
|
|
|
1045
1023
|
//#region src/component/component-collector-legacy.ts
|
|
1046
1024
|
const idGen = new IdGenerator("class_component_");
|
|
1047
1025
|
/**
|
|
1048
|
-
* Get a ctx and visitor object for the rule to collect class
|
|
1026
|
+
* Get a ctx and visitor object for the rule to collect class componentss
|
|
1049
1027
|
* @param context The ESLint rule context
|
|
1050
1028
|
* @returns The ctx and visitor of the collector
|
|
1051
1029
|
*/
|
|
@@ -1108,9 +1086,7 @@ function isAssignmentToThisState(node) {
|
|
|
1108
1086
|
* @param isStatic Whether the method is static
|
|
1109
1087
|
*/
|
|
1110
1088
|
function createLifecycleChecker(methodName, isStatic = false) {
|
|
1111
|
-
return
|
|
1112
|
-
return AST.isMethodOrProperty(node) && node.static === isStatic && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === methodName;
|
|
1113
|
-
};
|
|
1089
|
+
return (node) => AST.isMethodOrProperty(node) && node.static === isStatic && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === methodName;
|
|
1114
1090
|
}
|
|
1115
1091
|
const isRender = createLifecycleChecker("render");
|
|
1116
1092
|
const isComponentDidCatch = createLifecycleChecker("componentDidCatch");
|
|
@@ -1134,36 +1110,22 @@ const isGetDerivedStateFromError = createLifecycleChecker("getDerivedStateFromEr
|
|
|
1134
1110
|
//#endregion
|
|
1135
1111
|
//#region src/component/component-method-callback.ts
|
|
1136
1112
|
/**
|
|
1137
|
-
*
|
|
1138
|
-
* @param node The
|
|
1139
|
-
* @returns
|
|
1113
|
+
* Check if the given node is a componentDidMount callback
|
|
1114
|
+
* @param node The node to check
|
|
1115
|
+
* @returns True if the node is a componentDidMount callback, false otherwise
|
|
1140
1116
|
*/
|
|
1141
1117
|
function isComponentDidMountCallback(node) {
|
|
1142
1118
|
return AST.isFunction(node) && isComponentDidMount(node.parent) && node.parent.value === node;
|
|
1143
1119
|
}
|
|
1144
1120
|
/**
|
|
1145
|
-
*
|
|
1146
|
-
* @param node The
|
|
1147
|
-
* @returns
|
|
1121
|
+
* Check if the given node is a componentWillUnmount callback
|
|
1122
|
+
* @param node The node to check
|
|
1123
|
+
* @returns True if the node is a componentWillUnmount callback, false otherwise
|
|
1148
1124
|
*/
|
|
1149
1125
|
function isComponentWillUnmountCallback(node) {
|
|
1150
1126
|
return AST.isFunction(node) && isComponentWillUnmount(node.parent) && node.parent.value === node;
|
|
1151
1127
|
}
|
|
1152
1128
|
|
|
1153
|
-
//#endregion
|
|
1154
|
-
//#region src/component/component-phase.ts
|
|
1155
|
-
const ComponentPhaseRelevance = birecord({
|
|
1156
|
-
mount: "unmount",
|
|
1157
|
-
setup: "cleanup"
|
|
1158
|
-
});
|
|
1159
|
-
|
|
1160
|
-
//#endregion
|
|
1161
|
-
//#region src/component/component-phase-helpers.ts
|
|
1162
|
-
const isInversePhase = dual(2, (a, b) => ComponentPhaseRelevance.get(a) === b);
|
|
1163
|
-
function getPhaseKindOfFunction(node) {
|
|
1164
|
-
return match(node).when(isUseEffectSetupCallback, () => "setup").when(isUseEffectCleanupCallback, () => "cleanup").when(isComponentDidMountCallback, () => "mount").when(isComponentWillUnmountCallback, () => "unmount").otherwise(() => null);
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
1129
|
//#endregion
|
|
1168
1130
|
//#region src/component/component-render-prop.ts
|
|
1169
1131
|
/**
|
|
@@ -1248,7 +1210,7 @@ function isDeclaredInRenderPropLoose(node) {
|
|
|
1248
1210
|
*/
|
|
1249
1211
|
function findEnclosingComponentOrHook(node, test = (n, name) => {
|
|
1250
1212
|
if (name == null) return false;
|
|
1251
|
-
return isComponentNameLoose(name) ||
|
|
1213
|
+
return isComponentNameLoose(name) || isHookName(name);
|
|
1252
1214
|
}) {
|
|
1253
1215
|
const enclosingNode = AST.findParentNode(node, (n) => {
|
|
1254
1216
|
if (!AST.isFunction(n)) return false;
|
|
@@ -1263,7 +1225,7 @@ function findEnclosingComponentOrHook(node, test = (n, name) => {
|
|
|
1263
1225
|
//#endregion
|
|
1264
1226
|
//#region src/hierarchy/is-inside-component-or-hook.ts
|
|
1265
1227
|
/**
|
|
1266
|
-
*
|
|
1228
|
+
* Check if a given AST node is inside a React component or hook
|
|
1267
1229
|
* @param node The AST node to check
|
|
1268
1230
|
* @returns True if the node is inside a component or hook, false otherwise
|
|
1269
1231
|
*/
|
|
@@ -1274,7 +1236,7 @@ function isInsideComponentOrHook(node) {
|
|
|
1274
1236
|
//#endregion
|
|
1275
1237
|
//#region src/ref/ref-name.ts
|
|
1276
1238
|
/**
|
|
1277
|
-
*
|
|
1239
|
+
* Check if a given name corresponds to a ref name
|
|
1278
1240
|
* @param name The name to check
|
|
1279
1241
|
* @returns True if the name is "ref" or ends with "Ref"
|
|
1280
1242
|
*/
|
|
@@ -1285,7 +1247,7 @@ function isRefName(name) {
|
|
|
1285
1247
|
//#endregion
|
|
1286
1248
|
//#region src/ref/is-from-ref.ts
|
|
1287
1249
|
/**
|
|
1288
|
-
*
|
|
1250
|
+
* Check if the variable with the given name is initialized or derived from a ref
|
|
1289
1251
|
* @param name The variable name
|
|
1290
1252
|
* @param initialScope The initial scope
|
|
1291
1253
|
* @returns True if the variable is derived from a ref, false otherwise
|
|
@@ -1304,4 +1266,4 @@ function isInitializedFromRef(name, initialScope) {
|
|
|
1304
1266
|
}
|
|
1305
1267
|
|
|
1306
1268
|
//#endregion
|
|
1307
|
-
export { ComponentDetectionHint, ComponentFlag,
|
|
1269
|
+
export { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, findEnclosingComponentOrHook, findParentJsxAttribute, getComponentFlagFromInitPath, getFunctionComponentId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, hasNoneOrLooseComponentName, 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, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isHook, isHookCall, isHookCallWithName, isHookId, isHookName, isInitializedFromReact, isInitializedFromReactNative, isInitializedFromRef, isInsideComponentOrHook, isJsxFragmentElement, isJsxHostElement, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isRefName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, 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, resolveJsxAttributeValue, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-react/core",
|
|
3
|
-
"version": "2.7.5-next.
|
|
3
|
+
"version": "2.7.5-next.10",
|
|
4
4
|
"description": "ESLint React's ESLint utility module for static analysis of React core APIs and patterns.",
|
|
5
5
|
"homepage": "https://github.com/Rel1cx/eslint-react",
|
|
6
6
|
"bugs": {
|
|
@@ -30,15 +30,14 @@
|
|
|
30
30
|
"./package.json"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@typescript-eslint/scope-manager": "^8.
|
|
34
|
-
"@typescript-eslint/types": "^8.
|
|
35
|
-
"@typescript-eslint/utils": "^8.
|
|
36
|
-
"birecord": "^0.1.1",
|
|
33
|
+
"@typescript-eslint/scope-manager": "^8.54.0",
|
|
34
|
+
"@typescript-eslint/types": "^8.54.0",
|
|
35
|
+
"@typescript-eslint/utils": "^8.54.0",
|
|
37
36
|
"ts-pattern": "^5.9.0",
|
|
38
|
-
"@eslint-react/ast": "2.7.5-next.
|
|
39
|
-
"@eslint-react/shared": "2.7.5-next.
|
|
40
|
-
"@eslint-react/
|
|
41
|
-
"@eslint-react/
|
|
37
|
+
"@eslint-react/ast": "2.7.5-next.10",
|
|
38
|
+
"@eslint-react/shared": "2.7.5-next.10",
|
|
39
|
+
"@eslint-react/eff": "2.7.5-next.10",
|
|
40
|
+
"@eslint-react/var": "2.7.5-next.10"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"tsdown": "^0.20.1",
|