@eslint-react/core 2.3.13-next.0 → 2.3.13-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +123 -45
- package/dist/index.js +236 -187
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
import { TSESTree } from "@typescript-eslint/types";
|
|
2
1
|
import * as AST from "@eslint-react/ast";
|
|
3
2
|
import { unit } from "@eslint-react/eff";
|
|
4
3
|
import { RuleContext } from "@eslint-react/shared";
|
|
5
|
-
import
|
|
4
|
+
import { TSESTree } from "@typescript-eslint/types";
|
|
6
5
|
import { ESLintUtils, TSESTree as TSESTree$1 } from "@typescript-eslint/utils";
|
|
6
|
+
import * as birecord0 from "birecord";
|
|
7
7
|
import { Scope } from "@typescript-eslint/scope-manager";
|
|
8
8
|
import * as typescript0 from "typescript";
|
|
9
9
|
|
|
10
|
-
//#region src/component/component-children.d.ts
|
|
11
|
-
/**
|
|
12
|
-
* Determines whether inside `createElement`'s children.
|
|
13
|
-
* @param context The rule context
|
|
14
|
-
* @param node The AST node to check
|
|
15
|
-
* @returns `true` if the node is inside createElement's children
|
|
16
|
-
*/
|
|
17
|
-
declare function isChildrenOfCreateElement(context: RuleContext, node: TSESTree.Node): boolean;
|
|
18
|
-
//#endregion
|
|
19
10
|
//#region src/component/component-detection-hint.d.ts
|
|
20
11
|
type ComponentDetectionHint = bigint;
|
|
21
12
|
/**
|
|
@@ -93,34 +84,94 @@ interface SemanticNode {
|
|
|
93
84
|
//#region src/component/component-flag.d.ts
|
|
94
85
|
type ComponentFlag = bigint;
|
|
95
86
|
declare const ComponentFlag: {
|
|
87
|
+
/** No flags set */
|
|
96
88
|
None: bigint;
|
|
89
|
+
/** Indicates the component is a pure component (e.g. extends PureComponent) */
|
|
97
90
|
PureComponent: bigint;
|
|
91
|
+
/** Indicates the component creates elements using `createElement` instead of JSX */
|
|
98
92
|
CreateElement: bigint;
|
|
93
|
+
/** Indicates the component is memoized (e.g. React.memo) */
|
|
99
94
|
Memo: bigint;
|
|
95
|
+
/** Indicates the component forwards a ref (e.g. React.forwardRef) */
|
|
100
96
|
ForwardRef: bigint;
|
|
97
|
+
/** Indicates the component is asynchronous */
|
|
101
98
|
Async: bigint;
|
|
102
99
|
};
|
|
103
100
|
//#endregion
|
|
104
101
|
//#region src/component/component-semantic-node.d.ts
|
|
102
|
+
/**
|
|
103
|
+
* Represents a React function component
|
|
104
|
+
*/
|
|
105
105
|
interface FunctionComponent extends SemanticNode {
|
|
106
|
+
/**
|
|
107
|
+
* The identifier or identifier sequence of the component
|
|
108
|
+
*/
|
|
106
109
|
id: unit | TSESTree.Identifier | TSESTree.Identifier[];
|
|
110
|
+
/**
|
|
111
|
+
* The kind of component
|
|
112
|
+
*/
|
|
107
113
|
kind: "function";
|
|
114
|
+
/**
|
|
115
|
+
* The AST node of the function
|
|
116
|
+
*/
|
|
108
117
|
node: AST.TSESTreeFunction;
|
|
118
|
+
/**
|
|
119
|
+
* Flags describing the component's characteristics
|
|
120
|
+
*/
|
|
109
121
|
flag: ComponentFlag;
|
|
122
|
+
/**
|
|
123
|
+
* Hint for how the component was detected
|
|
124
|
+
*/
|
|
110
125
|
hint: ComponentDetectionHint;
|
|
126
|
+
/**
|
|
127
|
+
* The initialization path of the function
|
|
128
|
+
*/
|
|
111
129
|
initPath: unit | AST.FunctionInitPath;
|
|
130
|
+
/**
|
|
131
|
+
* List of hook calls within the component
|
|
132
|
+
*/
|
|
112
133
|
hookCalls: TSESTree.CallExpression[];
|
|
134
|
+
/**
|
|
135
|
+
* The display name of the component
|
|
136
|
+
*/
|
|
113
137
|
displayName: unit | TSESTree.Expression;
|
|
114
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Represents a React class component
|
|
141
|
+
*/
|
|
115
142
|
interface ClassComponent extends SemanticNode {
|
|
143
|
+
/**
|
|
144
|
+
* The identifier of the component
|
|
145
|
+
*/
|
|
116
146
|
id: unit | TSESTree.Identifier;
|
|
147
|
+
/**
|
|
148
|
+
* The kind of component
|
|
149
|
+
*/
|
|
117
150
|
kind: "class";
|
|
151
|
+
/**
|
|
152
|
+
* The AST node of the class
|
|
153
|
+
*/
|
|
118
154
|
node: AST.TSESTreeClass;
|
|
155
|
+
/**
|
|
156
|
+
* Flags describing the component's characteristics
|
|
157
|
+
*/
|
|
119
158
|
flag: ComponentFlag;
|
|
159
|
+
/**
|
|
160
|
+
* Hint for how the component was detected
|
|
161
|
+
*/
|
|
120
162
|
hint: ComponentDetectionHint;
|
|
163
|
+
/**
|
|
164
|
+
* List of methods and properties in the class
|
|
165
|
+
*/
|
|
121
166
|
methods: AST.TSESTreeMethodOrProperty[];
|
|
167
|
+
/**
|
|
168
|
+
* The display name of the component
|
|
169
|
+
*/
|
|
122
170
|
displayName: unit | TSESTree.Expression;
|
|
123
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Union type representing either a class or function component
|
|
174
|
+
*/
|
|
124
175
|
type Component = ClassComponent | FunctionComponent;
|
|
125
176
|
//#endregion
|
|
126
177
|
//#region src/component/component-collector.d.ts
|
|
@@ -167,27 +218,32 @@ declare namespace useComponentCollectorLegacy {
|
|
|
167
218
|
* @returns The context and listeners for the rule
|
|
168
219
|
*/
|
|
169
220
|
declare function useComponentCollectorLegacy(): useComponentCollectorLegacy.ReturnType;
|
|
170
|
-
//#endregion
|
|
171
|
-
//#region src/component/component-definition.d.ts
|
|
172
221
|
/**
|
|
173
|
-
* Check whether given node is a
|
|
174
|
-
* @
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
*
|
|
181
|
-
* @param node The
|
|
182
|
-
* @
|
|
222
|
+
* Check whether the given node is a this.setState() call
|
|
223
|
+
* @param node The node to check
|
|
224
|
+
* @param node
|
|
225
|
+
* @internal
|
|
226
|
+
*/
|
|
227
|
+
declare function isThisSetState(node: TSESTree$1.CallExpression): boolean;
|
|
228
|
+
/**
|
|
229
|
+
* Check whether the given node is an assignment to this.state
|
|
230
|
+
* @param node The node to check
|
|
231
|
+
* @param node
|
|
232
|
+
* @internal
|
|
183
233
|
*/
|
|
184
|
-
declare function
|
|
234
|
+
declare function isAssignmentToThisState(node: TSESTree$1.AssignmentExpression): boolean;
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region src/component/component-definition.d.ts
|
|
185
237
|
/**
|
|
186
238
|
* Determines if a function node represents a valid React component definition
|
|
239
|
+
*
|
|
187
240
|
* @param context The rule context
|
|
188
|
-
* @param node The function node to
|
|
189
|
-
* @param hint Component detection hints
|
|
190
|
-
* @
|
|
241
|
+
* @param node The function node to analyze
|
|
242
|
+
* @param hint Component detection hints (bit flags) to customize detection logic
|
|
243
|
+
* @param context
|
|
244
|
+
* @param node
|
|
245
|
+
* @param hint
|
|
246
|
+
* @returns `true` if the node is considered a component definition
|
|
191
247
|
*/
|
|
192
248
|
declare function isComponentDefinition(context: RuleContext, node: AST.TSESTreeFunction, hint: bigint): boolean;
|
|
193
249
|
//#endregion
|
|
@@ -239,9 +295,26 @@ declare function isFunctionOfComponentDidMount(node: TSESTree.Node): boolean;
|
|
|
239
295
|
declare function isFunctionOfComponentWillUnmount(node: TSESTree.Node): boolean;
|
|
240
296
|
//#endregion
|
|
241
297
|
//#region src/component/component-name.d.ts
|
|
298
|
+
/**
|
|
299
|
+
* Check if a string matches the strict component name pattern
|
|
300
|
+
* @param name The name to check
|
|
301
|
+
*/
|
|
242
302
|
declare function isComponentName(name: string): boolean;
|
|
303
|
+
/**
|
|
304
|
+
* Check if a string matches the loose component name pattern
|
|
305
|
+
* @param name The name to check
|
|
306
|
+
*/
|
|
243
307
|
declare function isComponentNameLoose(name: string): boolean;
|
|
308
|
+
/**
|
|
309
|
+
* Get component name from an identifier or identifier sequence (e.g., MemberExpression)
|
|
310
|
+
* @param id The identifier or identifier sequence
|
|
311
|
+
*/
|
|
244
312
|
declare function getComponentNameFromId(id: TSESTree.Identifier | TSESTree.Identifier[] | unit): string | undefined;
|
|
313
|
+
/**
|
|
314
|
+
* Check if the function has no name or a loose component name
|
|
315
|
+
* @param context The rule context
|
|
316
|
+
* @param fn The function node
|
|
317
|
+
*/
|
|
245
318
|
declare function hasNoneOrLooseComponentName(context: RuleContext, fn: AST.TSESTreeFunction): boolean;
|
|
246
319
|
//#endregion
|
|
247
320
|
//#region src/component/component-phase-helpers.d.ts
|
|
@@ -317,11 +390,6 @@ declare function isDirectValueOfRenderPropertyLoose(node: TSESTree.Node): boolea
|
|
|
317
390
|
*/
|
|
318
391
|
declare function isDeclaredInRenderPropLoose(node: TSESTree.Node): boolean;
|
|
319
392
|
//#endregion
|
|
320
|
-
//#region src/component/component-state.d.ts
|
|
321
|
-
type ComponentStateKind = "actionState" | "state";
|
|
322
|
-
declare function isThisSetState(node: TSESTree.CallExpression): boolean;
|
|
323
|
-
declare function isAssignmentToThisState(node: TSESTree.AssignmentExpression): boolean;
|
|
324
|
-
//#endregion
|
|
325
393
|
//#region src/component/component-wrapper.d.ts
|
|
326
394
|
/**
|
|
327
395
|
* Check if the node is a call expression for a component wrapper
|
|
@@ -484,7 +552,7 @@ declare function resolveJsxAttributeValue(context: RuleContext, attribute: AST.T
|
|
|
484
552
|
readonly toStatic: () => string | number | bigint | boolean | RegExp | null;
|
|
485
553
|
} | {
|
|
486
554
|
readonly kind: "expression";
|
|
487
|
-
readonly node: TSESTree.
|
|
555
|
+
readonly node: TSESTree.JSXEmptyExpression | TSESTree.Expression;
|
|
488
556
|
readonly toStatic: () => unknown;
|
|
489
557
|
} | {
|
|
490
558
|
readonly kind: "element";
|
|
@@ -492,7 +560,7 @@ declare function resolveJsxAttributeValue(context: RuleContext, attribute: AST.T
|
|
|
492
560
|
readonly toStatic: () => undefined;
|
|
493
561
|
} | {
|
|
494
562
|
readonly kind: "spreadChild";
|
|
495
|
-
readonly node: TSESTree.
|
|
563
|
+
readonly node: TSESTree.JSXEmptyExpression | TSESTree.Expression;
|
|
496
564
|
readonly toStatic: () => undefined;
|
|
497
565
|
} | {
|
|
498
566
|
readonly kind: "spreadProps";
|
|
@@ -582,8 +650,8 @@ declare function isJsxLike(code: {
|
|
|
582
650
|
* Determines if a JSX element is a host element
|
|
583
651
|
* Host elements in React start with lowercase letters (e.g., div, span)
|
|
584
652
|
*
|
|
585
|
-
* @param context
|
|
586
|
-
* @param node
|
|
653
|
+
* @param context ESLint rule context
|
|
654
|
+
* @param node AST node to check
|
|
587
655
|
* @returns boolean indicating if the element is a host element
|
|
588
656
|
*/
|
|
589
657
|
declare function isJsxHostElement(context: RuleContext, node: TSESTree.Node): boolean;
|
|
@@ -591,8 +659,8 @@ declare function isJsxHostElement(context: RuleContext, node: TSESTree.Node): bo
|
|
|
591
659
|
* Determines if a JSX element is a React Fragment
|
|
592
660
|
* Fragments can be imported from React and used like <Fragment> or <React.Fragment>
|
|
593
661
|
*
|
|
594
|
-
* @param context
|
|
595
|
-
* @param node
|
|
662
|
+
* @param context ESLint rule context
|
|
663
|
+
* @param node AST node to check
|
|
596
664
|
* @returns boolean indicating if the element is a Fragment with type narrowing
|
|
597
665
|
*/
|
|
598
666
|
declare function isJsxFragmentElement(context: RuleContext, node: TSESTree.Node): node is TSESTree.JSXElement;
|
|
@@ -603,8 +671,8 @@ declare function isJsxFragmentElement(context: RuleContext, node: TSESTree.Node)
|
|
|
603
671
|
* For JSX elements, returns the stringified name (e.g., "div", "Button", "React.Fragment")
|
|
604
672
|
* For JSX fragments, returns an empty string
|
|
605
673
|
*
|
|
606
|
-
* @param context
|
|
607
|
-
* @param node
|
|
674
|
+
* @param context ESLint rule context
|
|
675
|
+
* @param node JSX element or fragment node
|
|
608
676
|
* @returns String representation of the element type
|
|
609
677
|
*/
|
|
610
678
|
declare function getJsxElementType(context: RuleContext, node: TSESTree.JSXElement | TSESTree.JSXFragment): string;
|
|
@@ -613,8 +681,8 @@ declare function getJsxElementType(context: RuleContext, node: TSESTree.JSXEleme
|
|
|
613
681
|
/**
|
|
614
682
|
* Traverses up the AST to find a parent JSX attribute node that matches a given test
|
|
615
683
|
*
|
|
616
|
-
* @param node
|
|
617
|
-
* @param test
|
|
684
|
+
* @param node The starting AST node
|
|
685
|
+
* @param test Optional predicate function to test if the attribute meets criteria
|
|
618
686
|
* Defaults to always returning true (matches any attribute)
|
|
619
687
|
* @returns The first matching JSX attribute node found when traversing upwards, or undefined
|
|
620
688
|
*/
|
|
@@ -624,7 +692,7 @@ declare function findParentJsxAttribute(node: TSESTree.Node, test?: (node: TSEST
|
|
|
624
692
|
/**
|
|
625
693
|
* Incomplete but sufficient stringification of JSX nodes for common use cases
|
|
626
694
|
*
|
|
627
|
-
* @param node
|
|
695
|
+
* @param node JSX node from TypeScript ESTree
|
|
628
696
|
* @returns String representation of the JSX node
|
|
629
697
|
*/
|
|
630
698
|
declare function stringifyJsx(node: TSESTree$1.JSXIdentifier | TSESTree$1.JSXNamespacedName | TSESTree$1.JSXMemberExpression | TSESTree$1.JSXOpeningElement | TSESTree$1.JSXClosingElement | TSESTree$1.JSXOpeningFragment | TSESTree$1.JSXClosingFragment | TSESTree$1.JSXText): string;
|
|
@@ -637,7 +705,7 @@ declare function stringifyJsx(node: TSESTree$1.JSXIdentifier | TSESTree$1.JSXNam
|
|
|
637
705
|
* @param prev The previous AST node in the traversal (used for context)
|
|
638
706
|
* @internal
|
|
639
707
|
*/
|
|
640
|
-
declare function getInstanceId(node: TSESTree.Node, prev?: TSESTree.Node): TSESTree.
|
|
708
|
+
declare function getInstanceId(node: TSESTree.Node, prev?: TSESTree.Node): TSESTree.ArrayExpression | TSESTree.ArrayPattern | TSESTree.ArrowFunctionExpression | TSESTree.AssignmentExpression | TSESTree.AwaitExpression | TSESTree.PrivateInExpression | TSESTree.SymmetricBinaryExpression | TSESTree.CallExpression | TSESTree.ChainExpression | TSESTree.ClassExpression | TSESTree.ConditionalExpression | TSESTree.FunctionExpression | TSESTree.Identifier | TSESTree.ImportExpression | TSESTree.JSXElement | TSESTree.JSXFragment | TSESTree.BigIntLiteral | TSESTree.BooleanLiteral | TSESTree.NullLiteral | TSESTree.NumberLiteral | TSESTree.RegExpLiteral | TSESTree.StringLiteral | TSESTree.LogicalExpression | TSESTree.MemberExpressionComputedName | TSESTree.MemberExpressionNonComputedName | TSESTree.MetaProperty | TSESTree.NewExpression | TSESTree.ObjectExpression | TSESTree.ObjectPattern | TSESTree.PrivateIdentifier | TSESTree.SequenceExpression | TSESTree.Super | TSESTree.TaggedTemplateExpression | TSESTree.TemplateLiteral | TSESTree.ThisExpression | TSESTree.TSAsExpression | TSESTree.TSInstantiationExpression | TSESTree.TSNonNullExpression | TSESTree.TSSatisfiesExpression | TSESTree.TSTypeAssertion | TSESTree.UnaryExpressionBitwiseNot | TSESTree.UnaryExpressionDelete | TSESTree.UnaryExpressionMinus | TSESTree.UnaryExpressionNot | TSESTree.UnaryExpressionPlus | TSESTree.UnaryExpressionTypeof | TSESTree.UnaryExpressionVoid | TSESTree.UpdateExpression | TSESTree.YieldExpression | undefined;
|
|
641
709
|
//#endregion
|
|
642
710
|
//#region src/utils/is-from-react.d.ts
|
|
643
711
|
/**
|
|
@@ -671,6 +739,11 @@ declare namespace isReactAPI {
|
|
|
671
739
|
(context: RuleContext): (node: unit | null | TSESTree.Node) => node is TSESTree.MemberExpression | TSESTree.Identifier;
|
|
672
740
|
};
|
|
673
741
|
}
|
|
742
|
+
/**
|
|
743
|
+
* Checks if the node is a React API identifier or member expression
|
|
744
|
+
* @param api The React API name to check against (e.g., "useState", "React.memo")
|
|
745
|
+
* @returns A predicate function to check if a node matches the API
|
|
746
|
+
*/
|
|
674
747
|
declare function isReactAPI(api: string): isReactAPI.ReturnType;
|
|
675
748
|
declare namespace isReactAPICall {
|
|
676
749
|
type ReturnType = {
|
|
@@ -678,6 +751,11 @@ declare namespace isReactAPICall {
|
|
|
678
751
|
(context: RuleContext): (node: unit | null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
679
752
|
};
|
|
680
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Checks if the node is a call expression to a specific React API
|
|
756
|
+
* @param api The React API name to check against
|
|
757
|
+
* @returns A predicate function to check if a node is a call to the API
|
|
758
|
+
*/
|
|
681
759
|
declare function isReactAPICall(api: string): isReactAPICall.ReturnType;
|
|
682
760
|
declare const isCaptureOwnerStack: isReactAPI.ReturnType;
|
|
683
761
|
declare const isChildrenCount: isReactAPI.ReturnType;
|
|
@@ -706,4 +784,4 @@ declare const isForwardRefCall: isReactAPICall.ReturnType;
|
|
|
706
784
|
declare const isMemoCall: isReactAPICall.ReturnType;
|
|
707
785
|
declare const isLazyCall: isReactAPICall.ReturnType;
|
|
708
786
|
//#endregion
|
|
709
|
-
export { ClassComponent, Component, ComponentDetectionHint, ComponentEffectPhaseKind, ComponentFlag, ComponentKind, ComponentLifecyclePhaseKind, ComponentPhaseKind, ComponentPhaseRelevance,
|
|
787
|
+
export { ClassComponent, Component, ComponentDetectionHint, ComponentEffectPhaseKind, ComponentFlag, ComponentKind, ComponentLifecyclePhaseKind, ComponentPhaseKind, ComponentPhaseRelevance, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, FunctionComponent, Hook, JsxAttributeValue, JsxConfig, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, SemanticEntry, SemanticNode, findParentJsxAttribute, getComponentFlagFromInitPath, getComponentNameFromId, getFunctionComponentId, getInstanceId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, getPhaseKindOfFunction, hasNoneOrLooseComponentName, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFunctionOfComponentDidMount, isFunctionOfComponentWillUnmount, isFunctionOfUseEffectCleanup, isFunctionOfUseEffectSetup, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isInitializedFromReact, isInitializedFromSource, isInstanceIdEqual, isInversePhase, isJsxFragmentElement, isJsxHostElement, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isReactHook, isReactHookCall, isReactHookCallWithName, isReactHookCallWithNameAlias, isReactHookId, isReactHookName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectLikeCall, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseSyncExternalStoreCall, isUseTransitionCall, resolveJsxAttributeValue, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
|
package/dist/index.js
CHANGED
|
@@ -1,75 +1,13 @@
|
|
|
1
|
-
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
2
1
|
import * as AST from "@eslint-react/ast";
|
|
3
2
|
import { constFalse, constTrue, dual, flip, getOrElseUpdate, identity, unit } from "@eslint-react/eff";
|
|
4
3
|
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";
|
|
4
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
5
5
|
import { findProperty, findVariable, getVariableDefinitionNode, isNodeValueEqual } from "@eslint-react/var";
|
|
6
6
|
import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
7
7
|
import { P, isMatching, match } from "ts-pattern";
|
|
8
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from "@typescript-eslint/utils";
|
|
8
9
|
import birecord from "birecord";
|
|
9
10
|
|
|
10
|
-
//#region src/utils/is-react-api.ts
|
|
11
|
-
function isReactAPI(api) {
|
|
12
|
-
const func = (context, node) => {
|
|
13
|
-
if (node == null) return false;
|
|
14
|
-
const getText = (n) => context.sourceCode.getText(n);
|
|
15
|
-
const name = AST.toStringFormat(node, getText);
|
|
16
|
-
if (name === api) return true;
|
|
17
|
-
if (name.substring(name.indexOf(".") + 1) === api) return true;
|
|
18
|
-
return false;
|
|
19
|
-
};
|
|
20
|
-
return dual(2, func);
|
|
21
|
-
}
|
|
22
|
-
function isReactAPICall(api) {
|
|
23
|
-
const func = (context, node) => {
|
|
24
|
-
if (node == null) return false;
|
|
25
|
-
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
26
|
-
return isReactAPI(api)(context, node.callee);
|
|
27
|
-
};
|
|
28
|
-
return dual(2, func);
|
|
29
|
-
}
|
|
30
|
-
const isCaptureOwnerStack = isReactAPI("captureOwnerStack");
|
|
31
|
-
const isChildrenCount = isReactAPI("Children.count");
|
|
32
|
-
const isChildrenForEach = isReactAPI("Children.forEach");
|
|
33
|
-
const isChildrenMap = isReactAPI("Children.map");
|
|
34
|
-
const isChildrenOnly = isReactAPI("Children.only");
|
|
35
|
-
const isChildrenToArray = isReactAPI("Children.toArray");
|
|
36
|
-
const isCloneElement = isReactAPI("cloneElement");
|
|
37
|
-
const isCreateContext = isReactAPI("createContext");
|
|
38
|
-
const isCreateElement = isReactAPI("createElement");
|
|
39
|
-
const isCreateRef = isReactAPI("createRef");
|
|
40
|
-
const isForwardRef = isReactAPI("forwardRef");
|
|
41
|
-
const isMemo = isReactAPI("memo");
|
|
42
|
-
const isLazy = isReactAPI("lazy");
|
|
43
|
-
const isCaptureOwnerStackCall = isReactAPICall("captureOwnerStack");
|
|
44
|
-
const isChildrenCountCall = isReactAPICall("Children.count");
|
|
45
|
-
const isChildrenForEachCall = isReactAPICall("Children.forEach");
|
|
46
|
-
const isChildrenMapCall = isReactAPICall("Children.map");
|
|
47
|
-
const isChildrenOnlyCall = isReactAPICall("Children.only");
|
|
48
|
-
const isChildrenToArrayCall = isReactAPICall("Children.toArray");
|
|
49
|
-
const isCloneElementCall = isReactAPICall("cloneElement");
|
|
50
|
-
const isCreateContextCall = isReactAPICall("createContext");
|
|
51
|
-
const isCreateElementCall = isReactAPICall("createElement");
|
|
52
|
-
const isCreateRefCall = isReactAPICall("createRef");
|
|
53
|
-
const isForwardRefCall = isReactAPICall("forwardRef");
|
|
54
|
-
const isMemoCall = isReactAPICall("memo");
|
|
55
|
-
const isLazyCall = isReactAPICall("lazy");
|
|
56
|
-
|
|
57
|
-
//#endregion
|
|
58
|
-
//#region src/component/component-children.ts
|
|
59
|
-
/**
|
|
60
|
-
* Determines whether inside `createElement`'s children.
|
|
61
|
-
* @param context The rule context
|
|
62
|
-
* @param node The AST node to check
|
|
63
|
-
* @returns `true` if the node is inside createElement's children
|
|
64
|
-
*/
|
|
65
|
-
function isChildrenOfCreateElement(context, node) {
|
|
66
|
-
const parent = node.parent;
|
|
67
|
-
if (parent == null || parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
68
|
-
if (!isCreateElementCall(context, parent)) return false;
|
|
69
|
-
return parent.arguments.slice(2).some((arg) => arg === node);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
//#endregion
|
|
73
11
|
//#region src/hook/hook-name.ts
|
|
74
12
|
const REACT_BUILTIN_HOOK_NAMES = [
|
|
75
13
|
"use",
|
|
@@ -276,7 +214,7 @@ function isFunctionOfUseEffectCleanup(node) {
|
|
|
276
214
|
/**
|
|
277
215
|
* Incomplete but sufficient stringification of JSX nodes for common use cases
|
|
278
216
|
*
|
|
279
|
-
* @param node
|
|
217
|
+
* @param node JSX node from TypeScript ESTree
|
|
280
218
|
* @returns String representation of the JSX node
|
|
281
219
|
*/
|
|
282
220
|
function stringifyJsx(node) {
|
|
@@ -554,8 +492,8 @@ function isJsxLike(code, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
|
554
492
|
* For JSX elements, returns the stringified name (e.g., "div", "Button", "React.Fragment")
|
|
555
493
|
* For JSX fragments, returns an empty string
|
|
556
494
|
*
|
|
557
|
-
* @param context
|
|
558
|
-
* @param node
|
|
495
|
+
* @param context ESLint rule context
|
|
496
|
+
* @param node JSX element or fragment node
|
|
559
497
|
* @returns String representation of the element type
|
|
560
498
|
*/
|
|
561
499
|
function getJsxElementType(context, node) {
|
|
@@ -569,8 +507,8 @@ function getJsxElementType(context, node) {
|
|
|
569
507
|
* Determines if a JSX element is a host element
|
|
570
508
|
* Host elements in React start with lowercase letters (e.g., div, span)
|
|
571
509
|
*
|
|
572
|
-
* @param context
|
|
573
|
-
* @param node
|
|
510
|
+
* @param context ESLint rule context
|
|
511
|
+
* @param node AST node to check
|
|
574
512
|
* @returns boolean indicating if the element is a host element
|
|
575
513
|
*/
|
|
576
514
|
function isJsxHostElement(context, node) {
|
|
@@ -580,8 +518,8 @@ function isJsxHostElement(context, node) {
|
|
|
580
518
|
* Determines if a JSX element is a React Fragment
|
|
581
519
|
* Fragments can be imported from React and used like <Fragment> or <React.Fragment>
|
|
582
520
|
*
|
|
583
|
-
* @param context
|
|
584
|
-
* @param node
|
|
521
|
+
* @param context ESLint rule context
|
|
522
|
+
* @param node AST node to check
|
|
585
523
|
* @returns boolean indicating if the element is a Fragment with type narrowing
|
|
586
524
|
*/
|
|
587
525
|
function isJsxFragmentElement(context, node) {
|
|
@@ -594,8 +532,8 @@ function isJsxFragmentElement(context, node) {
|
|
|
594
532
|
/**
|
|
595
533
|
* Traverses up the AST to find a parent JSX attribute node that matches a given test
|
|
596
534
|
*
|
|
597
|
-
* @param node
|
|
598
|
-
* @param test
|
|
535
|
+
* @param node The starting AST node
|
|
536
|
+
* @param test Optional predicate function to test if the attribute meets criteria
|
|
599
537
|
* Defaults to always returning true (matches any attribute)
|
|
600
538
|
* @returns The first matching JSX attribute node found when traversing upwards, or undefined
|
|
601
539
|
*/
|
|
@@ -606,6 +544,147 @@ function findParentJsxAttribute(node, test = constTrue) {
|
|
|
606
544
|
return AST.findParentNode(node, guard);
|
|
607
545
|
}
|
|
608
546
|
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/utils/get-instance-id.ts
|
|
549
|
+
/** eslint-disable jsdoc/require-param */
|
|
550
|
+
/**
|
|
551
|
+
* Gets the identifier node of an instance based on AST node relationships.
|
|
552
|
+
* Used for tracking where hooks or components are being assigned in the code.
|
|
553
|
+
* @param node The current AST node to evaluate
|
|
554
|
+
* @param prev The previous AST node in the traversal (used for context)
|
|
555
|
+
* @internal
|
|
556
|
+
*/
|
|
557
|
+
function getInstanceId(node, prev) {
|
|
558
|
+
switch (true) {
|
|
559
|
+
case node.type === AST_NODE_TYPES.VariableDeclarator && node.init === prev: return node.id;
|
|
560
|
+
case node.type === AST_NODE_TYPES.AssignmentExpression && node.right === prev: return node.left;
|
|
561
|
+
case node.type === AST_NODE_TYPES.PropertyDefinition && node.value === prev: return node.key;
|
|
562
|
+
case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node: return unit;
|
|
563
|
+
default: return getInstanceId(node.parent, node);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
//#endregion
|
|
568
|
+
//#region src/utils/is-from-source.ts
|
|
569
|
+
/**
|
|
570
|
+
* Get the arguments of a require expression
|
|
571
|
+
* @param node The node to match
|
|
572
|
+
* @returns The require expression arguments or undefined if the node is not a require expression
|
|
573
|
+
*/
|
|
574
|
+
function getRequireExpressionArguments(node) {
|
|
575
|
+
return match(node).with({
|
|
576
|
+
type: AST_NODE_TYPES.CallExpression,
|
|
577
|
+
arguments: P.select(),
|
|
578
|
+
callee: {
|
|
579
|
+
type: AST_NODE_TYPES.Identifier,
|
|
580
|
+
name: "require"
|
|
581
|
+
}
|
|
582
|
+
}, identity).with({
|
|
583
|
+
type: AST_NODE_TYPES.MemberExpression,
|
|
584
|
+
object: P.select()
|
|
585
|
+
}, getRequireExpressionArguments).otherwise(() => null);
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Check if an identifier name is initialized from source
|
|
589
|
+
* @param name The top-level identifier's name
|
|
590
|
+
* @param source The import source to check against
|
|
591
|
+
* @param initialScope Initial scope to search for the identifier
|
|
592
|
+
* @returns Whether the identifier name is initialized from source
|
|
593
|
+
* @internal
|
|
594
|
+
*/
|
|
595
|
+
function isInitializedFromSource(name, source, initialScope) {
|
|
596
|
+
const latestDef = findVariable(name, initialScope)?.defs.at(-1);
|
|
597
|
+
if (latestDef == null) return false;
|
|
598
|
+
const { node, parent } = latestDef;
|
|
599
|
+
if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
|
|
600
|
+
const { init } = node;
|
|
601
|
+
if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) return isInitializedFromSource(init.object.name, source, initialScope);
|
|
602
|
+
if (init.type === AST_NODE_TYPES.Identifier) return isInitializedFromSource(init.name, source, initialScope);
|
|
603
|
+
const arg0 = getRequireExpressionArguments(init)?.[0];
|
|
604
|
+
if (arg0 == null || !AST.isLiteral(arg0, "string")) return false;
|
|
605
|
+
return arg0.value === source || arg0.value.startsWith(`${source}/`);
|
|
606
|
+
}
|
|
607
|
+
return parent?.type === AST_NODE_TYPES.ImportDeclaration && parent.source.value === source;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
//#endregion
|
|
611
|
+
//#region src/utils/is-from-react.ts
|
|
612
|
+
/**
|
|
613
|
+
* Check if an identifier name is initialized from react
|
|
614
|
+
* @param name The top-level identifier's name
|
|
615
|
+
* @param importSource The import source to check against
|
|
616
|
+
* @param initialScope Initial scope to search for the identifier
|
|
617
|
+
* @returns Whether the identifier name is initialized from react
|
|
618
|
+
*/
|
|
619
|
+
function isInitializedFromReact(name, importSource, initialScope) {
|
|
620
|
+
return name.toLowerCase() === "react" || isInitializedFromSource(name, importSource, initialScope);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region src/utils/is-instance-id-equal.ts
|
|
625
|
+
/** @internal */
|
|
626
|
+
function isInstanceIdEqual(context, a, b) {
|
|
627
|
+
return AST.isNodeEqual(a, b) || isNodeValueEqual(a, b, [context.sourceCode.getScope(a), context.sourceCode.getScope(b)]);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
//#endregion
|
|
631
|
+
//#region src/utils/is-react-api.ts
|
|
632
|
+
/**
|
|
633
|
+
* Checks if the node is a React API identifier or member expression
|
|
634
|
+
* @param api The React API name to check against (e.g., "useState", "React.memo")
|
|
635
|
+
* @returns A predicate function to check if a node matches the API
|
|
636
|
+
*/
|
|
637
|
+
function isReactAPI(api) {
|
|
638
|
+
const func = (context, node) => {
|
|
639
|
+
if (node == null) return false;
|
|
640
|
+
const getText = (n) => context.sourceCode.getText(n);
|
|
641
|
+
const name = AST.toStringFormat(node, getText);
|
|
642
|
+
if (name === api) return true;
|
|
643
|
+
if (name.substring(name.indexOf(".") + 1) === api) return true;
|
|
644
|
+
return false;
|
|
645
|
+
};
|
|
646
|
+
return dual(2, func);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Checks if the node is a call expression to a specific React API
|
|
650
|
+
* @param api The React API name to check against
|
|
651
|
+
* @returns A predicate function to check if a node is a call to the API
|
|
652
|
+
*/
|
|
653
|
+
function isReactAPICall(api) {
|
|
654
|
+
const func = (context, node) => {
|
|
655
|
+
if (node == null) return false;
|
|
656
|
+
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
657
|
+
return isReactAPI(api)(context, node.callee);
|
|
658
|
+
};
|
|
659
|
+
return dual(2, func);
|
|
660
|
+
}
|
|
661
|
+
const isCaptureOwnerStack = isReactAPI("captureOwnerStack");
|
|
662
|
+
const isChildrenCount = isReactAPI("Children.count");
|
|
663
|
+
const isChildrenForEach = isReactAPI("Children.forEach");
|
|
664
|
+
const isChildrenMap = isReactAPI("Children.map");
|
|
665
|
+
const isChildrenOnly = isReactAPI("Children.only");
|
|
666
|
+
const isChildrenToArray = isReactAPI("Children.toArray");
|
|
667
|
+
const isCloneElement = isReactAPI("cloneElement");
|
|
668
|
+
const isCreateContext = isReactAPI("createContext");
|
|
669
|
+
const isCreateElement = isReactAPI("createElement");
|
|
670
|
+
const isCreateRef = isReactAPI("createRef");
|
|
671
|
+
const isForwardRef = isReactAPI("forwardRef");
|
|
672
|
+
const isMemo = isReactAPI("memo");
|
|
673
|
+
const isLazy = isReactAPI("lazy");
|
|
674
|
+
const isCaptureOwnerStackCall = isReactAPICall("captureOwnerStack");
|
|
675
|
+
const isChildrenCountCall = isReactAPICall("Children.count");
|
|
676
|
+
const isChildrenForEachCall = isReactAPICall("Children.forEach");
|
|
677
|
+
const isChildrenMapCall = isReactAPICall("Children.map");
|
|
678
|
+
const isChildrenOnlyCall = isReactAPICall("Children.only");
|
|
679
|
+
const isChildrenToArrayCall = isReactAPICall("Children.toArray");
|
|
680
|
+
const isCloneElementCall = isReactAPICall("cloneElement");
|
|
681
|
+
const isCreateContextCall = isReactAPICall("createContext");
|
|
682
|
+
const isCreateElementCall = isReactAPICall("createElement");
|
|
683
|
+
const isCreateRefCall = isReactAPICall("createRef");
|
|
684
|
+
const isForwardRefCall = isReactAPICall("forwardRef");
|
|
685
|
+
const isMemoCall = isReactAPICall("memo");
|
|
686
|
+
const isLazyCall = isReactAPICall("lazy");
|
|
687
|
+
|
|
609
688
|
//#endregion
|
|
610
689
|
//#region src/component/component-detection-hint.ts
|
|
611
690
|
/**
|
|
@@ -679,18 +758,19 @@ function isRenderMethodLike(node) {
|
|
|
679
758
|
//#endregion
|
|
680
759
|
//#region src/component/component-definition.ts
|
|
681
760
|
/**
|
|
682
|
-
* Function
|
|
761
|
+
* Function patterns for matching specific AST structures
|
|
762
|
+
* Used to identify where a function is defined (e.g., method, property)
|
|
683
763
|
*/
|
|
684
|
-
const
|
|
685
|
-
|
|
764
|
+
const FUNCTION_PATTERNS = {
|
|
765
|
+
CLASS_METHOD: {
|
|
686
766
|
type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
|
|
687
767
|
parent: AST_NODE_TYPES.MethodDefinition
|
|
688
768
|
},
|
|
689
|
-
|
|
769
|
+
CLASS_PROPERTY: {
|
|
690
770
|
type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
|
|
691
771
|
parent: AST_NODE_TYPES.Property
|
|
692
772
|
},
|
|
693
|
-
|
|
773
|
+
OBJECT_METHOD: {
|
|
694
774
|
type: P.union(AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression),
|
|
695
775
|
parent: {
|
|
696
776
|
type: AST_NODE_TYPES.Property,
|
|
@@ -699,39 +779,65 @@ const functionPatterns = {
|
|
|
699
779
|
}
|
|
700
780
|
};
|
|
701
781
|
/**
|
|
702
|
-
*
|
|
782
|
+
* Checks if the given node is a function within a render method of a class component.
|
|
783
|
+
*
|
|
784
|
+
* @param node The AST node to check
|
|
785
|
+
* @param node
|
|
786
|
+
* @returns `true` if the node is a render function inside a class component
|
|
787
|
+
*
|
|
703
788
|
* @example
|
|
704
789
|
* ```tsx
|
|
705
790
|
* class Component extends React.Component {
|
|
706
|
-
* renderHeader = () => <div />;
|
|
707
|
-
* renderFooter = () => <div />;
|
|
791
|
+
* renderHeader = () => <div />; // Returns true
|
|
708
792
|
* }
|
|
709
793
|
* ```
|
|
710
|
-
* @param node The AST node to check
|
|
711
|
-
* @returns `true` if node is a render function, `false` if not
|
|
712
794
|
*/
|
|
713
795
|
function isFunctionOfRenderMethod(node) {
|
|
714
|
-
|
|
796
|
+
const parent = node.parent;
|
|
797
|
+
const greatGrandparent = parent.parent?.parent;
|
|
798
|
+
return greatGrandparent != null && isRenderMethodLike(parent) && isClassComponent(greatGrandparent);
|
|
715
799
|
}
|
|
716
800
|
/**
|
|
717
|
-
* Checks if a function node should be excluded based on detection hints
|
|
801
|
+
* Checks if a function node should be excluded based on provided detection hints
|
|
802
|
+
*
|
|
718
803
|
* @param node The function node to check
|
|
719
804
|
* @param hint Component detection hints as bit flags
|
|
720
|
-
* @
|
|
805
|
+
* @param node
|
|
806
|
+
* @param hint
|
|
807
|
+
* @returns `true` if the function matches an exclusion hint
|
|
721
808
|
*/
|
|
722
809
|
function shouldExcludeBasedOnHint(node, hint) {
|
|
723
|
-
if (hint & ComponentDetectionHint.SkipObjectMethod && isMatching(
|
|
724
|
-
if (hint & ComponentDetectionHint.SkipClassMethod && isMatching(
|
|
725
|
-
if (hint & ComponentDetectionHint.SkipClassProperty && isMatching(
|
|
810
|
+
if (hint & ComponentDetectionHint.SkipObjectMethod && isMatching(FUNCTION_PATTERNS.OBJECT_METHOD)(node)) return true;
|
|
811
|
+
if (hint & ComponentDetectionHint.SkipClassMethod && isMatching(FUNCTION_PATTERNS.CLASS_METHOD)(node)) return true;
|
|
812
|
+
if (hint & ComponentDetectionHint.SkipClassProperty && isMatching(FUNCTION_PATTERNS.CLASS_PROPERTY)(node)) return true;
|
|
726
813
|
if (hint & ComponentDetectionHint.SkipArrayMapArgument && AST.isArrayMapCall(node.parent)) return true;
|
|
727
814
|
return false;
|
|
728
815
|
}
|
|
729
816
|
/**
|
|
817
|
+
* Determines if the node is an argument within `createElement`'s children list (3rd argument onwards)
|
|
818
|
+
*
|
|
819
|
+
* @param context The rule context
|
|
820
|
+
* @param node The AST node to check
|
|
821
|
+
* @param context
|
|
822
|
+
* @param node
|
|
823
|
+
* @returns `true` if the node is passed as a child to `createElement`
|
|
824
|
+
*/
|
|
825
|
+
function isChildrenOfCreateElement(context, node) {
|
|
826
|
+
const parent = node.parent;
|
|
827
|
+
if (parent?.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
828
|
+
if (!isCreateElementCall(context, parent)) return false;
|
|
829
|
+
return parent.arguments.slice(2).some((arg) => arg === node);
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
730
832
|
* Determines if a function node represents a valid React component definition
|
|
833
|
+
*
|
|
731
834
|
* @param context The rule context
|
|
732
|
-
* @param node The function node to
|
|
733
|
-
* @param hint Component detection hints
|
|
734
|
-
* @
|
|
835
|
+
* @param node The function node to analyze
|
|
836
|
+
* @param hint Component detection hints (bit flags) to customize detection logic
|
|
837
|
+
* @param context
|
|
838
|
+
* @param node
|
|
839
|
+
* @param hint
|
|
840
|
+
* @returns `true` if the node is considered a component definition
|
|
735
841
|
*/
|
|
736
842
|
function isComponentDefinition(context, node, hint) {
|
|
737
843
|
if (isChildrenOfCreateElement(context, node) || isFunctionOfRenderMethod(node)) return false;
|
|
@@ -746,89 +852,6 @@ function isComponentDefinition(context, node, hint) {
|
|
|
746
852
|
return significantParent == null || significantParent.type !== AST_NODE_TYPES.JSXExpressionContainer;
|
|
747
853
|
}
|
|
748
854
|
|
|
749
|
-
//#endregion
|
|
750
|
-
//#region src/utils/get-instance-id.ts
|
|
751
|
-
/** eslint-disable jsdoc/require-param */
|
|
752
|
-
/**
|
|
753
|
-
* Gets the identifier node of an instance based on AST node relationships.
|
|
754
|
-
* Used for tracking where hooks or components are being assigned in the code.
|
|
755
|
-
* @param node The current AST node to evaluate
|
|
756
|
-
* @param prev The previous AST node in the traversal (used for context)
|
|
757
|
-
* @internal
|
|
758
|
-
*/
|
|
759
|
-
function getInstanceId(node, prev) {
|
|
760
|
-
switch (true) {
|
|
761
|
-
case node.type === AST_NODE_TYPES.VariableDeclarator && node.init === prev: return node.id;
|
|
762
|
-
case node.type === AST_NODE_TYPES.AssignmentExpression && node.right === prev: return node.left;
|
|
763
|
-
case node.type === AST_NODE_TYPES.PropertyDefinition && node.value === prev: return node.key;
|
|
764
|
-
case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node: return unit;
|
|
765
|
-
default: return getInstanceId(node.parent, node);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
//#endregion
|
|
770
|
-
//#region src/utils/is-from-source.ts
|
|
771
|
-
/**
|
|
772
|
-
* Get the arguments of a require expression
|
|
773
|
-
* @param node The node to match
|
|
774
|
-
* @returns The require expression arguments or undefined if the node is not a require expression
|
|
775
|
-
*/
|
|
776
|
-
function getRequireExpressionArguments(node) {
|
|
777
|
-
return match(node).with({
|
|
778
|
-
type: AST_NODE_TYPES.CallExpression,
|
|
779
|
-
arguments: P.select(),
|
|
780
|
-
callee: {
|
|
781
|
-
type: AST_NODE_TYPES.Identifier,
|
|
782
|
-
name: "require"
|
|
783
|
-
}
|
|
784
|
-
}, identity).with({
|
|
785
|
-
type: AST_NODE_TYPES.MemberExpression,
|
|
786
|
-
object: P.select()
|
|
787
|
-
}, getRequireExpressionArguments).otherwise(() => null);
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Check if an identifier name is initialized from source
|
|
791
|
-
* @param name The top-level identifier's name
|
|
792
|
-
* @param source The import source to check against
|
|
793
|
-
* @param initialScope Initial scope to search for the identifier
|
|
794
|
-
* @returns Whether the identifier name is initialized from source
|
|
795
|
-
* @internal
|
|
796
|
-
*/
|
|
797
|
-
function isInitializedFromSource(name, source, initialScope) {
|
|
798
|
-
const latestDef = findVariable(name, initialScope)?.defs.at(-1);
|
|
799
|
-
if (latestDef == null) return false;
|
|
800
|
-
const { node, parent } = latestDef;
|
|
801
|
-
if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
|
|
802
|
-
const { init } = node;
|
|
803
|
-
if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) return isInitializedFromSource(init.object.name, source, initialScope);
|
|
804
|
-
if (init.type === AST_NODE_TYPES.Identifier) return isInitializedFromSource(init.name, source, initialScope);
|
|
805
|
-
const arg0 = getRequireExpressionArguments(init)?.[0];
|
|
806
|
-
if (arg0 == null || !AST.isLiteral(arg0, "string")) return false;
|
|
807
|
-
return arg0.value === source || arg0.value.startsWith(`${source}/`);
|
|
808
|
-
}
|
|
809
|
-
return parent?.type === AST_NODE_TYPES.ImportDeclaration && parent.source.value === source;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
//#endregion
|
|
813
|
-
//#region src/utils/is-from-react.ts
|
|
814
|
-
/**
|
|
815
|
-
* Check if an identifier name is initialized from react
|
|
816
|
-
* @param name The top-level identifier's name
|
|
817
|
-
* @param importSource The import source to check against
|
|
818
|
-
* @param initialScope Initial scope to search for the identifier
|
|
819
|
-
* @returns Whether the identifier name is initialized from react
|
|
820
|
-
*/
|
|
821
|
-
function isInitializedFromReact(name, importSource, initialScope) {
|
|
822
|
-
return name.toLowerCase() === "react" || isInitializedFromSource(name, importSource, initialScope);
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
//#endregion
|
|
826
|
-
//#region src/utils/is-instance-id-equal.ts
|
|
827
|
-
/** @internal */
|
|
828
|
-
function isInstanceIdEqual(context, a, b) {
|
|
829
|
-
return AST.isNodeEqual(a, b) || isNodeValueEqual(a, b, [context.sourceCode.getScope(a), context.sourceCode.getScope(b)]);
|
|
830
|
-
}
|
|
831
|
-
|
|
832
855
|
//#endregion
|
|
833
856
|
//#region src/component/component-wrapper.ts
|
|
834
857
|
/**
|
|
@@ -885,16 +908,33 @@ function getComponentFlagFromInitPath(initPath) {
|
|
|
885
908
|
|
|
886
909
|
//#endregion
|
|
887
910
|
//#region src/component/component-name.ts
|
|
911
|
+
/**
|
|
912
|
+
* Check if a string matches the strict component name pattern
|
|
913
|
+
* @param name The name to check
|
|
914
|
+
*/
|
|
888
915
|
function isComponentName(name) {
|
|
889
916
|
return RE_COMPONENT_NAME.test(name);
|
|
890
917
|
}
|
|
918
|
+
/**
|
|
919
|
+
* Check if a string matches the loose component name pattern
|
|
920
|
+
* @param name The name to check
|
|
921
|
+
*/
|
|
891
922
|
function isComponentNameLoose(name) {
|
|
892
923
|
return RE_COMPONENT_NAME_LOOSE.test(name);
|
|
893
924
|
}
|
|
925
|
+
/**
|
|
926
|
+
* Get component name from an identifier or identifier sequence (e.g., MemberExpression)
|
|
927
|
+
* @param id The identifier or identifier sequence
|
|
928
|
+
*/
|
|
894
929
|
function getComponentNameFromId(id) {
|
|
895
930
|
if (id == null) return unit;
|
|
896
931
|
return Array.isArray(id) ? id.map((n) => n.name).join(".") : id.name;
|
|
897
932
|
}
|
|
933
|
+
/**
|
|
934
|
+
* Check if the function has no name or a loose component name
|
|
935
|
+
* @param context The rule context
|
|
936
|
+
* @param fn The function node
|
|
937
|
+
*/
|
|
898
938
|
function hasNoneOrLooseComponentName(context, fn) {
|
|
899
939
|
const id = getFunctionComponentId(context, fn);
|
|
900
940
|
if (id == null) return true;
|
|
@@ -1040,6 +1080,26 @@ function useComponentCollectorLegacy() {
|
|
|
1040
1080
|
}
|
|
1041
1081
|
};
|
|
1042
1082
|
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Check whether the given node is a this.setState() call
|
|
1085
|
+
* @param node The node to check
|
|
1086
|
+
* @param node
|
|
1087
|
+
* @internal
|
|
1088
|
+
*/
|
|
1089
|
+
function isThisSetState(node) {
|
|
1090
|
+
const { callee } = node;
|
|
1091
|
+
return callee.type === AST_NODE_TYPES$1.MemberExpression && AST.isThisExpression(callee.object) && callee.property.type === AST_NODE_TYPES$1.Identifier && callee.property.name === "setState";
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Check whether the given node is an assignment to this.state
|
|
1095
|
+
* @param node The node to check
|
|
1096
|
+
* @param node
|
|
1097
|
+
* @internal
|
|
1098
|
+
*/
|
|
1099
|
+
function isAssignmentToThisState(node) {
|
|
1100
|
+
const { left } = node;
|
|
1101
|
+
return left.type === AST_NODE_TYPES$1.MemberExpression && AST.isThisExpression(left.object) && AST.getPropertyName(left.property) === "state";
|
|
1102
|
+
}
|
|
1043
1103
|
|
|
1044
1104
|
//#endregion
|
|
1045
1105
|
//#region src/component/component-method.ts
|
|
@@ -1165,15 +1225,4 @@ function isDeclaredInRenderPropLoose(node) {
|
|
|
1165
1225
|
}
|
|
1166
1226
|
|
|
1167
1227
|
//#endregion
|
|
1168
|
-
|
|
1169
|
-
function isThisSetState(node) {
|
|
1170
|
-
const { callee } = node;
|
|
1171
|
-
return callee.type === AST_NODE_TYPES.MemberExpression && AST.isThisExpression(callee.object) && callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "setState";
|
|
1172
|
-
}
|
|
1173
|
-
function isAssignmentToThisState(node) {
|
|
1174
|
-
const { left } = node;
|
|
1175
|
-
return left.type === AST_NODE_TYPES.MemberExpression && AST.isThisExpression(left.object) && AST.getPropertyName(left.property) === "state";
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
//#endregion
|
|
1179
|
-
export { ComponentDetectionHint, ComponentFlag, ComponentPhaseRelevance, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, findParentJsxAttribute, getComponentFlagFromInitPath, getComponentNameFromId, getFunctionComponentId, getInstanceId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, getPhaseKindOfFunction, hasNoneOrLooseComponentName, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOfCreateElement, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFunctionOfComponentDidMount, isFunctionOfComponentWillUnmount, isFunctionOfRenderMethod, isFunctionOfUseEffectCleanup, isFunctionOfUseEffectSetup, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isInitializedFromReact, isInitializedFromSource, isInstanceIdEqual, isInversePhase, isJsxFragmentElement, isJsxHostElement, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isReactHook, isReactHookCall, isReactHookCallWithName, isReactHookCallWithNameAlias, isReactHookId, isReactHookName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectLikeCall, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseSyncExternalStoreCall, isUseTransitionCall, resolveJsxAttributeValue, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
|
|
1228
|
+
export { ComponentDetectionHint, ComponentFlag, ComponentPhaseRelevance, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, findParentJsxAttribute, getComponentFlagFromInitPath, getComponentNameFromId, getFunctionComponentId, getInstanceId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, getPhaseKindOfFunction, hasNoneOrLooseComponentName, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFunctionOfComponentDidMount, isFunctionOfComponentWillUnmount, isFunctionOfUseEffectCleanup, isFunctionOfUseEffectSetup, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isInitializedFromReact, isInitializedFromSource, isInstanceIdEqual, isInversePhase, isJsxFragmentElement, isJsxHostElement, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isReactHook, isReactHookCall, isReactHookCallWithName, isReactHookCallWithNameAlias, isReactHookId, isReactHookName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectLikeCall, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, 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.3.13-next.
|
|
3
|
+
"version": "2.3.13-next.2",
|
|
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": {
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"@typescript-eslint/utils": "^8.48.1",
|
|
36
36
|
"birecord": "^0.1.1",
|
|
37
37
|
"ts-pattern": "^5.9.0",
|
|
38
|
-
"@eslint-react/ast": "2.3.13-next.
|
|
39
|
-
"@eslint-react/shared": "2.3.13-next.
|
|
40
|
-
"@eslint-react/
|
|
41
|
-
"@eslint-react/
|
|
38
|
+
"@eslint-react/ast": "2.3.13-next.2",
|
|
39
|
+
"@eslint-react/shared": "2.3.13-next.2",
|
|
40
|
+
"@eslint-react/var": "2.3.13-next.2",
|
|
41
|
+
"@eslint-react/eff": "2.3.13-next.2"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"eslint": "^8.57.0 || ^9.0.0",
|