@eslint-react/var 3.0.0-next.8 → 3.0.0-next.80

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 +70 -99
  2. package/dist/index.js +179 -243
  3. package/package.json +10 -9
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
  */
@@ -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: "call-expression" | "unsupported-node";
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 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,184 @@
1
1
  import * as ast from "@eslint-react/ast";
2
- import { dual, identity, unit } from "@eslint-react/eff";
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 * 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";
5
+ import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
8
6
 
9
- //#region src/var-definition.ts
7
+ //#region src/resolve.ts
10
8
  /**
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
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 getVariableDefinitionNode(variable, at) {
17
- if (variable == null) return unit;
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 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;
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/var-scope.ts
67
+ //#region src/compute-object-type.ts
45
68
  /**
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
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 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);
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
- * 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
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 getChildScopes(scope) {
78
- const scopes = [scope];
79
- for (const childScope of scope.childScopes) scopes.push(...getChildScopes(childScope));
80
- return scopes;
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/var-node-equality.ts
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 isNodeEqual(a, b, initialScopes) {
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] = initialScopes;
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 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;
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 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;
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) && isNodeEqual(a.object, b.object, initialScopes);
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/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
- }
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) || 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
- });
258
+ return ast.isNodeEqual(a, b) || isValueEqual(context, a, b);
323
259
  }
324
260
 
325
261
  //#endregion
326
- export { findEnclosingAssignmentTarget, findImportSource, findProperty, findVariable, getChildScopes, getObjectType, getVariableDefinitionNode, getVariableDefinitionNodeLoose, getVariables, isAssignmentTargetEqual, isNodeEqual };
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-next.8",
3
+ "version": "3.0.0-next.80",
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-next.8",
38
- "@eslint-react/eff": "3.0.0-next.8",
39
- "@eslint-react/shared": "3.0.0-next.8"
37
+ "@eslint-react/ast": "3.0.0-next.80",
38
+ "@eslint-react/shared": "3.0.0-next.80"
40
39
  },
41
40
  "devDependencies": {
42
- "tsdown": "^0.20.3",
43
- "@local/configs": "0.0.0"
41
+ "@typescript-eslint/typescript-estree": "canary",
42
+ "tsdown": "^0.21.0",
43
+ "@local/configs": "0.0.0",
44
+ "@local/eff": "3.0.0-beta.72"
44
45
  },
45
46
  "peerDependencies": {
46
- "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
47
- "typescript": ">=4.8.4 <6.0.0"
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": "tsc --noEmit"
56
+ "lint:ts": "tsl"
56
57
  }
57
58
  }