@seljs/checker 1.0.0 → 1.0.1-beta.9

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 (123) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  2. package/dist/checker/checker.cjs +482 -0
  3. package/dist/checker/checker.d.cts +176 -0
  4. package/dist/checker/checker.d.mts +176 -0
  5. package/dist/checker/checker.mjs +481 -0
  6. package/dist/checker/diagnostics.cjs +66 -0
  7. package/dist/checker/diagnostics.mjs +66 -0
  8. package/dist/checker/index.cjs +2 -0
  9. package/dist/checker/index.d.mts +2 -0
  10. package/dist/checker/index.mjs +3 -0
  11. package/dist/checker/type-compatibility.cjs +70 -0
  12. package/dist/checker/type-compatibility.d.cts +8 -0
  13. package/dist/checker/type-compatibility.d.mts +8 -0
  14. package/dist/checker/type-compatibility.mjs +69 -0
  15. package/dist/constants.cjs +14 -0
  16. package/dist/constants.d.cts +7 -0
  17. package/dist/constants.d.mts +7 -0
  18. package/dist/constants.mjs +13 -0
  19. package/dist/debug.cjs +7 -0
  20. package/dist/debug.mjs +5 -0
  21. package/dist/environment/codec-registry.cjs +109 -0
  22. package/dist/environment/codec-registry.d.cts +45 -0
  23. package/dist/environment/codec-registry.d.mts +45 -0
  24. package/dist/environment/codec-registry.mjs +108 -0
  25. package/dist/environment/hydrate.cjs +163 -0
  26. package/dist/environment/hydrate.d.cts +52 -0
  27. package/dist/environment/hydrate.d.mts +52 -0
  28. package/dist/environment/hydrate.mjs +160 -0
  29. package/dist/environment/index.cjs +4 -0
  30. package/dist/environment/index.d.mts +4 -0
  31. package/dist/environment/index.mjs +5 -0
  32. package/dist/environment/register-types.cjs +107 -0
  33. package/dist/environment/register-types.d.cts +15 -0
  34. package/dist/environment/register-types.d.mts +15 -0
  35. package/dist/environment/register-types.mjs +106 -0
  36. package/dist/environment/value-wrappers.cjs +44 -0
  37. package/dist/environment/value-wrappers.d.cts +20 -0
  38. package/dist/environment/value-wrappers.d.mts +20 -0
  39. package/dist/environment/value-wrappers.mjs +41 -0
  40. package/dist/index.cjs +27 -0
  41. package/dist/index.d.cts +11 -0
  42. package/dist/index.d.mts +11 -0
  43. package/dist/index.mjs +13 -0
  44. package/dist/rules/defaults/deferred-call.cjs +107 -0
  45. package/dist/rules/defaults/deferred-call.mjs +106 -0
  46. package/dist/rules/defaults/index.cjs +6 -0
  47. package/dist/rules/defaults/index.mjs +7 -0
  48. package/dist/rules/defaults/no-constant-condition.cjs +31 -0
  49. package/dist/rules/defaults/no-constant-condition.mjs +31 -0
  50. package/dist/rules/defaults/no-mixed-operators.cjs +39 -0
  51. package/dist/rules/defaults/no-mixed-operators.mjs +39 -0
  52. package/dist/rules/defaults/no-redundant-bool.cjs +26 -0
  53. package/dist/rules/defaults/no-redundant-bool.mjs +26 -0
  54. package/dist/rules/defaults/no-self-comparison.cjs +44 -0
  55. package/dist/rules/defaults/no-self-comparison.mjs +43 -0
  56. package/dist/rules/defaults/require-type.cjs +18 -0
  57. package/dist/rules/defaults/require-type.mjs +18 -0
  58. package/dist/rules/facade.cjs +31 -0
  59. package/dist/rules/facade.d.cts +20 -0
  60. package/dist/rules/facade.d.mts +20 -0
  61. package/dist/rules/facade.mjs +31 -0
  62. package/dist/rules/index.cjs +2 -0
  63. package/dist/rules/index.d.mts +3 -0
  64. package/dist/rules/index.mjs +3 -0
  65. package/dist/rules/runner.cjs +40 -0
  66. package/dist/rules/runner.d.cts +27 -0
  67. package/dist/rules/runner.d.mts +27 -0
  68. package/dist/rules/runner.mjs +40 -0
  69. package/dist/rules/types.d.cts +77 -0
  70. package/dist/rules/types.d.mts +77 -0
  71. package/dist/utils/ast-utils.cjs +164 -0
  72. package/dist/utils/ast-utils.mjs +162 -0
  73. package/package.json +23 -16
  74. package/dist/checker/checker.d.ts +0 -173
  75. package/dist/checker/checker.js +0 -567
  76. package/dist/checker/diagnostics.d.ts +0 -10
  77. package/dist/checker/diagnostics.js +0 -80
  78. package/dist/checker/index.d.ts +0 -2
  79. package/dist/checker/index.js +0 -2
  80. package/dist/checker/type-compatibility.d.ts +0 -16
  81. package/dist/checker/type-compatibility.js +0 -59
  82. package/dist/constants.d.ts +0 -4
  83. package/dist/constants.js +0 -10
  84. package/dist/debug.d.ts +0 -2
  85. package/dist/debug.js +0 -2
  86. package/dist/environment/codec-registry.d.ts +0 -42
  87. package/dist/environment/codec-registry.js +0 -146
  88. package/dist/environment/hydrate.d.ts +0 -48
  89. package/dist/environment/hydrate.js +0 -198
  90. package/dist/environment/index.d.ts +0 -4
  91. package/dist/environment/index.js +0 -4
  92. package/dist/environment/register-types.d.ts +0 -14
  93. package/dist/environment/register-types.js +0 -154
  94. package/dist/environment/value-wrappers.d.ts +0 -17
  95. package/dist/environment/value-wrappers.js +0 -65
  96. package/dist/index.d.ts +0 -4
  97. package/dist/index.js +0 -4
  98. package/dist/rules/defaults/deferred-call.d.ts +0 -13
  99. package/dist/rules/defaults/deferred-call.js +0 -162
  100. package/dist/rules/defaults/index.d.ts +0 -6
  101. package/dist/rules/defaults/index.js +0 -6
  102. package/dist/rules/defaults/no-constant-condition.d.ts +0 -7
  103. package/dist/rules/defaults/no-constant-condition.js +0 -36
  104. package/dist/rules/defaults/no-mixed-operators.d.ts +0 -9
  105. package/dist/rules/defaults/no-mixed-operators.js +0 -44
  106. package/dist/rules/defaults/no-redundant-bool.d.ts +0 -5
  107. package/dist/rules/defaults/no-redundant-bool.js +0 -27
  108. package/dist/rules/defaults/no-self-comparison.d.ts +0 -9
  109. package/dist/rules/defaults/no-self-comparison.js +0 -31
  110. package/dist/rules/defaults/require-type.d.ts +0 -7
  111. package/dist/rules/defaults/require-type.js +0 -19
  112. package/dist/rules/facade.d.ts +0 -22
  113. package/dist/rules/facade.js +0 -29
  114. package/dist/rules/index.d.ts +0 -3
  115. package/dist/rules/index.js +0 -3
  116. package/dist/rules/runner.d.ts +0 -16
  117. package/dist/rules/runner.js +0 -30
  118. package/dist/rules/types.d.ts +0 -73
  119. package/dist/rules/types.js +0 -1
  120. package/dist/utils/ast-utils.d.ts +0 -55
  121. package/dist/utils/ast-utils.js +0 -255
  122. package/dist/utils/index.d.ts +0 -1
  123. package/dist/utils/index.js +0 -1
@@ -0,0 +1,176 @@
1
+ import { SELRule } from "../rules/types.mjs";
2
+ import { SELSchema } from "@seljs/schema";
3
+
4
+ //#region src/checker/checker.d.ts
5
+ interface SELCheckResult {
6
+ valid: boolean;
7
+ type?: string;
8
+ diagnostics: SELDiagnostic[];
9
+ }
10
+ interface TypeAtResult {
11
+ type: string;
12
+ from: number;
13
+ to: number;
14
+ }
15
+ interface ExpectedTypeInfo {
16
+ /**
17
+ * The CEL type expected at this position.
18
+ */
19
+ expectedType: string;
20
+ /**
21
+ * How the expectation was inferred.
22
+ */
23
+ context: "operator" | "function-argument";
24
+ /**
25
+ * For function args: which parameter index.
26
+ */
27
+ paramIndex?: number;
28
+ /**
29
+ * For function args: the function/method name.
30
+ */
31
+ functionName?: string;
32
+ }
33
+ interface CompletionInfo {
34
+ kind: "top-level" | "dot-access" | "explicit";
35
+ receiverType?: string;
36
+ items: CompletionItem[];
37
+ /**
38
+ * Inferred expected type at cursor, if determinable.
39
+ */
40
+ expectedType?: string;
41
+ }
42
+ interface CompletionItem {
43
+ label: string;
44
+ type: string;
45
+ detail?: string;
46
+ description?: string;
47
+ }
48
+ /**
49
+ * A diagnostic message with optional position info. Used for both parse/type errors
50
+ * and rule violations. Positions are optional because some errors (e.g. from cel-js)
51
+ * may not include reliable spans, and rules may choose to report non-positional issues.
52
+ */
53
+ interface SELDiagnostic {
54
+ message: string;
55
+ severity: "error" | "warning" | "info";
56
+ from?: number;
57
+ to?: number;
58
+ }
59
+ /**
60
+ * Options for configuring the SELChecker.
61
+ */
62
+ interface SELCheckerOptions {
63
+ /**
64
+ * Lint rules to enable. Defaults to none (backward compatible).
65
+ */
66
+ rules?: readonly SELRule[];
67
+ }
68
+ /**
69
+ * SEL expression checker.
70
+ *
71
+ * Wraps a hydrated cel-js Environment built from a SELSchema and exposes
72
+ * parse/type-check, type inference, cursor-position type lookups, and
73
+ * type-aware completions.
74
+ */
75
+ declare class SELChecker {
76
+ private readonly rules;
77
+ private env;
78
+ private schema;
79
+ private structTypeMap;
80
+ constructor(schema: SELSchema, options?: SELCheckerOptions);
81
+ /**
82
+ * Parse and type-check an expression, returning position-aware diagnostics.
83
+ *
84
+ * Uses a single `env.parse()` call, then `.check()` on the result
85
+ * to avoid double-parsing. Rules run against the AST from the same parse:
86
+ * - Structural rules run after successful parse (even if type-check fails)
87
+ * - Type-aware rules run only after both parse and type-check succeed
88
+ */
89
+ check(expression: string): SELCheckResult;
90
+ /**
91
+ * Get the inferred CEL type of the full expression.
92
+ * Returns undefined if the expression is invalid.
93
+ */
94
+ typeOf(expression: string): string | undefined;
95
+ /**
96
+ * Get the inferred type at a cursor position (for hover info).
97
+ *
98
+ * Attempts to identify the sub-expression under the cursor and infer its
99
+ * type. Falls back to the full expression type when sub-expression
100
+ * isolation is not possible.
101
+ */
102
+ typeAt(expression: string, offset: number): TypeAtResult | undefined;
103
+ /**
104
+ * Get type-aware completions for a cursor context.
105
+ *
106
+ * Supports:
107
+ * - **dot-access**: after `contract.` — lists methods of that contract
108
+ * - **top-level**: at the start or after operators — lists variables,
109
+ * contracts, and functions
110
+ */
111
+ completionsAt(expression: string, offset: number): CompletionInfo;
112
+ /**
113
+ * Infer the expected type at a cursor position from surrounding context.
114
+ *
115
+ * Supports two contexts:
116
+ * - **Operator**: `expr > |` — infers the left operand type and derives the
117
+ * expected right operand type from the operator.
118
+ * - **Function argument**: `contract.method(arg, |)` — looks up the
119
+ * parameter type from the schema.
120
+ *
121
+ * Returns undefined when context cannot be determined, signaling that
122
+ * no narrowing should occur (safe fallback).
123
+ */
124
+ expectedTypeAt(expression: string, offset: number): ExpectedTypeInfo | undefined;
125
+ /**
126
+ * Resolve type and available members for a dot-access receiver expression.
127
+ */
128
+ dotCompletions(receiverExpression: string): CompletionInfo;
129
+ /**
130
+ * Resolve expected type from a structured context.
131
+ */
132
+ expectedTypeFor(context: {
133
+ kind: "operator";
134
+ leftExpression: string;
135
+ operator: string;
136
+ } | {
137
+ kind: "function-arg";
138
+ receiverName?: string;
139
+ functionName: string;
140
+ paramIndex: number;
141
+ }): string | undefined;
142
+ /**
143
+ * Rebuild the internal environment from an updated schema.
144
+ */
145
+ updateSchema(schema: SELSchema): void;
146
+ private buildStructTypeMap;
147
+ private structFieldsFor;
148
+ /**
149
+ * Scan backwards from a dot position to extract the receiver expression.
150
+ * Handles balanced parentheses so that `foo(erc20.name().` correctly
151
+ * identifies `erc20.name()` as the receiver, not `foo(erc20.name()`.
152
+ */
153
+ private extractReceiverBefore;
154
+ /**
155
+ * Find the largest call/dot-access chain node that contains the offset.
156
+ */
157
+ private findContainingChain;
158
+ /**
159
+ * Scan backwards from the end of text to find a trailing binary operator.
160
+ * Tries multi-character operators first (==, !=, etc.) to avoid partial matches.
161
+ */
162
+ private findTrailingOperator;
163
+ /**
164
+ * Find the enclosing function/method call around the cursor by scanning
165
+ * backwards for an unclosed `(`, then extracting the function name and
166
+ * counting commas to determine the parameter index.
167
+ */
168
+ private findEnclosingCall;
169
+ /**
170
+ * Look up the expected parameter type from the schema for a function or
171
+ * method call at the given parameter index.
172
+ */
173
+ private resolveRawParamType;
174
+ }
175
+ //#endregion
176
+ export { CompletionItem, SELChecker, SELCheckerOptions, SELDiagnostic };
@@ -0,0 +1,481 @@
1
+ import { extractDiagnostics } from "./diagnostics.mjs";
2
+ import { expectedTypeForOperator } from "./type-compatibility.mjs";
3
+ import { createCheckerEnvironment } from "../environment/hydrate.mjs";
4
+ import { findNodeWithParentAt, nodeSpan, walkAST } from "../utils/ast-utils.mjs";
5
+ import { runRules } from "../rules/runner.mjs";
6
+ import "../rules/index.mjs";
7
+ import { contractTypeName } from "@seljs/common";
8
+ //#region src/checker/checker.ts
9
+ /**
10
+ * SEL expression checker.
11
+ *
12
+ * Wraps a hydrated cel-js Environment built from a SELSchema and exposes
13
+ * parse/type-check, type inference, cursor-position type lookups, and
14
+ * type-aware completions.
15
+ */
16
+ var SELChecker = class {
17
+ rules;
18
+ env;
19
+ schema;
20
+ structTypeMap;
21
+ constructor(schema, options) {
22
+ this.schema = schema;
23
+ this.env = createCheckerEnvironment(schema);
24
+ this.rules = options?.rules ?? [];
25
+ this.structTypeMap = this.buildStructTypeMap(schema);
26
+ }
27
+ /**
28
+ * Parse and type-check an expression, returning position-aware diagnostics.
29
+ *
30
+ * Uses a single `env.parse()` call, then `.check()` on the result
31
+ * to avoid double-parsing. Rules run against the AST from the same parse:
32
+ * - Structural rules run after successful parse (even if type-check fails)
33
+ * - Type-aware rules run only after both parse and type-check succeed
34
+ */
35
+ check(expression) {
36
+ let parsed;
37
+ try {
38
+ parsed = this.env.parse(expression);
39
+ } catch (error) {
40
+ return {
41
+ valid: false,
42
+ diagnostics: extractDiagnostics(expression, error)
43
+ };
44
+ }
45
+ const structuralDiags = this.rules.length > 0 ? runRules({
46
+ expression,
47
+ ast: parsed.ast,
48
+ schema: this.schema,
49
+ rules: this.rules,
50
+ tier: "structural"
51
+ }) : [];
52
+ let typeResult;
53
+ try {
54
+ typeResult = parsed.check();
55
+ } catch (error) {
56
+ return {
57
+ valid: false,
58
+ diagnostics: [...extractDiagnostics(expression, error), ...structuralDiags]
59
+ };
60
+ }
61
+ if (!typeResult.valid) return {
62
+ valid: false,
63
+ diagnostics: [...typeResult.error ? extractDiagnostics(expression, typeResult.error) : [{
64
+ message: "Type check failed",
65
+ severity: "error",
66
+ from: 0,
67
+ to: expression.length
68
+ }], ...structuralDiags]
69
+ };
70
+ const typeAwareDiags = this.rules.length > 0 ? runRules({
71
+ expression,
72
+ ast: parsed.ast,
73
+ schema: this.schema,
74
+ rules: this.rules,
75
+ tier: "type-aware",
76
+ resolvedType: typeResult.type
77
+ }) : [];
78
+ const allRuleDiags = [...structuralDiags, ...typeAwareDiags];
79
+ return {
80
+ valid: !allRuleDiags.some((d) => d.severity === "error"),
81
+ type: typeResult.type,
82
+ diagnostics: allRuleDiags
83
+ };
84
+ }
85
+ /**
86
+ * Get the inferred CEL type of the full expression.
87
+ * Returns undefined if the expression is invalid.
88
+ */
89
+ typeOf(expression) {
90
+ return this.check(expression).type;
91
+ }
92
+ /**
93
+ * Get the inferred type at a cursor position (for hover info).
94
+ *
95
+ * Attempts to identify the sub-expression under the cursor and infer its
96
+ * type. Falls back to the full expression type when sub-expression
97
+ * isolation is not possible.
98
+ */
99
+ typeAt(expression, offset) {
100
+ if (offset < 0 || offset > expression.length) return;
101
+ let parsed;
102
+ try {
103
+ parsed = this.env.parse(expression);
104
+ } catch {
105
+ return;
106
+ }
107
+ const hit = findNodeWithParentAt(parsed.ast, offset);
108
+ if (!hit) {
109
+ const fullType = this.typeOf(expression);
110
+ return fullType ? {
111
+ type: fullType,
112
+ from: 0,
113
+ to: expression.length
114
+ } : void 0;
115
+ }
116
+ const { node } = hit;
117
+ if (node.op === "id") {
118
+ const name = node.args;
119
+ const span = nodeSpan(node);
120
+ const variable = this.schema.variables.find((v) => v.name === name);
121
+ if (variable) return {
122
+ type: variable.type,
123
+ from: span.from,
124
+ to: span.to
125
+ };
126
+ const contract = this.schema.contracts.find((c) => c.name === name);
127
+ if (contract) return {
128
+ type: contractTypeName(contract.name),
129
+ from: span.from,
130
+ to: span.to
131
+ };
132
+ }
133
+ const chainNode = this.findContainingChain(parsed.ast, offset);
134
+ if (chainNode) {
135
+ const chainSpan = nodeSpan(chainNode);
136
+ const chainExpr = expression.slice(chainSpan.from, chainSpan.to);
137
+ const chainType = this.typeOf(chainExpr);
138
+ if (chainType) return {
139
+ type: chainType,
140
+ from: chainSpan.from,
141
+ to: chainSpan.to
142
+ };
143
+ }
144
+ const fullType = this.typeOf(expression);
145
+ return fullType ? {
146
+ type: fullType,
147
+ from: 0,
148
+ to: expression.length
149
+ } : void 0;
150
+ }
151
+ /**
152
+ * Get type-aware completions for a cursor context.
153
+ *
154
+ * Supports:
155
+ * - **dot-access**: after `contract.` — lists methods of that contract
156
+ * - **top-level**: at the start or after operators — lists variables,
157
+ * contracts, and functions
158
+ */
159
+ completionsAt(expression, offset) {
160
+ const beforeCursor = expression.slice(0, offset);
161
+ const lastDotIdx = beforeCursor.lastIndexOf(".");
162
+ if (lastDotIdx > 0) {
163
+ const receiverExpr = this.extractReceiverBefore(beforeCursor, lastDotIdx);
164
+ if (receiverExpr) return this.dotCompletions(receiverExpr);
165
+ }
166
+ const items = [];
167
+ for (const variable of this.schema.variables) items.push({
168
+ label: variable.name,
169
+ type: variable.type,
170
+ description: variable.description
171
+ });
172
+ for (const contract of this.schema.contracts) items.push({
173
+ label: contract.name,
174
+ type: contractTypeName(contract.name),
175
+ description: contract.description
176
+ });
177
+ for (const fn of this.schema.functions) if (!fn.receiverType) items.push({
178
+ label: fn.name,
179
+ type: fn.returns,
180
+ detail: fn.signature,
181
+ description: fn.description
182
+ });
183
+ return {
184
+ kind: "top-level",
185
+ expectedType: this.expectedTypeAt(expression, offset)?.expectedType,
186
+ items
187
+ };
188
+ }
189
+ /**
190
+ * Infer the expected type at a cursor position from surrounding context.
191
+ *
192
+ * Supports two contexts:
193
+ * - **Operator**: `expr > |` — infers the left operand type and derives the
194
+ * expected right operand type from the operator.
195
+ * - **Function argument**: `contract.method(arg, |)` — looks up the
196
+ * parameter type from the schema.
197
+ *
198
+ * Returns undefined when context cannot be determined, signaling that
199
+ * no narrowing should occur (safe fallback).
200
+ */
201
+ expectedTypeAt(expression, offset) {
202
+ const beforeCursor = expression.slice(0, offset);
203
+ const trailing = this.findTrailingOperator(beforeCursor);
204
+ if (trailing) {
205
+ const expected = this.expectedTypeFor({
206
+ kind: "operator",
207
+ leftExpression: trailing.leftExpr,
208
+ operator: trailing.operator
209
+ });
210
+ if (expected) return {
211
+ expectedType: expected,
212
+ context: "operator"
213
+ };
214
+ }
215
+ const callInfo = this.findEnclosingCall(expression, offset);
216
+ if (callInfo) {
217
+ const expected = this.expectedTypeFor({
218
+ kind: "function-arg",
219
+ receiverName: callInfo.receiverName,
220
+ functionName: callInfo.functionName,
221
+ paramIndex: callInfo.paramIndex
222
+ });
223
+ if (expected) return {
224
+ expectedType: expected,
225
+ context: "function-argument",
226
+ paramIndex: callInfo.paramIndex,
227
+ functionName: callInfo.functionName
228
+ };
229
+ }
230
+ }
231
+ /**
232
+ * Resolve type and available members for a dot-access receiver expression.
233
+ */
234
+ dotCompletions(receiverExpression) {
235
+ const contract = this.schema.contracts.find((c) => c.name === receiverExpression);
236
+ if (contract) return {
237
+ kind: "dot-access",
238
+ receiverType: contractTypeName(contract.name),
239
+ items: contract.methods.map((method) => ({
240
+ label: method.name,
241
+ type: method.returns,
242
+ detail: `(${method.params.map((p) => `${p.name}: ${p.type}`).join(", ")}): ${method.returns}`,
243
+ description: method.description
244
+ }))
245
+ };
246
+ const receiverType = this.typeOf(receiverExpression);
247
+ if (receiverType) {
248
+ const contractByType = this.schema.contracts.find((c) => contractTypeName(c.name) === receiverType);
249
+ if (contractByType) return {
250
+ kind: "dot-access",
251
+ receiverType,
252
+ items: contractByType.methods.map((m) => ({
253
+ label: m.name,
254
+ type: m.returns,
255
+ detail: `(${m.params.map((p) => `${p.name}: ${p.type}`).join(", ")}): ${m.returns}`,
256
+ description: m.description
257
+ }))
258
+ };
259
+ const baseType = receiverType.includes("<") ? receiverType.slice(0, receiverType.indexOf("<")) : null;
260
+ const receiverMethods = this.schema.functions.filter((f) => f.receiverType === receiverType || baseType !== null && f.receiverType === baseType).map((f) => ({
261
+ label: f.name,
262
+ type: f.returns,
263
+ detail: f.signature,
264
+ description: f.description
265
+ }));
266
+ if (baseType === "list" || receiverType === "list") {
267
+ const macroItems = this.schema.macros.filter((m) => m.pattern.startsWith("list.")).map((m) => ({
268
+ label: m.name,
269
+ type: "",
270
+ detail: m.pattern,
271
+ description: m.description
272
+ }));
273
+ const seen = new Set(receiverMethods.map((m) => m.label));
274
+ for (const item of macroItems) if (!seen.has(item.label)) {
275
+ receiverMethods.push(item);
276
+ seen.add(item.label);
277
+ }
278
+ }
279
+ if (receiverMethods.length > 0) return {
280
+ kind: "dot-access",
281
+ receiverType,
282
+ items: receiverMethods
283
+ };
284
+ const structItems = this.structFieldsFor(receiverType);
285
+ if (structItems) return {
286
+ kind: "dot-access",
287
+ receiverType,
288
+ items: structItems
289
+ };
290
+ return {
291
+ kind: "dot-access",
292
+ receiverType,
293
+ items: []
294
+ };
295
+ }
296
+ return {
297
+ kind: "dot-access",
298
+ receiverType: receiverExpression,
299
+ items: []
300
+ };
301
+ }
302
+ /**
303
+ * Resolve expected type from a structured context.
304
+ */
305
+ expectedTypeFor(context) {
306
+ if (context.kind === "operator") {
307
+ const leftType = this.typeOf(context.leftExpression);
308
+ if (leftType) return expectedTypeForOperator(leftType, context.operator) ?? void 0;
309
+ return;
310
+ }
311
+ const rawType = this.resolveRawParamType(context.receiverName, context.functionName, context.paramIndex);
312
+ if (rawType && !rawType.includes("|")) return rawType;
313
+ }
314
+ /**
315
+ * Rebuild the internal environment from an updated schema.
316
+ */
317
+ updateSchema(schema) {
318
+ this.schema = schema;
319
+ this.env = createCheckerEnvironment(schema);
320
+ this.structTypeMap = this.buildStructTypeMap(schema);
321
+ }
322
+ buildStructTypeMap(schema) {
323
+ const map = /* @__PURE__ */ new Map();
324
+ for (const type of schema.types) if (type.kind === "struct") map.set(type.name, type);
325
+ return map;
326
+ }
327
+ structFieldsFor(typeName) {
328
+ const structType = this.structTypeMap.get(typeName);
329
+ if (!structType) return;
330
+ return (structType.fields ?? []).map((field) => ({
331
+ label: field.name,
332
+ type: field.type,
333
+ detail: field.type,
334
+ description: field.description
335
+ }));
336
+ }
337
+ /**
338
+ * Scan backwards from a dot position to extract the receiver expression.
339
+ * Handles balanced parentheses so that `foo(erc20.name().` correctly
340
+ * identifies `erc20.name()` as the receiver, not `foo(erc20.name()`.
341
+ */
342
+ extractReceiverBefore(text, dotIdx) {
343
+ let i = dotIdx - 1;
344
+ while (i >= 0 && (text[i] === " " || text[i] === " ")) i--;
345
+ if (i < 0) return "";
346
+ const isIdentStart = (code) => code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95;
347
+ const isIdentChar = (code) => isIdentStart(code) || code >= 48 && code <= 57;
348
+ const collectChain = () => {
349
+ if (text[i] === ")") {
350
+ const closePos = i;
351
+ let depth = 1;
352
+ i--;
353
+ while (i >= 0 && depth > 0) {
354
+ const ch = text[i];
355
+ if (ch === ")") depth++;
356
+ else if (ch === "(") depth--;
357
+ if (depth > 0) i--;
358
+ }
359
+ if (depth !== 0) {
360
+ i = closePos;
361
+ return;
362
+ }
363
+ i--;
364
+ }
365
+ if (i >= 0 && isIdentChar(text.charCodeAt(i))) {
366
+ while (i >= 0 && isIdentChar(text.charCodeAt(i))) i--;
367
+ const start = i + 1;
368
+ if (!isIdentStart(text.charCodeAt(start))) return;
369
+ if (i >= 0 && text[i] === ".") {
370
+ i--;
371
+ collectChain();
372
+ }
373
+ }
374
+ };
375
+ collectChain();
376
+ const start = i + 1;
377
+ return text.slice(start, dotIdx).trimEnd();
378
+ }
379
+ /**
380
+ * Find the largest call/dot-access chain node that contains the offset.
381
+ */
382
+ findContainingChain(root, offset) {
383
+ let best;
384
+ walkAST(root, (node) => {
385
+ if (node.op !== "." && node.op !== ".?" && node.op !== "rcall") return;
386
+ const span = nodeSpan(node);
387
+ if (offset >= span.from && offset <= span.to) {
388
+ if (!best || span.to - span.from > nodeSpan(best).to - nodeSpan(best).from) best = node;
389
+ }
390
+ });
391
+ return best;
392
+ }
393
+ /**
394
+ * Scan backwards from the end of text to find a trailing binary operator.
395
+ * Tries multi-character operators first (==, !=, etc.) to avoid partial matches.
396
+ */
397
+ findTrailingOperator(text) {
398
+ const trimmed = text.trimEnd();
399
+ for (const op of [
400
+ "==",
401
+ "!=",
402
+ ">=",
403
+ "<=",
404
+ "&&",
405
+ "||",
406
+ "in"
407
+ ]) if (trimmed.endsWith(op)) {
408
+ const left = trimmed.slice(0, -op.length).trimEnd();
409
+ if (left) return {
410
+ leftExpr: left,
411
+ operator: op
412
+ };
413
+ }
414
+ for (const op of [
415
+ ">",
416
+ "<",
417
+ "+",
418
+ "-",
419
+ "*",
420
+ "/",
421
+ "%"
422
+ ]) if (trimmed.endsWith(op)) {
423
+ const before = trimmed.slice(0, -1);
424
+ const lastChar = before.at(-1);
425
+ if (lastChar === ">" || lastChar === "<" || lastChar === "=" || lastChar === "!" || lastChar === "&" || lastChar === "|") continue;
426
+ const left = before.trimEnd();
427
+ if (left) return {
428
+ leftExpr: left,
429
+ operator: op
430
+ };
431
+ }
432
+ }
433
+ /**
434
+ * Find the enclosing function/method call around the cursor by scanning
435
+ * backwards for an unclosed `(`, then extracting the function name and
436
+ * counting commas to determine the parameter index.
437
+ */
438
+ findEnclosingCall(expression, offset) {
439
+ let depth = 0;
440
+ let commas = 0;
441
+ let parenPos = -1;
442
+ for (let i = offset - 1; i >= 0; i--) {
443
+ const ch = expression[i];
444
+ if (ch === ")") depth++;
445
+ else if (ch === "(") {
446
+ if (depth === 0) {
447
+ parenPos = i;
448
+ break;
449
+ }
450
+ depth--;
451
+ } else if (ch === "," && depth === 0) commas++;
452
+ }
453
+ if (parenPos < 0) return;
454
+ const beforeParen = expression.slice(0, parenPos);
455
+ const callMatch = /(?:(\w+)\.)?(\w+)\s*$/.exec(beforeParen);
456
+ if (!callMatch?.[2]) return;
457
+ return {
458
+ receiverName: callMatch[1],
459
+ functionName: callMatch[2],
460
+ paramIndex: commas
461
+ };
462
+ }
463
+ /**
464
+ * Look up the expected parameter type from the schema for a function or
465
+ * method call at the given parameter index.
466
+ */
467
+ resolveRawParamType(receiverName, functionName, paramIndex) {
468
+ if (receiverName) {
469
+ const contract = this.schema.contracts.find((c) => c.name === receiverName);
470
+ if (contract) {
471
+ const method = contract.methods.find((m) => m.name === functionName);
472
+ if (method && paramIndex < method.params.length) return method.params[paramIndex].type;
473
+ }
474
+ } else {
475
+ const fn = this.schema.functions.find((f) => f.name === functionName);
476
+ if (fn && paramIndex < fn.params.length) return fn.params[paramIndex].type;
477
+ }
478
+ }
479
+ };
480
+ //#endregion
481
+ export { SELChecker };