@telorun/ide-support 0.20.2 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cel/cursor-chain.d.ts +4 -0
- package/dist/cel/cursor-chain.d.ts.map +1 -1
- package/dist/cel/cursor-chain.js +1 -1
- package/dist/cel/module-calls.d.ts +56 -0
- package/dist/cel/module-calls.d.ts.map +1 -0
- package/dist/cel/module-calls.js +98 -0
- package/dist/cel/tokens.d.ts.map +1 -1
- package/dist/cel/tokens.js +9 -1
- package/dist/completions/cel-completions.d.ts.map +1 -1
- package/dist/completions/cel-completions.js +15 -0
- package/dist/completions/valid-capabilities.d.ts +1 -1
- package/dist/completions/valid-capabilities.d.ts.map +1 -1
- package/dist/completions/valid-capabilities.js +4 -0
- package/dist/definition/build-definition.d.ts.map +1 -1
- package/dist/definition/build-definition.js +46 -2
- package/dist/diagnostics/normalize.d.ts.map +1 -1
- package/dist/diagnostics/normalize.js +9 -1
- package/dist/hover/build-hover.d.ts.map +1 -1
- package/dist/hover/build-hover.js +17 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/rename/build-rename.d.ts +5 -2
- package/dist/rename/build-rename.d.ts.map +1 -1
- package/dist/rename/build-rename.js +41 -14
- package/dist/rename/find-sites.d.ts +5 -0
- package/dist/rename/find-sites.d.ts.map +1 -1
- package/dist/rename/find-sites.js +28 -0
- package/dist/signature-help/build-signature-help.d.ts +15 -0
- package/dist/signature-help/build-signature-help.d.ts.map +1 -0
- package/dist/signature-help/build-signature-help.js +87 -0
- package/dist/signature-help/index.d.ts +2 -0
- package/dist/signature-help/index.d.ts.map +1 -0
- package/dist/signature-help/index.js +1 -0
- package/dist/types.d.ts +19 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/cel/cursor-chain.ts +1 -1
- package/src/cel/module-calls.ts +138 -0
- package/src/cel/tokens.ts +8 -1
- package/src/completions/cel-completions.ts +15 -0
- package/src/completions/valid-capabilities.ts +6 -0
- package/src/definition/build-definition.ts +49 -1
- package/src/diagnostics/normalize.ts +9 -1
- package/src/hover/build-hover.ts +16 -2
- package/src/index.ts +4 -0
- package/src/rename/build-rename.ts +55 -10
- package/src/rename/find-sites.ts +30 -0
- package/src/signature-help/build-signature-help.ts +100 -0
- package/src/signature-help/index.ts +1 -0
- package/src/types.ts +17 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-sites.d.ts","sourceRoot":"","sources":["../../src/rename/find-sites.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAIjB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"find-sites.d.ts","sourceRoot":"","sources":["../../src/rename/find-sites.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAIjB,MAAM,mBAAmB,CAAC;AAM3B;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,QAAQ;IACvB;qDACiD;IACjD,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzB;AAqED;4CAC4C;AAC5C,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAYxE;AAED;;;wEAGwE;AACxE,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAC9B,IAAI,EAAE,MAAM,GACX,QAAQ,EAAE,CAkBZ;AAED;;iFAEiF;AACjF,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAUpE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAU1F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA2B5F;AAED;;qCAEqC;AACrC,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA0BxF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CelParseError, } from "@telorun/analyzer";
|
|
2
2
|
import { walkCel } from "../cel-chain.js";
|
|
3
|
+
import { moduleCallSites } from "../cel/module-calls.js";
|
|
3
4
|
import { scalarString } from "../completions/resolve-node.js";
|
|
4
5
|
/** Walk every scalar of a document, in source order. */
|
|
5
6
|
function eachScalar(node, visit) {
|
|
@@ -91,6 +92,33 @@ export function resourceSites(doc, name) {
|
|
|
91
92
|
});
|
|
92
93
|
return sites;
|
|
93
94
|
}
|
|
95
|
+
/** A function's calls within one document: `<receiver>.<name>(…)` in CEL, for a
|
|
96
|
+
* receiver naming the declaring module itself (`Self`, its own name). A call
|
|
97
|
+
* through an import alias is another module's reference and is not matched.
|
|
98
|
+
* `text` is the document text the CEL offsets are measured against. */
|
|
99
|
+
export function functionCallSites(doc, text, receivers, name) {
|
|
100
|
+
const sites = [];
|
|
101
|
+
if (!doc.root)
|
|
102
|
+
return sites;
|
|
103
|
+
eachScalar(doc.root, (scalar) => {
|
|
104
|
+
for (const segment of scalar.celSegments()) {
|
|
105
|
+
let ast;
|
|
106
|
+
try {
|
|
107
|
+
ast = segment.ast();
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (!(error instanceof CelParseError))
|
|
111
|
+
throw error;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
for (const site of moduleCallSites(text, ast, (receiver) => receivers.has(receiver))) {
|
|
115
|
+
if (site.name === name)
|
|
116
|
+
sites.push({ range: site.nameRange });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return sites;
|
|
121
|
+
}
|
|
94
122
|
/** A step's references within its declaring document: `steps.<name>` in CEL.
|
|
95
123
|
* Deliberately document-scoped — `steps.<name>.result` is readable only inside
|
|
96
124
|
* the resource whose body declares the step, and a resource is one document. */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signature help inside a CEL body: the signature of the module call the cursor
|
|
3
|
+
* is writing the arguments of, with the argument under the cursor active.
|
|
4
|
+
*
|
|
5
|
+
* TEXTUAL, like completion's cursor chain, because it fires while the call is
|
|
6
|
+
* unfinished — `Billing.total(items, ` does not parse — and a parse-first
|
|
7
|
+
* approach would go silent exactly when help is asked for. The scan tracks
|
|
8
|
+
* brackets and string literals, so a comma inside a list or a quoted string is
|
|
9
|
+
* not an argument separator. Which receivers are modules, and what a call binds
|
|
10
|
+
* to, stay the analyzer's answers.
|
|
11
|
+
*/
|
|
12
|
+
import { type AstDocument, type ManifestAnalysis } from "@telorun/analyzer";
|
|
13
|
+
import type { SignatureHelpResult } from "../types.js";
|
|
14
|
+
export declare function buildSignatureHelp(text: string, line: number, character: number, docs: AstDocument[] | undefined, analysis: ManifestAnalysis | undefined): SignatureHelpResult | undefined;
|
|
15
|
+
//# sourceMappingURL=build-signature-help.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-signature-help.d.ts","sourceRoot":"","sources":["../../src/signature-help/build-signature-help.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AA2CvD,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,WAAW,EAAE,GAAG,SAAS,EAC/B,QAAQ,EAAE,gBAAgB,GAAG,SAAS,GACrC,mBAAmB,GAAG,SAAS,CAkCjC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signature help inside a CEL body: the signature of the module call the cursor
|
|
3
|
+
* is writing the arguments of, with the argument under the cursor active.
|
|
4
|
+
*
|
|
5
|
+
* TEXTUAL, like completion's cursor chain, because it fires while the call is
|
|
6
|
+
* unfinished — `Billing.total(items, ` does not parse — and a parse-first
|
|
7
|
+
* approach would go silent exactly when help is asked for. The scan tracks
|
|
8
|
+
* brackets and string literals, so a comma inside a list or a quoted string is
|
|
9
|
+
* not an argument separator. Which receivers are modules, and what a call binds
|
|
10
|
+
* to, stay the analyzer's answers.
|
|
11
|
+
*/
|
|
12
|
+
import { parseToAst, } from "@telorun/analyzer";
|
|
13
|
+
import { bodyStart } from "../cel/cursor-chain.js";
|
|
14
|
+
import { describeFunction, functionSignature } from "../cel/module-calls.js";
|
|
15
|
+
import { resolveNodeAtPosition } from "../completions/resolve-node.js";
|
|
16
|
+
import { docIdentity } from "../doc-identity.js";
|
|
17
|
+
/** `Receiver.name` immediately before a `(`, where the receiver is a bare
|
|
18
|
+
* identifier — not a member of something else, which templating reads as an
|
|
19
|
+
* ordinary method call (`a.Billing.format(`). */
|
|
20
|
+
const CALLEE = /(?<![\w.])([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\s*$/;
|
|
21
|
+
/** The innermost bracket still open at the end of `source`. */
|
|
22
|
+
function openFrame(source) {
|
|
23
|
+
const stack = [];
|
|
24
|
+
for (let i = 0; i < source.length; i++) {
|
|
25
|
+
const c = source[i];
|
|
26
|
+
if (c === '"' || c === "'") {
|
|
27
|
+
// Skip the literal, honouring escapes; an unterminated one runs to the end.
|
|
28
|
+
for (i++; i < source.length && source[i] !== c; i++) {
|
|
29
|
+
if (source[i] === "\\")
|
|
30
|
+
i++;
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (c === "(") {
|
|
35
|
+
const match = CALLEE.exec(source.slice(0, i));
|
|
36
|
+
stack.push({ ...(match ? { call: { receiver: match[1], name: match[2] } } : {}), commas: 0 });
|
|
37
|
+
}
|
|
38
|
+
else if (c === "[" || c === "{") {
|
|
39
|
+
stack.push({ commas: 0 });
|
|
40
|
+
}
|
|
41
|
+
else if (c === ")" || c === "]" || c === "}") {
|
|
42
|
+
stack.pop();
|
|
43
|
+
}
|
|
44
|
+
else if (c === "," && stack.length > 0) {
|
|
45
|
+
stack[stack.length - 1].commas++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return stack[stack.length - 1];
|
|
49
|
+
}
|
|
50
|
+
export function buildSignatureHelp(text, line, character, docs, analysis) {
|
|
51
|
+
const query = analysis?.celScope;
|
|
52
|
+
if (!query)
|
|
53
|
+
return undefined;
|
|
54
|
+
const astDocs = docs ?? parseToAst(text);
|
|
55
|
+
const resolved = resolveNodeAtPosition(text, astDocs, line, character);
|
|
56
|
+
if (!resolved?.cel)
|
|
57
|
+
return undefined;
|
|
58
|
+
const { segment, offset } = resolved.cel;
|
|
59
|
+
const start = bodyStart(text, segment);
|
|
60
|
+
const frame = openFrame(segment.source.slice(0, Math.max(0, offset - start)));
|
|
61
|
+
if (!frame?.call)
|
|
62
|
+
return undefined;
|
|
63
|
+
const identity = docIdentity(astDocs[resolved.docIndex]);
|
|
64
|
+
const resource = query.resourceFor(identity.kind, identity.name);
|
|
65
|
+
if (!resource)
|
|
66
|
+
return undefined;
|
|
67
|
+
const scope = query.scopeAt(resource, resolved.concretePath ?? "");
|
|
68
|
+
if (!scope.moduleNames.has(frame.call.receiver))
|
|
69
|
+
return undefined;
|
|
70
|
+
const qualified = `${frame.call.receiver}.${frame.call.name}`;
|
|
71
|
+
const fn = scope.moduleFunction(qualified);
|
|
72
|
+
if (!fn)
|
|
73
|
+
return undefined;
|
|
74
|
+
const signature = functionSignature(qualified, fn);
|
|
75
|
+
const documentation = describeFunction(fn, scope.moduleCallFlags(qualified));
|
|
76
|
+
return {
|
|
77
|
+
signatures: [
|
|
78
|
+
{
|
|
79
|
+
label: signature.label,
|
|
80
|
+
parameters: signature.parameters,
|
|
81
|
+
...(documentation ? { documentation } : {}),
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
activeSignature: 0,
|
|
85
|
+
activeParameter: Math.min(frame.commas, Math.max(0, fn.params.length - 1)),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/signature-help/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { buildSignatureHelp } from "./build-signature-help.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { AnalysisRegistry, DiagnosticSeverity, DiagnosticTag } from "@telorun/analyzer";
|
|
2
2
|
export type { Position, Range, AnalysisDiagnostic, PositionIndex, } from "@telorun/analyzer";
|
|
3
|
-
import type { AnalysisRegistry, DiagnosticSeverity, DiagnosticTag, Position, PositionIndex, Range } from "@telorun/analyzer";
|
|
3
|
+
import type { AnalysisRegistry, DiagnosticFixTag, DiagnosticSeverity, DiagnosticTag, Position, PositionIndex, Range } from "@telorun/analyzer";
|
|
4
4
|
export type CompletionKind = "class" | "enumMember" | "property" | "folder" | "module" | "value";
|
|
5
5
|
/** A source span the host replaces wholesale when a completion is accepted. */
|
|
6
6
|
export interface ReplaceRange {
|
|
@@ -31,6 +31,21 @@ export interface HoverResult {
|
|
|
31
31
|
contents: string;
|
|
32
32
|
range?: ReplaceRange;
|
|
33
33
|
}
|
|
34
|
+
/** Signature help for the call the cursor is inside. Each parameter's `label` is
|
|
35
|
+
* its `[start, end]` span inside the signature's own `label`, the form both
|
|
36
|
+
* VS Code and Monaco highlight the active parameter by. */
|
|
37
|
+
export interface SignatureHelpResult {
|
|
38
|
+
signatures: Array<{
|
|
39
|
+
label: string;
|
|
40
|
+
documentation?: string;
|
|
41
|
+
parameters: Array<{
|
|
42
|
+
label: [number, number];
|
|
43
|
+
documentation?: string;
|
|
44
|
+
}>;
|
|
45
|
+
}>;
|
|
46
|
+
activeSignature: number;
|
|
47
|
+
activeParameter: number;
|
|
48
|
+
}
|
|
34
49
|
/** Semantic token type names emitted by `buildSemanticTokens`. Kept to the
|
|
35
50
|
* standard VS Code / LSP set so hosts register them against a stock legend and
|
|
36
51
|
* every theme colors them without extra configuration.
|
|
@@ -118,10 +133,12 @@ export interface NormalizedDiagnostic {
|
|
|
118
133
|
source: string;
|
|
119
134
|
message: string;
|
|
120
135
|
/** Mechanically applicable repairs. `replacement` is the whole corrected
|
|
121
|
-
* value at the diagnostic's range — apply it by replacing that range
|
|
136
|
+
* value at the diagnostic's range — apply it by replacing that range,
|
|
137
|
+
* rendered through `renderFixReplacement` with `tag` when one is present. */
|
|
122
138
|
suggestions?: Array<{
|
|
123
139
|
kind: "replace";
|
|
124
140
|
replacement: string;
|
|
141
|
+
tag?: DiagnosticFixTag;
|
|
125
142
|
}>;
|
|
126
143
|
/** LSP diagnostic tags, carried through verbatim. Orthogonal to `severity`:
|
|
127
144
|
* they say what KIND of thing the range is (deprecated, unnecessary), which
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGxF,YAAY,EACV,QAAQ,EACR,KAAK,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,EACb,QAAQ,EACR,aAAa,EACb,KAAK,EACN,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEjG,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,QAAQ,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;uBAKmB;IACnB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;+EAC+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;+BAqB+B;AAC/B,MAAM,MAAM,iBAAiB,GACzB,MAAM,GACN,WAAW,GACX,UAAU,GACV,UAAU,GACV,UAAU,GACV,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,UAAU,GACV,WAAW,CAAC;AAEhB;;;;+BAI+B;AAC/B,eAAO,MAAM,qBAAqB,EAAE,SAAS,iBAAiB,EAW7D,CAAC;AAEF;;sEAEsE;AACtE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED;;;wDAGwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;wDAGwD;AACxD,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;uCAMuC;AACvC,MAAM,WAAW,qBAAqB;IACpC;;2DAEuD;IACvD,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpD;kEAC8D;IAC9D,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C;;;oFAGgF;IAChF,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C;;0CAEsC;IACtC,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/ide-support",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "Editor-host-agnostic IDE support (completions, diagnostic normalization) for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"yaml": "^2.8.3",
|
|
40
|
-
"@telorun/analyzer": "0.
|
|
40
|
+
"@telorun/analyzer": "0.77.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^20.0.0",
|
package/src/cel/cursor-chain.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface CelCursorChain {
|
|
|
24
24
|
/** Where `segment.source` starts in document offsets. The segment range spans
|
|
25
25
|
* the delimiters (`${{ … }}`) and any trimmed whitespace, so the body has to be
|
|
26
26
|
* located inside it rather than assumed to start at `range[0]`. */
|
|
27
|
-
function bodyStart(text: string, segment: CelSegment): number {
|
|
27
|
+
export function bodyStart(text: string, segment: CelSegment): number {
|
|
28
28
|
const span = text.slice(segment.range[0], segment.range[1]);
|
|
29
29
|
const at = span.indexOf(segment.source);
|
|
30
30
|
return at < 0 ? segment.range[0] : segment.range[0] + at;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **Module calls inside a CEL body** — `Self.total(items)`, `Billing.isStale(x)`.
|
|
3
|
+
*
|
|
4
|
+
* In the tree an editor parses, a module call is a receiver-style call whose
|
|
5
|
+
* receiver is a plain identifier naming a module: `Self`, the module's own name
|
|
6
|
+
* or an import alias. Which identifiers those are is the scope's answer
|
|
7
|
+
* (`CelScope.moduleNames`), never a guess from the spelling, and what a call
|
|
8
|
+
* resolves to is the analyzer's (`CelScope.moduleFunction`), so completion,
|
|
9
|
+
* hover, signature help and go-to-declaration all name the function `telo check`
|
|
10
|
+
* binds the call to.
|
|
11
|
+
*/
|
|
12
|
+
import {
|
|
13
|
+
renderChain,
|
|
14
|
+
type CallableFlags,
|
|
15
|
+
type CelNode,
|
|
16
|
+
type CelScope,
|
|
17
|
+
type ResolvedFunction,
|
|
18
|
+
} from "@telorun/analyzer";
|
|
19
|
+
import { walkCel } from "../cel-chain.js";
|
|
20
|
+
import { schemaTypeName } from "./symbols.js";
|
|
21
|
+
|
|
22
|
+
/** One module call written in a CEL body. */
|
|
23
|
+
export interface ModuleCallSite {
|
|
24
|
+
node: Extract<CelNode, { kind: "methodCall" }>;
|
|
25
|
+
receiver: string;
|
|
26
|
+
name: string;
|
|
27
|
+
/** `Receiver.name`, as the analyzer keys a call. */
|
|
28
|
+
qualified: string;
|
|
29
|
+
receiverRange: [number, number];
|
|
30
|
+
/** The function name alone, in document offsets. */
|
|
31
|
+
nameRange: [number, number];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The span of a method's name after its receiver, read off the document text —
|
|
35
|
+
* the parsed node carries no span for the name itself. */
|
|
36
|
+
function nameRangeOf(
|
|
37
|
+
text: string,
|
|
38
|
+
node: Extract<CelNode, { kind: "methodCall" }>,
|
|
39
|
+
): [number, number] | undefined {
|
|
40
|
+
const at = text.indexOf(node.name, node.receiver.range[1]);
|
|
41
|
+
return at >= 0 && at + node.name.length <= node.range[1] ? [at, at + node.name.length] : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Whether `node` is a module call: a method call whose receiver is a plain
|
|
45
|
+
* identifier `isModule` accepts. The one recognizer every editor feature reads,
|
|
46
|
+
* mirroring the templating rule — `a.Billing.format(x)` is an ordinary method
|
|
47
|
+
* call, since its receiver is a field access. */
|
|
48
|
+
export function isModuleCallNode(
|
|
49
|
+
node: CelNode,
|
|
50
|
+
isModule: (receiver: string) => boolean,
|
|
51
|
+
): node is Extract<CelNode, { kind: "methodCall" }> & {
|
|
52
|
+
receiver: Extract<CelNode, { kind: "ident" }>;
|
|
53
|
+
} {
|
|
54
|
+
return node.kind === "methodCall" && node.receiver.kind === "ident" && isModule(node.receiver.name);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Every call in `ast` whose receiver `isModule` accepts. */
|
|
58
|
+
export function moduleCallSites(
|
|
59
|
+
text: string,
|
|
60
|
+
ast: CelNode,
|
|
61
|
+
isModule: (receiver: string) => boolean,
|
|
62
|
+
): ModuleCallSite[] {
|
|
63
|
+
const out: ModuleCallSite[] = [];
|
|
64
|
+
walkCel(ast, (node) => {
|
|
65
|
+
if (!isModuleCallNode(node, isModule)) return;
|
|
66
|
+
const nameRange = nameRangeOf(text, node);
|
|
67
|
+
if (!nameRange) return;
|
|
68
|
+
out.push({
|
|
69
|
+
node,
|
|
70
|
+
receiver: node.receiver.name,
|
|
71
|
+
name: node.name,
|
|
72
|
+
qualified: `${node.receiver.name}.${node.name}`,
|
|
73
|
+
receiverRange: node.receiver.range,
|
|
74
|
+
nameRange,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The module call whose receiver or name the cursor is on — the innermost, when
|
|
81
|
+
* one call's argument holds another. */
|
|
82
|
+
export function moduleCallAt(
|
|
83
|
+
text: string,
|
|
84
|
+
ast: CelNode,
|
|
85
|
+
offset: number,
|
|
86
|
+
scope: CelScope,
|
|
87
|
+
): (ModuleCallSite & { onReceiver: boolean }) | undefined {
|
|
88
|
+
const within = (range: [number, number]) => offset >= range[0] && offset <= range[1];
|
|
89
|
+
let hit: (ModuleCallSite & { onReceiver: boolean }) | undefined;
|
|
90
|
+
for (const site of moduleCallSites(text, ast, (receiver) => scope.moduleNames.has(receiver))) {
|
|
91
|
+
if (within(site.nameRange)) hit = { ...site, onReceiver: false };
|
|
92
|
+
else if (within(site.receiverRange)) hit = { ...site, onReceiver: true };
|
|
93
|
+
}
|
|
94
|
+
return hit;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A function's signature as a reader sees it, with each parameter's span inside
|
|
98
|
+
* the label — the shape signature help highlights the active parameter by. */
|
|
99
|
+
export function functionSignature(
|
|
100
|
+
qualified: string,
|
|
101
|
+
fn: ResolvedFunction,
|
|
102
|
+
): { label: string; parameters: Array<{ label: [number, number]; documentation?: string }> } {
|
|
103
|
+
let label = `${qualified}(`;
|
|
104
|
+
const parameters: Array<{ label: [number, number]; documentation?: string }> = [];
|
|
105
|
+
fn.params.forEach((param, index) => {
|
|
106
|
+
if (index > 0) label += ", ";
|
|
107
|
+
const text = `${param.name}${param.optional ? "?" : ""}: ${schemaTypeName(param.schema) ?? "dyn"}`;
|
|
108
|
+
const description = param.schema?.description;
|
|
109
|
+
parameters.push({
|
|
110
|
+
label: [label.length, label.length + text.length],
|
|
111
|
+
...(typeof description === "string" ? { documentation: description } : {}),
|
|
112
|
+
});
|
|
113
|
+
label += text;
|
|
114
|
+
});
|
|
115
|
+
label += `) → ${schemaTypeName(fn.returns) ?? fn.celType}`;
|
|
116
|
+
return { label, parameters };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** What a reader is told a function promises: its description and derived
|
|
120
|
+
* determinism, naming the chain to the leaf that decided it. */
|
|
121
|
+
export function describeFunction(fn: ResolvedFunction, flags: CallableFlags | undefined): string {
|
|
122
|
+
const lines: string[] = [];
|
|
123
|
+
const description = (fn.manifest.metadata as { description?: unknown } | undefined)?.description;
|
|
124
|
+
if (typeof description === "string" && description) lines.push(description);
|
|
125
|
+
if (flags) {
|
|
126
|
+
const facts: string[] = [];
|
|
127
|
+
facts.push(
|
|
128
|
+
flags.deterministic
|
|
129
|
+
? "deterministic"
|
|
130
|
+
: `non-deterministic${flags.nondeterministicVia.length ? ` (${renderChain(flags.nondeterministicVia)})` : ""}`,
|
|
131
|
+
);
|
|
132
|
+
if (flags.hostBacked) {
|
|
133
|
+
facts.push(`host-backed${flags.hostBackedVia.length ? ` (${renderChain(flags.hostBackedVia)})` : ""}`);
|
|
134
|
+
}
|
|
135
|
+
lines.push(facts.join(" · "));
|
|
136
|
+
}
|
|
137
|
+
return lines.join("\n\n");
|
|
138
|
+
}
|
package/src/cel/tokens.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { CelParseError, type CelNode, type CelScope, type CelSegment } from "@telorun/analyzer";
|
|
17
17
|
import type { SemanticTokenType } from "../types.js";
|
|
18
18
|
import { flattenChain } from "../cel-chain.js";
|
|
19
|
+
import { isModuleCallNode } from "./module-calls.js";
|
|
19
20
|
import { celRootSymbols, celSymbolAt } from "./symbols.js";
|
|
20
21
|
|
|
21
22
|
/** A token before it is placed on a line — document offsets, resolved by the
|
|
@@ -124,7 +125,13 @@ export function celSegmentTokens(
|
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
126
127
|
case "methodCall": {
|
|
127
|
-
|
|
128
|
+
// A module call's receiver is a module — a namespace a function is called
|
|
129
|
+
// through — and never a value the scope could confirm as a root name.
|
|
130
|
+
if (scope && isModuleCallNode(node, (receiver) => scope.moduleNames.has(receiver))) {
|
|
131
|
+
out.push({ range: node.receiver.range, type: "namespace" });
|
|
132
|
+
} else {
|
|
133
|
+
visit(node.receiver);
|
|
134
|
+
}
|
|
128
135
|
const span = spanOf(node.name, node.receiver.range[1], node.range[1]);
|
|
129
136
|
if (span) out.push({ range: span, type: "function" });
|
|
130
137
|
for (const arg of node.args) visit(arg);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import type { CelScopeQuery, CelSegment } from "@telorun/analyzer";
|
|
11
11
|
import type { CompletionResult, ReplaceRange } from "../types.js";
|
|
12
12
|
import { celCursorChain } from "../cel/cursor-chain.js";
|
|
13
|
+
import { describeFunction, functionSignature } from "../cel/module-calls.js";
|
|
13
14
|
import { celFunctions, celMemberSymbols, celRootSymbols, type CelSymbol } from "../cel/symbols.js";
|
|
14
15
|
|
|
15
16
|
/** The resource a cursor's document addresses. */
|
|
@@ -62,6 +63,20 @@ export function celCompletions(
|
|
|
62
63
|
const prefix = chain?.prefix ?? [];
|
|
63
64
|
|
|
64
65
|
if (chain?.member) {
|
|
66
|
+
// `<Module>.` offers the functions that module makes callable here — its own
|
|
67
|
+
// through `Self` or its name, an import's exported ones through the alias.
|
|
68
|
+
if (prefix.length === 1 && scope.moduleNames.has(prefix[0]!)) {
|
|
69
|
+
return scope.moduleFunctionsOf(prefix[0]!).map(({ name, function: fn }) => {
|
|
70
|
+
const qualified = `${prefix[0]}.${name}`;
|
|
71
|
+
const about = describeFunction(fn, scope.moduleCallFlags(qualified));
|
|
72
|
+
return {
|
|
73
|
+
label: name,
|
|
74
|
+
kind: "value" as const,
|
|
75
|
+
detail: functionSignature(qualified, fn).label,
|
|
76
|
+
...(about ? { documentation: about } : {}),
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
65
80
|
return celMemberSymbols(scope, prefix).map((s) => toResult(s));
|
|
66
81
|
}
|
|
67
82
|
|
|
@@ -4,6 +4,8 @@ export const CAPABILITY_VALUES = [
|
|
|
4
4
|
"Telo.Invocable",
|
|
5
5
|
"Telo.Provider",
|
|
6
6
|
"Telo.Mount",
|
|
7
|
+
"Telo.Sink",
|
|
8
|
+
"Telo.Callable",
|
|
7
9
|
"Telo.Type",
|
|
8
10
|
] as const;
|
|
9
11
|
|
|
@@ -15,5 +17,9 @@ export const CAPABILITY_DOCS: Record<string, string> = {
|
|
|
15
17
|
"Telo.Invocable": "Request handler: `invoke(inputs)` (scripts, endpoints).",
|
|
16
18
|
"Telo.Provider": "Value-flow source: `init()` + optional `provide()` (config, secrets).",
|
|
17
19
|
"Telo.Mount": "Mounted into a Service (HTTP APIs, middleware).",
|
|
20
|
+
"Telo.Sink":
|
|
21
|
+
"Record-stream destination: `write(record)` + `flush()` / `flushSync()` / `close()`, written to directly rather than dispatched.",
|
|
22
|
+
"Telo.Callable":
|
|
23
|
+
"Function: synchronous `call(args)` against a declared `params` / `returns` signature, called from CEL through a module name.",
|
|
18
24
|
"Telo.Type": "Pure schema definition — no runtime instance.",
|
|
19
25
|
};
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CelParseError,
|
|
2
3
|
parseToAst,
|
|
3
4
|
type AstDocument,
|
|
4
5
|
type AstScalar,
|
|
6
|
+
type CelNode,
|
|
5
7
|
type ManifestAnalysis,
|
|
6
8
|
type LoadedGraph,
|
|
9
|
+
type LoadedModule,
|
|
7
10
|
} from "@telorun/analyzer";
|
|
8
11
|
import type { DefinitionResult } from "../types.js";
|
|
12
|
+
import { moduleCallAt } from "../cel/module-calls.js";
|
|
9
13
|
import { resolveNodeAtPosition } from "../completions/resolve-node.js";
|
|
14
|
+
import { docIdentity } from "../doc-identity.js";
|
|
10
15
|
import { splitAliasQualified } from "./alias-qualified-value.js";
|
|
11
16
|
import { locateContextBinding } from "./locate-context-binding.js";
|
|
12
17
|
import { locateStepDeclaration } from "./locate-step.js";
|
|
13
|
-
import { moduleForFile } from "./manifest-navigation.js";
|
|
18
|
+
import { locateImport, moduleForFile } from "./manifest-navigation.js";
|
|
14
19
|
import { resolveCelTarget } from "./resolve-cel-target.js";
|
|
15
20
|
import { isKindSlot, resolveKindTarget } from "./resolve-kind-target.js";
|
|
16
21
|
import { resolveRefTarget } from "./resolve-ref-target.js";
|
|
@@ -50,6 +55,8 @@ export function buildDefinition(
|
|
|
50
55
|
const currentModule = moduleForFile(graph, currentFilePath) ?? graph.entry;
|
|
51
56
|
|
|
52
57
|
if (resolved.cel) {
|
|
58
|
+
const call = moduleCallTarget(graph, currentModule, text, astDocs, resolved, analysis);
|
|
59
|
+
if (call !== NOT_A_MODULE_CALL) return call;
|
|
53
60
|
return resolveCelTarget(graph, currentModule, resolved.cel.segment, resolved.cel.offset, {
|
|
54
61
|
// A step is resolved in the CURRENT document — `steps.<name>.result` is
|
|
55
62
|
// readable only inside the resource that declares it, which is one
|
|
@@ -79,3 +86,44 @@ export function buildDefinition(
|
|
|
79
86
|
if (isKindSlot(resolved)) return resolveKindTarget(graph, currentModule, split);
|
|
80
87
|
return undefined;
|
|
81
88
|
}
|
|
89
|
+
|
|
90
|
+
const NOT_A_MODULE_CALL = Symbol("not a module call");
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A module call under the cursor navigates to the callable resource it binds
|
|
94
|
+
* to — resolved the way a `!ref` of the same spelling is, since a call names a
|
|
95
|
+
* resource through the same grammar — and its alias to the import declaring it.
|
|
96
|
+
* Needs the analysis: which receivers are modules is the scope's answer.
|
|
97
|
+
*/
|
|
98
|
+
function moduleCallTarget(
|
|
99
|
+
graph: LoadedGraph,
|
|
100
|
+
currentModule: LoadedModule,
|
|
101
|
+
text: string,
|
|
102
|
+
docs: AstDocument[],
|
|
103
|
+
resolved: NonNullable<ReturnType<typeof resolveNodeAtPosition>>,
|
|
104
|
+
analysis: ManifestAnalysis | undefined,
|
|
105
|
+
): DefinitionResult | undefined | typeof NOT_A_MODULE_CALL {
|
|
106
|
+
const query = analysis?.celScope;
|
|
107
|
+
if (!resolved.cel || !query) return NOT_A_MODULE_CALL;
|
|
108
|
+
const identity = docIdentity(docs[resolved.docIndex]);
|
|
109
|
+
const resource = query.resourceFor(identity.kind, identity.name);
|
|
110
|
+
if (!resource) return NOT_A_MODULE_CALL;
|
|
111
|
+
let ast: CelNode;
|
|
112
|
+
try {
|
|
113
|
+
ast = resolved.cel.segment.ast();
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (!(error instanceof CelParseError)) throw error;
|
|
116
|
+
return NOT_A_MODULE_CALL;
|
|
117
|
+
}
|
|
118
|
+
const scope = query.scopeAt(resource, resolved.concretePath ?? "");
|
|
119
|
+
const call = moduleCallAt(text, ast, resolved.cel.offset, scope);
|
|
120
|
+
if (!call) return NOT_A_MODULE_CALL;
|
|
121
|
+
const imported = graph.importEdges.get(currentModule.owner.source)?.has(call.receiver) === true;
|
|
122
|
+
if (call.onReceiver) return imported ? locateImport(currentModule, call.receiver) : undefined;
|
|
123
|
+
if (!scope.moduleFunction(call.qualified)) return undefined;
|
|
124
|
+
return resolveRefTarget(graph, currentModule, {
|
|
125
|
+
...(imported ? { alias: call.receiver } : {}),
|
|
126
|
+
name: call.name,
|
|
127
|
+
onAlias: false,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
@@ -19,7 +19,15 @@ export function normalizeDiagnostic(
|
|
|
19
19
|
ctx: DiagnosticContext,
|
|
20
20
|
): NormalizedDiagnostic {
|
|
21
21
|
const fix = diagnosticFix(d);
|
|
22
|
-
const suggestions = fix
|
|
22
|
+
const suggestions = fix
|
|
23
|
+
? [
|
|
24
|
+
{
|
|
25
|
+
kind: "replace" as const,
|
|
26
|
+
replacement: fix.replacement,
|
|
27
|
+
...(fix.tag ? { tag: fix.tag } : {}),
|
|
28
|
+
},
|
|
29
|
+
]
|
|
30
|
+
: undefined;
|
|
23
31
|
|
|
24
32
|
return {
|
|
25
33
|
range: resolveRange(d, ctx),
|
package/src/hover/build-hover.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "@telorun/analyzer";
|
|
11
11
|
import type { HoverResult } from "../types.js";
|
|
12
12
|
import { chainAt } from "../cel-chain.js";
|
|
13
|
+
import { describeFunction, functionSignature, moduleCallAt } from "../cel/module-calls.js";
|
|
13
14
|
import { celSymbolAt } from "../cel/symbols.js";
|
|
14
15
|
import { docIdentity } from "../doc-identity.js";
|
|
15
16
|
import { navigateSchema } from "../completions/detect-context.js";
|
|
@@ -118,7 +119,7 @@ export function buildHover(
|
|
|
118
119
|
if (!resolved) return undefined;
|
|
119
120
|
|
|
120
121
|
if (resolved.cel) {
|
|
121
|
-
const hover = hoverForCel(resolved, astDocs, analysis?.celScope);
|
|
122
|
+
const hover = hoverForCel(text, resolved, astDocs, analysis?.celScope);
|
|
122
123
|
// A CEL body is still a field value; when the cursor is on nothing
|
|
123
124
|
// nameable inside it (an operator, a literal), fall through to the field's
|
|
124
125
|
// own hover rather than reporting nothing.
|
|
@@ -138,6 +139,7 @@ export function buildHover(
|
|
|
138
139
|
* declares it.
|
|
139
140
|
*/
|
|
140
141
|
function hoverForCel(
|
|
142
|
+
text: string,
|
|
141
143
|
resolved: ResolvedCursor,
|
|
142
144
|
docs: AstDocument[],
|
|
143
145
|
scopeQuery: CelScopeQuery | undefined,
|
|
@@ -158,12 +160,24 @@ function hoverForCel(
|
|
|
158
160
|
return undefined;
|
|
159
161
|
}
|
|
160
162
|
|
|
163
|
+
const scope = scopeQuery.scopeAt(resource, resolved.concretePath ?? "");
|
|
164
|
+
// A module call's name hovers as the function it binds to: its signature and
|
|
165
|
+
// what it promises about determinism.
|
|
166
|
+
const call = moduleCallAt(text, ast, resolved.cel.offset, scope);
|
|
167
|
+
if (call && !call.onReceiver) {
|
|
168
|
+
const fn = scope.moduleFunction(call.qualified);
|
|
169
|
+
if (!fn) return undefined;
|
|
170
|
+
const lines = ["```\n" + functionSignature(call.qualified, fn).label + "\n```"];
|
|
171
|
+
const about = describeFunction(fn, scope.moduleCallFlags(call.qualified));
|
|
172
|
+
if (about) lines.push("", about);
|
|
173
|
+
return { contents: lines.join("\n") };
|
|
174
|
+
}
|
|
175
|
+
|
|
161
176
|
const hit = chainAt(ast, resolved.cel.offset);
|
|
162
177
|
if (!hit) return undefined;
|
|
163
178
|
// The chain UP TO the cursor, not the whole chain: hovering `resources` in
|
|
164
179
|
// `resources.db.url` describes `resources`.
|
|
165
180
|
const parts = hit.parts.slice(0, hit.index + 1).map((p) => p.name);
|
|
166
|
-
const scope = scopeQuery.scopeAt(resource, resolved.concretePath ?? "");
|
|
167
181
|
const symbol = celSymbolAt(scope, parts);
|
|
168
182
|
if (!symbol) return undefined;
|
|
169
183
|
|
package/src/index.ts
CHANGED
|
@@ -5,9 +5,13 @@ export * from "./hover/index.js";
|
|
|
5
5
|
export * from "./semantic-tokens/index.js";
|
|
6
6
|
export * from "./definition/index.js";
|
|
7
7
|
export * from "./rename/index.js";
|
|
8
|
+
export * from "./signature-help/index.js";
|
|
8
9
|
export * from "./import-upgrades/index.js";
|
|
9
10
|
export * from "./workspace/index.js";
|
|
10
11
|
// The repo's single CEL-tree walk. Exported because every host that has to
|
|
11
12
|
// answer "where is this name read" needs it and a second copy would be a second
|
|
12
13
|
// answer — the editor asks it before deleting a resource.
|
|
13
14
|
export { walkCel, flattenChain, chainAt, type ChainPart } from "./cel-chain.js";
|
|
15
|
+
// A function's signature as every surface renders it — hover, completion,
|
|
16
|
+
// signature help and `telo cel functions` alike.
|
|
17
|
+
export { functionSignature } from "./cel/module-calls.js";
|