@telorun/ide-support 0.15.0 → 0.16.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 +30 -0
- package/dist/cel/cursor-chain.d.ts.map +1 -0
- package/dist/cel/cursor-chain.js +30 -0
- package/dist/cel/symbols.d.ts +82 -0
- package/dist/cel/symbols.d.ts.map +1 -0
- package/dist/cel/symbols.js +147 -0
- package/dist/cel/tokens.d.ts +32 -0
- package/dist/cel/tokens.d.ts.map +1 -0
- package/dist/cel/tokens.js +162 -0
- package/dist/completions/build.d.ts +6 -2
- package/dist/completions/build.d.ts.map +1 -1
- package/dist/completions/build.js +52 -24
- package/dist/completions/call-inputs.d.ts +25 -0
- package/dist/completions/call-inputs.d.ts.map +1 -0
- package/dist/completions/call-inputs.js +78 -0
- package/dist/completions/cel-completions.d.ts +26 -0
- package/dist/completions/cel-completions.d.ts.map +1 -0
- package/dist/completions/cel-completions.js +78 -0
- package/dist/completions/detect-context.d.ts +47 -8
- package/dist/completions/detect-context.d.ts.map +1 -1
- package/dist/completions/detect-context.js +51 -15
- package/dist/completions/prop-keys.d.ts +5 -1
- package/dist/completions/prop-keys.d.ts.map +1 -1
- package/dist/completions/prop-keys.js +51 -3
- package/dist/completions/resolve-node.d.ts +9 -2
- package/dist/completions/resolve-node.d.ts.map +1 -1
- package/dist/completions/resolve-node.js +63 -21
- package/dist/completions/valid-capabilities.js +1 -1
- package/dist/definition/build-definition.d.ts +6 -2
- package/dist/definition/build-definition.d.ts.map +1 -1
- package/dist/definition/build-definition.js +16 -3
- package/dist/definition/locate-context-binding.d.ts +15 -0
- package/dist/definition/locate-context-binding.d.ts.map +1 -0
- package/dist/definition/locate-context-binding.js +35 -0
- package/dist/definition/locate-step.d.ts +13 -0
- package/dist/definition/locate-step.d.ts.map +1 -0
- package/dist/definition/locate-step.js +33 -0
- package/dist/definition/resolve-cel-target.d.ts +11 -1
- package/dist/definition/resolve-cel-target.d.ts.map +1 -1
- package/dist/definition/resolve-cel-target.js +14 -14
- package/dist/doc-identity.d.ts +17 -0
- package/dist/doc-identity.d.ts.map +1 -0
- package/dist/doc-identity.js +19 -0
- package/dist/hover/build-hover.d.ts +6 -2
- package/dist/hover/build-hover.d.ts.map +1 -1
- package/dist/hover/build-hover.js +64 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/semantic-tokens/build-semantic-tokens.d.ts +13 -8
- package/dist/semantic-tokens/build-semantic-tokens.d.ts.map +1 -1
- package/dist/semantic-tokens/build-semantic-tokens.js +81 -37
- package/dist/types.d.ts +25 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +16 -2
- package/package.json +2 -2
- package/src/cel/cursor-chain.ts +58 -0
- package/src/cel/symbols.ts +189 -0
- package/src/cel/tokens.ts +169 -0
- package/src/completions/build.ts +85 -22
- package/src/completions/call-inputs.ts +92 -0
- package/src/completions/cel-completions.ts +108 -0
- package/src/completions/detect-context.ts +107 -13
- package/src/completions/prop-keys.ts +59 -2
- package/src/completions/resolve-node.ts +82 -17
- package/src/completions/valid-capabilities.ts +1 -1
- package/src/definition/build-definition.ts +30 -2
- package/src/definition/locate-context-binding.ts +53 -0
- package/src/definition/locate-step.ts +50 -0
- package/src/definition/resolve-cel-target.ts +25 -0
- package/src/doc-identity.ts +31 -0
- package/src/hover/build-hover.ts +67 -1
- package/src/index.ts +4 -0
- package/src/semantic-tokens/build-semantic-tokens.ts +84 -30
- package/src/types.ts +47 -6
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { type AnalysisRegistry, type AstDocument } from "@telorun/analyzer";
|
|
1
|
+
import { type AnalysisRegistry, type AstDocument, type ManifestAnalysis } from "@telorun/analyzer";
|
|
2
2
|
import type { SemanticToken } from "../types.js";
|
|
3
3
|
/** Registry-aware semantic tokens: a `kind:` value that resolves to a known
|
|
4
4
|
* definition is a `type`; a `capability:` value is an `interface`; a `!ref`
|
|
5
|
-
* target is a `variable`.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
5
|
+
* target is a `variable`. Ref targets are colored here rather than in the
|
|
6
|
+
* grammar because a `!ref` after a `key:` is tokenized by the bundled YAML
|
|
7
|
+
* grammar before a Telo pattern can claim it — the AST sees it unambiguously.
|
|
8
|
+
* An unresolved kind gets no token, so a typo stays uncolored — a quiet signal
|
|
9
|
+
* that pairs with the analyzer's `UNDEFINED_KIND` diagnostic.
|
|
10
|
+
*
|
|
11
|
+
* The inside of a `!cel` / `${{ }}` body is colored here too, and for the same
|
|
12
|
+
* reason one level down: a grammar can only know the roots someone hardcoded
|
|
13
|
+
* into it, while `scopeQuery` knows what is in scope at this exact site. With
|
|
14
|
+
* no query the names are colored syntactically instead — a CEL body must never
|
|
15
|
+
* read as a plain string, which is what the stock YAML grammar makes of it. */
|
|
16
|
+
export declare function buildSemanticTokens(text: string, registry: AnalysisRegistry | undefined, docs?: AstDocument[], analysis?: ManifestAnalysis): SemanticToken[];
|
|
12
17
|
//# sourceMappingURL=build-semantic-tokens.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-semantic-tokens.d.ts","sourceRoot":"","sources":["../../src/semantic-tokens/build-semantic-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"build-semantic-tokens.d.ts","sourceRoot":"","sources":["../../src/semantic-tokens/build-semantic-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAIhB,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAajD;;;;;;;;;;;;gFAYgF;AAChF,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,gBAAgB,GAAG,SAAS,EACtC,IAAI,CAAC,EAAE,WAAW,EAAE,EACpB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,aAAa,EAAE,CA2EjB"}
|
|
@@ -1,55 +1,99 @@
|
|
|
1
1
|
import { buildLineOffsets, offsetToPosition, parseToAst, } from "@telorun/analyzer";
|
|
2
|
+
import { celSegmentTokens } from "../cel/tokens.js";
|
|
3
|
+
import { docIdentity } from "../doc-identity.js";
|
|
2
4
|
import { scalarString } from "../completions/resolve-node.js";
|
|
3
5
|
import { CAPABILITY_VALUES } from "../completions/valid-capabilities.js";
|
|
4
6
|
const CAPABILITIES = new Set(CAPABILITY_VALUES);
|
|
7
|
+
/** Append one key segment to a concrete path (`routes[0]` + `handler`). */
|
|
8
|
+
function joinKey(concrete, key) {
|
|
9
|
+
return concrete ? `${concrete}.${key}` : key;
|
|
10
|
+
}
|
|
5
11
|
/** Registry-aware semantic tokens: a `kind:` value that resolves to a known
|
|
6
12
|
* definition is a `type`; a `capability:` value is an `interface`; a `!ref`
|
|
7
|
-
* target is a `variable`.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
13
|
+
* target is a `variable`. Ref targets are colored here rather than in the
|
|
14
|
+
* grammar because a `!ref` after a `key:` is tokenized by the bundled YAML
|
|
15
|
+
* grammar before a Telo pattern can claim it — the AST sees it unambiguously.
|
|
16
|
+
* An unresolved kind gets no token, so a typo stays uncolored — a quiet signal
|
|
17
|
+
* that pairs with the analyzer's `UNDEFINED_KIND` diagnostic.
|
|
18
|
+
*
|
|
19
|
+
* The inside of a `!cel` / `${{ }}` body is colored here too, and for the same
|
|
20
|
+
* reason one level down: a grammar can only know the roots someone hardcoded
|
|
21
|
+
* into it, while `scopeQuery` knows what is in scope at this exact site. With
|
|
22
|
+
* no query the names are colored syntactically instead — a CEL body must never
|
|
23
|
+
* read as a plain string, which is what the stock YAML grammar makes of it. */
|
|
24
|
+
export function buildSemanticTokens(text, registry, docs, analysis) {
|
|
14
25
|
const astDocs = docs ?? parseToAst(text);
|
|
15
26
|
const lineOffsets = buildLineOffsets(text);
|
|
16
27
|
const tokens = [];
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// Kind / capability values never span lines; a clamped single-line token.
|
|
28
|
+
const emitRange = (range, type) => {
|
|
29
|
+
const start = offsetToPosition(range[0], lineOffsets);
|
|
30
|
+
const end = offsetToPosition(range[1], lineOffsets);
|
|
31
|
+
// Kind / capability values and CEL identifiers never span lines; a clamped
|
|
32
|
+
// single-line token.
|
|
23
33
|
if (start.line !== end.line)
|
|
24
34
|
return;
|
|
25
|
-
tokens.push({
|
|
35
|
+
tokens.push({
|
|
36
|
+
line: start.line,
|
|
37
|
+
character: start.character,
|
|
38
|
+
length: end.character - start.character,
|
|
39
|
+
type,
|
|
40
|
+
});
|
|
26
41
|
};
|
|
27
|
-
const
|
|
28
|
-
if (node
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
const emit = (node, type) => {
|
|
43
|
+
if (node)
|
|
44
|
+
emitRange(node.range, type);
|
|
45
|
+
};
|
|
46
|
+
for (const doc of astDocs) {
|
|
47
|
+
if (!doc.root)
|
|
48
|
+
continue;
|
|
49
|
+
// The scope is resolved per SITE. `scopeAt` caches per (resource, path) for
|
|
50
|
+
// the analysis's lifetime, which is what keeps a whole-file colourizer off
|
|
51
|
+
// the per-keystroke cost of rebuilding a context-matched environment.
|
|
52
|
+
const identity = docIdentity(doc);
|
|
53
|
+
const scopeQuery = analysis?.celScope;
|
|
54
|
+
const resource = scopeQuery?.resourceFor(identity.kind, identity.name);
|
|
55
|
+
const scopeAt = (path) => scopeQuery && resource ? scopeQuery.scopeAt(resource, path) : undefined;
|
|
56
|
+
const celTokens = (node, path) => {
|
|
57
|
+
const segments = node.celSegments();
|
|
58
|
+
if (segments.length === 0)
|
|
59
|
+
return;
|
|
60
|
+
const scope = scopeAt(path);
|
|
61
|
+
for (const segment of segments) {
|
|
62
|
+
for (const span of celSegmentTokens(text, segment, scope))
|
|
63
|
+
emitRange(span.range, span.type);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const walk = (node, concrete) => {
|
|
67
|
+
if (node.kind === "map") {
|
|
68
|
+
for (const pair of node.entries) {
|
|
69
|
+
const key = scalarString(pair.key);
|
|
70
|
+
const value = scalarString(pair.value);
|
|
71
|
+
if (key === "kind" && value && registry?.resolveDefinition(value)) {
|
|
72
|
+
emit(pair.value, "type");
|
|
73
|
+
}
|
|
74
|
+
else if (key === "capability" && value && CAPABILITIES.has(value)) {
|
|
75
|
+
emit(pair.value, "interface");
|
|
76
|
+
}
|
|
77
|
+
if (pair.value)
|
|
78
|
+
walk(pair.value, key != null ? joinKey(concrete, key) : concrete);
|
|
34
79
|
}
|
|
35
|
-
|
|
36
|
-
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (node.kind === "seq") {
|
|
83
|
+
// Indices are kept: a CEL site's scope is addressed per item, so an
|
|
84
|
+
// index-erased path resolves the wrong context or none.
|
|
85
|
+
node.items.forEach((item, index) => walk(item, `${concrete}[${index}]`));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (node.kind === "scalar") {
|
|
89
|
+
if (node.tag === "!ref") {
|
|
90
|
+
emit(node, "variable");
|
|
91
|
+
return;
|
|
37
92
|
}
|
|
38
|
-
|
|
39
|
-
walk(pair.value);
|
|
93
|
+
celTokens(node, concrete);
|
|
40
94
|
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
for (const item of node.items)
|
|
44
|
-
walk(item);
|
|
45
|
-
}
|
|
46
|
-
else if (node.kind === "scalar" && node.tag === "!ref") {
|
|
47
|
-
emit(node, "variable");
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
for (const doc of astDocs) {
|
|
51
|
-
if (doc.root)
|
|
52
|
-
walk(doc.root);
|
|
95
|
+
};
|
|
96
|
+
walk(doc.root, "");
|
|
53
97
|
}
|
|
54
98
|
return tokens;
|
|
55
99
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -33,12 +33,32 @@ export interface HoverResult {
|
|
|
33
33
|
}
|
|
34
34
|
/** Semantic token type names emitted by `buildSemanticTokens`. Kept to the
|
|
35
35
|
* standard VS Code / LSP set so hosts register them against a stock legend and
|
|
36
|
-
* every theme colors them without extra configuration.
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
36
|
+
* every theme colors them without extra configuration.
|
|
37
|
+
*
|
|
38
|
+
* Manifest structure: `type` marks a resolved resource kind; `interface` marks
|
|
39
|
+
* a capability value; `variable` marks a `!ref` target.
|
|
40
|
+
*
|
|
41
|
+
* Inside a CEL body: `namespace` marks the ROOT of a chain, `property` a member
|
|
42
|
+
* it can resolve, `function` a call, and `number` / `string` / `keyword` /
|
|
43
|
+
* `operator` the syntax around them. A CEL name the scope CANNOT confirm gets
|
|
44
|
+
* no token — the same quiet signal an unresolved `kind:` gives, pairing with
|
|
45
|
+
* the analyzer's `CEL_UNKNOWN_FIELD`.
|
|
46
|
+
*
|
|
47
|
+
* The root is a `namespace` rather than a `variable` because colour encodes
|
|
48
|
+
* what a symbol IS, which is the invariant every language holds to — and a CEL
|
|
49
|
+
* root is not data the author declared, it is a scope the runtime injects
|
|
50
|
+
* (`request`, `steps`, `variables`, `self`). Members are uniformly `property`
|
|
51
|
+
* however deep, so a chain reads as scope · path. Colouring by the SHAPE of the
|
|
52
|
+
* value behind a name — object vs scalar — was considered and rejected: it is
|
|
53
|
+
* type-directed highlighting, so the palette becomes a type legend, a name
|
|
54
|
+
* changes colour as analysis resolves, and it says nothing exactly where the
|
|
55
|
+
* scope declares no shape. */
|
|
56
|
+
export type SemanticTokenType = "type" | "interface" | "variable" | "property" | "function" | "number" | "string" | "keyword" | "operator" | "namespace";
|
|
40
57
|
/** The legend a host registers before mapping `buildSemanticTokens` output. The
|
|
41
|
-
* numeric token-type of each `SemanticToken` is its index in this array
|
|
58
|
+
* numeric token-type of each `SemanticToken` is its index in this array, and a
|
|
59
|
+
* host registers it once at activation — so new types are APPENDED, never
|
|
60
|
+
* inserted, or an already-registered legend would repaint every existing
|
|
61
|
+
* token as something else. */
|
|
42
62
|
export declare const SEMANTIC_TOKEN_LEGEND: readonly SemanticTokenType[];
|
|
43
63
|
/** One absolute-positioned semantic token. Every Telo semantic token is
|
|
44
64
|
* single-line (kinds and capabilities never wrap), so a `{line, char, length}`
|
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,MAAM,mBAAmB,CAAC;AAGzE,YAAY,EACV,QAAQ,EACR,KAAK,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
|
@@ -2,5 +2,19 @@
|
|
|
2
2
|
// or `DiagnosticSeverity.Error` import from here.
|
|
3
3
|
export { AnalysisRegistry, DiagnosticSeverity } from "@telorun/analyzer";
|
|
4
4
|
/** The legend a host registers before mapping `buildSemanticTokens` output. The
|
|
5
|
-
* numeric token-type of each `SemanticToken` is its index in this array
|
|
6
|
-
|
|
5
|
+
* numeric token-type of each `SemanticToken` is its index in this array, and a
|
|
6
|
+
* host registers it once at activation — so new types are APPENDED, never
|
|
7
|
+
* inserted, or an already-registered legend would repaint every existing
|
|
8
|
+
* token as something else. */
|
|
9
|
+
export const SEMANTIC_TOKEN_LEGEND = [
|
|
10
|
+
"type",
|
|
11
|
+
"interface",
|
|
12
|
+
"variable",
|
|
13
|
+
"property",
|
|
14
|
+
"function",
|
|
15
|
+
"number",
|
|
16
|
+
"string",
|
|
17
|
+
"keyword",
|
|
18
|
+
"operator",
|
|
19
|
+
"namespace",
|
|
20
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/ide-support",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Editor-host-agnostic IDE support (completions, diagnostic normalization) for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"src/**"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@telorun/analyzer": "0.
|
|
39
|
+
"@telorun/analyzer": "0.65.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^20.0.0",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dotted chain the cursor is in the middle of typing.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately TEXTUAL, and only for completion. `chainAt` (over the parsed
|
|
5
|
+
* CEL AST) is what hover and go-to-declaration use, because there the
|
|
6
|
+
* expression is complete and the cursor sits on a specific identifier whose
|
|
7
|
+
* span matters. Completion fires on text that frequently does not parse —
|
|
8
|
+
* `req.` is not an expression — so a parse-first approach would go silent
|
|
9
|
+
* exactly while the user is asking for help. An open segment recovers to its
|
|
10
|
+
* longest parseable PREFIX, which by definition drops the token being typed.
|
|
11
|
+
*/
|
|
12
|
+
import type { CelSegment } from "@telorun/analyzer";
|
|
13
|
+
|
|
14
|
+
export interface CelCursorChain {
|
|
15
|
+
/** Identifiers resolved before the token under the cursor (`req.query.` → `["req","query"]`). */
|
|
16
|
+
prefix: string[];
|
|
17
|
+
/** The partial identifier being typed, possibly empty. */
|
|
18
|
+
token: string;
|
|
19
|
+
/** True when the cursor follows a `.` — a member position, where only the
|
|
20
|
+
* prefix's members are offered and functions are not. */
|
|
21
|
+
member: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Where `segment.source` starts in document offsets. The segment range spans
|
|
25
|
+
* the delimiters (`${{ … }}`) and any trimmed whitespace, so the body has to be
|
|
26
|
+
* located inside it rather than assumed to start at `range[0]`. */
|
|
27
|
+
function bodyStart(text: string, segment: CelSegment): number {
|
|
28
|
+
const span = text.slice(segment.range[0], segment.range[1]);
|
|
29
|
+
const at = span.indexOf(segment.source);
|
|
30
|
+
return at < 0 ? segment.range[0] : segment.range[0] + at;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const TRAILING_CHAIN = /[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*\.?$/;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Split the text before the cursor into a resolved prefix and a partial token.
|
|
37
|
+
*
|
|
38
|
+
* Returns undefined when the cursor does not follow an identifier-shaped run —
|
|
39
|
+
* after an operator, inside a string literal's content, at the start of an
|
|
40
|
+
* empty body — where the caller offers the root scope instead of a member list.
|
|
41
|
+
*/
|
|
42
|
+
export function celCursorChain(
|
|
43
|
+
text: string,
|
|
44
|
+
segment: CelSegment,
|
|
45
|
+
offset: number,
|
|
46
|
+
): CelCursorChain | undefined {
|
|
47
|
+
const start = bodyStart(text, segment);
|
|
48
|
+
const index = Math.max(0, Math.min(offset - start, segment.source.length));
|
|
49
|
+
const before = segment.source.slice(0, index);
|
|
50
|
+
const match = TRAILING_CHAIN.exec(before);
|
|
51
|
+
if (!match) return undefined;
|
|
52
|
+
const run = match[0];
|
|
53
|
+
if (run.endsWith(".")) {
|
|
54
|
+
return { prefix: run.slice(0, -1).split("."), token: "", member: true };
|
|
55
|
+
}
|
|
56
|
+
const parts = run.split(".");
|
|
57
|
+
return { prefix: parts.slice(0, -1), token: parts[parts.length - 1], member: parts.length > 1 };
|
|
58
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **What a name means inside a CEL expression.**
|
|
3
|
+
*
|
|
4
|
+
* The TYPE half of CEL language support — completion's candidate list and
|
|
5
|
+
* hover's tooltip are both this, read off the scope the analyzer resolved. It
|
|
6
|
+
* is deliberately separate from the DECLARATION half (`definition/`), which
|
|
7
|
+
* answers where a name was written: the two take different inputs and
|
|
8
|
+
* legitimately disagree. `steps.encode.result` has a type and no manifest node
|
|
9
|
+
* to jump to; a transport binding like `request` has a scope entry and no
|
|
10
|
+
* declaration at all. Joining them is hover's job, not this module's.
|
|
11
|
+
*/
|
|
12
|
+
import type { CelScope } from "@telorun/analyzer";
|
|
13
|
+
import { navigateSchema } from "../completions/detect-context.js";
|
|
14
|
+
|
|
15
|
+
/** One name in scope, with whatever the scope knows about it. Both `type` and
|
|
16
|
+
* `schema` are optional and neither implies the other: a CEL environment
|
|
17
|
+
* variable carries a type and no schema, a context property carries a schema
|
|
18
|
+
* and gets its type from it. */
|
|
19
|
+
export interface CelSymbol {
|
|
20
|
+
name: string;
|
|
21
|
+
/** CEL type name (`int`, `string`, `map`), when the environment declares one. */
|
|
22
|
+
type?: string;
|
|
23
|
+
/** JSON Schema node, when the context declares the shape. */
|
|
24
|
+
schema?: Record<string, any>;
|
|
25
|
+
description?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* One callable, with every overload the environment registered for it.
|
|
30
|
+
*
|
|
31
|
+
* Grouped rather than one entry per overload: the registry declares a signature
|
|
32
|
+
* per accepted argument list — `double` has four — and offering each as its own
|
|
33
|
+
* candidate turns a completion list into four identical labels the author
|
|
34
|
+
* cannot choose between. What varies between them is the signature, so that is
|
|
35
|
+
* what the grouped entry carries.
|
|
36
|
+
*/
|
|
37
|
+
export interface CelFunctionSymbol {
|
|
38
|
+
name: string;
|
|
39
|
+
/** Every registered overload's signature, in registration order. */
|
|
40
|
+
signatures: string[];
|
|
41
|
+
/** The type a receiver-style call is made ON (`string.startsWith`), or null
|
|
42
|
+
* for a global function. Part of the grouping key, since a global and a
|
|
43
|
+
* method sharing a name are genuinely two callables. */
|
|
44
|
+
receiverType: string | null;
|
|
45
|
+
/** The first description any overload carries — they describe the function,
|
|
46
|
+
* not the individual argument list. */
|
|
47
|
+
description?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The type a schema node declares, rendered for display. Unions are joined
|
|
51
|
+
* rather than collapsed — a slot admitting several shapes says so. */
|
|
52
|
+
export function schemaTypeName(schema: Record<string, any> | undefined): string | undefined {
|
|
53
|
+
if (!schema) return undefined;
|
|
54
|
+
const valueType = schema["x-telo-type"];
|
|
55
|
+
if (typeof valueType === "string") return valueType;
|
|
56
|
+
if (valueType && typeof valueType === "object" && typeof valueType.name === "string") {
|
|
57
|
+
return valueType.name;
|
|
58
|
+
}
|
|
59
|
+
const t = schema.type;
|
|
60
|
+
if (Array.isArray(t)) return t.join(" | ");
|
|
61
|
+
if (typeof t === "string") return t;
|
|
62
|
+
if (Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf)) {
|
|
63
|
+
const branches = (schema.anyOf ?? schema.oneOf) as Record<string, any>[];
|
|
64
|
+
const names = branches.map((b) => schemaTypeName(b)).filter(Boolean);
|
|
65
|
+
if (names.length > 0) return [...new Set(names)].join(" | ");
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The context's property map, or an empty one when the site is typed by the
|
|
71
|
+
* environment alone. */
|
|
72
|
+
function contextProperties(scope: CelScope): Record<string, any> {
|
|
73
|
+
const props = scope.contextSchema?.properties;
|
|
74
|
+
return props && typeof props === "object" ? (props as Record<string, any>) : {};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The names an expression may start with.
|
|
79
|
+
*
|
|
80
|
+
* Both sources, because neither is complete: the context schema carries the
|
|
81
|
+
* scope's own bindings (`steps`, `error`, a kind's named bindings, a transport's
|
|
82
|
+
* `request`), while the environment carries what was registered onto it
|
|
83
|
+
* directly — which is where the kernel globals live when no context applied.
|
|
84
|
+
* A name in both takes its schema from the context, which is the narrower.
|
|
85
|
+
*/
|
|
86
|
+
export function celRootSymbols(scope: CelScope): CelSymbol[] {
|
|
87
|
+
const out = new Map<string, CelSymbol>();
|
|
88
|
+
for (const variable of scope.env.getDefinitions().variables) {
|
|
89
|
+
out.set(variable.name, {
|
|
90
|
+
name: variable.name,
|
|
91
|
+
type: variable.type,
|
|
92
|
+
description: variable.description ?? undefined,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
for (const [name, schema] of Object.entries(contextProperties(scope))) {
|
|
96
|
+
const node = schema as Record<string, any>;
|
|
97
|
+
out.set(name, {
|
|
98
|
+
name,
|
|
99
|
+
type: schemaTypeName(node) ?? out.get(name)?.type,
|
|
100
|
+
schema: node,
|
|
101
|
+
description:
|
|
102
|
+
typeof node.description === "string" ? node.description : out.get(name)?.description,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return [...out.values()];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The schema at a dotted path from the scope root, or undefined when the path
|
|
109
|
+
* leaves what the context declares. Navigation is the shared schema walk, so a
|
|
110
|
+
* member reached through an array, a `$ref` or an `anyOf` branch resolves the
|
|
111
|
+
* same way it does for a structural field. */
|
|
112
|
+
function schemaAtPath(scope: CelScope, parts: string[]): Record<string, any> | undefined {
|
|
113
|
+
if (parts.length === 0) return scope.contextSchema ?? undefined;
|
|
114
|
+
const root = contextProperties(scope)[parts[0]] as Record<string, any> | undefined;
|
|
115
|
+
if (!root) return undefined;
|
|
116
|
+
if (parts.length === 1) return root;
|
|
117
|
+
return navigateSchema(root, parts.slice(1));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The members available after `prefix`.
|
|
122
|
+
*
|
|
123
|
+
* Empty when the prefix resolves to nothing OR to something whose shape the
|
|
124
|
+
* scope does not declare — an open node, a live value, a permissive contract.
|
|
125
|
+
* That is the honest answer: offering a guess here would be offering names the
|
|
126
|
+
* checker has no opinion about, which is exactly what the shared scope rule
|
|
127
|
+
* exists to prevent.
|
|
128
|
+
*/
|
|
129
|
+
export function celMemberSymbols(scope: CelScope, prefix: string[]): CelSymbol[] {
|
|
130
|
+
if (prefix.length === 0) return celRootSymbols(scope);
|
|
131
|
+
const node = schemaAtPath(scope, prefix);
|
|
132
|
+
const props = node?.properties;
|
|
133
|
+
if (!props || typeof props !== "object") return [];
|
|
134
|
+
return Object.entries(props as Record<string, any>).map(([name, raw]) => {
|
|
135
|
+
const child = raw as Record<string, any>;
|
|
136
|
+
return {
|
|
137
|
+
name,
|
|
138
|
+
type: schemaTypeName(child),
|
|
139
|
+
schema: child,
|
|
140
|
+
description: typeof child.description === "string" ? child.description : undefined,
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** What the chain `parts` resolves to — used for hover, where the cursor sits on
|
|
146
|
+
* one identifier of a complete chain and the symbol wanted is the one at that
|
|
147
|
+
* identifier, not at the chain's tail. */
|
|
148
|
+
export function celSymbolAt(scope: CelScope, parts: string[]): CelSymbol | undefined {
|
|
149
|
+
if (parts.length === 0) return undefined;
|
|
150
|
+
if (parts.length === 1) {
|
|
151
|
+
return celRootSymbols(scope).find((s) => s.name === parts[0]);
|
|
152
|
+
}
|
|
153
|
+
const schema = schemaAtPath(scope, parts);
|
|
154
|
+
if (!schema) return undefined;
|
|
155
|
+
return {
|
|
156
|
+
name: parts[parts.length - 1],
|
|
157
|
+
type: schemaTypeName(schema),
|
|
158
|
+
schema,
|
|
159
|
+
description: typeof schema.description === "string" ? schema.description : undefined,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The callables the environment declares, one entry per function.
|
|
165
|
+
*
|
|
166
|
+
* Read off `getDefinitions()` rather than a curated list, exactly as the call
|
|
167
|
+
* classifier does — so a function the registry gained is offered without this
|
|
168
|
+
* module being told, and one it never had is never offered. Overloads are
|
|
169
|
+
* folded into their function; see {@link CelFunctionSymbol}.
|
|
170
|
+
*/
|
|
171
|
+
export function celFunctions(scope: CelScope): CelFunctionSymbol[] {
|
|
172
|
+
const byKey = new Map<string, CelFunctionSymbol>();
|
|
173
|
+
for (const fn of scope.env.getDefinitions().functions) {
|
|
174
|
+
const key = `${fn.receiverType ?? ""}.${fn.name}`;
|
|
175
|
+
const existing = byKey.get(key);
|
|
176
|
+
if (existing) {
|
|
177
|
+
if (!existing.signatures.includes(fn.signature)) existing.signatures.push(fn.signature);
|
|
178
|
+
existing.description ??= fn.description ?? undefined;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
byKey.set(key, {
|
|
182
|
+
name: fn.name,
|
|
183
|
+
signatures: [fn.signature],
|
|
184
|
+
receiverType: fn.receiverType,
|
|
185
|
+
description: fn.description ?? undefined,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return [...byKey.values()];
|
|
189
|
+
}
|