ata-validator 1.45.0 → 1.46.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/README.md +9 -9
- package/bin/ata.js +16 -0
- package/index.js +73 -12
- package/lib/branch-collapse.js +1 -1
- package/lib/combined-runtime.js +149 -0
- package/lib/interpreter.js +1 -1
- package/lib/js-compiler.js +1089 -77
- package/lib/migrate.js +253 -0
- package/lib/regex-linear.js +35 -26
- package/lib/validator-core.js +26 -4
- package/lib/version.js +1 -1
- package/package.json +8 -8
package/lib/js-compiler.js
CHANGED
|
@@ -30,9 +30,23 @@ function uqSources () {
|
|
|
30
30
|
// for exactly this; the error and combined generators emit `helperCode` into
|
|
31
31
|
// their function source. The boolean generator's `helperCode` is only
|
|
32
32
|
// scanned for regex entries and must not be used here.
|
|
33
|
+
// The helpers the one-pass generator takes from lib/combined-runtime.js as
|
|
34
|
+
// closure values instead of writing into each program, by the hoistOnce key
|
|
35
|
+
// that would have written them.
|
|
36
|
+
const SHARED_RT = {
|
|
37
|
+
_deqHoisted: ['_deq'], _peHoisted: ['_pe'],
|
|
38
|
+
_own_PN: ['_PN', '_OP', '_gp'], _own_hop: ['_hop'], _own_h: ['_h'], _own_ok: ['_ok'], _own_hall: ['_hall'],
|
|
39
|
+
}
|
|
40
|
+
let _rt = null
|
|
41
|
+
function sharedRt () { return _rt || (_rt = require('./combined-runtime')) }
|
|
42
|
+
function bindRt (ctx, names) {
|
|
43
|
+
const rt = sharedRt()
|
|
44
|
+
for (const n of names) { ctx.closureVars.push(n); ctx.closureVals.push(rt[n]) }
|
|
45
|
+
}
|
|
33
46
|
function hoistOnce(ctx, key, code) {
|
|
34
47
|
if (ctx[key]) return
|
|
35
48
|
ctx[key] = true
|
|
49
|
+
if (ctx.sharedRuntime && SHARED_RT[key] !== undefined) { bindRt(ctx, SHARED_RT[key]); return }
|
|
36
50
|
// Recorded as shared: the text is a constant and the name it declares is
|
|
37
51
|
// fixed, so several compiled schemas in one bundle can use one copy. The
|
|
38
52
|
// rest of the preamble cannot move, because emitConstant and the generated
|
|
@@ -169,7 +183,7 @@ function enumCondition(ctx, vals, v) {
|
|
|
169
183
|
|
|
170
184
|
|
|
171
185
|
const { codeFor } = require('./error-codes')
|
|
172
|
-
const { ordinalFor } = require('./schema-order')
|
|
186
|
+
const { ordinalFor, rankFor } = require('./schema-order')
|
|
173
187
|
const { compileSafe, patternIsSafe } = require('./safe-regex')
|
|
174
188
|
// Whether a pattern goes to the linear-time engine. A pattern the platform
|
|
175
189
|
// RegExp also runs in linear time (nativeIsLinear) takes the RegExp: it answers
|
|
@@ -1037,6 +1051,11 @@ function withPlain (schema, v, lines, ctx, run) {
|
|
|
1037
1051
|
return run()
|
|
1038
1052
|
}
|
|
1039
1053
|
if (!ctx.plainOf) ctx.plainOf = new Map()
|
|
1054
|
+
// A flag an enclosing node already declared for this same value stands:
|
|
1055
|
+
// the straight-line unevaluatedProperties code (inlineUnevalBlock) declares
|
|
1056
|
+
// one for the object and writes every branch's verdict under it, where each
|
|
1057
|
+
// branch used to read the prototype again.
|
|
1058
|
+
if (ctx.plainReuse === v && ctx.plainOf.get(v) !== undefined) return run()
|
|
1040
1059
|
const name = '_pk' + ctx.varCounter++
|
|
1041
1060
|
const at = lines.length
|
|
1042
1061
|
const decl = `const ${name}=typeof ${v}==='object'&&${v}!==null&&${protoIs(ctx, v)}`
|
|
@@ -2261,12 +2280,165 @@ function refsAllPlainLocal (root, text) {
|
|
|
2261
2280
|
// character of a schema without references, so a short one is known not to
|
|
2262
2281
|
// reach EXPANDED_SHARE without walking it again at every compile.
|
|
2263
2282
|
const _refFreeLength = new WeakMap()
|
|
2283
|
+
// `$dynamicRef` resolved at compile time, the way the interpreted engine
|
|
2284
|
+
// resolves it: the target depends on the dynamic scope, the resources entered
|
|
2285
|
+
// on the way to the reference, so the schema is unrolled into one copy per
|
|
2286
|
+
// (node, scope) pair and every reference in a copy becomes a plain local
|
|
2287
|
+
// reference to the copy its scope selects. Two scopes are the same when every
|
|
2288
|
+
// dynamic anchor name resolves to the same resource in both, which is what
|
|
2289
|
+
// the lookup reads; keyed that way, a schema whose anchors all sit at the
|
|
2290
|
+
// root (the 2020-12 meta-schema) gets one copy per node, not one per path.
|
|
2291
|
+
// Nested `$id`, `$anchor` and `$dynamicAnchor` are dropped from the copies,
|
|
2292
|
+
// since nothing refers by them any more. Any reference that does not resolve
|
|
2293
|
+
// makes the whole rewrite give up (null) and the schema goes to the
|
|
2294
|
+
// interpreted engine, which reports it where it is reached: an unrolled copy
|
|
2295
|
+
// must never leave a reference out, because a reference that is not written
|
|
2296
|
+
// checks nothing. 2020-12 bookending is assumed: the v1 dialect, which drops
|
|
2297
|
+
// it, is routed to the interpreted engine before code generation is asked.
|
|
2298
|
+
// Dropped from every copy: identifiers, which nothing refers by any more,
|
|
2299
|
+
// and definition containers, whose members are reached only through the
|
|
2300
|
+
// references that name them and are copied where those land. A definition
|
|
2301
|
+
// nothing uses would otherwise sit in the output and could cost it the
|
|
2302
|
+
// generated code.
|
|
2303
|
+
const DYN_DROP = new Set(['$id', '$anchor', '$dynamicAnchor', '$defs', 'definitions'])
|
|
2304
|
+
const BOOL_TRUE = { bool: true }
|
|
2305
|
+
const BOOL_FALSE = { bool: false }
|
|
2306
|
+
function expandDynamicScopes(root, schemaMap) {
|
|
2307
|
+
if (root === null || typeof root !== 'object' || Array.isArray(root)) return null
|
|
2308
|
+
const { indexSchemas, resolveRef, splitFragment, resolveUri } = require('./interpreter')._refInternals
|
|
2309
|
+
let state
|
|
2310
|
+
try { state = indexSchemas(root, schemaMap && schemaMap.size ? schemaMap : null) } catch { return null }
|
|
2311
|
+
const anchorNames = []
|
|
2312
|
+
for (const m of state.dynamicAnchors.values()) for (const name of m.keys()) if (!anchorNames.includes(name)) anchorNames.push(name)
|
|
2313
|
+
anchorNames.sort()
|
|
2314
|
+
// The scope signature: for each anchor name, the first resource in the
|
|
2315
|
+
// chain that declares it.
|
|
2316
|
+
const signature = (chain) => {
|
|
2317
|
+
let sig = ''
|
|
2318
|
+
for (const name of anchorNames) {
|
|
2319
|
+
let hit = ''
|
|
2320
|
+
for (let i = 0; i < chain.length; i++) { const dyn = state.dynamicAnchors.get(chain[i]); if (dyn && dyn.has(name)) { hit = chain[i]; break } }
|
|
2321
|
+
sig += name + '=' + hit + ';'
|
|
2322
|
+
}
|
|
2323
|
+
return sig
|
|
2324
|
+
}
|
|
2325
|
+
const resolveDynamic = (ref, base, chain) => {
|
|
2326
|
+
let { node, base: refBase } = resolveRef(ref, base, state)
|
|
2327
|
+
const [, fragment] = splitFragment(resolveUri(base, ref))
|
|
2328
|
+
if (fragment && !fragment.startsWith('/')) {
|
|
2329
|
+
const initialDyn = state.dynamicAnchors.get(refBase)
|
|
2330
|
+
const bookended = node !== undefined && initialDyn !== undefined && initialDyn.get(fragment) === node
|
|
2331
|
+
if (bookended) {
|
|
2332
|
+
for (let i = 0; i < chain.length; i++) {
|
|
2333
|
+
const dyn = state.dynamicAnchors.get(chain[i])
|
|
2334
|
+
if (dyn && dyn.has(fragment)) { node = dyn.get(fragment); refBase = chain[i]; break }
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
return { node, base: refBase }
|
|
2339
|
+
}
|
|
2340
|
+
const copies = new Map() // node (or a boolean sentinel) -> Map(signature -> entry)
|
|
2341
|
+
const order = []
|
|
2342
|
+
let fail = false
|
|
2343
|
+
// The entry for a node in a scope: its copy, made on the first ask, and
|
|
2344
|
+
// later its name. A `$ref` slot holds the entry until names are given.
|
|
2345
|
+
const entryFor = (node, base, chain) => {
|
|
2346
|
+
if (node === true || node === false) {
|
|
2347
|
+
const key = node ? BOOL_TRUE : BOOL_FALSE
|
|
2348
|
+
let bySig = copies.get(key)
|
|
2349
|
+
if (bySig === undefined) { bySig = new Map(); copies.set(key, bySig) }
|
|
2350
|
+
let e = bySig.get('')
|
|
2351
|
+
if (e === undefined) { e = { copy: node, sig: '', name: null, referenced: false }; bySig.set('', e); order.push(e) }
|
|
2352
|
+
return e
|
|
2353
|
+
}
|
|
2354
|
+
if (node === null || typeof node !== 'object') { fail = true; return { copy: node, sig: '', name: null } }
|
|
2355
|
+
if (state.nodeBase.has(node) && state.nodeBase.get(node) !== base) base = state.nodeBase.get(node)
|
|
2356
|
+
if (!chain.includes(base)) chain = chain.concat(base)
|
|
2357
|
+
const sig = signature(chain)
|
|
2358
|
+
let bySig = copies.get(node)
|
|
2359
|
+
if (bySig === undefined) { bySig = new Map(); copies.set(node, bySig) }
|
|
2360
|
+
const hit = bySig.get(sig)
|
|
2361
|
+
if (hit !== undefined) return hit
|
|
2362
|
+
const e = { copy: Array.isArray(node) ? [] : {}, sig, name: null, referenced: false }
|
|
2363
|
+
bySig.set(sig, e)
|
|
2364
|
+
order.push(e)
|
|
2365
|
+
if (Array.isArray(node)) { for (const x of node) e.copy.push(copyValue(x, base, chain)); return e }
|
|
2366
|
+
const out = e.copy
|
|
2367
|
+
// Only a schema node reads `$ref` as a reference and carries an `$id`; in
|
|
2368
|
+
// a `properties` map those are property names. The index registers
|
|
2369
|
+
// every schema node, so that is the test.
|
|
2370
|
+
const isSchema = state.nodeBase.has(node)
|
|
2371
|
+
for (const k of Object.keys(node)) {
|
|
2372
|
+
if (fail) return e
|
|
2373
|
+
const v = node[k]
|
|
2374
|
+
if (isSchema && (k === '$ref' || k === '$dynamicRef')) {
|
|
2375
|
+
if (typeof v !== 'string') { fail = true; return e }
|
|
2376
|
+
const t = k === '$ref' ? resolveRef(v, base, state) : resolveDynamic(v, base, chain)
|
|
2377
|
+
if (t.node === undefined || t.node === null || (typeof t.node !== 'object' && typeof t.node !== 'boolean') || Array.isArray(t.node)) { fail = true; return e }
|
|
2378
|
+
const target = entryFor(t.node, t.base, chain)
|
|
2379
|
+
target.referenced = true
|
|
2380
|
+
out.$ref = target
|
|
2381
|
+
continue
|
|
2382
|
+
}
|
|
2383
|
+
if (isSchema && DYN_DROP.has(k) && !(k === '$id' && node === root)) continue
|
|
2384
|
+
setOwn(out, k, SKIP_VALUE_KEYS.has(k) ? v : copyValue(v, base, chain))
|
|
2385
|
+
}
|
|
2386
|
+
return e
|
|
2387
|
+
}
|
|
2388
|
+
// A value under a keyword: a schema or a list is copied, anything else
|
|
2389
|
+
// (a type name, a limit, a required name) is kept as it is.
|
|
2390
|
+
const copyValue = (v, base, chain) => (v !== null && typeof v === 'object') || typeof v === 'boolean' ? entryFor(v, base, chain).copy : v
|
|
2391
|
+
const rootBase = state.nodeBase.get(root)
|
|
2392
|
+
const rootEntry = entryFor(root, rootBase, [rootBase])
|
|
2393
|
+
if (fail) return null
|
|
2394
|
+
const rootCopy = rootEntry.copy
|
|
2395
|
+
const defsKey = root.$defs && typeof root.$defs === 'object' ? '$defs' : (root.definitions && typeof root.definitions === 'object' ? 'definitions' : '$defs')
|
|
2396
|
+
const existing = {}
|
|
2397
|
+
let n = 0
|
|
2398
|
+
// Only a copy something refers to becomes a definition; the rest sit
|
|
2399
|
+
// where they were copied.
|
|
2400
|
+
for (const e of order) {
|
|
2401
|
+
if (e === rootEntry) { e.name = '#'; continue }
|
|
2402
|
+
if (!e.referenced) continue
|
|
2403
|
+
let name
|
|
2404
|
+
do { name = '__ata_dyn_' + n++ } while (Object.prototype.hasOwnProperty.call(existing, name))
|
|
2405
|
+
e.name = name
|
|
2406
|
+
}
|
|
2407
|
+
// Write the names into the `$ref` slots.
|
|
2408
|
+
const seen = new Set()
|
|
2409
|
+
const fix = (o) => {
|
|
2410
|
+
if (o === null || typeof o !== 'object' || seen.has(o)) return
|
|
2411
|
+
seen.add(o)
|
|
2412
|
+
if (Array.isArray(o)) { for (const x of o) fix(x); return }
|
|
2413
|
+
for (const k of Object.keys(o)) {
|
|
2414
|
+
const v = o[k]
|
|
2415
|
+
if (k === '$ref' && v !== null && typeof v === 'object' && !Array.isArray(v) && typeof v.sig === 'string') {
|
|
2416
|
+
o.$ref = v.name === '#' ? '#' : '#/' + defsKey + '/' + v.name
|
|
2417
|
+
continue
|
|
2418
|
+
}
|
|
2419
|
+
if (!SKIP_VALUE_KEYS.has(k)) fix(v)
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
fix(rootCopy)
|
|
2423
|
+
const defs = {}
|
|
2424
|
+
for (const e of order) {
|
|
2425
|
+
if (e === rootEntry || !e.referenced) continue
|
|
2426
|
+
if (typeof e.copy === 'object') fix(e.copy)
|
|
2427
|
+
defs[e.name] = e.copy
|
|
2428
|
+
}
|
|
2429
|
+
rootCopy[defsKey] = defs
|
|
2430
|
+
return rootCopy
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2264
2433
|
function normalizeRefsWalk(root, schemaMap) {
|
|
2265
2434
|
const text = JSON.stringify(root)
|
|
2266
2435
|
const hasRef = text.includes('"$ref"')
|
|
2267
2436
|
_refFreeLength.set(root, hasRef ? -1 : text.length)
|
|
2437
|
+
if (/"\$(?:recursiveRef|recursiveAnchor)"/.test(text)) return null
|
|
2438
|
+
// A schema with dynamic references is unrolled by dynamic scope first; the
|
|
2439
|
+
// result holds plain local references only.
|
|
2440
|
+
if (/"\$(?:dynamicRef|dynamicAnchor)"/.test(text)) return expandDynamicScopes(root, schemaMap)
|
|
2268
2441
|
if (!hasRef) return root
|
|
2269
|
-
if (/"\$(?:dynamicRef|recursiveRef|dynamicAnchor|recursiveAnchor)"/.test(text)) return null
|
|
2270
2442
|
if (refsAllPlainLocal(root, text)) return root
|
|
2271
2443
|
const { indexSchemas, resolveRef } = require('./interpreter')._refInternals
|
|
2272
2444
|
const state = indexSchemas(root, schemaMap && schemaMap.size ? schemaMap : null)
|
|
@@ -2277,14 +2449,16 @@ function normalizeRefsWalk(root, schemaMap) {
|
|
|
2277
2449
|
const refTarget = new Map() // reference site node -> target node
|
|
2278
2450
|
const visited = new Set()
|
|
2279
2451
|
let fail = false
|
|
2452
|
+
let dynamic = false
|
|
2280
2453
|
const visit = (node) => {
|
|
2281
2454
|
if (fail || node === null || typeof node !== 'object' || visited.has(node)) return
|
|
2282
2455
|
visited.add(node)
|
|
2283
2456
|
if (Array.isArray(node)) { for (const n of node) visit(n); return }
|
|
2284
2457
|
if (node !== root && typeof node.$schema === 'string' && rootDialect !== null && node.$schema.replace(/#$/, '') !== rootDialect) { fail = true; return }
|
|
2285
|
-
// A dynamic reference reached through another document
|
|
2286
|
-
//
|
|
2287
|
-
if (node.$
|
|
2458
|
+
// A dynamic reference reached through another document is unrolled by
|
|
2459
|
+
// scope the same way one in the root is (expandDynamicScopes, below).
|
|
2460
|
+
if (node.$recursiveRef !== undefined || node.$recursiveAnchor !== undefined) { fail = true; return }
|
|
2461
|
+
if (node.$dynamicRef !== undefined || node.$dynamicAnchor !== undefined) { dynamic = true; return }
|
|
2288
2462
|
if (typeof node.$ref === 'string' && state.nodeBase.has(node)) {
|
|
2289
2463
|
const base = state.nodeBase.get(node)
|
|
2290
2464
|
const ref = node.$ref
|
|
@@ -2304,6 +2478,7 @@ function normalizeRefsWalk(root, schemaMap) {
|
|
|
2304
2478
|
}
|
|
2305
2479
|
visit(root)
|
|
2306
2480
|
if (fail) return null
|
|
2481
|
+
if (dynamic) return expandDynamicScopes(root, schemaMap)
|
|
2307
2482
|
if (refTarget.size === 0) return root
|
|
2308
2483
|
const defsKey = root.$defs && typeof root.$defs === 'object' ? '$defs' : (root.definitions && typeof root.definitions === 'object' ? 'definitions' : '$defs')
|
|
2309
2484
|
const existing = root[defsKey] && typeof root[defsKey] === 'object' ? root[defsKey] : {}
|
|
@@ -3420,11 +3595,20 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3420
3595
|
// once, there. Only that model does: with unevaluatedProperties true or a
|
|
3421
3596
|
// schema, skipping it here left anyOf unchecked and accepted a document no
|
|
3422
3597
|
// branch matched.
|
|
3423
|
-
|
|
3598
|
+
// Where unevaluatedProperties builds its evaluated set at run time, the
|
|
3599
|
+
// annotation pass already decides each anyOf and oneOf branch, so the
|
|
3600
|
+
// keywords' own blocks would decide them a second time: on a schema of
|
|
3601
|
+
// nested references that doubled the cost of accepting a document. Their
|
|
3602
|
+
// blocks go to the `else` of that pass instead, for a value that is not an
|
|
3603
|
+
// object, which the pass does not look at and which they must still judge.
|
|
3604
|
+
const nonObjApplicators = schema.unevaluatedProperties !== undefined && schema.additionalProperties === undefined &&
|
|
3605
|
+
(Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf)) && unevalBuiltAtRuntime(schema, ctx) ? [] : null
|
|
3606
|
+
const applicatorLines = nonObjApplicators || lines
|
|
3607
|
+
if (schema.anyOf && !unevalChecksAnyOf(schema, ctx)) genAnyOfNode(schema, v, applicatorLines, ctx, knownType)
|
|
3424
3608
|
|
|
3425
3609
|
// oneOf — branch fns hoisted to factory scope when safe (no recursion/ref).
|
|
3426
3610
|
// Falls back to inline closures if any branch touches recursive validation.
|
|
3427
|
-
if (schema.oneOf) genOneOfNode(schema, v,
|
|
3611
|
+
if (schema.oneOf) genOneOfNode(schema, v, applicatorLines, ctx, knownType)
|
|
3428
3612
|
|
|
3429
3613
|
// not
|
|
3430
3614
|
if (schema.not !== undefined) genNotNode(schema, v, lines, ctx, knownType)
|
|
@@ -3433,7 +3617,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3433
3617
|
if (schema.if !== undefined) genIfNode(schema, v, lines, ctx, knownType)
|
|
3434
3618
|
|
|
3435
3619
|
// unevaluatedProperties
|
|
3436
|
-
if (schema.unevaluatedProperties !== undefined) genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount)
|
|
3620
|
+
if (schema.unevaluatedProperties !== undefined) genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount, nonObjApplicators)
|
|
3437
3621
|
|
|
3438
3622
|
// unevaluatedItems
|
|
3439
3623
|
if (schema.unevaluatedItems !== undefined) genUnevaluatedItemsNode(schema, v, lines, ctx, knownType, isArr)
|
|
@@ -3763,7 +3947,9 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
3763
3947
|
// whatever function is compiling, carrying this def's key set.
|
|
3764
3948
|
const outerDeferred = ctx.deferredChecks
|
|
3765
3949
|
ctx.deferredChecks = null
|
|
3766
|
-
|
|
3950
|
+
const outerReuse = ctx.plainReuse
|
|
3951
|
+
ctx.plainReuse = undefined
|
|
3952
|
+
try { genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx) } finally { ctx.plainReuse = outerReuse }
|
|
3767
3953
|
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
3768
3954
|
ctx.deferredChecks = outerDeferred
|
|
3769
3955
|
// The def is hoisted above the entry function, where the recursive
|
|
@@ -3798,10 +3984,13 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
3798
3984
|
const outerDeferred = ctx.deferredChecks
|
|
3799
3985
|
ctx.deferredChecks = null
|
|
3800
3986
|
ctx.refStack.add(schema.$ref)
|
|
3987
|
+
const outerReuse = ctx.plainReuse
|
|
3988
|
+
ctx.plainReuse = undefined
|
|
3801
3989
|
try {
|
|
3802
3990
|
genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
|
|
3803
3991
|
} finally {
|
|
3804
3992
|
ctx.refStack.delete(schema.$ref)
|
|
3993
|
+
ctx.plainReuse = outerReuse
|
|
3805
3994
|
}
|
|
3806
3995
|
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
3807
3996
|
ctx.deferredChecks = outerDeferred
|
|
@@ -4008,12 +4197,15 @@ function unevalBuiltAtRuntime(schema, ctx, r) {
|
|
|
4008
4197
|
return r.dynamic && (r.allProps || !unevalPropsModeled(schema, ctx.schemaMap))
|
|
4009
4198
|
}
|
|
4010
4199
|
|
|
4011
|
-
function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount) {
|
|
4200
|
+
function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount, nonObjApplicators) {
|
|
4012
4201
|
const evalResult = collectEvaluated(schema, ctx.schemaMap, ctx.rootDefs)
|
|
4013
4202
|
if (unevalBuiltAtRuntime(schema, ctx, evalResult)) {
|
|
4014
|
-
genUnevalDynamicVerdict(schema, v, lines, ctx, isObj)
|
|
4203
|
+
genUnevalDynamicVerdict(schema, v, lines, ctx, isObj, nonObjApplicators)
|
|
4015
4204
|
return
|
|
4016
4205
|
}
|
|
4206
|
+
// genCodeNode moved the anyOf and oneOf blocks here only for the run-time
|
|
4207
|
+
// form; any other outcome would leave them unwritten.
|
|
4208
|
+
if (nonObjApplicators !== null && nonObjApplicators !== undefined) throw DECLINE
|
|
4017
4209
|
|
|
4018
4210
|
if (evalResult.allProps || schema.unevaluatedProperties === true) {
|
|
4019
4211
|
// All props evaluated or unevaluatedProperties:true — no-op
|
|
@@ -4955,7 +5147,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
4955
5147
|
if (hasUnresolvableRef(schema, eRootDefs, eAnchors, schemaMap, new Set())) return null
|
|
4956
5148
|
if (needsBaseTracking(schema, schemaMap, new Set())) return null
|
|
4957
5149
|
|
|
4958
|
-
const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, shared: [], refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootSchema: inputSchema, userFormats: userFormats || null,
|
|
5150
|
+
const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, shared: [], refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootE: schema, rootSchema: inputSchema, userFormats: userFormats || null,
|
|
4959
5151
|
// Custom format checkers referenced by the body, bound as
|
|
4960
5152
|
// closure parameters below the way the other entry points do.
|
|
4961
5153
|
closureVars: [], closureVals: [],
|
|
@@ -5150,6 +5342,224 @@ function holdsRef (node, seen = new Set()) {
|
|
|
5150
5342
|
return false
|
|
5151
5343
|
}
|
|
5152
5344
|
|
|
5345
|
+
// Every `$ref` under `node`, outside the values of enum, const, default and
|
|
5346
|
+
// examples. Null when one is not a plain pointer into the root's definitions
|
|
5347
|
+
// (after prepareForCodegen those are what remain, besides `#` itself) or
|
|
5348
|
+
// when the node carries a scope keyword of its own, which would not mean the
|
|
5349
|
+
// same thing under a wrapper.
|
|
5350
|
+
const BRANCH_SCOPE_KEYS = new Set(['$id', '$anchor', '$dynamicAnchor', '$dynamicRef', '$defs', 'definitions', '$schema'])
|
|
5351
|
+
function branchRefsLocal (node, defsKey, seen) {
|
|
5352
|
+
if (node === null || typeof node !== 'object') return true
|
|
5353
|
+
if (seen.has(node)) return true
|
|
5354
|
+
seen.add(node)
|
|
5355
|
+
if (Array.isArray(node)) { for (const x of node) if (!branchRefsLocal(x, defsKey, seen)) return false; return true }
|
|
5356
|
+
for (const k of Object.keys(node)) {
|
|
5357
|
+
if (SKIP_VALUE_KEYS.has(k)) continue
|
|
5358
|
+
if (BRANCH_SCOPE_KEYS.has(k)) return false
|
|
5359
|
+
const v = node[k]
|
|
5360
|
+
if (k === '$ref') { if (typeof v !== 'string' || !v.startsWith('#/' + defsKey + '/')) return false; continue }
|
|
5361
|
+
if (!branchRefsLocal(v, defsKey, seen)) return false
|
|
5362
|
+
}
|
|
5363
|
+
return true
|
|
5364
|
+
}
|
|
5365
|
+
// One verdict function per branch, compiled on its own with the root's
|
|
5366
|
+
// definitions under it, or null when a branch cannot be wrapped that way
|
|
5367
|
+
// (then the site keeps its error functions for the decision). The
|
|
5368
|
+
// definitions themselves are checked once per root.
|
|
5369
|
+
const _defsLocal = new WeakMap()
|
|
5370
|
+
function compileBranchVerdicts (branches, ctx) {
|
|
5371
|
+
const root = ctx.rootC
|
|
5372
|
+
if (root === null || typeof root !== 'object') return null
|
|
5373
|
+
const defsKey = root.$defs && typeof root.$defs === 'object' ? '$defs' : (root.definitions && typeof root.definitions === 'object' ? 'definitions' : null)
|
|
5374
|
+
if (defsKey !== null) {
|
|
5375
|
+
let ok = _defsLocal.get(root)
|
|
5376
|
+
if (ok === undefined) { ok = branchRefsLocal(root[defsKey], defsKey, new Set()); _defsLocal.set(root, ok) }
|
|
5377
|
+
if (!ok) return null
|
|
5378
|
+
}
|
|
5379
|
+
const out = []
|
|
5380
|
+
for (const sub of branches) {
|
|
5381
|
+
if (sub === true) { out.push(() => true); continue }
|
|
5382
|
+
if (sub === false) { out.push(() => false); continue }
|
|
5383
|
+
if (sub === null || typeof sub !== 'object' || Array.isArray(sub)) return null
|
|
5384
|
+
if (!branchRefsLocal(sub, defsKey === null ? '$defs' : defsKey, new Set())) return null
|
|
5385
|
+
const wrapped = Object.assign({}, sub)
|
|
5386
|
+
if (defsKey !== null) wrapped[defsKey] = root[defsKey]
|
|
5387
|
+
if (typeof ctx.rootSchema === 'object' && ctx.rootSchema !== null && typeof ctx.rootSchema.$schema === 'string') wrapped.$schema = ctx.rootSchema.$schema
|
|
5388
|
+
let fn = null
|
|
5389
|
+
try { fn = compileToJSCodegen(wrapped, ctx.schemaMap, ctx.userFormats) } catch (e) { if (e !== DECLINE) throw e }
|
|
5390
|
+
if (typeof fn !== 'function') return null
|
|
5391
|
+
out.push(fn)
|
|
5392
|
+
}
|
|
5393
|
+
return out
|
|
5394
|
+
}
|
|
5395
|
+
|
|
5396
|
+
// A verdict for one subtree of the combined function, as a function in the
|
|
5397
|
+
// preamble, so the collecting code of that subtree runs only for a value the
|
|
5398
|
+
// verdict rejects. The collecting code is slower than the verdict on an
|
|
5399
|
+
// accepted value (it keeps paths, builds nothing it can avoid but tests more
|
|
5400
|
+
// than the verdict's cheapest-first order), so without this the whole
|
|
5401
|
+
// function was put behind one verdict of the whole document (`_VF`), and a
|
|
5402
|
+
// rejected document was validated twice in full. With a guard at every
|
|
5403
|
+
// array element and every object-valued property, an accepted value costs
|
|
5404
|
+
// its verdict and a rejected one costs its verdict plus the collection of
|
|
5405
|
+
// the subtree that failed. Only in the combined function, the runtime path;
|
|
5406
|
+
// null for a subtree whose verdict declines, is a leaf, or is too large.
|
|
5407
|
+
const GUARD_KEYS = ['oneOf', 'anyOf', 'allOf', '$ref', 'items', 'prefixItems', 'properties', 'patternProperties', 'additionalProperties', 'not', 'if', 'contains', 'dependentSchemas', 'dependencies', 'propertyNames', 'unevaluatedProperties', 'unevaluatedItems']
|
|
5408
|
+
const GUARD_MAX = 16 * 1024
|
|
5409
|
+
const GUARD_BUDGET = 512 * 1024
|
|
5410
|
+
// Whether a schema node is worth a guard of its own: it applies something
|
|
5411
|
+
// beyond leaf checks. A node whose children are themselves worth guarding is
|
|
5412
|
+
// not guarded; the guards go on the children. Guarding both ran the parent's
|
|
5413
|
+
// verdict in full and then, when it failed, every child's verdict again: on a
|
|
5414
|
+
// list of 1000 users with one bad one that was two verdicts of the list
|
|
5415
|
+
// before the one user was collected.
|
|
5416
|
+
const weighty = (n) => n !== null && typeof n === 'object' && !Array.isArray(n) && GUARD_KEYS.some((k) => n[k] !== undefined)
|
|
5417
|
+
function childrenWeighty (sub) {
|
|
5418
|
+
if (weighty(sub.items)) return true
|
|
5419
|
+
if (Array.isArray(sub.prefixItems) && sub.prefixItems.some(weighty)) return true
|
|
5420
|
+
if (weighty(sub.additionalProperties)) return true
|
|
5421
|
+
if (sub.properties && typeof sub.properties === 'object') for (const k of Object.keys(sub.properties)) if (weighty(sub.properties[k])) return true
|
|
5422
|
+
if (sub.patternProperties && typeof sub.patternProperties === 'object') for (const k of Object.keys(sub.patternProperties)) if (weighty(sub.patternProperties[k])) return true
|
|
5423
|
+
return false
|
|
5424
|
+
}
|
|
5425
|
+
// Below this much verdict source the guard is written into the function
|
|
5426
|
+
// itself as a labelled block (`gN:{...break gN...}`), not as a function it
|
|
5427
|
+
// calls: in the first thousands of calls of a process, before the optimizing
|
|
5428
|
+
// compiler has inlined anything, each call is paid in full, and a schema
|
|
5429
|
+
// with six small guards paid six. Above it the guard stays a function, so
|
|
5430
|
+
// one large subtree does not swell the function around it.
|
|
5431
|
+
const GUARD_INLINE_MAX = 2048
|
|
5432
|
+
function subtreeGuard (sub, ctx, gv) {
|
|
5433
|
+
if (!ctx.inCombined || !weighty(sub) || childrenWeighty(sub)) return null
|
|
5434
|
+
if ((ctx.guardChars || 0) >= GUARD_BUDGET) return null
|
|
5435
|
+
// The verdict generator registers the definition functions it writes under
|
|
5436
|
+
// `defFns`, and so does the error generator for the branch functions of
|
|
5437
|
+
// this same program: shared, the verdict found the error generator's
|
|
5438
|
+
// `_defE` function under the definition's name and called it with one
|
|
5439
|
+
// argument. The guards keep a registry of their own.
|
|
5440
|
+
if (!ctx.guardDefFns) ctx.guardDefFns = new Map()
|
|
5441
|
+
const saved = snapshotCtx(ctx)
|
|
5442
|
+
const outerDefFns = ctx.defFns
|
|
5443
|
+
const outerShare = ctx.shareDefs
|
|
5444
|
+
ctx.defFns = ctx.guardDefFns
|
|
5445
|
+
// Definitions referenced from several places are written once, as
|
|
5446
|
+
// functions in the preamble (the verdict generator's shared mode), not
|
|
5447
|
+
// expanded into every guard that reaches them: expanded, the guards of a
|
|
5448
|
+
// 25-definition API schema came to 55 KB of source in a 133 KB function,
|
|
5449
|
+
// and a function that size reaches the optimizing compiler late.
|
|
5450
|
+
ctx.shareDefs = true
|
|
5451
|
+
try {
|
|
5452
|
+
const bl = []
|
|
5453
|
+
try { nestedGenCode(sub, gv, bl, ctx) } finally { ctx.defFns = outerDefFns; ctx.shareDefs = outerShare }
|
|
5454
|
+
if (bl.length === 0) { restoreCtx(ctx, saved); return null }
|
|
5455
|
+
const body = bl.join(';')
|
|
5456
|
+
// The verdict generator's recursion names (`_validate`, the cycle guard
|
|
5457
|
+
// state) are not this function's; a subtree that reaches them stays
|
|
5458
|
+
// unguarded.
|
|
5459
|
+
const added = ctx.preamble.slice(saved.get('preamble').n).join(';')
|
|
5460
|
+
if (body.length > GUARD_MAX || body.includes(SP_PLACEHOLDER) || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(body + added)) { restoreCtx(ctx, saved); return null }
|
|
5461
|
+
ctx.guardChars = (ctx.guardChars || 0) + body.length
|
|
5462
|
+
if (body.length <= GUARD_INLINE_MAX) return { body }
|
|
5463
|
+
const name = `_vg${ctx.varCounter++}`
|
|
5464
|
+
ctx.helperCode.push(`const ${name}=function(${gv}){${body};return true}`)
|
|
5465
|
+
return { name }
|
|
5466
|
+
} catch (e) {
|
|
5467
|
+
if (e !== DECLINE) throw e
|
|
5468
|
+
restoreCtx(ctx, saved)
|
|
5469
|
+
return null
|
|
5470
|
+
}
|
|
5471
|
+
}
|
|
5472
|
+
// The collecting code for `sub` at `v`, behind its guard when there is one.
|
|
5473
|
+
function genGuardedC (sub, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5474
|
+
const gi = ctx.varCounter++
|
|
5475
|
+
const gv = `_gv${gi}`
|
|
5476
|
+
const g = subtreeGuard(sub, ctx, gv)
|
|
5477
|
+
if (g === null) { genCodeC(sub, v, pathExpr, lines, ctx, schemaPrefix); return }
|
|
5478
|
+
if (g.name !== undefined) lines.push(`if(!${g.name}(${v})){`)
|
|
5479
|
+
else {
|
|
5480
|
+
// The verdict's exits become jumps out of the block: a failure leaves
|
|
5481
|
+
// the flag set, success clears it. replaceTopLevel keeps nested
|
|
5482
|
+
// closures' returns as they are (see the hybrid rewrite).
|
|
5483
|
+
lines.push(`{const ${gv}=${v};let _g${gi}=1;g${gi}:{${replaceTopLevel(g.body, `{_g${gi}=0;break g${gi}}`, `break g${gi}`)};_g${gi}=0}if(_g${gi}){`)
|
|
5484
|
+
}
|
|
5485
|
+
ctx.guardDepth = (ctx.guardDepth || 0) + 1
|
|
5486
|
+
try { genCodeC(sub, v, pathExpr, lines, ctx, schemaPrefix) } finally { ctx.guardDepth-- }
|
|
5487
|
+
lines.push(g.name !== undefined ? '}' : '}}')
|
|
5488
|
+
}
|
|
5489
|
+
|
|
5490
|
+
// A branch's error-collecting body rewritten to count what it would have
|
|
5491
|
+
// collected: each `_e.push({keyword:'k',...})` becomes a count and a severity
|
|
5492
|
+
// (lib/branch-collapse.js, __ataScore), and the body returns the score the
|
|
5493
|
+
// collapse would compute from the errors. When no branch of a oneOf or anyOf
|
|
5494
|
+
// holds, the collapse reports the closest branch's errors; it used to run
|
|
5495
|
+
// every branch's error function to score them, building every branch's
|
|
5496
|
+
// errors to keep one branch's. Now the counting bodies score, and only the
|
|
5497
|
+
// closest branch's error function runs. Null for a body the rewrite cannot
|
|
5498
|
+
// follow (a shared definition call, a nested collapse, a push that is not a
|
|
5499
|
+
// literal); that site keeps the old path.
|
|
5500
|
+
function countingBody (body) {
|
|
5501
|
+
// A nested oneOf or anyOf that does not hold is one collapsed error in the
|
|
5502
|
+
// branch's list (keyword oneOf or anyOf, severity 4): its marked section,
|
|
5503
|
+
// which would run the inner branches to build it, becomes that count.
|
|
5504
|
+
// Two kinds of section: CB is one error whatever the branches would say;
|
|
5505
|
+
// CU is a section the copy cannot score (the value is not the one the
|
|
5506
|
+
// counts were taken for), so the copy answers -1 and the picker runs the
|
|
5507
|
+
// branches as before.
|
|
5508
|
+
while (true) {
|
|
5509
|
+
const b1 = body.indexOf('/*CB*/'), b2 = body.indexOf('/*CU*/')
|
|
5510
|
+
const b = b1 < 0 ? b2 : b2 < 0 ? b1 : Math.min(b1, b2)
|
|
5511
|
+
if (b < 0) break
|
|
5512
|
+
const unknown = b === b2
|
|
5513
|
+
let depth = 1
|
|
5514
|
+
let j = b + 6
|
|
5515
|
+
while (j < body.length && depth > 0) {
|
|
5516
|
+
if (body.startsWith('/*CB*/', j) || body.startsWith('/*CU*/', j)) { depth++; j += 6; continue }
|
|
5517
|
+
if (body.startsWith('/*CE*/', j)) { depth--; j += 6; continue }
|
|
5518
|
+
j++
|
|
5519
|
+
}
|
|
5520
|
+
if (depth !== 0) return null
|
|
5521
|
+
body = body.slice(0, b) + (unknown ? 'return -1' : '(_n++,_s+=4)') + body.slice(j)
|
|
5522
|
+
}
|
|
5523
|
+
if (/\b(?:_defE\d|__ataCollapse|__ataMulti|_brf\d|_bsf\d)/.test(body)) return null
|
|
5524
|
+
const SEV = require('./branch-collapse').SEVERITY
|
|
5525
|
+
let out = ''
|
|
5526
|
+
let i = 0
|
|
5527
|
+
const head = 'const _e=[];'
|
|
5528
|
+
if (!body.startsWith(head)) return null
|
|
5529
|
+
out += 'let _n=0,_s=0;'
|
|
5530
|
+
i = head.length
|
|
5531
|
+
while (i < body.length) {
|
|
5532
|
+
const ch = body[i]
|
|
5533
|
+
if (ch === '"' || ch === "'" || ch === '`') { const j = skipString(body, i); out += body.slice(i, j); i = j; continue }
|
|
5534
|
+
if (body.startsWith('_e.push(', i)) {
|
|
5535
|
+
let j = i + 8
|
|
5536
|
+
let depth = 1
|
|
5537
|
+
while (j < body.length && depth > 0) {
|
|
5538
|
+
const c = body[j]
|
|
5539
|
+
if (c === '"' || c === "'" || c === '`') { j = skipString(body, j); continue }
|
|
5540
|
+
if (c === '(' || c === '{' || c === '[') depth++
|
|
5541
|
+
else if (c === ')' || c === '}' || c === ']') depth--
|
|
5542
|
+
j++
|
|
5543
|
+
}
|
|
5544
|
+
if (depth !== 0) return null
|
|
5545
|
+
const arg = body.slice(i + 8, j - 1)
|
|
5546
|
+
// The literal may still carry its `code` (the shape pass strips it).
|
|
5547
|
+
const m = /^\{(?:code:'ATA\d{4}',)?keyword:'([A-Za-z$][\w$]*)',/.exec(arg)
|
|
5548
|
+
if (m === null) return null
|
|
5549
|
+
out += `(_n++,_s+=${SEV[m[1]] || 4})`
|
|
5550
|
+
i = j
|
|
5551
|
+
continue
|
|
5552
|
+
}
|
|
5553
|
+
if (body.startsWith('return{valid:false,errors:_e}', i)) { out += 'return _n*100+_s'; i += 29; continue }
|
|
5554
|
+
if (body.startsWith('return{valid:_e.length===0,errors:_e}', i)) { out += 'return _n*100+_s'; i += 37; continue }
|
|
5555
|
+
if (body.startsWith('_e.length', i)) { out += '_n'; i += 9; continue }
|
|
5556
|
+
out += ch
|
|
5557
|
+
i++
|
|
5558
|
+
}
|
|
5559
|
+
if (/\b_e\b/.test(out.replace(/'[^']*'|"[^"]*"/g, ''))) return null
|
|
5560
|
+
return out
|
|
5561
|
+
}
|
|
5562
|
+
|
|
5153
5563
|
function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5154
5564
|
ctx.usesBranchCollapse = true
|
|
5155
5565
|
const fi = ctx.varCounter++
|
|
@@ -5211,23 +5621,59 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
|
|
|
5211
5621
|
// In the combined function `_e` is created on the first error and every
|
|
5212
5622
|
// error is collected (`_all` is true there), so the report only pushes.
|
|
5213
5623
|
const report = ctx.inCombined && !ctx.inBranchFn
|
|
5214
|
-
? `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed})(_e=_ap(_e,${collapsed}))`
|
|
5624
|
+
? `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed})(_e=_ap(_A,_e,${collapsed},${apOrd(ctx, branchSp)}))`
|
|
5215
5625
|
: `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed}){_e.push(${collapsed});if(!_all)return{valid:false,errors:_e}}`
|
|
5626
|
+
// Score first, collect once (countingBody), in the combined function where
|
|
5627
|
+
// the collapsed error is built in its final shape (no ordering key).
|
|
5628
|
+
let noneReport = null
|
|
5629
|
+
if (ctx.inCombined && (ctx.noOrdinal ? 'null' : '') === 'null') {
|
|
5630
|
+
const cbs = fns.map((b) => b === '' ? 'return 0' : countingBody(b))
|
|
5631
|
+
if (!cbs.some((c) => c === null)) {
|
|
5632
|
+
const sfx = usesSp ? ',_sp' : ''
|
|
5633
|
+
const code = branchKw === 'oneOf' ? 'ATA4001' : 'ATA4003'
|
|
5634
|
+
// `code: ` with the space: the runtime shape strips this generator's own
|
|
5635
|
+
// `code:'ATA....',` from the literals and keeps the collapse's, which
|
|
5636
|
+
// the reader expects to carry it (see the shape pass below).
|
|
5637
|
+
const lit = `{code: '${code}',keyword:'${branchKw}',instancePath:_pp${fi}||'',path:_pp${fi}||'',schemaPath:'${branchSp}',message:'value matched 0 of ${branches.length} ${branchKw} variants',params:{variants:${branches.length},closest:_bi${fi},closestName:_bt${fi}[_bi${fi}]||('variant '+(_bi${fi}+1))},branchErrors:_br${fi}.errors}`
|
|
5638
|
+
// In a branch function the error joins that function's list (the
|
|
5639
|
+
// combined function's `_ap` and ordering state are not its).
|
|
5640
|
+
const push = ctx.inBranchFn ? `_e.push(${lit});if(!_all)return{valid:false,errors:_e}` : `(_e=_ap(_A,_e,${lit},${apOrd(ctx, branchSp)}))`
|
|
5641
|
+
if (cbs.length === 1) {
|
|
5642
|
+
// One branch is the closest one: nothing to score, no counting copy.
|
|
5643
|
+
// Scoring it ran the branch a third time (verdict, count, errors).
|
|
5644
|
+
noneReport = `{let _bi${fi}=0;const _br${fi}=${fnArr}[0](${v},_pp${fi},_all${sfx});${push}}`
|
|
5645
|
+
} else {
|
|
5646
|
+
ctx.helperCode.push(cbs.map((b, i) => `const _bsf${fi}_${i}=function(${params}){${b}}`).join(';'))
|
|
5647
|
+
const picks = cbs.map((_, i) => `{const _s=_bsf${fi}_${i}(${v},_pp${fi},_all${sfx});if(_s<0)_ng${fi}=1;else if(_s<_bs${fi}){_bs${fi}=_s;_bi${fi}=${i}}}`).join('')
|
|
5648
|
+
// A copy that could not score (-1): every branch runs, as before.
|
|
5649
|
+
const noneOld = branchKw === 'anyOf'
|
|
5650
|
+
? `const ${resArr}=[];for(let _bi=0;_bi<${branches.length};_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}`
|
|
5651
|
+
: `const ${resArr}=[];for(let _bi=0;_bi<${branches.length};_bi++)${resArr}.push(${call});${report}`
|
|
5652
|
+
noneReport = `{let _bi${fi}=0,_bs${fi}=Infinity,_ng${fi}=0;${picks}if(_ng${fi}){${noneOld}}else{const _br${fi}=${fnArr}[_bi${fi}](${v},_pp${fi},_all${sfx});${push}}}`
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5216
5656
|
// The node's runtime unevaluatedProperties already counted how many of
|
|
5217
5657
|
// these branches pass, through the annotation functions, which answer the
|
|
5218
5658
|
// same validity. A oneOf with exactly one, an anyOf with any, holds and
|
|
5219
5659
|
// reports nothing, so its branches are not run a second time to say so.
|
|
5220
5660
|
// Only in the combined function proper, where that count is in scope.
|
|
5221
|
-
|
|
5661
|
+
// Inside a branch function only a factory-scope count (factoryCount) is
|
|
5662
|
+
// in reach; the node's own counts are locals of the combined function.
|
|
5663
|
+
const countedRaw = ctx.inCombined && ctx.branchCounts ? ctx.branchCounts.get(branches) : undefined
|
|
5664
|
+
const counted = countedRaw !== undefined && (!ctx.inBranchFn || countedRaw.factory) ? countedRaw : undefined
|
|
5222
5665
|
const same = counted !== undefined ? `${v}!==null&&typeof ${v}==='object'&&${counted.of}===${v}` : ''
|
|
5223
5666
|
let guard = counted !== undefined
|
|
5224
5667
|
? (keyword === 'anyOf' ? `if(!(${same}&&${counted.count}>=1))` : `if(!(${same}&&${counted.count}===1))`)
|
|
5225
5668
|
: ''
|
|
5226
5669
|
// More than one oneOf branch passed: the error names them and carries no
|
|
5227
5670
|
// branch errors, so it is built from the bits the count left.
|
|
5228
|
-
if (guard !== '' && keyword === 'oneOf' && counted.mask && ctx.inCombined
|
|
5671
|
+
if (guard !== '' && keyword === 'oneOf' && counted.mask && ctx.inCombined) {
|
|
5229
5672
|
const ord0 = ctx.noOrdinal ? 'null' : (() => { const o = ordinalFor(ctx.rootSchema, unescapeSp(branchSp)); return o === null ? 'null' : o })()
|
|
5230
|
-
|
|
5673
|
+
const multiC = `__ataMulti(_pi${fi},${branches.length},${pp},'${branchSp}',${ord0})`
|
|
5674
|
+
const pushC = ctx.inBranchFn ? `_e.push(${multiC});if(!_all)return{valid:false,errors:_e}` : `(_e=_ap(_A,_e,${multiC},${apOrd(ctx, branchSp)}))`
|
|
5675
|
+
// The marked section is one error in a counting copy (countingBody).
|
|
5676
|
+
guard = `if(${same}&&${counted.count}>1){/*CB*/const _pi${fi}=[];for(let _k=0;_k<${branches.length};_k++)if((${counted.mask}>>_k)&1)_pi${fi}.push(_k);${pushC}/*CE*/}else if(!(${same}&&${counted.count}===1))`
|
|
5231
5677
|
}
|
|
5232
5678
|
// Without such a count, each branch also gets a verdict function, and the
|
|
5233
5679
|
// passing branches are counted with those: a oneOf with exactly one and an
|
|
@@ -5272,29 +5718,49 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
|
|
|
5272
5718
|
restoreCtx(ctx, saved)
|
|
5273
5719
|
}
|
|
5274
5720
|
}
|
|
5721
|
+
// In the combined function (the runtime path; a standalone module cannot
|
|
5722
|
+
// carry a compiled function) a branch that holds a reference, or a site
|
|
5723
|
+
// whose inline verdicts were too large, gets a verdict compiled on its own
|
|
5724
|
+
// and handed in as a closure value: the branch with the root's definitions
|
|
5725
|
+
// under it, through compileToJSCodegen, with its own shared definitions and
|
|
5726
|
+
// its own regex engines. Without one, every branch's error function ran on
|
|
5727
|
+
// every value the keyword saw, accepted or not: json-patch's six-way oneOf
|
|
5728
|
+
// cost 1363 ns on a valid list of patches where its verdict costs 261.
|
|
5729
|
+
if (vfArr === null && guard === '' && ctx.inCombined) {
|
|
5730
|
+
const compiled = compileBranchVerdicts(branches, ctx)
|
|
5731
|
+
if (compiled !== null) {
|
|
5732
|
+
vfArr = `_bvf${fi}`
|
|
5733
|
+
for (let i = 0; i < compiled.length; i++) { ctx.closureVars.push(`${vfArr}_${i}`); ctx.closureVals.push(compiled[i]) }
|
|
5734
|
+
}
|
|
5735
|
+
}
|
|
5275
5736
|
if (vfArr !== null) {
|
|
5276
5737
|
const L = branches.length
|
|
5277
5738
|
const top = ctx.inCombined && !ctx.inBranchFn
|
|
5278
5739
|
// An accepted document is still answered by the verdict first (see the
|
|
5279
5740
|
// result shape in compileToJSCombined): that is the fastest path to it,
|
|
5280
5741
|
// and dropping it measured 20 ns slower per accepted oneOf.
|
|
5281
|
-
if (top) ctx.unguardedCollapse = true
|
|
5742
|
+
if (top && !ctx.guardDepth) ctx.unguardedCollapse = true
|
|
5282
5743
|
const tests = branches.map((_, i) => `${vfArr}_${i}(${v})`)
|
|
5283
5744
|
if (keyword === 'anyOf') {
|
|
5284
|
-
|
|
5745
|
+
const none = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}`
|
|
5746
|
+
// In a branch function the section a counting copy replaces is marked
|
|
5747
|
+
// (countingBody): when the keyword does not hold, the error function
|
|
5748
|
+
// reports one collapsed error, whatever the branches would say.
|
|
5749
|
+
lines.push(`{const _pp${fi}=${pp};if(!(${tests.join('||')})){${top ? '' : '/*CB*/'}if(_all===0){_e.push(null);return{valid:false,errors:_e}}${none}${top ? '' : '/*CE*/'}}}`)
|
|
5285
5750
|
} else {
|
|
5286
5751
|
const mk = L <= 30
|
|
5287
5752
|
const collect = mk
|
|
5288
5753
|
? `const _pi${fi}=[];for(let _k=0;_k<${L};_k++)if((_m${fi}>>_k)&1)_pi${fi}.push(_k);`
|
|
5289
5754
|
: ''
|
|
5290
5755
|
const multi = `__ataMulti(_pi${fi},${L},_pp${fi},'${branchSp}',${ord})`
|
|
5291
|
-
const pushMulti = top ? `(_e=_ap(_e,${multi}))` : `_e.push(${multi});if(!_all)return{valid:false,errors:_e}`
|
|
5756
|
+
const pushMulti = top ? `(_e=_ap(_A,_e,${multi},${apOrd(ctx, branchSp)}))` : `_e.push(${multi});if(!_all)return{valid:false,errors:_e}`
|
|
5292
5757
|
const count = tests.map((t, i) => `if(${t}){_n${fi}++;${mk ? `_m${fi}|=${1 << i}` : `_pi${fi}.push(${i})`}}`).join('')
|
|
5293
|
-
|
|
5758
|
+
const none = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++)${resArr}.push(${call});${report}`
|
|
5759
|
+
lines.push(`{const _pp${fi}=${pp};let _n${fi}=0${mk ? `,_m${fi}=0` : `,_pi${fi}=[]`};${count}if(_n${fi}!==1){${top ? '' : '/*CB*/'}if(_all===0){_e.push(null);return{valid:false,errors:_e}}if(_n${fi}>1){${collect}${pushMulti}}else{${none}}${top ? '' : '/*CE*/'}}}`)
|
|
5294
5760
|
}
|
|
5295
5761
|
return
|
|
5296
5762
|
}
|
|
5297
|
-
if (guard === '' && ctx.inCombined && !ctx.inBranchFn) ctx.unguardedCollapse = true
|
|
5763
|
+
if (guard === '' && ctx.inCombined && !ctx.inBranchFn && !ctx.guardDepth) ctx.unguardedCollapse = true
|
|
5298
5764
|
if (keyword === 'anyOf') {
|
|
5299
5765
|
// anyOf reports nothing once a branch passes, so the branches are first
|
|
5300
5766
|
// only asked whether they pass. They are called with `_all` set to 0, a
|
|
@@ -5308,12 +5774,24 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
|
|
|
5308
5774
|
// no one read. Only when no branch passes do they run again with the
|
|
5309
5775
|
// caller's `_all`, and the collapse sees them all, as before.
|
|
5310
5776
|
const probe = `${fnArr}[_bi](${v},_pp${fi},0${usesSp ? ',_sp' : ''})`
|
|
5311
|
-
|
|
5777
|
+
const none = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}`
|
|
5778
|
+
const probeOld = `let _bp${fi}=false;for(let _bi=0;_bi<${fnArr}.length;_bi++){if(${probe}.valid){_bp${fi}=true;break}}if(!_bp${fi}){if(_all===0){_e.push(null);return{valid:false,errors:_e}}${none}}`
|
|
5779
|
+
// With a count: on the counted object the guard already said no branch
|
|
5780
|
+
// held (CB, one error in a counting copy); on another object the branches
|
|
5781
|
+
// are probed as before (CU, a counting copy cannot know).
|
|
5782
|
+
if (counted !== undefined) lines.push(`${guard}{const _pp${fi}=${pp};if(${same}){/*CB*/if(_all===0){_e.push(null);return{valid:false,errors:_e}}${none}/*CE*/}else{/*CU*/${probeOld}/*CE*/}}`)
|
|
5783
|
+
else lines.push(`${guard}{const _pp${fi}=${pp};${probeOld}}`)
|
|
5312
5784
|
} else {
|
|
5313
5785
|
// Asked only whether it passes (`_all` 0, see anyOf above), oneOf counts
|
|
5314
5786
|
// the passing branches and answers; the branch errors of a probe are not
|
|
5315
5787
|
// errors anyone reads and are not collapsed.
|
|
5316
|
-
|
|
5788
|
+
// With a count from the node's unevaluatedProperties (`counted`), a count
|
|
5789
|
+
// of zero on the counted object means no branch held, which is the case
|
|
5790
|
+
// the closest-branch path answers without running every branch.
|
|
5791
|
+
const oldO = `const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++)${resArr}.push(${call});if(_all===0){let _n=0;for(const _r of ${resArr})if(_r.valid)_n++;if(_n!==1){_e.push(null);return{valid:false,errors:_e}}}else{${report}}`
|
|
5792
|
+
const noneO = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++)${resArr}.push(${call});${report}`
|
|
5793
|
+
if (counted !== undefined) lines.push(`${guard}{const _pp${fi}=${pp};if(${same}){/*CB*/if(_all===0){_e.push(null);return{valid:false,errors:_e}}${noneO}/*CE*/}else{/*CU*/${oldO}/*CE*/}}`)
|
|
5794
|
+
else lines.push(`${guard}{const _pp${fi}=${pp};${oldO}}`)
|
|
5317
5795
|
}
|
|
5318
5796
|
}
|
|
5319
5797
|
|
|
@@ -5340,19 +5818,60 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5340
5818
|
ctx.regExpMap = new Map();
|
|
5341
5819
|
}
|
|
5342
5820
|
// $ref — resolve local and cross-schema refs
|
|
5343
|
-
if (schema.$ref) {
|
|
5344
|
-
//
|
|
5345
|
-
//
|
|
5346
|
-
//
|
|
5347
|
-
//
|
|
5348
|
-
//
|
|
5349
|
-
//
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5821
|
+
if (schema.$ref && !schema[REF_DONE]) {
|
|
5822
|
+
// A keyword beside $ref that validates applies too (2020-12): the
|
|
5823
|
+
// reference is written on its own, then the node again with the
|
|
5824
|
+
// reference marked done, on a fresh copy that keeps `$ref` for the
|
|
5825
|
+
// annotation readers, as genCodeCNode does. This used to decline, which
|
|
5826
|
+
// kept the 2020-12 meta-schema (every vocabulary schema is a reference
|
|
5827
|
+
// with siblings once unrolled by scope) off both error generators and on
|
|
5828
|
+
// the interpreter for its errors, about 2 microseconds a rejection.
|
|
5829
|
+
if (Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))) {
|
|
5830
|
+
genCodeENode({ $ref: schema.$ref }, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5831
|
+
const rest = { ...schema }
|
|
5832
|
+
Object.defineProperty(rest, REF_DONE, { value: true })
|
|
5833
|
+
genCodeENode(rest, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5834
|
+
return
|
|
5835
|
+
}
|
|
5836
|
+
// A self-reference is a call to a helper over the root schema that takes
|
|
5837
|
+
// the path and the schema path as arguments, as a definition on a cycle
|
|
5838
|
+
// does below: the root helper takes no path, so errors raised through it
|
|
5839
|
+
// carried the root's path, and this declined, which left a standalone
|
|
5840
|
+
// module reporting one stub error for a schema with `$ref: "#"` anywhere
|
|
5841
|
+
// in it. This used to fall through as a permissive no-op, which accepted
|
|
5842
|
+
// whatever the reference guarded.
|
|
5843
|
+
if (schema.$ref === '#') {
|
|
5844
|
+
// The body comes from the prepared root (`rootE`), whose references
|
|
5845
|
+
// are the resolvable ones; from the schema as given, a reference by
|
|
5846
|
+
// `$id` resolved to nothing and the helper for the suite's recursive
|
|
5847
|
+
// tree checked no node.
|
|
5848
|
+
if (!ctx.rootE || typeof ctx.rootE !== 'object') throw DECLINE
|
|
5849
|
+
if (!ctx.defFns) ctx.defFns = new Map()
|
|
5850
|
+
let fnName = ctx.defFns.get('#')
|
|
5851
|
+
if (!fnName) {
|
|
5852
|
+
fnName = '_defE' + ctx.defFns.size + '_root'
|
|
5853
|
+
ctx.defFns.set('#', fnName)
|
|
5854
|
+
// The helper is its own function, so the references expanded in it
|
|
5855
|
+
// start from nothing: inside the expansion of a definition that led
|
|
5856
|
+
// here, the definition's own reference would otherwise read as
|
|
5857
|
+
// already being expanded.
|
|
5858
|
+
const bodyLines = []
|
|
5859
|
+
const outerStack = ctx.refStack
|
|
5860
|
+
ctx.refStack = new Set()
|
|
5861
|
+
try { genCodeE(ctx.rootE, 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER) } finally { ctx.refStack = outerStack }
|
|
5862
|
+
let body = bodyLines.join('\n ')
|
|
5863
|
+
body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
|
|
5864
|
+
if (body.includes(SP_PLACEHOLDER)) throw DECLINE
|
|
5865
|
+
ctx.helperCode.push(
|
|
5866
|
+
`const ${fnName}_s=new Set()\n function ${fnName}(d,_p,_all,_e,_sp){\n ` +
|
|
5867
|
+
`if(_sg){if(typeof d!=='object'||d===null)return ${fnName}_b(d,_p,_all,_e,_sp);if(${fnName}_s.has(d))return;${fnName}_s.add(d);try{return ${fnName}_b(d,_p,_all,_e,_sp)}finally{${fnName}_s.delete(d)}}\n ` +
|
|
5868
|
+
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d,_p,_all,_e,_sp)\n _sd--\n return _r\n }\n ` +
|
|
5869
|
+
`function ${fnName}_b(d,_p,_all,_e,_sp){${body}}`,
|
|
5870
|
+
)
|
|
5871
|
+
}
|
|
5872
|
+
lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e,'${schemaPrefix}');if(!_all&&_e.length)return{valid:false,errors:_e}`)
|
|
5873
|
+
return
|
|
5874
|
+
}
|
|
5356
5875
|
const m = schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
|
|
5357
5876
|
if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
|
|
5358
5877
|
const defName = m[1]
|
|
@@ -5392,7 +5911,10 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5392
5911
|
lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e,'${schemaPrefix}');if(!_all&&_e.length)return{valid:false,errors:_e}`)
|
|
5393
5912
|
return
|
|
5394
5913
|
}
|
|
5395
|
-
|
|
5914
|
+
// A reference back into what is being expanded, on a cycle the
|
|
5915
|
+
// definitions alone do not show (through the root, or another
|
|
5916
|
+
// document): declined. Returning emitted nothing, which read as valid.
|
|
5917
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5396
5918
|
ctx.refStack.add(schema.$ref)
|
|
5397
5919
|
genCodeE(ctx.rootDefs[defName], v, pathExpr, lines, ctx, schemaPrefix)
|
|
5398
5920
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -5403,7 +5925,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5403
5925
|
const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
|
|
5404
5926
|
const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
|
|
5405
5927
|
if (anchorTarget) {
|
|
5406
|
-
if (ctx.refStack.has(schema.$ref))
|
|
5928
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5407
5929
|
ctx.refStack.add(schema.$ref)
|
|
5408
5930
|
genCodeE(anchorTarget, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5409
5931
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -5411,7 +5933,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5411
5933
|
}
|
|
5412
5934
|
}
|
|
5413
5935
|
if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
|
|
5414
|
-
if (ctx.refStack.has(schema.$ref))
|
|
5936
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5415
5937
|
ctx.refStack.add(schema.$ref)
|
|
5416
5938
|
genCodeE(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
|
|
5417
5939
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -5421,13 +5943,16 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5421
5943
|
if (ctx.schemaMap && schema.$ref.includes('#') && !schema.$ref.startsWith('#')) {
|
|
5422
5944
|
const r = resolveCrossSchemaRef(schema.$ref, ctx.schemaMap)
|
|
5423
5945
|
if (r) {
|
|
5424
|
-
if (ctx.refStack.has(schema.$ref))
|
|
5946
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5425
5947
|
ctx.refStack.add(schema.$ref)
|
|
5426
5948
|
genCodeE(r.schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5427
5949
|
ctx.refStack.delete(schema.$ref)
|
|
5428
5950
|
return
|
|
5429
5951
|
}
|
|
5430
5952
|
}
|
|
5953
|
+
// A reference none of the above resolved: declined. Falling through
|
|
5954
|
+
// emitted nothing for it, which read as valid.
|
|
5955
|
+
throw DECLINE
|
|
5431
5956
|
}
|
|
5432
5957
|
|
|
5433
5958
|
// $dynamicRef — resolve via anchors map
|
|
@@ -6019,8 +6544,63 @@ function sharedErr (lit, rootSchema) {
|
|
|
6019
6544
|
// the value at `pathExpr`, which enrichment otherwise finds again by walking
|
|
6020
6545
|
// the document. `paramsShared` names a frozen params object hoisted for a
|
|
6021
6546
|
// static site, so a static error's params stay one shared object as before.
|
|
6547
|
+
// The ordinal `_ap` is handed with each error of the combined function: the
|
|
6548
|
+
// error's place in schema order when its path is fixed at compile time, -1
|
|
6549
|
+
// when it is not (a path through a shared definition, a collapsed branch
|
|
6550
|
+
// error, an enriched error). `_ap` keeps whether the list is still in order
|
|
6551
|
+
// as it grows; a list that came out in order, which is most of them, is
|
|
6552
|
+
// handed out without the sort that looked every error's path up in a map.
|
|
6553
|
+
function apOrd (ctx, sp, coarse) {
|
|
6554
|
+
// Inside a shared definition's function every error takes the ordinal of
|
|
6555
|
+
// the reference that reached it, handed in as `_so`: errors behind one
|
|
6556
|
+
// `$ref` sort where the reference is written, in the order the definition
|
|
6557
|
+
// reports them.
|
|
6558
|
+
if (typeof sp === 'string' && sp.includes(SP_PLACEHOLDER) && ctx && ctx.inDefC) return '_so'
|
|
6559
|
+
if (typeof sp !== 'string' || sp.includes(SP_PLACEHOLDER) || sp.includes('+')) return -1
|
|
6560
|
+
if (!ctx || !ctx.rootSchema) return -1
|
|
6561
|
+
const path = unescapeSp(sp)
|
|
6562
|
+
const o = ordinalFor(ctx.rootSchema, path)
|
|
6563
|
+
if (typeof o !== 'number') return -1
|
|
6564
|
+
// The reader orders by ordinal and, between equal ordinals, by the rank of
|
|
6565
|
+
// the path (tieOrder in lib/rejections.js). Both are known here, so a site
|
|
6566
|
+
// gets a token that assignOrdKeys turns into a dense integer in exactly that
|
|
6567
|
+
// order when the program is assembled: two errors with one key are ones the
|
|
6568
|
+
// reader keeps as they came. Keyed by the ordinal alone, two errors under
|
|
6569
|
+
// one synthetic definition shared a key and sent every such list to the
|
|
6570
|
+
// reader, which ranked the paths again: 45 percent of reading a list
|
|
6571
|
+
// schema's two errors.
|
|
6572
|
+
if (!ctx.ordSites) ctx.ordSites = new Map()
|
|
6573
|
+
let site = ctx.ordSites.get(path)
|
|
6574
|
+
if (site === undefined) { site = { i: ctx.ordSites.size, o, r: rankFor(ctx.rootSchema, path), key: -1 }; ctx.ordSites.set(path, site) }
|
|
6575
|
+
// A coarse key, for the errors behind a reference into a shared definition
|
|
6576
|
+
// (see `ap` in lib/combined-runtime.js): -(k+2), written when k is known.
|
|
6577
|
+
return coarse ? `@@C${site.i}@@` : `@@O${site.i}@@`
|
|
6578
|
+
}
|
|
6579
|
+
|
|
6580
|
+
// The dense keys of a program's error sites, in the reader's order: ordinal,
|
|
6581
|
+
// then rank as tieOrder compares it, element by element and the shorter
|
|
6582
|
+
// first; equal pairs share a key, and a site without a rank keeps -1, which
|
|
6583
|
+
// hands its list to the reader. Replaces the tokens apOrd wrote.
|
|
6584
|
+
function assignOrdKeys (ctx, src) {
|
|
6585
|
+
if (!ctx.ordSites || ctx.ordSites.size === 0) return src
|
|
6586
|
+
const sites = [...ctx.ordSites.values()]
|
|
6587
|
+
const cmp = (a, b) => {
|
|
6588
|
+
if (a.o !== b.o) return a.o - b.o
|
|
6589
|
+
const m = Math.min(a.r.length, b.r.length)
|
|
6590
|
+
for (let k = 0; k < m; k++) if (a.r[k] !== b.r[k]) return a.r[k] - b.r[k]
|
|
6591
|
+
return a.r.length - b.r.length
|
|
6592
|
+
}
|
|
6593
|
+
const ranked = sites.filter((x) => x.r !== null).sort(cmp)
|
|
6594
|
+
let key = 0
|
|
6595
|
+
for (let i = 0; i < ranked.length; i++) { if (i > 0 && cmp(ranked[i - 1], ranked[i]) !== 0) key++; ranked[i].key = key }
|
|
6596
|
+
return src.replace(/@@([OC])(\d+)@@/g, (_, t, i) => { const k = sites[+i].key; return String(t === 'O' || k < 0 ? k : -(k + 2)) })
|
|
6597
|
+
}
|
|
6598
|
+
|
|
6599
|
+
// The error list and its order live in lib/combined-runtime.js (`ap`, `sr`):
|
|
6600
|
+
// `_A` is this program's state, reset at the top of each call.
|
|
6601
|
+
const AP_RESET = '_A.mo=true;_A.lo=-1;_A.on=0;'
|
|
6022
6602
|
function errPushC(ctx, keyword, sp, pathExpr, paramsCode, msgCode, valueExpr, lean, paramsShared) {
|
|
6023
|
-
if (!ctx.rich) return `(_e=_ap(_e,${lean}))`
|
|
6603
|
+
if (!ctx.rich) return `(_e=_ap(_A,_e,${lean},${apOrd(ctx, sp)}))`
|
|
6024
6604
|
if (!ctx.richMk) {
|
|
6025
6605
|
ctx.richMk = true
|
|
6026
6606
|
const { makeRich } = require('./enrich-site')
|
|
@@ -6036,7 +6616,7 @@ function errPushC(ctx, keyword, sp, pathExpr, paramsCode, msgCode, valueExpr, le
|
|
|
6036
6616
|
// the definition and the reference's own comes in as `_sp`.
|
|
6037
6617
|
const rel = sp.startsWith(SP_PLACEHOLDER)
|
|
6038
6618
|
ctx.closureVals.push(errorSite(keyword, unescapeSp(rel ? sp.slice(SP_PLACEHOLDER.length) : sp), paramsVal, fmt))
|
|
6039
|
-
return `(_e=_ap(_e,_mk(${siteVar},${pathExpr || '""'},${paramsShared || paramsCode},${msgCode},${valueExpr},d${rel ? ',_sp' : ''})))`
|
|
6619
|
+
return `(_e=_ap(_A,_e,_mk(${siteVar},${pathExpr || '""'},${paramsShared || paramsCode},${msgCode},${valueExpr},d${rel ? ',_sp' : ''}),-1))`
|
|
6040
6620
|
}
|
|
6041
6621
|
|
|
6042
6622
|
function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts) {
|
|
@@ -6122,7 +6702,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
6122
6702
|
// creates it as a one-element literal. Pushing onto an empty `[]` grew its
|
|
6123
6703
|
// backing store on every rejection, about 7% of reading errors across the
|
|
6124
6704
|
// official suite.
|
|
6125
|
-
const ctx = { noOrdinal: !!(opts && opts.runtimeShape), rich: !!(opts && opts.rich), inCombined: true, hoisted: [], rootC: schema, varCounter: 0,
|
|
6705
|
+
const ctx = { noOrdinal: !!(opts && opts.runtimeShape), rich: !!(opts && opts.rich), inCombined: true, hoisted: [], rootC: schema, varCounter: 0, preamble: [], helperCode: [], shared: [], closureVars: ['_cpLen', '_A', '_ap', '_sr', '_srX'], closureVals: [_cpLen, sharedRt().apState(), sharedRt().ap, sharedRt().sr, sharedRt().srX], sharedRuntime: true,
|
|
6126
6706
|
rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: inputSchema, userFormats: userFormats || null }
|
|
6127
6707
|
const lines = []
|
|
6128
6708
|
try {
|
|
@@ -6143,6 +6723,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
6143
6723
|
ctx.closureVars.push('_ER', '_EE', '_SRT', '_FB', '_VF')
|
|
6144
6724
|
ctx.closureVals.push(opts.resultShape.Rejection, opts.resultShape.empty, opts.resultShape.sort, opts.resultShape.fallback, opts.resultShape.verdict || null)
|
|
6145
6725
|
}
|
|
6726
|
+
if (ctx.usesBranchCollapse) bindRt(ctx, ['__ataCollapse', '__ataMulti'])
|
|
6146
6727
|
const closureNames = ctx.usesSafeRe ? [...ctx.closureVars, '__ataSafeRe'] : ctx.closureVars
|
|
6147
6728
|
const closureArgs = ctx.usesSafeRe ? [...ctx.closureVals, compileSafe] : ctx.closureVals
|
|
6148
6729
|
const closureParams = closureNames.join(',')
|
|
@@ -6161,7 +6742,6 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
6161
6742
|
// the branch functions (this function collects everything), and the
|
|
6162
6743
|
// runtime shape the error generator gives its literals, without code and
|
|
6163
6744
|
// docUrl. The collapsed error itself keeps its code, as everywhere.
|
|
6164
|
-
if (ctx.usesBranchCollapse) ctx.helperCode.push(require('./branch-collapse').embedSource())
|
|
6165
6745
|
if (ctx.usesBranchCollapse && opts && opts.runtimeShape) {
|
|
6166
6746
|
const shape = (src) => src.replace(/code:'ATA\d{4}',/g, '').replace(/,docUrl:'https:\/\/ata-validator\.com\/e\/ATA\d{4}'/g, '')
|
|
6167
6747
|
for (let i = 0; i < ctx.helperCode.length; i++) ctx.helperCode[i] = shape(ctx.helperCode[i])
|
|
@@ -6182,7 +6762,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
6182
6762
|
ctx.helperCode.push(`function _bodyC(d){${hoistDecl}${allDecl}let _e;\n ${checks}\n return _e}`)
|
|
6183
6763
|
hoistDecl = ''
|
|
6184
6764
|
allDecl = ''
|
|
6185
|
-
checks = `_sd=0;try{_e=_bodyC(d)}catch(_err){if(_err!==_CYC)throw _err;_sg=true;${clears}
|
|
6765
|
+
checks = `_sd=0;try{_e=_bodyC(d)}catch(_err){if(_err!==_CYC)throw _err;_sg=true;${clears};${AP_RESET}try{_e=_bodyC(d)}finally{_sg=false}}`
|
|
6186
6766
|
}
|
|
6187
6767
|
// In the result shape this function is validate() itself, so a document
|
|
6188
6768
|
// that makes it throw is answered by the caller's previous path instead
|
|
@@ -6195,18 +6775,21 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
6195
6775
|
// Not when every collapse is guarded by a count (emitBranchCollapse): an
|
|
6196
6776
|
// accepted document then runs no branch twice, and a rejected one would
|
|
6197
6777
|
// pay for the verdict for nothing.
|
|
6198
|
-
? `try{${hoistDecl}${ctx.usesBranchCollapse && ctx.unguardedCollapse ? 'if(_VF!==null&&_VF(d))return{valid:true,data:d,errors:_EE};' : ''}${allDecl}let _e
|
|
6778
|
+
? `try{${hoistDecl}${ctx.usesBranchCollapse && ctx.unguardedCollapse ? 'if(_VF!==null&&_VF(d))return{valid:true,data:d,errors:_EE};' : ''}${allDecl}let _e;${AP_RESET}\n ` + checks +
|
|
6199
6779
|
// A collapsed branch error is presented even alone (its ordinal and
|
|
6200
6780
|
// raw branch errors stay inside); otherwise only a list of two or more
|
|
6201
6781
|
// needs ordering.
|
|
6202
|
-
`\n return _e?new _ER(
|
|
6203
|
-
: hoistDecl + allDecl + `let _e
|
|
6204
|
-
|
|
6782
|
+
`\n return _e?new _ER(_A.mo?_e:${process.env.ATA_CHECK_SORT ? '_srX(_A,_e,_SRT)' : '_sr(_A,_e,_SRT)'}):{valid:true,data:d,errors:_EE}}catch(_x){return _FB(d)}`
|
|
6783
|
+
: hoistDecl + allDecl + `let _e;${AP_RESET}\n ` + checks + `\n return _e?{valid:false,errors:_e}:R`
|
|
6784
|
+
// The verdict generator's hoisted functions (subtreeGuard writes guards
|
|
6785
|
+
// through it) come first: a oneOf branch it could not hoist was a closure
|
|
6786
|
+
// built at every call of the guard, six per element of a json-patch list.
|
|
6787
|
+
const helpers = (ctx.preamble.length ? ctx.preamble.join('\n ') + '\n ' : '') + (ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : '')
|
|
6205
6788
|
|
|
6206
6789
|
try {
|
|
6207
6790
|
if (typeof process !== 'undefined' && process.env && process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + helpers + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
|
|
6208
6791
|
const factory = new Function('R' + (closureParams ? ',' + closureParams : ''),
|
|
6209
|
-
`${helpers}return function(d){${inner}}`)
|
|
6792
|
+
assignOrdKeys(ctx, `${helpers}return function _vC(d){${inner}}`))
|
|
6210
6793
|
return factory(VALID_RESULT, ...closureArgs)
|
|
6211
6794
|
} catch (e) {
|
|
6212
6795
|
if (typeof process !== 'undefined' && process.env && process.env.ATA_DEBUG) console.error('compileToJSCombined error:', e.message, '\n', inner.slice(0, 500))
|
|
@@ -6262,11 +6845,32 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6262
6845
|
// declares its own branch counts (genUnevaluatedDynamicC). The document root
|
|
6263
6846
|
// comes in as `d` for the rich errors that read it. Not for a definition on a
|
|
6264
6847
|
// reference cycle, which keeps its existing path. False when not shared.
|
|
6848
|
+
const COMBINED_DEF_FN_MIN = 160
|
|
6849
|
+
const _defOwnChars = new WeakMap()
|
|
6850
|
+
// The definition's own size, its annotations left out, measured once.
|
|
6851
|
+
function defOwnChars (def) {
|
|
6852
|
+
if (def === null || typeof def !== 'object') return 0
|
|
6853
|
+
let n = _defOwnChars.get(def)
|
|
6854
|
+
if (n === undefined) {
|
|
6855
|
+
n = 0
|
|
6856
|
+
const walk = (x) => { if (x === null || typeof x !== 'object') { n += 6; return } if (Array.isArray(x)) { for (const y of x) walk(y); return } for (const k of Object.keys(x)) { if (SIZE_SKIPS.has(k)) continue; n += k.length + 4; walk(x[k]) } }
|
|
6857
|
+
walk(def)
|
|
6858
|
+
_defOwnChars.set(def, n)
|
|
6859
|
+
}
|
|
6860
|
+
return n
|
|
6861
|
+
}
|
|
6862
|
+
|
|
6265
6863
|
function emitSharedDefC(defName, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
6266
6864
|
if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
|
|
6267
6865
|
if (!ctx.sharedDefs) ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs)
|
|
6268
6866
|
const cyclic = ctx.cyclicDefs.has(defName)
|
|
6269
|
-
|
|
6867
|
+
// In the combined function every definition of some size is a function of
|
|
6868
|
+
// its own, referenced once or not: expanded, a 25-definition API schema
|
|
6869
|
+
// was one 122 KB function, which the optimizing compiler took tens of
|
|
6870
|
+
// thousands of calls to reach, so its first twenty thousand answers cost
|
|
6871
|
+
// 89 ns each where the hundred-thousandth costs 10. Functions the size of
|
|
6872
|
+
// one definition tier up early, and the hot ones first.
|
|
6873
|
+
if (!cyclic && !ctx.sharedDefs.has(defName) && !(ctx.inCombined && defOwnChars(ctx.rootDefs[defName]) >= COMBINED_DEF_FN_MIN)) return false
|
|
6270
6874
|
emitDefFnC(defName, ctx.rootDefs[defName], cyclic, v, pathExpr, lines, ctx, schemaPrefix)
|
|
6271
6875
|
return true
|
|
6272
6876
|
}
|
|
@@ -6296,8 +6900,10 @@ function emitDefFnC(key, target, cyclic, v, pathExpr, lines, ctx, schemaPrefix)
|
|
|
6296
6900
|
const bodyLines = []
|
|
6297
6901
|
let hoisted
|
|
6298
6902
|
try {
|
|
6903
|
+
ctx.inDefC = (ctx.inDefC || 0) + 1
|
|
6299
6904
|
genCodeC(target, '_dv', '_p', bodyLines, ctx, SP_PLACEHOLDER)
|
|
6300
6905
|
} finally {
|
|
6906
|
+
ctx.inDefC--
|
|
6301
6907
|
hoisted = ctx.hoisted
|
|
6302
6908
|
ctx.hoisted = outerHoisted
|
|
6303
6909
|
ctx.refStack = outerRefs
|
|
@@ -6306,19 +6912,19 @@ function emitDefFnC(key, target, cyclic, v, pathExpr, lines, ctx, schemaPrefix)
|
|
|
6306
6912
|
body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
|
|
6307
6913
|
if (body.includes(SP_PLACEHOLDER)) throw DECLINE
|
|
6308
6914
|
const decl = hoisted.length ? `let ${hoisted.map((n) => n + '=null').join(',')};` : ''
|
|
6309
|
-
const fn = `function ${cyclic ? fnName + '_b' : fnName}(_dv,_p,_sp,d){const _all=true;${decl}let _e;\n ${body}\n return _e}`
|
|
6915
|
+
const fn = `function ${cyclic ? fnName + '_b' : fnName}(_dv,_p,_sp,d,_so){const _all=true;${decl}let _e;\n ${body}\n return _e}`
|
|
6310
6916
|
if (cyclic) {
|
|
6311
6917
|
if (!ctx.cycFnsC) ctx.cycFnsC = []
|
|
6312
6918
|
ctx.cycFnsC.push(fnName)
|
|
6313
6919
|
ctx.helperCode.push(
|
|
6314
|
-
`const ${fnName}_s=new Set()\n function ${fnName}(_dv,_p,_sp,d){\n ` +
|
|
6315
|
-
`if(_sg){if(typeof _dv!=='object'||_dv===null)return ${fnName}_b(_dv,_p,_sp,d);if(${fnName}_s.has(_dv))return;${fnName}_s.add(_dv);try{return ${fnName}_b(_dv,_p,_sp,d)}finally{${fnName}_s.delete(_dv)}}\n ` +
|
|
6316
|
-
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(_dv,_p,_sp,d)\n _sd--\n return _r\n }\n ` + fn)
|
|
6920
|
+
`const ${fnName}_s=new Set()\n function ${fnName}(_dv,_p,_sp,d,_so){\n ` +
|
|
6921
|
+
`if(_sg){if(typeof _dv!=='object'||_dv===null)return ${fnName}_b(_dv,_p,_sp,d,_so);if(${fnName}_s.has(_dv))return;${fnName}_s.add(_dv);try{return ${fnName}_b(_dv,_p,_sp,d,_so)}finally{${fnName}_s.delete(_dv)}}\n ` +
|
|
6922
|
+
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(_dv,_p,_sp,d,_so)\n _sd--\n return _r\n }\n ` + fn)
|
|
6317
6923
|
} else {
|
|
6318
6924
|
ctx.helperCode.push(fn)
|
|
6319
6925
|
}
|
|
6320
6926
|
}
|
|
6321
|
-
lines.push(`{const _r=${fnName}(${v},${pathExpr || '""'},'${schemaPrefix}',d);if(_r!==undefined){if(_e===undefined)_e=_r;else for(let _i=0;_i<_r.length;_i++)_e.push(_r[_i])}}`)
|
|
6927
|
+
lines.push(`{const _r=${fnName}(${v},${pathExpr || '""'},'${schemaPrefix}',d,${apOrd(ctx, `${schemaPrefix}/$ref`, true)});if(_r!==undefined){if(_e===undefined)_e=_r;else for(let _i=0;_i<_r.length;_i++)_e.push(_r[_i])}}`)
|
|
6322
6928
|
}
|
|
6323
6929
|
|
|
6324
6930
|
const REF_DONE = Symbol('ata.refDone')
|
|
@@ -6459,12 +7065,12 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6459
7065
|
const lit = {keyword, instancePath: pathVal, schemaPath: spVal, params: Object.freeze(paramsVal), message: msgVal}
|
|
6460
7066
|
if (o !== null) lit._o = o
|
|
6461
7067
|
ctx.closureVals.push(sharedErr(lit, ctx.rootSchema))
|
|
6462
|
-
return `(_e=_ap(_e,${errVar}))`
|
|
7068
|
+
return `(_e=_ap(_A,_e,${errVar},${apOrd(ctx, sp)}))`
|
|
6463
7069
|
}
|
|
6464
7070
|
}
|
|
6465
7071
|
}
|
|
6466
7072
|
// Dynamic path (e.g., array index): inline as before
|
|
6467
|
-
return `(_e=_ap(_e,{keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode}${ordinalField(ctx, sp)}}))`
|
|
7073
|
+
return `(_e=_ap(_A,_e,{keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode}${ordinalField(ctx, sp)}},${apOrd(ctx, sp)}))`
|
|
6468
7074
|
}
|
|
6469
7075
|
|
|
6470
7076
|
// enum and const apply to a value of any type, so a value that fails
|
|
@@ -6554,7 +7160,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6554
7160
|
const pathVal = pathExpr ? literalValue(pathExpr) : ''
|
|
6555
7161
|
ctx.closureVars.push(errVar)
|
|
6556
7162
|
ctx.closureVals.push(sharedErr({keyword: 'required', instancePath: pathVal, schemaPath: unescapeSp(`${schemaPrefix}/required`), params: Object.freeze({missingProperty: key}), message: `must have required property '${key}'`}, ctx.rootSchema))
|
|
6557
|
-
lines.push(`if(${check}){(_e=_ap(_e,${errVar}))}`)
|
|
7163
|
+
lines.push(`if(${check}){(_e=_ap(_A,_e,${errVar},${apOrd(ctx, `${schemaPrefix}/required`)}))}`)
|
|
6558
7164
|
} else {
|
|
6559
7165
|
lines.push(`if(${check}){${errPushC(ctx, 'required', `${schemaPrefix}/required`, pathExpr, `{missingProperty:'${esc(key)}'}`, `"must have required property '${esc(key)}'"`, v, `{keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/required'${ordinalField(ctx, `${schemaPrefix}/required`)},params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'"}`)}}`)
|
|
6560
7166
|
}
|
|
@@ -6568,7 +7174,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6568
7174
|
const pathVal = pathExpr ? literalValue(pathExpr) : ''
|
|
6569
7175
|
ctx.closureVars.push(errVar)
|
|
6570
7176
|
ctx.closureVals.push(sharedErr({keyword: 'required', instancePath: pathVal, schemaPath: unescapeSp(`${schemaPrefix}/required`), params: Object.freeze({missingProperty: key}), message: `must have required property '${key}'`}, ctx.rootSchema))
|
|
6571
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!${ownKeyExpr(ctx, v, key)}){(_e=_ap(_e,${errVar}))}`)
|
|
7177
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!${ownKeyExpr(ctx, v, key)}){(_e=_ap(_A,_e,${errVar},${apOrd(ctx, `${schemaPrefix}/required`)}))}`)
|
|
6572
7178
|
} else {
|
|
6573
7179
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!${ownKeyExpr(ctx, v, key)}){${errPushC(ctx, 'required', `${schemaPrefix}/required`, pathExpr, `{missingProperty:'${esc(key)}'}`, `"must have required property '${esc(key)}'"`, v, `{keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/required'${ordinalField(ctx, `${schemaPrefix}/required`)},params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'"}`)}}`)
|
|
6574
7180
|
}
|
|
@@ -6667,8 +7273,12 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6667
7273
|
const checks = propKeys.map(k => `_k${ci}[_i]!==${JSON.stringify(k)}`).join('&&') || 'true'
|
|
6668
7274
|
body = `let _x${ci}=0;for(const _f${ci} in ${v}){if(${gate}){_x${ci}=1;break}}if(_x${ci}){${detail(checks)}}`
|
|
6669
7275
|
} else {
|
|
6670
|
-
|
|
6671
|
-
body
|
|
7276
|
+
// The name set lives in the closure, built once with the function: built
|
|
7277
|
+
// in the body it was a 33-name Set on every call of this function, which
|
|
7278
|
+
// is where a rejected npm-badges document spent most of its 701 ns.
|
|
7279
|
+
ctx.closureVars.push(`_a${ci}`)
|
|
7280
|
+
ctx.closureVals.push(new Set(propKeys))
|
|
7281
|
+
body = `let _x${ci}=0;for(const _f${ci} in ${v}){if(!_a${ci}.has(_f${ci})){_x${ci}=1;break}}if(_x${ci}){${detail(`!_a${ci}.has(_k${ci}[_i])`)}}`
|
|
6672
7282
|
}
|
|
6673
7283
|
// When every declared property is required, a clean object has exactly the
|
|
6674
7284
|
// declared keys: if the enumerated count equals the declared count and
|
|
@@ -6720,7 +7330,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6720
7330
|
const pathVal = pathExpr ? literalValue(pathExpr) : ''
|
|
6721
7331
|
ctx.closureVars.push(errVar)
|
|
6722
7332
|
ctx.closureVals.push(sharedErr({keyword: 'required', instancePath: pathVal, schemaPath: unescapeSp(`${schemaPrefix}/dependentRequired`), params: Object.freeze({missingProperty: dep}), message: `must have required property '${dep}'`}, ctx.rootSchema))
|
|
6723
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){(_e=_ap(_e,${errVar}))}`)
|
|
7333
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){(_e=_ap(_A,_e,${errVar},-1))}`)
|
|
6724
7334
|
} else {
|
|
6725
7335
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){${errPushC(ctx, 'required', `${schemaPrefix}/dependentRequired`, pathExpr, `{missingProperty:'${esc(dep)}'}`, `"must have required property '${esc(dep)}'"`, v, `{keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/dependentRequired'${ordinalField(ctx, `${schemaPrefix}/dependentRequired`)},params:{missingProperty:'${esc(dep)}'},message:"must have required property '${esc(dep)}'"}`)}}`)
|
|
6726
7336
|
}
|
|
@@ -6736,16 +7346,16 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6736
7346
|
if (requiredSet.has(key) && isObj) {
|
|
6737
7347
|
// A hoisted local was already cleared if the value is only inherited.
|
|
6738
7348
|
lines.push(`if(${pv}!==undefined${hoisted[key] ? '' : '&&' + ownGuard(ctx, v, key)}){`)
|
|
6739
|
-
|
|
7349
|
+
genGuardedC(prop, pv, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
|
|
6740
7350
|
lines.push(`}`)
|
|
6741
7351
|
} else if (isObj) {
|
|
6742
7352
|
const oi = ctx.varCounter++
|
|
6743
7353
|
lines.push(`{const _o${oi}=${v}[${JSON.stringify(key)}];if(_o${oi}!==undefined&&${ownGuard(ctx, v, key)}){`)
|
|
6744
|
-
|
|
7354
|
+
genGuardedC(prop, `_o${oi}`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
|
|
6745
7355
|
lines.push(`}}`)
|
|
6746
7356
|
} else {
|
|
6747
7357
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
6748
|
-
|
|
7358
|
+
genGuardedC(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
|
|
6749
7359
|
lines.push(`}`)
|
|
6750
7360
|
}
|
|
6751
7361
|
}
|
|
@@ -6775,7 +7385,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6775
7385
|
ctx.varCounter++
|
|
6776
7386
|
const childPath = childPathDynExpr(pathExpr, idx)
|
|
6777
7387
|
lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
|
|
6778
|
-
|
|
7388
|
+
genGuardedC(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
|
|
6779
7389
|
lines.push(`}}`)
|
|
6780
7390
|
}
|
|
6781
7391
|
|
|
@@ -6853,13 +7463,25 @@ function genUniqueItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, is
|
|
|
6853
7463
|
|
|
6854
7464
|
// The verdict form of genUnevaluatedDynamicC: the same set, built the same
|
|
6855
7465
|
// way, and the answer false at the first key outside it.
|
|
6856
|
-
function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj) {
|
|
6857
|
-
if (schema.additionalProperties !== undefined)
|
|
7466
|
+
function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj, nonObjApplicators) {
|
|
7467
|
+
if (schema.additionalProperties !== undefined) {
|
|
7468
|
+
if (nonObjApplicators) throw DECLINE
|
|
7469
|
+
return
|
|
7470
|
+
}
|
|
6858
7471
|
const ui = ctx.varCounter++
|
|
6859
7472
|
const S = `_ev${ui}`
|
|
6860
7473
|
const body = [`const ${S}=[]`]
|
|
6861
7474
|
addLocalAnnotations(schema, v, S, body, ctx)
|
|
6862
|
-
|
|
7475
|
+
// With the anyOf and oneOf blocks moved here (see genCodeNode), their
|
|
7476
|
+
// passing branches are counted by the annotation pass and judged from the
|
|
7477
|
+
// count: a oneOf holds with exactly one, an anyOf with at least one.
|
|
7478
|
+
const counts = nonObjApplicators ? { anyOf: `_ac${ui}`, oneOf: `_oc${ui}`, oneOfMask: null, of: null, verdict: true } : null
|
|
7479
|
+
if (counts) body.push(`let ${counts.anyOf}=0,${counts.oneOf}=0`)
|
|
7480
|
+
addTopApplicatorAnnotations(schema, v, S, body, ctx, counts)
|
|
7481
|
+
if (counts) {
|
|
7482
|
+
if (Array.isArray(schema.anyOf)) body.push(`if(${counts.anyOf}<1)return false`)
|
|
7483
|
+
if (Array.isArray(schema.oneOf)) body.push(`if(${counts.oneOf}!==1)return false`)
|
|
7484
|
+
}
|
|
6863
7485
|
const kVar = `_uk${ui}`
|
|
6864
7486
|
const up = schema.unevaluatedProperties
|
|
6865
7487
|
if (up === false) {
|
|
@@ -6871,7 +7493,10 @@ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj) {
|
|
|
6871
7493
|
} else {
|
|
6872
7494
|
throw DECLINE
|
|
6873
7495
|
}
|
|
6874
|
-
|
|
7496
|
+
// A value that is not an object skips the pass, so the blocks moved here
|
|
7497
|
+
// run for it in the `else`; a value known to be an object has no `else`.
|
|
7498
|
+
const other = nonObjApplicators && nonObjApplicators.length && !isObj ? `else{${nonObjApplicators.join('\n ')}}` : ''
|
|
7499
|
+
lines.push(isObj ? `{${body.join(';')}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body.join(';')}}${other}`)
|
|
6875
7500
|
}
|
|
6876
7501
|
|
|
6877
7502
|
// unevaluatedProperties where what is evaluated depends on which branches
|
|
@@ -6881,6 +7506,236 @@ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj) {
|
|
|
6881
7506
|
// branch, an if, then or else, or a $ref target counts only when it passes,
|
|
6882
7507
|
// a oneOf only when exactly one branch passes, and `not` never. Each branch is
|
|
6883
7508
|
// asked through an annotation function (emitAnnot).
|
|
7509
|
+
// Run-time unevaluatedProperties written as straight-line code. The names a
|
|
7510
|
+
// node and its in-place applicators can evaluate are known when the code is
|
|
7511
|
+
// written (their `properties`), so the evaluated set is a bit per name in an
|
|
7512
|
+
// integer, each branch's verdict a labelled block, and a failed oneOf's
|
|
7513
|
+
// rollback the integer it started from. The annotation functions this
|
|
7514
|
+
// replaces (emitAnnot) pushed names onto an array, asked it with `includes`
|
|
7515
|
+
// for every key, and were a call per branch per level: ten functions and
|
|
7516
|
+
// twenty calls for the suite's nested-reference case, which in the first
|
|
7517
|
+
// thousands of calls of a process, before anything is inlined, cost 548 ns
|
|
7518
|
+
// against 131. Null when the shape is not modelled: names that are only known
|
|
7519
|
+
// at run time (patternProperties, additionalProperties), a nested
|
|
7520
|
+
// unevaluatedProperties, a reference cycle, more than 30 names, or too much
|
|
7521
|
+
// code; the annotation functions then stand in as before.
|
|
7522
|
+
const ANNOT_INLINE_MAX = 8192
|
|
7523
|
+
const ANNOT_NAME_MAX = 30
|
|
7524
|
+
const ANNOT_BLOCKERS = ['unevaluatedItems', '$dynamicRef', '$recursiveRef', 'dependencies']
|
|
7525
|
+
// Besides a bit per declared name, the mask has a bit per pattern of a
|
|
7526
|
+
// patternProperties (set when the subschema holding it holds: the keys it
|
|
7527
|
+
// matches are evaluated) and one bit, `all`, for a subschema that evaluates
|
|
7528
|
+
// every key when it holds (additionalProperties of any value, or a nested
|
|
7529
|
+
// `unevaluatedProperties: true`). A nested `unevaluatedProperties` that
|
|
7530
|
+
// checks something is not modelled.
|
|
7531
|
+
const ALL_BIT_KEY = '\u0000all'
|
|
7532
|
+
function annotSubs (node, ctx) {
|
|
7533
|
+
const subs = []
|
|
7534
|
+
for (const b of node.allOf || []) subs.push(b)
|
|
7535
|
+
for (const b of node.anyOf || []) subs.push(b)
|
|
7536
|
+
for (const b of node.oneOf || []) subs.push(b)
|
|
7537
|
+
if (node.if !== undefined) subs.push(node.if)
|
|
7538
|
+
if (node.then !== undefined) subs.push(node.then)
|
|
7539
|
+
if (node.else !== undefined) subs.push(node.else)
|
|
7540
|
+
if (node.dependentSchemas && typeof node.dependentSchemas === 'object') for (const ds of Object.values(node.dependentSchemas)) subs.push(ds)
|
|
7541
|
+
if (typeof node.$ref === 'string') subs.push(refTargetFor(node.$ref, ctx))
|
|
7542
|
+
return subs
|
|
7543
|
+
}
|
|
7544
|
+
function annotNames (node, ctx, names, seen, top) {
|
|
7545
|
+
if (node === true || node === false) return true
|
|
7546
|
+
if (node === null || typeof node !== 'object' || Array.isArray(node)) return false
|
|
7547
|
+
if (seen.has(node)) return false
|
|
7548
|
+
seen.add(node)
|
|
7549
|
+
try {
|
|
7550
|
+
for (const k of ANNOT_BLOCKERS) if (node[k] !== undefined) return false
|
|
7551
|
+
if (!top && node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== true) return false
|
|
7552
|
+
const add = (key) => { if (!names.has(key)) { if (names.size >= ANNOT_NAME_MAX) return false; names.set(key, names.size) } return true }
|
|
7553
|
+
if (node.properties && typeof node.properties === 'object') for (const k of Object.keys(node.properties)) if (!add(k)) return false
|
|
7554
|
+
if (node.patternProperties && typeof node.patternProperties === 'object') for (const pat of Object.keys(node.patternProperties)) if (!add('/' + pat)) return false
|
|
7555
|
+
if ((!top && node.additionalProperties !== undefined) || (!top && node.unevaluatedProperties === true)) if (!add(ALL_BIT_KEY)) return false
|
|
7556
|
+
let subs
|
|
7557
|
+
try { subs = annotSubs(node, ctx) } catch { return false }
|
|
7558
|
+
for (const b of subs) if (!annotNames(b, ctx, names, seen, false)) return false
|
|
7559
|
+
return true
|
|
7560
|
+
} finally { seen.delete(node) }
|
|
7561
|
+
}
|
|
7562
|
+
// The bits a subschema's own keywords evaluate when it holds: its declared
|
|
7563
|
+
// names that are present, every pattern it declares, and `all` for a
|
|
7564
|
+
// subschema that evaluates every key.
|
|
7565
|
+
function ownBits (sub, v, names, ctx, t, top) {
|
|
7566
|
+
const out = []
|
|
7567
|
+
for (const k of Object.keys(sub.properties || {})) out.push(`if(${ownKeyExpr(ctx, v, k)})${t}|=${1 << names.get(k)}`)
|
|
7568
|
+
let fixed = 0
|
|
7569
|
+
for (const pat of Object.keys(sub.patternProperties || {})) fixed |= 1 << names.get('/' + pat)
|
|
7570
|
+
if (!top && (sub.additionalProperties !== undefined || sub.unevaluatedProperties === true)) fixed |= 1 << names.get(ALL_BIT_KEY)
|
|
7571
|
+
if (fixed !== 0) out.push(`${t}|=${fixed}`)
|
|
7572
|
+
return out
|
|
7573
|
+
}
|
|
7574
|
+
// The test for one pattern bit against a key variable, a closure regex or a
|
|
7575
|
+
// fixed prefix test (fastPrefixCheck) as addLocalAnnotations writes them.
|
|
7576
|
+
function patternKeyTest (ctx, pat, kv) {
|
|
7577
|
+
const fast = fastPrefixCheck(pat, kv)
|
|
7578
|
+
if (fast) return fast
|
|
7579
|
+
const ri = ctx.varCounter++
|
|
7580
|
+
ctx.closureVars.push(`_re${ri}`)
|
|
7581
|
+
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
7582
|
+
return `_re${ri}.test(${kv})`
|
|
7583
|
+
}
|
|
7584
|
+
// Factory-scope count variables for one anyOf or oneOf array (by identity),
|
|
7585
|
+
// registered in ctx.branchCounts for emitBranchCollapse: `of` the object
|
|
7586
|
+
// they were counted for, `count` how many branches held, `mask` which (oneOf
|
|
7587
|
+
// up to 30 branches). Declared once per program; a stale value is never
|
|
7588
|
+
// taken, since every reader checks `of` against the value at hand.
|
|
7589
|
+
function factoryCount (ctx, arr, withMask) {
|
|
7590
|
+
if (!ctx.inCombined) return null
|
|
7591
|
+
if (!ctx.branchCounts) ctx.branchCounts = new Map()
|
|
7592
|
+
const hit = ctx.branchCounts.get(arr)
|
|
7593
|
+
if (hit !== undefined) return hit.factory ? hit : null
|
|
7594
|
+
const id = ctx.varCounter++
|
|
7595
|
+
const fc = { of: `_fa${id}`, count: `_fn${id}`, mask: withMask ? `_fm${id}` : null, factory: true }
|
|
7596
|
+
ctx.helperCode.push(`let ${fc.of}=null,${fc.count}=0${withMask ? `,${fc.mask}=0` : ''}`)
|
|
7597
|
+
ctx.branchCounts.set(arr, fc)
|
|
7598
|
+
return fc
|
|
7599
|
+
}
|
|
7600
|
+
// Lines that set `ok` (declared by the caller, 1 or 0) for `sub` at `v`, an
|
|
7601
|
+
// object, and OR the names it evaluated into `tv` when it holds.
|
|
7602
|
+
function emitAnnotInline (sub, v, names, ctx, out, ok, tv) {
|
|
7603
|
+
if (sub === true) { out.push(`${ok}=1`); return }
|
|
7604
|
+
if (sub === false) { out.push(`${ok}=0`); return }
|
|
7605
|
+
const id = ctx.varCounter++
|
|
7606
|
+
const t = `_t${id}`
|
|
7607
|
+
const local = {}
|
|
7608
|
+
for (const k of Object.keys(sub)) if (!ANNOT_APPLICATORS.has(k)) setOwn(local, k, sub[k])
|
|
7609
|
+
const vl = []
|
|
7610
|
+
// The value is known to be an object here (the block runs for objects
|
|
7611
|
+
// only), which the verdict is told when the branch does not say otherwise:
|
|
7612
|
+
// told a type, the generator takes it for the value's and skips the
|
|
7613
|
+
// branch's own `type`, so a `type: "string"` branch has to keep its check.
|
|
7614
|
+
ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
|
|
7615
|
+
try { genCode(local, v, vl, ctx, local.type === undefined || local.type === 'object' ? 'object' : undefined) } finally { ctx.nestedBoolean-- }
|
|
7616
|
+
out.push(`let ${t}=0;${ok}=1`)
|
|
7617
|
+
if (vl.length) out.push(`a${id}:{${replaceTopLevel(vl.join(';'), `break a${id}`, `{${ok}=0;break a${id}}`)}}`)
|
|
7618
|
+
if (sub.not !== undefined) {
|
|
7619
|
+
const nl = []
|
|
7620
|
+
nestedGenCode(sub.not, v, nl, ctx)
|
|
7621
|
+
if (nl.length) out.push(`if(${ok}){let _nk${id}=1;n${id}:{${replaceTopLevel(nl.join(';'), `break n${id}`, `{_nk${id}=0;break n${id}}`)}};if(_nk${id})${ok}=0}`)
|
|
7622
|
+
else out.push(`${ok}=0`)
|
|
7623
|
+
}
|
|
7624
|
+
const own = ownBits(sub, v, names, ctx, t, false)
|
|
7625
|
+
if (own.length) out.push(`if(${ok}){${own.join(';')}}`)
|
|
7626
|
+
let bi = 0
|
|
7627
|
+
const child = (b) => { const o = `_ok${id}_${bi}`, tb = `_tb${id}_${bi}`; bi++; out.push(`let ${o}=1,${tb}=0`); emitAnnotInline(b, v, names, ctx, out, o, tb); return [o, tb] }
|
|
7628
|
+
for (const b of sub.allOf || []) { const [o, tb] = child(b); out.push(`if(!${o})${ok}=0;else ${t}|=${tb}`) }
|
|
7629
|
+
// The counts of a nested anyOf or oneOf go to factory-scope variables as
|
|
7630
|
+
// well (factoryCount), where the keyword's own error code in a branch
|
|
7631
|
+
// function, and the counting copies, read them instead of deciding the
|
|
7632
|
+
// branches again: the suite's nested-reference case decided each branch
|
|
7633
|
+
// four times over (this pass, the verdict closures, the counting copies,
|
|
7634
|
+
// the chosen branch's error function).
|
|
7635
|
+
if (Array.isArray(sub.anyOf)) {
|
|
7636
|
+
const na = `_na${id}`
|
|
7637
|
+
const fc = factoryCount(ctx, sub.anyOf, false)
|
|
7638
|
+
out.push(`let ${na}=0${fc ? `;${fc.of}=${v};${fc.count}=0` : ''}`)
|
|
7639
|
+
for (const b of sub.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${t}|=${tb}${fc ? `;${fc.count}++` : ''}}`) }
|
|
7640
|
+
out.push(`if(${na}===0)${ok}=0`)
|
|
7641
|
+
}
|
|
7642
|
+
if (Array.isArray(sub.oneOf)) {
|
|
7643
|
+
const nc = `_nc${id}`, tc = `_tc${id}`
|
|
7644
|
+
const fc = factoryCount(ctx, sub.oneOf, sub.oneOf.length <= 30)
|
|
7645
|
+
out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
|
|
7646
|
+
sub.oneOf.forEach((b, i) => { const [o, tb] = child(b); out.push(`if(${o}){${nc}++;${tc}=${tb}${fc ? `;${fc.count}++${fc.mask ? `;${fc.mask}|=${1 << i}` : ''}` : ''}}`) })
|
|
7647
|
+
out.push(`if(${nc}===1)${t}|=${tc};else ${ok}=0`)
|
|
7648
|
+
}
|
|
7649
|
+
if (sub.if !== undefined) {
|
|
7650
|
+
const [oi, ti] = child(sub.if)
|
|
7651
|
+
const thenPart = []
|
|
7652
|
+
if (sub.then !== undefined) { const saved = out.length; const [ot, tt] = child(sub.then); thenPart.push(...out.splice(saved)); thenPart.push(`if(!${ot})${ok}=0;else ${t}|=${tt}`) }
|
|
7653
|
+
const elsePart = []
|
|
7654
|
+
if (sub.else !== undefined) { const saved = out.length; const [oe, te] = child(sub.else); elsePart.push(...out.splice(saved)); elsePart.push(`if(!${oe})${ok}=0;else ${t}|=${te}`) }
|
|
7655
|
+
out.push(`if(${oi}){${t}|=${ti};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
|
|
7656
|
+
}
|
|
7657
|
+
if (sub.dependentSchemas && typeof sub.dependentSchemas === 'object') {
|
|
7658
|
+
for (const [k, ds] of Object.entries(sub.dependentSchemas)) { const saved = out.length; const [o, tb] = child(ds); const body = out.splice(saved); out.push(`if(${ownKeyExpr(ctx, v, k)}){${body.join(';')};if(!${o})${ok}=0;else ${t}|=${tb}}`) }
|
|
7659
|
+
}
|
|
7660
|
+
if (typeof sub.$ref === 'string') { const [o, tb] = child(refTargetFor(sub.$ref, ctx)); out.push(`if(!${o})${ok}=0;else ${t}|=${tb}`) }
|
|
7661
|
+
out.push(`if(${ok})${tv}|=${t}`)
|
|
7662
|
+
}
|
|
7663
|
+
// The whole run-time unevaluatedProperties block for `schema` at `v`, or
|
|
7664
|
+
// null. `counts` as in genUnevaluatedDynamicC: how many anyOf and oneOf
|
|
7665
|
+
// branches held, for the keywords' own code to read.
|
|
7666
|
+
function inlineUnevalBlock (schema, v, pathExpr, ctx, schemaPrefix, fail, counts) {
|
|
7667
|
+
const names = new Map()
|
|
7668
|
+
if (!annotNames(schema, ctx, names, new Set(), true)) return null
|
|
7669
|
+
const saved = snapshotCtx(ctx)
|
|
7670
|
+
try {
|
|
7671
|
+
const id = ctx.varCounter++
|
|
7672
|
+
const m = `_m${id}`
|
|
7673
|
+
const out = [`let ${m}=0`]
|
|
7674
|
+
// One prototype flag for the object, read by every presence test below.
|
|
7675
|
+
if (!ctx.plainOf) ctx.plainOf = new Map()
|
|
7676
|
+
const prevPk = ctx.plainOf.get(v)
|
|
7677
|
+
const pk = prevPk !== undefined ? prevPk : `_pk${id}`
|
|
7678
|
+
const prevReuse = ctx.plainReuse
|
|
7679
|
+
if (prevPk === undefined) { out.push(`const ${pk}=${protoIs(ctx, v)}`); ctx.plainOf.set(v, pk); (ctx.plainUsed || (ctx.plainUsed = new Set())).add(pk) }
|
|
7680
|
+
ctx.plainReuse = v
|
|
7681
|
+
out.push(...ownBits(schema, v, names, ctx, m, true))
|
|
7682
|
+
let bi = 0
|
|
7683
|
+
const child = (b) => { const o = `_ok${id}_${bi}`, tb = `_tb${id}_${bi}`; bi++; out.push(`let ${o}=1,${tb}=0`); emitAnnotInline(b, v, names, ctx, out, o, tb); return [o, tb] }
|
|
7684
|
+
for (const b of schema.allOf || []) { const [, tb] = child(b); out.push(`${m}|=${tb}`) }
|
|
7685
|
+
if (Array.isArray(schema.anyOf)) {
|
|
7686
|
+
const na = counts ? counts.anyOf : `_na${id}`
|
|
7687
|
+
out.push(counts ? `${na}=0` : `let ${na}=0`)
|
|
7688
|
+
for (const b of schema.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${m}|=${tb}}`) }
|
|
7689
|
+
}
|
|
7690
|
+
if (Array.isArray(schema.oneOf)) {
|
|
7691
|
+
const nc = counts ? counts.oneOf : `_nc${id}`, tc = `_tc${id}`
|
|
7692
|
+
out.push(counts ? `${nc}=0;let ${tc}=0` : `let ${nc}=0,${tc}=0`)
|
|
7693
|
+
schema.oneOf.forEach((b, i) => { const [o, tb] = child(b); out.push(`if(${o}){${nc}++;${tc}=${tb};${counts && counts.oneOfMask && schema.oneOf.length <= 30 ? `${counts.oneOfMask}|=${1 << i};` : ''}}`) })
|
|
7694
|
+
out.push(`if(${nc}===1)${m}|=${tc}`)
|
|
7695
|
+
}
|
|
7696
|
+
if (schema.if !== undefined) {
|
|
7697
|
+
const [oi, ti] = child(schema.if)
|
|
7698
|
+
const thenPart = []
|
|
7699
|
+
if (schema.then !== undefined) { const sv = out.length; const [ot, tt] = child(schema.then); thenPart.push(...out.splice(sv)); thenPart.push(`if(${ot})${m}|=${tt}`) }
|
|
7700
|
+
const elsePart = []
|
|
7701
|
+
if (schema.else !== undefined) { const sv = out.length; const [oe, te] = child(schema.else); elsePart.push(...out.splice(sv)); elsePart.push(`if(${oe})${m}|=${te}`) }
|
|
7702
|
+
out.push(`if(${oi}){${m}|=${ti};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
|
|
7703
|
+
}
|
|
7704
|
+
if (schema.dependentSchemas && typeof schema.dependentSchemas === 'object') {
|
|
7705
|
+
for (const [k, ds] of Object.entries(schema.dependentSchemas)) { const sv = out.length; const [o, tb] = child(ds); const body = out.splice(sv); out.push(`if(${ownKeyExpr(ctx, v, k)}){${body.join(';')};if(${o})${m}|=${tb}}`) }
|
|
7706
|
+
}
|
|
7707
|
+
if (typeof schema.$ref === 'string') { const [o, tb] = child(refTargetFor(schema.$ref, ctx)); out.push(`if(${o})${m}|=${tb}`) }
|
|
7708
|
+
// What is left: every own key whose bit is not set.
|
|
7709
|
+
const kVar = `_uk${id}`
|
|
7710
|
+
const cases = [...names].filter(([k]) => k !== ALL_BIT_KEY && k[0] !== '/').map(([k, i]) => `case ${JSON.stringify(k)}:_ub${id}=${1 << i};break`).join(';')
|
|
7711
|
+
const allBit = names.has(ALL_BIT_KEY) ? `if((${m}&${1 << names.get(ALL_BIT_KEY)})!==0)continue;` : ''
|
|
7712
|
+
const patBits = [...names].filter(([k]) => k[0] === '/').map(([k, i]) => `if((${m}&${1 << i})!==0&&${patternKeyTest(ctx, k.slice(1), `_uk${id}`)})continue;`).join('')
|
|
7713
|
+
const up = schema.unevaluatedProperties
|
|
7714
|
+
let body
|
|
7715
|
+
if (up === false) {
|
|
7716
|
+
body = fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:${kVar}}`, "'must NOT have unevaluated properties'")
|
|
7717
|
+
} else if (typeof up === 'object' && up !== null) {
|
|
7718
|
+
const pe = emitPtrEsc(ctx)
|
|
7719
|
+
const p = pathExpr ? `${pathExpr}+'/'+${pe}(${kVar})` : `'/'+${pe}(${kVar})`
|
|
7720
|
+
const sub = []
|
|
7721
|
+
genCodeC(up, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/unevaluatedProperties')
|
|
7722
|
+
body = sub.join('\n ')
|
|
7723
|
+
} else {
|
|
7724
|
+
throw DECLINE
|
|
7725
|
+
}
|
|
7726
|
+
if (body) out.push(`for(const ${kVar} in ${v}){if(!${ownKeyExpr(ctx, v, kVar).split(JSON.stringify(kVar)).join(kVar)})continue;let _ub${id}=0;switch(${kVar}){${cases}}if(_ub${id}!==0&&(${m}&_ub${id})!==0)continue;${allBit}${patBits}${body}}`)
|
|
7727
|
+
const text = out.join(';')
|
|
7728
|
+
ctx.plainReuse = prevReuse
|
|
7729
|
+
if (prevPk === undefined) ctx.plainOf.delete(v)
|
|
7730
|
+
if (text.length > ANNOT_INLINE_MAX || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(text)) { restoreCtx(ctx, saved); return null }
|
|
7731
|
+
return text
|
|
7732
|
+
} catch (e) {
|
|
7733
|
+
if (e !== DECLINE) throw e
|
|
7734
|
+
restoreCtx(ctx, saved)
|
|
7735
|
+
return null
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
|
|
6884
7739
|
function genUnevaluatedDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
|
|
6885
7740
|
// additionalProperties evaluates every key the others do not, pass or fail.
|
|
6886
7741
|
if (schema.additionalProperties !== undefined) return
|
|
@@ -6904,6 +7759,12 @@ function genUnevaluatedDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, f
|
|
|
6904
7759
|
if (Array.isArray(schema.anyOf)) ctx.branchCounts.set(schema.anyOf, { of: counts.of, count: counts.anyOf, mask: null })
|
|
6905
7760
|
if (Array.isArray(schema.oneOf)) ctx.branchCounts.set(schema.oneOf, { of: counts.of, count: counts.oneOf, mask: schema.oneOf.length <= 30 ? counts.oneOfMask : null })
|
|
6906
7761
|
}
|
|
7762
|
+
const inline = inlineUnevalBlock(schema, v, pathExpr, ctx, schemaPrefix, fail, counts)
|
|
7763
|
+
if (inline !== null) {
|
|
7764
|
+
const pre = share ? `${counts.of}=${v};${counts.oneOfMask}=0;` : ''
|
|
7765
|
+
lines.push(isObj ? `{${pre}${inline}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${pre}${inline}}`)
|
|
7766
|
+
return
|
|
7767
|
+
}
|
|
6907
7768
|
addTopApplicatorAnnotations(schema, v, S, body, ctx, counts)
|
|
6908
7769
|
const kVar = `_uk${ui}`
|
|
6909
7770
|
const up = schema.unevaluatedProperties
|
|
@@ -6966,7 +7827,9 @@ function addTopApplicatorAnnotations(node, v, S, out, ctx, counts) {
|
|
|
6966
7827
|
const t = counts ? counts.oneOf : `_oc${ctx.varCounter++}`
|
|
6967
7828
|
// Which branches passed, as bits, for a collapse that has to name them.
|
|
6968
7829
|
const mask = counts && node.oneOf.length <= 30 ? counts.oneOfMask : null
|
|
6969
|
-
|
|
7830
|
+
// In a verdict (counts.verdict), a second passing branch is the answer.
|
|
7831
|
+
const over = counts && counts.verdict ? `if(${t}>1)return false;` : ''
|
|
7832
|
+
out.push(`{const ${t}m=${S}.length;${counts ? '' : 'let '}${t}=0;${node.oneOf.map((b, i) => `if(${emitAnnot(b, ctx)}(${v},${S})){${t}++;${over}${mask ? `${mask}|=${1 << i};` : ''}}`).join('')}if(${t}!==1)_rb(${S},${t}m)}`)
|
|
6970
7833
|
}
|
|
6971
7834
|
if (node.if !== undefined) {
|
|
6972
7835
|
const thenCall = node.then !== undefined ? `${emitAnnot(node.then, ctx)}(${v},${S})` : ''
|
|
@@ -7003,7 +7866,8 @@ function emitAnnot(sub, ctx) {
|
|
|
7003
7866
|
if (sub === null || typeof sub !== 'object') throw DECLINE
|
|
7004
7867
|
if (!ctx.annFns) {
|
|
7005
7868
|
ctx.annFns = new Map()
|
|
7006
|
-
|
|
7869
|
+
if (ctx.sharedRuntime) bindRt(ctx, ['_anT', '_anF', '_rb'])
|
|
7870
|
+
else (ctx.preamble || ctx.helperCode).push('function _anT(){return true}function _anF(){return false}function _rb(S,m){if(S.length!==m)S.length=m}')
|
|
7007
7871
|
}
|
|
7008
7872
|
const hit = ctx.annFns.get(sub)
|
|
7009
7873
|
if (hit !== undefined) {
|
|
@@ -7035,7 +7899,8 @@ function emitAnnot(sub, ctx) {
|
|
|
7035
7899
|
const failOut = '{_rb(S,_m);return false}'
|
|
7036
7900
|
for (const b of sub.allOf || []) body.push(`if(!${emitAnnot(b, ctx)}(d,S))${failOut}`)
|
|
7037
7901
|
if (Array.isArray(sub.anyOf)) body.push(`{let _a=false;${sub.anyOf.map((b) => `if(${emitAnnot(b, ctx)}(d,S))_a=true;`).join('')}if(!_a)${failOut}}`)
|
|
7038
|
-
|
|
7902
|
+
// A second passing branch settles the oneOf; the rest are not run.
|
|
7903
|
+
if (Array.isArray(sub.oneOf)) body.push(`{let _c=0;${sub.oneOf.map((b) => `if(${emitAnnot(b, ctx)}(d,S)&&++_c>1)${failOut}`).join('')}if(_c!==1)${failOut}}`)
|
|
7039
7904
|
if (sub.if !== undefined) {
|
|
7040
7905
|
const thenCheck = sub.then !== undefined ? `if(!${emitAnnot(sub.then, ctx)}(d,S))${failOut}` : ''
|
|
7041
7906
|
const elseCheck = sub.else !== undefined ? `if(!${emitAnnot(sub.else, ctx)}(d,S))${failOut}` : ''
|
|
@@ -7076,9 +7941,153 @@ function emitAnnot(sub, ctx) {
|
|
|
7076
7941
|
// subschema marks them only when it passes (emitAnnotI); what is left must
|
|
7077
7942
|
// pass unevaluatedItems. `false` reports once, at the first position left,
|
|
7078
7943
|
// as the interpreter does.
|
|
7944
|
+
// Run-time unevaluatedItems written as straight-line code, the way
|
|
7945
|
+
// inlineUnevalBlock writes unevaluatedProperties. Without `contains` the
|
|
7946
|
+
// positions a node and its in-place applicators evaluate are a prefix: items
|
|
7947
|
+
// evaluates every position, prefixItems the first p, a nested
|
|
7948
|
+
// `unevaluatedItems: true` every position once its subschema holds; the
|
|
7949
|
+
// record is one integer, merged with max where a branch holds, and what is
|
|
7950
|
+
// left starts at that index. `contains` (positions that are not a prefix), a
|
|
7951
|
+
// tuple `items`, a nested unevaluatedItems that checks something, dynamic
|
|
7952
|
+
// references and reference cycles keep the annotation functions.
|
|
7953
|
+
const ANNOT_I_BLOCKERS = ['contains', 'additionalItems', '$dynamicRef', '$recursiveRef', 'dependencies']
|
|
7954
|
+
function annotItemsFit (node, ctx, seen, top) {
|
|
7955
|
+
if (node === true || node === false) return true
|
|
7956
|
+
if (node === null || typeof node !== 'object' || Array.isArray(node)) return false
|
|
7957
|
+
if (seen.has(node)) return false
|
|
7958
|
+
seen.add(node)
|
|
7959
|
+
try {
|
|
7960
|
+
for (const k of ANNOT_I_BLOCKERS) if (node[k] !== undefined) return false
|
|
7961
|
+
if (Array.isArray(node.items)) return false
|
|
7962
|
+
if (!top && node.unevaluatedItems !== undefined && node.unevaluatedItems !== true) return false
|
|
7963
|
+
if (node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== true) return false
|
|
7964
|
+
let subs
|
|
7965
|
+
try { subs = annotSubs(node, ctx) } catch { return false }
|
|
7966
|
+
for (const b of subs) if (!annotItemsFit(b, ctx, seen, false)) return false
|
|
7967
|
+
return true
|
|
7968
|
+
} finally { seen.delete(node) }
|
|
7969
|
+
}
|
|
7970
|
+
// The positions a node's own keywords evaluate, as an expression of the
|
|
7971
|
+
// array length `len`: null when none.
|
|
7972
|
+
function ownPrefix (node, len, top) {
|
|
7973
|
+
if (node.items !== undefined) return len
|
|
7974
|
+
if (!top && node.unevaluatedItems === true) return len
|
|
7975
|
+
if (Array.isArray(node.prefixItems) && node.prefixItems.length) return `(${len}<${node.prefixItems.length}?${len}:${node.prefixItems.length})`
|
|
7976
|
+
return null
|
|
7977
|
+
}
|
|
7978
|
+
// Lines that set `ok` for `sub` at `v`, an array of length `len`, and raise
|
|
7979
|
+
// `tn` to the positions it evaluated when it holds.
|
|
7980
|
+
function emitAnnotInlineI (sub, v, len, ctx, out, ok, tn) {
|
|
7981
|
+
if (sub === true) { out.push(`${ok}=1`); return }
|
|
7982
|
+
if (sub === false) { out.push(`${ok}=0`); return }
|
|
7983
|
+
const id = ctx.varCounter++
|
|
7984
|
+
const t = `_t${id}`
|
|
7985
|
+
const local = {}
|
|
7986
|
+
for (const k of Object.keys(sub)) if (!ANNOT_ITEM_APPLICATORS.has(k)) setOwn(local, k, sub[k])
|
|
7987
|
+
const vl = []
|
|
7988
|
+
ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
|
|
7989
|
+
try { genCode(local, v, vl, ctx, local.type === undefined || local.type === 'array' ? 'array' : undefined) } finally { ctx.nestedBoolean-- }
|
|
7990
|
+
out.push(`let ${t}=0;${ok}=1`)
|
|
7991
|
+
if (vl.length) out.push(`a${id}:{${replaceTopLevel(vl.join(';'), `break a${id}`, `{${ok}=0;break a${id}}`)}}`)
|
|
7992
|
+
if (sub.not !== undefined) {
|
|
7993
|
+
const nl = []
|
|
7994
|
+
nestedGenCode(sub.not, v, nl, ctx)
|
|
7995
|
+
if (nl.length) out.push(`if(${ok}){let _nk${id}=1;n${id}:{${replaceTopLevel(nl.join(';'), `break n${id}`, `{_nk${id}=0;break n${id}}`)}};if(_nk${id})${ok}=0}`)
|
|
7996
|
+
else out.push(`${ok}=0`)
|
|
7997
|
+
}
|
|
7998
|
+
const own = ownPrefix(sub, len, false)
|
|
7999
|
+
if (own !== null) out.push(`if(${ok})${t}=${own}`)
|
|
8000
|
+
let bi = 0
|
|
8001
|
+
const child = (b) => { const o = `_ok${id}_${bi}`, tb = `_tb${id}_${bi}`; bi++; out.push(`let ${o}=1,${tb}=0`); emitAnnotInlineI(b, v, len, ctx, out, o, tb); return [o, tb] }
|
|
8002
|
+
const raise = (tb) => `if(${tb}>${t})${t}=${tb}`
|
|
8003
|
+
for (const b of sub.allOf || []) { const [o, tb] = child(b); out.push(`if(!${o})${ok}=0;else ${raise(tb)}`) }
|
|
8004
|
+
if (Array.isArray(sub.anyOf)) {
|
|
8005
|
+
const na = `_na${id}`
|
|
8006
|
+
const fc = factoryCount(ctx, sub.anyOf, false)
|
|
8007
|
+
out.push(`let ${na}=0${fc ? `;${fc.of}=${v};${fc.count}=0` : ''}`)
|
|
8008
|
+
for (const b of sub.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${raise(tb)}${fc ? `;${fc.count}++` : ''}}`) }
|
|
8009
|
+
out.push(`if(${na}===0)${ok}=0`)
|
|
8010
|
+
}
|
|
8011
|
+
if (Array.isArray(sub.oneOf)) {
|
|
8012
|
+
const nc = `_nc${id}`, tc = `_tc${id}`
|
|
8013
|
+
const fc = factoryCount(ctx, sub.oneOf, sub.oneOf.length <= 30)
|
|
8014
|
+
out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
|
|
8015
|
+
sub.oneOf.forEach((b, i) => { const [o, tb] = child(b); out.push(`if(${o}){${nc}++;${tc}=${tb}${fc ? `;${fc.count}++${fc.mask ? `;${fc.mask}|=${1 << i}` : ''}` : ''}}`) })
|
|
8016
|
+
out.push(`if(${nc}===1){${raise(tc)}}else ${ok}=0`)
|
|
8017
|
+
}
|
|
8018
|
+
if (sub.if !== undefined) {
|
|
8019
|
+
const [oi, ti] = child(sub.if)
|
|
8020
|
+
const thenPart = []
|
|
8021
|
+
if (sub.then !== undefined) { const sv = out.length; const [ot, tt] = child(sub.then); thenPart.push(...out.splice(sv)); thenPart.push(`if(!${ot})${ok}=0;else ${raise(tt)}`) }
|
|
8022
|
+
const elsePart = []
|
|
8023
|
+
if (sub.else !== undefined) { const sv = out.length; const [oe, te] = child(sub.else); elsePart.push(...out.splice(sv)); elsePart.push(`if(!${oe})${ok}=0;else ${raise(te)}`) }
|
|
8024
|
+
out.push(`if(${oi}){${raise(ti)};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
|
|
8025
|
+
}
|
|
8026
|
+
if (typeof sub.$ref === 'string') { const [o, tb] = child(refTargetFor(sub.$ref, ctx)); out.push(`if(!${o})${ok}=0;else ${raise(tb)}`) }
|
|
8027
|
+
out.push(`if(${ok}&&${t}>${tn})${tn}=${t}`)
|
|
8028
|
+
}
|
|
8029
|
+
function inlineUnevalItemsBlock (schema, v, pathExpr, ctx, schemaPrefix, fail) {
|
|
8030
|
+
if (!ctx.inCombined || !annotItemsFit(schema, ctx, new Set(), true)) return null
|
|
8031
|
+
const saved = snapshotCtx(ctx)
|
|
8032
|
+
try {
|
|
8033
|
+
const id = ctx.varCounter++
|
|
8034
|
+
const n = `_un${id}`, len = `_ul${id}`
|
|
8035
|
+
const out = [`const ${len}=${v}.length;let ${n}=0`]
|
|
8036
|
+
const own = ownPrefix(schema, len, true)
|
|
8037
|
+
if (own !== null) out.push(`${n}=${own}`)
|
|
8038
|
+
let bi = 0
|
|
8039
|
+
const child = (b) => { const o = `_ok${id}_${bi}`, tb = `_tb${id}_${bi}`; bi++; out.push(`let ${o}=1,${tb}=0`); emitAnnotInlineI(b, v, len, ctx, out, o, tb); return [o, tb] }
|
|
8040
|
+
const raise = (tb) => `if(${tb}>${n})${n}=${tb}`
|
|
8041
|
+
for (const b of schema.allOf || []) { const [, tb] = child(b); out.push(raise(tb)) }
|
|
8042
|
+
if (Array.isArray(schema.anyOf)) {
|
|
8043
|
+
const fc = factoryCount(ctx, schema.anyOf, false)
|
|
8044
|
+
if (fc) out.push(`${fc.of}=${v};${fc.count}=0`)
|
|
8045
|
+
for (const b of schema.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${raise(tb)}${fc ? `;${fc.count}++` : ''}}`) }
|
|
8046
|
+
}
|
|
8047
|
+
if (Array.isArray(schema.oneOf)) {
|
|
8048
|
+
const fc = factoryCount(ctx, schema.oneOf, schema.oneOf.length <= 30)
|
|
8049
|
+
const nc = `_nc${id}`, tc = `_tc${id}`
|
|
8050
|
+
out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
|
|
8051
|
+
schema.oneOf.forEach((b, i) => { const [o, tb] = child(b); out.push(`if(${o}){${nc}++;${tc}=${tb}${fc ? `;${fc.count}++${fc.mask ? `;${fc.mask}|=${1 << i}` : ''}` : ''}}`) })
|
|
8052
|
+
out.push(`if(${nc}===1)${raise(tc)}`)
|
|
8053
|
+
}
|
|
8054
|
+
if (schema.if !== undefined) {
|
|
8055
|
+
const [oi, ti] = child(schema.if)
|
|
8056
|
+
const thenPart = []
|
|
8057
|
+
if (schema.then !== undefined) { const sv = out.length; const [ot, tt] = child(schema.then); thenPart.push(...out.splice(sv)); thenPart.push(`if(${ot})${raise(tt)}`) }
|
|
8058
|
+
const elsePart = []
|
|
8059
|
+
if (schema.else !== undefined) { const sv = out.length; const [oe, te] = child(schema.else); elsePart.push(...out.splice(sv)); elsePart.push(`if(${oe})${raise(te)}`) }
|
|
8060
|
+
out.push(`if(${oi}){${raise(ti)};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
|
|
8061
|
+
}
|
|
8062
|
+
if (typeof schema.$ref === 'string') { const [o, tb] = child(refTargetFor(schema.$ref, ctx)); out.push(`if(${o})${raise(tb)}`) }
|
|
8063
|
+
const iv = `_ix${id}`
|
|
8064
|
+
const ui2 = schema.unevaluatedItems
|
|
8065
|
+
if (ui2 === false) {
|
|
8066
|
+
// Reported once, at the first position left, as the interpreter does.
|
|
8067
|
+
out.push(`if(${n}<${len}){const ${iv}=${n};${fail('unevaluatedItems', 'unevaluatedItems', `{limit:${iv}}`, `'must NOT have more than '+${iv}+' items'`)}}`)
|
|
8068
|
+
} else if (typeof ui2 === 'object' && ui2 !== null) {
|
|
8069
|
+
const pe = pathExpr ? `${pathExpr}+'/'+${iv}` : `'/'+${iv}`
|
|
8070
|
+
const sub = []
|
|
8071
|
+
genCodeC(ui2, `${v}[${iv}]`, pe, sub, ctx, schemaPrefix + '/unevaluatedItems')
|
|
8072
|
+
if (sub.length) out.push(`for(let ${iv}=${n};${iv}<${len};${iv}++){${sub.join('\n ')}\n }`)
|
|
8073
|
+
} else {
|
|
8074
|
+
throw DECLINE
|
|
8075
|
+
}
|
|
8076
|
+
const text = out.join(';')
|
|
8077
|
+
if (text.length > ANNOT_INLINE_MAX || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(text)) { restoreCtx(ctx, saved); return null }
|
|
8078
|
+
return text
|
|
8079
|
+
} catch (e) {
|
|
8080
|
+
if (e !== DECLINE) throw e
|
|
8081
|
+
restoreCtx(ctx, saved)
|
|
8082
|
+
return null
|
|
8083
|
+
}
|
|
8084
|
+
}
|
|
8085
|
+
|
|
7079
8086
|
function genUnevaluatedItemsDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
7080
8087
|
if (schema.$dynamicRef !== undefined || schema.$recursiveRef !== undefined) throw DECLINE
|
|
7081
8088
|
if (Array.isArray(schema.items) || schema.additionalItems !== undefined) throw DECLINE
|
|
8089
|
+
const inline = inlineUnevalItemsBlock(schema, v, pathExpr, ctx, schemaPrefix, fail)
|
|
8090
|
+
if (inline !== null) { lines.push(`if(Array.isArray(${v})){${inline}}`); return }
|
|
7082
8091
|
annItemHelpers(ctx)
|
|
7083
8092
|
const ui = ctx.varCounter++
|
|
7084
8093
|
const S = `_es${ui}`
|
|
@@ -7185,7 +8194,7 @@ function emitAnnotI(sub, ctx) {
|
|
|
7185
8194
|
if (locals.length) body.push(`if(Array.isArray(d)){${locals.join(';')}}`)
|
|
7186
8195
|
for (const b of sub.allOf || []) body.push(`if(!${emitAnnotI(b, ctx)}(d,${R}))${failOut}`)
|
|
7187
8196
|
if (Array.isArray(sub.anyOf)) body.push(`{let _a=false;${sub.anyOf.map((b) => `if(${emitAnnotI(b, ctx)}(d,${R}))_a=true;`).join('')}if(!_a)${failOut}}`)
|
|
7188
|
-
if (Array.isArray(sub.oneOf)) body.push(`{let _c=0;${sub.oneOf.map((b) => `if(${emitAnnotI(b, ctx)}(d,${R})
|
|
8197
|
+
if (Array.isArray(sub.oneOf)) body.push(`{let _c=0;${sub.oneOf.map((b) => `if(${emitAnnotI(b, ctx)}(d,${R})&&++_c>1)${failOut}`).join('')}if(_c!==1)${failOut}}`)
|
|
7189
8198
|
if (sub.if !== undefined) {
|
|
7190
8199
|
const thenCheck = sub.then !== undefined ? `if(!${emitAnnotI(sub.then, ctx)}(d,${R}))${failOut}` : ''
|
|
7191
8200
|
const elseCheck = sub.else !== undefined ? `if(!${emitAnnotI(sub.else, ctx)}(d,${R}))${failOut}` : ''
|
|
@@ -7227,8 +8236,9 @@ function genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fai
|
|
|
7227
8236
|
const checksU = propKeysU.map(k => `_k${ui}[_i]!==${JSON.stringify(k)}`).join('&&')
|
|
7228
8237
|
innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++)if(${checksU}){${failU}}`
|
|
7229
8238
|
} else {
|
|
7230
|
-
|
|
7231
|
-
|
|
8239
|
+
ctx.closureVars.push(`_a${ui}`)
|
|
8240
|
+
ctx.closureVals.push(new Set(propKeysU))
|
|
8241
|
+
innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++)if(!_a${ui}.has(_k${ui}[_i])){${failU}}`
|
|
7232
8242
|
}
|
|
7233
8243
|
lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
|
|
7234
8244
|
}
|
|
@@ -7630,5 +8640,7 @@ function _collectEval(schema, result, defs, schemaMap, refStack, isRoot) {
|
|
|
7630
8640
|
}
|
|
7631
8641
|
|
|
7632
8642
|
module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, expandedChars, AJV_MESSAGES, _setCodeBudget }
|
|
8643
|
+
// For tests: the dynamic-scope unrolling, to hold its output to the interpreter.
|
|
8644
|
+
Object.defineProperty(module.exports, '_expandDynamicScopes', { value: expandDynamicScopes, enumerable: false })
|
|
7633
8645
|
// For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
|
|
7634
8646
|
Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })
|