@telorun/analyzer 0.65.0 → 0.67.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 +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +194 -65
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +67 -16
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +7 -1
- package/dist/cel-environment.d.ts +12 -0
- package/dist/cel-environment.d.ts.map +1 -1
- package/dist/cel-environment.js +35 -19
- package/dist/cel-scope.d.ts +8 -0
- package/dist/cel-scope.d.ts.map +1 -1
- package/dist/cel-scope.js +89 -11
- package/dist/definition-registry.d.ts +41 -0
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +76 -0
- package/dist/dependency-graph.d.ts.map +1 -1
- package/dist/dependency-graph.js +65 -0
- package/dist/eval-paths.d.ts +68 -0
- package/dist/eval-paths.d.ts.map +1 -1
- package/dist/eval-paths.js +138 -0
- package/dist/flatten-for-analyzer.d.ts +37 -1
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +104 -5
- package/dist/import-resolution-diagnostics.d.ts +1 -1
- package/dist/import-resolution-diagnostics.js +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/inline-imports.d.ts.map +1 -1
- package/dist/inline-imports.js +1 -0
- package/dist/manifest-visitor.d.ts +4 -0
- package/dist/manifest-visitor.d.ts.map +1 -1
- package/dist/manifest-visitor.js +36 -1
- package/dist/migrations/entries/index.d.ts.map +1 -1
- package/dist/migrations/entries/index.js +2 -0
- package/dist/migrations/entries/schema-prepare-bucket.json +27 -0
- package/dist/peer-binding.d.ts +232 -0
- package/dist/peer-binding.d.ts.map +1 -0
- package/dist/peer-binding.js +418 -0
- package/dist/precompile.d.ts.map +1 -1
- package/dist/precompile.js +8 -0
- package/dist/ref-slot.d.ts +13 -0
- package/dist/ref-slot.d.ts.map +1 -1
- package/dist/ref-slot.js +26 -0
- package/dist/reference-field-map.d.ts +28 -0
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +25 -0
- package/dist/referrer-rule.d.ts +17 -0
- package/dist/referrer-rule.d.ts.map +1 -1
- package/dist/referrer-rule.js +4 -1
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.js +1 -1
- package/dist/resolve-ref-sentinels.d.ts.map +1 -1
- package/dist/resolve-ref-sentinels.js +14 -1
- package/dist/resource-input.d.ts +75 -0
- package/dist/resource-input.d.ts.map +1 -0
- package/dist/resource-input.js +90 -0
- package/dist/resource-rule.d.ts +40 -27
- package/dist/resource-rule.d.ts.map +1 -1
- package/dist/resource-rule.js +61 -7
- package/dist/rule-condition.d.ts +11 -0
- package/dist/rule-condition.d.ts.map +1 -1
- package/dist/rule-condition.js +13 -0
- package/dist/schema-projection.d.ts +119 -4
- package/dist/schema-projection.d.ts.map +1 -1
- package/dist/schema-projection.js +213 -18
- package/dist/system-kinds.d.ts +7 -2
- package/dist/system-kinds.d.ts.map +1 -1
- package/dist/system-kinds.js +7 -2
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/template-body.d.ts +50 -0
- package/dist/template-body.d.ts.map +1 -0
- package/dist/template-body.js +58 -0
- package/dist/validate-cel-context.d.ts +1 -17
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +73 -78
- package/dist/validate-identifier-names.d.ts.map +1 -1
- package/dist/validate-identifier-names.js +17 -2
- package/dist/validate-reference-forms.d.ts +10 -0
- package/dist/validate-reference-forms.d.ts.map +1 -1
- package/dist/validate-reference-forms.js +23 -0
- package/dist/validate-references.d.ts +17 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +77 -18
- package/dist/validate-referrer-rules.d.ts +38 -4
- package/dist/validate-referrer-rules.d.ts.map +1 -1
- package/dist/validate-referrer-rules.js +139 -31
- package/dist/validate-resource-inputs.d.ts +35 -0
- package/dist/validate-resource-inputs.d.ts.map +1 -0
- package/dist/validate-resource-inputs.js +319 -0
- package/dist/validate-resource-rules.d.ts +11 -2
- package/dist/validate-resource-rules.d.ts.map +1 -1
- package/dist/validate-resource-rules.js +18 -12
- package/dist/validate-schema-projection.d.ts.map +1 -1
- package/dist/validate-schema-projection.js +33 -2
- package/dist/validate-template-dispatch.d.ts +27 -0
- package/dist/validate-template-dispatch.d.ts.map +1 -0
- package/dist/validate-template-dispatch.js +95 -0
- package/dist/with-synthetic-positions.d.ts +1 -1
- package/dist/with-synthetic-positions.js +1 -1
- package/package.json +3 -3
- package/src/analyzer.ts +236 -70
- package/src/builtins.ts +69 -16
- package/src/call-graph.ts +7 -1
- package/src/cel-environment.ts +48 -19
- package/src/cel-scope.ts +113 -19
- package/src/definition-registry.ts +78 -0
- package/src/dependency-graph.ts +66 -0
- package/src/eval-paths.ts +152 -0
- package/src/flatten-for-analyzer.ts +117 -4
- package/src/import-resolution-diagnostics.ts +1 -1
- package/src/index.ts +57 -4
- package/src/inline-imports.ts +1 -0
- package/src/manifest-visitor.ts +41 -1
- package/src/migrations/entries/index.ts +2 -0
- package/src/migrations/entries/schema-prepare-bucket.json +27 -0
- package/src/peer-binding.ts +641 -0
- package/src/precompile.ts +8 -0
- package/src/ref-slot.ts +36 -0
- package/src/reference-field-map.ts +42 -0
- package/src/referrer-rule.ts +20 -1
- package/src/release/index.ts +1 -1
- package/src/resolve-ref-sentinels.ts +12 -1
- package/src/resource-input.ts +132 -0
- package/src/resource-rule.ts +69 -7
- package/src/rule-condition.ts +15 -0
- package/src/schema-projection.ts +302 -20
- package/src/system-kinds.ts +7 -2
- package/src/telo-version.ts +1 -1
- package/src/template-body.ts +104 -0
- package/src/validate-cel-context.ts +73 -78
- package/src/validate-identifier-names.ts +18 -3
- package/src/validate-reference-forms.ts +22 -0
- package/src/validate-references.ts +85 -16
- package/src/validate-referrer-rules.ts +215 -28
- package/src/validate-resource-inputs.ts +367 -0
- package/src/validate-resource-rules.ts +26 -12
- package/src/validate-schema-projection.ts +50 -1
- package/src/validate-template-dispatch.ts +99 -0
- package/src/with-synthetic-positions.ts +1 -1
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { RULE_BUDGET_MS, compileRuleCondition, conditionRefusals } from "./rule-condition.js";
|
|
2
|
-
import { RESOURCE_RULES_ANNOTATION, celSourceOf, findDynamicLeaf, pointerSegments, readRawResourceRules, readResourceRules, readNodes, resolveRuleSubjects, } from "./resource-rule.js";
|
|
1
|
+
import { RULE_BUDGET_MS, UNTAGGED_CONDITION, compileRuleCondition, conditionRefusals, } from "./rule-condition.js";
|
|
2
|
+
import { RESOURCE_RULES_ANNOTATION, celSourceOf, findDynamicLeaf, isTaggedCondition, pointerSegments, readRawResourceRules, readResourceRules, readNodes, resolveRuleSubjects, } from "./resource-rule.js";
|
|
3
3
|
/** Published name for the shared budget — see `rule-condition.ts`. */
|
|
4
4
|
export const RESOURCE_RULE_BUDGET_MS = RULE_BUDGET_MS;
|
|
5
5
|
function isObject(value) {
|
|
6
6
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
7
7
|
}
|
|
8
8
|
/** Navigate a kind's own schema to the node describing what a pointer names, so
|
|
9
|
-
* an `in:` naming a field the kind does not declare is caught at the kind.
|
|
10
|
-
|
|
9
|
+
* an `in:` naming a field the kind does not declare is caught at the kind.
|
|
10
|
+
* Exported because the peer-rule strict half asks the same question of the
|
|
11
|
+
* REFERRER's schema, and two navigators would eventually disagree about what a
|
|
12
|
+
* pointer names. */
|
|
13
|
+
export function schemaAtPointer(schema, pointer) {
|
|
11
14
|
const segments = pointerSegments(pointer);
|
|
12
15
|
if (!segments)
|
|
13
16
|
return undefined;
|
|
@@ -27,7 +30,7 @@ function schemaAtPointer(schema, pointer) {
|
|
|
27
30
|
}
|
|
28
31
|
/** True when a schema node describes something a rule can iterate. Unknown or
|
|
29
32
|
* absent `type` passes: an open schema is not evidence of a defect. */
|
|
30
|
-
function isIterableSchema(node) {
|
|
33
|
+
export function isIterableSchema(node) {
|
|
31
34
|
if (!isObject(node))
|
|
32
35
|
return true;
|
|
33
36
|
const type = node.type;
|
|
@@ -121,6 +124,9 @@ effectiveSchema) {
|
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
}
|
|
127
|
+
if (condition !== undefined && condition.length > 0 && !isTaggedCondition(entry.condition)) {
|
|
128
|
+
issue(`${at}.condition`, UNTAGGED_CONDITION);
|
|
129
|
+
}
|
|
124
130
|
if (condition) {
|
|
125
131
|
for (const refusal of conditionRefusals(condition))
|
|
126
132
|
issue(`${at}.condition`, refusal);
|
|
@@ -159,14 +165,14 @@ export function evaluateResourceRules(manifest, definitionSchema) {
|
|
|
159
165
|
// Only the nodes this condition READS decide whether it can run — see
|
|
160
166
|
// `readNodes`. Scanning the whole subject would disable every
|
|
161
167
|
// resource-wide rule on any manifest containing one unrelated expression.
|
|
162
|
-
let
|
|
168
|
+
let dynamic;
|
|
163
169
|
for (const node of readNodes(chains, { self, this: subject.value })) {
|
|
164
|
-
|
|
165
|
-
if (
|
|
170
|
+
dynamic = findDynamicLeaf(node);
|
|
171
|
+
if (dynamic !== undefined)
|
|
166
172
|
break;
|
|
167
173
|
}
|
|
168
|
-
if (
|
|
169
|
-
findings.push({ kind: "skipped", rule, path: subject.path,
|
|
174
|
+
if (dynamic !== undefined) {
|
|
175
|
+
findings.push({ kind: "skipped", rule, path: subject.path, dynamic });
|
|
170
176
|
continue;
|
|
171
177
|
}
|
|
172
178
|
let held;
|
|
@@ -239,8 +245,8 @@ declarationIsOurs) {
|
|
|
239
245
|
code: "RESOURCE_RULE_SKIPPED",
|
|
240
246
|
severity: "information",
|
|
241
247
|
message: `${manifest.kind}/${name}: rule '${finding.rule.code}' did not run` +
|
|
242
|
-
`${at ? ` at '${at}'` : ""} — the value holds
|
|
243
|
-
`'${finding.
|
|
248
|
+
`${at ? ` at '${at}'` : ""} — the value holds ${finding.dynamic.what} at ` +
|
|
249
|
+
`'${finding.dynamic.path}', which is not known until the resource is created. ` +
|
|
244
250
|
"Reported rather than dropped: a check whose coverage varies invisibly reads as passing.",
|
|
245
251
|
manifest,
|
|
246
252
|
path: at,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-schema-projection.d.ts","sourceRoot":"","sources":["../src/validate-schema-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-schema-projection.d.ts","sourceRoot":"","sources":["../src/validate-schema-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AASrD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,2BAA2B,GAAG,uBAAuB,CAAC;IAC5D,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,qBAAqB,EAAE,CAqJ5F"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { projectionKeyMap, rawSchemaProjection, readSchemaProjection, schemaProjectionIsMisplaced, } from "./schema-projection.js";
|
|
1
|
+
import { projectionKeyMap, rawSchemaProjection, readSchemaProjection, schemaMapBranch, schemaProjectionIsMisplaced, } from "./schema-projection.js";
|
|
2
2
|
const PROJECTION = "x-telo-schema-projection";
|
|
3
3
|
function isObject(value) {
|
|
4
4
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -52,6 +52,30 @@ export function validateSchemaProjection(manifest) {
|
|
|
52
52
|
`entry's identity — without it the projection yields a schema that rejects every value.`),
|
|
53
53
|
];
|
|
54
54
|
}
|
|
55
|
+
const reference = raw.reference;
|
|
56
|
+
if (reference !== undefined) {
|
|
57
|
+
if (!isObject(reference)) {
|
|
58
|
+
issues.push(issue("SCHEMA_PROJECTION_INVALID", `${base}.reference`, `'reference' says how an entry whose '${projection.key}' holds a REFERENCE projects. ` +
|
|
59
|
+
`It is an object naming 'from' (the target field to read), 'keyword' (the schema ` +
|
|
60
|
+
`keyword its values become) and one of 'base' / 'baseFrom'.`));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const absent = ["from", "keyword"].filter((f) => typeof reference[f] !== "string");
|
|
64
|
+
if (absent.length > 0) {
|
|
65
|
+
issues.push(issue("SCHEMA_PROJECTION_INVALID", `${base}.reference`, `'reference' is missing ${absent.map((f) => `'${f}'`).join(" and ")}. Without them a ` +
|
|
66
|
+
`referenced entry projects to nothing, so it vanishes from every consumer's view.`));
|
|
67
|
+
}
|
|
68
|
+
const bases = ["base", "baseFrom"].filter((f) => reference[f] !== undefined);
|
|
69
|
+
if (bases.length !== 1) {
|
|
70
|
+
issues.push(issue("SCHEMA_PROJECTION_INVALID", `${base}.reference`, bases.length === 0
|
|
71
|
+
? `'reference' declares neither 'base' nor 'baseFrom', so the projected node has no ` +
|
|
72
|
+
`type. Write 'base' where the named type IS its own base type, or 'baseFrom' ` +
|
|
73
|
+
`naming the target field that holds a value of this kind's own map.`
|
|
74
|
+
: `'reference' declares both 'base' and 'baseFrom'. They are two answers to one ` +
|
|
75
|
+
`question — where the projected node's type comes from — so declare exactly one.`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
55
79
|
const map = projectionKeyMap(schema, projection);
|
|
56
80
|
if (!map) {
|
|
57
81
|
return [
|
|
@@ -106,5 +130,12 @@ function keySchema(schema, entries, key) {
|
|
|
106
130
|
if (!isObject(entry) || !isObject(entry.properties))
|
|
107
131
|
return undefined;
|
|
108
132
|
const field = entry.properties[key];
|
|
109
|
-
|
|
133
|
+
if (!isObject(field))
|
|
134
|
+
return undefined;
|
|
135
|
+
// The vocabulary may sit on a BRANCH — a slot unioning a closed value set with
|
|
136
|
+
// a reference — and the branch carrying the map is the one whose `enum` the map
|
|
137
|
+
// has to answer for. Reading the union node instead finds no `enum` at all, so
|
|
138
|
+
// the completeness check would silently stop running exactly where the two
|
|
139
|
+
// halves can disagree. Asked through the single reader, never re-derived here.
|
|
140
|
+
return schemaMapBranch(field) ?? field;
|
|
110
141
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import { type AnalysisDiagnostic } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* A `!ref` at a definition's dispatch slot must name a sibling `resources:`
|
|
5
|
+
* entry.
|
|
6
|
+
*
|
|
7
|
+
* `!ref` is the one spelling an author — and the editor's rename, completion and
|
|
8
|
+
* go-to-definition — expects to be RESOLVED. But a `Telo.Definition` is in both
|
|
9
|
+
* `REF_VALIDATION_SKIP_KINDS` and `REF_RESOLUTION_SKIP_KINDS`, and the dispatch
|
|
10
|
+
* slots carry no `x-telo-ref` (the accepted targets are the definition's own
|
|
11
|
+
* template-internal entries, not resources of any module), so no reference pass
|
|
12
|
+
* reaches them. Introducing the tag at a slot nothing resolves would be worse
|
|
13
|
+
* than the string form it replaces: a typo that used to be an obvious runtime
|
|
14
|
+
* miss becomes a typo in a construct that advertises static resolution.
|
|
15
|
+
*
|
|
16
|
+
* Decidable only when every sibling name is LITERAL. A template routinely names
|
|
17
|
+
* its entries with CEL (`name: !cel "self.name + '-query'"`), and an expression
|
|
18
|
+
* could expand to the referenced name — so one dynamic sibling switches the
|
|
19
|
+
* check off for that definition rather than inventing a miss.
|
|
20
|
+
*
|
|
21
|
+
* Entry-module-scoped, like every other declaration check: a published
|
|
22
|
+
* dependency's template body is not the consumer's to fix.
|
|
23
|
+
*
|
|
24
|
+
* Browser-safe.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateTemplateDispatch(manifests: ResourceManifest[], rootModules: ReadonlySet<string>): AnalysisDiagnostic[];
|
|
27
|
+
//# sourceMappingURL=validate-template-dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-template-dispatch.d.ts","sourceRoot":"","sources":["../src/validate-template-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AASzE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAC/B,kBAAkB,EAAE,CAgDtB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { isRefSentinel } from "@telorun/templating";
|
|
2
|
+
import { distance } from "./levenshtein.js";
|
|
3
|
+
import { DiagnosticSeverity } from "./types.js";
|
|
4
|
+
const SOURCE = "telo-analyzer";
|
|
5
|
+
/** The four slots a `Telo.Definition` names its dispatch target in. Each takes
|
|
6
|
+
* the same grammar; which one is legal for a given capability is
|
|
7
|
+
* `validate-provider-coherence`'s question, not this one's. */
|
|
8
|
+
const DISPATCH_FIELDS = ["invoke", "run", "provide", "mount"];
|
|
9
|
+
/**
|
|
10
|
+
* A `!ref` at a definition's dispatch slot must name a sibling `resources:`
|
|
11
|
+
* entry.
|
|
12
|
+
*
|
|
13
|
+
* `!ref` is the one spelling an author — and the editor's rename, completion and
|
|
14
|
+
* go-to-definition — expects to be RESOLVED. But a `Telo.Definition` is in both
|
|
15
|
+
* `REF_VALIDATION_SKIP_KINDS` and `REF_RESOLUTION_SKIP_KINDS`, and the dispatch
|
|
16
|
+
* slots carry no `x-telo-ref` (the accepted targets are the definition's own
|
|
17
|
+
* template-internal entries, not resources of any module), so no reference pass
|
|
18
|
+
* reaches them. Introducing the tag at a slot nothing resolves would be worse
|
|
19
|
+
* than the string form it replaces: a typo that used to be an obvious runtime
|
|
20
|
+
* miss becomes a typo in a construct that advertises static resolution.
|
|
21
|
+
*
|
|
22
|
+
* Decidable only when every sibling name is LITERAL. A template routinely names
|
|
23
|
+
* its entries with CEL (`name: !cel "self.name + '-query'"`), and an expression
|
|
24
|
+
* could expand to the referenced name — so one dynamic sibling switches the
|
|
25
|
+
* check off for that definition rather than inventing a miss.
|
|
26
|
+
*
|
|
27
|
+
* Entry-module-scoped, like every other declaration check: a published
|
|
28
|
+
* dependency's template body is not the consumer's to fix.
|
|
29
|
+
*
|
|
30
|
+
* Browser-safe.
|
|
31
|
+
*/
|
|
32
|
+
export function validateTemplateDispatch(manifests, rootModules) {
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const m of manifests) {
|
|
35
|
+
if (m.kind !== "Telo.Definition")
|
|
36
|
+
continue;
|
|
37
|
+
const meta = m.metadata;
|
|
38
|
+
const name = meta?.name;
|
|
39
|
+
if (!name)
|
|
40
|
+
continue;
|
|
41
|
+
if (meta?.module && !rootModules.has(meta.module))
|
|
42
|
+
continue;
|
|
43
|
+
const bodies = m.resources;
|
|
44
|
+
if (!Array.isArray(bodies))
|
|
45
|
+
continue;
|
|
46
|
+
const siblings = [];
|
|
47
|
+
let anyDynamic = false;
|
|
48
|
+
for (const entry of bodies) {
|
|
49
|
+
const entryName = entry?.metadata?.name;
|
|
50
|
+
if (typeof entryName === "string" && !entryName.includes("${{"))
|
|
51
|
+
siblings.push(entryName);
|
|
52
|
+
else if (entryName !== undefined)
|
|
53
|
+
anyDynamic = true;
|
|
54
|
+
}
|
|
55
|
+
if (anyDynamic)
|
|
56
|
+
continue;
|
|
57
|
+
for (const field of DISPATCH_FIELDS) {
|
|
58
|
+
const value = m[field];
|
|
59
|
+
if (!isRefSentinel(value))
|
|
60
|
+
continue;
|
|
61
|
+
const source = value.source;
|
|
62
|
+
const target = source.startsWith("Self.") ? source.slice("Self.".length) : source;
|
|
63
|
+
if (siblings.includes(target))
|
|
64
|
+
continue;
|
|
65
|
+
const suggestion = nearest(target, siblings);
|
|
66
|
+
out.push({
|
|
67
|
+
severity: DiagnosticSeverity.Error,
|
|
68
|
+
code: "TEMPLATE_DISPATCH_UNKNOWN",
|
|
69
|
+
source: SOURCE,
|
|
70
|
+
message: `${m.kind}/${name}: '${field}: !ref ${source}' names no entry in 'resources:'. ` +
|
|
71
|
+
`Available: ${siblings.join(", ") || "(none)"}.` +
|
|
72
|
+
(suggestion ? ` Did you mean '${suggestion}'?` : ""),
|
|
73
|
+
data: {
|
|
74
|
+
resource: { kind: m.kind, name },
|
|
75
|
+
filePath: meta?.source,
|
|
76
|
+
path: field,
|
|
77
|
+
...(suggestion ? { fix: { replacement: suggestion } } : {}),
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
/** The closest sibling name, when one is close enough to be a typo rather than a
|
|
85
|
+
* different name. */
|
|
86
|
+
function nearest(target, candidates) {
|
|
87
|
+
let best;
|
|
88
|
+
for (const candidate of candidates) {
|
|
89
|
+
const d = distance(target, candidate);
|
|
90
|
+
if (!best || d < best.d)
|
|
91
|
+
best = { name: candidate, d };
|
|
92
|
+
}
|
|
93
|
+
const limit = Math.max(1, Math.floor(target.length / 3));
|
|
94
|
+
return best && best.d <= limit ? best.name : undefined;
|
|
95
|
+
}
|
|
@@ -8,7 +8,7 @@ import type { ResourceManifest } from "@telorun/sdk";
|
|
|
8
8
|
* manifest (the dedup that backs `DUPLICATE_RESOURCE_NAME` reads
|
|
9
9
|
* `(source, sourceLine)` to distinguish pipeline echoes from real
|
|
10
10
|
* collisions). Production callers — the `Loader`, `flattenForAnalyzer`,
|
|
11
|
-
*
|
|
11
|
+
* telo studio's `emitDocsFor`, the VSCode extension — all stamp
|
|
12
12
|
* positions already. This helper is the escape hatch for **programmatic
|
|
13
13
|
* callers** (tests, ad-hoc scripts) that construct `ResourceManifest`
|
|
14
14
|
* literals without going through a loader: it gives every otherwise-naked
|
|
@@ -8,7 +8,7 @@ import { REF_VALIDATION_SKIP_KINDS } from "./system-kinds.js";
|
|
|
8
8
|
* manifest (the dedup that backs `DUPLICATE_RESOURCE_NAME` reads
|
|
9
9
|
* `(source, sourceLine)` to distinguish pipeline echoes from real
|
|
10
10
|
* collisions). Production callers — the `Loader`, `flattenForAnalyzer`,
|
|
11
|
-
*
|
|
11
|
+
* telo studio's `emitDocsFor`, the VSCode extension — all stamp
|
|
12
12
|
* positions already. This helper is the escape hatch for **programmatic
|
|
13
13
|
* callers** (tests, ad-hoc scripts) that construct `ResourceManifest`
|
|
14
14
|
* literals without going through a loader: it gives every otherwise-naked
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.0",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"jsonpath-plus": "^10.3.0",
|
|
44
44
|
"packageurl-js": "^2.0.1",
|
|
45
45
|
"yaml": "^2.8.3",
|
|
46
|
-
"@telorun/templating": "0.
|
|
46
|
+
"@telorun/templating": "0.18.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^20.0.0",
|
|
50
50
|
"typescript": "^5.0.0",
|
|
51
51
|
"vitest": "^2.1.8",
|
|
52
|
-
"@telorun/sdk": "0.
|
|
52
|
+
"@telorun/sdk": "0.83.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@telorun/sdk": "*"
|