@eslint-react/core 2.6.4-next.0 → 2.6.5-next.0
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 +62 -38
- package/dist/index.js +126 -63
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -13,18 +13,6 @@ type ComponentDetectionHint = bigint;
|
|
|
13
13
|
* Hints for component collector
|
|
14
14
|
*/
|
|
15
15
|
declare const ComponentDetectionHint: {
|
|
16
|
-
/**
|
|
17
|
-
* Skip function component created by React.memo
|
|
18
|
-
*/
|
|
19
|
-
readonly SkipMemo: bigint;
|
|
20
|
-
/**
|
|
21
|
-
* Skip function component created by React.forwardRef
|
|
22
|
-
*/
|
|
23
|
-
readonly SkipForwardRef: bigint;
|
|
24
|
-
/**
|
|
25
|
-
* Skip function component defined as array map argument
|
|
26
|
-
*/
|
|
27
|
-
readonly SkipArrayMapArgument: bigint;
|
|
28
16
|
/**
|
|
29
17
|
* Skip function component defined on object method
|
|
30
18
|
*/
|
|
@@ -37,6 +25,10 @@ declare const ComponentDetectionHint: {
|
|
|
37
25
|
* Skip function component defined on class property
|
|
38
26
|
*/
|
|
39
27
|
readonly SkipClassProperty: bigint;
|
|
28
|
+
/**
|
|
29
|
+
* Skip function component defined as array map callback
|
|
30
|
+
*/
|
|
31
|
+
readonly SkipArrayMapCallback: bigint;
|
|
40
32
|
readonly None: 0n;
|
|
41
33
|
readonly SkipUndefined: bigint;
|
|
42
34
|
readonly SkipNullLiteral: bigint;
|
|
@@ -72,7 +64,7 @@ interface SemanticEntry {
|
|
|
72
64
|
//#endregion
|
|
73
65
|
//#region src/semantic/semantic-node.d.ts
|
|
74
66
|
interface SemanticNode {
|
|
75
|
-
id: unit | TSESTree.
|
|
67
|
+
id: unit | TSESTree.Node;
|
|
76
68
|
key: string;
|
|
77
69
|
kind: string;
|
|
78
70
|
name: unit | string;
|
|
@@ -100,7 +92,7 @@ interface FunctionComponent extends SemanticNode {
|
|
|
100
92
|
/**
|
|
101
93
|
* The identifier or identifier sequence of the component
|
|
102
94
|
*/
|
|
103
|
-
id: unit |
|
|
95
|
+
id: unit | AST.FunctionID;
|
|
104
96
|
/**
|
|
105
97
|
* The kind of component
|
|
106
98
|
*/
|
|
@@ -117,6 +109,10 @@ interface FunctionComponent extends SemanticNode {
|
|
|
117
109
|
* Hint for how the component was detected
|
|
118
110
|
*/
|
|
119
111
|
hint: ComponentDetectionHint;
|
|
112
|
+
/**
|
|
113
|
+
* List of expressions returned by the component
|
|
114
|
+
*/
|
|
115
|
+
rets: TSESTree.ReturnStatement["argument"][];
|
|
120
116
|
/**
|
|
121
117
|
* The initialization path of the function
|
|
122
118
|
*/
|
|
@@ -174,6 +170,7 @@ type FunctionEntry$1 = {
|
|
|
174
170
|
node: AST.TSESTreeFunction;
|
|
175
171
|
hookCalls: TSESTree.CallExpression[];
|
|
176
172
|
isComponent: boolean;
|
|
173
|
+
rets: TSESTree.ReturnStatement["argument"][];
|
|
177
174
|
};
|
|
178
175
|
declare namespace useComponentCollector {
|
|
179
176
|
type Options = {
|
|
@@ -237,7 +234,7 @@ declare function isAssignmentToThisState(node: TSESTree$1.AssignmentExpression):
|
|
|
237
234
|
declare function isComponentDefinition(context: RuleContext, node: AST.TSESTreeFunction, hint: bigint): boolean;
|
|
238
235
|
//#endregion
|
|
239
236
|
//#region src/component/component-id.d.ts
|
|
240
|
-
declare function getFunctionComponentId(context: RuleContext, node: AST.TSESTreeFunction):
|
|
237
|
+
declare function getFunctionComponentId(context: RuleContext, node: AST.TSESTreeFunction): AST.FunctionID | unit;
|
|
241
238
|
//#endregion
|
|
242
239
|
//#region src/component/component-init-path.d.ts
|
|
243
240
|
declare function getComponentFlagFromInitPath(initPath: FunctionComponent["initPath"]): bigint;
|
|
@@ -304,11 +301,6 @@ declare function isComponentName(name: string): boolean;
|
|
|
304
301
|
* @param name The name to check
|
|
305
302
|
*/
|
|
306
303
|
declare function isComponentNameLoose(name: string): boolean;
|
|
307
|
-
/**
|
|
308
|
-
* Get component name from an identifier or identifier sequence (e.g., MemberExpression)
|
|
309
|
-
* @param id The identifier or identifier sequence
|
|
310
|
-
*/
|
|
311
|
-
declare function getComponentNameFromId(id: TSESTree.Identifier | TSESTree.Identifier[] | unit): string | undefined;
|
|
312
304
|
/**
|
|
313
305
|
* Check if the function has no name or a loose component name
|
|
314
306
|
* @param context The rule context
|
|
@@ -404,10 +396,56 @@ declare function isComponentWrapperCall(context: RuleContext, node: TSESTree.Nod
|
|
|
404
396
|
* @returns `true` if the node is a call expression for a component wrapper loosely
|
|
405
397
|
*/
|
|
406
398
|
declare function isComponentWrapperCallLoose(context: RuleContext, node: TSESTree.Node): boolean;
|
|
399
|
+
/**
|
|
400
|
+
* Check if the node is a callback function passed to a component wrapper
|
|
401
|
+
* @param context The ESLint rule context
|
|
402
|
+
* @param node The node to check
|
|
403
|
+
* @returns `true` if the node is a callback function passed to a component wrapper
|
|
404
|
+
*/
|
|
405
|
+
declare function isComponentWrapperCallback(context: RuleContext, node: TSESTree.Node): boolean;
|
|
406
|
+
/**
|
|
407
|
+
* Check if the node is a callback function passed to a component wrapper loosely
|
|
408
|
+
* @param context The ESLint rule context
|
|
409
|
+
* @param node The node to check
|
|
410
|
+
* @returns `true` if the node is a callback function passed to a component wrapper loosely
|
|
411
|
+
*/
|
|
412
|
+
declare function isComponentWrapperCallbackLoose(context: RuleContext, node: TSESTree.Node): boolean;
|
|
413
|
+
//#endregion
|
|
414
|
+
//#region src/hierarchy/find-enclosing-assignment-target.d.ts
|
|
415
|
+
/**
|
|
416
|
+
* Finds the enclosing assignment target (variable, property, etc.) for a given node
|
|
417
|
+
*
|
|
418
|
+
* @todo Verify correctness and completeness of this function
|
|
419
|
+
* @param node The starting node
|
|
420
|
+
* @returns The enclosing assignment target node, or undefined if not found
|
|
421
|
+
*/
|
|
422
|
+
declare function findEnclosingAssignmentTarget(node: 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;
|
|
423
|
+
/**
|
|
424
|
+
* Type representing the possible assignment targets returned by `findEnclosingAssignmentTarget`
|
|
425
|
+
*/
|
|
426
|
+
type AssignmentTarget = ReturnType<typeof findEnclosingAssignmentTarget>;
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/hierarchy/find-enclosing-component-or-hook.d.ts
|
|
429
|
+
type FindEnclosingComponentOrHookFilter = (n: TSESTree.Node, name: string | null) => boolean;
|
|
430
|
+
/**
|
|
431
|
+
* Find the enclosing React component or hook for a given AST node.
|
|
432
|
+
* @param node The AST node to start the search from.
|
|
433
|
+
* @param test Optional test function to customize component or hook identification.
|
|
434
|
+
* @returns The enclosing component or hook node, or `null` if none is found.
|
|
435
|
+
*/
|
|
436
|
+
declare function findEnclosingComponentOrHook(node: TSESTree.Node | unit, test?: FindEnclosingComponentOrHookFilter): TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclarationWithName | TSESTree.FunctionDeclarationWithOptionalName | TSESTree.FunctionExpression | undefined;
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/hierarchy/is-inside-component-or-hook.d.ts
|
|
439
|
+
/**
|
|
440
|
+
* Checks if a given AST node is inside a React component or hook.
|
|
441
|
+
* @param node The AST node to check.
|
|
442
|
+
* @returns True if the node is inside a component or hook, false otherwise.
|
|
443
|
+
*/
|
|
444
|
+
declare function isInsideComponentOrHook(node: TSESTree.Node | unit): boolean;
|
|
407
445
|
//#endregion
|
|
408
446
|
//#region src/hook/hook-semantic-node.d.ts
|
|
409
447
|
interface Hook extends SemanticNode {
|
|
410
|
-
id:
|
|
448
|
+
id: AST.FunctionID | unit;
|
|
411
449
|
node: AST.TSESTreeFunction;
|
|
412
450
|
name: string;
|
|
413
451
|
hookCalls: TSESTree.CallExpression[];
|
|
@@ -429,10 +467,10 @@ declare namespace useHookCollector {
|
|
|
429
467
|
listeners: ESLintUtils.RuleListener;
|
|
430
468
|
};
|
|
431
469
|
}
|
|
432
|
-
declare function useHookCollector(): useHookCollector.ReturnType;
|
|
470
|
+
declare function useHookCollector(context: RuleContext): useHookCollector.ReturnType;
|
|
433
471
|
//#endregion
|
|
434
472
|
//#region src/hook/hook-id.d.ts
|
|
435
|
-
declare function isReactHookId(id: TSESTree.Identifier | TSESTree.MemberExpression
|
|
473
|
+
declare function isReactHookId(id: TSESTree.Node): id is TSESTree.Identifier | TSESTree.MemberExpression;
|
|
436
474
|
//#endregion
|
|
437
475
|
//#region src/hook/hook-is.d.ts
|
|
438
476
|
/**
|
|
@@ -720,20 +758,6 @@ declare function findParentJsxAttribute(node: TSESTree.Node, test?: (node: TSEST
|
|
|
720
758
|
*/
|
|
721
759
|
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;
|
|
722
760
|
//#endregion
|
|
723
|
-
//#region src/utils/find-enclosing-assignment-target.d.ts
|
|
724
|
-
/**
|
|
725
|
-
* Finds the enclosing assignment target (variable, property, etc.) for a given node
|
|
726
|
-
*
|
|
727
|
-
* @todo Verify correctness and completeness of this function
|
|
728
|
-
* @param node The starting node
|
|
729
|
-
* @returns The enclosing assignment target node, or undefined if not found
|
|
730
|
-
*/
|
|
731
|
-
declare function findEnclosingAssignmentTarget(node: 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;
|
|
732
|
-
/**
|
|
733
|
-
* Type representing the possible assignment targets returned by `findEnclosingAssignmentTarget`
|
|
734
|
-
*/
|
|
735
|
-
type AssignmentTarget = ReturnType<typeof findEnclosingAssignmentTarget>;
|
|
736
|
-
//#endregion
|
|
737
761
|
//#region src/utils/is-from-react.d.ts
|
|
738
762
|
/**
|
|
739
763
|
* Checks if a variable is initialized from React import
|
|
@@ -810,4 +834,4 @@ declare const isForwardRefCall: isReactAPICall.ReturnType;
|
|
|
810
834
|
declare const isMemoCall: isReactAPICall.ReturnType;
|
|
811
835
|
declare const isLazyCall: isReactAPICall.ReturnType;
|
|
812
836
|
//#endregion
|
|
813
|
-
export { AssignmentTarget, 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, findEnclosingAssignmentTarget, findParentJsxAttribute, getComponentFlagFromInitPath,
|
|
837
|
+
export { AssignmentTarget, ClassComponent, Component, ComponentDetectionHint, ComponentEffectPhaseKind, ComponentFlag, ComponentKind, ComponentLifecyclePhaseKind, ComponentPhaseKind, ComponentPhaseRelevance, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, FindEnclosingComponentOrHookFilter, FunctionComponent, Hook, JsxAttributeValue, JsxConfig, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, SemanticEntry, SemanticNode, findEnclosingAssignmentTarget, findEnclosingComponentOrHook, findParentJsxAttribute, getComponentFlagFromInitPath, getFunctionComponentId, 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, isComponentWrapperCallback, isComponentWrapperCallbackLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFunctionOfComponentDidMount, isFunctionOfComponentWillUnmount, isFunctionOfUseEffectCleanup, isFunctionOfUseEffectSetup, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isInitializedFromReact, isInitializedFromReactNative, isInsideComponentOrHook, 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
|
@@ -40,6 +40,16 @@ function isReactHookName(name) {
|
|
|
40
40
|
return name === "use" || /^use[A-Z0-9]/.test(name);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/hook/hook-id.ts
|
|
45
|
+
function isReactHookId(id) {
|
|
46
|
+
switch (id.type) {
|
|
47
|
+
case AST_NODE_TYPES.Identifier: return isReactHookName(id.name);
|
|
48
|
+
case AST_NODE_TYPES.MemberExpression: return "name" in id.property && isReactHookName(id.property.name);
|
|
49
|
+
default: return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
//#endregion
|
|
44
54
|
//#region src/hook/hook-is.ts
|
|
45
55
|
/**
|
|
@@ -50,7 +60,11 @@ function isReactHookName(name) {
|
|
|
50
60
|
function isReactHook(node) {
|
|
51
61
|
if (node == null) return false;
|
|
52
62
|
const id = AST.getFunctionId(node);
|
|
53
|
-
|
|
63
|
+
switch (id?.type) {
|
|
64
|
+
case AST_NODE_TYPES.Identifier: return isReactHookName(id.name);
|
|
65
|
+
case AST_NODE_TYPES.MemberExpression: return "name" in id.property && isReactHookName(id.property.name);
|
|
66
|
+
default: return false;
|
|
67
|
+
}
|
|
54
68
|
}
|
|
55
69
|
/**
|
|
56
70
|
* Check if the given node is a React Hook call by its name.
|
|
@@ -132,15 +146,15 @@ const isUseTransitionCall = flip(isReactHookCallWithName)("useTransition");
|
|
|
132
146
|
//#endregion
|
|
133
147
|
//#region src/hook/hook-collector.ts
|
|
134
148
|
const idGen$2 = new IdGenerator("hook_");
|
|
135
|
-
function useHookCollector() {
|
|
149
|
+
function useHookCollector(context) {
|
|
136
150
|
const hooks = /* @__PURE__ */ new Map();
|
|
137
151
|
const functionEntries = [];
|
|
152
|
+
const getText = (n) => context.sourceCode.getText(n);
|
|
138
153
|
const getCurrentEntry = () => functionEntries.at(-1);
|
|
139
154
|
const onFunctionEnter = (node) => {
|
|
140
155
|
const id = AST.getFunctionId(node);
|
|
141
156
|
const key = idGen$2.next();
|
|
142
|
-
|
|
143
|
-
if (name != null && isReactHookName(name)) {
|
|
157
|
+
if (id != null && isReactHookId(id)) {
|
|
144
158
|
functionEntries.push({
|
|
145
159
|
key,
|
|
146
160
|
node,
|
|
@@ -150,7 +164,7 @@ function useHookCollector() {
|
|
|
150
164
|
id,
|
|
151
165
|
key,
|
|
152
166
|
kind: "function",
|
|
153
|
-
name,
|
|
167
|
+
name: AST.toStringFormat(id, getText),
|
|
154
168
|
node,
|
|
155
169
|
flag: 0n,
|
|
156
170
|
hint: 0n,
|
|
@@ -190,16 +204,6 @@ function useHookCollector() {
|
|
|
190
204
|
};
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
//#endregion
|
|
194
|
-
//#region src/hook/hook-id.ts
|
|
195
|
-
function isReactHookId(id) {
|
|
196
|
-
switch (id.type) {
|
|
197
|
-
case AST_NODE_TYPES.Identifier: return isReactHookName(id.name);
|
|
198
|
-
case AST_NODE_TYPES.MemberExpression: return "name" in id.property && isReactHookName(id.property.name);
|
|
199
|
-
default: return false;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
207
|
//#endregion
|
|
204
208
|
//#region src/hook/hook-parts.ts
|
|
205
209
|
/**
|
|
@@ -572,26 +576,6 @@ function findParentJsxAttribute(node, test = constTrue) {
|
|
|
572
576
|
return AST.findParentNode(node, guard);
|
|
573
577
|
}
|
|
574
578
|
|
|
575
|
-
//#endregion
|
|
576
|
-
//#region src/utils/find-enclosing-assignment-target.ts
|
|
577
|
-
/** eslint-disable jsdoc/require-param */
|
|
578
|
-
/**
|
|
579
|
-
* Finds the enclosing assignment target (variable, property, etc.) for a given node
|
|
580
|
-
*
|
|
581
|
-
* @todo Verify correctness and completeness of this function
|
|
582
|
-
* @param node The starting node
|
|
583
|
-
* @returns The enclosing assignment target node, or undefined if not found
|
|
584
|
-
*/
|
|
585
|
-
function findEnclosingAssignmentTarget(node) {
|
|
586
|
-
switch (true) {
|
|
587
|
-
case node.type === AST_NODE_TYPES.VariableDeclarator: return node.id;
|
|
588
|
-
case node.type === AST_NODE_TYPES.AssignmentExpression: return node.left;
|
|
589
|
-
case node.type === AST_NODE_TYPES.PropertyDefinition: return node.key;
|
|
590
|
-
case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node: return unit;
|
|
591
|
-
default: return findEnclosingAssignmentTarget(node.parent);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
579
|
//#endregion
|
|
596
580
|
//#region src/utils/is-from-react.ts
|
|
597
581
|
/**
|
|
@@ -694,17 +678,15 @@ const isLazyCall = isReactAPICall("lazy");
|
|
|
694
678
|
*/
|
|
695
679
|
const ComponentDetectionHint = {
|
|
696
680
|
...JsxDetectionHint,
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
SkipClassMethod: 1n << 68n,
|
|
702
|
-
SkipClassProperty: 1n << 69n
|
|
681
|
+
SkipObjectMethod: 1n << 64n,
|
|
682
|
+
SkipClassMethod: 1n << 65n,
|
|
683
|
+
SkipClassProperty: 1n << 66n,
|
|
684
|
+
SkipArrayMapCallback: 1n << 67n
|
|
703
685
|
};
|
|
704
686
|
/**
|
|
705
687
|
* Default component detection hint
|
|
706
688
|
*/
|
|
707
|
-
const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.
|
|
689
|
+
const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.SkipArrayMapCallback | ComponentDetectionHint.SkipBooleanLiteral | ComponentDetectionHint.SkipEmptyArray | ComponentDetectionHint.SkipNumberLiteral | ComponentDetectionHint.SkipStringLiteral | ComponentDetectionHint.SkipUndefined | ComponentDetectionHint.StrictArray | ComponentDetectionHint.StrictConditional | ComponentDetectionHint.StrictLogical;
|
|
708
690
|
|
|
709
691
|
//#endregion
|
|
710
692
|
//#region src/component/component-is.ts
|
|
@@ -809,7 +791,7 @@ function shouldExcludeBasedOnHint(node, hint) {
|
|
|
809
791
|
if (hint & ComponentDetectionHint.SkipObjectMethod && isMatching(FUNCTION_PATTERNS.OBJECT_METHOD)(node)) return true;
|
|
810
792
|
if (hint & ComponentDetectionHint.SkipClassMethod && isMatching(FUNCTION_PATTERNS.CLASS_METHOD)(node)) return true;
|
|
811
793
|
if (hint & ComponentDetectionHint.SkipClassProperty && isMatching(FUNCTION_PATTERNS.CLASS_PROPERTY)(node)) return true;
|
|
812
|
-
if (hint & ComponentDetectionHint.
|
|
794
|
+
if (hint & ComponentDetectionHint.SkipArrayMapCallback && AST.isArrayMapCallLoose(node.parent)) return true;
|
|
813
795
|
return false;
|
|
814
796
|
}
|
|
815
797
|
/**
|
|
@@ -868,6 +850,30 @@ function isComponentWrapperCallLoose(context, node) {
|
|
|
868
850
|
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
869
851
|
return isComponentWrapperCall(context, node) || isUseCallbackCall(node);
|
|
870
852
|
}
|
|
853
|
+
/**
|
|
854
|
+
* Check if the node is a callback function passed to a component wrapper
|
|
855
|
+
* @param context The ESLint rule context
|
|
856
|
+
* @param node The node to check
|
|
857
|
+
* @returns `true` if the node is a callback function passed to a component wrapper
|
|
858
|
+
*/
|
|
859
|
+
function isComponentWrapperCallback(context, node) {
|
|
860
|
+
if (!AST.isFunction(node)) return false;
|
|
861
|
+
const parent = node.parent;
|
|
862
|
+
if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
863
|
+
return isComponentWrapperCall(context, parent);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Check if the node is a callback function passed to a component wrapper loosely
|
|
867
|
+
* @param context The ESLint rule context
|
|
868
|
+
* @param node The node to check
|
|
869
|
+
* @returns `true` if the node is a callback function passed to a component wrapper loosely
|
|
870
|
+
*/
|
|
871
|
+
function isComponentWrapperCallbackLoose(context, node) {
|
|
872
|
+
if (!AST.isFunction(node)) return false;
|
|
873
|
+
const parent = node.parent;
|
|
874
|
+
if (parent.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
875
|
+
return isComponentWrapperCallLoose(context, parent);
|
|
876
|
+
}
|
|
871
877
|
|
|
872
878
|
//#endregion
|
|
873
879
|
//#region src/component/component-id.ts
|
|
@@ -875,8 +881,8 @@ function getFunctionComponentId(context, node) {
|
|
|
875
881
|
const functionId = AST.getFunctionId(node);
|
|
876
882
|
if (functionId != null) return functionId;
|
|
877
883
|
const { parent } = node;
|
|
878
|
-
if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator
|
|
879
|
-
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
|
|
884
|
+
if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.id;
|
|
885
|
+
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;
|
|
880
886
|
return unit;
|
|
881
887
|
}
|
|
882
888
|
|
|
@@ -917,14 +923,6 @@ function isComponentNameLoose(name) {
|
|
|
917
923
|
return RE_COMPONENT_NAME_LOOSE.test(name);
|
|
918
924
|
}
|
|
919
925
|
/**
|
|
920
|
-
* Get component name from an identifier or identifier sequence (e.g., MemberExpression)
|
|
921
|
-
* @param id The identifier or identifier sequence
|
|
922
|
-
*/
|
|
923
|
-
function getComponentNameFromId(id) {
|
|
924
|
-
if (id == null) return unit;
|
|
925
|
-
return Array.isArray(id) ? id.map((n) => n.name).join(".") : id.name;
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
926
|
* Check if the function has no name or a loose component name
|
|
929
927
|
* @param context The rule context
|
|
930
928
|
* @param fn The function node
|
|
@@ -932,8 +930,9 @@ function getComponentNameFromId(id) {
|
|
|
932
930
|
function hasNoneOrLooseComponentName(context, fn) {
|
|
933
931
|
const id = getFunctionComponentId(context, fn);
|
|
934
932
|
if (id == null) return true;
|
|
935
|
-
|
|
936
|
-
|
|
933
|
+
if (id.type === AST_NODE_TYPES.Identifier) return isComponentNameLoose(id.name);
|
|
934
|
+
if (id.type === AST_NODE_TYPES.MemberExpression && id.property.type === AST_NODE_TYPES.Identifier) return isComponentNameLoose(id.property.name);
|
|
935
|
+
return false;
|
|
937
936
|
}
|
|
938
937
|
|
|
939
938
|
//#endregion
|
|
@@ -949,6 +948,7 @@ function useComponentCollector(context, options = {}) {
|
|
|
949
948
|
const { collectDisplayName = false, collectHookCalls = false, hint = DEFAULT_COMPONENT_DETECTION_HINT } = options;
|
|
950
949
|
const functionEntries = [];
|
|
951
950
|
const components = /* @__PURE__ */ new Map();
|
|
951
|
+
const getText = (n) => context.sourceCode.getText(n);
|
|
952
952
|
const getCurrentEntry = () => functionEntries.at(-1);
|
|
953
953
|
const onFunctionEnter = (node) => {
|
|
954
954
|
const key = idGen$1.next();
|
|
@@ -956,7 +956,8 @@ function useComponentCollector(context, options = {}) {
|
|
|
956
956
|
key,
|
|
957
957
|
node,
|
|
958
958
|
hookCalls: [],
|
|
959
|
-
isComponent: false
|
|
959
|
+
isComponent: false,
|
|
960
|
+
rets: []
|
|
960
961
|
});
|
|
961
962
|
};
|
|
962
963
|
const onFunctionExit = () => {
|
|
@@ -979,11 +980,12 @@ function useComponentCollector(context, options = {}) {
|
|
|
979
980
|
const entry = getCurrentEntry();
|
|
980
981
|
if (entry == null) return;
|
|
981
982
|
const { body } = entry.node;
|
|
983
|
+
if (body.type === AST_NODE_TYPES.BlockStatement) return;
|
|
982
984
|
if (!(hasNoneOrLooseComponentName(context, entry.node) && isJsxLike(context.sourceCode, body, hint) && isComponentDefinition(context, entry.node, hint))) return;
|
|
983
985
|
const initPath = AST.getFunctionInitPath(entry.node);
|
|
984
986
|
const id = getFunctionComponentId(context, entry.node);
|
|
985
987
|
const key = entry.key;
|
|
986
|
-
const name =
|
|
988
|
+
const name = id == null ? unit : AST.toStringFormat(id, getText);
|
|
987
989
|
components.set(key, {
|
|
988
990
|
id,
|
|
989
991
|
key,
|
|
@@ -994,7 +996,8 @@ function useComponentCollector(context, options = {}) {
|
|
|
994
996
|
flag: getComponentFlagFromInitPath(initPath),
|
|
995
997
|
hint,
|
|
996
998
|
hookCalls: entry.hookCalls,
|
|
997
|
-
initPath
|
|
999
|
+
initPath,
|
|
1000
|
+
rets: [body]
|
|
998
1001
|
});
|
|
999
1002
|
},
|
|
1000
1003
|
...collectDisplayName ? { [AST.SEL_DISPLAY_NAME_ASSIGNMENT_EXPRESSION](node) {
|
|
@@ -1014,12 +1017,13 @@ function useComponentCollector(context, options = {}) {
|
|
|
1014
1017
|
"ReturnStatement[type]"(node) {
|
|
1015
1018
|
const entry = getCurrentEntry();
|
|
1016
1019
|
if (entry == null) return;
|
|
1020
|
+
entry.rets.push(node.argument);
|
|
1017
1021
|
if (!(hasNoneOrLooseComponentName(context, entry.node) && isJsxLike(context.sourceCode, node.argument, hint) && isComponentDefinition(context, entry.node, hint))) return;
|
|
1018
1022
|
entry.isComponent = true;
|
|
1019
1023
|
const initPath = AST.getFunctionInitPath(entry.node);
|
|
1020
1024
|
const id = getFunctionComponentId(context, entry.node);
|
|
1021
1025
|
const key = entry.key;
|
|
1022
|
-
const name =
|
|
1026
|
+
const name = id == null ? unit : AST.toStringFormat(id, getText);
|
|
1023
1027
|
components.set(key, {
|
|
1024
1028
|
id,
|
|
1025
1029
|
key,
|
|
@@ -1030,7 +1034,8 @@ function useComponentCollector(context, options = {}) {
|
|
|
1030
1034
|
flag: getComponentFlagFromInitPath(initPath),
|
|
1031
1035
|
hint,
|
|
1032
1036
|
hookCalls: entry.hookCalls,
|
|
1033
|
-
initPath
|
|
1037
|
+
initPath,
|
|
1038
|
+
rets: entry.rets
|
|
1034
1039
|
});
|
|
1035
1040
|
}
|
|
1036
1041
|
}
|
|
@@ -1173,8 +1178,13 @@ function getPhaseKindOfFunction(node) {
|
|
|
1173
1178
|
*/
|
|
1174
1179
|
function isRenderFunctionLoose(context, node) {
|
|
1175
1180
|
if (!AST.isFunction(node)) return false;
|
|
1176
|
-
|
|
1177
|
-
|
|
1181
|
+
const id = AST.getFunctionId(node);
|
|
1182
|
+
switch (true) {
|
|
1183
|
+
case id?.type === AST_NODE_TYPES.Identifier: return id.name.startsWith("render");
|
|
1184
|
+
case id?.type === AST_NODE_TYPES.MemberExpression && id.property.type === AST_NODE_TYPES.Identifier: return id.property.name.startsWith("render");
|
|
1185
|
+
case node.parent.type === AST_NODE_TYPES.JSXExpressionContainer && node.parent.parent.type === AST_NODE_TYPES.JSXAttribute && node.parent.parent.name.type === AST_NODE_TYPES.JSXIdentifier: return node.parent.parent.name.name.startsWith("render");
|
|
1186
|
+
}
|
|
1187
|
+
return false;
|
|
1178
1188
|
}
|
|
1179
1189
|
/**
|
|
1180
1190
|
* Unsafe check whether given JSXAttribute is a render prop
|
|
@@ -1227,4 +1237,57 @@ function isDeclaredInRenderPropLoose(node) {
|
|
|
1227
1237
|
}
|
|
1228
1238
|
|
|
1229
1239
|
//#endregion
|
|
1230
|
-
|
|
1240
|
+
//#region src/hierarchy/find-enclosing-assignment-target.ts
|
|
1241
|
+
/** eslint-disable jsdoc/require-param */
|
|
1242
|
+
/**
|
|
1243
|
+
* Finds the enclosing assignment target (variable, property, etc.) for a given node
|
|
1244
|
+
*
|
|
1245
|
+
* @todo Verify correctness and completeness of this function
|
|
1246
|
+
* @param node The starting node
|
|
1247
|
+
* @returns The enclosing assignment target node, or undefined if not found
|
|
1248
|
+
*/
|
|
1249
|
+
function findEnclosingAssignmentTarget(node) {
|
|
1250
|
+
switch (true) {
|
|
1251
|
+
case node.type === AST_NODE_TYPES.VariableDeclarator: return node.id;
|
|
1252
|
+
case node.type === AST_NODE_TYPES.AssignmentExpression: return node.left;
|
|
1253
|
+
case node.type === AST_NODE_TYPES.PropertyDefinition: return node.key;
|
|
1254
|
+
case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node: return unit;
|
|
1255
|
+
default: return findEnclosingAssignmentTarget(node.parent);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
//#endregion
|
|
1260
|
+
//#region src/hierarchy/find-enclosing-component-or-hook.ts
|
|
1261
|
+
/**
|
|
1262
|
+
* Find the enclosing React component or hook for a given AST node.
|
|
1263
|
+
* @param node The AST node to start the search from.
|
|
1264
|
+
* @param test Optional test function to customize component or hook identification.
|
|
1265
|
+
* @returns The enclosing component or hook node, or `null` if none is found.
|
|
1266
|
+
*/
|
|
1267
|
+
function findEnclosingComponentOrHook(node, test = (n, name) => {
|
|
1268
|
+
if (name == null) return false;
|
|
1269
|
+
return isComponentNameLoose(name) || isReactHookName(name);
|
|
1270
|
+
}) {
|
|
1271
|
+
const enclosingNode = AST.findParentNode(node, (n) => {
|
|
1272
|
+
if (!AST.isFunction(n)) return false;
|
|
1273
|
+
return test(n, match(AST.getFunctionId(n)).with({ type: AST_NODE_TYPES.Identifier }, (id) => id.name).with({
|
|
1274
|
+
type: AST_NODE_TYPES.MemberExpression,
|
|
1275
|
+
property: { type: AST_NODE_TYPES.Identifier }
|
|
1276
|
+
}, (me) => me.property.name).otherwise(() => null));
|
|
1277
|
+
});
|
|
1278
|
+
return AST.isFunction(enclosingNode) ? enclosingNode : unit;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
//#endregion
|
|
1282
|
+
//#region src/hierarchy/is-inside-component-or-hook.ts
|
|
1283
|
+
/**
|
|
1284
|
+
* Checks if a given AST node is inside a React component or hook.
|
|
1285
|
+
* @param node The AST node to check.
|
|
1286
|
+
* @returns True if the node is inside a component or hook, false otherwise.
|
|
1287
|
+
*/
|
|
1288
|
+
function isInsideComponentOrHook(node) {
|
|
1289
|
+
return findEnclosingComponentOrHook(node) != null;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
//#endregion
|
|
1293
|
+
export { ComponentDetectionHint, ComponentFlag, ComponentPhaseRelevance, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, findEnclosingAssignmentTarget, findEnclosingComponentOrHook, findParentJsxAttribute, getComponentFlagFromInitPath, getFunctionComponentId, 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, isComponentWrapperCallback, isComponentWrapperCallbackLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFunctionOfComponentDidMount, isFunctionOfComponentWillUnmount, isFunctionOfUseEffectCleanup, isFunctionOfUseEffectSetup, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isInitializedFromReact, isInitializedFromReactNative, isInsideComponentOrHook, 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.6.
|
|
3
|
+
"version": "2.6.5-next.0",
|
|
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.53.0",
|
|
36
36
|
"birecord": "^0.1.1",
|
|
37
37
|
"ts-pattern": "^5.9.0",
|
|
38
|
-
"@eslint-react/ast": "2.6.
|
|
39
|
-
"@eslint-react/eff": "2.6.
|
|
40
|
-
"@eslint-react/var": "2.6.
|
|
41
|
-
"@eslint-react/shared": "2.6.
|
|
38
|
+
"@eslint-react/ast": "2.6.5-next.0",
|
|
39
|
+
"@eslint-react/eff": "2.6.5-next.0",
|
|
40
|
+
"@eslint-react/var": "2.6.5-next.0",
|
|
41
|
+
"@eslint-react/shared": "2.6.5-next.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"tsdown": "^0.20.0-beta.3",
|