@telorun/analyzer 0.70.0 → 0.71.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/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +4 -5
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +50 -8
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +10 -4
- package/dist/cel-scope-query.d.ts +14 -0
- package/dist/cel-scope-query.d.ts.map +1 -1
- package/dist/cel-scope-query.js +36 -6
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +3 -4
- package/dist/flatten-for-analyzer.d.ts +2 -2
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/manifest-visitor.d.ts.map +1 -1
- package/dist/manifest-visitor.js +11 -3
- package/dist/module-alias-scope.d.ts +65 -0
- package/dist/module-alias-scope.d.ts.map +1 -0
- package/dist/module-alias-scope.js +25 -0
- package/dist/{zone-module-documents.d.ts → module-documents.d.ts} +11 -7
- package/dist/module-documents.d.ts.map +1 -0
- package/dist/ref-sentinel-target.d.ts +38 -0
- package/dist/ref-sentinel-target.d.ts.map +1 -0
- package/dist/ref-sentinel-target.js +13 -0
- package/dist/resolve-schema-type-refs.d.ts.map +1 -1
- package/dist/resolve-schema-type-refs.js +2 -1
- package/dist/resolve-throws-union.d.ts +18 -1
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +93 -9
- package/dist/resolve-zone-requirements.d.ts +3 -3
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +12 -12
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/template-body.d.ts.map +1 -1
- package/dist/template-body.js +2 -4
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-invocation-contract.d.ts +5 -0
- package/dist/validate-invocation-contract.d.ts.map +1 -1
- package/dist/validate-invocation-contract.js +114 -3
- package/dist/validate-logging.d.ts.map +1 -1
- package/dist/validate-logging.js +2 -2
- package/dist/validate-references.d.ts +16 -5
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +30 -12
- package/dist/validate-resource-inputs.d.ts +1 -26
- package/dist/validate-resource-inputs.d.ts.map +1 -1
- package/dist/validate-resource-inputs.js +12 -2
- package/dist/validate-schema-type-refs.d.ts.map +1 -1
- package/dist/validate-schema-type-refs.js +2 -1
- package/dist/validate-throws-coverage.d.ts +5 -1
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +6 -2
- package/package.json +2 -2
- package/src/analysis-registry.ts +4 -5
- package/src/analyzer.ts +61 -10
- package/src/call-graph.ts +9 -3
- package/src/cel-scope-query.ts +45 -8
- package/src/definition-registry.ts +3 -6
- package/src/flatten-for-analyzer.ts +3 -3
- package/src/index.ts +2 -2
- package/src/manifest-visitor.ts +11 -3
- package/src/module-alias-scope.ts +94 -0
- package/src/{zone-module-documents.ts → module-documents.ts} +10 -6
- package/src/ref-sentinel-target.ts +46 -0
- package/src/resolve-schema-type-refs.ts +2 -1
- package/src/resolve-throws-union.ts +123 -8
- package/src/resolve-zone-requirements.ts +14 -14
- package/src/telo-version.ts +1 -1
- package/src/template-body.ts +2 -5
- package/src/types.ts +3 -3
- package/src/validate-invocation-contract.ts +128 -2
- package/src/validate-logging.ts +2 -3
- package/src/validate-references.ts +41 -11
- package/src/validate-resource-inputs.ts +26 -3
- package/src/validate-schema-type-refs.ts +2 -1
- package/src/validate-throws-coverage.ts +12 -1
- package/dist/zone-module-documents.d.ts.map +0 -1
- /package/dist/{zone-module-documents.js → module-documents.js} +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { effectiveContractField, mappingFieldFor, needsContractMapping, } from "./extends-resolution.js";
|
|
1
|
+
import { ancestorChain, effectiveContractField, mappingFieldFor, needsContractMapping, } from "./extends-resolution.js";
|
|
2
|
+
import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
|
|
3
|
+
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
2
4
|
import { buildReferenceFieldMap, isRefEntry } from "./reference-field-map.js";
|
|
5
|
+
import { checkSchemaCompatibility } from "./schema-compat.js";
|
|
3
6
|
import { DiagnosticSeverity } from "./types.js";
|
|
4
7
|
const SOURCE = "telo-analyzer";
|
|
5
8
|
/**
|
|
@@ -18,6 +21,11 @@ const SOURCE = "telo-analyzer";
|
|
|
18
21
|
* whose input contract is now `inputType:`.
|
|
19
22
|
* - CONTRACT_TYPE_NOT_FOUND: a contract names a type that is not declared in
|
|
20
23
|
* scope, so every call through it would fail at dispatch.
|
|
24
|
+
* - CONTRACT_NOT_SUBSTITUTABLE: a definition replaces an ancestor's contract
|
|
25
|
+
* with a shape that cannot stand in for it. The one check nothing else makes:
|
|
26
|
+
* contracts resolve to the nearest declaration in BOTH halves, so a child that
|
|
27
|
+
* declares its own is compared against its abstract by neither the analyzer
|
|
28
|
+
* nor dispatch.
|
|
21
29
|
*
|
|
22
30
|
* Deliberately NOT diagnosed: an input that is neither `required:` nor
|
|
23
31
|
* defaulted. It is indistinguishable from a genuinely optional one — `Ai.Text`
|
|
@@ -29,8 +37,7 @@ const SOURCE = "telo-analyzer";
|
|
|
29
37
|
export function validateInvocationContract(manifests, registry, aliases, aliasesByModule = new Map()) {
|
|
30
38
|
const diagnostics = [];
|
|
31
39
|
const resolveDef = (kind, from) => {
|
|
32
|
-
const
|
|
33
|
-
const scope = (module ? aliasesByModule.get(module) : undefined) ?? aliases;
|
|
40
|
+
const scope = moduleAliasScope(from?.metadata, aliases, aliasesByModule);
|
|
34
41
|
return registry.resolve(kind) ?? registry.resolve(scope.resolveKind(kind) ?? kind);
|
|
35
42
|
};
|
|
36
43
|
// A published dependency's declarations are not the consumer's to fix.
|
|
@@ -58,6 +65,7 @@ export function validateInvocationContract(manifests, registry, aliases, aliases
|
|
|
58
65
|
if (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract") {
|
|
59
66
|
checkMappingRequired(m, resource, filePath, resolveDef, diagnostics);
|
|
60
67
|
checkContractResolves(m, md, manifests, resource, filePath, diagnostics);
|
|
68
|
+
checkAncestorSubstitutability(m, md, manifests, resource, filePath, resolveDef, diagnostics);
|
|
61
69
|
continue;
|
|
62
70
|
}
|
|
63
71
|
// A RESOURCE (an instance of some kind) — a leftover `inputs:` map is only
|
|
@@ -318,6 +326,109 @@ function namedTypeReference(value) {
|
|
|
318
326
|
return undefined;
|
|
319
327
|
return typeof ref.name === "string" ? ref.name : undefined;
|
|
320
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* A REPLACING CONTRACT MUST STILL STAND IN FOR THE ONE IT REPLACES.
|
|
331
|
+
*
|
|
332
|
+
* Contracts resolve to the NEAREST declaration and never merge, which is right —
|
|
333
|
+
* a call signature is not additive, and a backend narrowing `status` to the
|
|
334
|
+
* states it actually has could not express that through a merge. But the
|
|
335
|
+
* consequence is that an abstract's contract binds only the children that
|
|
336
|
+
* declare none of their own: a child that declares one is checked against
|
|
337
|
+
* nothing, statically or at dispatch, since `resolveBoundContract` stops at the
|
|
338
|
+
* same nearest declaration. So an abstract could state a floor every
|
|
339
|
+
* implementation must answer with (`Durable.Run`'s `runId` / `status`, the
|
|
340
|
+
* identity every later question about a run is asked by) and have that floor hold
|
|
341
|
+
* for exactly the implementations that declared nothing — while the real ones,
|
|
342
|
+
* which all declare their own, escaped it. A slot typed by the abstract would
|
|
343
|
+
* then be typed by a promise nothing kept.
|
|
344
|
+
*
|
|
345
|
+
* The rule is substitutability, and its direction differs per contract because
|
|
346
|
+
* one is produced and the other consumed:
|
|
347
|
+
*
|
|
348
|
+
* - `outputType` is COVARIANT — a consumer written against the ancestor reads
|
|
349
|
+
* the ancestor's shape, so the child's output must be acceptable where the
|
|
350
|
+
* ancestor's is declared. Dropping a required property is what breaks it.
|
|
351
|
+
* - `inputType` is CONTRAVARIANT — a caller written against the ancestor sends
|
|
352
|
+
* the ancestor's shape, so the ancestor's input must be acceptable where the
|
|
353
|
+
* child's is declared. Requiring an input the ancestor never mentions is what
|
|
354
|
+
* breaks it: no such caller could satisfy it.
|
|
355
|
+
*
|
|
356
|
+
* **Only an ABSTRACT ancestor's contract is a contract**, and that narrowing is a
|
|
357
|
+
* decision rather than an oversight. Extending a CONCRETE kind is how a friendlier
|
|
358
|
+
* schema is put over an existing controller — `base:` reshapes its config and
|
|
359
|
+
* `inputs:` / `result:` translate its call signature — so a child there is
|
|
360
|
+
* SUPPOSED to present different inputs, and demanding substitutability would
|
|
361
|
+
* reject the pattern the standard library and every custom-kind example are built
|
|
362
|
+
* on. An abstract has no implementation to reshape: extending one is a claim to
|
|
363
|
+
* BE the thing, its contract is written for implementors, and a slot typed by it
|
|
364
|
+
* is polymorphic by construction — which is exactly where an unkept promise has
|
|
365
|
+
* nowhere to be caught.
|
|
366
|
+
*
|
|
367
|
+
* A direction the child BRIDGES (`inputs:` for inputs, `result:` for outputs) is
|
|
368
|
+
* skipped for the same reason one hop down: the mapping is the author saying the
|
|
369
|
+
* shapes differ deliberately and are translated.
|
|
370
|
+
*
|
|
371
|
+
* The comparison is {@link checkSchemaCompatibility}, which reports only DEFINITE
|
|
372
|
+
* mismatches — a union, an absent `type`, an undeclared argument all read as
|
|
373
|
+
* compatible — so narrowing a value's type, adding an optional property or
|
|
374
|
+
* restricting an enum stays legal, which is the whole point of letting a child
|
|
375
|
+
* replace the contract at all.
|
|
376
|
+
*/
|
|
377
|
+
function checkAncestorSubstitutability(m, md, manifests, resource, filePath, resolveDef, diagnostics) {
|
|
378
|
+
const def = m;
|
|
379
|
+
for (const direction of ["inputType", "outputType"]) {
|
|
380
|
+
const own = md[direction];
|
|
381
|
+
if (own === undefined || own === null)
|
|
382
|
+
continue;
|
|
383
|
+
// A declared bridge says the shapes differ on purpose and are translated.
|
|
384
|
+
if (md[mappingFieldFor(direction)] != null)
|
|
385
|
+
continue;
|
|
386
|
+
// The nearest ANCESTOR that declares it — the contract this one replaces.
|
|
387
|
+
// Walking past self is the whole question: `contractDeclarer` would answer
|
|
388
|
+
// "this one", which is what nothing checks.
|
|
389
|
+
const ancestor = ancestorChain(def, resolveDef).find((a) => {
|
|
390
|
+
const inherited = a[direction];
|
|
391
|
+
return inherited !== undefined && inherited !== null;
|
|
392
|
+
});
|
|
393
|
+
if (!ancestor || ancestor.kind !== "Telo.Abstract")
|
|
394
|
+
continue;
|
|
395
|
+
const inherited = ancestor[direction];
|
|
396
|
+
// The WHOLE manifest set, which is what `analyzerContractScope`'s
|
|
397
|
+
// `typeManifestsFor` hands `resolveContract` — so this resolves a named type
|
|
398
|
+
// exactly as the resolver the kernel binds with does. Filtering to the
|
|
399
|
+
// declaring module looked more careful and was strictly worse: a contract
|
|
400
|
+
// naming a shape from a shared module resolved to nothing, and an
|
|
401
|
+
// unresolvable side is skipped, so the check silently switched itself off for
|
|
402
|
+
// precisely the libraries that factor their shapes out. Nothing else caught
|
|
403
|
+
// it either — `CONTRACT_TYPE_NOT_FOUND` finds the type through its own global
|
|
404
|
+
// fallback, so such a kind passed `telo check` with no diagnostic at all.
|
|
405
|
+
const ownSchema = resolveTypeFieldToSchema(own, manifests);
|
|
406
|
+
const ancestorSchema = resolveTypeFieldToSchema(inherited, manifests);
|
|
407
|
+
// An unresolvable side says nothing about compatibility; CONTRACT_TYPE_NOT_FOUND
|
|
408
|
+
// is what reports a contract that names a type nothing declares.
|
|
409
|
+
if (!ownSchema || !ancestorSchema)
|
|
410
|
+
continue;
|
|
411
|
+
const { compatible, issues } = direction === "outputType"
|
|
412
|
+
? checkSchemaCompatibility(ownSchema, ancestorSchema)
|
|
413
|
+
: checkSchemaCompatibility(ancestorSchema, ownSchema);
|
|
414
|
+
if (compatible)
|
|
415
|
+
continue;
|
|
416
|
+
const ancestorName = `${ancestor.metadata?.module ?? ""}.${ancestor.metadata?.name ?? "?"}`.replace(/^\./, "");
|
|
417
|
+
const why = direction === "outputType"
|
|
418
|
+
? `every caller that holds this through '${ancestorName}' reads the shape that kind declares`
|
|
419
|
+
: `a caller that holds this through '${ancestorName}' sends the shape that kind declares`;
|
|
420
|
+
diagnostics.push({
|
|
421
|
+
severity: DiagnosticSeverity.Error,
|
|
422
|
+
code: "CONTRACT_NOT_SUBSTITUTABLE",
|
|
423
|
+
source: SOURCE,
|
|
424
|
+
message: `${m.kind}/${resource.name}: \`${direction}\` replaces the one declared by '${ancestorName}' ` +
|
|
425
|
+
`with a shape that cannot stand in for it — ${issues.join("; ")}. Contracts replace rather ` +
|
|
426
|
+
`than merge, so nothing re-checks this at dispatch: ${why}. Restate the fields ` +
|
|
427
|
+
`'${ancestorName}' declares, or drop \`${direction}\` to inherit the contract unchanged.`,
|
|
428
|
+
data: { resource, filePath, path: direction },
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
321
432
|
/** A child that inherits its controller and REPLACES a contract must bridge it:
|
|
322
433
|
* contracts resolve to the nearest declaration and never merge, so the
|
|
323
434
|
* inherited controller only understands its own shape. Without the mapping the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-logging.d.ts","sourceRoot":"","sources":["../src/validate-logging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-logging.d.ts","sourceRoot":"","sources":["../src/validate-logging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAKzE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,kBAAkB,EAAE,CAqBtB"}
|
package/dist/validate-logging.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseRedactionPath, RedactionPathError } from "./redaction-path.js";
|
|
2
2
|
import { DiagnosticSeverity } from "./types.js";
|
|
3
|
+
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
3
4
|
const SOURCE = "telo-analyzer";
|
|
4
5
|
/**
|
|
5
6
|
* Static validation of the `logging:` block — `kernel/specs/logging.md` §14.1
|
|
@@ -106,8 +107,7 @@ function isSinkKind(manifest, registry, aliases, aliasesByModule) {
|
|
|
106
107
|
return false;
|
|
107
108
|
if (manifest.kind === "Telo.ConsoleSink" || manifest.kind === "Telo.FileSink")
|
|
108
109
|
return true;
|
|
109
|
-
const
|
|
110
|
-
const resolver = (ownModule ? aliasesByModule?.get(ownModule) : undefined) ?? aliases;
|
|
110
|
+
const resolver = moduleAliasScope(manifest.metadata, aliases, aliasesByModule);
|
|
111
111
|
const canonical = resolver.resolveKind(manifest.kind) ?? manifest.kind;
|
|
112
112
|
return registry.resolve(canonical)?.capability === "Telo.Sink";
|
|
113
113
|
}
|
|
@@ -11,12 +11,23 @@ import type { DefinitionRegistry } from "./definition-registry.js";
|
|
|
11
11
|
*
|
|
12
12
|
* A value satisfies the slot when it transitively extends the target kind, or —
|
|
13
13
|
* for a CONCRETE target — IS that kind; `getByExtends` is the same transitive
|
|
14
|
-
* subtype index for both
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* subtype index for both. Accepts a constraint that resolves to nothing, and an
|
|
15
|
+
* abstract with no loaded implementations: partial context, where a rejection
|
|
16
|
+
* would be a guess.
|
|
17
|
+
*
|
|
18
|
+
* An abstract is satisfied only by an implementer, never by the abstract itself,
|
|
19
|
+
* because a resource declared `kind: <some abstract>` is refused at `create()`
|
|
20
|
+
* and reported as `ABSTRACT_KIND_INSTANTIATED` at its declaration — so accepting
|
|
21
|
+
* it here would leave the reference sites silent about a manifest that cannot
|
|
22
|
+
* run. `isDeclaration` is the ONE exception and it is not an instance: a
|
|
23
|
+
* library's `resources:` entry is constrained by kind alone, so its kind-only
|
|
24
|
+
* stand-in routinely IS an abstract (`connection: {kind: Sql.Connection}`), and
|
|
25
|
+
* every use of that name inside the library is a reference to it.
|
|
18
26
|
*/
|
|
19
|
-
export declare function kindSatisfies(resolved: string, targetKind: string, registry: DefinitionRegistry
|
|
27
|
+
export declare function kindSatisfies(resolved: string, targetKind: string, registry: DefinitionRegistry,
|
|
28
|
+
/** The referent is a kind-only stand-in for a `resources:` entry rather than a
|
|
29
|
+
* resource anything instantiates, so identity satisfies an abstract slot. */
|
|
30
|
+
isDeclaration?: boolean): boolean;
|
|
20
31
|
/**
|
|
21
32
|
* Phase 3 — Reference validation.
|
|
22
33
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAarD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAarD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAMnE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,kBAAkB;AAC5B;8EAC8E;AAC9E,aAAa,UAAQ,GACpB,OAAO,CAwBT;AAgED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA0hBtB"}
|
|
@@ -5,6 +5,8 @@ import { navigateJsonPointer, substituteCelFields } from "./schema-compat.js";
|
|
|
5
5
|
import { REF_VALIDATION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
6
6
|
import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
|
|
7
7
|
import { DiagnosticSeverity } from "./types.js";
|
|
8
|
+
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
9
|
+
import { isInjectedDeclaration } from "./resource-input.js";
|
|
8
10
|
const SOURCE = "telo-analyzer";
|
|
9
11
|
/**
|
|
10
12
|
* Liskov substitutability at a kind constraint: is `resolved` (a canonical
|
|
@@ -16,17 +18,28 @@ const SOURCE = "telo-analyzer";
|
|
|
16
18
|
*
|
|
17
19
|
* A value satisfies the slot when it transitively extends the target kind, or —
|
|
18
20
|
* for a CONCRETE target — IS that kind; `getByExtends` is the same transitive
|
|
19
|
-
* subtype index for both
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* subtype index for both. Accepts a constraint that resolves to nothing, and an
|
|
22
|
+
* abstract with no loaded implementations: partial context, where a rejection
|
|
23
|
+
* would be a guess.
|
|
24
|
+
*
|
|
25
|
+
* An abstract is satisfied only by an implementer, never by the abstract itself,
|
|
26
|
+
* because a resource declared `kind: <some abstract>` is refused at `create()`
|
|
27
|
+
* and reported as `ABSTRACT_KIND_INSTANTIATED` at its declaration — so accepting
|
|
28
|
+
* it here would leave the reference sites silent about a manifest that cannot
|
|
29
|
+
* run. `isDeclaration` is the ONE exception and it is not an instance: a
|
|
30
|
+
* library's `resources:` entry is constrained by kind alone, so its kind-only
|
|
31
|
+
* stand-in routinely IS an abstract (`connection: {kind: Sql.Connection}`), and
|
|
32
|
+
* every use of that name inside the library is a reference to it.
|
|
23
33
|
*/
|
|
24
|
-
export function kindSatisfies(resolved, targetKind, registry
|
|
34
|
+
export function kindSatisfies(resolved, targetKind, registry,
|
|
35
|
+
/** The referent is a kind-only stand-in for a `resources:` entry rather than a
|
|
36
|
+
* resource anything instantiates, so identity satisfies an abstract slot. */
|
|
37
|
+
isDeclaration = false) {
|
|
25
38
|
const canonical = registry.resolveRef(targetKind) ?? targetKind;
|
|
26
39
|
const targetDef = registry.resolve(canonical);
|
|
27
40
|
if (!targetDef)
|
|
28
41
|
return true;
|
|
29
|
-
if (targetDef.kind !== "Telo.Abstract" && resolved === canonical)
|
|
42
|
+
if ((targetDef.kind !== "Telo.Abstract" || isDeclaration) && resolved === canonical)
|
|
30
43
|
return true;
|
|
31
44
|
const subtypes = registry.getByExtends(canonical);
|
|
32
45
|
if (subtypes.some((d) => `${d.metadata.module}.${d.metadata.name}` === resolved))
|
|
@@ -53,7 +66,9 @@ export function kindSatisfies(resolved, targetKind, registry) {
|
|
|
53
66
|
/** {@link kindSatisfies} at every kind a slot accepts, rendered as messages.
|
|
54
67
|
* The acceptance rule is not restated here — only what to say when it fails,
|
|
55
68
|
* which needs the slot's alternatives and the target's flavour. */
|
|
56
|
-
function checkKind(kind, entry, registry, aliases
|
|
69
|
+
function checkKind(kind, entry, registry, aliases,
|
|
70
|
+
/** See {@link kindSatisfies}: the referent is a `resources:` stand-in. */
|
|
71
|
+
isDeclaration = false) {
|
|
57
72
|
const resolved = aliases.resolveKind(kind) ?? kind;
|
|
58
73
|
// A qualified kind whose prefix names no import in this scope is a bad NAME
|
|
59
74
|
// rather than a bad type — `{kind: NotAnAlias.Script}` where only `Lib` is
|
|
@@ -76,7 +91,7 @@ function checkKind(kind, entry, registry, aliases) {
|
|
|
76
91
|
const targetDef = registry.resolve(targetKind);
|
|
77
92
|
if (!targetDef)
|
|
78
93
|
return [];
|
|
79
|
-
if (!unknownAlias && kindSatisfies(resolved, targetKind, registry))
|
|
94
|
+
if (!unknownAlias && kindSatisfies(resolved, targetKind, registry, isDeclaration))
|
|
80
95
|
return [];
|
|
81
96
|
const subtypes = registry.getByExtends(targetKind);
|
|
82
97
|
const subtypeKinds = new Set(subtypes.map((d) => `${d.metadata.module}.${d.metadata.name}`));
|
|
@@ -311,7 +326,7 @@ export function validateReferences(resources, context) {
|
|
|
311
326
|
});
|
|
312
327
|
return;
|
|
313
328
|
}
|
|
314
|
-
const kindErrors = checkKind(target.kind, entry, registry, aliases);
|
|
329
|
+
const kindErrors = checkKind(target.kind, entry, registry, aliases, isInjectedDeclaration(target));
|
|
315
330
|
if (kindErrors.length > 0) {
|
|
316
331
|
diagnostics.push({
|
|
317
332
|
severity: DiagnosticSeverity.Error,
|
|
@@ -359,7 +374,11 @@ export function validateReferences(resources, context) {
|
|
|
359
374
|
return;
|
|
360
375
|
}
|
|
361
376
|
// 2. Kind check
|
|
362
|
-
const
|
|
377
|
+
const objectTarget = typeof refVal.name === "string"
|
|
378
|
+
? (visibleScopeManifests.find((m) => m.metadata?.name === refVal.name) ??
|
|
379
|
+
byName.get(refVal.name))
|
|
380
|
+
: undefined;
|
|
381
|
+
const kindErrors = checkKind(refVal.kind, entry, registry, aliases, isInjectedDeclaration(objectTarget));
|
|
363
382
|
if (kindErrors.length > 0) {
|
|
364
383
|
diagnostics.push({
|
|
365
384
|
severity: DiagnosticSeverity.Error,
|
|
@@ -448,8 +467,7 @@ export function validateReferences(resources, context) {
|
|
|
448
467
|
if (!isAbsolute && anchorName.includes(".")) {
|
|
449
468
|
const resolvedResourceKind = aliases.resolveKind(r.kind) ?? r.kind;
|
|
450
469
|
const resourceDef = registry.resolve(r.kind) ?? registry.resolve(resolvedResourceKind);
|
|
451
|
-
const
|
|
452
|
-
const ownerScope = (owningModule ? aliasesByModule?.get(owningModule) : undefined) ?? aliases;
|
|
470
|
+
const ownerScope = moduleAliasScope(resourceDef?.metadata, aliases, aliasesByModule);
|
|
453
471
|
const targetKind = ownerScope.resolveKind(anchorName);
|
|
454
472
|
if (!targetKind) {
|
|
455
473
|
// Names the ALIAS, not the whole kind path: the alias is what an
|
|
@@ -2,34 +2,9 @@ import type { ResourceManifest } from "@telorun/sdk";
|
|
|
2
2
|
import type { AliasResolver } from "./alias-resolver.js";
|
|
3
3
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
4
|
import { type AnalysisDiagnostic } from "./types.js";
|
|
5
|
-
/**
|
|
6
|
-
* The strict half of {@link readResourceInputs} — the `validate-ref-slots.ts`
|
|
7
|
-
* split applied to the resource-input boundary, and not optional for the same
|
|
8
|
-
* reason: the two sides fail in OPPOSITE directions. An unreadable DECLARATION
|
|
9
|
-
* leaves the library referencing a name nothing stands behind; an unchecked
|
|
10
|
-
* INJECTION hands a library an instance of a kind it never asked for, which
|
|
11
|
-
* surfaces as a method-missing failure inside someone else's module.
|
|
12
|
-
*
|
|
13
|
-
* Two surfaces, scoped the same way every other declaration check is — to the
|
|
14
|
-
* entry's own modules, since a published dependency's block is not the
|
|
15
|
-
* consumer's to fix:
|
|
16
|
-
*
|
|
17
|
-
* - **The library's own declaration.** Each entry's alias-qualified `kind:` must
|
|
18
|
-
* resolve in the DECLARING module's scope (`RESOURCE_INPUT_KIND_UNRESOLVED`).
|
|
19
|
-
* A constraint that resolves to nothing accepts anything, which is the same
|
|
20
|
-
* hole `X_TELO_REF_UNRESOLVED` exists to close one level down.
|
|
21
|
-
* - **The injection site.** Every declared entry must be supplied
|
|
22
|
-
* (`RESOURCE_INPUT_MISSING` — the same failure as a missing required
|
|
23
|
-
* variable), nothing beyond them may be (`RESOURCE_INPUT_UNKNOWN`), the value
|
|
24
|
-
* must name a resource that exists (`RESOURCE_INPUT_UNRESOLVED`), and its
|
|
25
|
-
* kind must satisfy the declared constraint transitively
|
|
26
|
-
* (`RESOURCE_INPUT_KIND_MISMATCH`).
|
|
27
|
-
*
|
|
28
|
-
* Browser-safe.
|
|
29
|
-
*/
|
|
30
5
|
export declare function validateResourceInputs(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver, rootModules: Set<string>,
|
|
31
6
|
/** Kind acceptance, transitively — the `checkKind` rule `validate-references`
|
|
32
7
|
* applies at an ordinary ref slot, passed in rather than re-derived so the
|
|
33
8
|
* two cannot disagree about what satisfies a constraint. */
|
|
34
|
-
acceptsKind: (suppliedKind: string, requiredKind: string) => boolean): AnalysisDiagnostic[];
|
|
9
|
+
acceptsKind: (suppliedKind: string, requiredKind: string, suppliedIsDeclaration: boolean) => boolean): AnalysisDiagnostic[];
|
|
35
10
|
//# sourceMappingURL=validate-resource-inputs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-resource-inputs.d.ts","sourceRoot":"","sources":["../src/validate-resource-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-resource-inputs.d.ts","sourceRoot":"","sources":["../src/validate-resource-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,YAAY,CAAC;AAqCzE,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC;AACxB;;6DAE6D;AAC7D,WAAW,EAAE,CACX,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,qBAAqB,EAAE,OAAO,KAC3B,OAAO,GACX,kBAAkB,EAAE,CAyMtB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isRefSentinel } from "@telorun/templating";
|
|
2
2
|
import { findDynamicLeaf } from "./resource-rule.js";
|
|
3
|
-
import { readResourceInputs, readSuppliedResources } from "./resource-input.js";
|
|
3
|
+
import { isInjectedDeclaration, readResourceInputs, readSuppliedResources, } from "./resource-input.js";
|
|
4
4
|
import { DiagnosticSeverity } from "./types.js";
|
|
5
5
|
const SOURCE = "telo-analyzer";
|
|
6
6
|
/**
|
|
@@ -28,6 +28,7 @@ const SOURCE = "telo-analyzer";
|
|
|
28
28
|
*
|
|
29
29
|
* Browser-safe.
|
|
30
30
|
*/
|
|
31
|
+
const declaringModule = (m) => m.metadata?.module;
|
|
31
32
|
export function validateResourceInputs(manifests, registry, aliases, rootModules,
|
|
32
33
|
/** Kind acceptance, transitively — the `checkKind` rule `validate-references`
|
|
33
34
|
* applies at an ordinary ref slot, passed in rather than re-derived so the
|
|
@@ -186,7 +187,16 @@ acceptsKind) {
|
|
|
186
187
|
if (typeof suppliedKind !== "string")
|
|
187
188
|
continue;
|
|
188
189
|
const canonical = aliases.resolveKind(suppliedKind) ?? suppliedKind;
|
|
189
|
-
|
|
190
|
+
// A library forwarding an input it was itself handed supplies a kind-only
|
|
191
|
+
// stand-in, whose kind is routinely the abstract the target declares. That
|
|
192
|
+
// is identity against an abstract, which an ordinary reference is refused
|
|
193
|
+
// — the referent there would be a resource nothing can instantiate, while
|
|
194
|
+
// here it is another declaration.
|
|
195
|
+
const suppliedName = value.name;
|
|
196
|
+
const supplied = typeof suppliedName === "string"
|
|
197
|
+
? manifests.find((c) => c.metadata?.name === suppliedName && isOwn(declaringModule(c)))
|
|
198
|
+
: undefined;
|
|
199
|
+
if (acceptsKind(canonical, entry, isInjectedDeclaration(supplied)))
|
|
190
200
|
continue;
|
|
191
201
|
out.push({
|
|
192
202
|
severity: DiagnosticSeverity.Error,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-schema-type-refs.d.ts","sourceRoot":"","sources":["../src/validate-schema-type-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-schema-type-refs.d.ts","sourceRoot":"","sources":["../src/validate-schema-type-refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMzE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC3C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GACvB,kBAAkB,EAAE,CAuDtB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { canonicalTypeSchemaId, parseTeloTypeRef } from "@telorun/sdk";
|
|
2
2
|
import { DiagnosticSeverity } from "./types.js";
|
|
3
|
+
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
3
4
|
const SOURCE = "telo-analyzer";
|
|
4
5
|
const SCHEMA_FIELDS = ["schema", "inputType", "outputType"];
|
|
5
6
|
/**
|
|
@@ -28,7 +29,7 @@ export function validateSchemaTypeRefs(manifests, registry, aliases, aliasesByMo
|
|
|
28
29
|
// validated against their own library when it's analyzed as a root.
|
|
29
30
|
if (ownModule && !rootModules.has(ownModule))
|
|
30
31
|
continue;
|
|
31
|
-
const resolver = (
|
|
32
|
+
const resolver = moduleAliasScope(m.metadata, aliases, aliasesByModule);
|
|
32
33
|
const filePath = m.metadata?.source;
|
|
33
34
|
const label = `${m.kind}/${name}`;
|
|
34
35
|
const walk = (value, path) => {
|
|
@@ -4,5 +4,9 @@ import { type AliasResolver } from "./alias-resolver.js";
|
|
|
4
4
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
5
5
|
import { type AnalysisDiagnostic } from "./types.js";
|
|
6
6
|
/** Entry point — invoked once per analyze() run. */
|
|
7
|
-
export declare function validateThrowsCoverage(manifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, env: Environment, aliasesByModule?: Map<string, AliasResolver>, rootModules?: Set<string
|
|
7
|
+
export declare function validateThrowsCoverage(manifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, env: Environment, aliasesByModule?: Map<string, AliasResolver>, rootModules?: Set<string>,
|
|
8
|
+
/** Each imported library's full document set, so the walk can follow an
|
|
9
|
+
* exported entry point into the siblings it invokes — which a consumer's
|
|
10
|
+
* flat set does not carry. */
|
|
11
|
+
moduleManifests?: Map<string, ResourceManifest[]>): AnalysisDiagnostic[];
|
|
8
12
|
//# sourceMappingURL=validate-throws-coverage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAsfzE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,
|
|
1
|
+
{"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAsfzE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa;AACpC;;+BAE+B;AAC/B,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAa,GAC3D,kBAAkB,EAAE,CAyDtB"}
|
|
@@ -431,10 +431,14 @@ function schemaHasStepContext(schema) {
|
|
|
431
431
|
return false;
|
|
432
432
|
}
|
|
433
433
|
/** Entry point — invoked once per analyze() run. */
|
|
434
|
-
export function validateThrowsCoverage(manifests, defs, aliases, env, aliasesByModule = new Map(), rootModules = new Set()
|
|
434
|
+
export function validateThrowsCoverage(manifests, defs, aliases, env, aliasesByModule = new Map(), rootModules = new Set(),
|
|
435
|
+
/** Each imported library's full document set, so the walk can follow an
|
|
436
|
+
* exported entry point into the siblings it invokes — which a consumer's
|
|
437
|
+
* flat set does not carry. */
|
|
438
|
+
moduleManifests = new Map()) {
|
|
435
439
|
const diagnostics = [];
|
|
436
440
|
diagnostics.push(...validateThrowsDeclarations(manifests));
|
|
437
|
-
const resolveCtx = createResolveCtx(manifests, defs, aliases, aliasesByModule, rootModules);
|
|
441
|
+
const resolveCtx = createResolveCtx(manifests, defs, aliases, aliasesByModule, rootModules, moduleManifests);
|
|
438
442
|
// The alias resolver for a manifest's own lexical scope — an imported library's
|
|
439
443
|
// resolver when it owns the manifest, else undefined (fall back to root aliases).
|
|
440
444
|
const scopeResolverFor = (m) => scopeResolverForModule(m.metadata?.module, rootModules, aliasesByModule);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -49,7 +49,7 @@
|
|
|
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.87.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@telorun/sdk": "*"
|
package/src/analysis-registry.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { visitManifest as runVisitManifest, type ManifestVisitor } from "./manif
|
|
|
9
9
|
import type { ContractDirection, DefResolver } from "./extends-resolution.js";
|
|
10
10
|
import { resolveContract } from "./invocation-contract.js";
|
|
11
11
|
import { createResolveCtx, resolveThrowsUnion } from "./resolve-throws-union.js";
|
|
12
|
+
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
12
13
|
import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
|
|
13
14
|
import { resolveSchemaTypeRefs as resolveSchemaTypeRefsIn } from "./resolve-schema-type-refs.js";
|
|
14
15
|
import type { AnalysisContext } from "./types.js";
|
|
@@ -258,8 +259,7 @@ export class AnalysisRegistry {
|
|
|
258
259
|
*/
|
|
259
260
|
resolveSchemaFrom(schemaFrom: string, declaringKind: string): Record<string, any> | undefined {
|
|
260
261
|
const def = this.resolveDefinition(declaringKind);
|
|
261
|
-
const
|
|
262
|
-
const scope = (ownerModule ? this.aliasesByModule.get(ownerModule) : undefined) ?? this.aliases;
|
|
262
|
+
const scope = moduleAliasScope(def?.metadata, this.aliases, this.aliasesByModule);
|
|
263
263
|
return this.defs.resolveSchemaFromNode(schemaFrom, scope);
|
|
264
264
|
}
|
|
265
265
|
|
|
@@ -337,7 +337,7 @@ export class AnalysisRegistry {
|
|
|
337
337
|
* the kind was read off. Falls back to the global table when the module is
|
|
338
338
|
* unknown or is a root. */
|
|
339
339
|
resolveDefinitionIn(kind: string, module?: string): ResourceDefinition | undefined {
|
|
340
|
-
const scope = (module
|
|
340
|
+
const scope = moduleAliasScope({ module }, this.aliases, this.aliasesByModule);
|
|
341
341
|
const canonical = scope.resolveKind(kind);
|
|
342
342
|
return this.defs.resolve(kind) ?? (canonical ? this.defs.resolve(canonical) : undefined);
|
|
343
343
|
}
|
|
@@ -354,8 +354,7 @@ export class AnalysisRegistry {
|
|
|
354
354
|
resolverForDefinition(def: {
|
|
355
355
|
metadata?: { module?: string };
|
|
356
356
|
}): (kind: string) => ResourceDefinition | undefined {
|
|
357
|
-
const
|
|
358
|
-
const scope = (ownModule ? this.aliasesByModule.get(ownModule) : undefined) ?? this.aliases;
|
|
357
|
+
const scope = moduleAliasScope(def?.metadata, this.aliases, this.aliasesByModule);
|
|
359
358
|
return (kind) => {
|
|
360
359
|
const canonical = scope.resolveKind(kind);
|
|
361
360
|
return this.defs.resolve(kind) ?? (canonical ? this.defs.resolve(canonical) : undefined);
|
package/src/analyzer.ts
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
} from "./validate-observed-state.js";
|
|
48
48
|
import { computeSuggestKind } from "./kind-suggest.js";
|
|
49
49
|
import { visitManifest } from "./manifest-visitor.js";
|
|
50
|
+
import { declaringModuleScope, moduleAliasScope } from "./module-alias-scope.js";
|
|
50
51
|
import { isModuleKind } from "./module-kinds.js";
|
|
51
52
|
import { normalizeInlineResources } from "./normalize-inline-resources.js";
|
|
52
53
|
import { REF_VALIDATION_SKIP_KINDS } from "./system-kinds.js";
|
|
@@ -999,10 +1000,10 @@ export class StaticAnalyzer {
|
|
|
999
1000
|
if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract") continue;
|
|
1000
1001
|
const def = m as unknown as ResourceDefinition;
|
|
1001
1002
|
const ownModule = (def.metadata as { module?: string } | undefined)?.module;
|
|
1002
|
-
const scopeResolver =
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1003
|
+
const scopeResolver = declaringModuleScope(ownModule, aliases, {
|
|
1004
|
+
aliasesByModule,
|
|
1005
|
+
rootModules,
|
|
1006
|
+
});
|
|
1006
1007
|
// Canonicalize alias-form `x-telo-ref` constraints in the DECLARING module's
|
|
1007
1008
|
// scope, before the schema reaches `register()` and the lazily-built field
|
|
1008
1009
|
// maps. Same pre-resolution `capability` / `extends` get below.
|
|
@@ -1425,7 +1426,7 @@ export class StaticAnalyzer {
|
|
|
1425
1426
|
// analyzer knowing it exists: going native costs a module, not a change
|
|
1426
1427
|
// here.
|
|
1427
1428
|
const resolveRegionDef = (kind: string, module?: string) => {
|
|
1428
|
-
const scope = (module
|
|
1429
|
+
const scope = moduleAliasScope({ module }, aliases, aliasesByModule);
|
|
1429
1430
|
const canonical = scope.resolveKind(kind);
|
|
1430
1431
|
return defs.resolve(kind) ?? (canonical ? defs.resolve(canonical) : undefined);
|
|
1431
1432
|
};
|
|
@@ -1456,8 +1457,10 @@ export class StaticAnalyzer {
|
|
|
1456
1457
|
if (!ownModule || !m.metadata?.name || typeof m.schema !== "object" || m.schema === null) {
|
|
1457
1458
|
continue;
|
|
1458
1459
|
}
|
|
1459
|
-
const scopeResolver =
|
|
1460
|
-
|
|
1460
|
+
const scopeResolver = declaringModuleScope(ownModule, aliases, {
|
|
1461
|
+
aliasesByModule,
|
|
1462
|
+
rootModules,
|
|
1463
|
+
});
|
|
1461
1464
|
const canonicalKind = scopeResolver.resolveKind(m.kind as string) ?? (m.kind as string);
|
|
1462
1465
|
if (defs.resolve(canonicalKind)?.capability !== "Telo.Type") continue;
|
|
1463
1466
|
const typeName = m.metadata.name as string;
|
|
@@ -1877,6 +1880,37 @@ export class StaticAnalyzer {
|
|
|
1877
1880
|
continue;
|
|
1878
1881
|
}
|
|
1879
1882
|
|
|
1883
|
+
// An abstract names a CONTRACT for reference slots, not something to
|
|
1884
|
+
// instantiate — the kernel refuses it outright at `create()`. Reported
|
|
1885
|
+
// here, at the declaration, because that is the line the author has to
|
|
1886
|
+
// change and because a runtime-only refusal is exactly the shape static
|
|
1887
|
+
// analysis exists to remove. The hint mirrors the kernel's: the concrete
|
|
1888
|
+
// implementations this analysis holds, or the reason there are none.
|
|
1889
|
+
//
|
|
1890
|
+
// Injected declarations never reach this loop (skipped above), which is
|
|
1891
|
+
// what keeps a `resources:` entry — kind-only and routinely abstract by
|
|
1892
|
+
// design — out of it.
|
|
1893
|
+
if (definition.kind === "Telo.Abstract") {
|
|
1894
|
+
const canonical = resolvedKind ?? m.kind;
|
|
1895
|
+
const impls = defs
|
|
1896
|
+
.getByExtends(canonical)
|
|
1897
|
+
.filter((d) => d.kind !== "Telo.Abstract")
|
|
1898
|
+
.map((d) => `${d.metadata.module}.${d.metadata.name}`);
|
|
1899
|
+
const kindInfo =
|
|
1900
|
+
canonical !== m.kind ? `'${m.kind}' (resolved to '${canonical}')` : `'${m.kind}'`;
|
|
1901
|
+
const hint = impls.length
|
|
1902
|
+
? `instantiate a concrete implementation: ${impls.join(", ")}`
|
|
1903
|
+
: "no concrete implementations are registered — import a module that provides one";
|
|
1904
|
+
diagnostics.push({
|
|
1905
|
+
severity: DiagnosticSeverity.Error,
|
|
1906
|
+
code: "ABSTRACT_KIND_INSTANTIATED",
|
|
1907
|
+
source: SOURCE,
|
|
1908
|
+
message: `Kind ${kindInfo} is abstract and cannot be instantiated directly; ${hint}.`,
|
|
1909
|
+
data: { resource, filePath, path: "kind" },
|
|
1910
|
+
});
|
|
1911
|
+
continue;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1880
1914
|
// Validate resource config against the definition's AUTHOR-FACING schema —
|
|
1881
1915
|
// inheritance-resolved, with `kind` / `metadata` injected. See
|
|
1882
1916
|
// `validationSchemaFor`, which is where both derivations and the reason
|
|
@@ -2690,9 +2724,25 @@ export class StaticAnalyzer {
|
|
|
2690
2724
|
// Validate provider coherence rules for `provide:` template-target definitions.
|
|
2691
2725
|
diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
|
|
2692
2726
|
|
|
2693
|
-
// Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
|
|
2727
|
+
// Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7).
|
|
2728
|
+
// The library document sets collected for the zone stage serve here too: a
|
|
2729
|
+
// library's exported entry point is forwarded into the flat set while the
|
|
2730
|
+
// siblings it invokes are not, so without them the walk stops at the
|
|
2731
|
+
// boundary and reports an empty union for a body that plainly throws.
|
|
2732
|
+
const libraryManifests = new Map<string, ResourceManifest[]>();
|
|
2733
|
+
for (const doc of options?.moduleDocuments ?? []) {
|
|
2734
|
+
libraryManifests.set(doc.module, doc.manifests);
|
|
2735
|
+
}
|
|
2694
2736
|
diagnostics.push(
|
|
2695
|
-
...validateThrowsCoverage(
|
|
2737
|
+
...validateThrowsCoverage(
|
|
2738
|
+
allManifests,
|
|
2739
|
+
defs,
|
|
2740
|
+
aliases,
|
|
2741
|
+
this.celEnv,
|
|
2742
|
+
aliasesByModule,
|
|
2743
|
+
rootModules,
|
|
2744
|
+
libraryManifests,
|
|
2745
|
+
),
|
|
2696
2746
|
);
|
|
2697
2747
|
|
|
2698
2748
|
// Warn about declared variables / secrets / ports that no CEL references.
|
|
@@ -2710,7 +2760,8 @@ export class StaticAnalyzer {
|
|
|
2710
2760
|
defs,
|
|
2711
2761
|
aliases,
|
|
2712
2762
|
rootModules,
|
|
2713
|
-
(supplied, required) =>
|
|
2763
|
+
(supplied, required, isDeclaration) =>
|
|
2764
|
+
kindSatisfies(supplied, required, defs, isDeclaration),
|
|
2714
2765
|
),
|
|
2715
2766
|
);
|
|
2716
2767
|
|