@telorun/ide-support 0.15.0 → 0.16.0

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 (69) hide show
  1. package/dist/cel/cursor-chain.d.ts +30 -0
  2. package/dist/cel/cursor-chain.d.ts.map +1 -0
  3. package/dist/cel/cursor-chain.js +30 -0
  4. package/dist/cel/symbols.d.ts +82 -0
  5. package/dist/cel/symbols.d.ts.map +1 -0
  6. package/dist/cel/symbols.js +147 -0
  7. package/dist/cel/tokens.d.ts +32 -0
  8. package/dist/cel/tokens.d.ts.map +1 -0
  9. package/dist/cel/tokens.js +162 -0
  10. package/dist/completions/build.d.ts +6 -2
  11. package/dist/completions/build.d.ts.map +1 -1
  12. package/dist/completions/build.js +52 -24
  13. package/dist/completions/call-inputs.d.ts +25 -0
  14. package/dist/completions/call-inputs.d.ts.map +1 -0
  15. package/dist/completions/call-inputs.js +78 -0
  16. package/dist/completions/cel-completions.d.ts +26 -0
  17. package/dist/completions/cel-completions.d.ts.map +1 -0
  18. package/dist/completions/cel-completions.js +78 -0
  19. package/dist/completions/detect-context.d.ts +47 -8
  20. package/dist/completions/detect-context.d.ts.map +1 -1
  21. package/dist/completions/detect-context.js +51 -15
  22. package/dist/completions/prop-keys.d.ts +5 -1
  23. package/dist/completions/prop-keys.d.ts.map +1 -1
  24. package/dist/completions/prop-keys.js +51 -3
  25. package/dist/completions/resolve-node.d.ts +9 -2
  26. package/dist/completions/resolve-node.d.ts.map +1 -1
  27. package/dist/completions/resolve-node.js +63 -21
  28. package/dist/definition/build-definition.d.ts +6 -2
  29. package/dist/definition/build-definition.d.ts.map +1 -1
  30. package/dist/definition/build-definition.js +16 -3
  31. package/dist/definition/locate-context-binding.d.ts +15 -0
  32. package/dist/definition/locate-context-binding.d.ts.map +1 -0
  33. package/dist/definition/locate-context-binding.js +35 -0
  34. package/dist/definition/locate-step.d.ts +13 -0
  35. package/dist/definition/locate-step.d.ts.map +1 -0
  36. package/dist/definition/locate-step.js +33 -0
  37. package/dist/definition/resolve-cel-target.d.ts +11 -1
  38. package/dist/definition/resolve-cel-target.d.ts.map +1 -1
  39. package/dist/definition/resolve-cel-target.js +14 -14
  40. package/dist/doc-identity.d.ts +17 -0
  41. package/dist/doc-identity.d.ts.map +1 -0
  42. package/dist/doc-identity.js +19 -0
  43. package/dist/hover/build-hover.d.ts +6 -2
  44. package/dist/hover/build-hover.d.ts.map +1 -1
  45. package/dist/hover/build-hover.js +64 -3
  46. package/dist/semantic-tokens/build-semantic-tokens.d.ts +13 -8
  47. package/dist/semantic-tokens/build-semantic-tokens.d.ts.map +1 -1
  48. package/dist/semantic-tokens/build-semantic-tokens.js +81 -37
  49. package/dist/types.d.ts +25 -5
  50. package/dist/types.d.ts.map +1 -1
  51. package/dist/types.js +16 -2
  52. package/package.json +2 -2
  53. package/src/cel/cursor-chain.ts +58 -0
  54. package/src/cel/symbols.ts +189 -0
  55. package/src/cel/tokens.ts +169 -0
  56. package/src/completions/build.ts +85 -22
  57. package/src/completions/call-inputs.ts +92 -0
  58. package/src/completions/cel-completions.ts +108 -0
  59. package/src/completions/detect-context.ts +107 -13
  60. package/src/completions/prop-keys.ts +59 -2
  61. package/src/completions/resolve-node.ts +82 -17
  62. package/src/definition/build-definition.ts +30 -2
  63. package/src/definition/locate-context-binding.ts +53 -0
  64. package/src/definition/locate-step.ts +50 -0
  65. package/src/definition/resolve-cel-target.ts +25 -0
  66. package/src/doc-identity.ts +31 -0
  67. package/src/hover/build-hover.ts +67 -1
  68. package/src/semantic-tokens/build-semantic-tokens.ts +84 -30
  69. package/src/types.ts +47 -6
@@ -36,7 +36,11 @@ function enter(scope, map, ancestorsLen) {
36
36
  const kind = resourceKindOf(map);
37
37
  return kind ? { kind, depth: ancestorsLen } : scope;
38
38
  }
39
- function descend(node, ancestors, offset, scope) {
39
+ /** Append one key segment to a concrete path (`routes[0]` + `handler`). */
40
+ function joinKey(concrete, key) {
41
+ return concrete ? `${concrete}.${key}` : key;
42
+ }
43
+ function descend(node, ancestors, concrete, offset, scope) {
40
44
  if (node.kind === "map") {
41
45
  const mapScope = enter(scope, node, ancestors.length);
42
46
  for (const pair of node.entries) {
@@ -46,6 +50,7 @@ function descend(node, ancestors, offset, scope) {
46
50
  type: "key",
47
51
  container: node,
48
52
  path: ancestors,
53
+ concretePath: concrete,
49
54
  keyNode: pair.key,
50
55
  keyName,
51
56
  scope: mapScope,
@@ -53,13 +58,15 @@ function descend(node, ancestors, offset, scope) {
53
58
  }
54
59
  if (pair.value && within(pair.value.range, offset)) {
55
60
  const childAncestors = keyName != null ? [...ancestors, keyName] : ancestors;
61
+ const childConcrete = keyName != null ? joinKey(concrete, keyName) : concrete;
56
62
  if (pair.value.kind === "map" || pair.value.kind === "seq") {
57
- return descend(pair.value, childAncestors, offset, mapScope) ?? { type: "empty" };
63
+ return descend(pair.value, childAncestors, childConcrete, offset, mapScope) ?? { type: "empty" };
58
64
  }
59
65
  return {
60
66
  type: "value",
61
67
  container: node,
62
68
  path: ancestors,
69
+ concretePath: childConcrete,
63
70
  keyName,
64
71
  keyEnd: pair.key.range[1],
65
72
  valueNode: pair.value,
@@ -70,23 +77,33 @@ function descend(node, ancestors, offset, scope) {
70
77
  }
71
78
  if (node.kind === "seq") {
72
79
  // Sequence items are transparent to the key path (mirrors the schema
73
- // walker, which auto-descends arrays).
74
- for (const item of node.items) {
80
+ // walker, which auto-descends arrays) but NOT to the concrete path: an
81
+ // `x-telo-context` scope, an error-bearing region and a step's identity are
82
+ // all addressed per item, so a CEL site is unreachable without the index.
83
+ for (const [index, item] of node.items.entries()) {
75
84
  if (within(item.range, offset)) {
85
+ const itemConcrete = `${concrete}[${index}]`;
76
86
  if (item.kind === "map" || item.kind === "seq") {
77
- return descend(item, ancestors, offset, scope) ?? { type: "empty" };
87
+ return descend(item, ancestors, itemConcrete, offset, scope) ?? { type: "empty" };
78
88
  }
79
89
  // A bare scalar list item (`targets:\n - One`) has no enclosing map of
80
90
  // keyed siblings — leave `container` undefined rather than treating the
81
91
  // seq as a map.
82
- return { type: "value", container: undefined, path: ancestors, keyEnd: item.range[0], valueNode: item };
92
+ return {
93
+ type: "value",
94
+ container: undefined,
95
+ path: ancestors,
96
+ concretePath: itemConcrete,
97
+ keyEnd: item.range[0],
98
+ valueNode: item,
99
+ };
83
100
  }
84
101
  }
85
102
  return undefined;
86
103
  }
87
104
  return undefined;
88
105
  }
89
- function collectScopes(node, ancestors, scope, lineOffsets, maps, pairs) {
106
+ function collectScopes(node, ancestors, concrete, scope, lineOffsets, maps, pairs) {
90
107
  if (node.kind === "map") {
91
108
  const mapScope = enter(scope, node, ancestors.length);
92
109
  const keys = new Set();
@@ -99,11 +116,19 @@ function collectScopes(node, ancestors, scope, lineOffsets, maps, pairs) {
99
116
  childColumn = offsetToPosition(pair.key.range[0], lineOffsets).character;
100
117
  }
101
118
  if (childColumn >= 0) {
102
- maps.push({ path: ancestors, childColumn, keys, rangeStart: node.range[0], scope: mapScope });
119
+ maps.push({
120
+ path: ancestors,
121
+ concrete,
122
+ childColumn,
123
+ keys,
124
+ rangeStart: node.range[0],
125
+ scope: mapScope,
126
+ });
103
127
  }
104
128
  for (const pair of node.entries) {
105
129
  const keyName = scalarString(pair.key);
106
130
  const fullPath = keyName != null ? [...ancestors, keyName] : ancestors;
131
+ const fullConcrete = keyName != null ? joinKey(concrete, keyName) : concrete;
107
132
  const childKeys = new Set();
108
133
  if (pair.value?.kind === "map") {
109
134
  for (const p of pair.value.entries) {
@@ -114,18 +139,19 @@ function collectScopes(node, ancestors, scope, lineOffsets, maps, pairs) {
114
139
  }
115
140
  pairs.push({
116
141
  path: fullPath,
142
+ concrete: fullConcrete,
117
143
  keyColumn: offsetToPosition(pair.key.range[0], lineOffsets).character,
118
144
  keyOffset: pair.key.range[0],
119
145
  childKeys,
120
146
  scope: mapScope,
121
147
  });
122
- if (pair.value)
123
- collectScopes(pair.value, fullPath, mapScope, lineOffsets, maps, pairs);
148
+ if (pair.value) {
149
+ collectScopes(pair.value, fullPath, fullConcrete, mapScope, lineOffsets, maps, pairs);
150
+ }
124
151
  }
125
152
  }
126
153
  else if (node.kind === "seq") {
127
- for (const item of node.items)
128
- collectScopes(item, ancestors, scope, lineOffsets, maps, pairs);
154
+ node.items.forEach((item, index) => collectScopes(item, ancestors, `${concrete}[${index}]`, scope, lineOffsets, maps, pairs));
129
155
  }
130
156
  }
131
157
  /** Resolve the container a new key at `cursorColumn` belongs to. Prefers an
@@ -134,7 +160,7 @@ function collectScopes(node, ancestors, scope, lineOffsets, maps, pairs) {
134
160
  function columnSearch(root, cursorColumn, cursorOffset, lineOffsets) {
135
161
  const maps = [];
136
162
  const pairs = [];
137
- collectScopes(root, [], { depth: 0 }, lineOffsets, maps, pairs);
163
+ collectScopes(root, [], "", { depth: 0 }, lineOffsets, maps, pairs);
138
164
  // Sibling level: a map whose children already sit at the cursor's column.
139
165
  let sibling;
140
166
  for (const m of maps) {
@@ -143,8 +169,14 @@ function columnSearch(root, cursorColumn, cursorOffset, lineOffsets) {
143
169
  sibling = m;
144
170
  }
145
171
  }
146
- if (sibling)
147
- return { path: sibling.path, existingKeys: sibling.keys, scope: sibling.scope };
172
+ if (sibling) {
173
+ return {
174
+ path: sibling.path,
175
+ concrete: sibling.concrete,
176
+ existingKeys: sibling.keys,
177
+ scope: sibling.scope,
178
+ };
179
+ }
148
180
  // Nest under the nearest-preceding key shallower than the cursor.
149
181
  let nest;
150
182
  for (const p of pairs) {
@@ -156,9 +188,15 @@ function columnSearch(root, cursorColumn, cursorOffset, lineOffsets) {
156
188
  }
157
189
  }
158
190
  }
159
- if (nest)
160
- return { path: nest.path, existingKeys: nest.childKeys, scope: nest.scope };
161
- return { path: [], existingKeys: new Set(), scope: { depth: 0 } };
191
+ if (nest) {
192
+ return {
193
+ path: nest.path,
194
+ concrete: nest.concrete,
195
+ existingKeys: nest.childKeys,
196
+ scope: nest.scope,
197
+ };
198
+ }
199
+ return { path: [], concrete: "", existingKeys: new Set(), scope: { depth: 0 } };
162
200
  }
163
201
  // ---------------------------------------------------------------------------
164
202
  function selectDoc(docs, offset) {
@@ -189,7 +227,7 @@ export function resolveNodeAtPosition(text, docs, line, character) {
189
227
  return undefined;
190
228
  const doc = docs[docIndex];
191
229
  const docKind = docKindOf(doc);
192
- const found = doc.root ? descend(doc.root, [], offset, { depth: 0 }) : undefined;
230
+ const found = doc.root ? descend(doc.root, [], "", offset, { depth: 0 }) : undefined;
193
231
  // Cursor sits on an existing map key → key/prop-key position.
194
232
  if (found?.type === "key") {
195
233
  const existingKeys = new Set();
@@ -204,6 +242,7 @@ export function resolveNodeAtPosition(text, docs, line, character) {
204
242
  docKind,
205
243
  slot: "key",
206
244
  path: found.path,
245
+ concretePath: found.concretePath,
207
246
  node: found.keyNode,
208
247
  replaceRange: { start: toPos(found.keyNode.range[0]), end: toPos(found.keyNode.range[1]) },
209
248
  container: found.container,
@@ -226,13 +265,14 @@ export function resolveNodeAtPosition(text, docs, line, character) {
226
265
  toPos(value.range[0]).line !== toPos(found.keyEnd).line;
227
266
  if (isPartialKey && doc.root) {
228
267
  const col = toPos(value.range[0]).character;
229
- const { path, existingKeys, scope } = columnSearch(doc.root, col, offset, lineOffsets);
268
+ const { path, concrete, existingKeys, scope } = columnSearch(doc.root, col, offset, lineOffsets);
230
269
  return {
231
270
  docIndex,
232
271
  offset,
233
272
  docKind,
234
273
  slot: "key",
235
274
  path,
275
+ concretePath: concrete,
236
276
  container: found.container,
237
277
  existingKeys,
238
278
  resourceKind: scope.kind,
@@ -246,6 +286,7 @@ export function resolveNodeAtPosition(text, docs, line, character) {
246
286
  docKind,
247
287
  slot: "value",
248
288
  path: found.keyName != null ? [...found.path, found.keyName] : found.path,
289
+ concretePath: found.concretePath,
249
290
  node: value,
250
291
  container: found.container,
251
292
  prefix: text.slice(value.range[0], clampedEnd),
@@ -259,13 +300,14 @@ export function resolveNodeAtPosition(text, docs, line, character) {
259
300
  // resolved by cursor column.
260
301
  const resolution = doc.root
261
302
  ? columnSearch(doc.root, character, offset, lineOffsets)
262
- : { path: [], existingKeys: new Set(), scope: { depth: 0 } };
303
+ : { path: [], concrete: "", existingKeys: new Set(), scope: { depth: 0 } };
263
304
  return {
264
305
  docIndex,
265
306
  offset,
266
307
  docKind,
267
308
  slot: "key",
268
309
  path: resolution.path,
310
+ concretePath: resolution.concrete,
269
311
  existingKeys: resolution.existingKeys,
270
312
  resourceKind: resolution.scope.kind,
271
313
  resourceDepth: resolution.scope.depth,
@@ -1,4 +1,4 @@
1
- import { type AstDocument, type LoadedGraph } from "@telorun/analyzer";
1
+ import { type AstDocument, type ManifestAnalysis, type LoadedGraph } from "@telorun/analyzer";
2
2
  import type { DefinitionResult } from "../types.js";
3
3
  /** Resolve the symbol under the cursor to where it is declared.
4
4
  *
@@ -16,5 +16,9 @@ import type { DefinitionResult } from "../types.js";
16
16
  * cursor is on nothing navigable, or the target can't be found (a kernel
17
17
  * built-in, a scope-local name, an unexported entry, or an import that failed
18
18
  * to load). */
19
- export declare function buildDefinition(text: string, line: number, character: number, graph: LoadedGraph, currentFilePath: string, docs?: AstDocument[]): DefinitionResult | undefined;
19
+ export declare function buildDefinition(text: string, line: number, character: number, graph: LoadedGraph, currentFilePath: string, docs?: AstDocument[],
20
+ /** Lets a `steps.<name>` read navigate to the step that produced it, and a
21
+ * context binding to its declaration — both need the declaring kind's own
22
+ * annotations, which only the analysis can read. */
23
+ analysis?: ManifestAnalysis): DefinitionResult | undefined;
20
24
  //# sourceMappingURL=build-definition.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-definition.d.ts","sourceRoot":"","sources":["../../src/definition/build-definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,WAAW,EAAkB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACnG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAQpD;;;;;;;;;;;;;;;gBAegB;AAChB,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,WAAW,EAClB,eAAe,EAAE,MAAM,EACvB,IAAI,CAAC,EAAE,WAAW,EAAE,GACnB,gBAAgB,GAAG,SAAS,CAoB9B"}
1
+ {"version":3,"file":"build-definition.d.ts","sourceRoot":"","sources":["../../src/definition/build-definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAUpD;;;;;;;;;;;;;;;gBAegB;AAChB,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,WAAW,EAClB,eAAe,EAAE,MAAM,EACvB,IAAI,CAAC,EAAE,WAAW,EAAE;AACpB;;qDAEqD;AACrD,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,gBAAgB,GAAG,SAAS,CAoC9B"}
@@ -1,6 +1,8 @@
1
- import { parseToAst } from "@telorun/analyzer";
1
+ import { parseToAst, } from "@telorun/analyzer";
2
2
  import { resolveNodeAtPosition } from "../completions/resolve-node.js";
3
3
  import { splitAliasQualified } from "./alias-qualified-value.js";
4
+ import { locateContextBinding } from "./locate-context-binding.js";
5
+ import { locateStepDeclaration } from "./locate-step.js";
4
6
  import { moduleForFile } from "./manifest-navigation.js";
5
7
  import { resolveCelTarget } from "./resolve-cel-target.js";
6
8
  import { isKindSlot, resolveKindTarget } from "./resolve-kind-target.js";
@@ -21,14 +23,25 @@ import { resolveRefTarget } from "./resolve-ref-target.js";
21
23
  * cursor is on nothing navigable, or the target can't be found (a kernel
22
24
  * built-in, a scope-local name, an unexported entry, or an import that failed
23
25
  * to load). */
24
- export function buildDefinition(text, line, character, graph, currentFilePath, docs) {
26
+ export function buildDefinition(text, line, character, graph, currentFilePath, docs,
27
+ /** Lets a `steps.<name>` read navigate to the step that produced it, and a
28
+ * context binding to its declaration — both need the declaring kind's own
29
+ * annotations, which only the analysis can read. */
30
+ analysis) {
25
31
  const astDocs = docs ?? parseToAst(text);
26
32
  const resolved = resolveNodeAtPosition(text, astDocs, line, character);
27
33
  if (!resolved)
28
34
  return undefined;
29
35
  const currentModule = moduleForFile(graph, currentFilePath) ?? graph.entry;
30
36
  if (resolved.cel) {
31
- return resolveCelTarget(graph, currentModule, resolved.cel.segment, resolved.cel.offset);
37
+ return resolveCelTarget(graph, currentModule, resolved.cel.segment, resolved.cel.offset, {
38
+ // A step is resolved in the CURRENT document — `steps.<name>.result` is
39
+ // readable only inside the resource that declares it, which is one
40
+ // document. Supplied as a closure so the chain resolver stays free of the
41
+ // AST and the scope query alike.
42
+ locateStep: (stepName) => locateStepDeclaration(graph, currentFilePath, astDocs, resolved.docIndex, stepName, analysis?.celScope),
43
+ locateContextBinding: (parts) => locateContextBinding(graph, astDocs, resolved.docIndex, resolved.concretePath ?? "", parts, analysis?.celScope),
44
+ });
32
45
  }
33
46
  const node = resolved.node;
34
47
  if (resolved.slot !== "value" || node?.kind !== "scalar")
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Where a CEL context binding was declared.
3
+ *
4
+ * `request.query` is written in the route's own `request.schema`, `self.<field>`
5
+ * in the definition's `schema`, `result.<field>` in the INVOKED resource's
6
+ * `outputType` — each derived by an `x-telo-context-*` annotation rather than
7
+ * reached through a reference slot, so none of them is navigable by the graph
8
+ * walk the other CEL roots use. The scope query resolves the annotation to a
9
+ * manifest identity plus a path; locating that path in the loaded files is what
10
+ * this adds.
11
+ */
12
+ import type { AstDocument, CelScopeQuery, LoadedGraph } from "@telorun/analyzer";
13
+ import type { DefinitionResult } from "../types.js";
14
+ export declare function locateContextBinding(graph: LoadedGraph, docs: AstDocument[], docIndex: number, sitePath: string, parts: string[], scopeQuery: CelScopeQuery | undefined): DefinitionResult | undefined;
15
+ //# sourceMappingURL=locate-context-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locate-context-binding.d.ts","sourceRoot":"","sources":["../../src/definition/locate-context-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAYpD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,WAAW,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,aAAa,GAAG,SAAS,GACpC,gBAAgB,GAAG,SAAS,CAqB9B"}
@@ -0,0 +1,35 @@
1
+ import { docIdentity } from "../doc-identity.js";
2
+ /** Every file of every module in the graph — a binding's declaration may sit in
3
+ * another module (an invoked handler's `outputType`), so the search is not
4
+ * confined to the current file. */
5
+ function allFiles(graph) {
6
+ const out = [];
7
+ for (const mod of graph.modules.values())
8
+ out.push(mod.owner, ...mod.partials);
9
+ return out;
10
+ }
11
+ export function locateContextBinding(graph, docs, docIndex, sitePath, parts, scopeQuery) {
12
+ if (!scopeQuery)
13
+ return undefined;
14
+ const identity = docIdentity(docs[docIndex]);
15
+ const resource = scopeQuery.resourceFor(identity.kind, identity.name);
16
+ if (!resource)
17
+ return undefined;
18
+ const site = scopeQuery.contextDeclarationSite(resource, sitePath, parts);
19
+ if (!site)
20
+ return undefined;
21
+ for (const file of allFiles(graph)) {
22
+ for (let i = 0; i < file.manifests.length; i++) {
23
+ const manifest = file.manifests[i];
24
+ if (manifest?.kind !== site.kind || manifest.metadata?.name !== site.name)
25
+ continue;
26
+ const index = file.positions[i]?.positionIndex;
27
+ // The KEY span, so the jump underlines `query:` rather than the block that
28
+ // follows it — a declaration, not a selection.
29
+ const range = index?.get(`@key:${site.path}`) ?? index?.get(site.path);
30
+ if (range)
31
+ return { uri: file.source, range };
32
+ }
33
+ }
34
+ return undefined;
35
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Where a `Run` step is declared.
3
+ *
4
+ * The one CEL scope whose members really are written in the manifest but are
5
+ * reached through no reference slot — `steps.encode.result` names something a
6
+ * `!ref` resolver has never heard of. Finding it needs the declaring kind's own
7
+ * step-body annotation, which is why it goes through the scope query rather
8
+ * than through the graph navigation the other CEL roots use.
9
+ */
10
+ import type { AstDocument, CelScopeQuery, LoadedGraph } from "@telorun/analyzer";
11
+ import type { DefinitionResult } from "../types.js";
12
+ export declare function locateStepDeclaration(graph: LoadedGraph, filePath: string, docs: AstDocument[], docIndex: number, stepName: string, scopeQuery: CelScopeQuery | undefined): DefinitionResult | undefined;
13
+ //# sourceMappingURL=locate-step.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locate-step.d.ts","sourceRoot":"","sources":["../../src/definition/locate-step.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAcpD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,WAAW,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,aAAa,GAAG,SAAS,GACpC,gBAAgB,GAAG,SAAS,CAkB9B"}
@@ -0,0 +1,33 @@
1
+ import { docIdentity } from "../doc-identity.js";
2
+ /** The loaded file for `filePath`, whose position index maps a manifest path to
3
+ * a source range. */
4
+ function loadedFile(graph, filePath) {
5
+ for (const mod of graph.modules.values()) {
6
+ if (mod.owner.source === filePath)
7
+ return mod.owner;
8
+ const partial = mod.partials.find((p) => p.source === filePath);
9
+ if (partial)
10
+ return partial;
11
+ }
12
+ return undefined;
13
+ }
14
+ export function locateStepDeclaration(graph, filePath, docs, docIndex, stepName, scopeQuery) {
15
+ if (!scopeQuery)
16
+ return undefined;
17
+ const identity = docIdentity(docs[docIndex]);
18
+ const resource = scopeQuery.resourceFor(identity.kind, identity.name);
19
+ if (!resource)
20
+ return undefined;
21
+ const stepPath = scopeQuery.stepDeclarationPath(resource, stepName);
22
+ if (!stepPath)
23
+ return undefined;
24
+ const file = loadedFile(graph, filePath);
25
+ const index = file?.positions[docIndex]?.positionIndex;
26
+ if (!index)
27
+ return undefined;
28
+ // The step's own `name:` value is what the jump underlines — the step object's
29
+ // range would highlight the whole block, which reads as a selection rather
30
+ // than as a declaration.
31
+ const range = index.get(`${stepPath}.name`) ?? index.get(`@key:${stepPath}.name`) ?? index.get(stepPath);
32
+ return range ? { uri: file.source, range } : undefined;
33
+ }
@@ -13,5 +13,15 @@ import type { DefinitionResult } from "../types.js";
13
13
  * analyzer reports the syntax error itself. Only that failure is tolerated: a
14
14
  * defect in the CEL wrapper propagates rather than reading as "nothing to
15
15
  * navigate to". */
16
- export declare function resolveCelTarget(graph: LoadedGraph, currentModule: LoadedModule, segment: CelSegment, offset: number): DefinitionResult | undefined;
16
+ export interface CelTargetResolvers {
17
+ /** Where a step of the CURRENT resource is declared. Supplied by the caller
18
+ * because finding one needs the declaring kind's step-body annotation, which
19
+ * the graph alone does not carry. */
20
+ locateStep?(stepName: string): DefinitionResult | undefined;
21
+ /** Where a context binding (`request.query`, `self.<field>`,
22
+ * `result.<field>`) was declared. Same reason: the site is derived by an
23
+ * `x-telo-context-*` annotation, which only the scope query can read. */
24
+ locateContextBinding?(parts: string[]): DefinitionResult | undefined;
25
+ }
26
+ export declare function resolveCelTarget(graph: LoadedGraph, currentModule: LoadedModule, segment: CelSegment, offset: number, resolvers?: CelTargetResolvers): DefinitionResult | undefined;
17
27
  //# sourceMappingURL=resolve-cel-target.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-cel-target.d.ts","sourceRoot":"","sources":["../../src/definition/resolve-cel-target.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAqCpD;;;;;;;;;;;;oBAYoB;AACpB,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,YAAY,EAC3B,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,GACb,gBAAgB,GAAG,SAAS,CAqB9B"}
1
+ {"version":3,"file":"resolve-cel-target.d.ts","sourceRoot":"","sources":["../../src/definition/resolve-cel-target.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAqCpD;;;;;;;;;;;;oBAYoB;AACpB,MAAM,WAAW,kBAAkB;IACjC;;0CAEsC;IACtC,UAAU,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAAC;IAC5D;;8EAE0E;IAC1E,oBAAoB,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAAC;CACtE;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAClB,aAAa,EAAE,YAAY,EAC3B,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,kBAAkB,GAC7B,gBAAgB,GAAG,SAAS,CAkC9B"}
@@ -25,20 +25,7 @@ function resolveResourceChain(graph, currentModule, parts, index) {
25
25
  return resolveExportedResource(graph, edge.targetSource, parts[2].name);
26
26
  return undefined;
27
27
  }
28
- /** Resolve the CEL identifier under the cursor to where it is declared.
29
- *
30
- * `variables` / `secrets` / `ports` navigate to their block on the module doc,
31
- * and their member to that block's entry. `resources` navigates through the
32
- * same instance lookup a `!ref` uses. Anything else (a step result, a handler
33
- * scope like `request`, a member of a resolved value) has no manifest
34
- * declaration to jump to and resolves to nothing.
35
- *
36
- * A body the author is still writing may not parse; that means there is no
37
- * chain to hit-test, not an error to surface from a navigation request — the
38
- * analyzer reports the syntax error itself. Only that failure is tolerated: a
39
- * defect in the CEL wrapper propagates rather than reading as "nothing to
40
- * navigate to". */
41
- export function resolveCelTarget(graph, currentModule, segment, offset) {
28
+ export function resolveCelTarget(graph, currentModule, segment, offset, resolvers) {
42
29
  let ast;
43
30
  try {
44
31
  ast = segment.ast();
@@ -62,5 +49,18 @@ export function resolveCelTarget(graph, currentModule, segment, offset) {
62
49
  }
63
50
  if (root === "resources")
64
51
  return resolveResourceChain(graph, currentModule, parts, index);
52
+ // `steps.<name>` navigates to the step; `steps` itself and `.result` do not —
53
+ // the first names no one step, the second is the contract's output, which is
54
+ // declared by the invoked target rather than at the read site.
55
+ if (root === "steps" && index === 1 && resolvers?.locateStep) {
56
+ return resolvers.locateStep(parts[1].name);
57
+ }
58
+ // Anything else in scope came from an `x-telo-context-*` annotation, which
59
+ // names a real manifest node for `request` / `self` / `result` and friends.
60
+ // The chain UP TO the cursor is what resolves — hovering `query` in
61
+ // `request.query.lastEventId` navigates to `query`, not to the leaf.
62
+ if (index >= 1 && resolvers?.locateContextBinding) {
63
+ return resolvers.locateContextBinding(parts.slice(0, index + 1).map((p) => p.name));
64
+ }
65
65
  return undefined;
66
66
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A document's `kind` + `metadata.name` — the pair that names the resource an
3
+ * analyzed manifest set holds it under.
4
+ *
5
+ * Its own module because it is a document-identity primitive, not a completion
6
+ * one: hover, semantic tokens and both declaration locators need it, and
7
+ * reaching it through the completion entry point dragged that whole module
8
+ * graph — CEL completion, call-input resolution, the import-source machinery —
9
+ * into surfaces that use none of it.
10
+ */
11
+ import type { AstDocument } from "@telorun/analyzer";
12
+ /** Either half may be absent while the author is still writing the document. */
13
+ export declare function docIdentity(doc: AstDocument | undefined): {
14
+ kind?: string;
15
+ name?: string;
16
+ };
17
+ //# sourceMappingURL=doc-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc-identity.d.ts","sourceRoot":"","sources":["../src/doc-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,mBAAmB,CAAC;AAK7D,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAc1F"}
@@ -0,0 +1,19 @@
1
+ const scalar = (node) => node?.kind === "scalar" && typeof node.value === "string" ? node.value : undefined;
2
+ /** Either half may be absent while the author is still writing the document. */
3
+ export function docIdentity(doc) {
4
+ if (doc?.root?.kind !== "map")
5
+ return {};
6
+ let kind;
7
+ let name;
8
+ for (const pair of doc.root.entries) {
9
+ const key = scalar(pair.key);
10
+ if (key === "kind")
11
+ kind = scalar(pair.value);
12
+ else if (key === "metadata" && pair.value?.kind === "map") {
13
+ const meta = pair.value;
14
+ const nameEntry = meta.entries.find((e) => scalar(e.key) === "name");
15
+ name = scalar(nameEntry?.value);
16
+ }
17
+ }
18
+ return { kind, name };
19
+ }
@@ -1,4 +1,8 @@
1
- import { type AnalysisRegistry, type AstDocument } from "@telorun/analyzer";
1
+ import { type AnalysisRegistry, type AstDocument, type ManifestAnalysis } from "@telorun/analyzer";
2
2
  import type { HoverResult } from "../types.js";
3
- export declare function buildHover(text: string, line: number, character: number, registry: AnalysisRegistry | undefined, docs?: AstDocument[]): HoverResult | undefined;
3
+ export declare function buildHover(text: string, line: number, character: number, registry: AnalysisRegistry | undefined, docs?: AstDocument[],
4
+ /** The host's analysis. Without it a CEL identifier hovers as nothing — its
5
+ * type is a property of the resolved scope, and there is no second source
6
+ * for it. */
7
+ analysis?: ManifestAnalysis): HoverResult | undefined;
4
8
  //# sourceMappingURL=build-hover.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-hover.d.ts","sourceRoot":"","sources":["../../src/hover/build-hover.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAyF/C,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,gBAAgB,GAAG,SAAS,EACtC,IAAI,CAAC,EAAE,WAAW,EAAE,GACnB,WAAW,GAAG,SAAS,CAOzB"}
1
+ {"version":3,"file":"build-hover.d.ts","sourceRoot":"","sources":["../../src/hover/build-hover.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAGhB,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA8F/C,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,gBAAgB,GAAG,SAAS,EACtC,IAAI,CAAC,EAAE,WAAW,EAAE;AACpB;;cAEc;AACd,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,WAAW,GAAG,SAAS,CAczB"}
@@ -1,4 +1,7 @@
1
- import { parseToAst, readRefSlot, } from "@telorun/analyzer";
1
+ import { CelParseError, parseToAst, readRefSlot, } from "@telorun/analyzer";
2
+ import { chainAt } from "../cel-chain.js";
3
+ import { celSymbolAt } from "../cel/symbols.js";
4
+ import { docIdentity } from "../doc-identity.js";
2
5
  import { navigateSchema } from "../completions/detect-context.js";
3
6
  import { resolveNodeAtPosition, scalarString, } from "../completions/resolve-node.js";
4
7
  import { CAPABILITY_DOCS } from "../completions/valid-capabilities.js";
@@ -79,17 +82,75 @@ function fieldSchemaFor(resourceKind, relativePath, registry) {
79
82
  const def = registry.resolveDefinition(resourceKind);
80
83
  if (!def?.schema)
81
84
  return undefined;
82
- return navigateSchema(def.schema, relativePath);
85
+ return navigateSchema(def.schema, relativePath, (from) => registry.resolveSchemaFrom(from, resourceKind));
83
86
  }
84
- export function buildHover(text, line, character, registry, docs) {
87
+ export function buildHover(text, line, character, registry, docs,
88
+ /** The host's analysis. Without it a CEL identifier hovers as nothing — its
89
+ * type is a property of the resolved scope, and there is no second source
90
+ * for it. */
91
+ analysis) {
85
92
  const astDocs = docs ?? parseToAst(text);
86
93
  const resolved = resolveNodeAtPosition(text, astDocs, line, character);
87
94
  if (!resolved)
88
95
  return undefined;
96
+ if (resolved.cel) {
97
+ const hover = hoverForCel(resolved, astDocs, analysis?.celScope);
98
+ // A CEL body is still a field value; when the cursor is on nothing
99
+ // nameable inside it (an operator, a literal), fall through to the field's
100
+ // own hover rather than reporting nothing.
101
+ if (hover)
102
+ return hover;
103
+ }
89
104
  if (resolved.slot === "value")
90
105
  return hoverForValue(resolved, registry);
91
106
  return hoverForKey(resolved, registry);
92
107
  }
108
+ /**
109
+ * Hover for one identifier of a CEL chain.
110
+ *
111
+ * The TYPE comes from the resolved scope; a DESCRIPTION comes from whatever
112
+ * schema node declared the name. Where to jump is the other half's answer
113
+ * (`resolveCelTarget`) and is deliberately not consulted here — hover must
114
+ * still say what `steps.encode.result` IS even though nothing in the manifest
115
+ * declares it.
116
+ */
117
+ function hoverForCel(resolved, docs, scopeQuery) {
118
+ if (!resolved.cel || !scopeQuery)
119
+ return undefined;
120
+ const identity = docIdentity(docs[resolved.docIndex]);
121
+ const resource = scopeQuery.resourceFor(identity.kind, identity.name);
122
+ if (!resource)
123
+ return undefined;
124
+ let ast;
125
+ try {
126
+ ast = resolved.cel.segment.ast();
127
+ }
128
+ catch (error) {
129
+ // An expression the author is still writing does not parse. That means
130
+ // there is no chain to hit-test, not an error to report from a hover — the
131
+ // analyzer reports the syntax error itself. Only that failure is tolerated.
132
+ if (!(error instanceof CelParseError))
133
+ throw error;
134
+ return undefined;
135
+ }
136
+ const hit = chainAt(ast, resolved.cel.offset);
137
+ if (!hit)
138
+ return undefined;
139
+ // The chain UP TO the cursor, not the whole chain: hovering `resources` in
140
+ // `resources.db.url` describes `resources`.
141
+ const parts = hit.parts.slice(0, hit.index + 1).map((p) => p.name);
142
+ const scope = scopeQuery.scopeAt(resource, resolved.concretePath ?? "");
143
+ const symbol = celSymbolAt(scope, parts);
144
+ if (!symbol)
145
+ return undefined;
146
+ const lines = [symbol.type ? `**${symbol.name}**: \`${symbol.type}\`` : `**${symbol.name}**`];
147
+ if (symbol.description)
148
+ lines.push("", symbol.description);
149
+ const chainText = parts.join(".");
150
+ if (chainText !== symbol.name)
151
+ lines.push("", `\`${chainText}\``);
152
+ return { contents: lines.join("\n") };
153
+ }
93
154
  function hoverForValue(resolved, registry) {
94
155
  const key = resolved.path[resolved.path.length - 1];
95
156
  const value = scalarString(resolved.node);
@@ -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`. Everything else (structure, CEL, tags) is left to the
6
- * TextMate grammar. Ref targets are colored here rather than in the grammar
7
- * because a `!ref` after a `key:` is tokenized by the bundled YAML grammar
8
- * before a Telo pattern can claim it the AST sees it unambiguously. An
9
- * unresolved kind gets no token, so a typo stays uncolored — a quiet signal
10
- * that pairs with the analyzer's `UNDEFINED_KIND` diagnostic. */
11
- export declare function buildSemanticTokens(text: string, registry: AnalysisRegistry | undefined, docs?: AstDocument[]): SemanticToken[];
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,EAEjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD;;;;;;;kEAOkE;AAClE,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,gBAAgB,GAAG,SAAS,EACtC,IAAI,CAAC,EAAE,WAAW,EAAE,GACnB,aAAa,EAAE,CAqCjB"}
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"}