@telorun/ide-support 0.10.0 → 0.11.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/dist/completions/build.d.ts.map +1 -1
- package/dist/completions/build.js +20 -13
- package/dist/completions/detect-context.d.ts +9 -6
- package/dist/completions/detect-context.d.ts.map +1 -1
- package/dist/completions/detect-context.js +21 -20
- package/dist/hover/build-hover.d.ts.map +1 -1
- package/dist/hover/build-hover.js +8 -4
- package/package.json +2 -2
- package/src/completions/build.ts +19 -12
- package/src/completions/detect-context.ts +21 -18
- package/src/hover/build-hover.ts +8 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/completions/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAe,MAAM,mBAAmB,CAAC;AACrG,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/completions/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAe,MAAM,mBAAmB,CAAC;AACrG,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AA8I3E,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,gBAAgB,GAAG,SAAS,EACtC,OAAO,CAAC,EAAE,qBAAqB,EAC/B,IAAI,CAAC,EAAE,WAAW,EAAE,GACnB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAyB7B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseToAst } from "@telorun/analyzer";
|
|
2
|
-
import { detectContext,
|
|
2
|
+
import { detectContext, lookupRefConstraints } from "./detect-context.js";
|
|
3
3
|
import { importSourceCompletions } from "./import-source.js";
|
|
4
4
|
import { propKeyCompletions } from "./prop-keys.js";
|
|
5
5
|
import { CAPABILITY_VALUES } from "./valid-capabilities.js";
|
|
@@ -36,16 +36,20 @@ function extractInFileResources(docs) {
|
|
|
36
36
|
* kind match. Falls back to listing every in-file resource so the
|
|
37
37
|
* user still sees something rather than nothing when the registry
|
|
38
38
|
* doesn't recognize the kind yet. */
|
|
39
|
-
function refNameCompletions(docs, refKind,
|
|
39
|
+
function refNameCompletions(docs, refKind, refConstraints, registry, replaceRange) {
|
|
40
40
|
const resources = extractInFileResources(docs);
|
|
41
41
|
let acceptable;
|
|
42
42
|
if (refKind) {
|
|
43
43
|
acceptable = new Set([refKind]);
|
|
44
44
|
}
|
|
45
|
-
else if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
else if (refConstraints.length > 0 && registry) {
|
|
46
|
+
// Union across the slot's accepted kinds: a resource satisfying any one of
|
|
47
|
+
// them fills the slot. A constraint the registry can't resolve contributes
|
|
48
|
+
// nothing rather than narrowing to the ones it could.
|
|
49
|
+
const resolved = refConstraints.map((c) => registry.userFacingKindsForRef(c));
|
|
50
|
+
if (resolved.some(Boolean)) {
|
|
51
|
+
acceptable = new Set(resolved.flatMap((kinds) => kinds ?? []));
|
|
52
|
+
}
|
|
49
53
|
}
|
|
50
54
|
const seen = new Set();
|
|
51
55
|
const out = [];
|
|
@@ -74,10 +78,13 @@ function refConstrainedKinds(registry, parentDocKind, parentYamlPath) {
|
|
|
74
78
|
const definition = registry.resolveDefinition(parentDocKind);
|
|
75
79
|
if (!definition?.schema)
|
|
76
80
|
return undefined;
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
81
|
+
const constraints = lookupRefConstraints(definition.schema, parentYamlPath);
|
|
82
|
+
if (constraints.length === 0)
|
|
83
|
+
return undefined;
|
|
84
|
+
const resolved = constraints.map((c) => registry.userFacingKindsForRef(c));
|
|
85
|
+
if (!resolved.some(Boolean))
|
|
79
86
|
return undefined;
|
|
80
|
-
return
|
|
87
|
+
return [...new Set(resolved.flatMap((kinds) => kinds ?? []))];
|
|
81
88
|
}
|
|
82
89
|
function kindCompletions(registry, docKind, yamlPath, replaceRange) {
|
|
83
90
|
let kinds;
|
|
@@ -126,10 +133,10 @@ export async function buildCompletions(text, line, character, registry, adapter,
|
|
|
126
133
|
return capabilityCompletions();
|
|
127
134
|
if (ctx.type === "ref-name") {
|
|
128
135
|
const definition = registry?.resolveDefinition(ctx.docKind);
|
|
129
|
-
const
|
|
130
|
-
?
|
|
131
|
-
:
|
|
132
|
-
return refNameCompletions(astDocs, ctx.refKind,
|
|
136
|
+
const refConstraints = definition?.schema
|
|
137
|
+
? lookupRefConstraints(definition.schema, ctx.yamlPath)
|
|
138
|
+
: [];
|
|
139
|
+
return refNameCompletions(astDocs, ctx.refKind, refConstraints, registry, ctx.replaceRange);
|
|
133
140
|
}
|
|
134
141
|
if (ctx.type === "field-value") {
|
|
135
142
|
if (ctx.field === "import-source") {
|
|
@@ -56,12 +56,15 @@ export declare function navigateSchema(schema: Record<string, any>, path: string
|
|
|
56
56
|
* completion to discover what kind of resource the user is targeting in an
|
|
57
57
|
* object-form ref. Walking stops at the first line with a strictly smaller
|
|
58
58
|
* indent (that's the parent's structural boundary). */
|
|
59
|
-
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
|
|
59
|
+
/** Every kind the `x-telo-ref` slot at `yamlPath` accepts, or an empty array
|
|
60
|
+
* when the path doesn't resolve or declares no constraint.
|
|
61
|
+
*
|
|
62
|
+
* All of them, not the first: a slot accepting `Invocable | Runnable` used to
|
|
63
|
+
* complete only the invocables, because a single-constraint lookup stopped at
|
|
64
|
+
* the first branch. The analyzer's accessor unions a `kind:` list and the
|
|
65
|
+
* `anyOf` / `oneOf` branches alike, so completion now offers what the slot
|
|
66
|
+
* actually takes. */
|
|
67
|
+
export declare function lookupRefConstraints(definitionSchema: Record<string, any>, yamlPath: string[]): string[];
|
|
65
68
|
/** Derive a `CompletionCtx` from the AST-resolved cursor (Approach B). The
|
|
66
69
|
* structural resolution lives in `resolveNodeAtPosition`; this only maps a
|
|
67
70
|
* resolved slot onto the completion the editor should offer. `docs` lets a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-context.d.ts","sourceRoot":"","sources":["../../src/completions/detect-context.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"detect-context.d.ts","sourceRoot":"","sources":["../../src/completions/detect-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACjG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,YAAY,EAAE,YAAY,EAAE,CAAC;AAE7B,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,MAAM,CAAC;IACb;;;uEAGmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;+BAE2B;IAC3B,YAAY,EAAE,YAAY,CAAC;CAC5B,GACD;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GACpF;IACE;;;gDAG4C;IAC5C,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB;6DACyD;IACzD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,YAAY,CAAC;CAC5B,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,YAAY,EAAE,YAAY,CAAC;CAC5B,CAAC;AAuBN;;;;;sCAKsC;AACtC,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,EAAE,GACb,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA4BjC;AAwCD;;;;;wDAKwD;AACxD;;;;;;;sBAOsB;AACtB,wBAAgB,oBAAoB,CAClC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACrC,QAAQ,EAAE,MAAM,EAAE,GACjB,MAAM,EAAE,CAIV;AAED;;;;oBAIoB;AACpB,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,WAAW,EAAE,GACnB,aAAa,GAAG,SAAS,CAsE3B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseToAst } from "@telorun/analyzer";
|
|
1
|
+
import { parseToAst, readRefSlot, refSlotAnnotation } from "@telorun/analyzer";
|
|
2
2
|
import { resolveNodeAtPosition } from "./resolve-node.js";
|
|
3
3
|
/** Returns every schema branch reachable from `node` after peeling `anyOf` /
|
|
4
4
|
* `oneOf` recursively. A branch with no combinators is its own only entry.
|
|
@@ -66,9 +66,12 @@ export function navigateSchema(schema, path) {
|
|
|
66
66
|
/** Merge multiple peeled schema branches into one node for completion purposes.
|
|
67
67
|
* Property maps are unioned (first branch wins on key collision). `required`
|
|
68
68
|
* becomes the intersection so optional-in-any-branch keys still surface.
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
*
|
|
70
|
+
* The parent's reference slot is re-stamped on the merged node, because peeling
|
|
71
|
+
* is exactly what destroys it: for the canonical multi-kind shape
|
|
72
|
+
* (`anyOf: [{x-telo-ref: A}, {x-telo-ref: B}]`) the constraint lives ONLY in the
|
|
73
|
+
* branches, and merging them left a node declaring no slot at all. Re-emitted
|
|
74
|
+
* through the analyzer's accessor so both annotation shapes survive the merge. */
|
|
72
75
|
function unionLeaves(parent, leaves) {
|
|
73
76
|
const properties = {};
|
|
74
77
|
const requiredSets = [];
|
|
@@ -87,8 +90,9 @@ function unionLeaves(parent, leaves) {
|
|
|
87
90
|
required = [...requiredSets[0]].filter((k) => requiredSets.every((s) => s.has(k)));
|
|
88
91
|
}
|
|
89
92
|
const out = { type: "object", properties, required };
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
const slot = readRefSlot(parent);
|
|
94
|
+
if (slot && slot.kinds.length > 0)
|
|
95
|
+
out["x-telo-ref"] = refSlotAnnotation(slot);
|
|
92
96
|
return out;
|
|
93
97
|
}
|
|
94
98
|
/** Walks up and down from `cursorLine` looking for a sibling line at the
|
|
@@ -97,22 +101,19 @@ function unionLeaves(parent, leaves) {
|
|
|
97
101
|
* completion to discover what kind of resource the user is targeting in an
|
|
98
102
|
* object-form ref. Walking stops at the first line with a strictly smaller
|
|
99
103
|
* indent (that's the parent's structural boundary). */
|
|
100
|
-
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
|
|
104
|
+
/** Every kind the `x-telo-ref` slot at `yamlPath` accepts, or an empty array
|
|
105
|
+
* when the path doesn't resolve or declares no constraint.
|
|
106
|
+
*
|
|
107
|
+
* All of them, not the first: a slot accepting `Invocable | Runnable` used to
|
|
108
|
+
* complete only the invocables, because a single-constraint lookup stopped at
|
|
109
|
+
* the first branch. The analyzer's accessor unions a `kind:` list and the
|
|
110
|
+
* `anyOf` / `oneOf` branches alike, so completion now offers what the slot
|
|
111
|
+
* actually takes. */
|
|
112
|
+
export function lookupRefConstraints(definitionSchema, yamlPath) {
|
|
106
113
|
const node = navigateSchema(definitionSchema, yamlPath);
|
|
107
114
|
if (!node)
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
return node["x-telo-ref"];
|
|
111
|
-
for (const branch of peelCombinators(node)) {
|
|
112
|
-
if (typeof branch["x-telo-ref"] === "string")
|
|
113
|
-
return branch["x-telo-ref"];
|
|
114
|
-
}
|
|
115
|
-
return undefined;
|
|
115
|
+
return [];
|
|
116
|
+
return readRefSlot(node)?.kinds ?? [];
|
|
116
117
|
}
|
|
117
118
|
/** Derive a `CompletionCtx` from the AST-resolved cursor (Approach B). The
|
|
118
119
|
* structural resolution lives in `resolveNodeAtPosition`; this only maps a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-hover.d.ts","sourceRoot":"","sources":["../../src/hover/build-hover.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import { parseToAst, } from "@telorun/analyzer";
|
|
1
|
+
import { parseToAst, readRefSlot, } from "@telorun/analyzer";
|
|
2
2
|
import { navigateSchema } from "../completions/detect-context.js";
|
|
3
3
|
import { resolveNodeAtPosition, scalarString, } from "../completions/resolve-node.js";
|
|
4
4
|
import { CAPABILITY_DOCS } from "../completions/valid-capabilities.js";
|
|
@@ -57,9 +57,13 @@ function fieldHover(keyName, field) {
|
|
|
57
57
|
if (typeof field.description === "string" && field.description) {
|
|
58
58
|
lines.push("", field.description);
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
// Through the shared accessor, so a slot whose constraint sits in an `anyOf`
|
|
61
|
+
// branch — or in a `kind:` list — hovers like any other. Reading the raw
|
|
62
|
+
// annotation here meant those slots showed no reference line at all.
|
|
63
|
+
const refKinds = readRefSlot(field)?.kinds ?? [];
|
|
64
|
+
if (refKinds.length > 0) {
|
|
65
|
+
lines.push("", `Reference → ${refKinds.map((k) => `\`${k}\``).join(" | ")}`);
|
|
66
|
+
}
|
|
63
67
|
if (Array.isArray(field.enum) && field.enum.length > 0) {
|
|
64
68
|
lines.push("", `Allowed: ${field.enum.map((v) => `\`${v}\``).join(", ")}`);
|
|
65
69
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/ide-support",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Editor-host-agnostic IDE support (completions, diagnostic normalization) for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"src/**"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@telorun/analyzer": "0.
|
|
39
|
+
"@telorun/analyzer": "0.54.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^20.0.0",
|
package/src/completions/build.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseToAst, type AnalysisRegistry, type AstDocument, type AstMap } from "@telorun/analyzer";
|
|
2
2
|
import type { CompletionResult, IdeEnvironmentAdapter } from "../types.js";
|
|
3
3
|
import type { ReplaceRange } from "./detect-context.js";
|
|
4
|
-
import { detectContext,
|
|
4
|
+
import { detectContext, lookupRefConstraints } from "./detect-context.js";
|
|
5
5
|
import { importSourceCompletions } from "./import-source.js";
|
|
6
6
|
import { propKeyCompletions } from "./prop-keys.js";
|
|
7
7
|
import { CAPABILITY_VALUES } from "./valid-capabilities.js";
|
|
@@ -47,7 +47,7 @@ function extractInFileResources(docs: AstDocument[]): ResourceRecord[] {
|
|
|
47
47
|
function refNameCompletions(
|
|
48
48
|
docs: AstDocument[],
|
|
49
49
|
refKind: string | undefined,
|
|
50
|
-
|
|
50
|
+
refConstraints: string[],
|
|
51
51
|
registry: AnalysisRegistry | undefined,
|
|
52
52
|
replaceRange: ReplaceRange,
|
|
53
53
|
): CompletionResult[] {
|
|
@@ -56,9 +56,14 @@ function refNameCompletions(
|
|
|
56
56
|
|
|
57
57
|
if (refKind) {
|
|
58
58
|
acceptable = new Set([refKind]);
|
|
59
|
-
} else if (
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
} else if (refConstraints.length > 0 && registry) {
|
|
60
|
+
// Union across the slot's accepted kinds: a resource satisfying any one of
|
|
61
|
+
// them fills the slot. A constraint the registry can't resolve contributes
|
|
62
|
+
// nothing rather than narrowing to the ones it could.
|
|
63
|
+
const resolved = refConstraints.map((c) => registry.userFacingKindsForRef(c));
|
|
64
|
+
if (resolved.some(Boolean)) {
|
|
65
|
+
acceptable = new Set(resolved.flatMap((kinds) => kinds ?? []));
|
|
66
|
+
}
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
const seen = new Set<string>();
|
|
@@ -90,12 +95,14 @@ function refConstrainedKinds(
|
|
|
90
95
|
): string[] | undefined {
|
|
91
96
|
const definition = registry.resolveDefinition(parentDocKind);
|
|
92
97
|
if (!definition?.schema) return undefined;
|
|
93
|
-
const
|
|
98
|
+
const constraints = lookupRefConstraints(
|
|
94
99
|
definition.schema as Record<string, any>,
|
|
95
100
|
parentYamlPath,
|
|
96
101
|
);
|
|
97
|
-
if (
|
|
98
|
-
|
|
102
|
+
if (constraints.length === 0) return undefined;
|
|
103
|
+
const resolved = constraints.map((c) => registry.userFacingKindsForRef(c));
|
|
104
|
+
if (!resolved.some(Boolean)) return undefined;
|
|
105
|
+
return [...new Set(resolved.flatMap((kinds) => kinds ?? []))];
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
function kindCompletions(
|
|
@@ -154,10 +161,10 @@ export async function buildCompletions(
|
|
|
154
161
|
if (ctx.type === "capability") return capabilityCompletions();
|
|
155
162
|
if (ctx.type === "ref-name") {
|
|
156
163
|
const definition = registry?.resolveDefinition(ctx.docKind);
|
|
157
|
-
const
|
|
158
|
-
?
|
|
159
|
-
:
|
|
160
|
-
return refNameCompletions(astDocs, ctx.refKind,
|
|
164
|
+
const refConstraints = definition?.schema
|
|
165
|
+
? lookupRefConstraints(definition.schema as Record<string, any>, ctx.yamlPath)
|
|
166
|
+
: [];
|
|
167
|
+
return refNameCompletions(astDocs, ctx.refKind, refConstraints, registry, ctx.replaceRange);
|
|
161
168
|
}
|
|
162
169
|
if (ctx.type === "field-value") {
|
|
163
170
|
if (ctx.field === "import-source") {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseToAst, type AstDocument } from "@telorun/analyzer";
|
|
1
|
+
import { parseToAst, readRefSlot, refSlotAnnotation, type AstDocument } from "@telorun/analyzer";
|
|
2
2
|
import type { ReplaceRange } from "../types.js";
|
|
3
3
|
import { resolveNodeAtPosition } from "./resolve-node.js";
|
|
4
4
|
|
|
@@ -108,9 +108,12 @@ export function navigateSchema(
|
|
|
108
108
|
/** Merge multiple peeled schema branches into one node for completion purposes.
|
|
109
109
|
* Property maps are unioned (first branch wins on key collision). `required`
|
|
110
110
|
* becomes the intersection so optional-in-any-branch keys still surface.
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
111
|
+
*
|
|
112
|
+
* The parent's reference slot is re-stamped on the merged node, because peeling
|
|
113
|
+
* is exactly what destroys it: for the canonical multi-kind shape
|
|
114
|
+
* (`anyOf: [{x-telo-ref: A}, {x-telo-ref: B}]`) the constraint lives ONLY in the
|
|
115
|
+
* branches, and merging them left a node declaring no slot at all. Re-emitted
|
|
116
|
+
* through the analyzer's accessor so both annotation shapes survive the merge. */
|
|
114
117
|
function unionLeaves(
|
|
115
118
|
parent: Record<string, any>,
|
|
116
119
|
leaves: Record<string, any>[],
|
|
@@ -135,7 +138,8 @@ function unionLeaves(
|
|
|
135
138
|
);
|
|
136
139
|
}
|
|
137
140
|
const out: Record<string, any> = { type: "object", properties, required };
|
|
138
|
-
|
|
141
|
+
const slot = readRefSlot(parent);
|
|
142
|
+
if (slot && slot.kinds.length > 0) out["x-telo-ref"] = refSlotAnnotation(slot);
|
|
139
143
|
return out;
|
|
140
144
|
}
|
|
141
145
|
|
|
@@ -145,22 +149,21 @@ function unionLeaves(
|
|
|
145
149
|
* completion to discover what kind of resource the user is targeting in an
|
|
146
150
|
* object-form ref. Walking stops at the first line with a strictly smaller
|
|
147
151
|
* indent (that's the parent's structural boundary). */
|
|
148
|
-
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
|
|
152
|
+
/** Every kind the `x-telo-ref` slot at `yamlPath` accepts, or an empty array
|
|
153
|
+
* when the path doesn't resolve or declares no constraint.
|
|
154
|
+
*
|
|
155
|
+
* All of them, not the first: a slot accepting `Invocable | Runnable` used to
|
|
156
|
+
* complete only the invocables, because a single-constraint lookup stopped at
|
|
157
|
+
* the first branch. The analyzer's accessor unions a `kind:` list and the
|
|
158
|
+
* `anyOf` / `oneOf` branches alike, so completion now offers what the slot
|
|
159
|
+
* actually takes. */
|
|
160
|
+
export function lookupRefConstraints(
|
|
154
161
|
definitionSchema: Record<string, any>,
|
|
155
162
|
yamlPath: string[],
|
|
156
|
-
): string
|
|
163
|
+
): string[] {
|
|
157
164
|
const node = navigateSchema(definitionSchema, yamlPath);
|
|
158
|
-
if (!node) return
|
|
159
|
-
|
|
160
|
-
for (const branch of peelCombinators(node)) {
|
|
161
|
-
if (typeof branch["x-telo-ref"] === "string") return branch["x-telo-ref"];
|
|
162
|
-
}
|
|
163
|
-
return undefined;
|
|
165
|
+
if (!node) return [];
|
|
166
|
+
return readRefSlot(node)?.kinds ?? [];
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
/** Derive a `CompletionCtx` from the AST-resolved cursor (Approach B). The
|
package/src/hover/build-hover.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseToAst,
|
|
3
|
+
readRefSlot,
|
|
3
4
|
type AnalysisRegistry,
|
|
4
5
|
type AstDocument,
|
|
5
6
|
} from "@telorun/analyzer";
|
|
@@ -65,8 +66,13 @@ function fieldHover(keyName: string, field: Record<string, any>): string {
|
|
|
65
66
|
if (typeof field.description === "string" && field.description) {
|
|
66
67
|
lines.push("", field.description);
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
// Through the shared accessor, so a slot whose constraint sits in an `anyOf`
|
|
70
|
+
// branch — or in a `kind:` list — hovers like any other. Reading the raw
|
|
71
|
+
// annotation here meant those slots showed no reference line at all.
|
|
72
|
+
const refKinds = readRefSlot(field)?.kinds ?? [];
|
|
73
|
+
if (refKinds.length > 0) {
|
|
74
|
+
lines.push("", `Reference → ${refKinds.map((k) => `\`${k}\``).join(" | ")}`);
|
|
75
|
+
}
|
|
70
76
|
if (Array.isArray(field.enum) && field.enum.length > 0) {
|
|
71
77
|
lines.push("", `Allowed: ${field.enum.map((v: unknown) => `\`${v}\``).join(", ")}`);
|
|
72
78
|
}
|