@telorun/analyzer 0.63.0 → 0.65.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 +24 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +35 -0
- package/dist/analyzer.d.ts +3 -37
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +76 -470
- package/dist/cel-scope-query.d.ts +109 -0
- package/dist/cel-scope-query.d.ts.map +1 -0
- package/dist/cel-scope-query.js +270 -0
- package/dist/cel-scope.d.ts +166 -0
- package/dist/cel-scope.d.ts.map +1 -0
- package/dist/cel-scope.js +377 -0
- package/dist/definition-registry.d.ts +38 -6
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +66 -22
- package/dist/find-manifest.d.ts +10 -0
- package/dist/find-manifest.d.ts.map +1 -0
- package/dist/find-manifest.js +12 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/invocation-contract.d.ts +11 -0
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +15 -0
- package/dist/manifest-analysis.d.ts +73 -0
- package/dist/manifest-analysis.d.ts.map +1 -0
- package/dist/manifest-analysis.js +78 -0
- package/dist/manifest-path.d.ts +18 -0
- package/dist/manifest-path.d.ts.map +1 -0
- package/dist/manifest-path.js +37 -0
- package/dist/schema-compat.d.ts +59 -22
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +60 -75
- package/dist/schema-error-report.d.ts +68 -0
- package/dist/schema-error-report.d.ts.map +1 -0
- package/dist/schema-error-report.js +356 -0
- package/dist/schema-walk.d.ts +25 -0
- package/dist/schema-walk.d.ts.map +1 -0
- package/dist/schema-walk.js +126 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/validate-nested-inline.d.ts +22 -1
- package/dist/validate-nested-inline.d.ts.map +1 -1
- package/dist/validate-nested-inline.js +17 -9
- package/dist/validate-step-inputs.d.ts +17 -0
- package/dist/validate-step-inputs.d.ts.map +1 -1
- package/dist/validate-step-inputs.js +108 -9
- package/package.json +2 -2
- package/src/analysis-registry.ts +37 -0
- package/src/analyzer.ts +83 -587
- package/src/cel-scope-query.ts +337 -0
- package/src/cel-scope.ts +570 -0
- package/src/definition-registry.ts +79 -24
- package/src/find-manifest.ts +19 -0
- package/src/index.ts +23 -2
- package/src/invocation-contract.ts +22 -0
- package/src/manifest-analysis.ts +132 -0
- package/src/manifest-path.ts +34 -0
- package/src/schema-compat.ts +92 -79
- package/src/schema-error-report.ts +417 -0
- package/src/schema-walk.ts +144 -0
- package/src/telo-version.ts +1 -1
- package/src/validate-nested-inline.ts +35 -14
- package/src/validate-step-inputs.ts +153 -11
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **Ask what a CEL site sees, from outside the analysis pass.**
|
|
3
|
+
*
|
|
4
|
+
* The pass resolves a scope per expression as it walks. An IDE has no walk: it
|
|
5
|
+
* has a cursor, and needs the same answer for one address — often for an
|
|
6
|
+
* expression the last analysis never saw, because the user is typing it. So the
|
|
7
|
+
* query is driven by (manifest, path) rather than by a visitor event, and the
|
|
8
|
+
* `x-telo-context` match is recomputed here exactly as the visitor computes it
|
|
9
|
+
* (`extractContextsFromSchema` + `pathMatchesScope`, the same two functions).
|
|
10
|
+
*
|
|
11
|
+
* The scope RULE itself is not re-implemented — {@link CelScopeResolver} is the
|
|
12
|
+
* one that answers, here and in the pass. What this module adds is the way in.
|
|
13
|
+
*/
|
|
14
|
+
import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
|
|
15
|
+
import type { Environment } from "@marcbachmann/cel-js";
|
|
16
|
+
import { AliasResolver, type ModuleScopes } from "./alias-resolver.js";
|
|
17
|
+
import { buildCelEnvironment } from "./cel-environment.js";
|
|
18
|
+
import { CelScopeResolver, type CelScope } from "./cel-scope.js";
|
|
19
|
+
import { DefinitionRegistry } from "./definition-registry.js";
|
|
20
|
+
import { buildKernelGlobalsIndex } from "./kernel-globals.js";
|
|
21
|
+
import { isModuleKind } from "./module-kinds.js";
|
|
22
|
+
import { navigateConcretePath } from "./manifest-path.js";
|
|
23
|
+
import { findManifest } from "./find-manifest.js";
|
|
24
|
+
import { resolveLocalRef, walkStepArray } from "./schema-walk.js";
|
|
25
|
+
import { readStepSlot } from "./step-slot.js";
|
|
26
|
+
import {
|
|
27
|
+
buildObservedStateIndex,
|
|
28
|
+
buildObservedStateResourcesSchema,
|
|
29
|
+
} from "./validate-observed-state.js";
|
|
30
|
+
import {
|
|
31
|
+
extractContextsFromSchema,
|
|
32
|
+
getManifestItem,
|
|
33
|
+
pathMatchesScope,
|
|
34
|
+
} from "./validate-cel-context.js";
|
|
35
|
+
|
|
36
|
+
/** Where a CEL context binding was declared: the manifest that declares it,
|
|
37
|
+
* by identity, plus the concrete path within it. Identity rather than the
|
|
38
|
+
* object because a host locates a manifest in its own loaded files, which is
|
|
39
|
+
* what carries the source ranges. */
|
|
40
|
+
export interface ContextDeclarationSite {
|
|
41
|
+
kind: string;
|
|
42
|
+
name: string;
|
|
43
|
+
path: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Join a concrete path segment, tolerating an empty base (the manifest root). */
|
|
47
|
+
function joinPath(base: string, segment: string): string {
|
|
48
|
+
return base ? `${base}.${segment}` : segment;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The concrete path of the array ITEM an `x-telo-context` scope matched — the
|
|
52
|
+
* path half of `getManifestItem`, which returns only the value. Empty when the
|
|
53
|
+
* scope is not per-item. */
|
|
54
|
+
function manifestItemPath(exprPath: string, scope: string | undefined): string {
|
|
55
|
+
if (!scope) return "";
|
|
56
|
+
const stripped = scope.startsWith("$.") ? scope.slice(2) : scope;
|
|
57
|
+
const wildcard = stripped.indexOf("[*]");
|
|
58
|
+
if (wildcard === -1) return "";
|
|
59
|
+
const arrayProp = stripped.slice(0, wildcard);
|
|
60
|
+
const match = exprPath.match(new RegExp(`^${arrayProp}\\[(\\d+)\\]`));
|
|
61
|
+
return match ? `${arrayProp}[${match[1]}]` : "";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The analyzer state a query resolves against — what an `AnalysisRegistry`
|
|
65
|
+
* already holds, plus the manifest set the caller analyzed. */
|
|
66
|
+
export interface CelScopeQueryContext {
|
|
67
|
+
defs: DefinitionRegistry;
|
|
68
|
+
aliases: AliasResolver;
|
|
69
|
+
aliasesByModule: Map<string, AliasResolver>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A reusable scope query over one manifest set.
|
|
74
|
+
*
|
|
75
|
+
* Built once per analysis rather than per keystroke: the observed-state index
|
|
76
|
+
* and the kernel globals are a function of the whole set, and rebuilding them
|
|
77
|
+
* for every cursor move would put a full-set walk on the hover path.
|
|
78
|
+
*/
|
|
79
|
+
export class CelScopeQuery {
|
|
80
|
+
private readonly resolver: CelScopeResolver;
|
|
81
|
+
/** The resource the resolver is currently entered on — `enterResource` is the
|
|
82
|
+
* per-resource half of the rule, so re-entering per query is only needed when
|
|
83
|
+
* the cursor moves to a different resource. */
|
|
84
|
+
private entered: ResourceManifest | undefined;
|
|
85
|
+
/** Resolved scopes, keyed by resource and path. A context-matched site builds
|
|
86
|
+
* a fresh typed environment by design (a clone plus a re-registration of
|
|
87
|
+
* every variable), which is affordable once per site in a batch pass and not
|
|
88
|
+
* once per site per KEYSTROKE — which is what a whole-file colourizer asks
|
|
89
|
+
* for. The lifetime is this query's, which is the analysis's. */
|
|
90
|
+
private readonly scopeCache = new Map<ResourceManifest, Map<string, CelScope>>();
|
|
91
|
+
|
|
92
|
+
constructor(
|
|
93
|
+
private readonly manifests: ResourceManifest[],
|
|
94
|
+
ctx: CelScopeQueryContext,
|
|
95
|
+
celEnv?: Environment,
|
|
96
|
+
) {
|
|
97
|
+
const { defs, aliases, aliasesByModule } = ctx;
|
|
98
|
+
const rootModules = new Set<string>();
|
|
99
|
+
for (const m of manifests) {
|
|
100
|
+
if (isModuleKind(m.kind) && m.metadata?.name) rootModules.add(m.metadata.name as string);
|
|
101
|
+
}
|
|
102
|
+
const scopes: ModuleScopes = { aliasesByModule, rootModules };
|
|
103
|
+
const observedState = buildObservedStateIndex(manifests, defs, aliases, scopes);
|
|
104
|
+
const reportsObservedState = [...observedState.values()].some((r) => r.status);
|
|
105
|
+
|
|
106
|
+
this.resolver = new CelScopeResolver({
|
|
107
|
+
celEnv: celEnv ?? buildCelEnvironment(),
|
|
108
|
+
defs,
|
|
109
|
+
aliases,
|
|
110
|
+
scopes,
|
|
111
|
+
allManifests: manifests,
|
|
112
|
+
kernelGlobals: buildKernelGlobalsIndex(manifests, observedState),
|
|
113
|
+
moduleManifest:
|
|
114
|
+
manifests.find((mm) => mm.kind === "Telo.Application") ??
|
|
115
|
+
manifests.find((mm) => mm.kind === "Telo.Library"),
|
|
116
|
+
observedStateContext: reportsObservedState
|
|
117
|
+
? {
|
|
118
|
+
type: "object",
|
|
119
|
+
additionalProperties: true,
|
|
120
|
+
properties: { resources: buildObservedStateResourcesSchema(observedState, true) },
|
|
121
|
+
}
|
|
122
|
+
: null,
|
|
123
|
+
});
|
|
124
|
+
this.ctx = ctx;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private readonly ctx: CelScopeQueryContext;
|
|
128
|
+
|
|
129
|
+
/** The manifest a cursor's document addresses — {@link findManifest}, the one
|
|
130
|
+
* implementation `ManifestAnalysis` also answers from. */
|
|
131
|
+
resourceFor(kind: string | undefined, name: string | undefined): ResourceManifest | undefined {
|
|
132
|
+
return findManifest(this.manifests, kind, name);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* What CEL at `path` in `resource` is typed against.
|
|
137
|
+
*
|
|
138
|
+
* `path` is the CONCRETE path, indices and all (`routes[0].handler.url`) —
|
|
139
|
+
* an `x-telo-context` region, an error-bearing branch and a step's identity
|
|
140
|
+
* are each addressed per item, so an index-erased path resolves the wrong
|
|
141
|
+
* scope or none.
|
|
142
|
+
*/
|
|
143
|
+
scopeAt(resource: ResourceManifest, path: string): CelScope {
|
|
144
|
+
let byPath = this.scopeCache.get(resource);
|
|
145
|
+
if (!byPath) this.scopeCache.set(resource, (byPath = new Map()));
|
|
146
|
+
const cached = byPath.get(path);
|
|
147
|
+
if (cached) return cached;
|
|
148
|
+
if (this.entered !== resource) {
|
|
149
|
+
this.resolver.enterResource(resource, this.definitionFor(resource));
|
|
150
|
+
this.entered = resource;
|
|
151
|
+
}
|
|
152
|
+
const { contextSchema, matchedScope } = this.matchContext(resource, path);
|
|
153
|
+
const scope = this.resolver.scopeFor({ source: resource, path, contextSchema, matchedScope });
|
|
154
|
+
byPath.set(path, scope);
|
|
155
|
+
return scope;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Where a CEL context binding was DECLARED — the manifest node the
|
|
160
|
+
* `x-telo-context-*` annotation derived it from.
|
|
161
|
+
*
|
|
162
|
+
* The mirror of {@link scopeAt}: that one resolves the annotation into a
|
|
163
|
+
* schema and the provenance is gone by the time it returns, while
|
|
164
|
+
* go-to-declaration wants the path and not the schema. Re-walking the
|
|
165
|
+
* annotation is what keeps `CelScope` free of an origin field every type
|
|
166
|
+
* consumer would have to ignore.
|
|
167
|
+
*
|
|
168
|
+
* Generic over the annotation, so `request.query` lands on the route's own
|
|
169
|
+
* `request.schema.query`, `self.<field>` on the definition's `schema`, and
|
|
170
|
+
* `result.<field>` on the INVOKED resource's `outputType` — one walk, no
|
|
171
|
+
* transport and no resource kind named here.
|
|
172
|
+
*
|
|
173
|
+
* Every candidate path is checked against the manifest before it is returned,
|
|
174
|
+
* so a binding the author never declared (a context annotation's static
|
|
175
|
+
* fallback properties) resolves to nothing rather than to a guessed node.
|
|
176
|
+
*/
|
|
177
|
+
contextDeclarationSite(
|
|
178
|
+
resource: ResourceManifest,
|
|
179
|
+
sitePath: string,
|
|
180
|
+
parts: string[],
|
|
181
|
+
): ContextDeclarationSite | undefined {
|
|
182
|
+
if (parts.length < 2) return undefined;
|
|
183
|
+
const { contextSchema, matchedScope } = this.matchContext(resource, sitePath);
|
|
184
|
+
const annotated = contextSchema?.properties?.[parts[0]] as Record<string, any> | undefined;
|
|
185
|
+
if (!annotated) return undefined;
|
|
186
|
+
|
|
187
|
+
const origin = this.originOf(annotated, resource, sitePath, matchedScope);
|
|
188
|
+
if (!origin) return undefined;
|
|
189
|
+
|
|
190
|
+
// The ORIGIN decides the shape rather than positional fallthrough. A
|
|
191
|
+
// property-map origin holds the names themselves, so its first hop is direct
|
|
192
|
+
// and ONLY direct — trying `properties` there would let an author's own
|
|
193
|
+
// `properties:` key win over the name they wrote. Every deeper hop, and
|
|
194
|
+
// every hop of a JSON-Schema origin, goes through `properties`, with the
|
|
195
|
+
// inline `{ kind, schema }` wrapper as the one alternative a type field is
|
|
196
|
+
// routinely written as.
|
|
197
|
+
let base = origin.path;
|
|
198
|
+
for (let i = 1; i < parts.length; i++) {
|
|
199
|
+
const candidates =
|
|
200
|
+
i === 1 && origin.propertyMap
|
|
201
|
+
? [joinPath(base, parts[i])]
|
|
202
|
+
: [
|
|
203
|
+
joinPath(joinPath(base, "properties"), parts[i]),
|
|
204
|
+
joinPath(joinPath(joinPath(base, "schema"), "properties"), parts[i]),
|
|
205
|
+
];
|
|
206
|
+
const hit = candidates.find(
|
|
207
|
+
(candidate) => navigateConcretePath(origin.manifest, candidate) !== undefined,
|
|
208
|
+
);
|
|
209
|
+
if (!hit) return undefined;
|
|
210
|
+
base = hit;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const metadata = origin.manifest.metadata as { name?: string } | undefined;
|
|
214
|
+
if (!origin.manifest.kind || !metadata?.name) return undefined;
|
|
215
|
+
return { kind: origin.manifest.kind, name: metadata.name, path: base };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The manifest and path an annotated context property is derived from, and
|
|
219
|
+
* whether that node holds a property MAP (names directly) or a JSON Schema. */
|
|
220
|
+
private originOf(
|
|
221
|
+
annotated: Record<string, any>,
|
|
222
|
+
resource: ResourceManifest,
|
|
223
|
+
sitePath: string,
|
|
224
|
+
matchedScope: string | undefined,
|
|
225
|
+
): { manifest: Record<string, any>; path: string; propertyMap: boolean } | undefined {
|
|
226
|
+
const root = resource as Record<string, any>;
|
|
227
|
+
|
|
228
|
+
// Per-scope: the annotation navigates the enclosing ARRAY ITEM, so the path
|
|
229
|
+
// it yields is relative to that item rather than to the resource.
|
|
230
|
+
const from = annotated["x-telo-context-from"];
|
|
231
|
+
if (typeof from === "string") {
|
|
232
|
+
const itemPath = manifestItemPath(sitePath, matchedScope);
|
|
233
|
+
return { manifest: root, path: joinPath(itemPath, from.split("/").join(".")), propertyMap: true };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const fromRoot = annotated["x-telo-context-from-root"];
|
|
237
|
+
if (typeof fromRoot === "string") {
|
|
238
|
+
return { manifest: root, path: fromRoot.split("/").join("."), propertyMap: false };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Cross-manifest: the binding is declared by whatever this slot REFERENCES,
|
|
242
|
+
// which is the node a reader wants when a result's members do not resolve.
|
|
243
|
+
const refFrom = annotated["x-telo-context-ref-from"];
|
|
244
|
+
if (typeof refFrom === "string") {
|
|
245
|
+
const slash = refFrom.indexOf("/");
|
|
246
|
+
if (slash === -1) return undefined;
|
|
247
|
+
const item = matchedScope
|
|
248
|
+
? getManifestItem(sitePath, matchedScope, root)
|
|
249
|
+
: root;
|
|
250
|
+
const ref = item[refFrom.slice(0, slash)] as { kind?: string; name?: string } | undefined;
|
|
251
|
+
if (!ref?.kind || !ref.name) return undefined;
|
|
252
|
+
const target = this.manifests.find(
|
|
253
|
+
(m) => m.kind === ref.kind && (m.metadata as { name?: string } | undefined)?.name === ref.name,
|
|
254
|
+
) as Record<string, any> | undefined;
|
|
255
|
+
if (!target) return undefined;
|
|
256
|
+
return { manifest: target, path: refFrom.slice(slash + 1).split("/").join("."), propertyMap: false };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// A kind's own declaration: the target is the `Telo.Definition` document,
|
|
260
|
+
// which is an ordinary manifest in the set.
|
|
261
|
+
const fromRefKind = annotated["x-telo-context-from-ref-kind"];
|
|
262
|
+
const first = Array.isArray(fromRefKind) ? fromRefKind[0] : fromRefKind;
|
|
263
|
+
if (typeof first === "string") {
|
|
264
|
+
const hash = first.indexOf("#");
|
|
265
|
+
if (hash <= 0) return undefined;
|
|
266
|
+
const kindValue = navigateConcretePath(root, first.slice(0, hash).split("/").join("."));
|
|
267
|
+
if (typeof kindValue !== "string") return undefined;
|
|
268
|
+
const canonical = this.ctx.aliases.resolveKind(kindValue) ?? kindValue;
|
|
269
|
+
const suffix = canonical.slice(canonical.indexOf(".") + 1);
|
|
270
|
+
const target = this.manifests.find(
|
|
271
|
+
(m) =>
|
|
272
|
+
(m.kind === "Telo.Definition" || m.kind === "Telo.Abstract") &&
|
|
273
|
+
(m.metadata as { name?: string } | undefined)?.name === suffix,
|
|
274
|
+
) as Record<string, any> | undefined;
|
|
275
|
+
if (!target) return undefined;
|
|
276
|
+
return { manifest: target, path: first.slice(hash + 1), propertyMap: false };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// `x-telo-context-element-from` / `-collection-from` type a binding from an
|
|
280
|
+
// EXPRESSION, so there is no declaration to navigate to.
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Where the step named `stepName` is declared in `resource`, as a concrete
|
|
286
|
+
* path (`steps[2]`), or undefined when the resource declares no step body or
|
|
287
|
+
* holds no such step.
|
|
288
|
+
*
|
|
289
|
+
* For go-to-declaration on `steps.<name>.result`, which is the one CEL scope
|
|
290
|
+
* whose members ARE written somewhere in the manifest but are reached through
|
|
291
|
+
* no reference slot. Driven by the kind's own step-body annotation and the
|
|
292
|
+
* shared nesting walk, so a step inside a `try:` inside a `catch:` is found
|
|
293
|
+
* and no resource kind is named here.
|
|
294
|
+
*/
|
|
295
|
+
stepDeclarationPath(resource: ResourceManifest, stepName: string): string | undefined {
|
|
296
|
+
const schema = this.definitionFor(resource)?.schema as Record<string, any> | undefined;
|
|
297
|
+
const props = schema?.properties as Record<string, any> | undefined;
|
|
298
|
+
if (!schema || !props) return undefined;
|
|
299
|
+
for (const [fieldName, fieldSchema] of Object.entries(props)) {
|
|
300
|
+
if (!readStepSlot(fieldSchema)) continue;
|
|
301
|
+
const steps = (resource as Record<string, any>)[fieldName];
|
|
302
|
+
if (!Array.isArray(steps)) continue;
|
|
303
|
+
const itemSchema = resolveLocalRef(
|
|
304
|
+
(fieldSchema as Record<string, any>).items as Record<string, any> | undefined,
|
|
305
|
+
schema,
|
|
306
|
+
);
|
|
307
|
+
let found: string | undefined;
|
|
308
|
+
walkStepArray(steps, itemSchema, schema, fieldName, (step, stepPath) => {
|
|
309
|
+
if (found === undefined && step.name === stepName) found = stepPath;
|
|
310
|
+
});
|
|
311
|
+
if (found) return found;
|
|
312
|
+
}
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private definitionFor(resource: ResourceManifest): ResourceDefinition | undefined {
|
|
317
|
+
const { defs, aliases } = this.ctx;
|
|
318
|
+
const canonical = aliases.resolveKind(resource.kind);
|
|
319
|
+
return defs.resolve(resource.kind) ?? (canonical ? defs.resolve(canonical) : undefined);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** The `x-telo-context` region this path falls in, matched exactly as the
|
|
323
|
+
* manifest visitor matches it for an expression it walked onto. */
|
|
324
|
+
private matchContext(
|
|
325
|
+
resource: ResourceManifest,
|
|
326
|
+
path: string,
|
|
327
|
+
): { contextSchema?: Record<string, any>; matchedScope?: string } {
|
|
328
|
+
const schema = this.definitionFor(resource)?.schema as Record<string, any> | undefined;
|
|
329
|
+
if (!schema) return {};
|
|
330
|
+
for (const ctx of extractContextsFromSchema(schema)) {
|
|
331
|
+
if (pathMatchesScope(path, ctx.scope)) {
|
|
332
|
+
return { contextSchema: ctx.schema, matchedScope: ctx.scope };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return {};
|
|
336
|
+
}
|
|
337
|
+
}
|