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