@telorun/ide-support 0.8.0 → 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/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
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CelParseError,
|
|
3
|
+
type CelNode,
|
|
4
|
+
type CelSegment,
|
|
5
|
+
type LoadedGraph,
|
|
6
|
+
type LoadedModule,
|
|
7
|
+
} from "@telorun/analyzer";
|
|
8
|
+
import type { DefinitionResult } from "../types.js";
|
|
9
|
+
import {
|
|
10
|
+
locateImport,
|
|
11
|
+
locateModuleDocKey,
|
|
12
|
+
locateResource,
|
|
13
|
+
moduleFiles,
|
|
14
|
+
} from "./manifest-navigation.js";
|
|
15
|
+
import { resolveExportedResource } from "./resolve-export-chain.js";
|
|
16
|
+
|
|
17
|
+
/** Root CEL scopes whose members are declared as a block on the module doc, so
|
|
18
|
+
* `variables.port` navigates to `variables:` and then to its `port:` entry. */
|
|
19
|
+
const DECLARATION_SCOPES = new Set(["variables", "secrets", "ports"]);
|
|
20
|
+
|
|
21
|
+
/** One identifier of a dotted CEL chain, with the span the cursor hit-tests
|
|
22
|
+
* against. */
|
|
23
|
+
interface ChainPart {
|
|
24
|
+
name: string;
|
|
25
|
+
range: [number, number];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Flatten `a.b.c` into its identifiers. Returns undefined as soon as the chain
|
|
29
|
+
* is rooted in something other than a plain identifier (a call, an index), so a
|
|
30
|
+
* navigable prefix is never invented out of a computed expression. */
|
|
31
|
+
function flattenChain(node: CelNode): ChainPart[] | undefined {
|
|
32
|
+
if (node.kind === "ident") return [{ name: node.name, range: node.range }];
|
|
33
|
+
if (node.kind !== "member") return undefined;
|
|
34
|
+
const head = flattenChain(node.target);
|
|
35
|
+
return head ? [...head, { name: node.property, range: node.propertyRange }] : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Exhaustive by construction: a new `CelNode` variant fails the build here
|
|
39
|
+
* rather than silently going unwalked, so the analyzer's node model and this
|
|
40
|
+
* walk cannot drift apart unnoticed. */
|
|
41
|
+
function celChildren(node: CelNode): CelNode[] {
|
|
42
|
+
switch (node.kind) {
|
|
43
|
+
case "literal":
|
|
44
|
+
case "ident":
|
|
45
|
+
return [];
|
|
46
|
+
case "member":
|
|
47
|
+
return [node.target];
|
|
48
|
+
case "index":
|
|
49
|
+
return [node.target, node.index];
|
|
50
|
+
case "call":
|
|
51
|
+
return node.args;
|
|
52
|
+
case "methodCall":
|
|
53
|
+
return [node.receiver, ...node.args];
|
|
54
|
+
case "list":
|
|
55
|
+
return node.items;
|
|
56
|
+
case "map":
|
|
57
|
+
return node.entries.flatMap((e) => [e.key, e.value]);
|
|
58
|
+
case "ternary":
|
|
59
|
+
return [node.cond, node.then, node.else];
|
|
60
|
+
case "unary":
|
|
61
|
+
return [node.operand];
|
|
62
|
+
case "binary":
|
|
63
|
+
return [node.left, node.right];
|
|
64
|
+
}
|
|
65
|
+
const unhandled: never = node;
|
|
66
|
+
throw new Error(`Unhandled CEL node: ${JSON.stringify(unhandled)}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The dotted chain under `offset`, and which of its identifiers was hit. The
|
|
70
|
+
* walk is outermost-first so the longest chain wins — `resources.Store.conn`
|
|
71
|
+
* resolves as one chain rather than as its `resources.Store` prefix. */
|
|
72
|
+
function chainAt(
|
|
73
|
+
node: CelNode,
|
|
74
|
+
offset: number,
|
|
75
|
+
): { parts: ChainPart[]; index: number } | undefined {
|
|
76
|
+
if (offset < node.range[0] || offset > node.range[1]) return undefined;
|
|
77
|
+
const parts = flattenChain(node);
|
|
78
|
+
if (parts) {
|
|
79
|
+
const index = parts.findIndex((p) => offset >= p.range[0] && offset <= p.range[1]);
|
|
80
|
+
if (index >= 0) return { parts, index };
|
|
81
|
+
}
|
|
82
|
+
for (const child of celChildren(node)) {
|
|
83
|
+
const hit = chainAt(child, offset);
|
|
84
|
+
if (hit) return hit;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** `resources.<name>` is a local instance; `resources.<Alias>.<name>` is an
|
|
90
|
+
* imported module's exported one. A local name wins over an import alias, so a
|
|
91
|
+
* deeper access on a local instance (`resources.db.url`) reads as a field of
|
|
92
|
+
* that instance rather than as a cross-module lookup. */
|
|
93
|
+
function resolveResourceChain(
|
|
94
|
+
graph: LoadedGraph,
|
|
95
|
+
currentModule: LoadedModule,
|
|
96
|
+
parts: ChainPart[],
|
|
97
|
+
index: number,
|
|
98
|
+
): DefinitionResult | undefined {
|
|
99
|
+
if (index === 0) return undefined;
|
|
100
|
+
const first = parts[1].name;
|
|
101
|
+
|
|
102
|
+
const local = locateResource(moduleFiles(currentModule), first);
|
|
103
|
+
if (local) return index === 1 ? local : undefined;
|
|
104
|
+
|
|
105
|
+
const edge = graph.importEdges.get(currentModule.owner.source)?.get(first);
|
|
106
|
+
if (!edge) return undefined;
|
|
107
|
+
if (index === 1) return locateImport(currentModule, first);
|
|
108
|
+
if (index === 2) return resolveExportedResource(graph, edge.targetSource, parts[2].name);
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Resolve the CEL identifier under the cursor to where it is declared.
|
|
113
|
+
*
|
|
114
|
+
* `variables` / `secrets` / `ports` navigate to their block on the module doc,
|
|
115
|
+
* and their member to that block's entry. `resources` navigates through the
|
|
116
|
+
* same instance lookup a `!ref` uses. Anything else (a step result, a handler
|
|
117
|
+
* scope like `request`, a member of a resolved value) has no manifest
|
|
118
|
+
* declaration to jump to and resolves to nothing.
|
|
119
|
+
*
|
|
120
|
+
* A body the author is still writing may not parse; that means there is no
|
|
121
|
+
* chain to hit-test, not an error to surface from a navigation request — the
|
|
122
|
+
* analyzer reports the syntax error itself. Only that failure is tolerated: a
|
|
123
|
+
* defect in the CEL wrapper propagates rather than reading as "nothing to
|
|
124
|
+
* navigate to". */
|
|
125
|
+
export function resolveCelTarget(
|
|
126
|
+
graph: LoadedGraph,
|
|
127
|
+
currentModule: LoadedModule,
|
|
128
|
+
segment: CelSegment,
|
|
129
|
+
offset: number,
|
|
130
|
+
): DefinitionResult | undefined {
|
|
131
|
+
let ast: CelNode;
|
|
132
|
+
try {
|
|
133
|
+
ast = segment.ast();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (!(error instanceof CelParseError)) throw error;
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const hit = chainAt(ast, offset);
|
|
140
|
+
if (!hit) return undefined;
|
|
141
|
+
const { parts, index } = hit;
|
|
142
|
+
const root = parts[0].name;
|
|
143
|
+
|
|
144
|
+
if (DECLARATION_SCOPES.has(root)) {
|
|
145
|
+
if (index === 0) return locateModuleDocKey(currentModule, root);
|
|
146
|
+
if (index === 1) return locateModuleDocKey(currentModule, `${root}.${parts[1].name}`);
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
if (root === "resources") return resolveResourceChain(graph, currentModule, parts, index);
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseExportEntry,
|
|
3
|
+
resolveExportedKinds,
|
|
4
|
+
type LoadedGraph,
|
|
5
|
+
type LoadedModule,
|
|
6
|
+
} from "@telorun/analyzer";
|
|
7
|
+
import type { DefinitionResult } from "../types.js";
|
|
8
|
+
import {
|
|
9
|
+
locateKindDefinition,
|
|
10
|
+
locateResource,
|
|
11
|
+
moduleDoc,
|
|
12
|
+
moduleFiles,
|
|
13
|
+
moduleName,
|
|
14
|
+
} from "./manifest-navigation.js";
|
|
15
|
+
|
|
16
|
+
/** One `exports.*` list as authored. `undefined` means the block is ABSENT,
|
|
17
|
+
* which is not the same as an empty list — the distinction is the gate itself,
|
|
18
|
+
* and it is load-bearing in both halves (`AliasResolver.registerImport`,
|
|
19
|
+
* `ModuleContext.buildExportTable`), so it must survive here too. */
|
|
20
|
+
function exportList(
|
|
21
|
+
mod: LoadedModule,
|
|
22
|
+
block: "kinds" | "resources",
|
|
23
|
+
): readonly string[] | undefined {
|
|
24
|
+
const exports = (moduleDoc(mod) as { exports?: Record<string, unknown> } | undefined)?.exports;
|
|
25
|
+
const list = exports?.[block];
|
|
26
|
+
if (!Array.isArray(list)) return undefined;
|
|
27
|
+
return list.filter((e): e is string => typeof e === "string");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Every loaded module keyed by its `metadata.name` — what `resolveExportedKinds`
|
|
31
|
+
* speaks, since a canonical kind names its owning module rather than a URL. */
|
|
32
|
+
function modulesByName(graph: LoadedGraph): Map<string, LoadedModule> {
|
|
33
|
+
const byName = new Map<string, LoadedModule>();
|
|
34
|
+
for (const mod of graph.modules.values()) {
|
|
35
|
+
const name = moduleName(mod);
|
|
36
|
+
if (name && !byName.has(name)) byName.set(name, mod);
|
|
37
|
+
}
|
|
38
|
+
return byName;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Resolve the kind suffix `name` exported by the module at `targetSource` to
|
|
42
|
+
* the `Telo.Definition` / `Telo.Abstract` doc that owns it.
|
|
43
|
+
*
|
|
44
|
+
* The gate and the transitive re-export chain come from the analyzer's own
|
|
45
|
+
* `resolveExportedKinds` fixpoint rather than a local walk, so navigation
|
|
46
|
+
* cannot disagree with `telo check` about what an import exposes — including
|
|
47
|
+
* the two rules a hand-rolled walk gets wrong: `exports.kinds: []` gates
|
|
48
|
+
* everything while an absent block gates nothing, and a re-export FROM an
|
|
49
|
+
* ungated module resolves straight to it. */
|
|
50
|
+
export function resolveExportedKind(
|
|
51
|
+
graph: LoadedGraph,
|
|
52
|
+
targetSource: string,
|
|
53
|
+
name: string,
|
|
54
|
+
): DefinitionResult | undefined {
|
|
55
|
+
const target = graph.modules.get(targetSource);
|
|
56
|
+
if (!target) return undefined;
|
|
57
|
+
|
|
58
|
+
// No `exports.kinds` block → every kind the module defines is importable (the
|
|
59
|
+
// legacy permissive default the kernel still honors for already-published
|
|
60
|
+
// versions). The fixpoint builds tables only from declared entries, so an
|
|
61
|
+
// ungated module has none to look this up in — and this is also the cheap
|
|
62
|
+
// path, so it settles before anything is indexed.
|
|
63
|
+
if (exportList(target, "kinds") === undefined) {
|
|
64
|
+
return locateKindDefinition(moduleFiles(target), name);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const targetName = moduleName(target);
|
|
68
|
+
if (!targetName) return undefined;
|
|
69
|
+
|
|
70
|
+
const byName = modulesByName(graph);
|
|
71
|
+
const tables = resolveExportedKinds(
|
|
72
|
+
[...byName].map(([module, mod]) => ({ module, exportsKinds: exportList(mod, "kinds") })),
|
|
73
|
+
(module, alias) => {
|
|
74
|
+
const owner = byName.get(module)?.owner.source;
|
|
75
|
+
return owner ? graph.importEdges.get(owner)?.get(alias)?.targetModuleName ?? undefined : undefined;
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const canonical = tables.get(targetName)?.get(name);
|
|
80
|
+
if (!canonical) return undefined;
|
|
81
|
+
|
|
82
|
+
// `<owningModule>.<Kind>` — a re-export resolves to its true owner, so the
|
|
83
|
+
// jump lands where the kind is actually declared, however many hops away.
|
|
84
|
+
const dot = canonical.lastIndexOf(".");
|
|
85
|
+
const owner = byName.get(canonical.slice(0, dot));
|
|
86
|
+
return owner ? locateKindDefinition(moduleFiles(owner), canonical.slice(dot + 1)) : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Resolve the instance `name` exported by the module at `targetSource`,
|
|
90
|
+
* following `<Alias>.<name>` re-exports transitively. `seen` bounds cyclic
|
|
91
|
+
* import graphs.
|
|
92
|
+
*
|
|
93
|
+
* Unlike kinds there is no permissive default: the kernel reads
|
|
94
|
+
* `exports.resources ?? []` and builds its export table strictly from that, so
|
|
95
|
+
* an absent block exports nothing and an unlisted name is an
|
|
96
|
+
* `UNRESOLVED_REFERENCE` — navigating to it would tell the author a wiring is
|
|
97
|
+
* real that the analyzer rejects. */
|
|
98
|
+
export function resolveExportedResource(
|
|
99
|
+
graph: LoadedGraph,
|
|
100
|
+
targetSource: string,
|
|
101
|
+
name: string,
|
|
102
|
+
seen: Set<string> = new Set(),
|
|
103
|
+
): DefinitionResult | undefined {
|
|
104
|
+
if (seen.has(targetSource)) return undefined;
|
|
105
|
+
seen.add(targetSource);
|
|
106
|
+
const target = graph.modules.get(targetSource);
|
|
107
|
+
if (!target) return undefined;
|
|
108
|
+
|
|
109
|
+
const entries = (exportList(target, "resources") ?? []).map(parseExportEntry);
|
|
110
|
+
const entry = entries.find((e) => e.name === name);
|
|
111
|
+
if (!entry) return undefined;
|
|
112
|
+
|
|
113
|
+
// `Self.<name>` names the declaring module's own instance, exactly as a bare
|
|
114
|
+
// name does — `ModuleContext.buildExportTable` treats the two alike.
|
|
115
|
+
if (!entry.alias || entry.alias === "Self") return locateResource(moduleFiles(target), name);
|
|
116
|
+
|
|
117
|
+
const edge = graph.importEdges.get(target.owner.source)?.get(entry.alias);
|
|
118
|
+
return edge ? resolveExportedResource(graph, edge.targetSource, name, seen) : undefined;
|
|
119
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { LoadedGraph, LoadedModule } from "@telorun/analyzer";
|
|
2
|
+
import type { DefinitionResult } from "../types.js";
|
|
3
|
+
import type { ResolvedCursor } from "../completions/resolve-node.js";
|
|
4
|
+
import type { AliasQualifiedValue } from "./alias-qualified-value.js";
|
|
5
|
+
import { locateImport, locateKindDefinition, moduleFiles } from "./manifest-navigation.js";
|
|
6
|
+
import { resolveExportedKind } from "./resolve-export-chain.js";
|
|
7
|
+
|
|
8
|
+
const DEFINITION_DOC_KINDS = new Set(["Telo.Definition", "Telo.Abstract"]);
|
|
9
|
+
|
|
10
|
+
/** Whether the cursor sits in a slot whose value is an alias-qualified kind.
|
|
11
|
+
*
|
|
12
|
+
* `x-telo-ref` is an `x-telo-*` annotation, unambiguous wherever it appears.
|
|
13
|
+
* `extends:` is only a kind at the top level of a definition doc, so it is
|
|
14
|
+
* gated on both. `kind:` is positional by nature: a map carrying a
|
|
15
|
+
* `<Alias>.<Kind>` value under `kind` IS an inline resource declaration as far
|
|
16
|
+
* as the analyzer is concerned too (`resourceKindOf`), so there is no further
|
|
17
|
+
* structure to check — an unresolvable value simply navigates nowhere.
|
|
18
|
+
*
|
|
19
|
+
* `capability:` is deliberately absent — its values are kernel built-ins with
|
|
20
|
+
* no manifest to jump to (hover documents them instead). */
|
|
21
|
+
export function isKindSlot(resolved: ResolvedCursor): boolean {
|
|
22
|
+
const key = resolved.path[resolved.path.length - 1];
|
|
23
|
+
if (key === "x-telo-ref" || key === "kind") return true;
|
|
24
|
+
return (
|
|
25
|
+
key === "extends" &&
|
|
26
|
+
resolved.path.length === 1 &&
|
|
27
|
+
DEFINITION_DOC_KINDS.has(resolved.docKind ?? "")
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Resolve an alias-qualified kind (`Http.Server`) to the `Telo.Definition` /
|
|
32
|
+
* `Telo.Abstract` doc that registers it, or — when the cursor sits on the
|
|
33
|
+
* alias — to the import that declares the alias.
|
|
34
|
+
*
|
|
35
|
+
* `Self.<Kind>` is the declaring module's own kind; `Telo.<Kind>` is a kernel
|
|
36
|
+
* built-in with no manifest, so it resolves to nothing. Across an import the
|
|
37
|
+
* walk honors `exports.kinds` and follows `<Inner>.<Kind>` re-exports to the
|
|
38
|
+
* owning module, matching what the kernel resolves the kind to at runtime. */
|
|
39
|
+
export function resolveKindTarget(
|
|
40
|
+
graph: LoadedGraph,
|
|
41
|
+
currentModule: LoadedModule,
|
|
42
|
+
kind: AliasQualifiedValue,
|
|
43
|
+
): DefinitionResult | undefined {
|
|
44
|
+
const { alias, name, onAlias } = kind;
|
|
45
|
+
|
|
46
|
+
if (alias === undefined) {
|
|
47
|
+
// The legacy `<namespace>/<module>#<Kind>` identity form of `x-telo-ref`
|
|
48
|
+
// still resolves for already-published module versions, but it names a
|
|
49
|
+
// module this manifest need not import, so there is no alias to follow.
|
|
50
|
+
if (name.includes("#")) return undefined;
|
|
51
|
+
return locateKindDefinition(moduleFiles(currentModule), name);
|
|
52
|
+
}
|
|
53
|
+
if (alias === "Self") return locateKindDefinition(moduleFiles(currentModule), name);
|
|
54
|
+
if (alias === "Telo") return undefined;
|
|
55
|
+
|
|
56
|
+
if (onAlias) return locateImport(currentModule, alias);
|
|
57
|
+
|
|
58
|
+
const edge = graph.importEdges.get(currentModule.owner.source)?.get(alias);
|
|
59
|
+
if (!edge) return undefined;
|
|
60
|
+
return resolveExportedKind(graph, edge.targetSource, name);
|
|
61
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { LoadedGraph, LoadedModule } from "@telorun/analyzer";
|
|
2
|
+
import type { DefinitionResult } from "../types.js";
|
|
3
|
+
import type { AliasQualifiedValue } from "./alias-qualified-value.js";
|
|
4
|
+
import { locateImport, locateResource, moduleFiles } from "./manifest-navigation.js";
|
|
5
|
+
import { resolveExportedResource } from "./resolve-export-chain.js";
|
|
6
|
+
|
|
7
|
+
/** Resolve a `!ref` target to the resource instance it names.
|
|
8
|
+
*
|
|
9
|
+
* The grammar mirrors `resolveRefSentinels`: a bare name (or `Self.name`) is a
|
|
10
|
+
* local resource in the current module; `Alias.name` is an exported instance of
|
|
11
|
+
* the module the import `Alias` points at, followed transitively through
|
|
12
|
+
* re-exports and gated on each module's `exports.resources`. The alias half
|
|
13
|
+
* navigates to the import that declares it. Returns `undefined` when the target
|
|
14
|
+
* can't be found (a scope-local name, an instance the target does not export,
|
|
15
|
+
* or an import that failed to load). */
|
|
16
|
+
export function resolveRefTarget(
|
|
17
|
+
graph: LoadedGraph,
|
|
18
|
+
currentModule: LoadedModule,
|
|
19
|
+
ref: AliasQualifiedValue,
|
|
20
|
+
): DefinitionResult | undefined {
|
|
21
|
+
const { alias, name, onAlias } = ref;
|
|
22
|
+
|
|
23
|
+
if (alias === undefined || alias === "Self") {
|
|
24
|
+
// `Self` names the declaring module itself — there is no import to jump to,
|
|
25
|
+
// so the alias half resolves like the name half.
|
|
26
|
+
return locateResource(moduleFiles(currentModule), name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (onAlias) return locateImport(currentModule, alias);
|
|
30
|
+
|
|
31
|
+
const edge = graph.importEdges.get(currentModule.owner.source)?.get(alias);
|
|
32
|
+
if (!edge) return undefined;
|
|
33
|
+
return resolveExportedResource(graph, edge.targetSource, name);
|
|
34
|
+
}
|