@telorun/ide-support 0.4.2 → 0.4.5
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/LICENSE +2 -2
- package/README.md +3 -3
- package/dist/completions/build.d.ts.map +1 -1
- package/dist/completions/build.js +141 -12
- package/dist/completions/detect-context.d.ts +71 -15
- package/dist/completions/detect-context.d.ts.map +1 -1
- package/dist/completions/detect-context.js +249 -43
- package/dist/completions/prop-keys.d.ts.map +1 -1
- package/dist/completions/prop-keys.js +33 -5
- package/dist/diagnostics/range-resolver.d.ts +9 -3
- package/dist/diagnostics/range-resolver.d.ts.map +1 -1
- package/dist/diagnostics/range-resolver.js +39 -6
- package/package.json +7 -4
- package/src/completions/build.ts +166 -12
- package/src/completions/detect-context.ts +283 -43
- package/src/completions/prop-keys.ts +43 -7
- package/src/diagnostics/range-resolver.ts +36 -5
|
@@ -2,6 +2,20 @@ import type { AnalysisRegistry } from "@telorun/analyzer";
|
|
|
2
2
|
import type { CompletionResult } from "../types.js";
|
|
3
3
|
import { navigateSchema } from "./detect-context.js";
|
|
4
4
|
|
|
5
|
+
/** Kernel-implicit fields. Every Telo resource declares its `kind` and a
|
|
6
|
+
* `metadata` object; the analyzer's schema validator injects them when
|
|
7
|
+
* the definition uses `additionalProperties: false`. Completion has the
|
|
8
|
+
* same need — domain-specific schemas (`Http.Api`, `Sql.Query`, …) don't
|
|
9
|
+
* enumerate these in their own `properties`, so without an explicit fallback
|
|
10
|
+
* the user can't autocomplete `kind:` or `metadata:` on those resources. */
|
|
11
|
+
const ROOT_IMPLICIT_PROPS: Record<string, Record<string, any>> = {
|
|
12
|
+
kind: { type: "string", description: "The fully-qualified resource kind." },
|
|
13
|
+
metadata: {
|
|
14
|
+
type: "object",
|
|
15
|
+
description: "Resource metadata (name, namespace, version).",
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
5
19
|
export function propKeyCompletions(
|
|
6
20
|
kind: string,
|
|
7
21
|
yamlPath: string[],
|
|
@@ -11,24 +25,46 @@ export function propKeyCompletions(
|
|
|
11
25
|
if (!registry) return [];
|
|
12
26
|
|
|
13
27
|
const definition = registry.resolveDefinition(kind);
|
|
14
|
-
if (!definition?.schema)
|
|
28
|
+
if (!definition?.schema) {
|
|
29
|
+
// Unknown kind (often: an unloaded import). At root level, still surface
|
|
30
|
+
// the universal `kind` / `metadata` keys so completion isn't dead when
|
|
31
|
+
// the registry hasn't resolved the resource type yet.
|
|
32
|
+
if (yamlPath.length === 0) {
|
|
33
|
+
return buildItems(ROOT_IMPLICIT_PROPS, existingKeys, new Set<string>());
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
15
37
|
|
|
16
38
|
const targetSchema = yamlPath.length === 0
|
|
17
39
|
? (definition.schema as Record<string, any>)
|
|
18
40
|
: navigateSchema(definition.schema as Record<string, any>, yamlPath);
|
|
19
41
|
|
|
20
|
-
if (!targetSchema?.properties)
|
|
42
|
+
if (!targetSchema?.properties) {
|
|
43
|
+
if (yamlPath.length === 0) {
|
|
44
|
+
return buildItems(ROOT_IMPLICIT_PROPS, existingKeys, new Set<string>());
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
21
48
|
|
|
22
49
|
const required = new Set<string>(
|
|
23
50
|
Array.isArray(targetSchema.required) ? targetSchema.required : [],
|
|
24
51
|
);
|
|
25
|
-
const
|
|
52
|
+
const properties =
|
|
53
|
+
yamlPath.length === 0
|
|
54
|
+
? { ...ROOT_IMPLICIT_PROPS, ...(targetSchema.properties as Record<string, any>) }
|
|
55
|
+
: (targetSchema.properties as Record<string, any>);
|
|
26
56
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
57
|
+
return buildItems(properties, existingKeys, required);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildItems(
|
|
61
|
+
properties: Record<string, any>,
|
|
62
|
+
existingKeys: Set<string>,
|
|
63
|
+
required: Set<string>,
|
|
64
|
+
): CompletionResult[] {
|
|
65
|
+
const items: CompletionResult[] = [];
|
|
66
|
+
for (const [prop, propSchema] of Object.entries(properties)) {
|
|
30
67
|
if (existingKeys.has(prop)) continue;
|
|
31
|
-
if (yamlPath.length === 0 && (prop === "kind" || prop === "metadata")) continue;
|
|
32
68
|
|
|
33
69
|
const item: CompletionResult = {
|
|
34
70
|
label: prop,
|
|
@@ -9,16 +9,28 @@ const ZERO_RANGE: Range = {
|
|
|
9
9
|
/** Falls back through the chain from the VS Code extension's inline resolver
|
|
10
10
|
* (ide/vscode/src/extension.ts:203-216 before this package existed):
|
|
11
11
|
* 1. `d.range` if present.
|
|
12
|
-
* 2. `positionIndex.get(d.data.path)`
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* 2. `positionIndex.get(d.data.path)` for a direct hit (covers diagnostics
|
|
13
|
+
* that target an existing value, e.g. wrong type, enum violation).
|
|
14
|
+
* 3. If the leaf is missing (e.g. "missing required property" — `.type`
|
|
15
|
+
* isn't in the YAML yet), walk one segment up at a time and squiggle
|
|
16
|
+
* just the parent's key identifier (`@key:<parent>`), not the parent's
|
|
17
|
+
* full value block. Keeps the squiggle scoped to the incomplete entry
|
|
18
|
+
* instead of spreading across every line of the surrounding map.
|
|
19
|
+
* 4. Whole-line span at `sourceLine` when known.
|
|
20
|
+
* 5. `(0,0)-(0,0)` as a last resort. Never undefined. */
|
|
15
21
|
export function resolveRange(d: AnalysisDiagnostic, ctx: DiagnosticContext): Range {
|
|
16
22
|
if (d.range) return d.range;
|
|
17
23
|
|
|
18
24
|
const fieldPath = (d.data as { path?: string } | undefined)?.path;
|
|
19
25
|
if (fieldPath !== undefined && ctx.positionIndex) {
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
26
|
+
const direct = ctx.positionIndex.get(fieldPath);
|
|
27
|
+
if (direct) return direct;
|
|
28
|
+
for (const parent of parentPaths(fieldPath).slice(1)) {
|
|
29
|
+
const keyRange = ctx.positionIndex.get(`@key:${parent}`);
|
|
30
|
+
if (keyRange) return keyRange;
|
|
31
|
+
const valueRange = ctx.positionIndex.get(parent);
|
|
32
|
+
if (valueRange) return valueRange;
|
|
33
|
+
}
|
|
22
34
|
}
|
|
23
35
|
|
|
24
36
|
if (ctx.sourceLine !== undefined) {
|
|
@@ -30,3 +42,22 @@ export function resolveRange(d: AnalysisDiagnostic, ctx: DiagnosticContext): Ran
|
|
|
30
42
|
|
|
31
43
|
return ZERO_RANGE;
|
|
32
44
|
}
|
|
45
|
+
|
|
46
|
+
/** Yield `path`, then progressively shorter parents formed by stripping
|
|
47
|
+
* trailing dotted segments and array index suffixes. For
|
|
48
|
+
* `"secrets.openaiApiKey.type"` → `["secrets.openaiApiKey.type",
|
|
49
|
+
* "secrets.openaiApiKey", "secrets"]`. For `"routes[0].handler"` →
|
|
50
|
+
* `["routes[0].handler", "routes[0]", "routes"]`. */
|
|
51
|
+
function parentPaths(path: string): string[] {
|
|
52
|
+
const out: string[] = [];
|
|
53
|
+
let cur = path;
|
|
54
|
+
while (cur.length > 0) {
|
|
55
|
+
out.push(cur);
|
|
56
|
+
const lastDot = cur.lastIndexOf(".");
|
|
57
|
+
const lastBracket = cur.lastIndexOf("[");
|
|
58
|
+
const cut = Math.max(lastDot, lastBracket);
|
|
59
|
+
if (cut <= 0) break;
|
|
60
|
+
cur = cur.slice(0, cut);
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|