@eslint-react/var 3.0.0-next.9 → 3.0.0-rc.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 +70 -99
- package/dist/index.js +179 -243
- package/package.json +9 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,69 +1,7 @@
|
|
|
1
|
-
import { unit } from "@eslint-react/eff";
|
|
2
1
|
import { TSESTree } from "@typescript-eslint/types";
|
|
3
|
-
import { Scope, Variable } from "@typescript-eslint/scope-manager";
|
|
4
2
|
import { RuleContext } from "@eslint-react/shared";
|
|
5
3
|
|
|
6
|
-
//#region src/
|
|
7
|
-
/**
|
|
8
|
-
* Finds the enclosing assignment target (variable, property, etc.) for a given node
|
|
9
|
-
*
|
|
10
|
-
* @todo Verify correctness and completeness of this function
|
|
11
|
-
* @param node The starting node
|
|
12
|
-
* @returns The enclosing assignment target node, or undefined if not found
|
|
13
|
-
*/
|
|
14
|
-
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;
|
|
15
|
-
/**
|
|
16
|
-
* Type representing the possible assignment targets returned by `findEnclosingAssignmentTarget`
|
|
17
|
-
*/
|
|
18
|
-
type AssignmentTarget = ReturnType<typeof findEnclosingAssignmentTarget>;
|
|
19
|
-
/**
|
|
20
|
-
* Check if two assignment targets are equal
|
|
21
|
-
* Compares nodes directly or by their values
|
|
22
|
-
* @param context The rule context
|
|
23
|
-
* @param a The first node to compare
|
|
24
|
-
* @param b The second node to compare
|
|
25
|
-
* @returns True if the assignment targets are equal
|
|
26
|
-
* @internal
|
|
27
|
-
*/
|
|
28
|
-
declare function isAssignmentTargetEqual(context: RuleContext, a: TSESTree.Node, b: TSESTree.Node): boolean;
|
|
29
|
-
//#endregion
|
|
30
|
-
//#region src/var-definition.d.ts
|
|
31
|
-
/**
|
|
32
|
-
* Get the definition node of a variable at a specific definition index
|
|
33
|
-
* @param variable The variable to get the definition node from
|
|
34
|
-
* @param at The index of the definition to retrieve (negative index supported)
|
|
35
|
-
* @returns The definition node or unit if not found
|
|
36
|
-
*/
|
|
37
|
-
declare function getVariableDefinitionNode(variable: Variable | unit, at: number): unit | TSESTree.ClassDeclaration | TSESTree.ClassDeclarationWithName | TSESTree.ClassDeclarationWithOptionalName | TSESTree.Expression | TSESTree.FunctionDeclaration | TSESTree.FunctionDeclarationWithName | TSESTree.FunctionDeclarationWithOptionalName;
|
|
38
|
-
/**
|
|
39
|
-
* Get the definition node of a variable at a specific definition index (loose version)
|
|
40
|
-
* Also returns the function node if the definition is a parameter
|
|
41
|
-
* @param variable The variable to get the definition node from
|
|
42
|
-
* @param at The index of the definition to retrieve
|
|
43
|
-
* @returns The definition node or unit if not found
|
|
44
|
-
*/
|
|
45
|
-
declare function getVariableDefinitionNodeLoose(variable: Variable | unit, at: number): unit | TSESTree.ClassDeclaration | TSESTree.ClassDeclarationWithName | TSESTree.ClassDeclarationWithOptionalName | TSESTree.Expression | TSESTree.FunctionDeclaration | TSESTree.FunctionDeclarationWithName | TSESTree.FunctionDeclarationWithOptionalName;
|
|
46
|
-
//#endregion
|
|
47
|
-
//#region src/var-import-source.d.ts
|
|
48
|
-
/**
|
|
49
|
-
* Find the import source of a variable
|
|
50
|
-
* @param name The variable name
|
|
51
|
-
* @param initialScope The initial scope to search
|
|
52
|
-
* @returns The import source or undefined if not found
|
|
53
|
-
*/
|
|
54
|
-
declare function findImportSource(name: string, initialScope: Scope): string | undefined;
|
|
55
|
-
//#endregion
|
|
56
|
-
//#region src/var-node-equality.d.ts
|
|
57
|
-
/**
|
|
58
|
-
* Determine whether node value equals to another node value
|
|
59
|
-
* @param a node to compare
|
|
60
|
-
* @param b node to compare
|
|
61
|
-
* @param initialScopes initial scopes of the two nodes
|
|
62
|
-
* @returns `true` if node value equal
|
|
63
|
-
*/
|
|
64
|
-
declare function isNodeEqual(a: TSESTree.Node, b: TSESTree.Node, initialScopes: [aScope: Scope, bScope: Scope]): boolean;
|
|
65
|
-
//#endregion
|
|
66
|
-
//#region src/var-object-type.d.ts
|
|
4
|
+
//#region src/compute-object-type.d.ts
|
|
67
5
|
/**
|
|
68
6
|
* Represents the type classification of an object node
|
|
69
7
|
*/
|
|
@@ -72,10 +10,10 @@ type ObjectType = {
|
|
|
72
10
|
node: TSESTree.JSXElement | TSESTree.JSXFragment;
|
|
73
11
|
} | {
|
|
74
12
|
kind: "array";
|
|
75
|
-
node: TSESTree.ArrayExpression;
|
|
13
|
+
node: TSESTree.ArrayExpression | TSESTree.CallExpression;
|
|
76
14
|
} | {
|
|
77
15
|
kind: "plain";
|
|
78
|
-
node: TSESTree.ObjectExpression;
|
|
16
|
+
node: TSESTree.ObjectExpression | TSESTree.CallExpression;
|
|
79
17
|
} | {
|
|
80
18
|
kind: "class";
|
|
81
19
|
node: TSESTree.ClassExpression;
|
|
@@ -87,57 +25,90 @@ type ObjectType = {
|
|
|
87
25
|
node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression;
|
|
88
26
|
} | {
|
|
89
27
|
kind: "regexp";
|
|
90
|
-
node: TSESTree.RegExpLiteral;
|
|
28
|
+
node: TSESTree.RegExpLiteral | TSESTree.CallExpression;
|
|
91
29
|
} | {
|
|
92
30
|
kind: "unknown";
|
|
93
31
|
node: TSESTree.Node;
|
|
94
|
-
reason
|
|
32
|
+
reason?: string;
|
|
95
33
|
};
|
|
96
34
|
/**
|
|
97
35
|
* Detect the ObjectType of a given node
|
|
36
|
+
* @param context The context of the rule
|
|
98
37
|
* @param node The node to check
|
|
99
|
-
* @param initialScope The initial scope to check for variable declarations
|
|
100
38
|
* @returns The ObjectType of the node, or undefined if not detected
|
|
101
39
|
*/
|
|
102
|
-
declare function
|
|
40
|
+
declare function computeObjectType(context: RuleContext, node: TSESTree.Node | null): ObjectType | null;
|
|
103
41
|
//#endregion
|
|
104
|
-
//#region src/
|
|
42
|
+
//#region src/find-enclosing-assignment-target.d.ts
|
|
43
|
+
/**
|
|
44
|
+
* Finds the enclosing assignment target (variable, property, etc.) for a given node
|
|
45
|
+
*
|
|
46
|
+
* @todo Verify correctness and completeness of this function
|
|
47
|
+
* @param node The starting node
|
|
48
|
+
* @returns The enclosing assignment target node, or null if not found
|
|
49
|
+
*/
|
|
50
|
+
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 | null;
|
|
105
51
|
/**
|
|
106
|
-
*
|
|
107
|
-
* Handles spread elements by recursively resolving the referenced object
|
|
108
|
-
* @param name The property name to find
|
|
109
|
-
* @param properties The array of properties to search
|
|
110
|
-
* @param initialScope The scope to use for variable resolution
|
|
111
|
-
* @param seen Set of already seen variable names to prevent circular references
|
|
112
|
-
* @returns The found property or unit if not found
|
|
52
|
+
* Type representing the possible assignment targets returned by `findEnclosingAssignmentTarget`
|
|
113
53
|
*/
|
|
114
|
-
|
|
54
|
+
type AssignmentTarget = ReturnType<typeof findEnclosingAssignmentTarget>;
|
|
115
55
|
//#endregion
|
|
116
|
-
//#region src/
|
|
56
|
+
//#region src/is-assignment-target-equal.d.ts
|
|
117
57
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* @
|
|
58
|
+
* Check if two assignment targets are equal
|
|
59
|
+
* Compares nodes directly or by their values
|
|
60
|
+
* @param context The rule context
|
|
61
|
+
* @param a The first node to compare
|
|
62
|
+
* @param b The second node to compare
|
|
63
|
+
* @returns True if the assignment targets are equal
|
|
64
|
+
* @internal
|
|
121
65
|
*/
|
|
122
|
-
declare function
|
|
66
|
+
declare function isAssignmentTargetEqual(context: RuleContext, a: TSESTree.Node, b: TSESTree.Node): boolean;
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/is-value-equal.d.ts
|
|
123
69
|
/**
|
|
124
|
-
*
|
|
125
|
-
* @param
|
|
126
|
-
* @
|
|
127
|
-
* @
|
|
128
|
-
* @
|
|
129
|
-
* @param initialScope The scope to start searching from
|
|
130
|
-
* @returns The found variable or unit if not found
|
|
70
|
+
* Determine whether node value equals to another node value
|
|
71
|
+
* @param context rule context
|
|
72
|
+
* @param a node to compare
|
|
73
|
+
* @param b node to compare
|
|
74
|
+
* @returns `true` if node value equal
|
|
131
75
|
*/
|
|
132
|
-
declare
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
};
|
|
76
|
+
declare function isValueEqual(context: RuleContext, a: TSESTree.Node, b: TSESTree.Node): boolean;
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/resolve.d.ts
|
|
136
79
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
80
|
+
* Resolves an identifier to the AST node that represents its value,
|
|
81
|
+
* suitable for use in ESLint rule analysis.
|
|
82
|
+
*
|
|
83
|
+
* The resolution follows these rules per definition type:
|
|
84
|
+
*
|
|
85
|
+
* | Definition type | `def.node` | Returns |
|
|
86
|
+
* |--------------------------|----------------------------------------------|------------------------------------|
|
|
87
|
+
* | `Variable` | `VariableDeclarator` | `def.node.init` (or `null`) |
|
|
88
|
+
* | `FunctionName` | `FunctionDeclaration` / `FunctionExpression` | `def.node` |
|
|
89
|
+
* | `ClassName` | `ClassDeclaration` / `ClassExpression` | `def.node` |
|
|
90
|
+
* | `Parameter` | containing function node | `def.node` (if a real function) |
|
|
91
|
+
* | `TSEnumName` | `TSEnumDeclaration` | `def.node` |
|
|
92
|
+
* | `TSEnumMember` | `TSEnumMember` | `def.node.initializer` (or `null`) |
|
|
93
|
+
* | `ImportBinding` | import specifier | `null` |
|
|
94
|
+
* | `CatchClause` | `CatchClause` | `null` |
|
|
95
|
+
* | `TSModuleName` | `TSModuleDeclaration` | `null` |
|
|
96
|
+
* | `Type` | type alias node | `null` |
|
|
97
|
+
* | `ImplicitGlobalVariable` | any node | `null` |
|
|
98
|
+
*
|
|
99
|
+
* @param context The ESLint rule context used for scope lookup.
|
|
100
|
+
* @param node The identifier to resolve.
|
|
101
|
+
* @param options Optional settings:
|
|
102
|
+
* - `at`: Index of the definition to resolve (default: `0` for the first definition).
|
|
103
|
+
* - `localOnly`: If `true`, only consider variables declared in the same scope as the identifier
|
|
104
|
+
* will miss variables declared in an outer scope). When `false` (default), traverse the scope
|
|
105
|
+
* chain upward via `findVariable` so that references to outer-scope bindings are resolved
|
|
106
|
+
* correctly.
|
|
107
|
+
* @returns The resolved node, or `null` if the identifier cannot be resolved to a value node.
|
|
140
108
|
*/
|
|
141
|
-
declare function
|
|
109
|
+
declare function resolve(context: RuleContext, node: TSESTree.Identifier, options?: Partial<{
|
|
110
|
+
at: number;
|
|
111
|
+
localOnly: boolean;
|
|
112
|
+
}>): TSESTree.Node | null;
|
|
142
113
|
//#endregion
|
|
143
|
-
export { AssignmentTarget, ObjectType,
|
|
114
|
+
export { AssignmentTarget, ObjectType, computeObjectType, findEnclosingAssignmentTarget, isAssignmentTargetEqual, isValueEqual, resolve };
|
package/dist/index.js
CHANGED
|
@@ -1,87 +1,184 @@
|
|
|
1
1
|
import * as ast from "@eslint-react/ast";
|
|
2
|
-
import {
|
|
2
|
+
import { isIdentifier } from "@eslint-react/ast";
|
|
3
|
+
import { DefinitionType } from "@typescript-eslint/scope-manager";
|
|
3
4
|
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
4
|
-
import
|
|
5
|
-
import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
6
|
-
import { DefinitionType, ScopeType } from "@typescript-eslint/scope-manager";
|
|
7
|
-
import { P, match } from "ts-pattern";
|
|
5
|
+
import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
8
6
|
|
|
9
|
-
//#region src/
|
|
7
|
+
//#region src/resolve.ts
|
|
10
8
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Resolves an identifier to the AST node that represents its value,
|
|
10
|
+
* suitable for use in ESLint rule analysis.
|
|
11
|
+
*
|
|
12
|
+
* The resolution follows these rules per definition type:
|
|
13
|
+
*
|
|
14
|
+
* | Definition type | `def.node` | Returns |
|
|
15
|
+
* |--------------------------|----------------------------------------------|------------------------------------|
|
|
16
|
+
* | `Variable` | `VariableDeclarator` | `def.node.init` (or `null`) |
|
|
17
|
+
* | `FunctionName` | `FunctionDeclaration` / `FunctionExpression` | `def.node` |
|
|
18
|
+
* | `ClassName` | `ClassDeclaration` / `ClassExpression` | `def.node` |
|
|
19
|
+
* | `Parameter` | containing function node | `def.node` (if a real function) |
|
|
20
|
+
* | `TSEnumName` | `TSEnumDeclaration` | `def.node` |
|
|
21
|
+
* | `TSEnumMember` | `TSEnumMember` | `def.node.initializer` (or `null`) |
|
|
22
|
+
* | `ImportBinding` | import specifier | `null` |
|
|
23
|
+
* | `CatchClause` | `CatchClause` | `null` |
|
|
24
|
+
* | `TSModuleName` | `TSModuleDeclaration` | `null` |
|
|
25
|
+
* | `Type` | type alias node | `null` |
|
|
26
|
+
* | `ImplicitGlobalVariable` | any node | `null` |
|
|
27
|
+
*
|
|
28
|
+
* @param context The ESLint rule context used for scope lookup.
|
|
29
|
+
* @param node The identifier to resolve.
|
|
30
|
+
* @param options Optional settings:
|
|
31
|
+
* - `at`: Index of the definition to resolve (default: `0` for the first definition).
|
|
32
|
+
* - `localOnly`: If `true`, only consider variables declared in the same scope as the identifier
|
|
33
|
+
* will miss variables declared in an outer scope). When `false` (default), traverse the scope
|
|
34
|
+
* chain upward via `findVariable` so that references to outer-scope bindings are resolved
|
|
35
|
+
* correctly.
|
|
36
|
+
* @returns The resolved node, or `null` if the identifier cannot be resolved to a value node.
|
|
15
37
|
*/
|
|
16
|
-
function
|
|
17
|
-
|
|
38
|
+
function resolve(context, node, options) {
|
|
39
|
+
const { at = 0, localOnly = false } = options ?? {};
|
|
40
|
+
const scope = context.sourceCode.getScope(node);
|
|
41
|
+
const variable = localOnly ? scope.set.get(node.name) : findVariable(scope, node);
|
|
42
|
+
if (variable == null) return null;
|
|
18
43
|
const def = variable.defs.at(at);
|
|
19
|
-
if (def == null) return
|
|
20
|
-
switch (
|
|
21
|
-
case
|
|
22
|
-
case
|
|
23
|
-
case
|
|
24
|
-
|
|
44
|
+
if (def == null) return null;
|
|
45
|
+
switch (def.type) {
|
|
46
|
+
case DefinitionType.FunctionName: return def.node;
|
|
47
|
+
case DefinitionType.ClassName: return def.node;
|
|
48
|
+
case DefinitionType.Variable: {
|
|
49
|
+
const { init } = def.node;
|
|
50
|
+
if (init == null) return null;
|
|
51
|
+
if ("declarations" in init) return null;
|
|
52
|
+
return init;
|
|
53
|
+
}
|
|
54
|
+
case DefinitionType.Parameter: return ast.isFunction(def.node) ? def.node : null;
|
|
55
|
+
case DefinitionType.TSEnumName: return def.node;
|
|
56
|
+
case DefinitionType.TSEnumMember: return def.node.initializer ?? null;
|
|
57
|
+
case DefinitionType.ImportBinding: return null;
|
|
58
|
+
case DefinitionType.CatchClause: return null;
|
|
59
|
+
case DefinitionType.TSModuleName: return null;
|
|
60
|
+
case DefinitionType.Type: return null;
|
|
61
|
+
case DefinitionType.ImplicitGlobalVariable: return null;
|
|
62
|
+
default: return null;
|
|
25
63
|
}
|
|
26
64
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Get the definition node of a variable at a specific definition index (loose version)
|
|
29
|
-
* Also returns the function node if the definition is a parameter
|
|
30
|
-
* @param variable The variable to get the definition node from
|
|
31
|
-
* @param at The index of the definition to retrieve
|
|
32
|
-
* @returns The definition node or unit if not found
|
|
33
|
-
*/
|
|
34
|
-
function getVariableDefinitionNodeLoose(variable, at) {
|
|
35
|
-
if (variable == null) return unit;
|
|
36
|
-
const node = getVariableDefinitionNode(variable, at);
|
|
37
|
-
if (node != null) return node;
|
|
38
|
-
const def = variable.defs.at(at);
|
|
39
|
-
if (def?.type === DefinitionType.Parameter && ast.isFunction(def.node)) return def.node;
|
|
40
|
-
return unit;
|
|
41
|
-
}
|
|
42
65
|
|
|
43
66
|
//#endregion
|
|
44
|
-
//#region src/
|
|
67
|
+
//#region src/compute-object-type.ts
|
|
45
68
|
/**
|
|
46
|
-
*
|
|
47
|
-
* @param
|
|
48
|
-
* @
|
|
69
|
+
* Detect the ObjectType of a given node
|
|
70
|
+
* @param context The context of the rule
|
|
71
|
+
* @param node The node to check
|
|
72
|
+
* @returns The ObjectType of the node, or undefined if not detected
|
|
49
73
|
*/
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
74
|
+
function computeObjectType(context, node) {
|
|
75
|
+
if (node == null) return null;
|
|
76
|
+
switch (node.type) {
|
|
77
|
+
case AST_NODE_TYPES.JSXElement:
|
|
78
|
+
case AST_NODE_TYPES.JSXFragment: return {
|
|
79
|
+
kind: "jsx",
|
|
80
|
+
node
|
|
81
|
+
};
|
|
82
|
+
case AST_NODE_TYPES.ArrayExpression: return {
|
|
83
|
+
kind: "array",
|
|
84
|
+
node
|
|
85
|
+
};
|
|
86
|
+
case AST_NODE_TYPES.ObjectExpression: return {
|
|
87
|
+
kind: "plain",
|
|
88
|
+
node
|
|
89
|
+
};
|
|
90
|
+
case AST_NODE_TYPES.ClassExpression: return {
|
|
91
|
+
kind: "class",
|
|
92
|
+
node
|
|
93
|
+
};
|
|
94
|
+
case AST_NODE_TYPES.NewExpression:
|
|
95
|
+
case AST_NODE_TYPES.ThisExpression: return {
|
|
96
|
+
kind: "instance",
|
|
97
|
+
node
|
|
98
|
+
};
|
|
99
|
+
case AST_NODE_TYPES.FunctionDeclaration:
|
|
100
|
+
case AST_NODE_TYPES.FunctionExpression:
|
|
101
|
+
case AST_NODE_TYPES.ArrowFunctionExpression: return {
|
|
102
|
+
kind: "function",
|
|
103
|
+
node
|
|
104
|
+
};
|
|
105
|
+
case AST_NODE_TYPES.Literal:
|
|
106
|
+
if ("regex" in node) return {
|
|
107
|
+
kind: "regexp",
|
|
108
|
+
node
|
|
109
|
+
};
|
|
110
|
+
return null;
|
|
111
|
+
case AST_NODE_TYPES.Identifier: {
|
|
112
|
+
if ((context.sourceCode.getScope(node).set.get(node.name)?.defs.at(-1))?.type === DefinitionType.Parameter) return null;
|
|
113
|
+
const initNode = resolve(context, node, {
|
|
114
|
+
at: -1,
|
|
115
|
+
localOnly: true
|
|
116
|
+
});
|
|
117
|
+
if (initNode == null) return null;
|
|
118
|
+
return computeObjectType(context, initNode);
|
|
119
|
+
}
|
|
120
|
+
case AST_NODE_TYPES.MemberExpression:
|
|
121
|
+
if (!("object" in node)) return null;
|
|
122
|
+
return computeObjectType(context, node.object);
|
|
123
|
+
case AST_NODE_TYPES.AssignmentExpression:
|
|
124
|
+
case AST_NODE_TYPES.AssignmentPattern:
|
|
125
|
+
if (!("right" in node)) return null;
|
|
126
|
+
return computeObjectType(context, node.right);
|
|
127
|
+
case AST_NODE_TYPES.LogicalExpression: return computeObjectType(context, node.right);
|
|
128
|
+
case AST_NODE_TYPES.ConditionalExpression: return computeObjectType(context, node.consequent) ?? computeObjectType(context, node.alternate);
|
|
129
|
+
case AST_NODE_TYPES.SequenceExpression:
|
|
130
|
+
if (node.expressions.length === 0) return null;
|
|
131
|
+
return computeObjectType(context, node.expressions[node.expressions.length - 1] ?? null);
|
|
132
|
+
case AST_NODE_TYPES.CallExpression:
|
|
133
|
+
switch (true) {
|
|
134
|
+
case isIdentifier(node.callee, "Boolean"): return null;
|
|
135
|
+
case isIdentifier(node.callee, "String"): return null;
|
|
136
|
+
case isIdentifier(node.callee, "Number"): return null;
|
|
137
|
+
case isIdentifier(node.callee, "Object"): return {
|
|
138
|
+
kind: "plain",
|
|
139
|
+
node
|
|
140
|
+
};
|
|
141
|
+
case isIdentifier(node.callee, "Array"): return {
|
|
142
|
+
kind: "array",
|
|
143
|
+
node
|
|
144
|
+
};
|
|
145
|
+
case isIdentifier(node.callee, "RegExp"): return {
|
|
146
|
+
kind: "regexp",
|
|
147
|
+
node
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
kind: "unknown",
|
|
152
|
+
node,
|
|
153
|
+
reason: "call-expression"
|
|
154
|
+
};
|
|
155
|
+
default:
|
|
156
|
+
if (!("expression" in node) || typeof node.expression !== "object") return null;
|
|
157
|
+
return computeObjectType(context, node.expression);
|
|
56
158
|
}
|
|
57
|
-
return variables.reverse();
|
|
58
159
|
}
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/find-enclosing-assignment-target.ts
|
|
59
163
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* @
|
|
63
|
-
* @
|
|
64
|
-
* @
|
|
65
|
-
* @param initialScope The scope to start searching from
|
|
66
|
-
* @returns The found variable or unit if not found
|
|
67
|
-
*/
|
|
68
|
-
const findVariable = dual(2, (nameOrNode, initialScope) => {
|
|
69
|
-
if (nameOrNode == null) return unit;
|
|
70
|
-
return astUtils.findVariable(initialScope, nameOrNode) ?? unit;
|
|
71
|
-
});
|
|
72
|
-
/**
|
|
73
|
-
* Get all child scopes recursively from a given scope
|
|
74
|
-
* @param scope The scope to get child scopes from
|
|
75
|
-
* @returns Array of all child scopes including the input scope
|
|
164
|
+
* Finds the enclosing assignment target (variable, property, etc.) for a given node
|
|
165
|
+
*
|
|
166
|
+
* @todo Verify correctness and completeness of this function
|
|
167
|
+
* @param node The starting node
|
|
168
|
+
* @returns The enclosing assignment target node, or null if not found
|
|
76
169
|
*/
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
170
|
+
function findEnclosingAssignmentTarget(node) {
|
|
171
|
+
switch (true) {
|
|
172
|
+
case node.type === AST_NODE_TYPES.VariableDeclarator: return node.id;
|
|
173
|
+
case node.type === AST_NODE_TYPES.AssignmentExpression: return node.left;
|
|
174
|
+
case node.type === AST_NODE_TYPES.PropertyDefinition: return node.key;
|
|
175
|
+
case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node: return null;
|
|
176
|
+
default: return findEnclosingAssignmentTarget(node.parent);
|
|
177
|
+
}
|
|
81
178
|
}
|
|
82
179
|
|
|
83
180
|
//#endregion
|
|
84
|
-
//#region src/
|
|
181
|
+
//#region src/is-value-equal.ts
|
|
85
182
|
const thisBlockTypes = [
|
|
86
183
|
AST_NODE_TYPES.FunctionDeclaration,
|
|
87
184
|
AST_NODE_TYPES.FunctionExpression,
|
|
@@ -90,35 +187,35 @@ const thisBlockTypes = [
|
|
|
90
187
|
];
|
|
91
188
|
/**
|
|
92
189
|
* Determine whether node value equals to another node value
|
|
190
|
+
* @param context rule context
|
|
93
191
|
* @param a node to compare
|
|
94
192
|
* @param b node to compare
|
|
95
|
-
* @param initialScopes initial scopes of the two nodes
|
|
96
193
|
* @returns `true` if node value equal
|
|
97
194
|
*/
|
|
98
|
-
function
|
|
195
|
+
function isValueEqual(context, a, b) {
|
|
99
196
|
a = ast.isTypeExpression(a) ? ast.getUnderlyingExpression(a) : a;
|
|
100
197
|
b = ast.isTypeExpression(b) ? ast.getUnderlyingExpression(b) : b;
|
|
101
|
-
const [aScope, bScope] =
|
|
198
|
+
const [aScope, bScope] = [context.sourceCode.getScope(a), context.sourceCode.getScope(b)];
|
|
102
199
|
switch (true) {
|
|
103
200
|
case a === b: return true;
|
|
104
201
|
case a.type === AST_NODE_TYPES.Literal && b.type === AST_NODE_TYPES.Literal: return a.value === b.value;
|
|
105
202
|
case a.type === AST_NODE_TYPES.TemplateElement && b.type === AST_NODE_TYPES.TemplateElement: return a.value.cooked === b.value.cooked;
|
|
106
203
|
case a.type === AST_NODE_TYPES.Identifier && b.type === AST_NODE_TYPES.Identifier: {
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const
|
|
204
|
+
const aDefNode = resolve(context, a);
|
|
205
|
+
const bDefNode = resolve(context, b);
|
|
206
|
+
const aDefNodeParent = aDefNode?.parent;
|
|
207
|
+
const bDefNodeParent = bDefNode?.parent;
|
|
208
|
+
const aVar = findVariable(aScope, a);
|
|
209
|
+
const bVar = findVariable(bScope, b);
|
|
113
210
|
const aDef = aVar?.defs.at(0);
|
|
114
211
|
const bDef = bVar?.defs.at(0);
|
|
115
212
|
const aDefParentParent = aDef?.parent?.parent;
|
|
116
213
|
const bDefParentParent = bDef?.parent?.parent;
|
|
117
214
|
switch (true) {
|
|
118
|
-
case
|
|
119
|
-
if (!ast.isNodeEqual(
|
|
120
|
-
const aParams =
|
|
121
|
-
const bParams =
|
|
215
|
+
case aDefNodeParent?.type === AST_NODE_TYPES.CallExpression && bDefNodeParent?.type === AST_NODE_TYPES.CallExpression && ast.isFunction(aDefNode) && ast.isFunction(bDefNode): {
|
|
216
|
+
if (!ast.isNodeEqual(aDefNodeParent.callee, bDefNodeParent.callee)) return false;
|
|
217
|
+
const aParams = aDefNode.params;
|
|
218
|
+
const bParams = bDefNode.params;
|
|
122
219
|
const aPos = aParams.findIndex((x) => ast.isNodeEqual(x, a));
|
|
123
220
|
const bPos = bParams.findIndex((x) => ast.isNodeEqual(x, b));
|
|
124
221
|
return aPos !== -1 && bPos !== -1 && aPos === bPos;
|
|
@@ -134,7 +231,7 @@ function isNodeEqual(a, b, initialScopes) {
|
|
|
134
231
|
default: return aVar != null && bVar != null && aVar === bVar;
|
|
135
232
|
}
|
|
136
233
|
}
|
|
137
|
-
case a.type === AST_NODE_TYPES.MemberExpression && b.type === AST_NODE_TYPES.MemberExpression: return ast.isNodeEqual(a.property, b.property) &&
|
|
234
|
+
case a.type === AST_NODE_TYPES.MemberExpression && b.type === AST_NODE_TYPES.MemberExpression: return ast.isNodeEqual(a.property, b.property) && isValueEqual(context, a.object, b.object);
|
|
138
235
|
case a.type === AST_NODE_TYPES.ThisExpression && b.type === AST_NODE_TYPES.ThisExpression:
|
|
139
236
|
if (aScope.block === bScope.block) return true;
|
|
140
237
|
return ast.findParentNode(a, ast.isOneOf(thisBlockTypes)) === ast.findParentNode(b, ast.isOneOf(thisBlockTypes));
|
|
@@ -147,24 +244,7 @@ function isNodeEqual(a, b, initialScopes) {
|
|
|
147
244
|
}
|
|
148
245
|
|
|
149
246
|
//#endregion
|
|
150
|
-
//#region src/
|
|
151
|
-
/** eslint-disable jsdoc/require-param */
|
|
152
|
-
/**
|
|
153
|
-
* Finds the enclosing assignment target (variable, property, etc.) for a given node
|
|
154
|
-
*
|
|
155
|
-
* @todo Verify correctness and completeness of this function
|
|
156
|
-
* @param node The starting node
|
|
157
|
-
* @returns The enclosing assignment target node, or undefined if not found
|
|
158
|
-
*/
|
|
159
|
-
function findEnclosingAssignmentTarget(node) {
|
|
160
|
-
switch (true) {
|
|
161
|
-
case node.type === AST_NODE_TYPES.VariableDeclarator: return node.id;
|
|
162
|
-
case node.type === AST_NODE_TYPES.AssignmentExpression: return node.left;
|
|
163
|
-
case node.type === AST_NODE_TYPES.PropertyDefinition: return node.key;
|
|
164
|
-
case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node: return unit;
|
|
165
|
-
default: return findEnclosingAssignmentTarget(node.parent);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
247
|
+
//#region src/is-assignment-target-equal.ts
|
|
168
248
|
/**
|
|
169
249
|
* Check if two assignment targets are equal
|
|
170
250
|
* Compares nodes directly or by their values
|
|
@@ -175,152 +255,8 @@ function findEnclosingAssignmentTarget(node) {
|
|
|
175
255
|
* @internal
|
|
176
256
|
*/
|
|
177
257
|
function isAssignmentTargetEqual(context, a, b) {
|
|
178
|
-
return ast.isNodeEqual(a, b) ||
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
//#endregion
|
|
182
|
-
//#region src/var-import-source.ts
|
|
183
|
-
/**
|
|
184
|
-
* Get the arguments of a require expression
|
|
185
|
-
* @param node The node to match
|
|
186
|
-
* @returns The require expression arguments or undefined if the node is not a require expression
|
|
187
|
-
*/
|
|
188
|
-
function getRequireExpressionArguments(node) {
|
|
189
|
-
return match(node).with({
|
|
190
|
-
type: AST_NODE_TYPES.CallExpression,
|
|
191
|
-
arguments: P.select(),
|
|
192
|
-
callee: {
|
|
193
|
-
type: AST_NODE_TYPES.Identifier,
|
|
194
|
-
name: "require"
|
|
195
|
-
}
|
|
196
|
-
}, identity).with({
|
|
197
|
-
type: AST_NODE_TYPES.MemberExpression,
|
|
198
|
-
object: P.select()
|
|
199
|
-
}, getRequireExpressionArguments).otherwise(() => null);
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Find the import source of a variable
|
|
203
|
-
* @param name The variable name
|
|
204
|
-
* @param initialScope The initial scope to search
|
|
205
|
-
* @returns The import source or undefined if not found
|
|
206
|
-
*/
|
|
207
|
-
function findImportSource(name, initialScope) {
|
|
208
|
-
const latestDef = findVariable(name, initialScope)?.defs.at(-1);
|
|
209
|
-
if (latestDef == null) return unit;
|
|
210
|
-
const { node, parent } = latestDef;
|
|
211
|
-
if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
|
|
212
|
-
const { init } = node;
|
|
213
|
-
if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) return findImportSource(init.object.name, initialScope);
|
|
214
|
-
if (init.type === AST_NODE_TYPES.Identifier) return findImportSource(init.name, initialScope);
|
|
215
|
-
const arg0 = getRequireExpressionArguments(init)?.[0];
|
|
216
|
-
if (arg0 == null || !ast.isLiteral(arg0, "string")) return unit;
|
|
217
|
-
return arg0.value;
|
|
218
|
-
}
|
|
219
|
-
if (parent?.type === AST_NODE_TYPES.ImportDeclaration) return parent.source.value;
|
|
220
|
-
return unit;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
//#endregion
|
|
224
|
-
//#region src/var-object-type.ts
|
|
225
|
-
/**
|
|
226
|
-
* Detect the ObjectType of a given node
|
|
227
|
-
* @param node The node to check
|
|
228
|
-
* @param initialScope The initial scope to check for variable declarations
|
|
229
|
-
* @returns The ObjectType of the node, or undefined if not detected
|
|
230
|
-
*/
|
|
231
|
-
function getObjectType(node, initialScope) {
|
|
232
|
-
if (node == null) return unit;
|
|
233
|
-
switch (node.type) {
|
|
234
|
-
case AST_NODE_TYPES.JSXElement:
|
|
235
|
-
case AST_NODE_TYPES.JSXFragment: return {
|
|
236
|
-
kind: "jsx",
|
|
237
|
-
node
|
|
238
|
-
};
|
|
239
|
-
case AST_NODE_TYPES.ArrayExpression: return {
|
|
240
|
-
kind: "array",
|
|
241
|
-
node
|
|
242
|
-
};
|
|
243
|
-
case AST_NODE_TYPES.ObjectExpression: return {
|
|
244
|
-
kind: "plain",
|
|
245
|
-
node
|
|
246
|
-
};
|
|
247
|
-
case AST_NODE_TYPES.ClassExpression: return {
|
|
248
|
-
kind: "class",
|
|
249
|
-
node
|
|
250
|
-
};
|
|
251
|
-
case AST_NODE_TYPES.NewExpression:
|
|
252
|
-
case AST_NODE_TYPES.ThisExpression: return {
|
|
253
|
-
kind: "instance",
|
|
254
|
-
node
|
|
255
|
-
};
|
|
256
|
-
case AST_NODE_TYPES.FunctionDeclaration:
|
|
257
|
-
case AST_NODE_TYPES.FunctionExpression:
|
|
258
|
-
case AST_NODE_TYPES.ArrowFunctionExpression: return {
|
|
259
|
-
kind: "function",
|
|
260
|
-
node
|
|
261
|
-
};
|
|
262
|
-
case AST_NODE_TYPES.Literal:
|
|
263
|
-
if ("regex" in node) return {
|
|
264
|
-
kind: "regexp",
|
|
265
|
-
node
|
|
266
|
-
};
|
|
267
|
-
return unit;
|
|
268
|
-
case AST_NODE_TYPES.Identifier:
|
|
269
|
-
if (!("name" in node) || typeof node.name !== "string") return unit;
|
|
270
|
-
return getObjectType(getVariableDefinitionNode(initialScope.set.get(node.name), -1), initialScope);
|
|
271
|
-
case AST_NODE_TYPES.MemberExpression:
|
|
272
|
-
if (!("object" in node)) return unit;
|
|
273
|
-
return getObjectType(node.object, initialScope);
|
|
274
|
-
case AST_NODE_TYPES.AssignmentExpression:
|
|
275
|
-
case AST_NODE_TYPES.AssignmentPattern:
|
|
276
|
-
if (!("right" in node)) return unit;
|
|
277
|
-
return getObjectType(node.right, initialScope);
|
|
278
|
-
case AST_NODE_TYPES.LogicalExpression: return getObjectType(node.right, initialScope);
|
|
279
|
-
case AST_NODE_TYPES.ConditionalExpression: return getObjectType(node.consequent, initialScope) ?? getObjectType(node.alternate, initialScope);
|
|
280
|
-
case AST_NODE_TYPES.SequenceExpression:
|
|
281
|
-
if (node.expressions.length === 0) return unit;
|
|
282
|
-
return getObjectType(node.expressions[node.expressions.length - 1], initialScope);
|
|
283
|
-
case AST_NODE_TYPES.CallExpression: return {
|
|
284
|
-
kind: "unknown",
|
|
285
|
-
node,
|
|
286
|
-
reason: "call-expression"
|
|
287
|
-
};
|
|
288
|
-
default:
|
|
289
|
-
if (!("expression" in node) || typeof node.expression !== "object") return unit;
|
|
290
|
-
return getObjectType(node.expression, initialScope);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
//#endregion
|
|
295
|
-
//#region src/var-property.ts
|
|
296
|
-
/**
|
|
297
|
-
* Find a property by name in an array of properties
|
|
298
|
-
* Handles spread elements by recursively resolving the referenced object
|
|
299
|
-
* @param name The property name to find
|
|
300
|
-
* @param properties The array of properties to search
|
|
301
|
-
* @param initialScope The scope to use for variable resolution
|
|
302
|
-
* @param seen Set of already seen variable names to prevent circular references
|
|
303
|
-
* @returns The found property or unit if not found
|
|
304
|
-
*/
|
|
305
|
-
function findProperty(name, properties, initialScope, seen = /* @__PURE__ */ new Set()) {
|
|
306
|
-
return properties.findLast((prop) => {
|
|
307
|
-
if (prop.type === AST_NODE_TYPES.Property) return "name" in prop.key && prop.key.name === name;
|
|
308
|
-
if (prop.type === AST_NODE_TYPES.SpreadElement) switch (prop.argument.type) {
|
|
309
|
-
case AST_NODE_TYPES.Identifier: {
|
|
310
|
-
if (seen.has(prop.argument.name)) return false;
|
|
311
|
-
const variableNode = getVariableDefinitionNode(findVariable(prop.argument.name, initialScope), 0);
|
|
312
|
-
if (variableNode?.type === AST_NODE_TYPES.ObjectExpression) {
|
|
313
|
-
seen.add(prop.argument.name);
|
|
314
|
-
return findProperty(name, variableNode.properties, initialScope, seen) != null;
|
|
315
|
-
}
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
case AST_NODE_TYPES.ObjectExpression: return findProperty(name, prop.argument.properties, initialScope, seen) != null;
|
|
319
|
-
default: return false;
|
|
320
|
-
}
|
|
321
|
-
return false;
|
|
322
|
-
});
|
|
258
|
+
return ast.isNodeEqual(a, b) || isValueEqual(context, a, b);
|
|
323
259
|
}
|
|
324
260
|
|
|
325
261
|
//#endregion
|
|
326
|
-
export {
|
|
262
|
+
export { computeObjectType, findEnclosingAssignmentTarget, isAssignmentTargetEqual, isValueEqual, resolve };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-react/var",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-rc.0",
|
|
4
4
|
"description": "ESLint React's TSESTree AST utility module for static analysis of variables.",
|
|
5
5
|
"homepage": "https://github.com/Rel1cx/eslint-react",
|
|
6
6
|
"bugs": {
|
|
@@ -34,17 +34,18 @@
|
|
|
34
34
|
"@typescript-eslint/types": "canary",
|
|
35
35
|
"@typescript-eslint/utils": "canary",
|
|
36
36
|
"ts-pattern": "^5.9.0",
|
|
37
|
-
"@eslint-react/ast": "3.0.0-
|
|
38
|
-
"@eslint-react/shared": "3.0.0-
|
|
39
|
-
"@eslint-react/eff": "3.0.0-next.9"
|
|
37
|
+
"@eslint-react/ast": "3.0.0-rc.0",
|
|
38
|
+
"@eslint-react/shared": "3.0.0-rc.0"
|
|
40
39
|
},
|
|
41
40
|
"devDependencies": {
|
|
42
|
-
"
|
|
41
|
+
"@typescript-eslint/typescript-estree": "canary",
|
|
42
|
+
"tsdown": "^0.21.0",
|
|
43
|
+
"@local/eff": "3.0.0-beta.72",
|
|
43
44
|
"@local/configs": "0.0.0"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
|
-
"eslint": "^
|
|
47
|
-
"typescript": "
|
|
47
|
+
"eslint": "^10.0.0",
|
|
48
|
+
"typescript": "*"
|
|
48
49
|
},
|
|
49
50
|
"engines": {
|
|
50
51
|
"node": ">=22.0.0"
|
|
@@ -52,6 +53,6 @@
|
|
|
52
53
|
"scripts": {
|
|
53
54
|
"build": "tsdown --dts-resolve",
|
|
54
55
|
"lint:publish": "publint",
|
|
55
|
-
"lint:ts": "
|
|
56
|
+
"lint:ts": "tsl"
|
|
56
57
|
}
|
|
57
58
|
}
|