@telorun/ide-support 0.7.10 → 0.9.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.
- package/README.md +3 -1
- package/dist/completions/import-source.js +2 -2
- package/dist/completions/resolve-node.d.ts +5 -0
- package/dist/completions/resolve-node.d.ts.map +1 -1
- package/dist/completions/resolve-node.js +4 -0
- package/dist/definition/alias-qualified-value.d.ts +23 -0
- package/dist/definition/alias-qualified-value.d.ts.map +1 -0
- package/dist/definition/alias-qualified-value.js +24 -0
- package/dist/definition/build-definition.d.ts +15 -8
- package/dist/definition/build-definition.d.ts.map +1 -1
- package/dist/definition/build-definition.js +35 -89
- package/dist/definition/manifest-navigation.d.ts +36 -0
- package/dist/definition/manifest-navigation.d.ts.map +1 -0
- package/dist/definition/manifest-navigation.js +92 -0
- package/dist/definition/resolve-cel-target.d.ts +17 -0
- package/dist/definition/resolve-cel-target.d.ts.map +1 -0
- package/dist/definition/resolve-cel-target.js +125 -0
- package/dist/definition/resolve-export-chain.d.ts +23 -0
- package/dist/definition/resolve-export-chain.d.ts.map +1 -0
- package/dist/definition/resolve-export-chain.js +89 -0
- package/dist/definition/resolve-kind-target.d.ts +26 -0
- package/dist/definition/resolve-kind-target.d.ts.map +1 -0
- package/dist/definition/resolve-kind-target.js +51 -0
- package/dist/definition/resolve-ref-target.d.ts +14 -0
- package/dist/definition/resolve-ref-target.d.ts.map +1 -0
- package/dist/definition/resolve-ref-target.js +25 -0
- package/dist/import-upgrades/build-import-upgrades.d.ts +78 -0
- package/dist/import-upgrades/build-import-upgrades.d.ts.map +1 -0
- package/dist/import-upgrades/build-import-upgrades.js +95 -0
- package/dist/import-upgrades/find-import-entries.d.ts +45 -0
- package/dist/import-upgrades/find-import-entries.d.ts.map +1 -0
- package/dist/import-upgrades/find-import-entries.js +102 -0
- package/dist/import-upgrades/index.d.ts +5 -0
- package/dist/import-upgrades/index.d.ts.map +1 -0
- package/dist/import-upgrades/index.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +2 -2
- package/src/completions/import-source.ts +2 -2
- package/src/completions/resolve-node.ts +9 -0
- package/src/definition/alias-qualified-value.ts +42 -0
- package/src/definition/build-definition.ts +34 -104
- package/src/definition/manifest-navigation.ts +124 -0
- package/src/definition/resolve-cel-target.ts +151 -0
- package/src/definition/resolve-export-chain.ts +119 -0
- package/src/definition/resolve-kind-target.ts +61 -0
- package/src/definition/resolve-ref-target.ts +34 -0
- package/src/import-upgrades/build-import-upgrades.ts +190 -0
- package/src/import-upgrades/find-import-entries.ts +175 -0
- package/src/import-upgrades/index.ts +10 -0
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -48,7 +48,9 @@ See [examples/](./examples/) for a list of working applications.
|
|
|
48
48
|
|
|
49
49
|
## Status
|
|
50
50
|
|
|
51
|
-
Telo is under
|
|
51
|
+
Telo is under heavy development. While it is pre-1.0, breaking changes ship in **minor** releases — manifest shapes, kind schemas, and APIs can change between versions. Pin your imports and expect to update manifests when you upgrade. The core runtime, module system, and standard library are functional, but Telo is not yet recommended for production use.
|
|
52
|
+
|
|
53
|
+
**1.0 lands when the Rust kernel reaches feature parity with the Node.js implementation** — that is the milestone that freezes the manifest contract across runtimes.
|
|
52
54
|
|
|
53
55
|
## The Meaning of Telo
|
|
54
56
|
|
|
@@ -130,8 +130,8 @@ async function refSearchCompletions(prefix, replaceRange, adapter) {
|
|
|
130
130
|
}
|
|
131
131
|
/** The `org/name` tail of a location ref: its last two path segments, with the
|
|
132
132
|
* transport scheme (`oci://`, `https://`, …) and registry host dropped.
|
|
133
|
-
* `oci://ghcr.io/telorun/telo-console` → `telorun/telo-console`; `
|
|
134
|
-
* → `
|
|
133
|
+
* `oci://ghcr.io/telorun/telo-console` → `telorun/telo-console`; `acme/console`
|
|
134
|
+
* → `acme/console`. Falls back to fewer segments (or the whole ref) when there
|
|
135
135
|
* aren't two. */
|
|
136
136
|
function refDisplayName(ref) {
|
|
137
137
|
const withoutScheme = ref.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
|
|
@@ -6,6 +6,11 @@ import { type AstDocument, type AstMap, type AstNode, type CelSegment, type Posi
|
|
|
6
6
|
* "which container am I typing into". */
|
|
7
7
|
export interface ResolvedCursor {
|
|
8
8
|
docIndex: number;
|
|
9
|
+
/** The cursor as a document offset. Carried so a consumer hit-testing inside a
|
|
10
|
+
* node (a CEL chain, the halves of an `Alias.Name` value) reuses the one this
|
|
11
|
+
* resolution was performed with, rather than recomputing the line table and
|
|
12
|
+
* risking a different answer. */
|
|
13
|
+
offset: number;
|
|
9
14
|
/** Top-level `kind:` value of the cursor's document, when present. */
|
|
10
15
|
docKind?: string;
|
|
11
16
|
slot: "key" | "value";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-node.d.ts","sourceRoot":"","sources":["../../src/completions/resolve-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,KAAK,OAAO,EAEZ,KAAK,UAAU,EACf,KAAK,QAAQ,EACd,MAAM,mBAAmB,CAAC;AAE3B;;;;0CAI0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC;IACtB;4DACwD;IACxD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC;IAClD,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;gFAC4E;IAC5E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;gCAC4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;iDAC6C;IAC7C,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B;;yCAEqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;mEAC+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;8EAC0E;IAC1E,GAAG,CAAC,EAAE;QAAE,OAAO,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAMD,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG1E;AAwPD;8DAC8D;AAC9D,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,WAAW,EAAE,EACnB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,cAAc,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"resolve-node.d.ts","sourceRoot":"","sources":["../../src/completions/resolve-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,KAAK,OAAO,EAEZ,KAAK,UAAU,EACf,KAAK,QAAQ,EACd,MAAM,mBAAmB,CAAC;AAE3B;;;;0CAI0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;sCAGkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC;IACtB;4DACwD;IACxD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC;IAClD,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;gFAC4E;IAC5E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;gCAC4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;iDAC6C;IAC7C,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B;;yCAEqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;mEAC+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;8EAC0E;IAC1E,GAAG,CAAC,EAAE;QAAE,OAAO,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAMD,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG1E;AAwPD;8DAC8D;AAC9D,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,WAAW,EAAE,EACnB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,cAAc,GAAG,SAAS,CAgG5B"}
|
|
@@ -200,6 +200,7 @@ export function resolveNodeAtPosition(text, docs, line, character) {
|
|
|
200
200
|
}
|
|
201
201
|
return {
|
|
202
202
|
docIndex,
|
|
203
|
+
offset,
|
|
203
204
|
docKind,
|
|
204
205
|
slot: "key",
|
|
205
206
|
path: found.path,
|
|
@@ -228,6 +229,7 @@ export function resolveNodeAtPosition(text, docs, line, character) {
|
|
|
228
229
|
const { path, existingKeys, scope } = columnSearch(doc.root, col, offset, lineOffsets);
|
|
229
230
|
return {
|
|
230
231
|
docIndex,
|
|
232
|
+
offset,
|
|
231
233
|
docKind,
|
|
232
234
|
slot: "key",
|
|
233
235
|
path,
|
|
@@ -240,6 +242,7 @@ export function resolveNodeAtPosition(text, docs, line, character) {
|
|
|
240
242
|
const clampedEnd = Math.min(offset, value.range[1]);
|
|
241
243
|
return {
|
|
242
244
|
docIndex,
|
|
245
|
+
offset,
|
|
243
246
|
docKind,
|
|
244
247
|
slot: "value",
|
|
245
248
|
path: found.keyName != null ? [...found.path, found.keyName] : found.path,
|
|
@@ -259,6 +262,7 @@ export function resolveNodeAtPosition(text, docs, line, character) {
|
|
|
259
262
|
: { path: [], existingKeys: new Set(), scope: { depth: 0 } };
|
|
260
263
|
return {
|
|
261
264
|
docIndex,
|
|
265
|
+
offset,
|
|
262
266
|
docKind,
|
|
263
267
|
slot: "key",
|
|
264
268
|
path: resolution.path,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AstScalar } from "@telorun/analyzer";
|
|
2
|
+
/** An `<Alias>.<Name>` scalar split at its first dot — the grammar `kind:`,
|
|
3
|
+
* `extends:`, `x-telo-ref` and `!ref` all share — plus which half the cursor
|
|
4
|
+
* sits on, so the alias navigates to its import and the suffix to the thing
|
|
5
|
+
* the alias qualifies. */
|
|
6
|
+
export interface AliasQualifiedValue {
|
|
7
|
+
/** Undefined for an unqualified value (a bare local name). */
|
|
8
|
+
alias?: string;
|
|
9
|
+
/** Everything after the first dot, or the whole value when unqualified. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** True when the cursor sits on the alias half (dot inclusive). */
|
|
12
|
+
onAlias: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Split the scalar at `node` around the cursor at `offset` (a document offset).
|
|
15
|
+
*
|
|
16
|
+
* The parsed value is authoritative where there is one — the source range spans
|
|
17
|
+
* the whole scalar token, so a quoted `x-telo-ref: "Alias.Kind"` would otherwise
|
|
18
|
+
* carry its quotes into the alias and miss every lookup. A `!ref` / `!cel`
|
|
19
|
+
* scalar resolves to a tagged sentinel rather than a string, so those fall back
|
|
20
|
+
* to the raw slice. The range is still what locates the value inside the token,
|
|
21
|
+
* which is what the cursor is hit-tested against. */
|
|
22
|
+
export declare function splitAliasQualified(text: string, node: AstScalar, offset: number): AliasQualifiedValue | undefined;
|
|
23
|
+
//# sourceMappingURL=alias-qualified-value.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alias-qualified-value.d.ts","sourceRoot":"","sources":["../../src/definition/alias-qualified-value.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;2BAG2B;AAC3B,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;sDAOsD;AACtD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,MAAM,GACb,mBAAmB,GAAG,SAAS,CAcjC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Split the scalar at `node` around the cursor at `offset` (a document offset).
|
|
2
|
+
*
|
|
3
|
+
* The parsed value is authoritative where there is one — the source range spans
|
|
4
|
+
* the whole scalar token, so a quoted `x-telo-ref: "Alias.Kind"` would otherwise
|
|
5
|
+
* carry its quotes into the alias and miss every lookup. A `!ref` / `!cel`
|
|
6
|
+
* scalar resolves to a tagged sentinel rather than a string, so those fall back
|
|
7
|
+
* to the raw slice. The range is still what locates the value inside the token,
|
|
8
|
+
* which is what the cursor is hit-tested against. */
|
|
9
|
+
export function splitAliasQualified(text, node, offset) {
|
|
10
|
+
const raw = text.slice(node.range[0], node.range[1]);
|
|
11
|
+
const value = typeof node.value === "string" ? node.value : raw.trim();
|
|
12
|
+
if (!value)
|
|
13
|
+
return undefined;
|
|
14
|
+
const at = raw.indexOf(value);
|
|
15
|
+
const start = node.range[0] + (at >= 0 ? at : 0);
|
|
16
|
+
const dot = value.indexOf(".");
|
|
17
|
+
if (dot === -1)
|
|
18
|
+
return { name: value, onAlias: false };
|
|
19
|
+
return {
|
|
20
|
+
alias: value.slice(0, dot),
|
|
21
|
+
name: value.slice(dot + 1),
|
|
22
|
+
onAlias: offset <= start + dot,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { type AstDocument, type LoadedGraph } from "@telorun/analyzer";
|
|
2
2
|
import type { DefinitionResult } from "../types.js";
|
|
3
|
-
/** Resolve the
|
|
3
|
+
/** Resolve the symbol under the cursor to where it is declared.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* Three navigable symbol classes, each dispatched on what the cursor sits in
|
|
6
|
+
* rather than on the field it happens to be under:
|
|
7
|
+
*
|
|
8
|
+
* - a CEL identifier (`variables.port`, `resources.Store.conn`) → its
|
|
9
|
+
* declaration on the module doc, or the resource it names;
|
|
10
|
+
* - an alias-qualified kind (`kind: Http.Server`, `extends:`, `x-telo-ref`) →
|
|
11
|
+
* the `Telo.Definition` that registers it;
|
|
12
|
+
* - a `!ref` target → the resource instance it names.
|
|
13
|
+
*
|
|
14
|
+
* In the last two the alias half (`Http`) navigates to the import that declares
|
|
15
|
+
* it, and the suffix to what the alias qualifies. Returns `undefined` when the
|
|
16
|
+
* cursor is on nothing navigable, or the target can't be found (a kernel
|
|
17
|
+
* built-in, a scope-local name, an unexported entry, or an import that failed
|
|
18
|
+
* to load). */
|
|
12
19
|
export declare function buildDefinition(text: string, line: number, character: number, graph: LoadedGraph, currentFilePath: string, docs?: AstDocument[]): DefinitionResult | undefined;
|
|
13
20
|
//# 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,
|
|
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,98 +1,44 @@
|
|
|
1
|
-
import { parseToAst
|
|
1
|
+
import { parseToAst } from "@telorun/analyzer";
|
|
2
2
|
import { resolveNodeAtPosition } from "../completions/resolve-node.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
/** First resource named `name` across `files`, located at its `metadata.name`
|
|
14
|
-
* (or its first line as a fallback). Names are unique within a module scope, so
|
|
15
|
-
* the first hit is the definition. */
|
|
16
|
-
function locateResource(files, name) {
|
|
17
|
-
for (const file of files) {
|
|
18
|
-
for (let i = 0; i < file.manifests.length; i++) {
|
|
19
|
-
const manifest = file.manifests[i];
|
|
20
|
-
if (!manifest || manifest.metadata?.name !== name)
|
|
21
|
-
continue;
|
|
22
|
-
const pos = file.positions[i];
|
|
23
|
-
const range = pos?.positionIndex.get("metadata.name") ??
|
|
24
|
-
pos?.positionIndex.get("@key:metadata.name") ??
|
|
25
|
-
(pos ? { start: { line: pos.sourceLine, character: 0 }, end: { line: pos.sourceLine, character: 0 } } : undefined);
|
|
26
|
-
if (range)
|
|
27
|
-
return { uri: file.source, range };
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
/** The `exports.resources` list of a module's owner doc (empty when absent). */
|
|
33
|
-
function exportedResources(mod) {
|
|
34
|
-
const doc = mod.owner.manifests.find((m) => m?.kind === "Telo.Library" || m?.kind === "Telo.Application");
|
|
35
|
-
const list = doc?.exports?.resources;
|
|
36
|
-
return Array.isArray(list) ? list.filter((e) => typeof e === "string") : [];
|
|
37
|
-
}
|
|
38
|
-
/** Follow `name` into `moduleSource` across the export boundary, honoring the
|
|
39
|
-
* `exports.resources` gate and re-export chains (`app → api → domain → …`). A
|
|
40
|
-
* terminal match is a locally-owned instance the module actually exports; a
|
|
41
|
-
* re-export entry `InnerAlias.name` hops through that module's own import edge.
|
|
42
|
-
* `seen` bounds cyclic import graphs. */
|
|
43
|
-
function resolveExported(graph, moduleSource, name, seen = new Set()) {
|
|
44
|
-
if (seen.has(moduleSource))
|
|
45
|
-
return undefined;
|
|
46
|
-
seen.add(moduleSource);
|
|
47
|
-
const mod = graph.modules.get(moduleSource);
|
|
48
|
-
if (!mod)
|
|
49
|
-
return undefined;
|
|
50
|
-
const exports = exportedResources(mod);
|
|
51
|
-
// No `exports.resources` block → ungated (the module hasn't opted into the
|
|
52
|
-
// gate, same as `exports.kinds`): a plain name match keeps navigation working
|
|
53
|
-
// for modules that predate explicit exports.
|
|
54
|
-
if (exports.length === 0) {
|
|
55
|
-
return locateResource([mod.owner, ...mod.partials], name);
|
|
56
|
-
}
|
|
57
|
-
if (exports.includes(name)) {
|
|
58
|
-
const local = locateResource([mod.owner, ...mod.partials], name);
|
|
59
|
-
if (local)
|
|
60
|
-
return local;
|
|
61
|
-
}
|
|
62
|
-
const reexport = exports.find((e) => e.endsWith(`.${name}`));
|
|
63
|
-
if (!reexport)
|
|
64
|
-
return undefined;
|
|
65
|
-
const innerAlias = reexport.slice(0, reexport.length - name.length - 1);
|
|
66
|
-
const edge = graph.importEdges.get(mod.owner.source)?.get(innerAlias);
|
|
67
|
-
return edge ? resolveExported(graph, edge.targetSource, name, seen) : undefined;
|
|
68
|
-
}
|
|
69
|
-
/** Resolve the `!ref` under the cursor to its target resource's definition.
|
|
3
|
+
import { splitAliasQualified } from "./alias-qualified-value.js";
|
|
4
|
+
import { moduleForFile } from "./manifest-navigation.js";
|
|
5
|
+
import { resolveCelTarget } from "./resolve-cel-target.js";
|
|
6
|
+
import { isKindSlot, resolveKindTarget } from "./resolve-kind-target.js";
|
|
7
|
+
import { resolveRefTarget } from "./resolve-ref-target.js";
|
|
8
|
+
/** Resolve the symbol under the cursor to where it is declared.
|
|
9
|
+
*
|
|
10
|
+
* Three navigable symbol classes, each dispatched on what the cursor sits in
|
|
11
|
+
* rather than on the field it happens to be under:
|
|
70
12
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
13
|
+
* - a CEL identifier (`variables.port`, `resources.Store.conn`) → its
|
|
14
|
+
* declaration on the module doc, or the resource it names;
|
|
15
|
+
* - an alias-qualified kind (`kind: Http.Server`, `extends:`, `x-telo-ref`) →
|
|
16
|
+
* the `Telo.Definition` that registers it;
|
|
17
|
+
* - a `!ref` target → the resource instance it names.
|
|
18
|
+
*
|
|
19
|
+
* In the last two the alias half (`Http`) navigates to the import that declares
|
|
20
|
+
* it, and the suffix to what the alias qualifies. Returns `undefined` when the
|
|
21
|
+
* cursor is on nothing navigable, or the target can't be found (a kernel
|
|
22
|
+
* built-in, a scope-local name, an unexported entry, or an import that failed
|
|
23
|
+
* to load). */
|
|
78
24
|
export function buildDefinition(text, line, character, graph, currentFilePath, docs) {
|
|
79
25
|
const astDocs = docs ?? parseToAst(text);
|
|
80
|
-
const
|
|
81
|
-
if (!
|
|
82
|
-
return undefined;
|
|
83
|
-
// The scalar's range covers the ref target text (the value after `!ref`).
|
|
84
|
-
const source = text.slice(node.range[0], node.range[1]).trim();
|
|
85
|
-
if (!source)
|
|
26
|
+
const resolved = resolveNodeAtPosition(text, astDocs, line, character);
|
|
27
|
+
if (!resolved)
|
|
86
28
|
return undefined;
|
|
87
29
|
const currentModule = moduleForFile(graph, currentFilePath) ?? graph.entry;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const name = dot === -1 ? source : source.slice(dot + 1);
|
|
91
|
-
if (alias === undefined || alias === "Self") {
|
|
92
|
-
return locateResource([currentModule.owner, ...currentModule.partials], name);
|
|
30
|
+
if (resolved.cel) {
|
|
31
|
+
return resolveCelTarget(graph, currentModule, resolved.cel.segment, resolved.cel.offset);
|
|
93
32
|
}
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
33
|
+
const node = resolved.node;
|
|
34
|
+
if (resolved.slot !== "value" || node?.kind !== "scalar")
|
|
96
35
|
return undefined;
|
|
97
|
-
|
|
36
|
+
const split = splitAliasQualified(text, node, resolved.offset);
|
|
37
|
+
if (!split)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (node.tag === "!ref")
|
|
40
|
+
return resolveRefTarget(graph, currentModule, split);
|
|
41
|
+
if (isKindSlot(resolved))
|
|
42
|
+
return resolveKindTarget(graph, currentModule, split);
|
|
43
|
+
return undefined;
|
|
98
44
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { LoadedFile, LoadedGraph, LoadedModule } from "@telorun/analyzer";
|
|
2
|
+
import type { DefinitionResult } from "../types.js";
|
|
3
|
+
/** The shape every navigation lookup reads off a `LoadedFile.manifests` entry.
|
|
4
|
+
* Narrower than `ResourceManifest` on purpose — navigation only ever asks what
|
|
5
|
+
* kind a doc is and what it is named. */
|
|
6
|
+
export interface NavigableManifest {
|
|
7
|
+
kind?: string;
|
|
8
|
+
metadata?: {
|
|
9
|
+
name?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** The module whose owner or partials includes `filePath`. */
|
|
13
|
+
export declare function moduleForFile(graph: LoadedGraph, filePath: string): LoadedModule | undefined;
|
|
14
|
+
/** Every file in a module's scope, owner first. */
|
|
15
|
+
export declare function moduleFiles(mod: LoadedModule): LoadedFile[];
|
|
16
|
+
/** First manifest across `files` satisfying `match`, located at its
|
|
17
|
+
* `metadata.name` (or its first line as a fallback). Names are unique within a
|
|
18
|
+
* module scope, so the first hit is the definition. */
|
|
19
|
+
export declare function locateManifest(files: LoadedFile[], match: (manifest: NavigableManifest) => boolean): DefinitionResult | undefined;
|
|
20
|
+
/** The resource instance named `name` in a module scope. */
|
|
21
|
+
export declare function locateResource(files: LoadedFile[], name: string): DefinitionResult | undefined;
|
|
22
|
+
/** The `Telo.Definition` / `Telo.Abstract` doc registering kind suffix `name`.
|
|
23
|
+
* Filtered by doc kind so an instance sharing the name never shadows it. */
|
|
24
|
+
export declare function locateKindDefinition(files: LoadedFile[], name: string): DefinitionResult | undefined;
|
|
25
|
+
/** The module doc's JSON projection, for reading its `exports` block. */
|
|
26
|
+
export declare function moduleDoc(mod: LoadedModule): Record<string, unknown> | undefined;
|
|
27
|
+
/** The module's `metadata.name` — the identity a canonical kind (`<module>.<Kind>`)
|
|
28
|
+
* and an `ImportEdge.targetModuleName` are expressed in. */
|
|
29
|
+
export declare function moduleName(mod: LoadedModule): string | undefined;
|
|
30
|
+
/** A dotted key path on the module doc (`imports.Http`, `variables.port`),
|
|
31
|
+
* located at the key itself. */
|
|
32
|
+
export declare function locateModuleDocKey(mod: LoadedModule, path: string): DefinitionResult | undefined;
|
|
33
|
+
/** Where the import bound to `alias` is declared: the `imports:` map entry, or
|
|
34
|
+
* the legacy standalone `Telo.Import` doc named after the alias. */
|
|
35
|
+
export declare function locateImport(mod: LoadedModule, alias: string): DefinitionResult | undefined;
|
|
36
|
+
//# sourceMappingURL=manifest-navigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-navigation.d.ts","sourceRoot":"","sources":["../../src/definition/manifest-navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAS,MAAM,mBAAmB,CAAC;AACtF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;0CAE0C;AAC1C,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9B;AAKD,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAM5F;AAED,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,UAAU,EAAE,CAE3D;AA0BD;;wDAEwD;AACxD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,UAAU,EAAE,EACnB,KAAK,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,OAAO,GAC9C,gBAAgB,GAAG,SAAS,CAU9B;AAED,4DAA4D;AAC5D,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE9F;AAED;6EAC6E;AAC7E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,UAAU,EAAE,EACnB,IAAI,EAAE,MAAM,GACX,gBAAgB,GAAG,SAAS,CAE9B;AAUD,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAGhF;AAED;6DAC6D;AAC7D,wBAAgB,UAAU,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAEhE;AAED;iCACiC;AACjC,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,MAAM,GACX,gBAAgB,GAAG,SAAS,CAK9B;AAED;qEACqE;AACrE,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAK3F"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const MODULE_DOC_KINDS = new Set(["Telo.Application", "Telo.Library"]);
|
|
2
|
+
const KIND_DOC_KINDS = new Set(["Telo.Definition", "Telo.Abstract"]);
|
|
3
|
+
/** The module whose owner or partials includes `filePath`. */
|
|
4
|
+
export function moduleForFile(graph, filePath) {
|
|
5
|
+
for (const mod of graph.modules.values()) {
|
|
6
|
+
if (mod.owner.source === filePath)
|
|
7
|
+
return mod;
|
|
8
|
+
if (mod.partials.some((p) => p.source === filePath))
|
|
9
|
+
return mod;
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Every file in a module's scope, owner first. */
|
|
14
|
+
export function moduleFiles(mod) {
|
|
15
|
+
return [mod.owner, ...mod.partials];
|
|
16
|
+
}
|
|
17
|
+
function rangeAt(file, docIndex, key) {
|
|
18
|
+
return file.positions[docIndex]?.positionIndex.get(key);
|
|
19
|
+
}
|
|
20
|
+
/** The value span of `path`, falling back to its key when the value has none. */
|
|
21
|
+
function valueRange(file, docIndex, path) {
|
|
22
|
+
return rangeAt(file, docIndex, path) ?? rangeAt(file, docIndex, `@key:${path}`);
|
|
23
|
+
}
|
|
24
|
+
/** The key span of `path` — what a declaration jump underlines, so the target
|
|
25
|
+
* reads as `variables:` / `imports.Http`, not the whole block that follows. */
|
|
26
|
+
function keyRange(file, docIndex, path) {
|
|
27
|
+
return rangeAt(file, docIndex, `@key:${path}`) ?? rangeAt(file, docIndex, path);
|
|
28
|
+
}
|
|
29
|
+
function docStartRange(file, docIndex) {
|
|
30
|
+
const pos = file.positions[docIndex];
|
|
31
|
+
if (!pos)
|
|
32
|
+
return undefined;
|
|
33
|
+
return {
|
|
34
|
+
start: { line: pos.sourceLine, character: 0 },
|
|
35
|
+
end: { line: pos.sourceLine, character: 0 },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** First manifest across `files` satisfying `match`, located at its
|
|
39
|
+
* `metadata.name` (or its first line as a fallback). Names are unique within a
|
|
40
|
+
* module scope, so the first hit is the definition. */
|
|
41
|
+
export function locateManifest(files, match) {
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
for (let i = 0; i < file.manifests.length; i++) {
|
|
44
|
+
const manifest = file.manifests[i];
|
|
45
|
+
if (!manifest || !match(manifest))
|
|
46
|
+
continue;
|
|
47
|
+
const range = valueRange(file, i, "metadata.name") ?? docStartRange(file, i);
|
|
48
|
+
if (range)
|
|
49
|
+
return { uri: file.source, range };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
/** The resource instance named `name` in a module scope. */
|
|
55
|
+
export function locateResource(files, name) {
|
|
56
|
+
return locateManifest(files, (m) => m.metadata?.name === name);
|
|
57
|
+
}
|
|
58
|
+
/** The `Telo.Definition` / `Telo.Abstract` doc registering kind suffix `name`.
|
|
59
|
+
* Filtered by doc kind so an instance sharing the name never shadows it. */
|
|
60
|
+
export function locateKindDefinition(files, name) {
|
|
61
|
+
return locateManifest(files, (m) => KIND_DOC_KINDS.has(m.kind ?? "") && m.metadata?.name === name);
|
|
62
|
+
}
|
|
63
|
+
/** Index of the module's `Telo.Application` / `Telo.Library` doc in its owner
|
|
64
|
+
* file — where `imports`, `variables`, `secrets` and `ports` are declared. */
|
|
65
|
+
function moduleDocIndex(mod) {
|
|
66
|
+
return mod.owner.manifests.findIndex((m) => MODULE_DOC_KINDS.has(m?.kind ?? ""));
|
|
67
|
+
}
|
|
68
|
+
/** The module doc's JSON projection, for reading its `exports` block. */
|
|
69
|
+
export function moduleDoc(mod) {
|
|
70
|
+
const index = moduleDocIndex(mod);
|
|
71
|
+
return index < 0 ? undefined : mod.owner.manifests[index] ?? undefined;
|
|
72
|
+
}
|
|
73
|
+
/** The module's `metadata.name` — the identity a canonical kind (`<module>.<Kind>`)
|
|
74
|
+
* and an `ImportEdge.targetModuleName` are expressed in. */
|
|
75
|
+
export function moduleName(mod) {
|
|
76
|
+
return moduleDoc(mod)?.metadata?.name;
|
|
77
|
+
}
|
|
78
|
+
/** A dotted key path on the module doc (`imports.Http`, `variables.port`),
|
|
79
|
+
* located at the key itself. */
|
|
80
|
+
export function locateModuleDocKey(mod, path) {
|
|
81
|
+
const index = moduleDocIndex(mod);
|
|
82
|
+
if (index < 0)
|
|
83
|
+
return undefined;
|
|
84
|
+
const range = keyRange(mod.owner, index, path);
|
|
85
|
+
return range ? { uri: mod.owner.source, range } : undefined;
|
|
86
|
+
}
|
|
87
|
+
/** Where the import bound to `alias` is declared: the `imports:` map entry, or
|
|
88
|
+
* the legacy standalone `Telo.Import` doc named after the alias. */
|
|
89
|
+
export function locateImport(mod, alias) {
|
|
90
|
+
return (locateModuleDocKey(mod, `imports.${alias}`) ??
|
|
91
|
+
locateManifest(moduleFiles(mod), (m) => m.kind === "Telo.Import" && m.metadata?.name === alias));
|
|
92
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type CelSegment, type LoadedGraph, type LoadedModule } from "@telorun/analyzer";
|
|
2
|
+
import type { DefinitionResult } from "../types.js";
|
|
3
|
+
/** Resolve the CEL identifier under the cursor to where it is declared.
|
|
4
|
+
*
|
|
5
|
+
* `variables` / `secrets` / `ports` navigate to their block on the module doc,
|
|
6
|
+
* and their member to that block's entry. `resources` navigates through the
|
|
7
|
+
* same instance lookup a `!ref` uses. Anything else (a step result, a handler
|
|
8
|
+
* scope like `request`, a member of a resolved value) has no manifest
|
|
9
|
+
* declaration to jump to and resolves to nothing.
|
|
10
|
+
*
|
|
11
|
+
* A body the author is still writing may not parse; that means there is no
|
|
12
|
+
* chain to hit-test, not an error to surface from a navigation request — the
|
|
13
|
+
* analyzer reports the syntax error itself. Only that failure is tolerated: a
|
|
14
|
+
* defect in the CEL wrapper propagates rather than reading as "nothing to
|
|
15
|
+
* navigate to". */
|
|
16
|
+
export declare function resolveCelTarget(graph: LoadedGraph, currentModule: LoadedModule, segment: CelSegment, offset: number): DefinitionResult | undefined;
|
|
17
|
+
//# sourceMappingURL=resolve-cel-target.d.ts.map
|
|
@@ -0,0 +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;AAwGpD;;;;;;;;;;;;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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { CelParseError, } from "@telorun/analyzer";
|
|
2
|
+
import { locateImport, locateModuleDocKey, locateResource, moduleFiles, } from "./manifest-navigation.js";
|
|
3
|
+
import { resolveExportedResource } from "./resolve-export-chain.js";
|
|
4
|
+
/** Root CEL scopes whose members are declared as a block on the module doc, so
|
|
5
|
+
* `variables.port` navigates to `variables:` and then to its `port:` entry. */
|
|
6
|
+
const DECLARATION_SCOPES = new Set(["variables", "secrets", "ports"]);
|
|
7
|
+
/** Flatten `a.b.c` into its identifiers. Returns undefined as soon as the chain
|
|
8
|
+
* is rooted in something other than a plain identifier (a call, an index), so a
|
|
9
|
+
* navigable prefix is never invented out of a computed expression. */
|
|
10
|
+
function flattenChain(node) {
|
|
11
|
+
if (node.kind === "ident")
|
|
12
|
+
return [{ name: node.name, range: node.range }];
|
|
13
|
+
if (node.kind !== "member")
|
|
14
|
+
return undefined;
|
|
15
|
+
const head = flattenChain(node.target);
|
|
16
|
+
return head ? [...head, { name: node.property, range: node.propertyRange }] : undefined;
|
|
17
|
+
}
|
|
18
|
+
/** Exhaustive by construction: a new `CelNode` variant fails the build here
|
|
19
|
+
* rather than silently going unwalked, so the analyzer's node model and this
|
|
20
|
+
* walk cannot drift apart unnoticed. */
|
|
21
|
+
function celChildren(node) {
|
|
22
|
+
switch (node.kind) {
|
|
23
|
+
case "literal":
|
|
24
|
+
case "ident":
|
|
25
|
+
return [];
|
|
26
|
+
case "member":
|
|
27
|
+
return [node.target];
|
|
28
|
+
case "index":
|
|
29
|
+
return [node.target, node.index];
|
|
30
|
+
case "call":
|
|
31
|
+
return node.args;
|
|
32
|
+
case "methodCall":
|
|
33
|
+
return [node.receiver, ...node.args];
|
|
34
|
+
case "list":
|
|
35
|
+
return node.items;
|
|
36
|
+
case "map":
|
|
37
|
+
return node.entries.flatMap((e) => [e.key, e.value]);
|
|
38
|
+
case "ternary":
|
|
39
|
+
return [node.cond, node.then, node.else];
|
|
40
|
+
case "unary":
|
|
41
|
+
return [node.operand];
|
|
42
|
+
case "binary":
|
|
43
|
+
return [node.left, node.right];
|
|
44
|
+
}
|
|
45
|
+
const unhandled = node;
|
|
46
|
+
throw new Error(`Unhandled CEL node: ${JSON.stringify(unhandled)}`);
|
|
47
|
+
}
|
|
48
|
+
/** The dotted chain under `offset`, and which of its identifiers was hit. The
|
|
49
|
+
* walk is outermost-first so the longest chain wins — `resources.Store.conn`
|
|
50
|
+
* resolves as one chain rather than as its `resources.Store` prefix. */
|
|
51
|
+
function chainAt(node, offset) {
|
|
52
|
+
if (offset < node.range[0] || offset > node.range[1])
|
|
53
|
+
return undefined;
|
|
54
|
+
const parts = flattenChain(node);
|
|
55
|
+
if (parts) {
|
|
56
|
+
const index = parts.findIndex((p) => offset >= p.range[0] && offset <= p.range[1]);
|
|
57
|
+
if (index >= 0)
|
|
58
|
+
return { parts, index };
|
|
59
|
+
}
|
|
60
|
+
for (const child of celChildren(node)) {
|
|
61
|
+
const hit = chainAt(child, offset);
|
|
62
|
+
if (hit)
|
|
63
|
+
return hit;
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
/** `resources.<name>` is a local instance; `resources.<Alias>.<name>` is an
|
|
68
|
+
* imported module's exported one. A local name wins over an import alias, so a
|
|
69
|
+
* deeper access on a local instance (`resources.db.url`) reads as a field of
|
|
70
|
+
* that instance rather than as a cross-module lookup. */
|
|
71
|
+
function resolveResourceChain(graph, currentModule, parts, index) {
|
|
72
|
+
if (index === 0)
|
|
73
|
+
return undefined;
|
|
74
|
+
const first = parts[1].name;
|
|
75
|
+
const local = locateResource(moduleFiles(currentModule), first);
|
|
76
|
+
if (local)
|
|
77
|
+
return index === 1 ? local : undefined;
|
|
78
|
+
const edge = graph.importEdges.get(currentModule.owner.source)?.get(first);
|
|
79
|
+
if (!edge)
|
|
80
|
+
return undefined;
|
|
81
|
+
if (index === 1)
|
|
82
|
+
return locateImport(currentModule, first);
|
|
83
|
+
if (index === 2)
|
|
84
|
+
return resolveExportedResource(graph, edge.targetSource, parts[2].name);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
/** Resolve the CEL identifier under the cursor to where it is declared.
|
|
88
|
+
*
|
|
89
|
+
* `variables` / `secrets` / `ports` navigate to their block on the module doc,
|
|
90
|
+
* and their member to that block's entry. `resources` navigates through the
|
|
91
|
+
* same instance lookup a `!ref` uses. Anything else (a step result, a handler
|
|
92
|
+
* scope like `request`, a member of a resolved value) has no manifest
|
|
93
|
+
* declaration to jump to and resolves to nothing.
|
|
94
|
+
*
|
|
95
|
+
* A body the author is still writing may not parse; that means there is no
|
|
96
|
+
* chain to hit-test, not an error to surface from a navigation request — the
|
|
97
|
+
* analyzer reports the syntax error itself. Only that failure is tolerated: a
|
|
98
|
+
* defect in the CEL wrapper propagates rather than reading as "nothing to
|
|
99
|
+
* navigate to". */
|
|
100
|
+
export function resolveCelTarget(graph, currentModule, segment, offset) {
|
|
101
|
+
let ast;
|
|
102
|
+
try {
|
|
103
|
+
ast = segment.ast();
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (!(error instanceof CelParseError))
|
|
107
|
+
throw error;
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
const hit = chainAt(ast, offset);
|
|
111
|
+
if (!hit)
|
|
112
|
+
return undefined;
|
|
113
|
+
const { parts, index } = hit;
|
|
114
|
+
const root = parts[0].name;
|
|
115
|
+
if (DECLARATION_SCOPES.has(root)) {
|
|
116
|
+
if (index === 0)
|
|
117
|
+
return locateModuleDocKey(currentModule, root);
|
|
118
|
+
if (index === 1)
|
|
119
|
+
return locateModuleDocKey(currentModule, `${root}.${parts[1].name}`);
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
if (root === "resources")
|
|
123
|
+
return resolveResourceChain(graph, currentModule, parts, index);
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type LoadedGraph } from "@telorun/analyzer";
|
|
2
|
+
import type { DefinitionResult } from "../types.js";
|
|
3
|
+
/** Resolve the kind suffix `name` exported by the module at `targetSource` to
|
|
4
|
+
* the `Telo.Definition` / `Telo.Abstract` doc that owns it.
|
|
5
|
+
*
|
|
6
|
+
* The gate and the transitive re-export chain come from the analyzer's own
|
|
7
|
+
* `resolveExportedKinds` fixpoint rather than a local walk, so navigation
|
|
8
|
+
* cannot disagree with `telo check` about what an import exposes — including
|
|
9
|
+
* the two rules a hand-rolled walk gets wrong: `exports.kinds: []` gates
|
|
10
|
+
* everything while an absent block gates nothing, and a re-export FROM an
|
|
11
|
+
* ungated module resolves straight to it. */
|
|
12
|
+
export declare function resolveExportedKind(graph: LoadedGraph, targetSource: string, name: string): DefinitionResult | undefined;
|
|
13
|
+
/** Resolve the instance `name` exported by the module at `targetSource`,
|
|
14
|
+
* following `<Alias>.<name>` re-exports transitively. `seen` bounds cyclic
|
|
15
|
+
* import graphs.
|
|
16
|
+
*
|
|
17
|
+
* Unlike kinds there is no permissive default: the kernel reads
|
|
18
|
+
* `exports.resources ?? []` and builds its export table strictly from that, so
|
|
19
|
+
* an absent block exports nothing and an unlisted name is an
|
|
20
|
+
* `UNRESOLVED_REFERENCE` — navigating to it would tell the author a wiring is
|
|
21
|
+
* real that the analyzer rejects. */
|
|
22
|
+
export declare function resolveExportedResource(graph: LoadedGraph, targetSource: string, name: string, seen?: Set<string>): DefinitionResult | undefined;
|
|
23
|
+
//# sourceMappingURL=resolve-export-chain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-export-chain.d.ts","sourceRoot":"","sources":["../../src/definition/resolve-export-chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,WAAW,EAEjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAkCpD;;;;;;;;8CAQ8C;AAC9C,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GACX,gBAAgB,GAAG,SAAS,CAiC9B;AAED;;;;;;;;sCAQsC;AACtC,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,GAAG,CAAC,MAAM,CAAa,GAC5B,gBAAgB,GAAG,SAAS,CAgB9B"}
|