@eslint-react/core 3.0.0-next.63 → 3.0.0-next.64
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 +53 -57
- package/dist/index.js +41 -58
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as ast from "@eslint-react/ast";
|
|
2
|
-
import { unit } from "@eslint-react/eff";
|
|
3
2
|
import { TSESTree } from "@typescript-eslint/types";
|
|
4
3
|
import { RegExpLike, RuleContext } from "@eslint-react/shared";
|
|
5
4
|
import { ESLintUtils, TSESTree as TSESTree$1 } from "@typescript-eslint/utils";
|
|
@@ -11,9 +10,9 @@ import * as typescript from "typescript";
|
|
|
11
10
|
* Find the import source of a variable
|
|
12
11
|
* @param name The variable name
|
|
13
12
|
* @param initialScope The initial scope to search
|
|
14
|
-
* @returns The import source or
|
|
13
|
+
* @returns The import source or null if not found
|
|
15
14
|
*/
|
|
16
|
-
declare function findImportSource(name: string, initialScope: Scope): string |
|
|
15
|
+
declare function findImportSource(name: string, initialScope: Scope): string | null;
|
|
17
16
|
//#endregion
|
|
18
17
|
//#region src/api/is-from-react.d.ts
|
|
19
18
|
/**
|
|
@@ -38,8 +37,8 @@ declare function isInitializedFromReactNative(name: string, initialScope: Scope,
|
|
|
38
37
|
//#region src/api/is-react-api.d.ts
|
|
39
38
|
declare namespace isReactAPI {
|
|
40
39
|
type ReturnType = {
|
|
41
|
-
(context: RuleContext, node:
|
|
42
|
-
(context: RuleContext): (node:
|
|
40
|
+
(context: RuleContext, node: null | TSESTree.Node): node is TSESTree.Identifier | TSESTree.MemberExpression;
|
|
41
|
+
(context: RuleContext): (node: null | TSESTree.Node) => node is TSESTree.MemberExpression | TSESTree.Identifier;
|
|
43
42
|
};
|
|
44
43
|
}
|
|
45
44
|
/**
|
|
@@ -50,8 +49,8 @@ declare namespace isReactAPI {
|
|
|
50
49
|
declare function isReactAPI(api: string): isReactAPI.ReturnType;
|
|
51
50
|
declare namespace isReactAPICall {
|
|
52
51
|
type ReturnType = {
|
|
53
|
-
(context: RuleContext, node:
|
|
54
|
-
(context: RuleContext): (node:
|
|
52
|
+
(context: RuleContext, node: null | TSESTree.Node): node is TSESTree.CallExpression;
|
|
53
|
+
(context: RuleContext): (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
55
54
|
};
|
|
56
55
|
}
|
|
57
56
|
/**
|
|
@@ -134,13 +133,13 @@ declare function isComponentDefinition(context: RuleContext, node: ast.TSESTreeF
|
|
|
134
133
|
*/
|
|
135
134
|
interface SemanticNode {
|
|
136
135
|
/** The identifier of the node */
|
|
137
|
-
id:
|
|
136
|
+
id: null | TSESTree.Node;
|
|
138
137
|
/** The unique key of the node */
|
|
139
138
|
key: string;
|
|
140
139
|
/** The kind of the node */
|
|
141
140
|
kind: string;
|
|
142
141
|
/** The name of the node */
|
|
143
|
-
name:
|
|
142
|
+
name: null | string;
|
|
144
143
|
/** The flag of the node */
|
|
145
144
|
flag: bigint;
|
|
146
145
|
/** The hint of the node */
|
|
@@ -160,9 +159,9 @@ interface SemanticFunc extends SemanticNode {
|
|
|
160
159
|
/** The AST node of the function */
|
|
161
160
|
node: ast.TSESTreeFunction;
|
|
162
161
|
/** The name of the function */
|
|
163
|
-
name: string |
|
|
162
|
+
name: string | null;
|
|
164
163
|
/** The return type annotation of the function */
|
|
165
|
-
type: TSESTree.TSTypeAnnotation |
|
|
164
|
+
type: TSESTree.TSTypeAnnotation | null;
|
|
166
165
|
/** The body of the function */
|
|
167
166
|
body: TSESTree.BlockStatement | TSESTree.Expression;
|
|
168
167
|
/** The directives of the function (e.g., "use strict", "use client", "use server", etc.) */
|
|
@@ -170,7 +169,7 @@ interface SemanticFunc extends SemanticNode {
|
|
|
170
169
|
/** The parameters of the function */
|
|
171
170
|
parameters: TSESTree.Parameter[];
|
|
172
171
|
/** The type parameters of the function */
|
|
173
|
-
typeParameters: TSESTree.TSTypeParameterDeclaration |
|
|
172
|
+
typeParameters: TSESTree.TSTypeParameterDeclaration | null;
|
|
174
173
|
}
|
|
175
174
|
//#endregion
|
|
176
175
|
//#region src/component/component-semantic-node.d.ts
|
|
@@ -205,7 +204,7 @@ interface FunctionComponentSemanticNode extends SemanticNode {
|
|
|
205
204
|
/**
|
|
206
205
|
* The initialization path of the function
|
|
207
206
|
*/
|
|
208
|
-
initPath:
|
|
207
|
+
initPath: null | ast.FunctionInitPath;
|
|
209
208
|
/**
|
|
210
209
|
* Indicates if the component is inside an export default declaration
|
|
211
210
|
*/
|
|
@@ -221,7 +220,7 @@ interface FunctionComponentSemanticNode extends SemanticNode {
|
|
|
221
220
|
/**
|
|
222
221
|
* The display name of the component
|
|
223
222
|
*/
|
|
224
|
-
displayName:
|
|
223
|
+
displayName: null | TSESTree.Expression;
|
|
225
224
|
/**
|
|
226
225
|
* The directives used in the function (e.g., "use strict", "use client", etc.)
|
|
227
226
|
*/
|
|
@@ -234,7 +233,7 @@ interface ClassComponentSemanticNode extends SemanticNode {
|
|
|
234
233
|
/**
|
|
235
234
|
* The identifier of the component
|
|
236
235
|
*/
|
|
237
|
-
id:
|
|
236
|
+
id: null | TSESTree.BindingName;
|
|
238
237
|
/**
|
|
239
238
|
* The kind of component
|
|
240
239
|
*/
|
|
@@ -258,7 +257,7 @@ interface ClassComponentSemanticNode extends SemanticNode {
|
|
|
258
257
|
/**
|
|
259
258
|
* The display name of the component
|
|
260
259
|
*/
|
|
261
|
-
displayName:
|
|
260
|
+
displayName: null | TSESTree.Expression;
|
|
262
261
|
}
|
|
263
262
|
/**
|
|
264
263
|
* Represents a React Component
|
|
@@ -278,7 +277,7 @@ declare namespace useComponentCollector {
|
|
|
278
277
|
ctx: {
|
|
279
278
|
getAllComponents: (node: TSESTree.Program) => FunctionComponentSemanticNode[];
|
|
280
279
|
getCurrentEntries: () => FunctionEntry$1[];
|
|
281
|
-
getCurrentEntry: () => FunctionEntry$1 |
|
|
280
|
+
getCurrentEntry: () => FunctionEntry$1 | null;
|
|
282
281
|
};
|
|
283
282
|
visitor: ESLintUtils.RuleListener;
|
|
284
283
|
};
|
|
@@ -413,7 +412,7 @@ declare function getComponentFlagFromInitPath(initPath: FunctionComponentSemanti
|
|
|
413
412
|
* Get function component identifier from `const Component = memo(() => {});`
|
|
414
413
|
* @param context The rule context
|
|
415
414
|
* @param node The function node to analyze
|
|
416
|
-
* @returns The function identifier or `
|
|
415
|
+
* @returns The function identifier or `null` if not found
|
|
417
416
|
*/
|
|
418
417
|
declare function getFunctionComponentId(context: RuleContext, node: ast.TSESTreeFunction): ast.FunctionID;
|
|
419
418
|
//#endregion
|
|
@@ -502,12 +501,12 @@ type FunctionSemanticNode = ClientFunctionSemanticNode | ServerFunctionSemanticN
|
|
|
502
501
|
* Determine if a node is the setup function passed to a useEffect-like hook
|
|
503
502
|
* @param node The AST node to check
|
|
504
503
|
*/
|
|
505
|
-
declare function isUseEffectSetupCallback(node: TSESTree.Node |
|
|
504
|
+
declare function isUseEffectSetupCallback(node: TSESTree.Node | null): boolean;
|
|
506
505
|
/**
|
|
507
506
|
* Determine if a node is the cleanup function returned by a useEffect-like hook's setup function
|
|
508
507
|
* @param node The AST node to check
|
|
509
508
|
*/
|
|
510
|
-
declare function isUseEffectCleanupCallback(node: TSESTree.Node |
|
|
509
|
+
declare function isUseEffectCleanupCallback(node: TSESTree.Node | null): boolean;
|
|
511
510
|
//#endregion
|
|
512
511
|
//#region src/hook/hook-semantic-node.d.ts
|
|
513
512
|
/**
|
|
@@ -537,7 +536,7 @@ declare namespace useHookCollector {
|
|
|
537
536
|
ctx: {
|
|
538
537
|
getAllHooks(node: TSESTree$1.Program): HookSemanticNode[];
|
|
539
538
|
getCurrentEntries(): FunctionEntry[];
|
|
540
|
-
getCurrentEntry(): FunctionEntry |
|
|
539
|
+
getCurrentEntry(): FunctionEntry | null;
|
|
541
540
|
};
|
|
542
541
|
visitor: ESLintUtils.RuleListener;
|
|
543
542
|
};
|
|
@@ -563,53 +562,53 @@ declare function isHookId(id: TSESTree.Node): id is TSESTree.Identifier | TSESTr
|
|
|
563
562
|
* @param node The function node to check
|
|
564
563
|
* @returns True if the function is a React Hook, false otherwise
|
|
565
564
|
*/
|
|
566
|
-
declare function isHook(node: ast.TSESTreeFunction |
|
|
565
|
+
declare function isHook(node: ast.TSESTreeFunction | null): boolean;
|
|
567
566
|
/**
|
|
568
567
|
* Check if the given node is a React Hook call by its name.
|
|
569
568
|
* @param node The node to check.
|
|
570
569
|
* @returns `true` if the node is a React Hook call, `false` otherwise.
|
|
571
570
|
*/
|
|
572
|
-
declare function isHookCall(node: TSESTree.Node |
|
|
571
|
+
declare function isHookCall(node: TSESTree.Node | null): node is TSESTree.CallExpression;
|
|
573
572
|
/**
|
|
574
573
|
* Check if a node is a call to a specific React hook.
|
|
575
574
|
* Returns a function that accepts a hook name to check against.
|
|
576
575
|
* @param node The AST node to check
|
|
577
576
|
* @returns A function that takes a hook name and returns boolean
|
|
578
577
|
*/
|
|
579
|
-
declare function isHookCallWithName(node: TSESTree.Node |
|
|
578
|
+
declare function isHookCallWithName(node: TSESTree.Node | null): (name: string) => boolean;
|
|
580
579
|
/**
|
|
581
580
|
* Detect useEffect calls and variations (useLayoutEffect, etc.) using a regex pattern
|
|
582
581
|
* @param node The AST node to check
|
|
583
582
|
* @param additionalEffectHooks Regex pattern matching custom hooks that should be treated as effect hooks
|
|
584
583
|
* @returns True if the node is a useEffect-like call
|
|
585
584
|
*/
|
|
586
|
-
declare function isUseEffectLikeCall(node: TSESTree.Node |
|
|
585
|
+
declare function isUseEffectLikeCall(node: TSESTree.Node | null, additionalEffectHooks?: RegExpLike): node is TSESTree.CallExpression;
|
|
587
586
|
/**
|
|
588
587
|
* Detect useState calls and variations (useCustomState, etc.) using a regex pattern
|
|
589
588
|
* @param node The AST node to check
|
|
590
589
|
* @param additionalStateHooks Regex pattern matching custom hooks that should be treated as state hooks
|
|
591
590
|
* @returns True if the node is a useState-like call
|
|
592
591
|
*/
|
|
593
|
-
declare function isUseStateLikeCall(node: TSESTree.Node |
|
|
594
|
-
declare const isUseCall: (node: TSESTree.Node |
|
|
595
|
-
declare const isUseActionStateCall: (node: TSESTree.Node |
|
|
596
|
-
declare const isUseCallbackCall: (node: TSESTree.Node |
|
|
597
|
-
declare const isUseContextCall: (node: TSESTree.Node |
|
|
598
|
-
declare const isUseDebugValueCall: (node: TSESTree.Node |
|
|
599
|
-
declare const isUseDeferredValueCall: (node: TSESTree.Node |
|
|
600
|
-
declare const isUseEffectCall: (node: TSESTree.Node |
|
|
601
|
-
declare const isUseFormStatusCall: (node: TSESTree.Node |
|
|
602
|
-
declare const isUseIdCall: (node: TSESTree.Node |
|
|
603
|
-
declare const isUseImperativeHandleCall: (node: TSESTree.Node |
|
|
604
|
-
declare const isUseInsertionEffectCall: (node: TSESTree.Node |
|
|
605
|
-
declare const isUseLayoutEffectCall: (node: TSESTree.Node |
|
|
606
|
-
declare const isUseMemoCall: (node: TSESTree.Node |
|
|
607
|
-
declare const isUseOptimisticCall: (node: TSESTree.Node |
|
|
608
|
-
declare const isUseReducerCall: (node: TSESTree.Node |
|
|
609
|
-
declare const isUseRefCall: (node: TSESTree.Node |
|
|
610
|
-
declare const isUseStateCall: (node: TSESTree.Node |
|
|
611
|
-
declare const isUseSyncExternalStoreCall: (node: TSESTree.Node |
|
|
612
|
-
declare const isUseTransitionCall: (node: TSESTree.Node |
|
|
592
|
+
declare function isUseStateLikeCall(node: TSESTree.Node | null, additionalStateHooks?: RegExpLike): node is TSESTree.CallExpression;
|
|
593
|
+
declare const isUseCall: (node: TSESTree.Node | null) => boolean;
|
|
594
|
+
declare const isUseActionStateCall: (node: TSESTree.Node | null) => boolean;
|
|
595
|
+
declare const isUseCallbackCall: (node: TSESTree.Node | null) => boolean;
|
|
596
|
+
declare const isUseContextCall: (node: TSESTree.Node | null) => boolean;
|
|
597
|
+
declare const isUseDebugValueCall: (node: TSESTree.Node | null) => boolean;
|
|
598
|
+
declare const isUseDeferredValueCall: (node: TSESTree.Node | null) => boolean;
|
|
599
|
+
declare const isUseEffectCall: (node: TSESTree.Node | null) => boolean;
|
|
600
|
+
declare const isUseFormStatusCall: (node: TSESTree.Node | null) => boolean;
|
|
601
|
+
declare const isUseIdCall: (node: TSESTree.Node | null) => boolean;
|
|
602
|
+
declare const isUseImperativeHandleCall: (node: TSESTree.Node | null) => boolean;
|
|
603
|
+
declare const isUseInsertionEffectCall: (node: TSESTree.Node | null) => boolean;
|
|
604
|
+
declare const isUseLayoutEffectCall: (node: TSESTree.Node | null) => boolean;
|
|
605
|
+
declare const isUseMemoCall: (node: TSESTree.Node | null) => boolean;
|
|
606
|
+
declare const isUseOptimisticCall: (node: TSESTree.Node | null) => boolean;
|
|
607
|
+
declare const isUseReducerCall: (node: TSESTree.Node | null) => boolean;
|
|
608
|
+
declare const isUseRefCall: (node: TSESTree.Node | null) => boolean;
|
|
609
|
+
declare const isUseStateCall: (node: TSESTree.Node | null) => boolean;
|
|
610
|
+
declare const isUseSyncExternalStoreCall: (node: TSESTree.Node | null) => boolean;
|
|
611
|
+
declare const isUseTransitionCall: (node: TSESTree.Node | null) => boolean;
|
|
613
612
|
//#endregion
|
|
614
613
|
//#region src/hook/hook-name.d.ts
|
|
615
614
|
declare const REACT_BUILTIN_HOOK_NAMES: readonly ["use", "useActionState", "useCallback", "useContext", "useDebugValue", "useDeferredValue", "useEffect", "useFormStatus", "useId", "useImperativeHandle", "useInsertionEffect", "useLayoutEffect", "useMemo", "useOptimistic", "useReducer", "useRef", "useState", "useSyncExternalStore", "useTransition"];
|
|
@@ -690,11 +689,11 @@ declare function resolveJsxAttributeValue(context: RuleContext, attribute: ast.T
|
|
|
690
689
|
} | {
|
|
691
690
|
readonly kind: "element";
|
|
692
691
|
readonly node: TSESTree.JSXElement;
|
|
693
|
-
readonly toStatic: () =>
|
|
692
|
+
readonly toStatic: () => null;
|
|
694
693
|
} | {
|
|
695
694
|
readonly kind: "spreadChild";
|
|
696
695
|
readonly node: TSESTree.JSXEmptyExpression | TSESTree.Expression;
|
|
697
|
-
readonly toStatic: () =>
|
|
696
|
+
readonly toStatic: () => null;
|
|
698
697
|
} | {
|
|
699
698
|
readonly kind: "spreadProps";
|
|
700
699
|
readonly node: TSESTree.Expression;
|
|
@@ -763,20 +762,17 @@ declare const DEFAULT_JSX_DETECTION_HINT: bigint;
|
|
|
763
762
|
* @param node The AST node to check
|
|
764
763
|
* @returns `true` if the node is a `JSXText` or a `Literal` node
|
|
765
764
|
*/
|
|
766
|
-
declare function isJsxText(node: TSESTree$1.Node | null
|
|
765
|
+
declare function isJsxText(node: TSESTree$1.Node | null): node is TSESTree$1.JSXText | TSESTree$1.Literal;
|
|
767
766
|
/**
|
|
768
767
|
* Determine if a node represents JSX-like content based on heuristics
|
|
769
768
|
* Supports configuration through hint flags to customize detection behavior
|
|
770
769
|
*
|
|
771
|
-
* @param
|
|
772
|
-
* @param code.getScope The function to get the scope of a node
|
|
770
|
+
* @param context The rule context with scope lookup capability
|
|
773
771
|
* @param node The AST node to analyze
|
|
774
772
|
* @param hint The configuration flags to adjust detection behavior
|
|
775
773
|
* @returns boolean Whether the node is considered JSX-like
|
|
776
774
|
*/
|
|
777
|
-
declare function isJsxLike(
|
|
778
|
-
getScope: (node: TSESTree$1.Node) => Scope;
|
|
779
|
-
}, node: TSESTree$1.Node | unit | null, hint?: JsxDetectionHint): boolean;
|
|
775
|
+
declare function isJsxLike(context: RuleContext, node: TSESTree$1.Node | null, hint?: JsxDetectionHint): boolean;
|
|
780
776
|
//#endregion
|
|
781
777
|
//#region src/jsx/jsx-element-is.d.ts
|
|
782
778
|
/**
|
|
@@ -819,9 +815,9 @@ declare function getJsxElementType(context: RuleContext, node: TSESTree.JSXEleme
|
|
|
819
815
|
* @param node The starting AST node
|
|
820
816
|
* @param test Optional predicate function to test if the attribute meets criteria
|
|
821
817
|
* Defaults to always returning true (matches any attribute)
|
|
822
|
-
* @returns The first matching JSX attribute node found when traversing upwards, or
|
|
818
|
+
* @returns The first matching JSX attribute node found when traversing upwards, or null
|
|
823
819
|
*/
|
|
824
|
-
declare function findParentJsxAttribute(node: TSESTree.Node, test?: (node: TSESTree.JSXAttribute) => boolean): TSESTree.JSXAttribute |
|
|
820
|
+
declare function findParentJsxAttribute(node: TSESTree.Node, test?: (node: TSESTree.JSXAttribute) => boolean): TSESTree.JSXAttribute | null;
|
|
825
821
|
//#endregion
|
|
826
822
|
//#region src/jsx/jsx-stringify.d.ts
|
|
827
823
|
/**
|
|
@@ -847,9 +843,9 @@ declare function isInitializedFromRef(name: string, initialScope: Scope): boolea
|
|
|
847
843
|
* Get the init expression of a ref variable
|
|
848
844
|
* @param name The variable name
|
|
849
845
|
* @param initialScope The initial scope
|
|
850
|
-
* @returns The init expression node if the variable is derived from a ref, or
|
|
846
|
+
* @returns The init expression node if the variable is derived from a ref, or null otherwise
|
|
851
847
|
*/
|
|
852
|
-
declare function getRefInit(name: string, initialScope: Scope): TSESTree$1.Expression |
|
|
848
|
+
declare function getRefInit(name: string, initialScope: Scope): TSESTree$1.Expression | null;
|
|
853
849
|
//#endregion
|
|
854
850
|
//#region src/ref/ref-name.d.ts
|
|
855
851
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import * as ast from "@eslint-react/ast";
|
|
2
|
-
import { constFalse, constTrue, dual, flip, getOrElseUpdate, identity
|
|
2
|
+
import { constFalse, constTrue, dual, flip, getOrElseUpdate, identity } from "@eslint-react/eff";
|
|
3
3
|
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
4
4
|
import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
5
5
|
import { P, match } from "ts-pattern";
|
|
6
6
|
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";
|
|
7
|
+
import { resolve } from "@eslint-react/var";
|
|
7
8
|
import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from "@typescript-eslint/utils";
|
|
8
9
|
|
|
9
10
|
//#region src/api/find-import-source.ts
|
|
10
11
|
/**
|
|
11
12
|
* Get the arguments of a require expression
|
|
12
13
|
* @param node The node to match
|
|
13
|
-
* @returns The require expression arguments or
|
|
14
|
+
* @returns The require expression arguments or null if the node is not a require expression
|
|
14
15
|
* @internal
|
|
15
16
|
*/
|
|
16
17
|
function getRequireExpressionArguments(node) {
|
|
@@ -30,22 +31,22 @@ function getRequireExpressionArguments(node) {
|
|
|
30
31
|
* Find the import source of a variable
|
|
31
32
|
* @param name The variable name
|
|
32
33
|
* @param initialScope The initial scope to search
|
|
33
|
-
* @returns The import source or
|
|
34
|
+
* @returns The import source or null if not found
|
|
34
35
|
*/
|
|
35
36
|
function findImportSource(name, initialScope) {
|
|
36
37
|
const latestDef = findVariable(initialScope, name)?.defs.at(-1);
|
|
37
|
-
if (latestDef == null) return
|
|
38
|
+
if (latestDef == null) return null;
|
|
38
39
|
const { node, parent } = latestDef;
|
|
39
40
|
if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
|
|
40
41
|
const { init } = node;
|
|
41
42
|
if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) return findImportSource(init.object.name, initialScope);
|
|
42
43
|
if (init.type === AST_NODE_TYPES.Identifier) return findImportSource(init.name, initialScope);
|
|
43
44
|
const arg0 = getRequireExpressionArguments(init)?.[0];
|
|
44
|
-
if (arg0 == null || !ast.isLiteral(arg0, "string")) return
|
|
45
|
+
if (arg0 == null || !ast.isLiteral(arg0, "string")) return null;
|
|
45
46
|
return arg0.value;
|
|
46
47
|
}
|
|
47
48
|
if (parent?.type === AST_NODE_TYPES.ImportDeclaration) return parent.source.value;
|
|
48
|
-
return
|
|
49
|
+
return null;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
//#endregion
|
|
@@ -312,7 +313,7 @@ function useHookCollector(context) {
|
|
|
312
313
|
const hooks = /* @__PURE__ */ new Map();
|
|
313
314
|
const functionEntries = [];
|
|
314
315
|
const getText = (n) => context.sourceCode.getText(n);
|
|
315
|
-
const getCurrentEntry = () => functionEntries.at(-1);
|
|
316
|
+
const getCurrentEntry = () => functionEntries.at(-1) ?? null;
|
|
316
317
|
const onFunctionEnter = (node) => {
|
|
317
318
|
const id = ast.getFunctionId(node);
|
|
318
319
|
const key = idGen$2.next();
|
|
@@ -400,7 +401,7 @@ function getJsxAttributeName(context, node) {
|
|
|
400
401
|
* @param initialScope (Optional) The initial scope to use for variable resolution
|
|
401
402
|
*/
|
|
402
403
|
function getJsxAttribute(context, node, initialScope) {
|
|
403
|
-
|
|
404
|
+
initialScope ?? context.sourceCode.getScope(node);
|
|
404
405
|
const attributes = node.openingElement.attributes;
|
|
405
406
|
/**
|
|
406
407
|
* Finds the last occurrence of a specific attribute
|
|
@@ -411,15 +412,7 @@ function getJsxAttribute(context, node, initialScope) {
|
|
|
411
412
|
if (attr.type === AST_NODE_TYPES.JSXAttribute) return getJsxAttributeName(context, attr) === name;
|
|
412
413
|
switch (attr.argument.type) {
|
|
413
414
|
case AST_NODE_TYPES.Identifier: {
|
|
414
|
-
const
|
|
415
|
-
function resolve(v) {
|
|
416
|
-
if (v == null) return unit;
|
|
417
|
-
const def = v.defs.at(0);
|
|
418
|
-
if (def == null) return unit;
|
|
419
|
-
if ("init" in def.node && def.node.init != null && !("declarations" in def.node.init)) return def.node.init;
|
|
420
|
-
return unit;
|
|
421
|
-
}
|
|
422
|
-
const initNode = resolve(variable);
|
|
415
|
+
const initNode = resolve(context, attr.argument);
|
|
423
416
|
if (initNode?.type === AST_NODE_TYPES.ObjectExpression) return ast.findProperty(initNode.properties, name) != null;
|
|
424
417
|
return false;
|
|
425
418
|
}
|
|
@@ -477,14 +470,14 @@ function resolveJsxAttributeValue(context, attribute) {
|
|
|
477
470
|
kind: "element",
|
|
478
471
|
node: node.value,
|
|
479
472
|
toStatic() {
|
|
480
|
-
return
|
|
473
|
+
return null;
|
|
481
474
|
}
|
|
482
475
|
};
|
|
483
476
|
case AST_NODE_TYPES.JSXSpreadChild: return {
|
|
484
477
|
kind: "spreadChild",
|
|
485
478
|
node: node.value.expression,
|
|
486
479
|
toStatic() {
|
|
487
|
-
return
|
|
480
|
+
return null;
|
|
488
481
|
}
|
|
489
482
|
};
|
|
490
483
|
}
|
|
@@ -498,8 +491,8 @@ function resolveJsxAttributeValue(context, attribute) {
|
|
|
498
491
|
kind: "spreadProps",
|
|
499
492
|
node: node.argument,
|
|
500
493
|
toStatic(name) {
|
|
501
|
-
if (name == null) return
|
|
502
|
-
return match(getStaticValue(node.argument, initialScope)?.value).with({ [name]: P.select(P.any) }, identity).otherwise(() =>
|
|
494
|
+
if (name == null) return null;
|
|
495
|
+
return match(getStaticValue(node.argument, initialScope)?.value).with({ [name]: P.select(P.any) }, identity).otherwise(() => null);
|
|
503
496
|
}
|
|
504
497
|
};
|
|
505
498
|
}
|
|
@@ -593,13 +586,12 @@ function isJsxText(node) {
|
|
|
593
586
|
* Determine if a node represents JSX-like content based on heuristics
|
|
594
587
|
* Supports configuration through hint flags to customize detection behavior
|
|
595
588
|
*
|
|
596
|
-
* @param
|
|
597
|
-
* @param code.getScope The function to get the scope of a node
|
|
589
|
+
* @param context The rule context with scope lookup capability
|
|
598
590
|
* @param node The AST node to analyze
|
|
599
591
|
* @param hint The configuration flags to adjust detection behavior
|
|
600
592
|
* @returns boolean Whether the node is considered JSX-like
|
|
601
593
|
*/
|
|
602
|
-
function isJsxLike(
|
|
594
|
+
function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
603
595
|
if (node == null) return false;
|
|
604
596
|
if (ast.isJSX(node)) return true;
|
|
605
597
|
switch (node.type) {
|
|
@@ -615,27 +607,27 @@ function isJsxLike(code, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
|
615
607
|
case AST_NODE_TYPES.TemplateLiteral: return !(hint & JsxDetectionHint.DoNotIncludeJsxWithStringValue);
|
|
616
608
|
case AST_NODE_TYPES.ArrayExpression:
|
|
617
609
|
if (node.elements.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue);
|
|
618
|
-
if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.elements.every((n) => isJsxLike(
|
|
619
|
-
return node.elements.some((n) => isJsxLike(
|
|
610
|
+
if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.elements.every((n) => isJsxLike(context, n, hint));
|
|
611
|
+
return node.elements.some((n) => isJsxLike(context, n, hint));
|
|
620
612
|
case AST_NODE_TYPES.LogicalExpression:
|
|
621
|
-
if (hint & JsxDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx) return isJsxLike(
|
|
622
|
-
return isJsxLike(
|
|
613
|
+
if (hint & JsxDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx) return isJsxLike(context, node.left, hint) && isJsxLike(context, node.right, hint);
|
|
614
|
+
return isJsxLike(context, node.left, hint) || isJsxLike(context, node.right, hint);
|
|
623
615
|
case AST_NODE_TYPES.ConditionalExpression: {
|
|
624
616
|
function leftHasJSX(node) {
|
|
625
617
|
if (Array.isArray(node.consequent)) {
|
|
626
618
|
if (node.consequent.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue);
|
|
627
|
-
if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.consequent.every((n) => isJsxLike(
|
|
628
|
-
return node.consequent.some((n) => isJsxLike(
|
|
619
|
+
if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.consequent.every((n) => isJsxLike(context, n, hint));
|
|
620
|
+
return node.consequent.some((n) => isJsxLike(context, n, hint));
|
|
629
621
|
}
|
|
630
|
-
return isJsxLike(
|
|
622
|
+
return isJsxLike(context, node.consequent, hint);
|
|
631
623
|
}
|
|
632
624
|
function rightHasJSX(node) {
|
|
633
|
-
return isJsxLike(
|
|
625
|
+
return isJsxLike(context, node.alternate, hint);
|
|
634
626
|
}
|
|
635
627
|
if (hint & JsxDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx) return leftHasJSX(node) && rightHasJSX(node);
|
|
636
628
|
return leftHasJSX(node) || rightHasJSX(node);
|
|
637
629
|
}
|
|
638
|
-
case AST_NODE_TYPES.SequenceExpression: return isJsxLike(
|
|
630
|
+
case AST_NODE_TYPES.SequenceExpression: return isJsxLike(context, node.expressions.at(-1) ?? null, hint);
|
|
639
631
|
case AST_NODE_TYPES.CallExpression:
|
|
640
632
|
if (hint & JsxDetectionHint.DoNotIncludeJsxWithCreateElementValue) return false;
|
|
641
633
|
switch (node.callee.type) {
|
|
@@ -643,19 +635,10 @@ function isJsxLike(code, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
|
643
635
|
case AST_NODE_TYPES.MemberExpression: return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "createElement";
|
|
644
636
|
}
|
|
645
637
|
return false;
|
|
646
|
-
case AST_NODE_TYPES.Identifier:
|
|
647
|
-
|
|
648
|
-
if (name === "undefined") return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue);
|
|
638
|
+
case AST_NODE_TYPES.Identifier:
|
|
639
|
+
if (node.name === "undefined") return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue);
|
|
649
640
|
if (ast.isJSXTagNameExpression(node)) return true;
|
|
650
|
-
|
|
651
|
-
if (v == null) return unit;
|
|
652
|
-
const def = v.defs.at(0);
|
|
653
|
-
if (def == null) return unit;
|
|
654
|
-
if ("init" in def.node && def.node.init != null && !("declarations" in def.node.init)) return def.node.init;
|
|
655
|
-
return unit;
|
|
656
|
-
}
|
|
657
|
-
return isJsxLike(code, resolve(findVariable(code.getScope(node), name)), hint);
|
|
658
|
-
}
|
|
641
|
+
return isJsxLike(context, resolve(context, node), hint);
|
|
659
642
|
}
|
|
660
643
|
return false;
|
|
661
644
|
}
|
|
@@ -713,7 +696,7 @@ function isJsxFragmentElement(context, node, jsxConfig) {
|
|
|
713
696
|
* @param node The starting AST node
|
|
714
697
|
* @param test Optional predicate function to test if the attribute meets criteria
|
|
715
698
|
* Defaults to always returning true (matches any attribute)
|
|
716
|
-
* @returns The first matching JSX attribute node found when traversing upwards, or
|
|
699
|
+
* @returns The first matching JSX attribute node found when traversing upwards, or null
|
|
717
700
|
*/
|
|
718
701
|
function findParentJsxAttribute(node, test = constTrue) {
|
|
719
702
|
const guard = (node) => {
|
|
@@ -901,7 +884,7 @@ function isComponentWrapperCallbackLoose(context, node) {
|
|
|
901
884
|
* Get function component identifier from `const Component = memo(() => {});`
|
|
902
885
|
* @param context The rule context
|
|
903
886
|
* @param node The function node to analyze
|
|
904
|
-
* @returns The function identifier or `
|
|
887
|
+
* @returns The function identifier or `null` if not found
|
|
905
888
|
*/
|
|
906
889
|
function getFunctionComponentId(context, node) {
|
|
907
890
|
const functionId = ast.getFunctionId(node);
|
|
@@ -909,7 +892,7 @@ function getFunctionComponentId(context, node) {
|
|
|
909
892
|
const { parent } = node;
|
|
910
893
|
if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.id;
|
|
911
894
|
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;
|
|
912
|
-
return
|
|
895
|
+
return null;
|
|
913
896
|
}
|
|
914
897
|
|
|
915
898
|
//#endregion
|
|
@@ -1049,14 +1032,14 @@ function useComponentCollector(context, options = {}) {
|
|
|
1049
1032
|
const functionEntries = [];
|
|
1050
1033
|
const components = /* @__PURE__ */ new Map();
|
|
1051
1034
|
const getText = (n) => context.sourceCode.getText(n);
|
|
1052
|
-
const getCurrentEntry = () => functionEntries.at(-1);
|
|
1035
|
+
const getCurrentEntry = () => functionEntries.at(-1) ?? null;
|
|
1053
1036
|
const onFunctionEnter = (node) => {
|
|
1054
1037
|
const key = idGen$1.next();
|
|
1055
1038
|
const exp = ast.findParentNode(node, (n) => n.type === AST_NODE_TYPES.ExportDefaultDeclaration);
|
|
1056
1039
|
const isExportDefault = exp != null;
|
|
1057
1040
|
const isExportDefaultDeclaration = exp != null && ast.getUnderlyingExpression(exp.declaration) === node;
|
|
1058
1041
|
const id = getFunctionComponentId(context, node);
|
|
1059
|
-
const name = id == null ?
|
|
1042
|
+
const name = id == null ? null : ast.getFullyQualifiedName(id, getText);
|
|
1060
1043
|
const initPath = ast.getFunctionInitPath(node);
|
|
1061
1044
|
const directives = ast.getFunctionDirectives(node);
|
|
1062
1045
|
const entry = {
|
|
@@ -1065,7 +1048,7 @@ function useComponentCollector(context, options = {}) {
|
|
|
1065
1048
|
kind: "function-component",
|
|
1066
1049
|
name,
|
|
1067
1050
|
directives,
|
|
1068
|
-
displayName:
|
|
1051
|
+
displayName: null,
|
|
1069
1052
|
flag: getComponentFlagFromInitPath(initPath),
|
|
1070
1053
|
hint,
|
|
1071
1054
|
hookCalls: [],
|
|
@@ -1103,13 +1086,13 @@ function useComponentCollector(context, options = {}) {
|
|
|
1103
1086
|
if (body.type === AST_NODE_TYPES.BlockStatement) return;
|
|
1104
1087
|
entry.rets.push(body);
|
|
1105
1088
|
if (!entry.isComponentDefinition) return;
|
|
1106
|
-
if (!components.has(entry.key) && !isJsxLike(context
|
|
1089
|
+
if (!components.has(entry.key) && !isJsxLike(context, body, hint)) return;
|
|
1107
1090
|
components.set(entry.key, entry);
|
|
1108
1091
|
},
|
|
1109
1092
|
...collectDisplayName ? { [ast.SEL_DISPLAY_NAME_ASSIGNMENT_EXPRESSION](node) {
|
|
1110
1093
|
const { left, right } = node;
|
|
1111
1094
|
if (left.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1112
|
-
const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name :
|
|
1095
|
+
const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name : null;
|
|
1113
1096
|
const component = [...components.values()].findLast(({ name }) => name != null && name === componentName);
|
|
1114
1097
|
if (component == null) return;
|
|
1115
1098
|
component.displayName = right;
|
|
@@ -1128,7 +1111,7 @@ function useComponentCollector(context, options = {}) {
|
|
|
1128
1111
|
entry.rets.push(node.argument);
|
|
1129
1112
|
if (!entry.isComponentDefinition) return;
|
|
1130
1113
|
const { argument } = node;
|
|
1131
|
-
if (!components.has(entry.key) && !isJsxLike(context
|
|
1114
|
+
if (!components.has(entry.key) && !isJsxLike(context, argument, hint)) return;
|
|
1132
1115
|
components.set(entry.key, entry);
|
|
1133
1116
|
}
|
|
1134
1117
|
}
|
|
@@ -1153,14 +1136,14 @@ function useComponentCollectorLegacy(context) {
|
|
|
1153
1136
|
if (!isClassComponent(node)) return;
|
|
1154
1137
|
const id = ast.getClassId(node);
|
|
1155
1138
|
const key = idGen.next();
|
|
1156
|
-
const name = id == null ?
|
|
1139
|
+
const name = id == null ? null : ast.getFullyQualifiedName(id, getText);
|
|
1157
1140
|
const flag = isPureComponent(node) ? ComponentFlag.PureComponent : ComponentFlag.None;
|
|
1158
1141
|
components.set(key, {
|
|
1159
1142
|
id,
|
|
1160
1143
|
key,
|
|
1161
1144
|
kind: "class-component",
|
|
1162
1145
|
name,
|
|
1163
|
-
displayName:
|
|
1146
|
+
displayName: null,
|
|
1164
1147
|
flag,
|
|
1165
1148
|
hint: 0n,
|
|
1166
1149
|
methods: [],
|
|
@@ -1208,7 +1191,7 @@ function isInitializedFromRef(name, initialScope) {
|
|
|
1208
1191
|
* Get the init expression of a ref variable
|
|
1209
1192
|
* @param name The variable name
|
|
1210
1193
|
* @param initialScope The initial scope
|
|
1211
|
-
* @returns The init expression node if the variable is derived from a ref, or
|
|
1194
|
+
* @returns The init expression node if the variable is derived from a ref, or null otherwise
|
|
1212
1195
|
*/
|
|
1213
1196
|
function getRefInit(name, initialScope) {
|
|
1214
1197
|
for (const { node } of findVariable(initialScope, name)?.defs ?? []) {
|
|
@@ -1220,7 +1203,7 @@ function getRefInit(name, initialScope) {
|
|
|
1220
1203
|
case init.type === AST_NODE_TYPES$1.CallExpression && isUseRefCall(init): return init;
|
|
1221
1204
|
}
|
|
1222
1205
|
}
|
|
1223
|
-
return
|
|
1206
|
+
return null;
|
|
1224
1207
|
}
|
|
1225
1208
|
|
|
1226
1209
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-react/core",
|
|
3
|
-
"version": "3.0.0-next.
|
|
3
|
+
"version": "3.0.0-next.64",
|
|
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": {
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"@typescript-eslint/types": "canary",
|
|
35
35
|
"@typescript-eslint/utils": "canary",
|
|
36
36
|
"ts-pattern": "^5.9.0",
|
|
37
|
-
"@eslint-react/
|
|
38
|
-
"@eslint-react/
|
|
39
|
-
"@eslint-react/
|
|
40
|
-
"@eslint-react/
|
|
37
|
+
"@eslint-react/ast": "3.0.0-next.64",
|
|
38
|
+
"@eslint-react/shared": "3.0.0-next.64",
|
|
39
|
+
"@eslint-react/eff": "3.0.0-next.64",
|
|
40
|
+
"@eslint-react/var": "3.0.0-next.64"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"tsdown": "^0.21.0-beta.2",
|