@telorun/analyzer 1.2.0 → 1.4.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/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +12 -1
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +14 -0
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +16 -0
- package/dist/dependency-graph.d.ts.map +1 -1
- package/dist/dependency-graph.js +27 -13
- package/dist/position-metadata.d.ts +6 -1
- package/dist/position-metadata.d.ts.map +1 -1
- package/dist/position-metadata.js +10 -2
- package/dist/precompile.d.ts.map +1 -1
- package/dist/precompile.js +9 -1
- package/dist/reference-field-map.js +58 -6
- package/dist/resolve-ref-sentinels.d.ts +27 -0
- package/dist/resolve-ref-sentinels.d.ts.map +1 -0
- package/dist/resolve-ref-sentinels.js +114 -0
- package/dist/schema-compat.d.ts +7 -1
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +19 -2
- package/dist/system-kinds.d.ts +25 -0
- package/dist/system-kinds.d.ts.map +1 -0
- package/dist/system-kinds.js +34 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +81 -12
- package/package.json +2 -2
- package/src/analyzer.ts +13 -1
- package/src/builtins.ts +14 -0
- package/src/definition-registry.ts +15 -0
- package/src/dependency-graph.ts +27 -14
- package/src/position-metadata.ts +10 -2
- package/src/precompile.ts +8 -1
- package/src/reference-field-map.ts +83 -6
- package/src/resolve-ref-sentinels.ts +127 -0
- package/src/schema-compat.ts +19 -2
- package/src/system-kinds.ts +37 -0
- package/src/validate-references.ts +78 -11
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Resource-kind sets used by analysis passes to decide what counts as a
|
|
2
|
+
* user-defined instance vs. a system-level blueprint. Pulled into one
|
|
3
|
+
* place so the three passes (reference validation, dependency graph,
|
|
4
|
+
* ref-sentinel resolution) don't drift; each pass exports its own
|
|
5
|
+
* scoped view with a comment explaining what's in and what's out. */
|
|
6
|
+
|
|
7
|
+
/** Skipped by reference validation: type blueprints whose own ref slots
|
|
8
|
+
* belong to a different phase (definition schema validation rather than
|
|
9
|
+
* per-resource validation). Telo.Application and Telo.Library
|
|
10
|
+
* intentionally fall through — Application has `targets` (real refs) and
|
|
11
|
+
* Library is harmless (no ref-bearing fields). Telo.Import is also
|
|
12
|
+
* intentionally not skipped — its `source` is not an x-telo-ref slot, so
|
|
13
|
+
* walking it is cheap and consistent. */
|
|
14
|
+
export const REF_VALIDATION_SKIP_KINDS: ReadonlySet<string> = new Set([
|
|
15
|
+
"Telo.Definition",
|
|
16
|
+
"Telo.Abstract",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
/** Excluded from the dependency graph: kinds that are not runtime nodes.
|
|
20
|
+
* Telo.Abstract is intentionally not in this set today — abstracts have
|
|
21
|
+
* no resource manifests, so they never reach graph construction; if
|
|
22
|
+
* that ever changes, add it explicitly. */
|
|
23
|
+
export const DEPENDENCY_GRAPH_SKIP_KINDS: ReadonlySet<string> = new Set([
|
|
24
|
+
"Telo.Definition",
|
|
25
|
+
"Telo.Import",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/** Skipped by `!ref` sentinel resolution: kinds whose bodies are
|
|
29
|
+
* blueprints or import-time metadata, not resource instances with
|
|
30
|
+
* user-referenced ref slots. Mirrors `REF_VALIDATION_SKIP_KINDS` but
|
|
31
|
+
* also drops Telo.Import (its `source` isn't a ref slot, and walking
|
|
32
|
+
* the field map on it is pointless since there's no registered kind). */
|
|
33
|
+
export const REF_RESOLUTION_SKIP_KINDS: ReadonlySet<string> = new Set([
|
|
34
|
+
"Telo.Definition",
|
|
35
|
+
"Telo.Abstract",
|
|
36
|
+
"Telo.Import",
|
|
37
|
+
]);
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import { isRefSentinel } from "@telorun/templating";
|
|
2
3
|
import { isRefEntry, isScopeEntry, isSchemaFromEntry, isInlineResource, resolveFieldEntries, resolveFieldValues, type RefFieldEntry } from "./reference-field-map.js";
|
|
3
4
|
import { navigateJsonPointer } from "./schema-compat.js";
|
|
5
|
+
import { REF_VALIDATION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
4
6
|
import { DiagnosticSeverity, type AnalysisDiagnostic, type AnalysisContext } from "./types.js";
|
|
5
7
|
import type { AliasResolver } from "./alias-resolver.js";
|
|
6
8
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
7
9
|
|
|
8
10
|
const SOURCE = "telo-analyzer";
|
|
9
|
-
/** Kinds skipped by reference validation. Telo.Application and Telo.Library
|
|
10
|
-
* are intentionally not here: Application has `targets` with x-telo-ref that
|
|
11
|
-
* must be validated, and Library has no ref-bearing fields so flows through
|
|
12
|
-
* harmlessly. Telo.Import is also not here for the same reason — its
|
|
13
|
-
* `source` field isn't x-telo-ref, so nothing gets checked. */
|
|
14
|
-
const SYSTEM_KINDS = new Set(["Telo.Definition", "Telo.Abstract"]);
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
13
|
* Checks whether `kind` satisfies the ref constraint in `entry`.
|
|
@@ -76,13 +72,52 @@ export function validateReferences(
|
|
|
76
72
|
const aliasesByModule = context.aliasesByModule;
|
|
77
73
|
if (!aliases || !registry) return diagnostics;
|
|
78
74
|
|
|
79
|
-
// Build outer resource lookup by name for resolution check
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
75
|
+
// Build outer resource lookup by name for resolution check, collecting
|
|
76
|
+
// every entry per name so we can surface name collisions as diagnostics
|
|
77
|
+
// (the kernel's resource registry shares one namespace across all
|
|
78
|
+
// non-system kinds — e.g. `Telo.Application HelloApi` and `Http.Api
|
|
79
|
+
// HelloApi` collide at boot with `ERR_DUPLICATE_RESOURCE`. Catching it
|
|
80
|
+
// statically removes a class of "everything analyzes clean, then the
|
|
81
|
+
// kernel refuses to start" surprises.)
|
|
82
|
+
//
|
|
83
|
+
// Telo.Import is excluded from the duplicate check on top of the
|
|
84
|
+
// SYSTEM_KINDS skip: its `metadata.name` is an alias, not a resource
|
|
85
|
+
// identity (aliases live in a separate namespace from resources, and
|
|
86
|
+
// colliding aliases vs. resource names is benign — the alias is only
|
|
87
|
+
// ever read as a kind prefix).
|
|
88
|
+
const byNameAll = new Map<string, ResourceManifest[]>();
|
|
83
89
|
for (const r of resources) {
|
|
84
|
-
if (r.metadata?.name
|
|
90
|
+
if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || r.kind === "Telo.Import") continue;
|
|
91
|
+
const name = r.metadata.name as string;
|
|
92
|
+
const existing = byNameAll.get(name);
|
|
93
|
+
if (existing) existing.push(r);
|
|
94
|
+
else byNameAll.set(name, [r]);
|
|
95
|
+
}
|
|
96
|
+
for (const [name, list] of byNameAll) {
|
|
97
|
+
if (list.length <= 1) continue;
|
|
98
|
+
const [first, ...rest] = list;
|
|
99
|
+
const firstLabel = `${first.kind}/${name}`;
|
|
100
|
+
for (const dup of rest) {
|
|
101
|
+
diagnostics.push({
|
|
102
|
+
severity: DiagnosticSeverity.Error,
|
|
103
|
+
code: "DUPLICATE_RESOURCE_NAME",
|
|
104
|
+
source: SOURCE,
|
|
105
|
+
message: `${dup.kind}/${name}: resource name collides with ${firstLabel} declared earlier (kernel runtime would fail with ERR_DUPLICATE_RESOURCE)`,
|
|
106
|
+
data: {
|
|
107
|
+
resource: { kind: dup.kind, name },
|
|
108
|
+
filePath: (dup.metadata as { source?: string } | undefined)?.source,
|
|
109
|
+
path: "metadata.name",
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
85
113
|
}
|
|
114
|
+
// Single-resource map for the resolution / scope lookups below — when a
|
|
115
|
+
// collision exists, falling back to the first occurrence keeps the rest
|
|
116
|
+
// of the pass behaving the same as before the duplicate diagnostic was
|
|
117
|
+
// added (resolution still finds *something*; the duplicate diagnostic
|
|
118
|
+
// is what surfaces the underlying problem to the user).
|
|
119
|
+
const byName = new Map<string, ResourceManifest>();
|
|
120
|
+
for (const [name, list] of byNameAll) byName.set(name, list[0]);
|
|
86
121
|
|
|
87
122
|
for (const r of resources) {
|
|
88
123
|
if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind)) continue;
|
|
@@ -145,6 +180,38 @@ export function validateReferences(
|
|
|
145
180
|
for (const { value: val, path: concretePath } of resolveFieldEntries(r, fieldPath)) {
|
|
146
181
|
if (!val) continue;
|
|
147
182
|
|
|
183
|
+
// `!ref <name>` sentinel — bare resource name marked at parse time as a
|
|
184
|
+
// reference. Look it up against the slot's x-telo-ref constraint exactly
|
|
185
|
+
// like the legacy bare-string path; the only difference is the value's
|
|
186
|
+
// shape (a TaggedSentinel rather than a raw string), which removed the
|
|
187
|
+
// string/inline ambiguity at the source.
|
|
188
|
+
if (isRefSentinel(val)) {
|
|
189
|
+
const refName = val.source;
|
|
190
|
+
const target =
|
|
191
|
+
byName.get(refName) ?? visibleScopeManifests.find((m) => m.metadata?.name === refName);
|
|
192
|
+
if (!target) {
|
|
193
|
+
diagnostics.push({
|
|
194
|
+
severity: DiagnosticSeverity.Error,
|
|
195
|
+
code: "UNRESOLVED_REFERENCE",
|
|
196
|
+
source: SOURCE,
|
|
197
|
+
message: `${resourceLabel}: reference at '${concretePath}' → resource '${refName}' not found`,
|
|
198
|
+
data: { resource: resourceData, filePath, path: concretePath },
|
|
199
|
+
});
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const kindErrors = checkKind(target.kind as string, entry, registry, aliases);
|
|
203
|
+
if (kindErrors.length > 0) {
|
|
204
|
+
diagnostics.push({
|
|
205
|
+
severity: DiagnosticSeverity.Error,
|
|
206
|
+
code: "REFERENCE_KIND_MISMATCH",
|
|
207
|
+
source: SOURCE,
|
|
208
|
+
message: `${resourceLabel}: reference at '${concretePath}' → ${kindErrors.join("; ")}`,
|
|
209
|
+
data: { resource: resourceData, filePath, path: concretePath },
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
148
215
|
// Name-only reference (plain string) — look up by name to validate.
|
|
149
216
|
// Qualified references use "Kind.Name" format (e.g. "Http.Api.PaymentApi");
|
|
150
217
|
// extract the resource name from the last dot segment.
|