@eslint-react/var 3.0.0-next.7 → 3.0.0-next.70

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +66 -95
  2. package/dist/index.js +161 -244
  3. package/package.json +7 -6
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/var-assignment-target.d.ts
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
  */
@@ -95,49 +33,82 @@ type ObjectType = {
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 getObjectType(node: TSESTree.Node | unit, initialScope: Scope): ObjectType | unit;
40
+ declare function computeObjectType(context: RuleContext, node: TSESTree.Node | null): ObjectType | null;
103
41
  //#endregion
104
- //#region src/var-property.d.ts
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
- * Find a property by name in an array of properties
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
- declare function findProperty(name: string, properties: (TSESTree.Property | TSESTree.RestElement | TSESTree.SpreadElement)[], initialScope: Scope, seen?: Set<string>): (typeof properties)[number] | unit;
54
+ type AssignmentTarget = ReturnType<typeof findEnclosingAssignmentTarget>;
115
55
  //#endregion
116
- //#region src/var-scope.d.ts
56
+ //#region src/is-assignment-target-equal.d.ts
117
57
  /**
118
- * Get all variables from the given scope up to the global scope
119
- * @param initialScope The scope to start from
120
- * @returns All variables from the given scope up to the global scope
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 getVariables(initialScope: Scope): Variable[];
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
- * Find a variable by name or identifier node in the scope chain
125
- * @param initialScope The scope to start searching from
126
- * @returns The found variable or unit if not found
127
- * @overload
128
- * @param nameOrNode The variable name or identifier node to find
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 const findVariable: {
133
- (initialScope: Scope): (nameOrNode: string | TSESTree.Identifier | unit) => Variable | unit;
134
- (nameOrNode: string | TSESTree.Identifier | unit, initialScope: Scope): Variable | unit;
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
- * Get all child scopes recursively from a given scope
138
- * @param scope The scope to get child scopes from
139
- * @returns Array of all child scopes including the input scope
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 getChildScopes(scope: Scope): readonly Scope[];
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, findEnclosingAssignmentTarget, findImportSource, findProperty, findVariable, getChildScopes, getObjectType, getVariableDefinitionNode, getVariableDefinitionNodeLoose, getVariables, isAssignmentTargetEqual, isNodeEqual };
114
+ export { AssignmentTarget, ObjectType, computeObjectType, findEnclosingAssignmentTarget, isAssignmentTargetEqual, isValueEqual, resolve };
package/dist/index.js CHANGED
@@ -1,87 +1,165 @@
1
- import * as ast from "@eslint-react/ast";
2
- import { dual, identity, unit } from "@eslint-react/eff";
1
+ import { DefinitionType } from "@typescript-eslint/scope-manager";
3
2
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
4
- import * as astUtils from "@typescript-eslint/utils/ast-utils";
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";
3
+ import * as ast from "@eslint-react/ast";
4
+ import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
8
5
 
9
- //#region src/var-definition.ts
6
+ //#region src/resolve.ts
10
7
  /**
11
- * Get the definition node of a variable at a specific definition index
12
- * @param variable The variable to get the definition node from
13
- * @param at The index of the definition to retrieve (negative index supported)
14
- * @returns The definition node or unit if not found
8
+ * Resolves an identifier to the AST node that represents its value,
9
+ * suitable for use in ESLint rule analysis.
10
+ *
11
+ * The resolution follows these rules per definition type:
12
+ *
13
+ * | Definition type | `def.node` | Returns |
14
+ * |--------------------------|----------------------------------------------|------------------------------------|
15
+ * | `Variable` | `VariableDeclarator` | `def.node.init` (or `null`) |
16
+ * | `FunctionName` | `FunctionDeclaration` / `FunctionExpression` | `def.node` |
17
+ * | `ClassName` | `ClassDeclaration` / `ClassExpression` | `def.node` |
18
+ * | `Parameter` | containing function node | `def.node` (if a real function) |
19
+ * | `TSEnumName` | `TSEnumDeclaration` | `def.node` |
20
+ * | `TSEnumMember` | `TSEnumMember` | `def.node.initializer` (or `null`) |
21
+ * | `ImportBinding` | import specifier | `null` |
22
+ * | `CatchClause` | `CatchClause` | `null` |
23
+ * | `TSModuleName` | `TSModuleDeclaration` | `null` |
24
+ * | `Type` | type alias node | `null` |
25
+ * | `ImplicitGlobalVariable` | any node | `null` |
26
+ *
27
+ * @param context The ESLint rule context used for scope lookup.
28
+ * @param node The identifier to resolve.
29
+ * @param options Optional settings:
30
+ * - `at`: Index of the definition to resolve (default: `0` for the first definition).
31
+ * - `localOnly`: If `true`, only consider variables declared in the same scope as the identifier
32
+ * will miss variables declared in an outer scope). When `false` (default), traverse the scope
33
+ * chain upward via `findVariable` so that references to outer-scope bindings are resolved
34
+ * correctly.
35
+ * @returns The resolved node, or `null` if the identifier cannot be resolved to a value node.
15
36
  */
16
- function getVariableDefinitionNode(variable, at) {
17
- if (variable == null) return unit;
37
+ function resolve(context, node, options) {
38
+ const { at = 0, localOnly = false } = options ?? {};
39
+ const scope = context.sourceCode.getScope(node);
40
+ const variable = localOnly ? scope.set.get(node.name) : findVariable(scope, node);
41
+ if (variable == null) return null;
18
42
  const def = variable.defs.at(at);
19
- if (def == null) return unit;
20
- switch (true) {
21
- case def.type === DefinitionType.FunctionName && def.node.type === AST_NODE_TYPES.FunctionDeclaration: return def.node;
22
- case def.type === DefinitionType.ClassName && def.node.type === AST_NODE_TYPES.ClassDeclaration: return def.node;
23
- case "init" in def.node && def.node.init != null && !("declarations" in def.node.init): return def.node.init;
24
- default: return unit;
43
+ if (def == null) return null;
44
+ switch (def.type) {
45
+ case DefinitionType.FunctionName: return def.node;
46
+ case DefinitionType.ClassName: return def.node;
47
+ case DefinitionType.Variable: {
48
+ const { init } = def.node;
49
+ if (init == null) return null;
50
+ if ("declarations" in init) return null;
51
+ return init;
52
+ }
53
+ case DefinitionType.Parameter: return ast.isFunction(def.node) ? def.node : null;
54
+ case DefinitionType.TSEnumName: return def.node;
55
+ case DefinitionType.TSEnumMember: return def.node.initializer ?? null;
56
+ case DefinitionType.ImportBinding: return null;
57
+ case DefinitionType.CatchClause: return null;
58
+ case DefinitionType.TSModuleName: return null;
59
+ case DefinitionType.Type: return null;
60
+ case DefinitionType.ImplicitGlobalVariable: return null;
61
+ default: return null;
25
62
  }
26
63
  }
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
64
 
43
65
  //#endregion
44
- //#region src/var-scope.ts
66
+ //#region src/compute-object-type.ts
45
67
  /**
46
- * Get all variables from the given scope up to the global scope
47
- * @param initialScope The scope to start from
48
- * @returns All variables from the given scope up to the global scope
68
+ * Detect the ObjectType of a given node
69
+ * @param context The context of the rule
70
+ * @param node The node to check
71
+ * @returns The ObjectType of the node, or undefined if not detected
49
72
  */
50
- function getVariables(initialScope) {
51
- let scope = initialScope;
52
- const variables = [...scope.variables];
53
- while (scope.type !== ScopeType.global) {
54
- scope = scope.upper;
55
- variables.push(...scope.variables);
73
+ function computeObjectType(context, node) {
74
+ if (node == null) return null;
75
+ switch (node.type) {
76
+ case AST_NODE_TYPES.JSXElement:
77
+ case AST_NODE_TYPES.JSXFragment: return {
78
+ kind: "jsx",
79
+ node
80
+ };
81
+ case AST_NODE_TYPES.ArrayExpression: return {
82
+ kind: "array",
83
+ node
84
+ };
85
+ case AST_NODE_TYPES.ObjectExpression: return {
86
+ kind: "plain",
87
+ node
88
+ };
89
+ case AST_NODE_TYPES.ClassExpression: return {
90
+ kind: "class",
91
+ node
92
+ };
93
+ case AST_NODE_TYPES.NewExpression:
94
+ case AST_NODE_TYPES.ThisExpression: return {
95
+ kind: "instance",
96
+ node
97
+ };
98
+ case AST_NODE_TYPES.FunctionDeclaration:
99
+ case AST_NODE_TYPES.FunctionExpression:
100
+ case AST_NODE_TYPES.ArrowFunctionExpression: return {
101
+ kind: "function",
102
+ node
103
+ };
104
+ case AST_NODE_TYPES.Literal:
105
+ if ("regex" in node) return {
106
+ kind: "regexp",
107
+ node
108
+ };
109
+ return null;
110
+ case AST_NODE_TYPES.Identifier: {
111
+ if ((context.sourceCode.getScope(node).set.get(node.name)?.defs.at(-1))?.type === DefinitionType.Parameter) return null;
112
+ const initNode = resolve(context, node, {
113
+ at: -1,
114
+ localOnly: true
115
+ });
116
+ if (initNode == null) return null;
117
+ return computeObjectType(context, initNode);
118
+ }
119
+ case AST_NODE_TYPES.MemberExpression:
120
+ if (!("object" in node)) return null;
121
+ return computeObjectType(context, node.object);
122
+ case AST_NODE_TYPES.AssignmentExpression:
123
+ case AST_NODE_TYPES.AssignmentPattern:
124
+ if (!("right" in node)) return null;
125
+ return computeObjectType(context, node.right);
126
+ case AST_NODE_TYPES.LogicalExpression: return computeObjectType(context, node.right);
127
+ case AST_NODE_TYPES.ConditionalExpression: return computeObjectType(context, node.consequent) ?? computeObjectType(context, node.alternate);
128
+ case AST_NODE_TYPES.SequenceExpression:
129
+ if (node.expressions.length === 0) return null;
130
+ return computeObjectType(context, node.expressions[node.expressions.length - 1] ?? null);
131
+ case AST_NODE_TYPES.CallExpression: return {
132
+ kind: "unknown",
133
+ node,
134
+ reason: "call-expression"
135
+ };
136
+ default:
137
+ if (!("expression" in node) || typeof node.expression !== "object") return null;
138
+ return computeObjectType(context, node.expression);
56
139
  }
57
- return variables.reverse();
58
140
  }
141
+
142
+ //#endregion
143
+ //#region src/find-enclosing-assignment-target.ts
59
144
  /**
60
- * Find a variable by name or identifier node in the scope chain
61
- * @param initialScope The scope to start searching from
62
- * @returns The found variable or unit if not found
63
- * @overload
64
- * @param nameOrNode The variable name or identifier node to find
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
145
+ * Finds the enclosing assignment target (variable, property, etc.) for a given node
146
+ *
147
+ * @todo Verify correctness and completeness of this function
148
+ * @param node The starting node
149
+ * @returns The enclosing assignment target node, or null if not found
76
150
  */
77
- function getChildScopes(scope) {
78
- const scopes = [scope];
79
- for (const childScope of scope.childScopes) scopes.push(...getChildScopes(childScope));
80
- return scopes;
151
+ function findEnclosingAssignmentTarget(node) {
152
+ switch (true) {
153
+ case node.type === AST_NODE_TYPES.VariableDeclarator: return node.id;
154
+ case node.type === AST_NODE_TYPES.AssignmentExpression: return node.left;
155
+ case node.type === AST_NODE_TYPES.PropertyDefinition: return node.key;
156
+ case node.type === AST_NODE_TYPES.BlockStatement || node.type === AST_NODE_TYPES.Program || node.parent === node: return null;
157
+ default: return findEnclosingAssignmentTarget(node.parent);
158
+ }
81
159
  }
82
160
 
83
161
  //#endregion
84
- //#region src/var-node-equality.ts
162
+ //#region src/is-value-equal.ts
85
163
  const thisBlockTypes = [
86
164
  AST_NODE_TYPES.FunctionDeclaration,
87
165
  AST_NODE_TYPES.FunctionExpression,
@@ -90,35 +168,35 @@ const thisBlockTypes = [
90
168
  ];
91
169
  /**
92
170
  * Determine whether node value equals to another node value
171
+ * @param context rule context
93
172
  * @param a node to compare
94
173
  * @param b node to compare
95
- * @param initialScopes initial scopes of the two nodes
96
174
  * @returns `true` if node value equal
97
175
  */
98
- function isNodeEqual(a, b, initialScopes) {
176
+ function isValueEqual(context, a, b) {
99
177
  a = ast.isTypeExpression(a) ? ast.getUnderlyingExpression(a) : a;
100
178
  b = ast.isTypeExpression(b) ? ast.getUnderlyingExpression(b) : b;
101
- const [aScope, bScope] = initialScopes;
179
+ const [aScope, bScope] = [context.sourceCode.getScope(a), context.sourceCode.getScope(b)];
102
180
  switch (true) {
103
181
  case a === b: return true;
104
182
  case a.type === AST_NODE_TYPES.Literal && b.type === AST_NODE_TYPES.Literal: return a.value === b.value;
105
183
  case a.type === AST_NODE_TYPES.TemplateElement && b.type === AST_NODE_TYPES.TemplateElement: return a.value.cooked === b.value.cooked;
106
184
  case a.type === AST_NODE_TYPES.Identifier && b.type === AST_NODE_TYPES.Identifier: {
107
- const aVar = findVariable(a, aScope);
108
- const bVar = findVariable(b, bScope);
109
- const aVarNode = getVariableDefinitionNodeLoose(aVar, 0);
110
- const bVarNode = getVariableDefinitionNodeLoose(bVar, 0);
111
- const aVarNodeParent = aVarNode?.parent;
112
- const bVarNodeParent = bVarNode?.parent;
185
+ const aDefNode = resolve(context, a);
186
+ const bDefNode = resolve(context, b);
187
+ const aDefNodeParent = aDefNode?.parent;
188
+ const bDefNodeParent = bDefNode?.parent;
189
+ const aVar = findVariable(aScope, a);
190
+ const bVar = findVariable(bScope, b);
113
191
  const aDef = aVar?.defs.at(0);
114
192
  const bDef = bVar?.defs.at(0);
115
193
  const aDefParentParent = aDef?.parent?.parent;
116
194
  const bDefParentParent = bDef?.parent?.parent;
117
195
  switch (true) {
118
- case aVarNodeParent?.type === AST_NODE_TYPES.CallExpression && bVarNodeParent?.type === AST_NODE_TYPES.CallExpression && ast.isFunction(aVarNode) && ast.isFunction(bVarNode): {
119
- if (!ast.isNodeEqual(aVarNodeParent.callee, bVarNodeParent.callee)) return false;
120
- const aParams = aVarNode.params;
121
- const bParams = bVarNode.params;
196
+ case aDefNodeParent?.type === AST_NODE_TYPES.CallExpression && bDefNodeParent?.type === AST_NODE_TYPES.CallExpression && ast.isFunction(aDefNode) && ast.isFunction(bDefNode): {
197
+ if (!ast.isNodeEqual(aDefNodeParent.callee, bDefNodeParent.callee)) return false;
198
+ const aParams = aDefNode.params;
199
+ const bParams = bDefNode.params;
122
200
  const aPos = aParams.findIndex((x) => ast.isNodeEqual(x, a));
123
201
  const bPos = bParams.findIndex((x) => ast.isNodeEqual(x, b));
124
202
  return aPos !== -1 && bPos !== -1 && aPos === bPos;
@@ -134,7 +212,7 @@ function isNodeEqual(a, b, initialScopes) {
134
212
  default: return aVar != null && bVar != null && aVar === bVar;
135
213
  }
136
214
  }
137
- case a.type === AST_NODE_TYPES.MemberExpression && b.type === AST_NODE_TYPES.MemberExpression: return ast.isNodeEqual(a.property, b.property) && isNodeEqual(a.object, b.object, initialScopes);
215
+ 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
216
  case a.type === AST_NODE_TYPES.ThisExpression && b.type === AST_NODE_TYPES.ThisExpression:
139
217
  if (aScope.block === bScope.block) return true;
140
218
  return ast.findParentNode(a, ast.isOneOf(thisBlockTypes)) === ast.findParentNode(b, ast.isOneOf(thisBlockTypes));
@@ -147,24 +225,7 @@ function isNodeEqual(a, b, initialScopes) {
147
225
  }
148
226
 
149
227
  //#endregion
150
- //#region src/var-assignment-target.ts
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
- }
228
+ //#region src/is-assignment-target-equal.ts
168
229
  /**
169
230
  * Check if two assignment targets are equal
170
231
  * Compares nodes directly or by their values
@@ -175,152 +236,8 @@ function findEnclosingAssignmentTarget(node) {
175
236
  * @internal
176
237
  */
177
238
  function isAssignmentTargetEqual(context, a, b) {
178
- return ast.isNodeEqual(a, b) || isNodeEqual(a, b, [context.sourceCode.getScope(a), context.sourceCode.getScope(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
- });
239
+ return ast.isNodeEqual(a, b) || isValueEqual(context, a, b);
323
240
  }
324
241
 
325
242
  //#endregion
326
- export { findEnclosingAssignmentTarget, findImportSource, findProperty, findVariable, getChildScopes, getObjectType, getVariableDefinitionNode, getVariableDefinitionNodeLoose, getVariables, isAssignmentTargetEqual, isNodeEqual };
243
+ 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-next.7",
3
+ "version": "3.0.0-next.70",
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,12 +34,13 @@
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-next.7",
38
- "@eslint-react/eff": "3.0.0-next.7",
39
- "@eslint-react/shared": "3.0.0-next.7"
37
+ "@eslint-react/ast": "3.0.0-next.70",
38
+ "@eslint-react/eff": "3.0.0-next.70",
39
+ "@eslint-react/shared": "3.0.0-next.70"
40
40
  },
41
41
  "devDependencies": {
42
- "tsdown": "^0.20.3",
42
+ "@typescript-eslint/typescript-estree": "canary",
43
+ "tsdown": "^0.21.0-beta.2",
43
44
  "@local/configs": "0.0.0"
44
45
  },
45
46
  "peerDependencies": {
@@ -52,6 +53,6 @@
52
53
  "scripts": {
53
54
  "build": "tsdown --dts-resolve",
54
55
  "lint:publish": "publint",
55
- "lint:ts": "tsc --noEmit"
56
+ "lint:ts": "tsl"
56
57
  }
57
58
  }