@telorun/analyzer 0.71.0 → 0.73.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 +92 -15
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +71 -97
- package/dist/catch-scope.d.ts +72 -0
- package/dist/catch-scope.d.ts.map +1 -0
- package/dist/catch-scope.js +102 -0
- package/dist/cel-scope-query.d.ts.map +1 -1
- package/dist/cel-scope-query.js +32 -11
- package/dist/cel-scope.d.ts.map +1 -1
- package/dist/cel-scope.js +1 -0
- package/dist/deprecation.d.ts +21 -0
- package/dist/deprecation.d.ts.map +1 -0
- package/dist/deprecation.js +26 -0
- package/dist/eval-paths.d.ts +36 -5
- package/dist/eval-paths.d.ts.map +1 -1
- package/dist/eval-paths.js +47 -6
- package/dist/extends-resolution.d.ts +12 -0
- package/dist/extends-resolution.d.ts.map +1 -1
- package/dist/extends-resolution.js +23 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/manifest-visitor.d.ts +17 -1
- package/dist/manifest-visitor.d.ts.map +1 -1
- package/dist/manifest-visitor.js +5 -1
- package/dist/migrations/report.d.ts +1 -1
- package/dist/migrations/report.d.ts.map +1 -1
- package/dist/migrations/report.js +5 -0
- package/dist/nearest-name.d.ts +22 -0
- package/dist/nearest-name.d.ts.map +1 -0
- package/dist/nearest-name.js +43 -0
- package/dist/ref-slot.d.ts +15 -0
- package/dist/ref-slot.d.ts.map +1 -1
- package/dist/ref-slot.js +7 -0
- package/dist/release/destinations.d.ts +59 -0
- package/dist/release/destinations.d.ts.map +1 -0
- package/dist/release/destinations.js +75 -0
- package/dist/release/index.d.ts +6 -2
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +3 -1
- package/dist/release/ledger.d.ts +20 -11
- package/dist/release/ledger.d.ts.map +1 -1
- package/dist/release/ledger.js +43 -14
- package/dist/release/release-plan.d.ts +3 -2
- package/dist/release/release-plan.d.ts.map +1 -1
- package/dist/release/release-plan.js +12 -19
- package/dist/release/workspace-config.d.ts +114 -34
- package/dist/release/workspace-config.d.ts.map +1 -1
- package/dist/release/workspace-config.js +322 -38
- package/dist/release/workspace-schema.d.ts +41 -0
- package/dist/release/workspace-schema.d.ts.map +1 -0
- package/dist/release/workspace-schema.js +77 -0
- package/dist/resolve-throws-union.d.ts +29 -1
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +111 -16
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +13 -1
- package/dist/schema-error-report.d.ts +7 -0
- package/dist/schema-error-report.d.ts.map +1 -1
- package/dist/schema-error-report.js +57 -8
- package/dist/schema-keywords.d.ts.map +1 -1
- package/dist/schema-keywords.js +3 -1
- package/dist/schema-walk.d.ts +27 -0
- package/dist/schema-walk.d.ts.map +1 -1
- package/dist/schema-walk.js +44 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/template-body.d.ts +10 -0
- package/dist/template-body.d.ts.map +1 -1
- package/dist/template-body.js +25 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/dist/validate-cel-context.d.ts +23 -8
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +48 -16
- package/dist/validate-exports.d.ts +28 -0
- package/dist/validate-exports.d.ts.map +1 -0
- package/dist/validate-exports.js +146 -0
- package/dist/validate-extends.d.ts.map +1 -1
- package/dist/validate-extends.js +37 -1
- package/dist/validate-identifier-names.d.ts +2 -2
- package/dist/validate-identifier-names.d.ts.map +1 -1
- package/dist/validate-identifier-names.js +22 -7
- package/dist/validate-provider-coherence.d.ts +4 -8
- package/dist/validate-provider-coherence.d.ts.map +1 -1
- package/dist/validate-provider-coherence.js +12 -123
- package/dist/validate-ref-slots.d.ts +1 -1
- package/dist/validate-ref-slots.d.ts.map +1 -1
- package/dist/validate-ref-slots.js +34 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +168 -63
- package/dist/validate-template-body.d.ts +36 -0
- package/dist/validate-template-body.d.ts.map +1 -0
- package/dist/validate-template-body.js +246 -0
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +236 -85
- package/package.json +3 -2
- package/src/analyzer.ts +118 -12
- package/src/builtins.ts +85 -97
- package/src/catch-scope.ts +157 -0
- package/src/cel-scope-query.ts +28 -9
- package/src/cel-scope.ts +1 -0
- package/src/deprecation.ts +36 -0
- package/src/eval-paths.ts +53 -6
- package/src/extends-resolution.ts +24 -0
- package/src/index.ts +9 -1
- package/src/manifest-visitor.ts +19 -2
- package/src/migrations/report.ts +5 -1
- package/src/nearest-name.ts +47 -0
- package/src/ref-slot.ts +19 -0
- package/src/release/destinations.ts +105 -0
- package/src/release/index.ts +33 -2
- package/src/release/ledger.ts +51 -21
- package/src/release/release-plan.ts +16 -26
- package/src/release/workspace-config.ts +483 -47
- package/src/release/workspace-schema.ts +99 -0
- package/src/resolve-throws-union.ts +139 -21
- package/src/schema-compat.ts +13 -0
- package/src/schema-error-report.ts +66 -10
- package/src/schema-keywords.ts +4 -1
- package/src/schema-walk.ts +56 -0
- package/src/telo-version.ts +1 -1
- package/src/template-body.ts +25 -0
- package/src/types.ts +18 -0
- package/src/validate-cel-context.ts +57 -16
- package/src/validate-exports.ts +185 -0
- package/src/validate-extends.ts +43 -0
- package/src/validate-identifier-names.ts +28 -9
- package/src/validate-provider-coherence.ts +17 -127
- package/src/validate-ref-slots.ts +41 -1
- package/src/validate-references.ts +174 -63
- package/src/validate-template-body.ts +274 -0
- package/src/validate-throws-coverage.ts +333 -92
- package/dist/validate-template-dispatch.d.ts +0 -27
- package/dist/validate-template-dispatch.d.ts.map +0 -1
- package/dist/validate-template-dispatch.js +0 -95
- package/src/validate-template-dispatch.ts +0 -99
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAiBzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAiBzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAsC9B,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAWvF,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AA4DjE,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAoGpB;0CAC0C;AAC1C,eAAO,MAAM,eAAe,GAAI,OAAO;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,KAAG,OACT,CAAC;AAErD;8EAC8E;AAC9E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD;AAqUD,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACtC;AA6DD,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAkFD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,EAC3B,eAAe,CAAC,EAAE,eAAe,GAChC,kBAAkB,EAAE;IAmlEvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,EAC3B,eAAe,CAAC,EAAE,eAAe,GAChC,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAyBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
|
package/dist/analyzer.js
CHANGED
|
@@ -3,7 +3,8 @@ import { defaultRegistry, isRefSentinel, isTaggedSentinel, plainChainOf, } from
|
|
|
3
3
|
import { AliasResolver, scopeResolverForModule, } from "./alias-resolver.js";
|
|
4
4
|
import { buildCelEnvironment, isKindDocument, } from "./cel-environment.js";
|
|
5
5
|
import { DefinitionRegistry } from "./definition-registry.js";
|
|
6
|
-
import {
|
|
6
|
+
import { readDeprecation } from "./deprecation.js";
|
|
7
|
+
import { effectiveAuthorSchema, inheritedCapability, inheritedRequiredFields, } from "./extends-resolution.js";
|
|
7
8
|
import { analyzerContractScope, resolveContract, } from "./invocation-contract.js";
|
|
8
9
|
import { buildCallGraph } from "./call-graph.js";
|
|
9
10
|
import { buildDependencyGraph, formatCycle } from "./dependency-graph.js";
|
|
@@ -44,9 +45,9 @@ import { validateSchemaTypeRefs } from "./validate-schema-type-refs.js";
|
|
|
44
45
|
import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
|
|
45
46
|
import { celTypeSatisfiesJsonSchema, checkSchemaCompatibility, navigateSchemaToExprPath, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
|
|
46
47
|
import { collectValueSchemaIssues } from "./validate-value-schema.js";
|
|
47
|
-
import { DiagnosticSeverity } from "./types.js";
|
|
48
|
+
import { DiagnosticSeverity, DiagnosticTag, } from "./types.js";
|
|
48
49
|
import { extractAccessChains, } from "./validate-cel-context.js";
|
|
49
|
-
import { celEvalModeAt, celEvalSites, mergeCelEvalSites, NO_CEL_EVAL_SITES, } from "./eval-paths.js";
|
|
50
|
+
import { celEvalModeAt, celEvalSites, implicitEvalSites, mergeCelEvalSites, NO_CEL_EVAL_SITES, } from "./eval-paths.js";
|
|
50
51
|
import { BINDINGS_ANNOTATION, findBindingSites, resolveBindingOrder, } from "./cel-bindings.js";
|
|
51
52
|
import { CEL_RESERVED_WORDS, checkName } from "./identifier-name.js";
|
|
52
53
|
import { validateIdentifierNames } from "./validate-identifier-names.js";
|
|
@@ -65,7 +66,8 @@ import { kindSatisfies, validateReferences } from "./validate-references.js";
|
|
|
65
66
|
import { validateReferenceForms } from "./validate-reference-forms.js";
|
|
66
67
|
import { isInjectedDeclaration } from "./resource-input.js";
|
|
67
68
|
import { validateResourceInputs } from "./validate-resource-inputs.js";
|
|
68
|
-
import {
|
|
69
|
+
import { validateExports } from "./validate-exports.js";
|
|
70
|
+
import { validateTemplateBody } from "./validate-template-body.js";
|
|
69
71
|
import { validateUnusedDeclarations } from "./validate-unused-declarations.js";
|
|
70
72
|
import { validateThrowsCoverage } from "./validate-throws-coverage.js";
|
|
71
73
|
import { readStepSlot } from "./step-slot.js";
|
|
@@ -1308,7 +1310,7 @@ export class StaticAnalyzer {
|
|
|
1308
1310
|
// unchecked where it is declared and its consequences land at whichever
|
|
1309
1311
|
// CEL site reads it — for a hyphen, sometimes as silent arithmetic. Takes
|
|
1310
1312
|
// the call graph for step names rather than re-walking the step arrays.
|
|
1311
|
-
diagnostics.push(...validateIdentifierNames(allManifests, defs, aliases, rootModules, getCallGraph()));
|
|
1313
|
+
diagnostics.push(...validateIdentifierNames(allManifests, defs, aliases, rootModules, getCallGraph(), { aliasesByModule, rootModules }));
|
|
1312
1314
|
// A file embed resolves at resource creation, so one written on a doc that
|
|
1313
1315
|
// is never instantiated is read by nothing and would ship silently.
|
|
1314
1316
|
diagnostics.push(...validateIncludePlacement(allManifests));
|
|
@@ -1652,6 +1654,45 @@ export class StaticAnalyzer {
|
|
|
1652
1654
|
});
|
|
1653
1655
|
continue;
|
|
1654
1656
|
}
|
|
1657
|
+
// A kind its own author marked `metadata.deprecated` still works exactly as
|
|
1658
|
+
// it did — the manifest is valid and keeps running — so this is a WARNING,
|
|
1659
|
+
// reported at the declaration because `kind:` is the line that has to
|
|
1660
|
+
// change. Emitted here rather than in a pass of its own so it reads the
|
|
1661
|
+
// kind THIS walk resolved: the resolution is alias- and gate-aware and
|
|
1662
|
+
// scope-dependent, and a second implementation of it would eventually
|
|
1663
|
+
// disagree about which definition a name means.
|
|
1664
|
+
//
|
|
1665
|
+
// Entry-module-scoped, like every other "not the consumer's to fix" check:
|
|
1666
|
+
// a library's internal use of a kind its own author deprecated is that
|
|
1667
|
+
// author's concern, and reporting it floods a consumer with lines they
|
|
1668
|
+
// cannot act on.
|
|
1669
|
+
//
|
|
1670
|
+
// No `DiagnosticFix`. `replacedBy` names a kind through the DECLARING
|
|
1671
|
+
// module's aliases; writing it into the consumer's file would produce a
|
|
1672
|
+
// prefix that resolves to nothing there — and a kind swap is not a
|
|
1673
|
+
// whole-value replacement anyway, since the successor needs its own import
|
|
1674
|
+
// and usually a different configuration.
|
|
1675
|
+
const deprecation = readDeprecation(definition.metadata);
|
|
1676
|
+
if (deprecation && (!ownModule || rootModules.has(ownModule))) {
|
|
1677
|
+
// Quoted CANONICALLY, not verbatim: `Self.Thing` is how the declaring
|
|
1678
|
+
// library names its own kind and means nothing where the warning lands.
|
|
1679
|
+
const declaringScope = scopeResolverForModule(definition.metadata?.module, rootModules, aliasesByModule) ?? aliases;
|
|
1680
|
+
const replacement = deprecation.replacedBy
|
|
1681
|
+
? (declaringScope.resolveKind(deprecation.replacedBy) ?? deprecation.replacedBy)
|
|
1682
|
+
: undefined;
|
|
1683
|
+
diagnostics.push({
|
|
1684
|
+
severity: DiagnosticSeverity.Warning,
|
|
1685
|
+
code: "DEPRECATED_KIND",
|
|
1686
|
+
source: SOURCE,
|
|
1687
|
+
// Warning-grade AND a deprecation: the severity says it must
|
|
1688
|
+
// eventually be dealt with, the tag says what it is, and an editor
|
|
1689
|
+
// strikes the kind through on the strength of the second.
|
|
1690
|
+
tags: [DiagnosticTag.Deprecated],
|
|
1691
|
+
message: `Kind '${m.kind}' is deprecated: ${deprecation.reason}` +
|
|
1692
|
+
(replacement ? ` Use '${replacement}' instead.` : ""),
|
|
1693
|
+
data: { resource, filePath, path: "kind" },
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1655
1696
|
// Validate resource config against the definition's AUTHOR-FACING schema —
|
|
1656
1697
|
// inheritance-resolved, with `kind` / `metadata` injected. See
|
|
1657
1698
|
// `validationSchemaFor`, which is where both derivations and the reason
|
|
@@ -1706,12 +1747,37 @@ export class StaticAnalyzer {
|
|
|
1706
1747
|
// is caught here rather than on the one input that selects it.
|
|
1707
1748
|
const valueSchemaIssues = collectValueSchemaIssues(m, schema, allManifests);
|
|
1708
1749
|
const issues = [...ajvIssues, ...valueSchemaIssues];
|
|
1750
|
+
// WHY A FIELD THIS KIND EXISTS TO SUPPLY IS STILL REQUIRED OF ITS
|
|
1751
|
+
// CONSUMER. A child that `extends` and declares no `base:` is authored
|
|
1752
|
+
// against merge(parent, own), so the parent's `required` stays on the
|
|
1753
|
+
// CHILD's surface — and a kind written to wire that field internally
|
|
1754
|
+
// then demands it from the consumer anyway. The `base:` mapping is what
|
|
1755
|
+
// narrows (with it the surface is the child's own schema alone), and
|
|
1756
|
+
// nothing in "is missing required property" points there, which is what
|
|
1757
|
+
// turns one mistake into a three-step dead end.
|
|
1758
|
+
//
|
|
1759
|
+
// Matched against names DERIVED from the parent, never by parsing the
|
|
1760
|
+
// message: which fields are inherited is a fact about the definition,
|
|
1761
|
+
// and a check that reads a validator's prose breaks when the prose does.
|
|
1762
|
+
const inheritedRequired = inheritedRequiredFields(definition, (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k));
|
|
1763
|
+
const parentKind = definition?.extends;
|
|
1709
1764
|
for (const issue of issues) {
|
|
1765
|
+
// Keyed on the STRUCTURED failure, never on the sentence: the field
|
|
1766
|
+
// names are derived from the parent, and matching them against the
|
|
1767
|
+
// validator's prose would break when the prose changes and mis-fire on
|
|
1768
|
+
// any other issue quoting the same name (an `additionalProperties`
|
|
1769
|
+
// rejection, an `enum` listing).
|
|
1770
|
+
const inherited = issue.keyword === "required" && issue.missingProperty !== undefined
|
|
1771
|
+
? inheritedRequired.filter((f) => f === issue.missingProperty)
|
|
1772
|
+
: [];
|
|
1773
|
+
const hint = inherited.length > 0 && parentKind
|
|
1774
|
+
? ` — ${inherited.map((f) => `'${f}'`).join(", ")} ${inherited.length > 1 ? "come" : "comes"} from '${parentKind}', which this kind extends without a 'base:' mapping, so the parent's required fields stay on this kind's author surface. Add 'base:' to set them internally; that also narrows the surface to this kind's own schema.`
|
|
1775
|
+
: "";
|
|
1710
1776
|
diagnostics.push({
|
|
1711
1777
|
severity: DiagnosticSeverity.Error,
|
|
1712
1778
|
code: "SCHEMA_VIOLATION",
|
|
1713
1779
|
source: SOURCE,
|
|
1714
|
-
message: `${m.kind}/${resource.name}: ${issue.message}`,
|
|
1780
|
+
message: `${m.kind}/${resource.name}: ${issue.message}${hint}`,
|
|
1715
1781
|
data: { resource, filePath, path: issue.path },
|
|
1716
1782
|
});
|
|
1717
1783
|
}
|
|
@@ -2076,8 +2142,14 @@ export class StaticAnalyzer {
|
|
|
2076
2142
|
}
|
|
2077
2143
|
// The non-eval-field check only applies to runtime resource instances:
|
|
2078
2144
|
// structural / templating kinds (capability `Telo.Template`, or no
|
|
2079
|
-
// definition) carry CEL the kernel evaluates by other rules.
|
|
2080
|
-
|
|
2145
|
+
// definition) carry CEL the kernel evaluates by other rules. The
|
|
2146
|
+
// capability is INHERITED along `extends` — an inheritance kind
|
|
2147
|
+
// writes none of its own — and reading the declared one left the
|
|
2148
|
+
// rule off for every such kind, and its expressions untyped.
|
|
2149
|
+
const resolveDef = (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k);
|
|
2150
|
+
const capability = e.definition
|
|
2151
|
+
? inheritedCapability(e.definition, resolveDef)
|
|
2152
|
+
: undefined;
|
|
2081
2153
|
celRuleApplies =
|
|
2082
2154
|
!!e.definition?.schema && capability !== undefined && capability !== "Telo.Template";
|
|
2083
2155
|
if (celRuleApplies) {
|
|
@@ -2089,12 +2161,14 @@ export class StaticAnalyzer {
|
|
|
2089
2161
|
// the own schema here reported `CEL_IN_NON_EVAL_FIELD` for an
|
|
2090
2162
|
// expression the runtime evaluates correctly, which is the two halves
|
|
2091
2163
|
// disagreeing about what the manifest means.
|
|
2092
|
-
const ownSchema = effectiveAuthorSchema(e.definition,
|
|
2164
|
+
const ownSchema = effectiveAuthorSchema(e.definition, resolveDef);
|
|
2093
2165
|
const capabilityDef = capability ? defs.resolve(capability) : undefined;
|
|
2094
2166
|
// A `Telo.Provider`'s fields are implicitly compile-eval — the
|
|
2095
2167
|
// capability abstract carries the root annotation — so its reads are
|
|
2096
|
-
// covered here without the provider restating anything.
|
|
2097
|
-
|
|
2168
|
+
// covered here without the provider restating anything. A base-form
|
|
2169
|
+
// child's own fields are too (`implicitEvalSites`): `base:` reads
|
|
2170
|
+
// them once at create().
|
|
2171
|
+
celSites = mergeCelEvalSites(celEvalSites(ownSchema), celEvalSites(capabilityDef?.schema), implicitEvalSites(e.definition));
|
|
2098
2172
|
}
|
|
2099
2173
|
else {
|
|
2100
2174
|
celSites = NO_CEL_EVAL_SITES;
|
|
@@ -2363,10 +2437,13 @@ export class StaticAnalyzer {
|
|
|
2363
2437
|
diagnostics.push(...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv, aliasesByModule, rootModules, libraryManifests));
|
|
2364
2438
|
// Warn about declared variables / secrets / ports that no CEL references.
|
|
2365
2439
|
diagnostics.push(...validateUnusedDeclarations(allManifests, this.celEnv));
|
|
2366
|
-
// A
|
|
2367
|
-
// entry —
|
|
2368
|
-
//
|
|
2369
|
-
diagnostics.push(...
|
|
2440
|
+
// A template body's reference surface — its entry names, its dispatch slots
|
|
2441
|
+
// and the ref slots inside each entry — which no reference pass reaches,
|
|
2442
|
+
// since a `Telo.Definition` is in both skip sets.
|
|
2443
|
+
diagnostics.push(...validateTemplateBody(allManifests, defs, aliases, aliasesByModule, rootModules));
|
|
2444
|
+
// A library's export list, resolved against what it declares — otherwise a
|
|
2445
|
+
// listed name that exists nowhere fails in the consumer's file.
|
|
2446
|
+
diagnostics.push(...validateExports(allManifests, defs, aliases, rootModules));
|
|
2370
2447
|
// A library's declared resource inputs, and every import that supplies them.
|
|
2371
2448
|
diagnostics.push(...validateResourceInputs(allManifests, defs, aliases, rootModules, (supplied, required, isDeclaration) => kindSatisfies(supplied, required, defs, isDeclaration)));
|
|
2372
2449
|
// Reroute diagnostics on synthetic (inline-extracted) resources back to
|
package/dist/builtins.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAiFA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AA8OvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EA2pB/C,CAAC"}
|
package/dist/builtins.js
CHANGED
|
@@ -20,6 +20,60 @@ const observedStateSlot = {
|
|
|
20
20
|
description: "What a resource of this kind reports while running, published at `resources.<name>.status.<field>`.",
|
|
21
21
|
$ref: manifestFragmentRef("JsonSchema7"),
|
|
22
22
|
};
|
|
23
|
+
/** The `self`-only CEL scope a dispatch slot's name template is written in. */
|
|
24
|
+
const dispatchSelfContext = {
|
|
25
|
+
type: "object",
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
properties: { self: { "x-telo-context-from-root": "schema" } },
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A `Telo.Definition` dispatch slot: which `resources:` entry receives the call.
|
|
31
|
+
*
|
|
32
|
+
* Three shapes, and the union is what keeps the slot CONSTRAINED for manifests
|
|
33
|
+
* no pass walks — `validate-template-body` resolves the target and its
|
|
34
|
+
* capability, but it is entry-scoped, so a dependency's definition would
|
|
35
|
+
* otherwise have nothing checking this slot at all. An array or a number fails
|
|
36
|
+
* every branch here, which is the floor AJV used to provide.
|
|
37
|
+
*
|
|
38
|
+
* `anyOf`, never `oneOf`: a `!ref` sentinel is an object, so it would match both
|
|
39
|
+
* the sentinel branch and any permissive object branch and then fail for
|
|
40
|
+
* matching twice. That is also how the sentinel used to pass — the legacy object
|
|
41
|
+
* branch required nothing, so it accepted one by accident; here it is a branch
|
|
42
|
+
* of its own that says so.
|
|
43
|
+
*/
|
|
44
|
+
function dispatchSlot(title, description) {
|
|
45
|
+
return {
|
|
46
|
+
title,
|
|
47
|
+
description,
|
|
48
|
+
anyOf: [
|
|
49
|
+
// `!ref <entry>` — the spelling to write.
|
|
50
|
+
{
|
|
51
|
+
type: "object",
|
|
52
|
+
required: ["__tagged", "engine", "source"],
|
|
53
|
+
properties: {
|
|
54
|
+
__tagged: { const: true },
|
|
55
|
+
engine: { const: "ref" },
|
|
56
|
+
source: { type: "string", minLength: 1 },
|
|
57
|
+
},
|
|
58
|
+
additionalProperties: false,
|
|
59
|
+
},
|
|
60
|
+
// Legacy: a name template expanded against `self`. Read forever, because
|
|
61
|
+
// published artifacts carry it; `DEPRECATED_TEMPLATE_DISPATCH_FORM` is
|
|
62
|
+
// what moves an author off it.
|
|
63
|
+
{ type: "string", "x-telo-context": dispatchSelfContext },
|
|
64
|
+
// Legacy: `{ kind?, name }`, where `name` is that same template.
|
|
65
|
+
{
|
|
66
|
+
type: "object",
|
|
67
|
+
required: ["name"],
|
|
68
|
+
properties: {
|
|
69
|
+
kind: { type: "string" },
|
|
70
|
+
name: { type: "string", "x-telo-context": dispatchSelfContext },
|
|
71
|
+
},
|
|
72
|
+
additionalProperties: true,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
23
77
|
/** Descriptive provenance a module declares about itself, shared by
|
|
24
78
|
* `Telo.Application` and `Telo.Library`.
|
|
25
79
|
*
|
|
@@ -459,99 +513,19 @@ export const KERNEL_BUILTINS = [
|
|
|
459
513
|
},
|
|
460
514
|
},
|
|
461
515
|
},
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
type: "object",
|
|
476
|
-
additionalProperties: true,
|
|
477
|
-
properties: {
|
|
478
|
-
kind: { type: "string" },
|
|
479
|
-
name: {
|
|
480
|
-
type: "string",
|
|
481
|
-
"x-telo-context": {
|
|
482
|
-
type: "object",
|
|
483
|
-
additionalProperties: false,
|
|
484
|
-
properties: {
|
|
485
|
-
self: { "x-telo-context-from-root": "schema" },
|
|
486
|
-
},
|
|
487
|
-
},
|
|
488
|
-
},
|
|
489
|
-
},
|
|
490
|
-
},
|
|
491
|
-
],
|
|
492
|
-
},
|
|
493
|
-
provide: {
|
|
494
|
-
type: "object",
|
|
495
|
-
additionalProperties: true,
|
|
496
|
-
properties: {
|
|
497
|
-
kind: { type: "string" },
|
|
498
|
-
name: {
|
|
499
|
-
type: "string",
|
|
500
|
-
"x-telo-context": {
|
|
501
|
-
type: "object",
|
|
502
|
-
additionalProperties: false,
|
|
503
|
-
properties: {
|
|
504
|
-
self: { "x-telo-context-from-root": "schema" },
|
|
505
|
-
},
|
|
506
|
-
},
|
|
507
|
-
},
|
|
508
|
-
},
|
|
509
|
-
},
|
|
510
|
-
run: {
|
|
511
|
-
type: "string",
|
|
512
|
-
"x-telo-context": {
|
|
513
|
-
type: "object",
|
|
514
|
-
additionalProperties: false,
|
|
515
|
-
properties: {
|
|
516
|
-
self: { "x-telo-context-from-root": "schema" },
|
|
517
|
-
},
|
|
518
|
-
},
|
|
519
|
-
},
|
|
520
|
-
// Mount dispatch: names the `resources:` entry (a Telo.Mount, e.g. an
|
|
521
|
-
// Http.Api) whose `register()` this definition delegates to. Same
|
|
522
|
-
// string / { kind, name } grammar as `invoke:`. The named child stays
|
|
523
|
-
// persistent so the produced mount's routes can `!ref` its siblings.
|
|
524
|
-
mount: {
|
|
525
|
-
oneOf: [
|
|
526
|
-
{
|
|
527
|
-
type: "string",
|
|
528
|
-
"x-telo-context": {
|
|
529
|
-
type: "object",
|
|
530
|
-
additionalProperties: false,
|
|
531
|
-
properties: {
|
|
532
|
-
self: { "x-telo-context-from-root": "schema" },
|
|
533
|
-
},
|
|
534
|
-
},
|
|
535
|
-
},
|
|
536
|
-
{
|
|
537
|
-
type: "object",
|
|
538
|
-
additionalProperties: true,
|
|
539
|
-
properties: {
|
|
540
|
-
kind: { type: "string" },
|
|
541
|
-
name: {
|
|
542
|
-
type: "string",
|
|
543
|
-
"x-telo-context": {
|
|
544
|
-
type: "object",
|
|
545
|
-
additionalProperties: false,
|
|
546
|
-
properties: {
|
|
547
|
-
self: { "x-telo-context-from-root": "schema" },
|
|
548
|
-
},
|
|
549
|
-
},
|
|
550
|
-
},
|
|
551
|
-
},
|
|
552
|
-
},
|
|
553
|
-
],
|
|
554
|
-
},
|
|
516
|
+
// A dispatch slot names the `resources:` entry that receives the call,
|
|
517
|
+
// as `!ref <entry>` — the one spelling every reference in Telo has. The
|
|
518
|
+
// string and `{ kind, name }` forms it used to admit (a CEL name
|
|
519
|
+
// template matched against CEL-named entries) were the removed
|
|
520
|
+
// reference object surviving in the one place nothing resolved it;
|
|
521
|
+
// `validate-template-body` is what resolves this slot, so the schema
|
|
522
|
+
// stays open here rather than reporting the same defect twice.
|
|
523
|
+
invoke: dispatchSlot("Invoke target", "The `resources:` entry whose `invoke()` this kind dispatches to, as `!ref <entry>`."),
|
|
524
|
+
provide: dispatchSlot("Provide target", "The `resources:` entry whose `invoke()` produces this provider's value, as `!ref <entry>`."),
|
|
525
|
+
run: dispatchSlot("Run target", "The `resources:` entry whose `run()` this kind dispatches to, as `!ref <entry>`."),
|
|
526
|
+
// The named child stays persistent so the produced mount's routes can
|
|
527
|
+
// `!ref` its siblings.
|
|
528
|
+
mount: dispatchSlot("Mount target", "The `resources:` entry (a Telo.Mount, e.g. an Http.Api) whose `register()` this kind delegates to, as `!ref <entry>`."),
|
|
555
529
|
inputs: {
|
|
556
530
|
type: "object",
|
|
557
531
|
additionalProperties: true,
|
|
@@ -572,11 +546,11 @@ export const KERNEL_BUILTINS = [
|
|
|
572
546
|
additionalProperties: false,
|
|
573
547
|
properties: {
|
|
574
548
|
self: { "x-telo-context-from-root": "schema" },
|
|
549
|
+
// Typed from the dispatch target's declared output: the slot
|
|
550
|
+
// holds a `!ref` to a `resources:` entry, resolved to that
|
|
551
|
+
// entry's kind (or its own `outputType`, where it narrows one).
|
|
575
552
|
result: {
|
|
576
|
-
"x-telo-context-from-ref-kind": [
|
|
577
|
-
"provide/kind#outputType",
|
|
578
|
-
"invoke/kind#outputType",
|
|
579
|
-
],
|
|
553
|
+
"x-telo-context-from-ref-kind": ["provide#outputType", "invoke#outputType"],
|
|
580
554
|
},
|
|
581
555
|
},
|
|
582
556
|
},
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which `catches:` lists answer for one dispatch site.
|
|
3
|
+
*
|
|
4
|
+
* A catch list is one rung of a SCOPE LADDER — a route's, its router's, its
|
|
5
|
+
* server's — and every rule over those lists needs the same two answers: what
|
|
6
|
+
* a scope list is checked against, and which scopes enclose a given site. This
|
|
7
|
+
* module owns that model; the rule checks in `validate-throws-coverage.ts` read
|
|
8
|
+
* one answer instead of rebuilding it beside four unrelated jobs.
|
|
9
|
+
*
|
|
10
|
+
* Browser-safe: no Node built-ins.
|
|
11
|
+
*/
|
|
12
|
+
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
13
|
+
import type { AliasResolver } from "./alias-resolver.js";
|
|
14
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
15
|
+
import { type ResolveCtx } from "./resolve-throws-union.js";
|
|
16
|
+
/** What a list proves it renders: the codes its coverage-proving `when:` clauses
|
|
17
|
+
* name, and whether it ends in a catch-all.
|
|
18
|
+
*
|
|
19
|
+
* Unfiltered by any denominator, because this is also what an enclosing scope
|
|
20
|
+
* contributes DOWNWARD — a server entry naming a code the server's own closure
|
|
21
|
+
* could not enumerate still renders it for the route that throws it. Filtering
|
|
22
|
+
* the local answer against the declared union happens where that answer is
|
|
23
|
+
* used, and subtracting from the declared set makes the two equivalent there. */
|
|
24
|
+
export interface ProvenCoverage {
|
|
25
|
+
codes: Set<string>;
|
|
26
|
+
hasCatchAll: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare const NO_COVERAGE: ProvenCoverage;
|
|
29
|
+
/** A declaration inside another resource's `x-telo-scope` array, kept with the
|
|
30
|
+
* resource that encloses it and the path it is written at in that resource's
|
|
31
|
+
* document. Both are needed: the owner is the only place its kind's alias scope
|
|
32
|
+
* can be found, and the only document position lookup can reach. */
|
|
33
|
+
export interface ScopedManifest {
|
|
34
|
+
manifest: ResourceManifest;
|
|
35
|
+
owner: ResourceManifest;
|
|
36
|
+
/** Concrete owner-relative path of the declaration (`with[0]`). */
|
|
37
|
+
path: string;
|
|
38
|
+
}
|
|
39
|
+
/** Every `with:`-scoped declaration in the set, once each.
|
|
40
|
+
*
|
|
41
|
+
* Scoped resources are absent from the flat manifest list, so every check that
|
|
42
|
+
* iterates it skips them — and standing a server up around a test is exactly
|
|
43
|
+
* that shape, so the sanctioned pattern was the one the pass could not see.
|
|
44
|
+
* Discovered through the shared visitor rather than a second scope walk. */
|
|
45
|
+
export declare function collectScopedManifests(manifests: ResourceManifest[], defs: DefinitionRegistry, aliases: AliasResolver, aliasesByModule: Map<string, AliasResolver>, rootModules: Set<string>): ScopedManifest[];
|
|
46
|
+
/** Which resources each resource's scope list encloses, along the slots that
|
|
47
|
+
* declare `throwsThrough` — one fact stated once, since the edge a throws
|
|
48
|
+
* closure crosses is the edge a catch scope encloses through. */
|
|
49
|
+
export declare function buildEnclosers(manifests: ResourceManifest[], definitionOf: (m: ResourceManifest) => {
|
|
50
|
+
schema?: Record<string, any>;
|
|
51
|
+
} | undefined, moduleOf: (m: ResourceManifest) => string | undefined, ctx: ResolveCtx): Map<ResourceManifest, ResourceManifest[]>;
|
|
52
|
+
/**
|
|
53
|
+
* What every scope enclosing a resource is guaranteed to render for it.
|
|
54
|
+
*
|
|
55
|
+
* **Across enclosers this INTERSECTS, and that is the whole correctness of the
|
|
56
|
+
* reduction.** Coverage claims that a throw cannot escape unrendered, so it holds
|
|
57
|
+
* only when EVERY path to the site renders it — a router mounted on a public
|
|
58
|
+
* server with a catch-all and an internal one without is covered on one path and
|
|
59
|
+
* bare on the other, and unioning the two reported it as fully covered while the
|
|
60
|
+
* internal server answered with the built-in envelope. A resource with no
|
|
61
|
+
* encloser contributes nothing rather than everything: the empty set is the
|
|
62
|
+
* identity for the site's own coverage, and treating "no paths" as "all paths
|
|
63
|
+
* agree" would assert coverage no list provides.
|
|
64
|
+
*
|
|
65
|
+
* A resource's OWN scope list is unioned in, because it applies on every path.
|
|
66
|
+
*
|
|
67
|
+
* Memoized, and a cycle among `throwsThrough` edges resolves to what has been
|
|
68
|
+
* accumulated so far rather than raising: this answers what a scope renders, and
|
|
69
|
+
* no cycle makes that answer larger.
|
|
70
|
+
*/
|
|
71
|
+
export declare function enclosingCoverage(manifest: ResourceManifest, ownScope: Map<ResourceManifest, ProvenCoverage>, enclosers: Map<ResourceManifest, ResourceManifest[]>, memo?: Map<ResourceManifest, ProvenCoverage>, walking?: Set<ResourceManifest>): ProvenCoverage;
|
|
72
|
+
//# sourceMappingURL=catch-scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catch-scope.d.ts","sourceRoot":"","sources":["../src/catch-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEhF;;;;;;;kFAOkF;AAClF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,WAAW,EAAE,cAAyD,CAAC;AAEpF;;;qEAGqE;AACrE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,gBAAgB,CAAC;IACxB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;6EAK6E;AAC7E,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC3C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GACvB,cAAc,EAAE,CAgBlB;AAED;;kEAEkE;AAClE,wBAAgB,cAAc,CAC5B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,YAAY,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,GAAG,SAAS,EACnF,QAAQ,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,MAAM,GAAG,SAAS,EACrD,GAAG,EAAE,UAAU,GACd,GAAG,CAAC,gBAAgB,EAAE,gBAAgB,EAAE,CAAC,CAe3C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,GAAG,CAAC,gBAAgB,EAAE,cAAc,CAAC,EAC/C,SAAS,EAAE,GAAG,CAAC,gBAAgB,EAAE,gBAAgB,EAAE,CAAC,EACpD,IAAI,GAAE,GAAG,CAAC,gBAAgB,EAAE,cAAc,CAAa,EACvD,OAAO,GAAE,GAAG,CAAC,gBAAgB,CAAa,GACzC,cAAc,CAgChB"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { visitManifest } from "./manifest-visitor.js";
|
|
2
|
+
import { forEachDrivenSlot } from "./schema-walk.js";
|
|
3
|
+
import { resolveRefManifest } from "./resolve-throws-union.js";
|
|
4
|
+
export const NO_COVERAGE = { codes: new Set(), hasCatchAll: false };
|
|
5
|
+
/** Every `with:`-scoped declaration in the set, once each.
|
|
6
|
+
*
|
|
7
|
+
* Scoped resources are absent from the flat manifest list, so every check that
|
|
8
|
+
* iterates it skips them — and standing a server up around a test is exactly
|
|
9
|
+
* that shape, so the sanctioned pattern was the one the pass could not see.
|
|
10
|
+
* Discovered through the shared visitor rather than a second scope walk. */
|
|
11
|
+
export function collectScopedManifests(manifests, defs, aliases, aliasesByModule, rootModules) {
|
|
12
|
+
const scoped = [];
|
|
13
|
+
visitManifest(manifests, defs, {
|
|
14
|
+
onScope: (event) => {
|
|
15
|
+
for (const { manifest, path } of event.declarations) {
|
|
16
|
+
if (!manifest?.kind || !manifest.metadata?.name)
|
|
17
|
+
continue;
|
|
18
|
+
scoped.push({ manifest, owner: event.source, path });
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
}, { aliases, aliasesByModule, rootModules });
|
|
22
|
+
return scoped;
|
|
23
|
+
}
|
|
24
|
+
/** Which resources each resource's scope list encloses, along the slots that
|
|
25
|
+
* declare `throwsThrough` — one fact stated once, since the edge a throws
|
|
26
|
+
* closure crosses is the edge a catch scope encloses through. */
|
|
27
|
+
export function buildEnclosers(manifests, definitionOf, moduleOf, ctx) {
|
|
28
|
+
const enclosers = new Map();
|
|
29
|
+
for (const manifest of manifests) {
|
|
30
|
+
const definition = definitionOf(manifest);
|
|
31
|
+
if (!definition?.schema)
|
|
32
|
+
continue;
|
|
33
|
+
forEachDrivenSlot(definition.schema, manifest, (driven) => {
|
|
34
|
+
if (driven.kind !== "ref" || !driven.slot.throwsThrough)
|
|
35
|
+
return;
|
|
36
|
+
const target = resolveRefManifest(driven.data, ctx, moduleOf(manifest));
|
|
37
|
+
if (!target || target === manifest)
|
|
38
|
+
return;
|
|
39
|
+
const list = enclosers.get(target);
|
|
40
|
+
if (list)
|
|
41
|
+
list.push(manifest);
|
|
42
|
+
else
|
|
43
|
+
enclosers.set(target, [manifest]);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return enclosers;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* What every scope enclosing a resource is guaranteed to render for it.
|
|
50
|
+
*
|
|
51
|
+
* **Across enclosers this INTERSECTS, and that is the whole correctness of the
|
|
52
|
+
* reduction.** Coverage claims that a throw cannot escape unrendered, so it holds
|
|
53
|
+
* only when EVERY path to the site renders it — a router mounted on a public
|
|
54
|
+
* server with a catch-all and an internal one without is covered on one path and
|
|
55
|
+
* bare on the other, and unioning the two reported it as fully covered while the
|
|
56
|
+
* internal server answered with the built-in envelope. A resource with no
|
|
57
|
+
* encloser contributes nothing rather than everything: the empty set is the
|
|
58
|
+
* identity for the site's own coverage, and treating "no paths" as "all paths
|
|
59
|
+
* agree" would assert coverage no list provides.
|
|
60
|
+
*
|
|
61
|
+
* A resource's OWN scope list is unioned in, because it applies on every path.
|
|
62
|
+
*
|
|
63
|
+
* Memoized, and a cycle among `throwsThrough` edges resolves to what has been
|
|
64
|
+
* accumulated so far rather than raising: this answers what a scope renders, and
|
|
65
|
+
* no cycle makes that answer larger.
|
|
66
|
+
*/
|
|
67
|
+
export function enclosingCoverage(manifest, ownScope, enclosers, memo = new Map(), walking = new Set()) {
|
|
68
|
+
const cached = memo.get(manifest);
|
|
69
|
+
if (cached)
|
|
70
|
+
return cached;
|
|
71
|
+
if (walking.has(manifest))
|
|
72
|
+
return NO_COVERAGE;
|
|
73
|
+
walking.add(manifest);
|
|
74
|
+
try {
|
|
75
|
+
const own = ownScope.get(manifest);
|
|
76
|
+
const result = {
|
|
77
|
+
codes: new Set(own?.codes ?? []),
|
|
78
|
+
hasCatchAll: own?.hasCatchAll ?? false,
|
|
79
|
+
};
|
|
80
|
+
const outer = (enclosers.get(manifest) ?? []).map((e) => enclosingCoverage(e, ownScope, enclosers, memo, walking));
|
|
81
|
+
if (outer.length > 0) {
|
|
82
|
+
const [first, ...rest] = outer;
|
|
83
|
+
const shared = new Set(first.codes);
|
|
84
|
+
let everyCatchAll = first.hasCatchAll;
|
|
85
|
+
for (const other of rest) {
|
|
86
|
+
for (const code of [...shared])
|
|
87
|
+
if (!other.codes.has(code))
|
|
88
|
+
shared.delete(code);
|
|
89
|
+
everyCatchAll &&= other.hasCatchAll;
|
|
90
|
+
}
|
|
91
|
+
for (const code of shared)
|
|
92
|
+
result.codes.add(code);
|
|
93
|
+
if (everyCatchAll)
|
|
94
|
+
result.hasCatchAll = true;
|
|
95
|
+
}
|
|
96
|
+
memo.set(manifest, result);
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
walking.delete(manifest);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cel-scope-query.d.ts","sourceRoot":"","sources":["../src/cel-scope-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAqB,MAAM,qBAAqB,CAAC;AAEvE,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"cel-scope-query.d.ts","sourceRoot":"","sources":["../src/cel-scope-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAqB,MAAM,qBAAqB,CAAC;AAEvE,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAmB9D;;;sCAGsC;AACtC,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;;iEAI6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoBD;gEACgE;AAChE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,qBAAa,aAAa;IActB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAb5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C;;oDAEgD;IAChD,OAAO,CAAC,OAAO,CAA+B;IAC9C;;;;sEAIkE;IAClE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsD;gBAG9D,SAAS,EAAE,gBAAgB,EAAE,EAC9C,GAAG,EAAE,oBAAoB,EACzB,MAAM,CAAC,EAAE,WAAW;IAgCtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAuB;IAE3C;+DAC2D;IAC3D,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS;IAI7F;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ;IAe3D;;;;;;;;;;;;;;;;;;OAkBG;IACH,sBAAsB,CACpB,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EAAE,GACd,sBAAsB,GAAG,SAAS;IA0CrC;oFACgF;IAChF,OAAO,CAAC,QAAQ;IAkGhB;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAqBrF;;;;;;;sDAOkD;IAClD,OAAO,CAAC,aAAa;IAOrB;wEACoE;IACpE,OAAO,CAAC,YAAY;CAarB"}
|
package/dist/cel-scope-query.js
CHANGED
|
@@ -4,6 +4,7 @@ import { buildKernelGlobalsIndex } from "./kernel-globals.js";
|
|
|
4
4
|
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
5
5
|
import { isModuleKind } from "./module-kinds.js";
|
|
6
6
|
import { navigateConcretePath } from "./manifest-path.js";
|
|
7
|
+
import { kindAtPath } from "./validate-cel-context.js";
|
|
7
8
|
import { findManifest } from "./find-manifest.js";
|
|
8
9
|
import { resolveLocalRef, walkStepArray } from "./schema-walk.js";
|
|
9
10
|
import { readStepSlot } from "./step-slot.js";
|
|
@@ -200,16 +201,30 @@ export class CelScopeQuery {
|
|
|
200
201
|
return { manifest: target, path: refFrom.slice(slash + 1).split("/").join("."), propertyMap: false };
|
|
201
202
|
}
|
|
202
203
|
// A kind's own declaration: the target is the `Telo.Definition` document,
|
|
203
|
-
// which is an ordinary manifest in the set.
|
|
204
|
+
// which is an ordinary manifest in the set. The slot holds a `!ref` to a
|
|
205
|
+
// `resources:` entry; the entry's own field wins where it narrows one, else
|
|
206
|
+
// the declaration is the entry's KIND. The first slot that resolves to an
|
|
207
|
+
// entry is the one the type resolver reads, so it is the one navigated.
|
|
204
208
|
const fromRefKind = annotated["x-telo-context-from-ref-kind"];
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
209
|
+
const slots = Array.isArray(fromRefKind) ? fromRefKind : [fromRefKind];
|
|
210
|
+
for (const slotSpec of slots) {
|
|
211
|
+
if (typeof slotSpec !== "string")
|
|
212
|
+
continue;
|
|
213
|
+
const hash = slotSpec.indexOf("#");
|
|
214
|
+
// `continue`, never `return`: a malformed spec is one entry of a list the
|
|
215
|
+
// type resolver walks to the end, and aborting here made the two answer
|
|
216
|
+
// differently for the same annotation.
|
|
208
217
|
if (hash <= 0)
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
218
|
+
continue;
|
|
219
|
+
const field = slotSpec.slice(hash + 1);
|
|
220
|
+
const namedKind = kindAtPath(root, slotSpec.slice(0, hash));
|
|
221
|
+
if (!namedKind)
|
|
222
|
+
continue;
|
|
223
|
+
if (namedKind.entry?.[field] !== undefined) {
|
|
224
|
+
const index = root.resources.indexOf(namedKind.entry);
|
|
225
|
+
return { manifest: resource, path: `resources[${index}].${field}`, propertyMap: false };
|
|
226
|
+
}
|
|
227
|
+
const kindValue = namedKind.kind;
|
|
213
228
|
// The kind was read off THIS resource, so it is spelled in the alias scope
|
|
214
229
|
// of the module that declared it — not the entry's.
|
|
215
230
|
const scope = moduleAliasScope(resource.metadata, this.ctx.aliases, this.ctx.aliasesByModule);
|
|
@@ -229,10 +244,16 @@ export class CelScopeQuery {
|
|
|
229
244
|
const target = (owningModule
|
|
230
245
|
? named.find((m) => m.metadata?.module === owningModule)
|
|
231
246
|
: undefined) ?? (owningModule && named.length > 1 ? undefined : named[0]);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
247
|
+
// Fall through to the next spec when this one names no kind, or names one
|
|
248
|
+
// that does not declare the field — the same order the TYPE resolver
|
|
249
|
+
// takes, so go-to-declaration lands where the type came from instead of on
|
|
250
|
+
// a field the target never had.
|
|
251
|
+
if (!target || target[field] === undefined)
|
|
252
|
+
continue;
|
|
253
|
+
return { manifest: target, path: field, propertyMap: false };
|
|
235
254
|
}
|
|
255
|
+
if (slots.some((s) => typeof s === "string"))
|
|
256
|
+
return undefined;
|
|
236
257
|
// `x-telo-context-element-from` / `-collection-from` type a binding from an
|
|
237
258
|
// EXPRESSION, so there is no declaration to navigate to.
|
|
238
259
|
return undefined;
|