ata-validator 1.45.0 → 1.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 means the same as
2286
- // one in the root: its target depends on the resources at run time.
2287
- if (node.$dynamicRef !== undefined || node.$dynamicAnchor !== undefined || node.$recursiveRef !== undefined || node.$recursiveAnchor !== undefined) { fail = true; return }
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] : {}
@@ -2896,7 +3071,13 @@ function tryGenCombined(schema, access, ctx) {
2896
3071
  function _deferOrInline(ctx, lines, v, check) {
2897
3072
  // Deferring is only sound at the top level of the root function. Inside a
2898
3073
  // conditional applicator (dependentSchemas) the check must stay in its block.
2899
- if (v === 'd' && !ctx.condDepth) {
3074
+ // Nor inside a verdict generated for a subschema (nestedGenCode, the inline
3075
+ // annotation verdicts of run-time unevaluatedProperties): those lines go
3076
+ // into a block or a function of their own and nothing flushes the deferred
3077
+ // list for them, so a deferred check was dropped. A branch of an anyOf with
3078
+ // `additionalProperties: { const: true }` at the root then read as holding
3079
+ // for every object, and unevaluatedProperties accepted what it evaluated.
3080
+ if (v === 'd' && !ctx.condDepth && !ctx.nestedBoolean) {
2900
3081
  if (!ctx.deferredChecks) ctx.deferredChecks = []
2901
3082
  ctx.deferredChecks.push(check)
2902
3083
  } else {
@@ -3420,11 +3601,20 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3420
3601
  // once, there. Only that model does: with unevaluatedProperties true or a
3421
3602
  // schema, skipping it here left anyOf unchecked and accepted a document no
3422
3603
  // branch matched.
3423
- if (schema.anyOf && !unevalChecksAnyOf(schema, ctx)) genAnyOfNode(schema, v, lines, ctx, knownType)
3604
+ // Where unevaluatedProperties builds its evaluated set at run time, the
3605
+ // annotation pass already decides each anyOf and oneOf branch, so the
3606
+ // keywords' own blocks would decide them a second time: on a schema of
3607
+ // nested references that doubled the cost of accepting a document. Their
3608
+ // blocks go to the `else` of that pass instead, for a value that is not an
3609
+ // object, which the pass does not look at and which they must still judge.
3610
+ const nonObjApplicators = schema.unevaluatedProperties !== undefined && schema.additionalProperties === undefined &&
3611
+ (Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf)) && unevalBuiltAtRuntime(schema, ctx) ? [] : null
3612
+ const applicatorLines = nonObjApplicators || lines
3613
+ if (schema.anyOf && !unevalChecksAnyOf(schema, ctx)) genAnyOfNode(schema, v, applicatorLines, ctx, knownType)
3424
3614
 
3425
3615
  // oneOf — branch fns hoisted to factory scope when safe (no recursion/ref).
3426
3616
  // Falls back to inline closures if any branch touches recursive validation.
3427
- if (schema.oneOf) genOneOfNode(schema, v, lines, ctx, knownType)
3617
+ if (schema.oneOf) genOneOfNode(schema, v, applicatorLines, ctx, knownType)
3428
3618
 
3429
3619
  // not
3430
3620
  if (schema.not !== undefined) genNotNode(schema, v, lines, ctx, knownType)
@@ -3433,7 +3623,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3433
3623
  if (schema.if !== undefined) genIfNode(schema, v, lines, ctx, knownType)
3434
3624
 
3435
3625
  // unevaluatedProperties
3436
- if (schema.unevaluatedProperties !== undefined) genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount)
3626
+ if (schema.unevaluatedProperties !== undefined) genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount, nonObjApplicators)
3437
3627
 
3438
3628
  // unevaluatedItems
3439
3629
  if (schema.unevaluatedItems !== undefined) genUnevaluatedItemsNode(schema, v, lines, ctx, knownType, isArr)
@@ -3763,7 +3953,9 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
3763
3953
  // whatever function is compiling, carrying this def's key set.
3764
3954
  const outerDeferred = ctx.deferredChecks
3765
3955
  ctx.deferredChecks = null
3766
- genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
3956
+ const outerReuse = ctx.plainReuse
3957
+ ctx.plainReuse = undefined
3958
+ try { genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx) } finally { ctx.plainReuse = outerReuse }
3767
3959
  if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
3768
3960
  ctx.deferredChecks = outerDeferred
3769
3961
  // The def is hoisted above the entry function, where the recursive
@@ -3798,10 +3990,13 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
3798
3990
  const outerDeferred = ctx.deferredChecks
3799
3991
  ctx.deferredChecks = null
3800
3992
  ctx.refStack.add(schema.$ref)
3993
+ const outerReuse = ctx.plainReuse
3994
+ ctx.plainReuse = undefined
3801
3995
  try {
3802
3996
  genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
3803
3997
  } finally {
3804
3998
  ctx.refStack.delete(schema.$ref)
3999
+ ctx.plainReuse = outerReuse
3805
4000
  }
3806
4001
  if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
3807
4002
  ctx.deferredChecks = outerDeferred
@@ -4008,12 +4203,15 @@ function unevalBuiltAtRuntime(schema, ctx, r) {
4008
4203
  return r.dynamic && (r.allProps || !unevalPropsModeled(schema, ctx.schemaMap))
4009
4204
  }
4010
4205
 
4011
- function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount) {
4206
+ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount, nonObjApplicators) {
4012
4207
  const evalResult = collectEvaluated(schema, ctx.schemaMap, ctx.rootDefs)
4013
4208
  if (unevalBuiltAtRuntime(schema, ctx, evalResult)) {
4014
- genUnevalDynamicVerdict(schema, v, lines, ctx, isObj)
4209
+ genUnevalDynamicVerdict(schema, v, lines, ctx, isObj, nonObjApplicators)
4015
4210
  return
4016
4211
  }
4212
+ // genCodeNode moved the anyOf and oneOf blocks here only for the run-time
4213
+ // form; any other outcome would leave them unwritten.
4214
+ if (nonObjApplicators !== null && nonObjApplicators !== undefined) throw DECLINE
4017
4215
 
4018
4216
  if (evalResult.allProps || schema.unevaluatedProperties === true) {
4019
4217
  // All props evaluated or unevaluatedProperties:true — no-op
@@ -4955,7 +5153,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
4955
5153
  if (hasUnresolvableRef(schema, eRootDefs, eAnchors, schemaMap, new Set())) return null
4956
5154
  if (needsBaseTracking(schema, schemaMap, new Set())) return null
4957
5155
 
4958
- const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, shared: [], refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootSchema: inputSchema, userFormats: userFormats || null,
5156
+ const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, shared: [], refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootE: schema, rootSchema: inputSchema, userFormats: userFormats || null,
4959
5157
  // Custom format checkers referenced by the body, bound as
4960
5158
  // closure parameters below the way the other entry points do.
4961
5159
  closureVars: [], closureVals: [],
@@ -5150,6 +5348,224 @@ function holdsRef (node, seen = new Set()) {
5150
5348
  return false
5151
5349
  }
5152
5350
 
5351
+ // Every `$ref` under `node`, outside the values of enum, const, default and
5352
+ // examples. Null when one is not a plain pointer into the root's definitions
5353
+ // (after prepareForCodegen those are what remain, besides `#` itself) or
5354
+ // when the node carries a scope keyword of its own, which would not mean the
5355
+ // same thing under a wrapper.
5356
+ const BRANCH_SCOPE_KEYS = new Set(['$id', '$anchor', '$dynamicAnchor', '$dynamicRef', '$defs', 'definitions', '$schema'])
5357
+ function branchRefsLocal (node, defsKey, seen) {
5358
+ if (node === null || typeof node !== 'object') return true
5359
+ if (seen.has(node)) return true
5360
+ seen.add(node)
5361
+ if (Array.isArray(node)) { for (const x of node) if (!branchRefsLocal(x, defsKey, seen)) return false; return true }
5362
+ for (const k of Object.keys(node)) {
5363
+ if (SKIP_VALUE_KEYS.has(k)) continue
5364
+ if (BRANCH_SCOPE_KEYS.has(k)) return false
5365
+ const v = node[k]
5366
+ if (k === '$ref') { if (typeof v !== 'string' || !v.startsWith('#/' + defsKey + '/')) return false; continue }
5367
+ if (!branchRefsLocal(v, defsKey, seen)) return false
5368
+ }
5369
+ return true
5370
+ }
5371
+ // One verdict function per branch, compiled on its own with the root's
5372
+ // definitions under it, or null when a branch cannot be wrapped that way
5373
+ // (then the site keeps its error functions for the decision). The
5374
+ // definitions themselves are checked once per root.
5375
+ const _defsLocal = new WeakMap()
5376
+ function compileBranchVerdicts (branches, ctx) {
5377
+ const root = ctx.rootC
5378
+ if (root === null || typeof root !== 'object') return null
5379
+ const defsKey = root.$defs && typeof root.$defs === 'object' ? '$defs' : (root.definitions && typeof root.definitions === 'object' ? 'definitions' : null)
5380
+ if (defsKey !== null) {
5381
+ let ok = _defsLocal.get(root)
5382
+ if (ok === undefined) { ok = branchRefsLocal(root[defsKey], defsKey, new Set()); _defsLocal.set(root, ok) }
5383
+ if (!ok) return null
5384
+ }
5385
+ const out = []
5386
+ for (const sub of branches) {
5387
+ if (sub === true) { out.push(() => true); continue }
5388
+ if (sub === false) { out.push(() => false); continue }
5389
+ if (sub === null || typeof sub !== 'object' || Array.isArray(sub)) return null
5390
+ if (!branchRefsLocal(sub, defsKey === null ? '$defs' : defsKey, new Set())) return null
5391
+ const wrapped = Object.assign({}, sub)
5392
+ if (defsKey !== null) wrapped[defsKey] = root[defsKey]
5393
+ if (typeof ctx.rootSchema === 'object' && ctx.rootSchema !== null && typeof ctx.rootSchema.$schema === 'string') wrapped.$schema = ctx.rootSchema.$schema
5394
+ let fn = null
5395
+ try { fn = compileToJSCodegen(wrapped, ctx.schemaMap, ctx.userFormats) } catch (e) { if (e !== DECLINE) throw e }
5396
+ if (typeof fn !== 'function') return null
5397
+ out.push(fn)
5398
+ }
5399
+ return out
5400
+ }
5401
+
5402
+ // A verdict for one subtree of the combined function, as a function in the
5403
+ // preamble, so the collecting code of that subtree runs only for a value the
5404
+ // verdict rejects. The collecting code is slower than the verdict on an
5405
+ // accepted value (it keeps paths, builds nothing it can avoid but tests more
5406
+ // than the verdict's cheapest-first order), so without this the whole
5407
+ // function was put behind one verdict of the whole document (`_VF`), and a
5408
+ // rejected document was validated twice in full. With a guard at every
5409
+ // array element and every object-valued property, an accepted value costs
5410
+ // its verdict and a rejected one costs its verdict plus the collection of
5411
+ // the subtree that failed. Only in the combined function, the runtime path;
5412
+ // null for a subtree whose verdict declines, is a leaf, or is too large.
5413
+ const GUARD_KEYS = ['oneOf', 'anyOf', 'allOf', '$ref', 'items', 'prefixItems', 'properties', 'patternProperties', 'additionalProperties', 'not', 'if', 'contains', 'dependentSchemas', 'dependencies', 'propertyNames', 'unevaluatedProperties', 'unevaluatedItems']
5414
+ const GUARD_MAX = 16 * 1024
5415
+ const GUARD_BUDGET = 512 * 1024
5416
+ // Whether a schema node is worth a guard of its own: it applies something
5417
+ // beyond leaf checks. A node whose children are themselves worth guarding is
5418
+ // not guarded; the guards go on the children. Guarding both ran the parent's
5419
+ // verdict in full and then, when it failed, every child's verdict again: on a
5420
+ // list of 1000 users with one bad one that was two verdicts of the list
5421
+ // before the one user was collected.
5422
+ const weighty = (n) => n !== null && typeof n === 'object' && !Array.isArray(n) && GUARD_KEYS.some((k) => n[k] !== undefined)
5423
+ function childrenWeighty (sub) {
5424
+ if (weighty(sub.items)) return true
5425
+ if (Array.isArray(sub.prefixItems) && sub.prefixItems.some(weighty)) return true
5426
+ if (weighty(sub.additionalProperties)) return true
5427
+ if (sub.properties && typeof sub.properties === 'object') for (const k of Object.keys(sub.properties)) if (weighty(sub.properties[k])) return true
5428
+ if (sub.patternProperties && typeof sub.patternProperties === 'object') for (const k of Object.keys(sub.patternProperties)) if (weighty(sub.patternProperties[k])) return true
5429
+ return false
5430
+ }
5431
+ // Below this much verdict source the guard is written into the function
5432
+ // itself as a labelled block (`gN:{...break gN...}`), not as a function it
5433
+ // calls: in the first thousands of calls of a process, before the optimizing
5434
+ // compiler has inlined anything, each call is paid in full, and a schema
5435
+ // with six small guards paid six. Above it the guard stays a function, so
5436
+ // one large subtree does not swell the function around it.
5437
+ const GUARD_INLINE_MAX = 2048
5438
+ function subtreeGuard (sub, ctx, gv) {
5439
+ if (!ctx.inCombined || !weighty(sub) || childrenWeighty(sub)) return null
5440
+ if ((ctx.guardChars || 0) >= GUARD_BUDGET) return null
5441
+ // The verdict generator registers the definition functions it writes under
5442
+ // `defFns`, and so does the error generator for the branch functions of
5443
+ // this same program: shared, the verdict found the error generator's
5444
+ // `_defE` function under the definition's name and called it with one
5445
+ // argument. The guards keep a registry of their own.
5446
+ if (!ctx.guardDefFns) ctx.guardDefFns = new Map()
5447
+ const saved = snapshotCtx(ctx)
5448
+ const outerDefFns = ctx.defFns
5449
+ const outerShare = ctx.shareDefs
5450
+ ctx.defFns = ctx.guardDefFns
5451
+ // Definitions referenced from several places are written once, as
5452
+ // functions in the preamble (the verdict generator's shared mode), not
5453
+ // expanded into every guard that reaches them: expanded, the guards of a
5454
+ // 25-definition API schema came to 55 KB of source in a 133 KB function,
5455
+ // and a function that size reaches the optimizing compiler late.
5456
+ ctx.shareDefs = true
5457
+ try {
5458
+ const bl = []
5459
+ try { nestedGenCode(sub, gv, bl, ctx) } finally { ctx.defFns = outerDefFns; ctx.shareDefs = outerShare }
5460
+ if (bl.length === 0) { restoreCtx(ctx, saved); return null }
5461
+ const body = bl.join(';')
5462
+ // The verdict generator's recursion names (`_validate`, the cycle guard
5463
+ // state) are not this function's; a subtree that reaches them stays
5464
+ // unguarded.
5465
+ const added = ctx.preamble.slice(saved.get('preamble').n).join(';')
5466
+ if (body.length > GUARD_MAX || body.includes(SP_PLACEHOLDER) || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(body + added)) { restoreCtx(ctx, saved); return null }
5467
+ ctx.guardChars = (ctx.guardChars || 0) + body.length
5468
+ if (body.length <= GUARD_INLINE_MAX) return { body }
5469
+ const name = `_vg${ctx.varCounter++}`
5470
+ ctx.helperCode.push(`const ${name}=function(${gv}){${body};return true}`)
5471
+ return { name }
5472
+ } catch (e) {
5473
+ if (e !== DECLINE) throw e
5474
+ restoreCtx(ctx, saved)
5475
+ return null
5476
+ }
5477
+ }
5478
+ // The collecting code for `sub` at `v`, behind its guard when there is one.
5479
+ function genGuardedC (sub, v, pathExpr, lines, ctx, schemaPrefix) {
5480
+ const gi = ctx.varCounter++
5481
+ const gv = `_gv${gi}`
5482
+ const g = subtreeGuard(sub, ctx, gv)
5483
+ if (g === null) { genCodeC(sub, v, pathExpr, lines, ctx, schemaPrefix); return }
5484
+ if (g.name !== undefined) lines.push(`if(!${g.name}(${v})){`)
5485
+ else {
5486
+ // The verdict's exits become jumps out of the block: a failure leaves
5487
+ // the flag set, success clears it. replaceTopLevel keeps nested
5488
+ // closures' returns as they are (see the hybrid rewrite).
5489
+ 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}){`)
5490
+ }
5491
+ ctx.guardDepth = (ctx.guardDepth || 0) + 1
5492
+ try { genCodeC(sub, v, pathExpr, lines, ctx, schemaPrefix) } finally { ctx.guardDepth-- }
5493
+ lines.push(g.name !== undefined ? '}' : '}}')
5494
+ }
5495
+
5496
+ // A branch's error-collecting body rewritten to count what it would have
5497
+ // collected: each `_e.push({keyword:'k',...})` becomes a count and a severity
5498
+ // (lib/branch-collapse.js, __ataScore), and the body returns the score the
5499
+ // collapse would compute from the errors. When no branch of a oneOf or anyOf
5500
+ // holds, the collapse reports the closest branch's errors; it used to run
5501
+ // every branch's error function to score them, building every branch's
5502
+ // errors to keep one branch's. Now the counting bodies score, and only the
5503
+ // closest branch's error function runs. Null for a body the rewrite cannot
5504
+ // follow (a shared definition call, a nested collapse, a push that is not a
5505
+ // literal); that site keeps the old path.
5506
+ function countingBody (body) {
5507
+ // A nested oneOf or anyOf that does not hold is one collapsed error in the
5508
+ // branch's list (keyword oneOf or anyOf, severity 4): its marked section,
5509
+ // which would run the inner branches to build it, becomes that count.
5510
+ // Two kinds of section: CB is one error whatever the branches would say;
5511
+ // CU is a section the copy cannot score (the value is not the one the
5512
+ // counts were taken for), so the copy answers -1 and the picker runs the
5513
+ // branches as before.
5514
+ while (true) {
5515
+ const b1 = body.indexOf('/*CB*/'), b2 = body.indexOf('/*CU*/')
5516
+ const b = b1 < 0 ? b2 : b2 < 0 ? b1 : Math.min(b1, b2)
5517
+ if (b < 0) break
5518
+ const unknown = b === b2
5519
+ let depth = 1
5520
+ let j = b + 6
5521
+ while (j < body.length && depth > 0) {
5522
+ if (body.startsWith('/*CB*/', j) || body.startsWith('/*CU*/', j)) { depth++; j += 6; continue }
5523
+ if (body.startsWith('/*CE*/', j)) { depth--; j += 6; continue }
5524
+ j++
5525
+ }
5526
+ if (depth !== 0) return null
5527
+ body = body.slice(0, b) + (unknown ? 'return -1' : '(_n++,_s+=4)') + body.slice(j)
5528
+ }
5529
+ if (/\b(?:_defE\d|__ataCollapse|__ataMulti|_brf\d|_bsf\d)/.test(body)) return null
5530
+ const SEV = require('./branch-collapse').SEVERITY
5531
+ let out = ''
5532
+ let i = 0
5533
+ const head = 'const _e=[];'
5534
+ if (!body.startsWith(head)) return null
5535
+ out += 'let _n=0,_s=0;'
5536
+ i = head.length
5537
+ while (i < body.length) {
5538
+ const ch = body[i]
5539
+ if (ch === '"' || ch === "'" || ch === '`') { const j = skipString(body, i); out += body.slice(i, j); i = j; continue }
5540
+ if (body.startsWith('_e.push(', i)) {
5541
+ let j = i + 8
5542
+ let depth = 1
5543
+ while (j < body.length && depth > 0) {
5544
+ const c = body[j]
5545
+ if (c === '"' || c === "'" || c === '`') { j = skipString(body, j); continue }
5546
+ if (c === '(' || c === '{' || c === '[') depth++
5547
+ else if (c === ')' || c === '}' || c === ']') depth--
5548
+ j++
5549
+ }
5550
+ if (depth !== 0) return null
5551
+ const arg = body.slice(i + 8, j - 1)
5552
+ // The literal may still carry its `code` (the shape pass strips it).
5553
+ const m = /^\{(?:code:'ATA\d{4}',)?keyword:'([A-Za-z$][\w$]*)',/.exec(arg)
5554
+ if (m === null) return null
5555
+ out += `(_n++,_s+=${SEV[m[1]] || 4})`
5556
+ i = j
5557
+ continue
5558
+ }
5559
+ if (body.startsWith('return{valid:false,errors:_e}', i)) { out += 'return _n*100+_s'; i += 29; continue }
5560
+ if (body.startsWith('return{valid:_e.length===0,errors:_e}', i)) { out += 'return _n*100+_s'; i += 37; continue }
5561
+ if (body.startsWith('_e.length', i)) { out += '_n'; i += 9; continue }
5562
+ out += ch
5563
+ i++
5564
+ }
5565
+ if (/\b_e\b/.test(out.replace(/'[^']*'|"[^"]*"/g, ''))) return null
5566
+ return out
5567
+ }
5568
+
5153
5569
  function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaPrefix) {
5154
5570
  ctx.usesBranchCollapse = true
5155
5571
  const fi = ctx.varCounter++
@@ -5211,23 +5627,59 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
5211
5627
  // In the combined function `_e` is created on the first error and every
5212
5628
  // error is collected (`_all` is true there), so the report only pushes.
5213
5629
  const report = ctx.inCombined && !ctx.inBranchFn
5214
- ? `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed})(_e=_ap(_e,${collapsed}))`
5630
+ ? `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed})(_e=_ap(_A,_e,${collapsed},${apOrd(ctx, branchSp)}))`
5215
5631
  : `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed}){_e.push(${collapsed});if(!_all)return{valid:false,errors:_e}}`
5632
+ // Score first, collect once (countingBody), in the combined function where
5633
+ // the collapsed error is built in its final shape (no ordering key).
5634
+ let noneReport = null
5635
+ if (ctx.inCombined && (ctx.noOrdinal ? 'null' : '') === 'null') {
5636
+ const cbs = fns.map((b) => b === '' ? 'return 0' : countingBody(b))
5637
+ if (!cbs.some((c) => c === null)) {
5638
+ const sfx = usesSp ? ',_sp' : ''
5639
+ const code = branchKw === 'oneOf' ? 'ATA4001' : 'ATA4003'
5640
+ // `code: ` with the space: the runtime shape strips this generator's own
5641
+ // `code:'ATA....',` from the literals and keeps the collapse's, which
5642
+ // the reader expects to carry it (see the shape pass below).
5643
+ 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}`
5644
+ // In a branch function the error joins that function's list (the
5645
+ // combined function's `_ap` and ordering state are not its).
5646
+ const push = ctx.inBranchFn ? `_e.push(${lit});if(!_all)return{valid:false,errors:_e}` : `(_e=_ap(_A,_e,${lit},${apOrd(ctx, branchSp)}))`
5647
+ if (cbs.length === 1) {
5648
+ // One branch is the closest one: nothing to score, no counting copy.
5649
+ // Scoring it ran the branch a third time (verdict, count, errors).
5650
+ noneReport = `{let _bi${fi}=0;const _br${fi}=${fnArr}[0](${v},_pp${fi},_all${sfx});${push}}`
5651
+ } else {
5652
+ ctx.helperCode.push(cbs.map((b, i) => `const _bsf${fi}_${i}=function(${params}){${b}}`).join(';'))
5653
+ 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('')
5654
+ // A copy that could not score (-1): every branch runs, as before.
5655
+ const noneOld = branchKw === 'anyOf'
5656
+ ? `const ${resArr}=[];for(let _bi=0;_bi<${branches.length};_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}`
5657
+ : `const ${resArr}=[];for(let _bi=0;_bi<${branches.length};_bi++)${resArr}.push(${call});${report}`
5658
+ 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}}}`
5659
+ }
5660
+ }
5661
+ }
5216
5662
  // The node's runtime unevaluatedProperties already counted how many of
5217
5663
  // these branches pass, through the annotation functions, which answer the
5218
5664
  // same validity. A oneOf with exactly one, an anyOf with any, holds and
5219
5665
  // reports nothing, so its branches are not run a second time to say so.
5220
5666
  // Only in the combined function proper, where that count is in scope.
5221
- const counted = ctx.inCombined && !ctx.inBranchFn && ctx.branchCounts ? ctx.branchCounts.get(branches) : undefined
5667
+ // Inside a branch function only a factory-scope count (factoryCount) is
5668
+ // in reach; the node's own counts are locals of the combined function.
5669
+ const countedRaw = ctx.inCombined && ctx.branchCounts ? ctx.branchCounts.get(branches) : undefined
5670
+ const counted = countedRaw !== undefined && (!ctx.inBranchFn || countedRaw.factory) ? countedRaw : undefined
5222
5671
  const same = counted !== undefined ? `${v}!==null&&typeof ${v}==='object'&&${counted.of}===${v}` : ''
5223
5672
  let guard = counted !== undefined
5224
5673
  ? (keyword === 'anyOf' ? `if(!(${same}&&${counted.count}>=1))` : `if(!(${same}&&${counted.count}===1))`)
5225
5674
  : ''
5226
5675
  // More than one oneOf branch passed: the error names them and carries no
5227
5676
  // branch errors, so it is built from the bits the count left.
5228
- if (guard !== '' && keyword === 'oneOf' && counted.mask && ctx.inCombined && !ctx.inBranchFn) {
5677
+ if (guard !== '' && keyword === 'oneOf' && counted.mask && ctx.inCombined) {
5229
5678
  const ord0 = ctx.noOrdinal ? 'null' : (() => { const o = ordinalFor(ctx.rootSchema, unescapeSp(branchSp)); return o === null ? 'null' : o })()
5230
- guard = `if(${same}&&${counted.count}>1){const _pi${fi}=[];for(let _k=0;_k<${branches.length};_k++)if((${counted.mask}>>_k)&1)_pi${fi}.push(_k);(_e=_ap(_e,__ataMulti(_pi${fi},${branches.length},${pp},'${branchSp}',${ord0})))}else if(!(${same}&&${counted.count}===1))`
5679
+ const multiC = `__ataMulti(_pi${fi},${branches.length},${pp},'${branchSp}',${ord0})`
5680
+ const pushC = ctx.inBranchFn ? `_e.push(${multiC});if(!_all)return{valid:false,errors:_e}` : `(_e=_ap(_A,_e,${multiC},${apOrd(ctx, branchSp)}))`
5681
+ // The marked section is one error in a counting copy (countingBody).
5682
+ 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
5683
  }
5232
5684
  // Without such a count, each branch also gets a verdict function, and the
5233
5685
  // passing branches are counted with those: a oneOf with exactly one and an
@@ -5272,29 +5724,49 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
5272
5724
  restoreCtx(ctx, saved)
5273
5725
  }
5274
5726
  }
5727
+ // In the combined function (the runtime path; a standalone module cannot
5728
+ // carry a compiled function) a branch that holds a reference, or a site
5729
+ // whose inline verdicts were too large, gets a verdict compiled on its own
5730
+ // and handed in as a closure value: the branch with the root's definitions
5731
+ // under it, through compileToJSCodegen, with its own shared definitions and
5732
+ // its own regex engines. Without one, every branch's error function ran on
5733
+ // every value the keyword saw, accepted or not: json-patch's six-way oneOf
5734
+ // cost 1363 ns on a valid list of patches where its verdict costs 261.
5735
+ if (vfArr === null && guard === '' && ctx.inCombined) {
5736
+ const compiled = compileBranchVerdicts(branches, ctx)
5737
+ if (compiled !== null) {
5738
+ vfArr = `_bvf${fi}`
5739
+ for (let i = 0; i < compiled.length; i++) { ctx.closureVars.push(`${vfArr}_${i}`); ctx.closureVals.push(compiled[i]) }
5740
+ }
5741
+ }
5275
5742
  if (vfArr !== null) {
5276
5743
  const L = branches.length
5277
5744
  const top = ctx.inCombined && !ctx.inBranchFn
5278
5745
  // An accepted document is still answered by the verdict first (see the
5279
5746
  // result shape in compileToJSCombined): that is the fastest path to it,
5280
5747
  // and dropping it measured 20 ns slower per accepted oneOf.
5281
- if (top) ctx.unguardedCollapse = true
5748
+ if (top && !ctx.guardDepth) ctx.unguardedCollapse = true
5282
5749
  const tests = branches.map((_, i) => `${vfArr}_${i}(${v})`)
5283
5750
  if (keyword === 'anyOf') {
5284
- lines.push(`{const _pp${fi}=${pp};if(!(${tests.join('||')})){if(_all===0){_e.push(null);return{valid:false,errors:_e}}const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}}}`)
5751
+ 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}`
5752
+ // In a branch function the section a counting copy replaces is marked
5753
+ // (countingBody): when the keyword does not hold, the error function
5754
+ // reports one collapsed error, whatever the branches would say.
5755
+ 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
5756
  } else {
5286
5757
  const mk = L <= 30
5287
5758
  const collect = mk
5288
5759
  ? `const _pi${fi}=[];for(let _k=0;_k<${L};_k++)if((_m${fi}>>_k)&1)_pi${fi}.push(_k);`
5289
5760
  : ''
5290
5761
  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}`
5762
+ const pushMulti = top ? `(_e=_ap(_A,_e,${multi},${apOrd(ctx, branchSp)}))` : `_e.push(${multi});if(!_all)return{valid:false,errors:_e}`
5292
5763
  const count = tests.map((t, i) => `if(${t}){_n${fi}++;${mk ? `_m${fi}|=${1 << i}` : `_pi${fi}.push(${i})`}}`).join('')
5293
- lines.push(`{const _pp${fi}=${pp};let _n${fi}=0${mk ? `,_m${fi}=0` : `,_pi${fi}=[]`};${count}if(_n${fi}!==1){if(_all===0){_e.push(null);return{valid:false,errors:_e}}if(_n${fi}>1){${collect}${pushMulti}}else{const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++)${resArr}.push(${call});${report}}}}`)
5764
+ const none = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++)${resArr}.push(${call});${report}`
5765
+ 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
5766
  }
5295
5767
  return
5296
5768
  }
5297
- if (guard === '' && ctx.inCombined && !ctx.inBranchFn) ctx.unguardedCollapse = true
5769
+ if (guard === '' && ctx.inCombined && !ctx.inBranchFn && !ctx.guardDepth) ctx.unguardedCollapse = true
5298
5770
  if (keyword === 'anyOf') {
5299
5771
  // anyOf reports nothing once a branch passes, so the branches are first
5300
5772
  // only asked whether they pass. They are called with `_all` set to 0, a
@@ -5308,12 +5780,24 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
5308
5780
  // no one read. Only when no branch passes do they run again with the
5309
5781
  // caller's `_all`, and the collapse sees them all, as before.
5310
5782
  const probe = `${fnArr}[_bi](${v},_pp${fi},0${usesSp ? ',_sp' : ''})`
5311
- lines.push(`${guard}{const _pp${fi}=${pp};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}}const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}}}`)
5783
+ 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}`
5784
+ 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}}`
5785
+ // With a count: on the counted object the guard already said no branch
5786
+ // held (CB, one error in a counting copy); on another object the branches
5787
+ // are probed as before (CU, a counting copy cannot know).
5788
+ 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*/}}`)
5789
+ else lines.push(`${guard}{const _pp${fi}=${pp};${probeOld}}`)
5312
5790
  } else {
5313
5791
  // Asked only whether it passes (`_all` 0, see anyOf above), oneOf counts
5314
5792
  // the passing branches and answers; the branch errors of a probe are not
5315
5793
  // errors anyone reads and are not collapsed.
5316
- lines.push(`${guard}{const _pp${fi}=${pp};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}}}`)
5794
+ // With a count from the node's unevaluatedProperties (`counted`), a count
5795
+ // of zero on the counted object means no branch held, which is the case
5796
+ // the closest-branch path answers without running every branch.
5797
+ 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}}`
5798
+ const noneO = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++)${resArr}.push(${call});${report}`
5799
+ 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*/}}`)
5800
+ else lines.push(`${guard}{const _pp${fi}=${pp};${oldO}}`)
5317
5801
  }
5318
5802
  }
5319
5803
 
@@ -5340,19 +5824,60 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
5340
5824
  ctx.regExpMap = new Map();
5341
5825
  }
5342
5826
  // $ref — resolve local and cross-schema refs
5343
- if (schema.$ref) {
5344
- // The reference is followed and the rest of the node is not compiled; a
5345
- // keyword beside it that validates would be dropped, so the node
5346
- // declines (as in genCodeCNode). Reached since unevaluatedProperties
5347
- // next to a $ref stopped declining the combined function, whose oneOf
5348
- // and anyOf branches come from this generator: a branch was taken as
5349
- // passing with its siblings unchecked.
5350
- for (const k of Object.keys(schema)) if (!REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-')) throw DECLINE
5351
- // A self-reference cannot be expressed here yet: the root helper takes no
5352
- // path argument, so errors raised through it would carry the path of the
5353
- // root rather than of the value that failed. This used to fall through as
5354
- // a permissive no-op, which accepted whatever the reference guarded.
5355
- if (schema.$ref === '#') throw DECLINE
5827
+ if (schema.$ref && !schema[REF_DONE]) {
5828
+ // A keyword beside $ref that validates applies too (2020-12): the
5829
+ // reference is written on its own, then the node again with the
5830
+ // reference marked done, on a fresh copy that keeps `$ref` for the
5831
+ // annotation readers, as genCodeCNode does. This used to decline, which
5832
+ // kept the 2020-12 meta-schema (every vocabulary schema is a reference
5833
+ // with siblings once unrolled by scope) off both error generators and on
5834
+ // the interpreter for its errors, about 2 microseconds a rejection.
5835
+ if (Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))) {
5836
+ genCodeENode({ $ref: schema.$ref }, v, pathExpr, lines, ctx, schemaPrefix)
5837
+ const rest = { ...schema }
5838
+ Object.defineProperty(rest, REF_DONE, { value: true })
5839
+ genCodeENode(rest, v, pathExpr, lines, ctx, schemaPrefix)
5840
+ return
5841
+ }
5842
+ // A self-reference is a call to a helper over the root schema that takes
5843
+ // the path and the schema path as arguments, as a definition on a cycle
5844
+ // does below: the root helper takes no path, so errors raised through it
5845
+ // carried the root's path, and this declined, which left a standalone
5846
+ // module reporting one stub error for a schema with `$ref: "#"` anywhere
5847
+ // in it. This used to fall through as a permissive no-op, which accepted
5848
+ // whatever the reference guarded.
5849
+ if (schema.$ref === '#') {
5850
+ // The body comes from the prepared root (`rootE`), whose references
5851
+ // are the resolvable ones; from the schema as given, a reference by
5852
+ // `$id` resolved to nothing and the helper for the suite's recursive
5853
+ // tree checked no node.
5854
+ if (!ctx.rootE || typeof ctx.rootE !== 'object') throw DECLINE
5855
+ if (!ctx.defFns) ctx.defFns = new Map()
5856
+ let fnName = ctx.defFns.get('#')
5857
+ if (!fnName) {
5858
+ fnName = '_defE' + ctx.defFns.size + '_root'
5859
+ ctx.defFns.set('#', fnName)
5860
+ // The helper is its own function, so the references expanded in it
5861
+ // start from nothing: inside the expansion of a definition that led
5862
+ // here, the definition's own reference would otherwise read as
5863
+ // already being expanded.
5864
+ const bodyLines = []
5865
+ const outerStack = ctx.refStack
5866
+ ctx.refStack = new Set()
5867
+ try { genCodeE(ctx.rootE, 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER) } finally { ctx.refStack = outerStack }
5868
+ let body = bodyLines.join('\n ')
5869
+ body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
5870
+ if (body.includes(SP_PLACEHOLDER)) throw DECLINE
5871
+ ctx.helperCode.push(
5872
+ `const ${fnName}_s=new Set()\n function ${fnName}(d,_p,_all,_e,_sp){\n ` +
5873
+ `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 ` +
5874
+ `if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d,_p,_all,_e,_sp)\n _sd--\n return _r\n }\n ` +
5875
+ `function ${fnName}_b(d,_p,_all,_e,_sp){${body}}`,
5876
+ )
5877
+ }
5878
+ lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e,'${schemaPrefix}');if(!_all&&_e.length)return{valid:false,errors:_e}`)
5879
+ return
5880
+ }
5356
5881
  const m = schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
5357
5882
  if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
5358
5883
  const defName = m[1]
@@ -5392,7 +5917,10 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
5392
5917
  lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e,'${schemaPrefix}');if(!_all&&_e.length)return{valid:false,errors:_e}`)
5393
5918
  return
5394
5919
  }
5395
- if (ctx.refStack.has(schema.$ref)) return
5920
+ // A reference back into what is being expanded, on a cycle the
5921
+ // definitions alone do not show (through the root, or another
5922
+ // document): declined. Returning emitted nothing, which read as valid.
5923
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
5396
5924
  ctx.refStack.add(schema.$ref)
5397
5925
  genCodeE(ctx.rootDefs[defName], v, pathExpr, lines, ctx, schemaPrefix)
5398
5926
  ctx.refStack.delete(schema.$ref)
@@ -5403,7 +5931,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
5403
5931
  const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
5404
5932
  const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
5405
5933
  if (anchorTarget) {
5406
- if (ctx.refStack.has(schema.$ref)) return
5934
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
5407
5935
  ctx.refStack.add(schema.$ref)
5408
5936
  genCodeE(anchorTarget, v, pathExpr, lines, ctx, schemaPrefix)
5409
5937
  ctx.refStack.delete(schema.$ref)
@@ -5411,7 +5939,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
5411
5939
  }
5412
5940
  }
5413
5941
  if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
5414
- if (ctx.refStack.has(schema.$ref)) return
5942
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
5415
5943
  ctx.refStack.add(schema.$ref)
5416
5944
  genCodeE(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
5417
5945
  ctx.refStack.delete(schema.$ref)
@@ -5421,13 +5949,16 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
5421
5949
  if (ctx.schemaMap && schema.$ref.includes('#') && !schema.$ref.startsWith('#')) {
5422
5950
  const r = resolveCrossSchemaRef(schema.$ref, ctx.schemaMap)
5423
5951
  if (r) {
5424
- if (ctx.refStack.has(schema.$ref)) return
5952
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
5425
5953
  ctx.refStack.add(schema.$ref)
5426
5954
  genCodeE(r.schema, v, pathExpr, lines, ctx, schemaPrefix)
5427
5955
  ctx.refStack.delete(schema.$ref)
5428
5956
  return
5429
5957
  }
5430
5958
  }
5959
+ // A reference none of the above resolved: declined. Falling through
5960
+ // emitted nothing for it, which read as valid.
5961
+ throw DECLINE
5431
5962
  }
5432
5963
 
5433
5964
  // $dynamicRef — resolve via anchors map
@@ -6019,8 +6550,63 @@ function sharedErr (lit, rootSchema) {
6019
6550
  // the value at `pathExpr`, which enrichment otherwise finds again by walking
6020
6551
  // the document. `paramsShared` names a frozen params object hoisted for a
6021
6552
  // static site, so a static error's params stay one shared object as before.
6553
+ // The ordinal `_ap` is handed with each error of the combined function: the
6554
+ // error's place in schema order when its path is fixed at compile time, -1
6555
+ // when it is not (a path through a shared definition, a collapsed branch
6556
+ // error, an enriched error). `_ap` keeps whether the list is still in order
6557
+ // as it grows; a list that came out in order, which is most of them, is
6558
+ // handed out without the sort that looked every error's path up in a map.
6559
+ function apOrd (ctx, sp, coarse) {
6560
+ // Inside a shared definition's function every error takes the ordinal of
6561
+ // the reference that reached it, handed in as `_so`: errors behind one
6562
+ // `$ref` sort where the reference is written, in the order the definition
6563
+ // reports them.
6564
+ if (typeof sp === 'string' && sp.includes(SP_PLACEHOLDER) && ctx && ctx.inDefC) return '_so'
6565
+ if (typeof sp !== 'string' || sp.includes(SP_PLACEHOLDER) || sp.includes('+')) return -1
6566
+ if (!ctx || !ctx.rootSchema) return -1
6567
+ const path = unescapeSp(sp)
6568
+ const o = ordinalFor(ctx.rootSchema, path)
6569
+ if (typeof o !== 'number') return -1
6570
+ // The reader orders by ordinal and, between equal ordinals, by the rank of
6571
+ // the path (tieOrder in lib/rejections.js). Both are known here, so a site
6572
+ // gets a token that assignOrdKeys turns into a dense integer in exactly that
6573
+ // order when the program is assembled: two errors with one key are ones the
6574
+ // reader keeps as they came. Keyed by the ordinal alone, two errors under
6575
+ // one synthetic definition shared a key and sent every such list to the
6576
+ // reader, which ranked the paths again: 45 percent of reading a list
6577
+ // schema's two errors.
6578
+ if (!ctx.ordSites) ctx.ordSites = new Map()
6579
+ let site = ctx.ordSites.get(path)
6580
+ if (site === undefined) { site = { i: ctx.ordSites.size, o, r: rankFor(ctx.rootSchema, path), key: -1 }; ctx.ordSites.set(path, site) }
6581
+ // A coarse key, for the errors behind a reference into a shared definition
6582
+ // (see `ap` in lib/combined-runtime.js): -(k+2), written when k is known.
6583
+ return coarse ? `@@C${site.i}@@` : `@@O${site.i}@@`
6584
+ }
6585
+
6586
+ // The dense keys of a program's error sites, in the reader's order: ordinal,
6587
+ // then rank as tieOrder compares it, element by element and the shorter
6588
+ // first; equal pairs share a key, and a site without a rank keeps -1, which
6589
+ // hands its list to the reader. Replaces the tokens apOrd wrote.
6590
+ function assignOrdKeys (ctx, src) {
6591
+ if (!ctx.ordSites || ctx.ordSites.size === 0) return src
6592
+ const sites = [...ctx.ordSites.values()]
6593
+ const cmp = (a, b) => {
6594
+ if (a.o !== b.o) return a.o - b.o
6595
+ const m = Math.min(a.r.length, b.r.length)
6596
+ for (let k = 0; k < m; k++) if (a.r[k] !== b.r[k]) return a.r[k] - b.r[k]
6597
+ return a.r.length - b.r.length
6598
+ }
6599
+ const ranked = sites.filter((x) => x.r !== null).sort(cmp)
6600
+ let key = 0
6601
+ for (let i = 0; i < ranked.length; i++) { if (i > 0 && cmp(ranked[i - 1], ranked[i]) !== 0) key++; ranked[i].key = key }
6602
+ return src.replace(/@@([OC])(\d+)@@/g, (_, t, i) => { const k = sites[+i].key; return String(t === 'O' || k < 0 ? k : -(k + 2)) })
6603
+ }
6604
+
6605
+ // The error list and its order live in lib/combined-runtime.js (`ap`, `sr`):
6606
+ // `_A` is this program's state, reset at the top of each call.
6607
+ const AP_RESET = '_A.mo=true;_A.lo=-1;_A.on=0;'
6022
6608
  function errPushC(ctx, keyword, sp, pathExpr, paramsCode, msgCode, valueExpr, lean, paramsShared) {
6023
- if (!ctx.rich) return `(_e=_ap(_e,${lean}))`
6609
+ if (!ctx.rich) return `(_e=_ap(_A,_e,${lean},${apOrd(ctx, sp)}))`
6024
6610
  if (!ctx.richMk) {
6025
6611
  ctx.richMk = true
6026
6612
  const { makeRich } = require('./enrich-site')
@@ -6036,7 +6622,7 @@ function errPushC(ctx, keyword, sp, pathExpr, paramsCode, msgCode, valueExpr, le
6036
6622
  // the definition and the reference's own comes in as `_sp`.
6037
6623
  const rel = sp.startsWith(SP_PLACEHOLDER)
6038
6624
  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' : ''})))`
6625
+ return `(_e=_ap(_A,_e,_mk(${siteVar},${pathExpr || '""'},${paramsShared || paramsCode},${msgCode},${valueExpr},d${rel ? ',_sp' : ''}),-1))`
6040
6626
  }
6041
6627
 
6042
6628
  function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts) {
@@ -6122,7 +6708,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
6122
6708
  // creates it as a one-element literal. Pushing onto an empty `[]` grew its
6123
6709
  // backing store on every rejection, about 7% of reading errors across the
6124
6710
  // official suite.
6125
- const ctx = { noOrdinal: !!(opts && opts.runtimeShape), rich: !!(opts && opts.rich), inCombined: true, hoisted: [], rootC: schema, varCounter: 0, helperCode: ['function _ap(e,x){if(e===undefined)return[x];e.push(x);return e}'], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen],
6711
+ 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
6712
  rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: inputSchema, userFormats: userFormats || null }
6127
6713
  const lines = []
6128
6714
  try {
@@ -6143,6 +6729,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
6143
6729
  ctx.closureVars.push('_ER', '_EE', '_SRT', '_FB', '_VF')
6144
6730
  ctx.closureVals.push(opts.resultShape.Rejection, opts.resultShape.empty, opts.resultShape.sort, opts.resultShape.fallback, opts.resultShape.verdict || null)
6145
6731
  }
6732
+ if (ctx.usesBranchCollapse) bindRt(ctx, ['__ataCollapse', '__ataMulti'])
6146
6733
  const closureNames = ctx.usesSafeRe ? [...ctx.closureVars, '__ataSafeRe'] : ctx.closureVars
6147
6734
  const closureArgs = ctx.usesSafeRe ? [...ctx.closureVals, compileSafe] : ctx.closureVals
6148
6735
  const closureParams = closureNames.join(',')
@@ -6161,7 +6748,6 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
6161
6748
  // the branch functions (this function collects everything), and the
6162
6749
  // runtime shape the error generator gives its literals, without code and
6163
6750
  // docUrl. The collapsed error itself keeps its code, as everywhere.
6164
- if (ctx.usesBranchCollapse) ctx.helperCode.push(require('./branch-collapse').embedSource())
6165
6751
  if (ctx.usesBranchCollapse && opts && opts.runtimeShape) {
6166
6752
  const shape = (src) => src.replace(/code:'ATA\d{4}',/g, '').replace(/,docUrl:'https:\/\/ata-validator\.com\/e\/ATA\d{4}'/g, '')
6167
6753
  for (let i = 0; i < ctx.helperCode.length; i++) ctx.helperCode[i] = shape(ctx.helperCode[i])
@@ -6182,7 +6768,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
6182
6768
  ctx.helperCode.push(`function _bodyC(d){${hoistDecl}${allDecl}let _e;\n ${checks}\n return _e}`)
6183
6769
  hoistDecl = ''
6184
6770
  allDecl = ''
6185
- checks = `_sd=0;try{_e=_bodyC(d)}catch(_err){if(_err!==_CYC)throw _err;_sg=true;${clears};try{_e=_bodyC(d)}finally{_sg=false}}`
6771
+ 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
6772
  }
6187
6773
  // In the result shape this function is validate() itself, so a document
6188
6774
  // that makes it throw is answered by the caller's previous path instead
@@ -6195,18 +6781,21 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
6195
6781
  // Not when every collapse is guarded by a count (emitBranchCollapse): an
6196
6782
  // accepted document then runs no branch twice, and a rejected one would
6197
6783
  // 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;\n ` + checks +
6784
+ ? `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
6785
  // A collapsed branch error is presented even alone (its ordinal and
6200
6786
  // raw branch errors stay inside); otherwise only a list of two or more
6201
6787
  // needs ordering.
6202
- `\n return _e?new _ER(${ctx.usesBranchCollapse ? '_SRT(_e)' : '_e.length>1?_SRT(_e):_e'}):{valid:true,data:d,errors:_EE}}catch(_x){return _FB(d)}`
6203
- : hoistDecl + allDecl + `let _e;\n ` + checks + `\n return _e?{valid:false,errors:_e}:R`
6204
- const helpers = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
6788
+ `\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)}`
6789
+ : hoistDecl + allDecl + `let _e;${AP_RESET}\n ` + checks + `\n return _e?{valid:false,errors:_e}:R`
6790
+ // The verdict generator's hoisted functions (subtreeGuard writes guards
6791
+ // through it) come first: a oneOf branch it could not hoist was a closure
6792
+ // built at every call of the guard, six per element of a json-patch list.
6793
+ const helpers = (ctx.preamble.length ? ctx.preamble.join('\n ') + '\n ' : '') + (ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : '')
6205
6794
 
6206
6795
  try {
6207
6796
  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
6797
  const factory = new Function('R' + (closureParams ? ',' + closureParams : ''),
6209
- `${helpers}return function(d){${inner}}`)
6798
+ assignOrdKeys(ctx, `${helpers}return function _vC(d){${inner}}`))
6210
6799
  return factory(VALID_RESULT, ...closureArgs)
6211
6800
  } catch (e) {
6212
6801
  if (typeof process !== 'undefined' && process.env && process.env.ATA_DEBUG) console.error('compileToJSCombined error:', e.message, '\n', inner.slice(0, 500))
@@ -6262,11 +6851,32 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6262
6851
  // declares its own branch counts (genUnevaluatedDynamicC). The document root
6263
6852
  // comes in as `d` for the rich errors that read it. Not for a definition on a
6264
6853
  // reference cycle, which keeps its existing path. False when not shared.
6854
+ const COMBINED_DEF_FN_MIN = 160
6855
+ const _defOwnChars = new WeakMap()
6856
+ // The definition's own size, its annotations left out, measured once.
6857
+ function defOwnChars (def) {
6858
+ if (def === null || typeof def !== 'object') return 0
6859
+ let n = _defOwnChars.get(def)
6860
+ if (n === undefined) {
6861
+ n = 0
6862
+ 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]) } }
6863
+ walk(def)
6864
+ _defOwnChars.set(def, n)
6865
+ }
6866
+ return n
6867
+ }
6868
+
6265
6869
  function emitSharedDefC(defName, v, pathExpr, lines, ctx, schemaPrefix) {
6266
6870
  if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
6267
6871
  if (!ctx.sharedDefs) ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs)
6268
6872
  const cyclic = ctx.cyclicDefs.has(defName)
6269
- if (!cyclic && !ctx.sharedDefs.has(defName)) return false
6873
+ // In the combined function every definition of some size is a function of
6874
+ // its own, referenced once or not: expanded, a 25-definition API schema
6875
+ // was one 122 KB function, which the optimizing compiler took tens of
6876
+ // thousands of calls to reach, so its first twenty thousand answers cost
6877
+ // 89 ns each where the hundred-thousandth costs 10. Functions the size of
6878
+ // one definition tier up early, and the hot ones first.
6879
+ if (!cyclic && !ctx.sharedDefs.has(defName) && !(ctx.inCombined && defOwnChars(ctx.rootDefs[defName]) >= COMBINED_DEF_FN_MIN)) return false
6270
6880
  emitDefFnC(defName, ctx.rootDefs[defName], cyclic, v, pathExpr, lines, ctx, schemaPrefix)
6271
6881
  return true
6272
6882
  }
@@ -6296,8 +6906,10 @@ function emitDefFnC(key, target, cyclic, v, pathExpr, lines, ctx, schemaPrefix)
6296
6906
  const bodyLines = []
6297
6907
  let hoisted
6298
6908
  try {
6909
+ ctx.inDefC = (ctx.inDefC || 0) + 1
6299
6910
  genCodeC(target, '_dv', '_p', bodyLines, ctx, SP_PLACEHOLDER)
6300
6911
  } finally {
6912
+ ctx.inDefC--
6301
6913
  hoisted = ctx.hoisted
6302
6914
  ctx.hoisted = outerHoisted
6303
6915
  ctx.refStack = outerRefs
@@ -6306,19 +6918,19 @@ function emitDefFnC(key, target, cyclic, v, pathExpr, lines, ctx, schemaPrefix)
6306
6918
  body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
6307
6919
  if (body.includes(SP_PLACEHOLDER)) throw DECLINE
6308
6920
  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}`
6921
+ const fn = `function ${cyclic ? fnName + '_b' : fnName}(_dv,_p,_sp,d,_so){const _all=true;${decl}let _e;\n ${body}\n return _e}`
6310
6922
  if (cyclic) {
6311
6923
  if (!ctx.cycFnsC) ctx.cycFnsC = []
6312
6924
  ctx.cycFnsC.push(fnName)
6313
6925
  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)
6926
+ `const ${fnName}_s=new Set()\n function ${fnName}(_dv,_p,_sp,d,_so){\n ` +
6927
+ `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 ` +
6928
+ `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
6929
  } else {
6318
6930
  ctx.helperCode.push(fn)
6319
6931
  }
6320
6932
  }
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])}}`)
6933
+ 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
6934
  }
6323
6935
 
6324
6936
  const REF_DONE = Symbol('ata.refDone')
@@ -6459,12 +7071,12 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6459
7071
  const lit = {keyword, instancePath: pathVal, schemaPath: spVal, params: Object.freeze(paramsVal), message: msgVal}
6460
7072
  if (o !== null) lit._o = o
6461
7073
  ctx.closureVals.push(sharedErr(lit, ctx.rootSchema))
6462
- return `(_e=_ap(_e,${errVar}))`
7074
+ return `(_e=_ap(_A,_e,${errVar},${apOrd(ctx, sp)}))`
6463
7075
  }
6464
7076
  }
6465
7077
  }
6466
7078
  // 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)}}))`
7079
+ return `(_e=_ap(_A,_e,{keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode}${ordinalField(ctx, sp)}},${apOrd(ctx, sp)}))`
6468
7080
  }
6469
7081
 
6470
7082
  // enum and const apply to a value of any type, so a value that fails
@@ -6554,7 +7166,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6554
7166
  const pathVal = pathExpr ? literalValue(pathExpr) : ''
6555
7167
  ctx.closureVars.push(errVar)
6556
7168
  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}))}`)
7169
+ lines.push(`if(${check}){(_e=_ap(_A,_e,${errVar},${apOrd(ctx, `${schemaPrefix}/required`)}))}`)
6558
7170
  } else {
6559
7171
  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
7172
  }
@@ -6568,7 +7180,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6568
7180
  const pathVal = pathExpr ? literalValue(pathExpr) : ''
6569
7181
  ctx.closureVars.push(errVar)
6570
7182
  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}))}`)
7183
+ 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
7184
  } else {
6573
7185
  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
7186
  }
@@ -6667,8 +7279,12 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6667
7279
  const checks = propKeys.map(k => `_k${ci}[_i]!==${JSON.stringify(k)}`).join('&&') || 'true'
6668
7280
  body = `let _x${ci}=0;for(const _f${ci} in ${v}){if(${gate}){_x${ci}=1;break}}if(_x${ci}){${detail(checks)}}`
6669
7281
  } else {
6670
- const allowed = propKeys.map(k => JSON.stringify(k)).join(',')
6671
- body = `const _a${ci}=new Set([${allowed}]);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])`)}}`
7282
+ // The name set lives in the closure, built once with the function: built
7283
+ // in the body it was a 33-name Set on every call of this function, which
7284
+ // is where a rejected npm-badges document spent most of its 701 ns.
7285
+ ctx.closureVars.push(`_a${ci}`)
7286
+ ctx.closureVals.push(new Set(propKeys))
7287
+ 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
7288
  }
6673
7289
  // When every declared property is required, a clean object has exactly the
6674
7290
  // declared keys: if the enumerated count equals the declared count and
@@ -6720,7 +7336,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6720
7336
  const pathVal = pathExpr ? literalValue(pathExpr) : ''
6721
7337
  ctx.closureVars.push(errVar)
6722
7338
  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}))}`)
7339
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){(_e=_ap(_A,_e,${errVar},-1))}`)
6724
7340
  } else {
6725
7341
  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
7342
  }
@@ -6736,16 +7352,16 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6736
7352
  if (requiredSet.has(key) && isObj) {
6737
7353
  // A hoisted local was already cleared if the value is only inherited.
6738
7354
  lines.push(`if(${pv}!==undefined${hoisted[key] ? '' : '&&' + ownGuard(ctx, v, key)}){`)
6739
- genCodeC(prop, pv, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
7355
+ genGuardedC(prop, pv, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
6740
7356
  lines.push(`}`)
6741
7357
  } else if (isObj) {
6742
7358
  const oi = ctx.varCounter++
6743
7359
  lines.push(`{const _o${oi}=${v}[${JSON.stringify(key)}];if(_o${oi}!==undefined&&${ownGuard(ctx, v, key)}){`)
6744
- genCodeC(prop, `_o${oi}`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
7360
+ genGuardedC(prop, `_o${oi}`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
6745
7361
  lines.push(`}}`)
6746
7362
  } else {
6747
7363
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
6748
- genCodeC(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
7364
+ genGuardedC(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
6749
7365
  lines.push(`}`)
6750
7366
  }
6751
7367
  }
@@ -6775,7 +7391,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6775
7391
  ctx.varCounter++
6776
7392
  const childPath = childPathDynExpr(pathExpr, idx)
6777
7393
  lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
6778
- genCodeC(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
7394
+ genGuardedC(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
6779
7395
  lines.push(`}}`)
6780
7396
  }
6781
7397
 
@@ -6853,13 +7469,25 @@ function genUniqueItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, is
6853
7469
 
6854
7470
  // The verdict form of genUnevaluatedDynamicC: the same set, built the same
6855
7471
  // way, and the answer false at the first key outside it.
6856
- function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj) {
6857
- if (schema.additionalProperties !== undefined) return
7472
+ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj, nonObjApplicators) {
7473
+ if (schema.additionalProperties !== undefined) {
7474
+ if (nonObjApplicators) throw DECLINE
7475
+ return
7476
+ }
6858
7477
  const ui = ctx.varCounter++
6859
7478
  const S = `_ev${ui}`
6860
7479
  const body = [`const ${S}=[]`]
6861
7480
  addLocalAnnotations(schema, v, S, body, ctx)
6862
- addTopApplicatorAnnotations(schema, v, S, body, ctx)
7481
+ // With the anyOf and oneOf blocks moved here (see genCodeNode), their
7482
+ // passing branches are counted by the annotation pass and judged from the
7483
+ // count: a oneOf holds with exactly one, an anyOf with at least one.
7484
+ const counts = nonObjApplicators ? { anyOf: `_ac${ui}`, oneOf: `_oc${ui}`, oneOfMask: null, of: null, verdict: true } : null
7485
+ if (counts) body.push(`let ${counts.anyOf}=0,${counts.oneOf}=0`)
7486
+ addTopApplicatorAnnotations(schema, v, S, body, ctx, counts)
7487
+ if (counts) {
7488
+ if (Array.isArray(schema.anyOf)) body.push(`if(${counts.anyOf}<1)return false`)
7489
+ if (Array.isArray(schema.oneOf)) body.push(`if(${counts.oneOf}!==1)return false`)
7490
+ }
6863
7491
  const kVar = `_uk${ui}`
6864
7492
  const up = schema.unevaluatedProperties
6865
7493
  if (up === false) {
@@ -6871,7 +7499,10 @@ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj) {
6871
7499
  } else {
6872
7500
  throw DECLINE
6873
7501
  }
6874
- lines.push(isObj ? `{${body.join(';')}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body.join(';')}}`)
7502
+ // A value that is not an object skips the pass, so the blocks moved here
7503
+ // run for it in the `else`; a value known to be an object has no `else`.
7504
+ const other = nonObjApplicators && nonObjApplicators.length && !isObj ? `else{${nonObjApplicators.join('\n ')}}` : ''
7505
+ lines.push(isObj ? `{${body.join(';')}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body.join(';')}}${other}`)
6875
7506
  }
6876
7507
 
6877
7508
  // unevaluatedProperties where what is evaluated depends on which branches
@@ -6881,6 +7512,236 @@ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj) {
6881
7512
  // branch, an if, then or else, or a $ref target counts only when it passes,
6882
7513
  // a oneOf only when exactly one branch passes, and `not` never. Each branch is
6883
7514
  // asked through an annotation function (emitAnnot).
7515
+ // Run-time unevaluatedProperties written as straight-line code. The names a
7516
+ // node and its in-place applicators can evaluate are known when the code is
7517
+ // written (their `properties`), so the evaluated set is a bit per name in an
7518
+ // integer, each branch's verdict a labelled block, and a failed oneOf's
7519
+ // rollback the integer it started from. The annotation functions this
7520
+ // replaces (emitAnnot) pushed names onto an array, asked it with `includes`
7521
+ // for every key, and were a call per branch per level: ten functions and
7522
+ // twenty calls for the suite's nested-reference case, which in the first
7523
+ // thousands of calls of a process, before anything is inlined, cost 548 ns
7524
+ // against 131. Null when the shape is not modelled: names that are only known
7525
+ // at run time (patternProperties, additionalProperties), a nested
7526
+ // unevaluatedProperties, a reference cycle, more than 30 names, or too much
7527
+ // code; the annotation functions then stand in as before.
7528
+ const ANNOT_INLINE_MAX = 8192
7529
+ const ANNOT_NAME_MAX = 30
7530
+ const ANNOT_BLOCKERS = ['unevaluatedItems', '$dynamicRef', '$recursiveRef', 'dependencies']
7531
+ // Besides a bit per declared name, the mask has a bit per pattern of a
7532
+ // patternProperties (set when the subschema holding it holds: the keys it
7533
+ // matches are evaluated) and one bit, `all`, for a subschema that evaluates
7534
+ // every key when it holds (additionalProperties of any value, or a nested
7535
+ // `unevaluatedProperties: true`). A nested `unevaluatedProperties` that
7536
+ // checks something is not modelled.
7537
+ const ALL_BIT_KEY = '\u0000all'
7538
+ function annotSubs (node, ctx) {
7539
+ const subs = []
7540
+ for (const b of node.allOf || []) subs.push(b)
7541
+ for (const b of node.anyOf || []) subs.push(b)
7542
+ for (const b of node.oneOf || []) subs.push(b)
7543
+ if (node.if !== undefined) subs.push(node.if)
7544
+ if (node.then !== undefined) subs.push(node.then)
7545
+ if (node.else !== undefined) subs.push(node.else)
7546
+ if (node.dependentSchemas && typeof node.dependentSchemas === 'object') for (const ds of Object.values(node.dependentSchemas)) subs.push(ds)
7547
+ if (typeof node.$ref === 'string') subs.push(refTargetFor(node.$ref, ctx))
7548
+ return subs
7549
+ }
7550
+ function annotNames (node, ctx, names, seen, top) {
7551
+ if (node === true || node === false) return true
7552
+ if (node === null || typeof node !== 'object' || Array.isArray(node)) return false
7553
+ if (seen.has(node)) return false
7554
+ seen.add(node)
7555
+ try {
7556
+ for (const k of ANNOT_BLOCKERS) if (node[k] !== undefined) return false
7557
+ if (!top && node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== true) return false
7558
+ const add = (key) => { if (!names.has(key)) { if (names.size >= ANNOT_NAME_MAX) return false; names.set(key, names.size) } return true }
7559
+ if (node.properties && typeof node.properties === 'object') for (const k of Object.keys(node.properties)) if (!add(k)) return false
7560
+ if (node.patternProperties && typeof node.patternProperties === 'object') for (const pat of Object.keys(node.patternProperties)) if (!add('/' + pat)) return false
7561
+ if ((!top && node.additionalProperties !== undefined) || (!top && node.unevaluatedProperties === true)) if (!add(ALL_BIT_KEY)) return false
7562
+ let subs
7563
+ try { subs = annotSubs(node, ctx) } catch { return false }
7564
+ for (const b of subs) if (!annotNames(b, ctx, names, seen, false)) return false
7565
+ return true
7566
+ } finally { seen.delete(node) }
7567
+ }
7568
+ // The bits a subschema's own keywords evaluate when it holds: its declared
7569
+ // names that are present, every pattern it declares, and `all` for a
7570
+ // subschema that evaluates every key.
7571
+ function ownBits (sub, v, names, ctx, t, top) {
7572
+ const out = []
7573
+ for (const k of Object.keys(sub.properties || {})) out.push(`if(${ownKeyExpr(ctx, v, k)})${t}|=${1 << names.get(k)}`)
7574
+ let fixed = 0
7575
+ for (const pat of Object.keys(sub.patternProperties || {})) fixed |= 1 << names.get('/' + pat)
7576
+ if (!top && (sub.additionalProperties !== undefined || sub.unevaluatedProperties === true)) fixed |= 1 << names.get(ALL_BIT_KEY)
7577
+ if (fixed !== 0) out.push(`${t}|=${fixed}`)
7578
+ return out
7579
+ }
7580
+ // The test for one pattern bit against a key variable, a closure regex or a
7581
+ // fixed prefix test (fastPrefixCheck) as addLocalAnnotations writes them.
7582
+ function patternKeyTest (ctx, pat, kv) {
7583
+ const fast = fastPrefixCheck(pat, kv)
7584
+ if (fast) return fast
7585
+ const ri = ctx.varCounter++
7586
+ ctx.closureVars.push(`_re${ri}`)
7587
+ ctx.closureVals.push(safeReClosure(ctx, pat))
7588
+ return `_re${ri}.test(${kv})`
7589
+ }
7590
+ // Factory-scope count variables for one anyOf or oneOf array (by identity),
7591
+ // registered in ctx.branchCounts for emitBranchCollapse: `of` the object
7592
+ // they were counted for, `count` how many branches held, `mask` which (oneOf
7593
+ // up to 30 branches). Declared once per program; a stale value is never
7594
+ // taken, since every reader checks `of` against the value at hand.
7595
+ function factoryCount (ctx, arr, withMask) {
7596
+ if (!ctx.inCombined) return null
7597
+ if (!ctx.branchCounts) ctx.branchCounts = new Map()
7598
+ const hit = ctx.branchCounts.get(arr)
7599
+ if (hit !== undefined) return hit.factory ? hit : null
7600
+ const id = ctx.varCounter++
7601
+ const fc = { of: `_fa${id}`, count: `_fn${id}`, mask: withMask ? `_fm${id}` : null, factory: true }
7602
+ ctx.helperCode.push(`let ${fc.of}=null,${fc.count}=0${withMask ? `,${fc.mask}=0` : ''}`)
7603
+ ctx.branchCounts.set(arr, fc)
7604
+ return fc
7605
+ }
7606
+ // Lines that set `ok` (declared by the caller, 1 or 0) for `sub` at `v`, an
7607
+ // object, and OR the names it evaluated into `tv` when it holds.
7608
+ function emitAnnotInline (sub, v, names, ctx, out, ok, tv) {
7609
+ if (sub === true) { out.push(`${ok}=1`); return }
7610
+ if (sub === false) { out.push(`${ok}=0`); return }
7611
+ const id = ctx.varCounter++
7612
+ const t = `_t${id}`
7613
+ const local = {}
7614
+ for (const k of Object.keys(sub)) if (!ANNOT_APPLICATORS.has(k)) setOwn(local, k, sub[k])
7615
+ const vl = []
7616
+ // The value is known to be an object here (the block runs for objects
7617
+ // only), which the verdict is told when the branch does not say otherwise:
7618
+ // told a type, the generator takes it for the value's and skips the
7619
+ // branch's own `type`, so a `type: "string"` branch has to keep its check.
7620
+ ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
7621
+ try { genCode(local, v, vl, ctx, local.type === undefined || local.type === 'object' ? 'object' : undefined) } finally { ctx.nestedBoolean-- }
7622
+ out.push(`let ${t}=0;${ok}=1`)
7623
+ if (vl.length) out.push(`a${id}:{${replaceTopLevel(vl.join(';'), `break a${id}`, `{${ok}=0;break a${id}}`)}}`)
7624
+ if (sub.not !== undefined) {
7625
+ const nl = []
7626
+ nestedGenCode(sub.not, v, nl, ctx)
7627
+ 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}`)
7628
+ else out.push(`${ok}=0`)
7629
+ }
7630
+ const own = ownBits(sub, v, names, ctx, t, false)
7631
+ if (own.length) out.push(`if(${ok}){${own.join(';')}}`)
7632
+ let bi = 0
7633
+ 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] }
7634
+ for (const b of sub.allOf || []) { const [o, tb] = child(b); out.push(`if(!${o})${ok}=0;else ${t}|=${tb}`) }
7635
+ // The counts of a nested anyOf or oneOf go to factory-scope variables as
7636
+ // well (factoryCount), where the keyword's own error code in a branch
7637
+ // function, and the counting copies, read them instead of deciding the
7638
+ // branches again: the suite's nested-reference case decided each branch
7639
+ // four times over (this pass, the verdict closures, the counting copies,
7640
+ // the chosen branch's error function).
7641
+ if (Array.isArray(sub.anyOf)) {
7642
+ const na = `_na${id}`
7643
+ const fc = factoryCount(ctx, sub.anyOf, false)
7644
+ out.push(`let ${na}=0${fc ? `;${fc.of}=${v};${fc.count}=0` : ''}`)
7645
+ for (const b of sub.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${t}|=${tb}${fc ? `;${fc.count}++` : ''}}`) }
7646
+ out.push(`if(${na}===0)${ok}=0`)
7647
+ }
7648
+ if (Array.isArray(sub.oneOf)) {
7649
+ const nc = `_nc${id}`, tc = `_tc${id}`
7650
+ const fc = factoryCount(ctx, sub.oneOf, sub.oneOf.length <= 30)
7651
+ out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
7652
+ 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}` : ''}` : ''}}`) })
7653
+ out.push(`if(${nc}===1)${t}|=${tc};else ${ok}=0`)
7654
+ }
7655
+ if (sub.if !== undefined) {
7656
+ const [oi, ti] = child(sub.if)
7657
+ const thenPart = []
7658
+ 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}`) }
7659
+ const elsePart = []
7660
+ 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}`) }
7661
+ out.push(`if(${oi}){${t}|=${ti};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
7662
+ }
7663
+ if (sub.dependentSchemas && typeof sub.dependentSchemas === 'object') {
7664
+ 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}}`) }
7665
+ }
7666
+ if (typeof sub.$ref === 'string') { const [o, tb] = child(refTargetFor(sub.$ref, ctx)); out.push(`if(!${o})${ok}=0;else ${t}|=${tb}`) }
7667
+ out.push(`if(${ok})${tv}|=${t}`)
7668
+ }
7669
+ // The whole run-time unevaluatedProperties block for `schema` at `v`, or
7670
+ // null. `counts` as in genUnevaluatedDynamicC: how many anyOf and oneOf
7671
+ // branches held, for the keywords' own code to read.
7672
+ function inlineUnevalBlock (schema, v, pathExpr, ctx, schemaPrefix, fail, counts) {
7673
+ const names = new Map()
7674
+ if (!annotNames(schema, ctx, names, new Set(), true)) return null
7675
+ const saved = snapshotCtx(ctx)
7676
+ try {
7677
+ const id = ctx.varCounter++
7678
+ const m = `_m${id}`
7679
+ const out = [`let ${m}=0`]
7680
+ // One prototype flag for the object, read by every presence test below.
7681
+ if (!ctx.plainOf) ctx.plainOf = new Map()
7682
+ const prevPk = ctx.plainOf.get(v)
7683
+ const pk = prevPk !== undefined ? prevPk : `_pk${id}`
7684
+ const prevReuse = ctx.plainReuse
7685
+ if (prevPk === undefined) { out.push(`const ${pk}=${protoIs(ctx, v)}`); ctx.plainOf.set(v, pk); (ctx.plainUsed || (ctx.plainUsed = new Set())).add(pk) }
7686
+ ctx.plainReuse = v
7687
+ out.push(...ownBits(schema, v, names, ctx, m, true))
7688
+ let bi = 0
7689
+ 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] }
7690
+ for (const b of schema.allOf || []) { const [, tb] = child(b); out.push(`${m}|=${tb}`) }
7691
+ if (Array.isArray(schema.anyOf)) {
7692
+ const na = counts ? counts.anyOf : `_na${id}`
7693
+ out.push(counts ? `${na}=0` : `let ${na}=0`)
7694
+ for (const b of schema.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${m}|=${tb}}`) }
7695
+ }
7696
+ if (Array.isArray(schema.oneOf)) {
7697
+ const nc = counts ? counts.oneOf : `_nc${id}`, tc = `_tc${id}`
7698
+ out.push(counts ? `${nc}=0;let ${tc}=0` : `let ${nc}=0,${tc}=0`)
7699
+ 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};` : ''}}`) })
7700
+ out.push(`if(${nc}===1)${m}|=${tc}`)
7701
+ }
7702
+ if (schema.if !== undefined) {
7703
+ const [oi, ti] = child(schema.if)
7704
+ const thenPart = []
7705
+ 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}`) }
7706
+ const elsePart = []
7707
+ 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}`) }
7708
+ out.push(`if(${oi}){${m}|=${ti};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
7709
+ }
7710
+ if (schema.dependentSchemas && typeof schema.dependentSchemas === 'object') {
7711
+ 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}}`) }
7712
+ }
7713
+ if (typeof schema.$ref === 'string') { const [o, tb] = child(refTargetFor(schema.$ref, ctx)); out.push(`if(${o})${m}|=${tb}`) }
7714
+ // What is left: every own key whose bit is not set.
7715
+ const kVar = `_uk${id}`
7716
+ const cases = [...names].filter(([k]) => k !== ALL_BIT_KEY && k[0] !== '/').map(([k, i]) => `case ${JSON.stringify(k)}:_ub${id}=${1 << i};break`).join(';')
7717
+ const allBit = names.has(ALL_BIT_KEY) ? `if((${m}&${1 << names.get(ALL_BIT_KEY)})!==0)continue;` : ''
7718
+ const patBits = [...names].filter(([k]) => k[0] === '/').map(([k, i]) => `if((${m}&${1 << i})!==0&&${patternKeyTest(ctx, k.slice(1), `_uk${id}`)})continue;`).join('')
7719
+ const up = schema.unevaluatedProperties
7720
+ let body
7721
+ if (up === false) {
7722
+ body = fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:${kVar}}`, "'must NOT have unevaluated properties'")
7723
+ } else if (typeof up === 'object' && up !== null) {
7724
+ const pe = emitPtrEsc(ctx)
7725
+ const p = pathExpr ? `${pathExpr}+'/'+${pe}(${kVar})` : `'/'+${pe}(${kVar})`
7726
+ const sub = []
7727
+ genCodeC(up, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/unevaluatedProperties')
7728
+ body = sub.join('\n ')
7729
+ } else {
7730
+ throw DECLINE
7731
+ }
7732
+ 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}}`)
7733
+ const text = out.join(';')
7734
+ ctx.plainReuse = prevReuse
7735
+ if (prevPk === undefined) ctx.plainOf.delete(v)
7736
+ if (text.length > ANNOT_INLINE_MAX || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(text)) { restoreCtx(ctx, saved); return null }
7737
+ return text
7738
+ } catch (e) {
7739
+ if (e !== DECLINE) throw e
7740
+ restoreCtx(ctx, saved)
7741
+ return null
7742
+ }
7743
+ }
7744
+
6884
7745
  function genUnevaluatedDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
6885
7746
  // additionalProperties evaluates every key the others do not, pass or fail.
6886
7747
  if (schema.additionalProperties !== undefined) return
@@ -6904,6 +7765,12 @@ function genUnevaluatedDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, f
6904
7765
  if (Array.isArray(schema.anyOf)) ctx.branchCounts.set(schema.anyOf, { of: counts.of, count: counts.anyOf, mask: null })
6905
7766
  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
7767
  }
7768
+ const inline = inlineUnevalBlock(schema, v, pathExpr, ctx, schemaPrefix, fail, counts)
7769
+ if (inline !== null) {
7770
+ const pre = share ? `${counts.of}=${v};${counts.oneOfMask}=0;` : ''
7771
+ lines.push(isObj ? `{${pre}${inline}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${pre}${inline}}`)
7772
+ return
7773
+ }
6907
7774
  addTopApplicatorAnnotations(schema, v, S, body, ctx, counts)
6908
7775
  const kVar = `_uk${ui}`
6909
7776
  const up = schema.unevaluatedProperties
@@ -6966,7 +7833,9 @@ function addTopApplicatorAnnotations(node, v, S, out, ctx, counts) {
6966
7833
  const t = counts ? counts.oneOf : `_oc${ctx.varCounter++}`
6967
7834
  // Which branches passed, as bits, for a collapse that has to name them.
6968
7835
  const mask = counts && node.oneOf.length <= 30 ? counts.oneOfMask : null
6969
- out.push(`{const ${t}m=${S}.length;${counts ? '' : 'let '}${t}=0;${node.oneOf.map((b, i) => `if(${emitAnnot(b, ctx)}(${v},${S})){${t}++;${mask ? `${mask}|=${1 << i};` : ''}}`).join('')}if(${t}!==1)_rb(${S},${t}m)}`)
7836
+ // In a verdict (counts.verdict), a second passing branch is the answer.
7837
+ const over = counts && counts.verdict ? `if(${t}>1)return false;` : ''
7838
+ 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
7839
  }
6971
7840
  if (node.if !== undefined) {
6972
7841
  const thenCall = node.then !== undefined ? `${emitAnnot(node.then, ctx)}(${v},${S})` : ''
@@ -7003,7 +7872,8 @@ function emitAnnot(sub, ctx) {
7003
7872
  if (sub === null || typeof sub !== 'object') throw DECLINE
7004
7873
  if (!ctx.annFns) {
7005
7874
  ctx.annFns = new Map()
7006
- ;(ctx.preamble || ctx.helperCode).push('function _anT(){return true}function _anF(){return false}function _rb(S,m){if(S.length!==m)S.length=m}')
7875
+ if (ctx.sharedRuntime) bindRt(ctx, ['_anT', '_anF', '_rb'])
7876
+ 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
7877
  }
7008
7878
  const hit = ctx.annFns.get(sub)
7009
7879
  if (hit !== undefined) {
@@ -7035,7 +7905,8 @@ function emitAnnot(sub, ctx) {
7035
7905
  const failOut = '{_rb(S,_m);return false}'
7036
7906
  for (const b of sub.allOf || []) body.push(`if(!${emitAnnot(b, ctx)}(d,S))${failOut}`)
7037
7907
  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
- if (Array.isArray(sub.oneOf)) body.push(`{let _c=0;${sub.oneOf.map((b) => `if(${emitAnnot(b, ctx)}(d,S))_c++;`).join('')}if(_c!==1)${failOut}}`)
7908
+ // A second passing branch settles the oneOf; the rest are not run.
7909
+ 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
7910
  if (sub.if !== undefined) {
7040
7911
  const thenCheck = sub.then !== undefined ? `if(!${emitAnnot(sub.then, ctx)}(d,S))${failOut}` : ''
7041
7912
  const elseCheck = sub.else !== undefined ? `if(!${emitAnnot(sub.else, ctx)}(d,S))${failOut}` : ''
@@ -7076,9 +7947,153 @@ function emitAnnot(sub, ctx) {
7076
7947
  // subschema marks them only when it passes (emitAnnotI); what is left must
7077
7948
  // pass unevaluatedItems. `false` reports once, at the first position left,
7078
7949
  // as the interpreter does.
7950
+ // Run-time unevaluatedItems written as straight-line code, the way
7951
+ // inlineUnevalBlock writes unevaluatedProperties. Without `contains` the
7952
+ // positions a node and its in-place applicators evaluate are a prefix: items
7953
+ // evaluates every position, prefixItems the first p, a nested
7954
+ // `unevaluatedItems: true` every position once its subschema holds; the
7955
+ // record is one integer, merged with max where a branch holds, and what is
7956
+ // left starts at that index. `contains` (positions that are not a prefix), a
7957
+ // tuple `items`, a nested unevaluatedItems that checks something, dynamic
7958
+ // references and reference cycles keep the annotation functions.
7959
+ const ANNOT_I_BLOCKERS = ['contains', 'additionalItems', '$dynamicRef', '$recursiveRef', 'dependencies']
7960
+ function annotItemsFit (node, ctx, seen, top) {
7961
+ if (node === true || node === false) return true
7962
+ if (node === null || typeof node !== 'object' || Array.isArray(node)) return false
7963
+ if (seen.has(node)) return false
7964
+ seen.add(node)
7965
+ try {
7966
+ for (const k of ANNOT_I_BLOCKERS) if (node[k] !== undefined) return false
7967
+ if (Array.isArray(node.items)) return false
7968
+ if (!top && node.unevaluatedItems !== undefined && node.unevaluatedItems !== true) return false
7969
+ if (node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== true) return false
7970
+ let subs
7971
+ try { subs = annotSubs(node, ctx) } catch { return false }
7972
+ for (const b of subs) if (!annotItemsFit(b, ctx, seen, false)) return false
7973
+ return true
7974
+ } finally { seen.delete(node) }
7975
+ }
7976
+ // The positions a node's own keywords evaluate, as an expression of the
7977
+ // array length `len`: null when none.
7978
+ function ownPrefix (node, len, top) {
7979
+ if (node.items !== undefined) return len
7980
+ if (!top && node.unevaluatedItems === true) return len
7981
+ if (Array.isArray(node.prefixItems) && node.prefixItems.length) return `(${len}<${node.prefixItems.length}?${len}:${node.prefixItems.length})`
7982
+ return null
7983
+ }
7984
+ // Lines that set `ok` for `sub` at `v`, an array of length `len`, and raise
7985
+ // `tn` to the positions it evaluated when it holds.
7986
+ function emitAnnotInlineI (sub, v, len, ctx, out, ok, tn) {
7987
+ if (sub === true) { out.push(`${ok}=1`); return }
7988
+ if (sub === false) { out.push(`${ok}=0`); return }
7989
+ const id = ctx.varCounter++
7990
+ const t = `_t${id}`
7991
+ const local = {}
7992
+ for (const k of Object.keys(sub)) if (!ANNOT_ITEM_APPLICATORS.has(k)) setOwn(local, k, sub[k])
7993
+ const vl = []
7994
+ ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
7995
+ try { genCode(local, v, vl, ctx, local.type === undefined || local.type === 'array' ? 'array' : undefined) } finally { ctx.nestedBoolean-- }
7996
+ out.push(`let ${t}=0;${ok}=1`)
7997
+ if (vl.length) out.push(`a${id}:{${replaceTopLevel(vl.join(';'), `break a${id}`, `{${ok}=0;break a${id}}`)}}`)
7998
+ if (sub.not !== undefined) {
7999
+ const nl = []
8000
+ nestedGenCode(sub.not, v, nl, ctx)
8001
+ 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}`)
8002
+ else out.push(`${ok}=0`)
8003
+ }
8004
+ const own = ownPrefix(sub, len, false)
8005
+ if (own !== null) out.push(`if(${ok})${t}=${own}`)
8006
+ let bi = 0
8007
+ 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] }
8008
+ const raise = (tb) => `if(${tb}>${t})${t}=${tb}`
8009
+ for (const b of sub.allOf || []) { const [o, tb] = child(b); out.push(`if(!${o})${ok}=0;else ${raise(tb)}`) }
8010
+ if (Array.isArray(sub.anyOf)) {
8011
+ const na = `_na${id}`
8012
+ const fc = factoryCount(ctx, sub.anyOf, false)
8013
+ out.push(`let ${na}=0${fc ? `;${fc.of}=${v};${fc.count}=0` : ''}`)
8014
+ for (const b of sub.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${raise(tb)}${fc ? `;${fc.count}++` : ''}}`) }
8015
+ out.push(`if(${na}===0)${ok}=0`)
8016
+ }
8017
+ if (Array.isArray(sub.oneOf)) {
8018
+ const nc = `_nc${id}`, tc = `_tc${id}`
8019
+ const fc = factoryCount(ctx, sub.oneOf, sub.oneOf.length <= 30)
8020
+ out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
8021
+ 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}` : ''}` : ''}}`) })
8022
+ out.push(`if(${nc}===1){${raise(tc)}}else ${ok}=0`)
8023
+ }
8024
+ if (sub.if !== undefined) {
8025
+ const [oi, ti] = child(sub.if)
8026
+ const thenPart = []
8027
+ 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)}`) }
8028
+ const elsePart = []
8029
+ 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)}`) }
8030
+ out.push(`if(${oi}){${raise(ti)};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
8031
+ }
8032
+ if (typeof sub.$ref === 'string') { const [o, tb] = child(refTargetFor(sub.$ref, ctx)); out.push(`if(!${o})${ok}=0;else ${raise(tb)}`) }
8033
+ out.push(`if(${ok}&&${t}>${tn})${tn}=${t}`)
8034
+ }
8035
+ function inlineUnevalItemsBlock (schema, v, pathExpr, ctx, schemaPrefix, fail) {
8036
+ if (!ctx.inCombined || !annotItemsFit(schema, ctx, new Set(), true)) return null
8037
+ const saved = snapshotCtx(ctx)
8038
+ try {
8039
+ const id = ctx.varCounter++
8040
+ const n = `_un${id}`, len = `_ul${id}`
8041
+ const out = [`const ${len}=${v}.length;let ${n}=0`]
8042
+ const own = ownPrefix(schema, len, true)
8043
+ if (own !== null) out.push(`${n}=${own}`)
8044
+ let bi = 0
8045
+ 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] }
8046
+ const raise = (tb) => `if(${tb}>${n})${n}=${tb}`
8047
+ for (const b of schema.allOf || []) { const [, tb] = child(b); out.push(raise(tb)) }
8048
+ if (Array.isArray(schema.anyOf)) {
8049
+ const fc = factoryCount(ctx, schema.anyOf, false)
8050
+ if (fc) out.push(`${fc.of}=${v};${fc.count}=0`)
8051
+ for (const b of schema.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${raise(tb)}${fc ? `;${fc.count}++` : ''}}`) }
8052
+ }
8053
+ if (Array.isArray(schema.oneOf)) {
8054
+ const fc = factoryCount(ctx, schema.oneOf, schema.oneOf.length <= 30)
8055
+ const nc = `_nc${id}`, tc = `_tc${id}`
8056
+ out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
8057
+ 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}` : ''}` : ''}}`) })
8058
+ out.push(`if(${nc}===1)${raise(tc)}`)
8059
+ }
8060
+ if (schema.if !== undefined) {
8061
+ const [oi, ti] = child(schema.if)
8062
+ const thenPart = []
8063
+ 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)}`) }
8064
+ const elsePart = []
8065
+ 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)}`) }
8066
+ out.push(`if(${oi}){${raise(ti)};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
8067
+ }
8068
+ if (typeof schema.$ref === 'string') { const [o, tb] = child(refTargetFor(schema.$ref, ctx)); out.push(`if(${o})${raise(tb)}`) }
8069
+ const iv = `_ix${id}`
8070
+ const ui2 = schema.unevaluatedItems
8071
+ if (ui2 === false) {
8072
+ // Reported once, at the first position left, as the interpreter does.
8073
+ out.push(`if(${n}<${len}){const ${iv}=${n};${fail('unevaluatedItems', 'unevaluatedItems', `{limit:${iv}}`, `'must NOT have more than '+${iv}+' items'`)}}`)
8074
+ } else if (typeof ui2 === 'object' && ui2 !== null) {
8075
+ const pe = pathExpr ? `${pathExpr}+'/'+${iv}` : `'/'+${iv}`
8076
+ const sub = []
8077
+ genCodeC(ui2, `${v}[${iv}]`, pe, sub, ctx, schemaPrefix + '/unevaluatedItems')
8078
+ if (sub.length) out.push(`for(let ${iv}=${n};${iv}<${len};${iv}++){${sub.join('\n ')}\n }`)
8079
+ } else {
8080
+ throw DECLINE
8081
+ }
8082
+ const text = out.join(';')
8083
+ if (text.length > ANNOT_INLINE_MAX || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(text)) { restoreCtx(ctx, saved); return null }
8084
+ return text
8085
+ } catch (e) {
8086
+ if (e !== DECLINE) throw e
8087
+ restoreCtx(ctx, saved)
8088
+ return null
8089
+ }
8090
+ }
8091
+
7079
8092
  function genUnevaluatedItemsDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
7080
8093
  if (schema.$dynamicRef !== undefined || schema.$recursiveRef !== undefined) throw DECLINE
7081
8094
  if (Array.isArray(schema.items) || schema.additionalItems !== undefined) throw DECLINE
8095
+ const inline = inlineUnevalItemsBlock(schema, v, pathExpr, ctx, schemaPrefix, fail)
8096
+ if (inline !== null) { lines.push(`if(Array.isArray(${v})){${inline}}`); return }
7082
8097
  annItemHelpers(ctx)
7083
8098
  const ui = ctx.varCounter++
7084
8099
  const S = `_es${ui}`
@@ -7185,7 +8200,7 @@ function emitAnnotI(sub, ctx) {
7185
8200
  if (locals.length) body.push(`if(Array.isArray(d)){${locals.join(';')}}`)
7186
8201
  for (const b of sub.allOf || []) body.push(`if(!${emitAnnotI(b, ctx)}(d,${R}))${failOut}`)
7187
8202
  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}))_c++;`).join('')}if(_c!==1)${failOut}}`)
8203
+ 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
8204
  if (sub.if !== undefined) {
7190
8205
  const thenCheck = sub.then !== undefined ? `if(!${emitAnnotI(sub.then, ctx)}(d,${R}))${failOut}` : ''
7191
8206
  const elseCheck = sub.else !== undefined ? `if(!${emitAnnotI(sub.else, ctx)}(d,${R}))${failOut}` : ''
@@ -7227,8 +8242,9 @@ function genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fai
7227
8242
  const checksU = propKeysU.map(k => `_k${ui}[_i]!==${JSON.stringify(k)}`).join('&&')
7228
8243
  innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++)if(${checksU}){${failU}}`
7229
8244
  } else {
7230
- const allowedU = propKeysU.map(k => JSON.stringify(k)).join(',')
7231
- innerU = `const _k${ui}=Object.keys(${v});const _a${ui}=new Set([${allowedU}]);for(let _i=0;_i<_k${ui}.length;_i++)if(!_a${ui}.has(_k${ui}[_i])){${failU}}`
8245
+ ctx.closureVars.push(`_a${ui}`)
8246
+ ctx.closureVals.push(new Set(propKeysU))
8247
+ 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
8248
  }
7233
8249
  lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
7234
8250
  }
@@ -7630,5 +8646,7 @@ function _collectEval(schema, result, defs, schemaMap, refStack, isRoot) {
7630
8646
  }
7631
8647
 
7632
8648
  module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, expandedChars, AJV_MESSAGES, _setCodeBudget }
8649
+ // For tests: the dynamic-scope unrolling, to hold its output to the interpreter.
8650
+ Object.defineProperty(module.exports, '_expandDynamicScopes', { value: expandDynamicScopes, enumerable: false })
7633
8651
  // For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
7634
8652
  Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })