@json-schema-engine/core 0.0.1
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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/coverage.d.ts +30 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +81 -0
- package/dist/coverage.js.map +1 -0
- package/dist/cursor.d.ts +16 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +28 -0
- package/dist/cursor.js.map +1 -0
- package/dist/dialect.d.ts +316 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +135 -0
- package/dist/dialect.js.map +1 -0
- package/dist/engine.d.ts +203 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +493 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +370 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +49 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +128 -0
- package/dist/json.js.map +1 -0
- package/dist/keywords/applicator.d.ts +57 -0
- package/dist/keywords/applicator.d.ts.map +1 -0
- package/dist/keywords/applicator.js +789 -0
- package/dist/keywords/applicator.js.map +1 -0
- package/dist/keywords/core.d.ts +42 -0
- package/dist/keywords/core.d.ts.map +1 -0
- package/dist/keywords/core.js +139 -0
- package/dist/keywords/core.js.map +1 -0
- package/dist/keywords/format.d.ts +43 -0
- package/dist/keywords/format.d.ts.map +1 -0
- package/dist/keywords/format.js +101 -0
- package/dist/keywords/format.js.map +1 -0
- package/dist/keywords/metaschemas2019.d.ts +3 -0
- package/dist/keywords/metaschemas2019.d.ts.map +1 -0
- package/dist/keywords/metaschemas2019.js +406 -0
- package/dist/keywords/metaschemas2019.js.map +1 -0
- package/dist/keywords/metaschemas2020.d.ts +3 -0
- package/dist/keywords/metaschemas2020.d.ts.map +1 -0
- package/dist/keywords/metaschemas2020.js +441 -0
- package/dist/keywords/metaschemas2020.js.map +1 -0
- package/dist/keywords/metaschemas6.d.ts +3 -0
- package/dist/keywords/metaschemas6.d.ts.map +1 -0
- package/dist/keywords/metaschemas6.js +221 -0
- package/dist/keywords/metaschemas6.js.map +1 -0
- package/dist/keywords/metaschemas7.d.ts +3 -0
- package/dist/keywords/metaschemas7.d.ts.map +1 -0
- package/dist/keywords/metaschemas7.js +248 -0
- package/dist/keywords/metaschemas7.js.map +1 -0
- package/dist/keywords/unevaluated.d.ts +14 -0
- package/dist/keywords/unevaluated.d.ts.map +1 -0
- package/dist/keywords/unevaluated.js +296 -0
- package/dist/keywords/unevaluated.js.map +1 -0
- package/dist/keywords/validation.d.ts +21 -0
- package/dist/keywords/validation.d.ts.map +1 -0
- package/dist/keywords/validation.js +332 -0
- package/dist/keywords/validation.js.map +1 -0
- package/dist/keywords/vocab2019.d.ts +36 -0
- package/dist/keywords/vocab2019.d.ts.map +1 -0
- package/dist/keywords/vocab2019.js +585 -0
- package/dist/keywords/vocab2019.js.map +1 -0
- package/dist/keywords/vocab2020.d.ts +20 -0
- package/dist/keywords/vocab2020.d.ts.map +1 -0
- package/dist/keywords/vocab2020.js +55 -0
- package/dist/keywords/vocab2020.js.map +1 -0
- package/dist/keywords/vocab7.d.ts +46 -0
- package/dist/keywords/vocab7.d.ts.map +1 -0
- package/dist/keywords/vocab7.js +305 -0
- package/dist/keywords/vocab7.js.map +1 -0
- package/dist/loader.d.ts +41 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +13 -0
- package/dist/loader.js.map +1 -0
- package/dist/lowering.d.ts +371 -0
- package/dist/lowering.d.ts.map +1 -0
- package/dist/lowering.js +59 -0
- package/dist/lowering.js.map +1 -0
- package/dist/output.d.ts +251 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +357 -0
- package/dist/output.js.map +1 -0
- package/dist/records.d.ts +71 -0
- package/dist/records.d.ts.map +1 -0
- package/dist/records.js +150 -0
- package/dist/records.js.map +1 -0
- package/dist/ref.d.ts +10 -0
- package/dist/ref.d.ts.map +1 -0
- package/dist/ref.js +5 -0
- package/dist/ref.js.map +1 -0
- package/dist/regex.d.ts +45 -0
- package/dist/regex.d.ts.map +1 -0
- package/dist/regex.js +163 -0
- package/dist/regex.js.map +1 -0
- package/dist/registry.d.ts +141 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +407 -0
- package/dist/registry.js.map +1 -0
- package/dist/result.d.ts +109 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +112 -0
- package/dist/result.js.map +1 -0
- package/dist/uri.d.ts +18 -0
- package/dist/uri.d.ts.map +1 -0
- package/dist/uri.js +27 -0
- package/dist/uri.js.map +1 -0
- package/dist/walk.d.ts +25 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +65 -0
- package/dist/walk.js.map +1 -0
- package/package.json +32 -0
- package/src/coverage.ts +88 -0
- package/src/cursor.ts +42 -0
- package/src/dialect.ts +428 -0
- package/src/engine.ts +741 -0
- package/src/index.ts +699 -0
- package/src/json.ts +137 -0
- package/src/keywords/applicator.ts +895 -0
- package/src/keywords/core.ts +168 -0
- package/src/keywords/format.ts +143 -0
- package/src/keywords/metaschemas2019.ts +410 -0
- package/src/keywords/metaschemas2020.ts +443 -0
- package/src/keywords/metaschemas6.ts +226 -0
- package/src/keywords/metaschemas7.ts +253 -0
- package/src/keywords/unevaluated.ts +329 -0
- package/src/keywords/validation.ts +627 -0
- package/src/keywords/vocab2019.ts +676 -0
- package/src/keywords/vocab2020.ts +82 -0
- package/src/keywords/vocab7.ts +393 -0
- package/src/loader.ts +60 -0
- package/src/lowering.ts +414 -0
- package/src/output.ts +651 -0
- package/src/records.ts +226 -0
- package/src/ref.ts +14 -0
- package/src/regex.ts +171 -0
- package/src/registry.ts +481 -0
- package/src/result.ts +252 -0
- package/src/uri.ts +35 -0
- package/src/walk.ts +88 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// The draft-07 metaschema, vendored verbatim from
|
|
2
|
+
// https://json-schema.org/draft-07/schema (JSON Schema specification
|
|
3
|
+
// artifact, BSD-licensed). Same role as metaschemas2020.ts; the $id's empty
|
|
4
|
+
// fragment is stripped to the resource URI.
|
|
5
|
+
import { JsonValue } from "../json.js";
|
|
6
|
+
|
|
7
|
+
export const METASCHEMAS_DRAFT_07: readonly (readonly [string, JsonValue])[] = [
|
|
8
|
+
["http://json-schema.org/draft-07/schema", {
|
|
9
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
10
|
+
"$id": "http://json-schema.org/draft-07/schema#",
|
|
11
|
+
"title": "Core schema meta-schema",
|
|
12
|
+
"definitions": {
|
|
13
|
+
"schemaArray": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"minItems": 1,
|
|
16
|
+
"items": {
|
|
17
|
+
"$ref": "#"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"nonNegativeInteger": {
|
|
21
|
+
"type": "integer",
|
|
22
|
+
"minimum": 0
|
|
23
|
+
},
|
|
24
|
+
"nonNegativeIntegerDefault0": {
|
|
25
|
+
"allOf": [
|
|
26
|
+
{
|
|
27
|
+
"$ref": "#/definitions/nonNegativeInteger"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"default": 0
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"simpleTypes": {
|
|
35
|
+
"enum": [
|
|
36
|
+
"array",
|
|
37
|
+
"boolean",
|
|
38
|
+
"integer",
|
|
39
|
+
"null",
|
|
40
|
+
"number",
|
|
41
|
+
"object",
|
|
42
|
+
"string"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"stringArray": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": {
|
|
48
|
+
"type": "string"
|
|
49
|
+
},
|
|
50
|
+
"uniqueItems": true,
|
|
51
|
+
"default": []
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"type": [
|
|
55
|
+
"object",
|
|
56
|
+
"boolean"
|
|
57
|
+
],
|
|
58
|
+
"properties": {
|
|
59
|
+
"$id": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"format": "uri-reference"
|
|
62
|
+
},
|
|
63
|
+
"$schema": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"format": "uri"
|
|
66
|
+
},
|
|
67
|
+
"$ref": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"format": "uri-reference"
|
|
70
|
+
},
|
|
71
|
+
"$comment": {
|
|
72
|
+
"type": "string"
|
|
73
|
+
},
|
|
74
|
+
"title": {
|
|
75
|
+
"type": "string"
|
|
76
|
+
},
|
|
77
|
+
"description": {
|
|
78
|
+
"type": "string"
|
|
79
|
+
},
|
|
80
|
+
"default": true,
|
|
81
|
+
"readOnly": {
|
|
82
|
+
"type": "boolean",
|
|
83
|
+
"default": false
|
|
84
|
+
},
|
|
85
|
+
"writeOnly": {
|
|
86
|
+
"type": "boolean",
|
|
87
|
+
"default": false
|
|
88
|
+
},
|
|
89
|
+
"examples": {
|
|
90
|
+
"type": "array",
|
|
91
|
+
"items": true
|
|
92
|
+
},
|
|
93
|
+
"multipleOf": {
|
|
94
|
+
"type": "number",
|
|
95
|
+
"exclusiveMinimum": 0
|
|
96
|
+
},
|
|
97
|
+
"maximum": {
|
|
98
|
+
"type": "number"
|
|
99
|
+
},
|
|
100
|
+
"exclusiveMaximum": {
|
|
101
|
+
"type": "number"
|
|
102
|
+
},
|
|
103
|
+
"minimum": {
|
|
104
|
+
"type": "number"
|
|
105
|
+
},
|
|
106
|
+
"exclusiveMinimum": {
|
|
107
|
+
"type": "number"
|
|
108
|
+
},
|
|
109
|
+
"maxLength": {
|
|
110
|
+
"$ref": "#/definitions/nonNegativeInteger"
|
|
111
|
+
},
|
|
112
|
+
"minLength": {
|
|
113
|
+
"$ref": "#/definitions/nonNegativeIntegerDefault0"
|
|
114
|
+
},
|
|
115
|
+
"pattern": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"format": "regex"
|
|
118
|
+
},
|
|
119
|
+
"additionalItems": {
|
|
120
|
+
"$ref": "#"
|
|
121
|
+
},
|
|
122
|
+
"items": {
|
|
123
|
+
"anyOf": [
|
|
124
|
+
{
|
|
125
|
+
"$ref": "#"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"$ref": "#/definitions/schemaArray"
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
"default": true
|
|
132
|
+
},
|
|
133
|
+
"maxItems": {
|
|
134
|
+
"$ref": "#/definitions/nonNegativeInteger"
|
|
135
|
+
},
|
|
136
|
+
"minItems": {
|
|
137
|
+
"$ref": "#/definitions/nonNegativeIntegerDefault0"
|
|
138
|
+
},
|
|
139
|
+
"uniqueItems": {
|
|
140
|
+
"type": "boolean",
|
|
141
|
+
"default": false
|
|
142
|
+
},
|
|
143
|
+
"contains": {
|
|
144
|
+
"$ref": "#"
|
|
145
|
+
},
|
|
146
|
+
"maxProperties": {
|
|
147
|
+
"$ref": "#/definitions/nonNegativeInteger"
|
|
148
|
+
},
|
|
149
|
+
"minProperties": {
|
|
150
|
+
"$ref": "#/definitions/nonNegativeIntegerDefault0"
|
|
151
|
+
},
|
|
152
|
+
"required": {
|
|
153
|
+
"$ref": "#/definitions/stringArray"
|
|
154
|
+
},
|
|
155
|
+
"additionalProperties": {
|
|
156
|
+
"$ref": "#"
|
|
157
|
+
},
|
|
158
|
+
"definitions": {
|
|
159
|
+
"type": "object",
|
|
160
|
+
"additionalProperties": {
|
|
161
|
+
"$ref": "#"
|
|
162
|
+
},
|
|
163
|
+
"default": {}
|
|
164
|
+
},
|
|
165
|
+
"properties": {
|
|
166
|
+
"type": "object",
|
|
167
|
+
"additionalProperties": {
|
|
168
|
+
"$ref": "#"
|
|
169
|
+
},
|
|
170
|
+
"default": {}
|
|
171
|
+
},
|
|
172
|
+
"patternProperties": {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"additionalProperties": {
|
|
175
|
+
"$ref": "#"
|
|
176
|
+
},
|
|
177
|
+
"propertyNames": {
|
|
178
|
+
"format": "regex"
|
|
179
|
+
},
|
|
180
|
+
"default": {}
|
|
181
|
+
},
|
|
182
|
+
"dependencies": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"additionalProperties": {
|
|
185
|
+
"anyOf": [
|
|
186
|
+
{
|
|
187
|
+
"$ref": "#"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"$ref": "#/definitions/stringArray"
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
"propertyNames": {
|
|
196
|
+
"$ref": "#"
|
|
197
|
+
},
|
|
198
|
+
"const": true,
|
|
199
|
+
"enum": {
|
|
200
|
+
"type": "array",
|
|
201
|
+
"items": true,
|
|
202
|
+
"minItems": 1,
|
|
203
|
+
"uniqueItems": true
|
|
204
|
+
},
|
|
205
|
+
"type": {
|
|
206
|
+
"anyOf": [
|
|
207
|
+
{
|
|
208
|
+
"$ref": "#/definitions/simpleTypes"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"type": "array",
|
|
212
|
+
"items": {
|
|
213
|
+
"$ref": "#/definitions/simpleTypes"
|
|
214
|
+
},
|
|
215
|
+
"minItems": 1,
|
|
216
|
+
"uniqueItems": true
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
"format": {
|
|
221
|
+
"type": "string"
|
|
222
|
+
},
|
|
223
|
+
"contentMediaType": {
|
|
224
|
+
"type": "string"
|
|
225
|
+
},
|
|
226
|
+
"contentEncoding": {
|
|
227
|
+
"type": "string"
|
|
228
|
+
},
|
|
229
|
+
"if": {
|
|
230
|
+
"$ref": "#"
|
|
231
|
+
},
|
|
232
|
+
"then": {
|
|
233
|
+
"$ref": "#"
|
|
234
|
+
},
|
|
235
|
+
"else": {
|
|
236
|
+
"$ref": "#"
|
|
237
|
+
},
|
|
238
|
+
"allOf": {
|
|
239
|
+
"$ref": "#/definitions/schemaArray"
|
|
240
|
+
},
|
|
241
|
+
"anyOf": {
|
|
242
|
+
"$ref": "#/definitions/schemaArray"
|
|
243
|
+
},
|
|
244
|
+
"oneOf": {
|
|
245
|
+
"$ref": "#/definitions/schemaArray"
|
|
246
|
+
},
|
|
247
|
+
"not": {
|
|
248
|
+
"$ref": "#"
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
"default": true
|
|
252
|
+
}],
|
|
253
|
+
];
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// Unevaluated vocabulary: the channel-consumer keyword class. Phase 1: runs
|
|
2
|
+
// after every other keyword in the same schema object has merged.
|
|
3
|
+
|
|
4
|
+
import { isObject } from "../json.js";
|
|
5
|
+
import { KeywordBehavior } from "../dialect.js";
|
|
6
|
+
import { LowerExpr, LowerStmt, lowerIR } from "../lowering.js";
|
|
7
|
+
import { childCursor } from "../cursor.js";
|
|
8
|
+
import { SELF } from "./core.js";
|
|
9
|
+
import {
|
|
10
|
+
properties,
|
|
11
|
+
patternProperties,
|
|
12
|
+
additionalProperties,
|
|
13
|
+
prefixItems,
|
|
14
|
+
items,
|
|
15
|
+
contains,
|
|
16
|
+
} from "./applicator.js";
|
|
17
|
+
|
|
18
|
+
/** 2020-12 unevaluated vocabulary URI. */
|
|
19
|
+
export const VOCAB_UNEVALUATED =
|
|
20
|
+
"https://json-schema.org/draft/2020-12/vocab/unevaluated";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* EXEMPLAR (consumer class): reads visible dependency records (engine.ts
|
|
24
|
+
* visibility rule), applies the subschema to properties nobody evaluated,
|
|
25
|
+
* and produces like any other applicator.
|
|
26
|
+
*/
|
|
27
|
+
export const unevaluatedProperties: KeywordBehavior = {
|
|
28
|
+
id: `${VOCAB_UNEVALUATED}#unevaluatedProperties`,
|
|
29
|
+
phase: 1,
|
|
30
|
+
analyze: () => ({
|
|
31
|
+
subschemas: SELF.subschemas,
|
|
32
|
+
consumes: [
|
|
33
|
+
properties.id,
|
|
34
|
+
patternProperties.id,
|
|
35
|
+
additionalProperties.id,
|
|
36
|
+
`${VOCAB_UNEVALUATED}#unevaluatedProperties`,
|
|
37
|
+
],
|
|
38
|
+
produces: [`${VOCAB_UNEVALUATED}#unevaluatedProperties`],
|
|
39
|
+
evaluatesNames: { kind: "all" },
|
|
40
|
+
applications: [
|
|
41
|
+
{ path: [], mode: "childSweep", conditional: false, asserts: true },
|
|
42
|
+
],
|
|
43
|
+
}),
|
|
44
|
+
// Static-coverage path only (D9a): the planner classifies this schema
|
|
45
|
+
// object as interpreted when any coverage contributor is dynamic, so
|
|
46
|
+
// lower() is never called with a null coverage.
|
|
47
|
+
lower: (_value, lctx) => {
|
|
48
|
+
// Runtime-coverage path (phase B activates it): fold the unit's runtime
|
|
49
|
+
// channel into an evaluated-name set and sweep the names it does not
|
|
50
|
+
// cover, mirroring evaluate()'s seen-set skip. The object-gated
|
|
51
|
+
// collectedNames produce is the same one the static path emits — its
|
|
52
|
+
// attempted-apply accumulator is exactly the interpreter's `matched`.
|
|
53
|
+
if (lctx.runtimeCoverage()) {
|
|
54
|
+
const f = lctx.binding();
|
|
55
|
+
const b = lctx.binding();
|
|
56
|
+
lctx.emit(
|
|
57
|
+
lowerIR.when(lowerIR.typeIs(lctx.instance, "object"), [
|
|
58
|
+
{ kind: "coverageFold", half: "names", binding: f },
|
|
59
|
+
{
|
|
60
|
+
kind: "forEachKey",
|
|
61
|
+
target: lctx.instance,
|
|
62
|
+
binding: b,
|
|
63
|
+
body: [
|
|
64
|
+
lowerIR.when(
|
|
65
|
+
lowerIR.not({
|
|
66
|
+
kind: "coverageCovers",
|
|
67
|
+
fold: f,
|
|
68
|
+
target: { kind: "binding", id: b },
|
|
69
|
+
}),
|
|
70
|
+
[
|
|
71
|
+
{
|
|
72
|
+
kind: "apply",
|
|
73
|
+
apply: {
|
|
74
|
+
path: [],
|
|
75
|
+
cursor: {
|
|
76
|
+
kind: "child",
|
|
77
|
+
of: { kind: "here" },
|
|
78
|
+
segment: { kind: "binding", id: b },
|
|
79
|
+
},
|
|
80
|
+
fold: "allMustPass",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
),
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{ kind: "produce", value: { kind: "collectedNames" } },
|
|
88
|
+
]),
|
|
89
|
+
);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const coverage = lctx.staticCoverage();
|
|
93
|
+
if (coverage === null) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
"unevaluatedProperties lowering requires static coverage (planner bug)",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
// The sweep is statically vacuous when sibling coverage is total, but the
|
|
99
|
+
// produce is not: evaluate() emits an (empty) names annotation for every
|
|
100
|
+
// object regardless. So the object-type guard always wraps a produce; the
|
|
101
|
+
// sweep is added only when some name can still be unevaluated.
|
|
102
|
+
const body: LowerStmt[] = [];
|
|
103
|
+
if (!coverage.coversAllNames) {
|
|
104
|
+
const b = lctx.binding();
|
|
105
|
+
const covered: LowerExpr[] = [
|
|
106
|
+
...coverage.names.map((n): LowerExpr =>
|
|
107
|
+
lowerIR.cmp("===", { kind: "binding", id: b }, lowerIR.constant(n)),
|
|
108
|
+
),
|
|
109
|
+
...coverage.patterns.map((p): LowerExpr =>
|
|
110
|
+
lowerIR.regexTest(p, { kind: "binding", id: b }),
|
|
111
|
+
),
|
|
112
|
+
];
|
|
113
|
+
body.push({
|
|
114
|
+
kind: "forEachKey",
|
|
115
|
+
target: lctx.instance,
|
|
116
|
+
binding: b,
|
|
117
|
+
body: [
|
|
118
|
+
lowerIR.when(
|
|
119
|
+
covered.length === 0
|
|
120
|
+
? lowerIR.constant(true)
|
|
121
|
+
: lowerIR.not(lowerIR.or(...covered)),
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
kind: "apply",
|
|
125
|
+
apply: {
|
|
126
|
+
path: [],
|
|
127
|
+
cursor: {
|
|
128
|
+
kind: "child",
|
|
129
|
+
of: { kind: "here" },
|
|
130
|
+
segment: { kind: "binding", id: b },
|
|
131
|
+
},
|
|
132
|
+
fold: "allMustPass",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
),
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
body.push({ kind: "produce", value: { kind: "collectedNames" } });
|
|
141
|
+
// Produce iff the instance is an object (nothing otherwise), matching
|
|
142
|
+
// evaluate()'s object-type guard before ctx.produce.
|
|
143
|
+
lctx.emit(lowerIR.when(lowerIR.typeIs(lctx.instance, "object"), body));
|
|
144
|
+
},
|
|
145
|
+
evaluate: (_value, cursor, ctx) => {
|
|
146
|
+
if (!isObject(cursor.value)) return true;
|
|
147
|
+
const seen = new Set<string>();
|
|
148
|
+
for (const p of ctx.visible([
|
|
149
|
+
properties.id,
|
|
150
|
+
patternProperties.id,
|
|
151
|
+
additionalProperties.id,
|
|
152
|
+
unevaluatedProperties.id,
|
|
153
|
+
])) {
|
|
154
|
+
for (const name of p.data as string[]) seen.add(name);
|
|
155
|
+
}
|
|
156
|
+
let ok = true;
|
|
157
|
+
const matched: string[] = [];
|
|
158
|
+
for (const name of Object.keys(cursor.value)) {
|
|
159
|
+
if (seen.has(name)) continue;
|
|
160
|
+
matched.push(name);
|
|
161
|
+
if (
|
|
162
|
+
!ctx.apply(
|
|
163
|
+
["unevaluatedProperties"],
|
|
164
|
+
childCursor(cursor, name, cursor.value[name]!),
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
ok = false;
|
|
168
|
+
}
|
|
169
|
+
// Dependency data only from an accepting keyword (Appendix D; see
|
|
170
|
+
// applicator.ts properties).
|
|
171
|
+
if (ok) ctx.produce(matched);
|
|
172
|
+
return ok;
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/** Same consumer shape as {@link unevaluatedProperties}, over array indexes not covered by `prefixItems`/`items`/`contains`. */
|
|
177
|
+
export const unevaluatedItems: KeywordBehavior = {
|
|
178
|
+
id: `${VOCAB_UNEVALUATED}#unevaluatedItems`,
|
|
179
|
+
phase: 1,
|
|
180
|
+
analyze: () => ({
|
|
181
|
+
subschemas: SELF.subschemas,
|
|
182
|
+
consumes: [
|
|
183
|
+
prefixItems.id,
|
|
184
|
+
items.id,
|
|
185
|
+
contains.id,
|
|
186
|
+
`${VOCAB_UNEVALUATED}#unevaluatedItems`,
|
|
187
|
+
],
|
|
188
|
+
produces: [`${VOCAB_UNEVALUATED}#unevaluatedItems`],
|
|
189
|
+
evaluatesIndexes: { kind: "all" },
|
|
190
|
+
applications: [
|
|
191
|
+
{ path: [], mode: "childSweep", conditional: false, asserts: true },
|
|
192
|
+
],
|
|
193
|
+
}),
|
|
194
|
+
// Static-coverage path only (D9a), same discipline as
|
|
195
|
+
// unevaluatedProperties: the planner classifies this schema object as
|
|
196
|
+
// interpreted whenever any index-coverage contributor is dynamic (e.g. a
|
|
197
|
+
// sibling `contains`, whose coverage is instance-dependent), so lower() is
|
|
198
|
+
// never called with a null/incomplete coverage.
|
|
199
|
+
lower: (_value, lctx) => {
|
|
200
|
+
// Runtime-coverage path (phase B activates it): fold the unit's runtime
|
|
201
|
+
// channel into a coveredPrefix/coveredIdx summary over this array's
|
|
202
|
+
// length and sweep the indexes it does not cover, mirroring evaluate()'s
|
|
203
|
+
// skip loop. The appliedTrue produce is the same one the static path
|
|
204
|
+
// emits (any attempted apply -> true, the interpreter's `applied` flag).
|
|
205
|
+
if (lctx.runtimeCoverage()) {
|
|
206
|
+
const f = lctx.binding();
|
|
207
|
+
const b = lctx.binding();
|
|
208
|
+
lctx.emit(
|
|
209
|
+
lowerIR.when(lowerIR.typeIs(lctx.instance, "array"), [
|
|
210
|
+
{ kind: "coverageFold", half: "indexes", binding: f },
|
|
211
|
+
{
|
|
212
|
+
kind: "forEachIndex",
|
|
213
|
+
target: lctx.instance,
|
|
214
|
+
binding: b,
|
|
215
|
+
start: 0,
|
|
216
|
+
body: [
|
|
217
|
+
lowerIR.when(
|
|
218
|
+
lowerIR.not({
|
|
219
|
+
kind: "coverageCovers",
|
|
220
|
+
fold: f,
|
|
221
|
+
target: { kind: "binding", id: b },
|
|
222
|
+
}),
|
|
223
|
+
[
|
|
224
|
+
{
|
|
225
|
+
kind: "apply",
|
|
226
|
+
apply: {
|
|
227
|
+
path: [],
|
|
228
|
+
cursor: {
|
|
229
|
+
kind: "child",
|
|
230
|
+
of: { kind: "here" },
|
|
231
|
+
segment: { kind: "binding", id: b },
|
|
232
|
+
},
|
|
233
|
+
fold: "allMustPass",
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
),
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
kind: "produce",
|
|
242
|
+
value: { kind: "collectedIndexes", render: "appliedTrue" },
|
|
243
|
+
},
|
|
244
|
+
]),
|
|
245
|
+
);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const coverage = lctx.staticCoverage();
|
|
249
|
+
if (coverage === null) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
"unevaluatedItems lowering requires static coverage (planner bug)",
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
if (coverage.coversAllIndexes) return; // statically vacuous
|
|
255
|
+
const b = lctx.binding();
|
|
256
|
+
lctx.emit(
|
|
257
|
+
lowerIR.when(lowerIR.typeIs(lctx.instance, "array"), [
|
|
258
|
+
{
|
|
259
|
+
kind: "forEachIndex",
|
|
260
|
+
target: lctx.instance,
|
|
261
|
+
binding: b,
|
|
262
|
+
start: coverage.prefixCount,
|
|
263
|
+
body: [
|
|
264
|
+
{
|
|
265
|
+
kind: "apply",
|
|
266
|
+
apply: {
|
|
267
|
+
path: [],
|
|
268
|
+
cursor: {
|
|
269
|
+
kind: "child",
|
|
270
|
+
of: { kind: "here" },
|
|
271
|
+
segment: { kind: "binding", id: b },
|
|
272
|
+
},
|
|
273
|
+
fold: "allMustPass",
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
]),
|
|
279
|
+
);
|
|
280
|
+
// Annotation: true iff it applied to any unevaluated index.
|
|
281
|
+
lctx.emit({
|
|
282
|
+
kind: "produce",
|
|
283
|
+
value: { kind: "collectedIndexes", render: "appliedTrue" },
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
evaluate: (_value, cursor, ctx) => {
|
|
287
|
+
if (!Array.isArray(cursor.value)) return true;
|
|
288
|
+
const length = cursor.value.length;
|
|
289
|
+
let coveredPrefix = 0;
|
|
290
|
+
const coveredIdx = new Set<number>();
|
|
291
|
+
for (const p of ctx.visible([
|
|
292
|
+
prefixItems.id,
|
|
293
|
+
items.id,
|
|
294
|
+
contains.id,
|
|
295
|
+
unevaluatedItems.id,
|
|
296
|
+
])) {
|
|
297
|
+
if (p.behaviorId === contains.id) {
|
|
298
|
+
if (p.data === true) coveredPrefix = length;
|
|
299
|
+
else for (const i of p.data as number[]) coveredIdx.add(i);
|
|
300
|
+
} else if (p.data === true) {
|
|
301
|
+
coveredPrefix = length;
|
|
302
|
+
} else if (p.behaviorId === prefixItems.id) {
|
|
303
|
+
coveredPrefix = Math.max(coveredPrefix, (p.data as number) + 1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
let ok = true;
|
|
307
|
+
let applied = false;
|
|
308
|
+
for (let i = coveredPrefix; i < length; i++) {
|
|
309
|
+
if (coveredIdx.has(i)) continue;
|
|
310
|
+
applied = true;
|
|
311
|
+
if (
|
|
312
|
+
!ctx.apply(
|
|
313
|
+
["unevaluatedItems"],
|
|
314
|
+
childCursor(cursor, i, cursor.value[i]!),
|
|
315
|
+
)
|
|
316
|
+
) {
|
|
317
|
+
ok = false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (applied && ok) ctx.produce(true);
|
|
321
|
+
return ok;
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/** The 2020-12 unevaluated vocabulary's keyword behaviors, by name. */
|
|
326
|
+
export const unevaluatedVocabulary: Record<string, KeywordBehavior> = {
|
|
327
|
+
unevaluatedProperties,
|
|
328
|
+
unevaluatedItems,
|
|
329
|
+
};
|