ata-validator 1.46.1 → 1.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3483 @@
1
+ 'use strict'
2
+ const { env } = require('./env')
3
+ const { compileFunction } = require('./compile-fn')
4
+
5
+ // The error-collecting generator (genCodeE) and the one-pass generator
6
+ // (genCodeC, compileToJSCombined) of lib/js-compiler.js, in a module of their
7
+ // own so that a process which only ever answers verdicts never parses them:
8
+ // they are 45 percent of the generator's source, and the module is read on
9
+ // the first call that compiles errors. Everything here was written as part
10
+ // of js-compiler.js and reads its helpers from that module's _internal.
11
+ const core = require('./js-compiler')
12
+ const { hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun } = core._internal
13
+
14
+
15
+ function emitPtrEsc(ctx) {
16
+ hoistOnce(ctx, '_peHoisted', PTR_ESC_HELPER)
17
+ return '_pe'
18
+ }
19
+
20
+ // The error and combined generators need the duplicate pair, not only the
21
+ // verdict. `_uqp` returns the index of the duplicate (-1 when every item is
22
+ // distinct) and leaves the item it repeats in `_uqpI`: the first item that has
23
+ // a later equal, paired with its first later equal. That is the pair the
24
+ // interpreted engine reports, and the generators used to report a different
25
+ // one (the earliest repeat, paired with what it repeats), so the same document
26
+ // named a different pair depending on the engine that answered. See
27
+ // lib/unique-items.js for how it finds the pair.
28
+ function emitUqp(ctx) {
29
+ emitUq(ctx)
30
+ hoistOnce(ctx, '_uqpHoisted', uqSources().uqp)
31
+ return '_uqp'
32
+ }
33
+
34
+ // An object or array a const error reports as `allowedValue`, parsed once per
35
+ // compiled function rather than on every error: parsing it per error made
36
+ // reading a const error two to five times what the check costs. A copy of its
37
+ // own, not the compared constant, so an error a caller edits cannot change
38
+ // what the validator compares against. Declared in the function's helper
39
+ // code, so standalone output carries it the same way.
40
+ function emitParamConstant(ctx, value) {
41
+ const name = `_pc${ctx.varCounter++}`
42
+ const decl = `const ${name}=JSON.parse(${JSON.stringify(JSON.stringify(value))})`
43
+ if (ctx.preamble) ctx.preamble.push(decl)
44
+ else ctx.helperCode.push(decl)
45
+ return name
46
+ }
47
+
48
+
49
+ const { codeFor } = require('./error-codes')
50
+ const { ordinalFor, rankFor } = require('./schema-order')
51
+
52
+ // Compile a JSON Schema into a pure JS validator function.
53
+ // Closure-based validator — no new Function() or eval().
54
+ // Returns null if the schema is too complex for JS compilation.
55
+
56
+ const DOC_BASE = 'https://ata-validator.com/e/'
57
+
58
+ // Build the literal fragments injected into an emitted _e.push({...}) call.
59
+ // Returns { codeStr, docUrl, frame } where `frame` is a JS source fragment
60
+ // like ",schemaSource:Object.freeze({...})" or "" if no source map hit.
61
+ // The fragment always starts with a leading comma when non-empty so callers
62
+ // can splice it inside an existing object literal.
63
+ // The error's position in schema declaration order, as a literal field the
64
+ // reader sorts by without deriving it from the path. Empty when the path is
65
+ // not into the root document.
66
+ function ordinalField (ctx, schemaPath) {
67
+ if (ctx && ctx.noOrdinal) return ''
68
+ const o = ctx && ctx.rootSchema ? ordinalFor(ctx.rootSchema, unescapeSp(schemaPath)) : null
69
+ return o === null ? '' : `,_o:${o}`
70
+ }
71
+
72
+ // A schema path as the generators hold it is text for a single-quoted source
73
+ // literal: ptrSeg put a backslash before every backslash and quote in a key.
74
+ // Anything that uses the path as a value, the ordinal lookup and the frozen
75
+ // error objects below, has to take that backslash off again, or a key such as
76
+ // `it's` reads as `it\'s` and the error names a path that does not exist.
77
+ // The value of a string literal the generators built for source. One that does
78
+ // not parse on its own (a key with a raw line break) would not parse inside the
79
+ // generated function either, so the generator declines, as it would have when
80
+ // that function failed to build.
81
+ function literalValue (lit) {
82
+ try {
83
+ return Function('return ' + lit)()
84
+ } catch {
85
+ throw DECLINE
86
+ }
87
+ }
88
+
89
+ function unescapeSp (sp) {
90
+ return sp.indexOf('\\') === -1 ? sp : sp.replace(/\\u([0-9a-fA-F]{4})|\\(.)/g, (m, h, c) => (h !== undefined ? String.fromCharCode(parseInt(h, 16)) : c))
91
+ }
92
+
93
+ const FRAME_INLINE_MAX = 120
94
+
95
+ function buildErrorLiteral (opts) {
96
+ const { keyword, format, schemaPath, sourceMap } = opts
97
+ let code = (keyword === 'format' && format) ? codeFor('format', format) : codeFor(keyword)
98
+ if (!code) code = 'ATA9001'
99
+ const docUrl = DOC_BASE + code
100
+ let frame = ''
101
+ if (sourceMap && sourceMap.file && sourceMap.map) {
102
+ const ptr = schemaPath && schemaPath.charAt(0) === '#' ? schemaPath.slice(1) : (schemaPath || '')
103
+ let hit = sourceMap.map[ptr + '#key']
104
+ if (!hit) hit = sourceMap.map[ptr]
105
+ if (hit) {
106
+ // Support both {line,col,text} (raw output of buildPositionMap) and
107
+ // [line,col,text] (compact form passed by callers that pre-flattened).
108
+ const line = Array.isArray(hit) ? hit[0] : hit.line
109
+ const col = Array.isArray(hit) ? hit[1] : hit.col
110
+ const text = Array.isArray(hit) ? hit[2] : hit.text
111
+ if (sourceMap.frames && text.length > FRAME_INLINE_MAX) {
112
+ // A long source line is written once at module scope (see
113
+ // sourceFrameDecls in aot-impl.js) and the literal names it. Inlined,
114
+ // every error site carried the whole line, and a schema kept on one
115
+ // line is one line of all of it: 181 KB minified for a 2 KB schema.
116
+ // Short lines stay inline: gzip folds the repeats, and measured on
117
+ // the 50-field fixture the table cost 1206 gzipped bytes where the
118
+ // inline frames cost 696.
119
+ const key = line + ':' + col
120
+ let f = sourceMap.frames.get(key)
121
+ if (!f) { f = { line, col, text, index: sourceMap.frames.size }; sourceMap.frames.set(key, f) }
122
+ frame = ',schemaSource:__ataSS[' + f.index + ']'
123
+ } else {
124
+ frame = ',schemaSource:Object.freeze({file:' + JSON.stringify(sourceMap.file) +
125
+ ',line:' + line +
126
+ ',col:' + col +
127
+ ',text:' + JSON.stringify(text) + '})'
128
+ }
129
+ }
130
+ }
131
+ return { codeStr: code, docUrl, frame }
132
+ }
133
+
134
+ // Stands for the schema path of a call to a recursive definition's error
135
+ // helper while its body is generated; see genCodeENode. Not a pointer, so no
136
+ // ordinal or source frame is looked up for it at compile time.
137
+ const SP_PLACEHOLDER = '@@ata-sp@@'
138
+
139
+ // Safe key escaping: use JSON.stringify to handle all special chars (newlines, null bytes, etc.)
140
+ // A schema string as the body of a JavaScript string literal, single- or
141
+ // double-quoted. JSON.stringify escapes the double quote and the backslash;
142
+ // the single quote needs its own escape, because the error emitters put these
143
+ // in '...' literals. Without it a property name holding a quote closed the
144
+ // literal, and the rest of the name ran as code in the generated validator.
145
+ function esc(s) { return JSON.stringify(s).slice(1, -1).replace(/'/g, "\\'") }
146
+ // A schema pointer segment for a pattern: JSON Pointer escaping, then made
147
+ // safe for the single-quoted literal the error emitters use.
148
+ // A pointer segment as text for a single-quoted literal. Control characters
149
+ // and the two line separators go in as \u escapes: written raw, a property
150
+ // named "foo\nbar" broke the literal, the combined function failed to compile
151
+ // and every schema with such a name lost it.
152
+ function ptrSeg(s) { return s.replace(/~/g, '~0').replace(/\//g, '~1').replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/[\u0000-\u001f\u2028\u2029]/g, (c) => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0')) }
153
+
154
+ // Resolve child path at codegen time when parent is a static string literal.
155
+ // This enables frozen pre-allocation for ALL nested error objects.
156
+ function childPathExpr(parentExpr, suffix) {
157
+ if (!parentExpr) return `'/${suffix}'`
158
+ if (parentExpr.startsWith("'") && !parentExpr.includes('+')) {
159
+ // Static parent: resolve at codegen time → '/parent/child' (single literal)
160
+ return `'${parentExpr.slice(1, -1)}/${suffix}'`
161
+ }
162
+ // Dynamic parent: keep as concat expression
163
+ return `${parentExpr}+'/${suffix}'`
164
+ }
165
+
166
+ // Same but for dynamic segments (array indices)
167
+ function childPathDynExpr(parentExpr, indexExpr) {
168
+ if (!parentExpr) return `'/'+${indexExpr}`
169
+ return `${parentExpr}+'/'+${indexExpr}`
170
+ }
171
+
172
+ // A contribution that names only what the node's own adjacent keywords
173
+ // already evaluate unconditionally changes nothing: a key in the node's own
174
+ // `properties` is evaluated whether or not the branch that also names it
175
+ // passes, and a branch prefix no longer than the node's own is covered
176
+ // position for position. Anything beyond those bounds is conditional
177
+ // evaluation the plain counterpart cannot express, and the node stays with
178
+ // the interpreted engine.
179
+ function unevalLocalOk (node, key) {
180
+ if (node[key] !== false) return false
181
+ if (node.$ref !== undefined || node.$dynamicRef !== undefined || node.$recursiveRef !== undefined) return false
182
+ if (node.dependentSchemas !== undefined || node.dependencies !== undefined) return false
183
+ const kind = key === 'unevaluatedProperties' ? 'props' : 'items'
184
+ if (kind === 'props' && node.patternProperties !== undefined) return false
185
+ if (kind === 'items' && node.contains !== undefined) return false
186
+ const names = new Set()
187
+ const state = { prefix: 0 }
188
+ for (const k of ['allOf', 'anyOf', 'oneOf']) {
189
+ if (node[k] !== undefined) {
190
+ if (!Array.isArray(node[k])) return false
191
+ for (const b of node[k]) if (!unevalContributions(b, kind, names, state, 0)) return false
192
+ }
193
+ }
194
+ for (const k of ['if', 'then', 'else']) {
195
+ if (node[k] !== undefined && !unevalContributions(node[k], kind, names, state, 0)) return false
196
+ }
197
+ if (kind === 'props') {
198
+ const own = node.properties && typeof node.properties === 'object' ? node.properties : {}
199
+ for (const n of names) if (!Object.prototype.hasOwnProperty.call(own, n)) return false
200
+ } else {
201
+ const ownPrefix = Array.isArray(node.prefixItems) ? node.prefixItems.length
202
+ : (Array.isArray(node.items) ? node.items.length : 0)
203
+ if (node.items !== undefined && !Array.isArray(node.items)) { /* all evaluated; any prefix is covered */ }
204
+ else if (state.prefix > ownPrefix) return false
205
+ }
206
+ return true
207
+ }
208
+
209
+ // Every occurrence in the document must qualify or the generator declines the
210
+ // whole schema, exactly as the string scan it replaces did. The walk is
211
+ // generic and may visit data positions (an enum value, say); judging those is
212
+ // harmless, since a data node that fails the test only makes the answer more
213
+ // conservative and the generators never read data positions as schemas.
214
+ function unevalAllProvablyLocal (root, only) {
215
+ const seen = new Set()
216
+ const stack = [root]
217
+ while (stack.length) {
218
+ const n = stack.pop()
219
+ if (n === null || typeof n !== 'object' || seen.has(n)) continue
220
+ seen.add(n)
221
+ if (!Array.isArray(n)) {
222
+ if (only !== 'items' && n.unevaluatedProperties !== undefined && !unevalLocalOk(n, 'unevaluatedProperties')) return false
223
+ if (n.unevaluatedItems !== undefined && !unevalLocalOk(n, 'unevaluatedItems')) return false
224
+ }
225
+ for (const k of Array.isArray(n) ? n : Object.values(n)) stack.push(k)
226
+ }
227
+ return true
228
+ }
229
+
230
+ // --- Error-collecting codegen: same checks, but pushes errors instead of returning false ---
231
+ // Returns a function: (data, allErrors) => { valid, errors }
232
+ // Valid path is still fast — only error path does extra work.
233
+ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts) {
234
+ const inputSchema = schema
235
+ schema = prepareForCodegen(schema, schemaMap)
236
+ // unevaluated* is generated only where it is provably its plain counterpart
237
+ // (see unevalLocalOk); any occurrence that is not keeps the whole schema on
238
+ // the interpreted engine's error path, exactly as before.
239
+ if (typeof schema === 'object' && schema !== null) {
240
+ const s = JSON.stringify(schema)
241
+ if ((s.includes('unevaluatedProperties') || s.includes('unevaluatedItems')) && !unevalAllProvablyLocal(schema)) return null
242
+ }
243
+ if (typeof schema === 'boolean') {
244
+ return schema
245
+ ? () => ({ valid: true, errors: [] })
246
+ : () => ({ valid: false, errors: [{ keyword: 'not', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
247
+ }
248
+ if (typeof schema !== 'object' || schema === null) return null
249
+ if (!sharedCodegenGate(schema, schemaMap)) return null
250
+ if (schema.patternProperties) {
251
+ for (const [pat, sub] of Object.entries(schema.patternProperties)) {
252
+ if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
253
+ }
254
+ }
255
+ if (schema.dependentSchemas) {
256
+ for (const sub of Object.values(schema.dependentSchemas)) {
257
+ if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
258
+ }
259
+ }
260
+ if (schema.propertyNames && typeof schema.propertyNames === 'object' && !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return null
261
+
262
+ // Build anchors map for $ref/#anchor and $dynamicRef resolution
263
+ const eRootDefs = schema.$defs || schema.definitions || null
264
+ const eAnchors = {}
265
+ if (schema.$dynamicAnchor) eAnchors['#' + schema.$dynamicAnchor] = schema
266
+ if (schema.$anchor) eAnchors['#' + schema.$anchor] = schema
267
+ if (typeof schema.$id === 'string' && schema.$id.startsWith('#')) eAnchors[schema.$id] = schema
268
+ if (eRootDefs) {
269
+ for (const def of Object.values(eRootDefs)) {
270
+ if (def && typeof def === 'object') {
271
+ if (def.$dynamicAnchor) eAnchors['#' + def.$dynamicAnchor] = def
272
+ if (def.$anchor) eAnchors['#' + def.$anchor] = def
273
+ if (typeof def.$id === 'string' && def.$id.startsWith('#')) eAnchors[def.$id] = def
274
+ }
275
+ }
276
+ }
277
+ if (schemaMap) {
278
+ for (const ext of schemaMap.values()) {
279
+ if (ext && typeof ext === 'object') {
280
+ if (ext.$dynamicAnchor && !eAnchors['#' + ext.$dynamicAnchor]) eAnchors['#' + ext.$dynamicAnchor] = ext
281
+ if (ext.$anchor && !eAnchors['#' + ext.$anchor]) eAnchors['#' + ext.$anchor] = ext
282
+ if (typeof ext.$id === 'string' && ext.$id.startsWith('#') && !eAnchors[ext.$id]) eAnchors[ext.$id] = ext
283
+ }
284
+ }
285
+ }
286
+
287
+ // As in the boolean generator: a reference this path cannot resolve or follow
288
+ // emits no lines, and an empty program is returned below as always-valid.
289
+ if (hasUnresolvableRef(schema, eRootDefs, eAnchors, schemaMap, new Set())) return null
290
+ if (needsBaseTracking(schema, schemaMap, new Set())) return null
291
+
292
+ const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, shared: [], refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootE: schema, rootSchema: inputSchema, userFormats: userFormats || null,
293
+ // Custom format checkers referenced by the body, bound as
294
+ // closure parameters below the way the other entry points do.
295
+ closureVars: [], closureVals: [],
296
+ sourceMap: (sourceOpts && sourceOpts.sourceMap && sourceOpts.schemaFile)
297
+ ? { file: sourceOpts.schemaFile, map: sourceOpts.sourceMap, frames: sourceOpts.frames || null }
298
+ : null }
299
+ ctx.helperCode.push('const _cpLen=s=>{let n=0;for(const _ of s)n++;return n}')
300
+ const lines = []
301
+ try {
302
+ genCodeE(schema, 'd', '', lines, ctx, '#')
303
+ } catch (e) {
304
+ if (e === DECLINE) return null
305
+ throw e
306
+ }
307
+ // Inline branch-collapse helper only when the schema actually used a
308
+ // oneOf/anyOf branch path (tracked via ctx.usesBranchCollapse). Keeps the
309
+ // AOT output small for schemas that don't compose. Standalone modules can't
310
+ // require('./branch-collapse'), so the helper body is embedded verbatim.
311
+ if (ctx.usesBranchCollapse) {
312
+ ctx.helperCode.push(require('./branch-collapse').embedSource())
313
+ }
314
+ if (lines.length === 0) { if (hasConstraints(schema, false)) return null; return (d) => ({ valid: true, errors: [] }) }
315
+
316
+ const checkStr = lines.join('\n ')
317
+ const defSetsE = ctx.defFns ? Array.from(ctx.defFns.values()) : []
318
+ const needsGuardE = ctx.usesRecursion || defSetsE.length > 0
319
+ const guardStrE = needsGuardE ? emitGuardState() : ''
320
+ const helpersE = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
321
+
322
+ let body
323
+ if (ctx.usesRecursion) {
324
+ // The abandoned first pass may have pushed errors, so the retry starts
325
+ // from an empty list and the reported errors come from the guarded run
326
+ // alone.
327
+ body = `const _e=[];\n ` + guardStrE + helpersE +
328
+ emitRootGuard('_validateE', 'd,_all,_e', '') +
329
+ `function _validateE_b(d,_all,_e){\n ${checkStr}\n }\n ` +
330
+ emitGuardedRun('_validateE(d,_all,_e)', '_e.length=0;', defSetsE) +
331
+ `return{valid:_e.length===0,errors:_e}`
332
+ } else if (defSetsE.length > 0) {
333
+ body = `const _e=[];\n ` + guardStrE + helpersE +
334
+ `function _bodyE(d,_all,_e){\n ${checkStr}\n }\n ` +
335
+ emitGuardedRun('_bodyE(d,_all,_e)', '_e.length=0;', defSetsE) +
336
+ `return{valid:_e.length===0,errors:_e}`
337
+ } else {
338
+ body = `const _e=[];\n ` + helpersE + checkStr +
339
+ `\n return{valid:_e.length===0,errors:_e}`
340
+ }
341
+ try {
342
+ let fn
343
+ // Custom format checkers come in as leading parameters, bound once here,
344
+ // so the body's `_uf_<name>(...)` calls resolve. Without a binding the
345
+ // first invalid document threw a ReferenceError instead of reporting.
346
+ const cvars = ctx.closureVars
347
+ const cvals = ctx.closureVals
348
+ // The helpers are constants: the uri tables, the compiled patterns, the
349
+ // branch-collapse functions, the name sets. In `body` they sit inside the
350
+ // function and were rebuilt on every call, which on a product schema with
351
+ // six URLs and one anyOf cost about 450 ns before a single check ran, more
352
+ // than every check and every error together. Here they run once, in a
353
+ // factory, as the verdict function's preamble does; with $defs or
354
+ // recursion too, below. `_errSource` stays `body` either way, for the
355
+ // emitters.
356
+ // The factory builds the helpers once and returns the error function.
357
+ // With $defs or recursion the helpers are the definitions' functions and
358
+ // the cycle guard's state lives beside them; the guarded run resets that
359
+ // state at its start, and a call made while one is running (a custom
360
+ // keyword validating with the same validator) gets a function from a fresh
361
+ // factory call instead of sharing it. `_errFactory` carries the same text
362
+ // to the standalone emitter.
363
+ let factoryBody
364
+ if (needsGuardE) {
365
+ const attempt = ctx.usesRecursion ? '_validateE(d,_all,_e)' : '_bodyE(d,_all,_e)'
366
+ const runner = ctx.usesRecursion
367
+ ? emitRootGuard('_validateE', 'd,_all,_e', '') + `function _validateE_b(d,_all,_e){\n ${checkStr}\n }\n `
368
+ : `function _bodyE(d,_all,_e){\n ${checkStr}\n }\n `
369
+ factoryBody = guardStrE + helpersE + runner + `return function(d,_all){const _e=[];\n ` +
370
+ emitGuardedRun(attempt, '_e.length=0;', defSetsE) + `return{valid:_e.length===0,errors:_e}}`
371
+ } else {
372
+ factoryBody = helpersE + 'return function(d,_all){const _e=[];\n ' + checkStr +
373
+ '\n return{valid:_e.length===0,errors:_e}}'
374
+ }
375
+ // For the runtime, the literals take the legacy shape directly. `code` and
376
+ // `docUrl` are there for standalone modules, which return errors as they
377
+ // are; the runtime derives both from the keyword when it enriches, and
378
+ // orders errors by schemaPath, so the literals carried three fields that
379
+ // every read then copied the error to remove. The patterns are this
380
+ // generator's own spelling; the collapse helper writes its fields with a
381
+ // space and keeps them.
382
+ if (sourceOpts && sourceOpts.runtimeShape) {
383
+ const shape = (src) => src.replace(/code:'ATA\d{4}',/g, '').replace(/,docUrl:'https:\/\/ata-validator\.com\/e\/ATA\d{4}'/g, '').replace(/,_o:\d+/g, '')
384
+ body = shape(body)
385
+ factoryBody = shape(factoryBody)
386
+ }
387
+ const hoistable = !ctx.usesRecursion && defSetsE.length === 0 && helpersE !== ''
388
+ if (hoistable || needsGuardE) {
389
+ const params = [...cvars, '__ataSafeRe']
390
+ const args = [...cvals, compileSafe]
391
+ const make = compileFunction(...params, factoryBody)
392
+ const main = make(...args)
393
+ if (needsGuardE) {
394
+ let busy = false
395
+ fn = (d, _all) => {
396
+ if (busy) return make(...args)(d, _all)
397
+ busy = true
398
+ try { return main(d, _all) } finally { busy = false }
399
+ }
400
+ } else {
401
+ fn = main
402
+ }
403
+ } else if (ctx.usesSafeRe) {
404
+ // Inlined helperCode references __ataSafeRe; bind the safe-regex factory.
405
+ // Standalone keeps the inline _errSource — the embed defines __ataSafeRe.
406
+ const built = compileFunction(...cvars, '__ataSafeRe', 'd', '_all', body)
407
+ fn = cvars.length
408
+ ? (d, _all) => built(...cvals, compileSafe, d, _all)
409
+ : (d, _all) => built(compileSafe, d, _all)
410
+ } else if (cvars.length) {
411
+ const built = compileFunction(...cvars, 'd', '_all', body)
412
+ fn = (d, _all) => built(...cvals, d, _all)
413
+ } else {
414
+ fn = compileFunction('d', '_all', body)
415
+ }
416
+ fn._errSource = body
417
+ fn._errFactory = factoryBody
418
+ fn._errGuarded = needsGuardE
419
+ fn._sharedHelpers = ctx.shared ? ctx.shared.slice() : []
420
+ fn._usesSafeRe = !!ctx.usesSafeRe
421
+ if (cvars.length) {
422
+ fn._formatClosures = cvars.map((name, i) => {
423
+ let format = null
424
+ for (const key of Object.keys(ctx.userFormats)) {
425
+ if (ctx.userFormats[key] === cvals[i]) { format = key; break }
426
+ }
427
+ return { name, fn: cvals[i], format }
428
+ })
429
+ }
430
+ return fn
431
+ } catch (e) {
432
+ // Declining here is safe (the interpreter takes over) but silent, which
433
+ // is how every past regression in this file hid. The env hook makes the
434
+ // decline visible without changing behavior.
435
+ if (env('ATA_DEBUG_ERRGEN')) console.error('error codegen declined:', e.message)
436
+ return null
437
+ }
438
+ }
439
+
440
+ // Emit codegen for oneOf/anyOf that runs each branch with error collection,
441
+ // aggregates results, and uses __ataCollapse to surface a single best-branch
442
+ // error. Wraps the existing per-branch evaluation; does not replace it.
443
+ const BRANCH_VERDICT_MAX = 4096
444
+
445
+ // What undoing a trial emission needs: during one, the context's arrays only
446
+ // grow and its maps and sets only gain keys (a reference pushed onto refStack
447
+ // by a declined branch is such a key), so their lengths and sizes are enough,
448
+ // and what was added is cut off the end, in insertion order. Copying them
449
+ // instead made the trial quadratic: 297 oneOf and anyOf on apollo-router,
450
+ // each copying a helper list thousands long.
451
+ function snapshotCtx (ctx) {
452
+ const out = new Map()
453
+ for (const k of Object.keys(ctx)) {
454
+ const x = ctx[k]
455
+ if (Array.isArray(x)) out.set(k, { kind: 'a', ref: x, n: x.length })
456
+ else if (x instanceof Map || x instanceof Set) out.set(k, { kind: 'c', ref: x, n: x.size })
457
+ else out.set(k, { kind: 'v', ref: x })
458
+ }
459
+ return out
460
+ }
461
+ function restoreCtx (ctx, saved) {
462
+ for (const k of Object.keys(ctx)) if (!saved.has(k)) delete ctx[k]
463
+ for (const [k, e] of saved) {
464
+ if (e.kind === 'a') { e.ref.length = Math.min(e.ref.length, e.n); ctx[k] = e.ref }
465
+ else if (e.kind === 'c') {
466
+ const extra = e.ref.size - e.n
467
+ if (extra > 0) {
468
+ const added = []
469
+ let i = 0
470
+ for (const key of e.ref.keys()) if (i++ >= e.n) added.push(key)
471
+ for (const key of added) e.ref.delete(key)
472
+ }
473
+ ctx[k] = e.ref
474
+ } else ctx[k] = e.ref
475
+ }
476
+ }
477
+
478
+ // Whether a node holds a reference anywhere below it.
479
+ function holdsRef (node, seen = new Set()) {
480
+ if (node === null || typeof node !== 'object' || seen.has(node)) return false
481
+ seen.add(node)
482
+ if (typeof node.$ref === 'string' || typeof node.$dynamicRef === 'string') return true
483
+ for (const k of Object.keys(node)) if (holdsRef(node[k], seen)) return true
484
+ return false
485
+ }
486
+
487
+ // Every `$ref` under `node`, outside the values of enum, const, default and
488
+ // examples. Null when one is not a plain pointer into the root's definitions
489
+ // (after prepareForCodegen those are what remain, besides `#` itself) or
490
+ // when the node carries a scope keyword of its own, which would not mean the
491
+ // same thing under a wrapper.
492
+ const BRANCH_SCOPE_KEYS = new Set(['$id', '$anchor', '$dynamicAnchor', '$dynamicRef', '$defs', 'definitions', '$schema'])
493
+ function branchRefsLocal (node, defsKey, seen) {
494
+ if (node === null || typeof node !== 'object') return true
495
+ if (seen.has(node)) return true
496
+ seen.add(node)
497
+ if (Array.isArray(node)) { for (const x of node) if (!branchRefsLocal(x, defsKey, seen)) return false; return true }
498
+ for (const k of Object.keys(node)) {
499
+ if (SKIP_VALUE_KEYS.has(k)) continue
500
+ if (BRANCH_SCOPE_KEYS.has(k)) return false
501
+ const v = node[k]
502
+ if (k === '$ref') { if (typeof v !== 'string' || !v.startsWith('#/' + defsKey + '/')) return false; continue }
503
+ if (!branchRefsLocal(v, defsKey, seen)) return false
504
+ }
505
+ return true
506
+ }
507
+ // One verdict function per branch, compiled on its own with the root's
508
+ // definitions under it, or null when a branch cannot be wrapped that way
509
+ // (then the site keeps its error functions for the decision). The
510
+ // definitions themselves are checked once per root.
511
+ const _defsLocal = new WeakMap()
512
+ function compileBranchVerdicts (branches, ctx) {
513
+ const root = ctx.rootC
514
+ if (root === null || typeof root !== 'object') return null
515
+ const defsKey = root.$defs && typeof root.$defs === 'object' ? '$defs' : (root.definitions && typeof root.definitions === 'object' ? 'definitions' : null)
516
+ if (defsKey !== null) {
517
+ let ok = _defsLocal.get(root)
518
+ if (ok === undefined) { ok = branchRefsLocal(root[defsKey], defsKey, new Set()); _defsLocal.set(root, ok) }
519
+ if (!ok) return null
520
+ }
521
+ const out = []
522
+ for (const sub of branches) {
523
+ if (sub === true) { out.push(() => true); continue }
524
+ if (sub === false) { out.push(() => false); continue }
525
+ if (sub === null || typeof sub !== 'object' || Array.isArray(sub)) return null
526
+ if (!branchRefsLocal(sub, defsKey === null ? '$defs' : defsKey, new Set())) return null
527
+ const wrapped = Object.assign({}, sub)
528
+ if (defsKey !== null) wrapped[defsKey] = root[defsKey]
529
+ if (typeof ctx.rootSchema === 'object' && ctx.rootSchema !== null && typeof ctx.rootSchema.$schema === 'string') wrapped.$schema = ctx.rootSchema.$schema
530
+ let fn = null
531
+ try { fn = compileToJSCodegen(wrapped, ctx.schemaMap, ctx.userFormats) } catch (e) { if (e !== DECLINE) throw e }
532
+ if (typeof fn !== 'function') return null
533
+ out.push(fn)
534
+ }
535
+ return out
536
+ }
537
+
538
+ // A verdict for one subtree of the combined function, as a function in the
539
+ // preamble, so the collecting code of that subtree runs only for a value the
540
+ // verdict rejects. The collecting code is slower than the verdict on an
541
+ // accepted value (it keeps paths, builds nothing it can avoid but tests more
542
+ // than the verdict's cheapest-first order), so without this the whole
543
+ // function was put behind one verdict of the whole document (`_VF`), and a
544
+ // rejected document was validated twice in full. With a guard at every
545
+ // array element and every object-valued property, an accepted value costs
546
+ // its verdict and a rejected one costs its verdict plus the collection of
547
+ // the subtree that failed. Only in the combined function, the runtime path;
548
+ // null for a subtree whose verdict declines, is a leaf, or is too large.
549
+ const GUARD_KEYS = ['oneOf', 'anyOf', 'allOf', '$ref', 'items', 'prefixItems', 'properties', 'patternProperties', 'additionalProperties', 'not', 'if', 'contains', 'dependentSchemas', 'dependencies', 'propertyNames', 'unevaluatedProperties', 'unevaluatedItems']
550
+ const GUARD_MAX = 16 * 1024
551
+ const GUARD_BUDGET = 512 * 1024
552
+ // Whether a schema node is worth a guard of its own: it applies something
553
+ // beyond leaf checks. A node whose children are themselves worth guarding is
554
+ // not guarded; the guards go on the children. Guarding both ran the parent's
555
+ // verdict in full and then, when it failed, every child's verdict again: on a
556
+ // list of 1000 users with one bad one that was two verdicts of the list
557
+ // before the one user was collected.
558
+ const weighty = (n) => n !== null && typeof n === 'object' && !Array.isArray(n) && GUARD_KEYS.some((k) => n[k] !== undefined)
559
+ function childrenWeighty (sub) {
560
+ if (weighty(sub.items)) return true
561
+ if (Array.isArray(sub.prefixItems) && sub.prefixItems.some(weighty)) return true
562
+ if (weighty(sub.additionalProperties)) return true
563
+ if (sub.properties && typeof sub.properties === 'object') for (const k of Object.keys(sub.properties)) if (weighty(sub.properties[k])) return true
564
+ if (sub.patternProperties && typeof sub.patternProperties === 'object') for (const k of Object.keys(sub.patternProperties)) if (weighty(sub.patternProperties[k])) return true
565
+ return false
566
+ }
567
+ // Below this much verdict source the guard is written into the function
568
+ // itself as a labelled block (`gN:{...break gN...}`), not as a function it
569
+ // calls: in the first thousands of calls of a process, before the optimizing
570
+ // compiler has inlined anything, each call is paid in full, and a schema
571
+ // with six small guards paid six. Above it the guard stays a function, so
572
+ // one large subtree does not swell the function around it.
573
+ const GUARD_INLINE_MAX = 2048
574
+ function subtreeGuard (sub, ctx, gv) {
575
+ if (!ctx.inCombined || !weighty(sub) || childrenWeighty(sub)) return null
576
+ if ((ctx.guardChars || 0) >= GUARD_BUDGET) return null
577
+ // The verdict generator registers the definition functions it writes under
578
+ // `defFns`, and so does the error generator for the branch functions of
579
+ // this same program: shared, the verdict found the error generator's
580
+ // `_defE` function under the definition's name and called it with one
581
+ // argument. The guards keep a registry of their own.
582
+ if (!ctx.guardDefFns) ctx.guardDefFns = new Map()
583
+ const saved = snapshotCtx(ctx)
584
+ const outerDefFns = ctx.defFns
585
+ const outerShare = ctx.shareDefs
586
+ ctx.defFns = ctx.guardDefFns
587
+ // Definitions referenced from several places are written once, as
588
+ // functions in the preamble (the verdict generator's shared mode), not
589
+ // expanded into every guard that reaches them: expanded, the guards of a
590
+ // 25-definition API schema came to 55 KB of source in a 133 KB function,
591
+ // and a function that size reaches the optimizing compiler late.
592
+ ctx.shareDefs = true
593
+ try {
594
+ const bl = []
595
+ try { nestedGenCode(sub, gv, bl, ctx) } finally { ctx.defFns = outerDefFns; ctx.shareDefs = outerShare }
596
+ if (bl.length === 0) { restoreCtx(ctx, saved); return null }
597
+ const body = bl.join(';')
598
+ // The verdict generator's recursion names (`_validate`, the cycle guard
599
+ // state) are not this function's; a subtree that reaches them stays
600
+ // unguarded.
601
+ const added = ctx.preamble.slice(saved.get('preamble').n).join(';')
602
+ if (body.length > GUARD_MAX || body.includes(SP_PLACEHOLDER) || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(body + added)) { restoreCtx(ctx, saved); return null }
603
+ ctx.guardChars = (ctx.guardChars || 0) + body.length
604
+ if (body.length <= GUARD_INLINE_MAX) return { body }
605
+ const name = `_vg${ctx.varCounter++}`
606
+ ctx.helperCode.push(`const ${name}=function(${gv}){${body};return true}`)
607
+ return { name }
608
+ } catch (e) {
609
+ if (e !== DECLINE) throw e
610
+ restoreCtx(ctx, saved)
611
+ return null
612
+ }
613
+ }
614
+ // The collecting code for `sub` at `v`, behind its guard when there is one.
615
+ function genGuardedC (sub, v, pathExpr, lines, ctx, schemaPrefix) {
616
+ const gi = ctx.varCounter++
617
+ const gv = `_gv${gi}`
618
+ const g = subtreeGuard(sub, ctx, gv)
619
+ if (g === null) { genCodeC(sub, v, pathExpr, lines, ctx, schemaPrefix); return }
620
+ if (g.name !== undefined) lines.push(`if(!${g.name}(${v})){`)
621
+ else {
622
+ // The verdict's exits become jumps out of the block: a failure leaves
623
+ // the flag set, success clears it. replaceTopLevel keeps nested
624
+ // closures' returns as they are (see the hybrid rewrite).
625
+ 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}){`)
626
+ }
627
+ ctx.guardDepth = (ctx.guardDepth || 0) + 1
628
+ try { genCodeC(sub, v, pathExpr, lines, ctx, schemaPrefix) } finally { ctx.guardDepth-- }
629
+ lines.push(g.name !== undefined ? '}' : '}}')
630
+ }
631
+
632
+ // A branch's error-collecting body rewritten to count what it would have
633
+ // collected: each `_e.push({keyword:'k',...})` becomes a count and a severity
634
+ // (lib/branch-collapse.js, __ataScore), and the body returns the score the
635
+ // collapse would compute from the errors. When no branch of a oneOf or anyOf
636
+ // holds, the collapse reports the closest branch's errors; it used to run
637
+ // every branch's error function to score them, building every branch's
638
+ // errors to keep one branch's. Now the counting bodies score, and only the
639
+ // closest branch's error function runs. Null for a body the rewrite cannot
640
+ // follow (a shared definition call, a nested collapse, a push that is not a
641
+ // literal); that site keeps the old path.
642
+ function countingBody (body) {
643
+ // A nested oneOf or anyOf that does not hold is one collapsed error in the
644
+ // branch's list (keyword oneOf or anyOf, severity 4): its marked section,
645
+ // which would run the inner branches to build it, becomes that count.
646
+ // Two kinds of section: CB is one error whatever the branches would say;
647
+ // CU is a section the copy cannot score (the value is not the one the
648
+ // counts were taken for), so the copy answers -1 and the picker runs the
649
+ // branches as before.
650
+ while (true) {
651
+ const b1 = body.indexOf('/*CB*/'), b2 = body.indexOf('/*CU*/')
652
+ const b = b1 < 0 ? b2 : b2 < 0 ? b1 : Math.min(b1, b2)
653
+ if (b < 0) break
654
+ const unknown = b === b2
655
+ let depth = 1
656
+ let j = b + 6
657
+ while (j < body.length && depth > 0) {
658
+ if (body.startsWith('/*CB*/', j) || body.startsWith('/*CU*/', j)) { depth++; j += 6; continue }
659
+ if (body.startsWith('/*CE*/', j)) { depth--; j += 6; continue }
660
+ j++
661
+ }
662
+ if (depth !== 0) return null
663
+ body = body.slice(0, b) + (unknown ? 'return -1' : '(_n++,_s+=4)') + body.slice(j)
664
+ }
665
+ if (/\b(?:_defE\d|__ataCollapse|__ataMulti|_brf\d|_bsf\d)/.test(body)) return null
666
+ const SEV = require('./branch-collapse').SEVERITY
667
+ let out = ''
668
+ let i = 0
669
+ const head = 'const _e=[];'
670
+ if (!body.startsWith(head)) return null
671
+ out += 'let _n=0,_s=0;'
672
+ i = head.length
673
+ while (i < body.length) {
674
+ const ch = body[i]
675
+ if (ch === '"' || ch === "'" || ch === '`') { const j = skipString(body, i); out += body.slice(i, j); i = j; continue }
676
+ if (body.startsWith('_e.push(', i)) {
677
+ let j = i + 8
678
+ let depth = 1
679
+ while (j < body.length && depth > 0) {
680
+ const c = body[j]
681
+ if (c === '"' || c === "'" || c === '`') { j = skipString(body, j); continue }
682
+ if (c === '(' || c === '{' || c === '[') depth++
683
+ else if (c === ')' || c === '}' || c === ']') depth--
684
+ j++
685
+ }
686
+ if (depth !== 0) return null
687
+ const arg = body.slice(i + 8, j - 1)
688
+ // The literal may still carry its `code` (the shape pass strips it).
689
+ const m = /^\{(?:code:'ATA\d{4}',)?keyword:'([A-Za-z$][\w$]*)',/.exec(arg)
690
+ if (m === null) return null
691
+ out += `(_n++,_s+=${SEV[m[1]] || 4})`
692
+ i = j
693
+ continue
694
+ }
695
+ if (body.startsWith('return{valid:false,errors:_e}', i)) { out += 'return _n*100+_s'; i += 29; continue }
696
+ if (body.startsWith('return{valid:_e.length===0,errors:_e}', i)) { out += 'return _n*100+_s'; i += 37; continue }
697
+ if (body.startsWith('_e.length', i)) { out += '_n'; i += 9; continue }
698
+ out += ch
699
+ i++
700
+ }
701
+ if (/\b_e\b/.test(out.replace(/'[^']*'|"[^"]*"/g, ''))) return null
702
+ return out
703
+ }
704
+
705
+ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaPrefix) {
706
+ ctx.usesBranchCollapse = true
707
+ const fi = ctx.varCounter++
708
+ const branchKw = keyword
709
+ const branchSp = schemaPrefix + '/' + branchKw
710
+ // Per-branch error-collecting functions, each returning {valid,errors}.
711
+ // They take the value, its instance path, `_all` and, inside a definition
712
+ // written once for all its references, that reference's schema path, so
713
+ // they can be built once per call of the error function rather than each
714
+ // time the keyword is reached: on a list of 1723 tokens that is thousands
715
+ // of closure arrays per validation.
716
+ const fns = []
717
+ const titles = []
718
+ let usesSp = false
719
+ for (let i = 0; i < branches.length; i++) {
720
+ const sub = branches[i]
721
+ const subLines = []
722
+ const subSp = branchSp + '/' + i
723
+ if (typeof sub === 'object' && sub !== null) {
724
+ // A branch is a function of its own with a local `_e`; a collapse
725
+ // inside it reports there, not into the combined function's lists.
726
+ ctx.inBranchFn = (ctx.inBranchFn || 0) + 1
727
+ try {
728
+ genCodeE(sub, '_bv', '_bpth', subLines, ctx, subSp)
729
+ } finally {
730
+ ctx.inBranchFn--
731
+ }
732
+ } else if (sub === false) {
733
+ // A false branch never matches; it reports the same error the
734
+ // interpreter attaches to a false schema.
735
+ subLines.push(`_e.push({keyword:'not',instancePath:_bpth,schemaPath:'${subSp}'${ordinalField(ctx, `${subSp}`)},params:{},message:'boolean schema is false'})`)
736
+ }
737
+ const title = (sub && typeof sub === 'object' && typeof sub.title === 'string') ? sub.title : ''
738
+ titles.push(title)
739
+ let body = subLines.length === 0 ? '' : `const _e=[];${subLines.join(';')};return{valid:_e.length===0,errors:_e}`
740
+ if (body.includes(SP_PLACEHOLDER)) {
741
+ body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
742
+ if (body.includes(SP_PLACEHOLDER)) throw DECLINE
743
+ usesSp = true
744
+ }
745
+ fns.push(body)
746
+ }
747
+ const params = usesSp ? '_bv,_bpth,_all,_sp' : '_bv,_bpth,_all'
748
+ const fnArr = `_brf${fi}`
749
+ const resArr = `_brr${fi}`
750
+ const titleArr = JSON.stringify(titles)
751
+ const collapsed = `_brc${fi}`
752
+ const pp = pathExpr || '""'
753
+ ctx.helperCode.push(`const ${fnArr}=[${fns.map((b) => b === '' ? `function(){return{valid:true,errors:[]}}` : `function(${params}){${b}}`).join(',')}];const _bt${fi}=${titleArr}`)
754
+ const call = `${fnArr}[_bi](${v},_pp${fi},_all${usesSp ? ',_sp' : ''})`
755
+ // branchSp is source text for a quoted literal, with backslashes and quotes
756
+ // escaped; the ordinal is looked up by the path itself. Looked up by the
757
+ // text, a path through a pattern such as `^\\d+$` found no node, or the
758
+ // wrong one, and the collapsed error sorted out of place against the other
759
+ // engines.
760
+ // In the runtime shape there is no ordering key (the reader looks the path
761
+ // up), so the collapsed error is in its final shape as it is built.
762
+ const ord = ctx.noOrdinal ? 'null' : (() => { const o = ordinalFor(ctx.rootSchema, unescapeSp(branchSp)); return o === null ? 'null' : o })()
763
+ // In the combined function `_e` is created on the first error and every
764
+ // error is collected (`_all` is true there), so the report only pushes.
765
+ const report = ctx.inCombined && !ctx.inBranchFn
766
+ ? `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed})(_e=_ap(_A,_e,${collapsed},${apOrd(ctx, branchSp)}))`
767
+ : `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed}){_e.push(${collapsed});if(!_all)return{valid:false,errors:_e}}`
768
+ // Score first, collect once (countingBody), in the combined function where
769
+ // the collapsed error is built in its final shape (no ordering key).
770
+ let noneReport = null
771
+ // A standalone module does without the counting copies (noCounting): each
772
+ // is a second copy of a branch's error body, 40 percent of a large module's
773
+ // source, and a module's rejections are rare enough that running every
774
+ // branch's error function and collapsing is the better trade.
775
+ if (ctx.inCombined && (ctx.noOrdinal ? 'null' : '') === 'null' && !ctx.noCounting) {
776
+ const cbs = fns.map((b) => b === '' ? 'return 0' : countingBody(b))
777
+ if (!cbs.some((c) => c === null)) {
778
+ const sfx = usesSp ? ',_sp' : ''
779
+ const code = branchKw === 'oneOf' ? 'ATA4001' : 'ATA4003'
780
+ // `code: ` with the space: the runtime shape strips this generator's own
781
+ // `code:'ATA....',` from the literals and keeps the collapse's, which
782
+ // the reader expects to carry it (see the shape pass below).
783
+ 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}`
784
+ // In a branch function the error joins that function's list (the
785
+ // combined function's `_ap` and ordering state are not its).
786
+ const push = ctx.inBranchFn ? `_e.push(${lit});if(!_all)return{valid:false,errors:_e}` : `(_e=_ap(_A,_e,${lit},${apOrd(ctx, branchSp)}))`
787
+ if (cbs.length === 1) {
788
+ // One branch is the closest one: nothing to score, no counting copy.
789
+ // Scoring it ran the branch a third time (verdict, count, errors).
790
+ noneReport = `{let _bi${fi}=0;const _br${fi}=${fnArr}[0](${v},_pp${fi},_all${sfx});${push}}`
791
+ } else {
792
+ ctx.helperCode.push(cbs.map((b, i) => `const _bsf${fi}_${i}=function(${params}){${b}}`).join(';'))
793
+ 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('')
794
+ // A copy that could not score (-1): every branch runs, as before.
795
+ const noneOld = branchKw === 'anyOf'
796
+ ? `const ${resArr}=[];for(let _bi=0;_bi<${branches.length};_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}`
797
+ : `const ${resArr}=[];for(let _bi=0;_bi<${branches.length};_bi++)${resArr}.push(${call});${report}`
798
+ 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}}}`
799
+ }
800
+ }
801
+ }
802
+ // The node's runtime unevaluatedProperties already counted how many of
803
+ // these branches pass, through the annotation functions, which answer the
804
+ // same validity. A oneOf with exactly one, an anyOf with any, holds and
805
+ // reports nothing, so its branches are not run a second time to say so.
806
+ // Only in the combined function proper, where that count is in scope.
807
+ // Inside a branch function only a factory-scope count (factoryCount) is
808
+ // in reach; the node's own counts are locals of the combined function.
809
+ const countedRaw = ctx.inCombined && ctx.branchCounts ? ctx.branchCounts.get(branches) : undefined
810
+ const counted = countedRaw !== undefined && (!ctx.inBranchFn || countedRaw.factory) ? countedRaw : undefined
811
+ const same = counted !== undefined ? `${v}!==null&&typeof ${v}==='object'&&${counted.of}===${v}` : ''
812
+ let guard = counted !== undefined
813
+ ? (keyword === 'anyOf' ? `if(!(${same}&&${counted.count}>=1))` : `if(!(${same}&&${counted.count}===1))`)
814
+ : ''
815
+ // More than one oneOf branch passed: the error names them and carries no
816
+ // branch errors, so it is built from the bits the count left.
817
+ if (guard !== '' && keyword === 'oneOf' && counted.mask && ctx.inCombined) {
818
+ const ord0 = ctx.noOrdinal ? 'null' : (() => { const o = ordinalFor(ctx.rootSchema, unescapeSp(branchSp)); return o === null ? 'null' : o })()
819
+ const multiC = `__ataMulti(_pi${fi},${branches.length},${pp},'${branchSp}',${ord0})`
820
+ const pushC = ctx.inBranchFn ? `_e.push(${multiC});if(!_all)return{valid:false,errors:_e}` : `(_e=_ap(_A,_e,${multiC},${apOrd(ctx, branchSp)}))`
821
+ // The marked section is one error in a counting copy (countingBody).
822
+ 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))`
823
+ }
824
+ // Without such a count, each branch also gets a verdict function, and the
825
+ // passing branches are counted with those: a oneOf with exactly one and an
826
+ // anyOf with any hold, and a oneOf with more than one is reported from the
827
+ // bits, so the error-collecting branches run only when no branch passes
828
+ // and the collapse needs their errors to pick the closest. Running them to
829
+ // learn the count built a result object and an error list per branch,
830
+ // which made a oneOf matched twice cost 69 ns where counting costs about
831
+ // 12. A branch the verdict generator declines keeps the old path.
832
+ let vfArr = null
833
+ // Branches that hold a reference are left to the error branches: their
834
+ // verdicts would expand every definition again, as a second copy, where the
835
+ // error branches call the shared one.
836
+ if (guard === '' && !branches.some((b) => holdsRef(b))) {
837
+ // A trial: the generators are not exception-safe on the context (a
838
+ // reference left on refStack by a declined branch made the next error
839
+ // function write that branch as nothing, which read as valid), so the
840
+ // context is put back as it was when the verdicts decline.
841
+ const saved = snapshotCtx(ctx)
842
+ try {
843
+ const vfs = branches.map((sub) => {
844
+ if (sub === true) return 'function(){return true}'
845
+ if (sub === false) return 'function(){return false}'
846
+ const bl = []
847
+ nestedGenCode(sub, '_bv', bl, ctx)
848
+ return bl.length === 0 ? 'function(){return true}' : `function(_bv){${bl.join(';')};return true}`
849
+ })
850
+ // Only while the verdicts are small: they are a second copy of the
851
+ // branches, with every reference expanded, and on large branches that
852
+ // copy cost more to compile than counting saves (apollo-router's error
853
+ // function grew from 1.5 to 2.5 MB of source).
854
+ if (!vfs.some((b) => b.includes(SP_PLACEHOLDER)) && vfs.reduce((n, b) => n + b.length, 0) <= BRANCH_VERDICT_MAX) {
855
+ // One constant per branch, called directly: through an array the one
856
+ // call site sees every branch and none of them is inlined.
857
+ vfArr = `_bvf${fi}`
858
+ ctx.helperCode.push(vfs.map((b, i) => `const ${vfArr}_${i}=${b}`).join(';'))
859
+ } else {
860
+ restoreCtx(ctx, saved)
861
+ }
862
+ } catch (e) {
863
+ if (e !== DECLINE) throw e
864
+ restoreCtx(ctx, saved)
865
+ }
866
+ }
867
+ // In the combined function (the runtime path; a standalone module cannot
868
+ // carry a compiled function) a branch that holds a reference, or a site
869
+ // whose inline verdicts were too large, gets a verdict compiled on its own
870
+ // and handed in as a closure value: the branch with the root's definitions
871
+ // under it, through compileToJSCodegen, with its own shared definitions and
872
+ // its own regex engines. Without one, every branch's error function ran on
873
+ // every value the keyword saw, accepted or not: json-patch's six-way oneOf
874
+ // cost 1363 ns on a valid list of patches where its verdict costs 261.
875
+ if (vfArr === null && guard === '' && ctx.inCombined) {
876
+ const compiled = compileBranchVerdicts(branches, ctx)
877
+ if (compiled !== null) {
878
+ vfArr = `_bvf${fi}`
879
+ for (let i = 0; i < compiled.length; i++) { ctx.closureVars.push(`${vfArr}_${i}`); ctx.closureVals.push(compiled[i]) }
880
+ }
881
+ }
882
+ if (vfArr !== null) {
883
+ const L = branches.length
884
+ const top = ctx.inCombined && !ctx.inBranchFn
885
+ // An accepted document is still answered by the verdict first (see the
886
+ // result shape in compileToJSCombined): that is the fastest path to it,
887
+ // and dropping it measured 20 ns slower per accepted oneOf.
888
+ if (top && !ctx.guardDepth) ctx.unguardedCollapse = true
889
+ const tests = branches.map((_, i) => `${vfArr}_${i}(${v})`)
890
+ if (keyword === 'anyOf') {
891
+ 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}`
892
+ // In a branch function the section a counting copy replaces is marked
893
+ // (countingBody): when the keyword does not hold, the error function
894
+ // reports one collapsed error, whatever the branches would say.
895
+ 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*/'}}}`)
896
+ } else {
897
+ const mk = L <= 30
898
+ const collect = mk
899
+ ? `const _pi${fi}=[];for(let _k=0;_k<${L};_k++)if((_m${fi}>>_k)&1)_pi${fi}.push(_k);`
900
+ : ''
901
+ const multi = `__ataMulti(_pi${fi},${L},_pp${fi},'${branchSp}',${ord})`
902
+ const pushMulti = top ? `(_e=_ap(_A,_e,${multi},${apOrd(ctx, branchSp)}))` : `_e.push(${multi});if(!_all)return{valid:false,errors:_e}`
903
+ const count = tests.map((t, i) => `if(${t}){_n${fi}++;${mk ? `_m${fi}|=${1 << i}` : `_pi${fi}.push(${i})`}}`).join('')
904
+ const none = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++)${resArr}.push(${call});${report}`
905
+ 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*/'}}}`)
906
+ }
907
+ return
908
+ }
909
+ if (guard === '' && ctx.inCombined && !ctx.inBranchFn && !ctx.guardDepth) ctx.unguardedCollapse = true
910
+ if (keyword === 'anyOf') {
911
+ // anyOf reports nothing once a branch passes, so the branches are first
912
+ // only asked whether they pass. They are called with `_all` set to 0, a
913
+ // third value next to true (collect everything) and false (stop at the
914
+ // first error, for abortEarly): every check reads it as `!_all` and stops
915
+ // at its first error, and an anyOf reached that way that fails returns
916
+ // at once instead of collecting its branches and collapsing them, since
917
+ // the caller only wants to know. An object tried against a branch that is
918
+ // an anyOf of primitive types used to collapse that anyOf in full and then
919
+ // pass the next branch, which on a list of 1723 tokens was 2353 collapses
920
+ // no one read. Only when no branch passes do they run again with the
921
+ // caller's `_all`, and the collapse sees them all, as before.
922
+ const probe = `${fnArr}[_bi](${v},_pp${fi},0${usesSp ? ',_sp' : ''})`
923
+ 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}`
924
+ 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}}`
925
+ // With a count: on the counted object the guard already said no branch
926
+ // held (CB, one error in a counting copy); on another object the branches
927
+ // are probed as before (CU, a counting copy cannot know).
928
+ 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*/}}`)
929
+ else lines.push(`${guard}{const _pp${fi}=${pp};${probeOld}}`)
930
+ } else {
931
+ // Asked only whether it passes (`_all` 0, see anyOf above), oneOf counts
932
+ // the passing branches and answers; the branch errors of a probe are not
933
+ // errors anyone reads and are not collapsed.
934
+ // With a count from the node's unevaluatedProperties (`counted`), a count
935
+ // of zero on the counted object means no branch held, which is the case
936
+ // the closest-branch path answers without running every branch.
937
+ 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}}`
938
+ const noneO = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++)${resArr}.push(${call});${report}`
939
+ 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*/}}`)
940
+ else lines.push(`${guard}{const _pp${fi}=${pp};${oldO}}`)
941
+ }
942
+ }
943
+
944
+ // Error-collecting code generator.
945
+ // Instead of `return false`, pushes to `_e` array and optionally early-returns.
946
+ // `_all` parameter: if falsy, return after first error.
947
+ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
948
+ return withPlain(schema, v, lines, ctx, () => genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix))
949
+ }
950
+ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
951
+ if (!schemaPrefix) schemaPrefix = '#'
952
+ // A false schema reports one error at the value that reached it, with the
953
+ // keyword the interpreter has always used for it, `not` (lib/plan-compiler.js
954
+ // line 58; its `err()` discards the 'false schema' label). Other validators
955
+ // name this keyword `false schema`; changing it here would change output
956
+ // the interpreter path already produces, so both engines say `not`.
957
+ if (schema === false) {
958
+ lines.push(`;_e.push({keyword:'not',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}'${ordinalField(ctx, `${schemaPrefix}`)},params:{},message:'boolean schema is false'});if(!_all)return{valid:false,errors:_e}`)
959
+ return
960
+ }
961
+ if (schema === true) return
962
+ if (typeof schema !== 'object' || schema === null) return
963
+ if (!ctx.regExpMap) {
964
+ ctx.regExpMap = new Map();
965
+ }
966
+ // $ref — resolve local and cross-schema refs
967
+ if (schema.$ref && !schema[REF_DONE]) {
968
+ // A keyword beside $ref that validates applies too (2020-12): the
969
+ // reference is written on its own, then the node again with the
970
+ // reference marked done, on a fresh copy that keeps `$ref` for the
971
+ // annotation readers, as genCodeCNode does. This used to decline, which
972
+ // kept the 2020-12 meta-schema (every vocabulary schema is a reference
973
+ // with siblings once unrolled by scope) off both error generators and on
974
+ // the interpreter for its errors, about 2 microseconds a rejection.
975
+ if (Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))) {
976
+ genCodeENode({ $ref: schema.$ref }, v, pathExpr, lines, ctx, schemaPrefix)
977
+ const rest = { ...schema }
978
+ Object.defineProperty(rest, REF_DONE, { value: true })
979
+ genCodeENode(rest, v, pathExpr, lines, ctx, schemaPrefix)
980
+ return
981
+ }
982
+ // A self-reference is a call to a helper over the root schema that takes
983
+ // the path and the schema path as arguments, as a definition on a cycle
984
+ // does below: the root helper takes no path, so errors raised through it
985
+ // carried the root's path, and this declined, which left a standalone
986
+ // module reporting one stub error for a schema with `$ref: "#"` anywhere
987
+ // in it. This used to fall through as a permissive no-op, which accepted
988
+ // whatever the reference guarded.
989
+ if (schema.$ref === '#') {
990
+ // The body comes from the prepared root (`rootE`), whose references
991
+ // are the resolvable ones; from the schema as given, a reference by
992
+ // `$id` resolved to nothing and the helper for the suite's recursive
993
+ // tree checked no node.
994
+ if (!ctx.rootE || typeof ctx.rootE !== 'object') throw DECLINE
995
+ if (!ctx.defFns) ctx.defFns = new Map()
996
+ let fnName = ctx.defFns.get('#')
997
+ if (!fnName) {
998
+ fnName = '_defE' + ctx.defFns.size + '_root'
999
+ ctx.defFns.set('#', fnName)
1000
+ // The helper is its own function, so the references expanded in it
1001
+ // start from nothing: inside the expansion of a definition that led
1002
+ // here, the definition's own reference would otherwise read as
1003
+ // already being expanded.
1004
+ const bodyLines = []
1005
+ const outerStack = ctx.refStack
1006
+ ctx.refStack = new Set()
1007
+ try { genCodeE(ctx.rootE, 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER) } finally { ctx.refStack = outerStack }
1008
+ let body = bodyLines.join('\n ')
1009
+ body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
1010
+ if (body.includes(SP_PLACEHOLDER)) throw DECLINE
1011
+ ctx.helperCode.push(
1012
+ `const ${fnName}_s=new Set()\n function ${fnName}(d,_p,_all,_e,_sp){\n ` +
1013
+ `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 ` +
1014
+ `if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d,_p,_all,_e,_sp)\n _sd--\n return _r\n }\n ` +
1015
+ `function ${fnName}_b(d,_p,_all,_e,_sp){${body}}`,
1016
+ )
1017
+ }
1018
+ lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e,'${schemaPrefix}');if(!_all&&_e.length)return{valid:false,errors:_e}`)
1019
+ return
1020
+ }
1021
+ const m = schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
1022
+ if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
1023
+ const defName = m[1]
1024
+ if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
1025
+ if (!ctx.sharedDefs) ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs)
1026
+ if (ctx.cyclicDefs.has(defName) || ctx.sharedDefs.has(defName)) {
1027
+ // Named helper for a def on a cycle, and for one referenced from
1028
+ // several places, which inlined would repeat its whole error code at
1029
+ // each: Uniswap's token-list schema has 16 references to 10
1030
+ // definitions. The path comes in as an argument because every call
1031
+ // site has its own. Only the error path runs these; the verdict
1032
+ // function, which answers valid documents, is unchanged.
1033
+ if (!ctx.defFns) ctx.defFns = new Map()
1034
+ let fnName = ctx.defFns.get(defName)
1035
+ if (!fnName) {
1036
+ fnName = '_defE' + ctx.defFns.size + '_' + defName.replace(/[^A-Za-z0-9_]/g, '_')
1037
+ ctx.defFns.set(defName, fnName)
1038
+ // The schema path comes in as an argument too, `_sp`, so an error
1039
+ // names the path it was reached by, as it does when the definition
1040
+ // is inlined and as the interpreted engine reports it; a fixed
1041
+ // `#/$defs/<name>` named the definition instead. The body is
1042
+ // generated against a placeholder prefix that becomes `_sp` in every
1043
+ // string it starts. One left anywhere else declines, rather than
1044
+ // emitting a path the placeholder stands in for.
1045
+ const bodyLines = []
1046
+ genCodeE(ctx.rootDefs[defName], 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER)
1047
+ let body = bodyLines.join('\n ')
1048
+ body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
1049
+ if (body.includes(SP_PLACEHOLDER)) throw DECLINE
1050
+ ctx.helperCode.push(
1051
+ `const ${fnName}_s=new Set()\n function ${fnName}(d,_p,_all,_e,_sp){\n ` +
1052
+ `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 ` +
1053
+ `if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d,_p,_all,_e,_sp)\n _sd--\n return _r\n }\n ` +
1054
+ `function ${fnName}_b(d,_p,_all,_e,_sp){${body}}`,
1055
+ )
1056
+ }
1057
+ lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e,'${schemaPrefix}');if(!_all&&_e.length)return{valid:false,errors:_e}`)
1058
+ return
1059
+ }
1060
+ // A reference back into what is being expanded, on a cycle the
1061
+ // definitions alone do not show (through the root, or another
1062
+ // document): declined. Returning emitted nothing, which read as valid.
1063
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
1064
+ ctx.refStack.add(schema.$ref)
1065
+ genCodeE(ctx.rootDefs[defName], v, pathExpr, lines, ctx, schemaPrefix)
1066
+ ctx.refStack.delete(schema.$ref)
1067
+ return
1068
+ }
1069
+ // Anchor ref: "#foo" — resolve via rootDefs or anchors map
1070
+ if (!m && schema.$ref.startsWith('#') && !schema.$ref.startsWith('#/')) {
1071
+ const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
1072
+ const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
1073
+ if (anchorTarget) {
1074
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
1075
+ ctx.refStack.add(schema.$ref)
1076
+ genCodeE(anchorTarget, v, pathExpr, lines, ctx, schemaPrefix)
1077
+ ctx.refStack.delete(schema.$ref)
1078
+ return
1079
+ }
1080
+ }
1081
+ if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
1082
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
1083
+ ctx.refStack.add(schema.$ref)
1084
+ genCodeE(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
1085
+ ctx.refStack.delete(schema.$ref)
1086
+ return
1087
+ }
1088
+ // Cross-schema ref with JSON pointer fragment ("<id>#/<path>")
1089
+ if (ctx.schemaMap && schema.$ref.includes('#') && !schema.$ref.startsWith('#')) {
1090
+ const r = resolveCrossSchemaRef(schema.$ref, ctx.schemaMap)
1091
+ if (r) {
1092
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
1093
+ ctx.refStack.add(schema.$ref)
1094
+ genCodeE(r.schema, v, pathExpr, lines, ctx, schemaPrefix)
1095
+ ctx.refStack.delete(schema.$ref)
1096
+ return
1097
+ }
1098
+ }
1099
+ // A reference none of the above resolved: declined. Falling through
1100
+ // emitted nothing for it, which read as valid.
1101
+ throw DECLINE
1102
+ }
1103
+
1104
+ // $dynamicRef — resolve via anchors map
1105
+ if (schema.$dynamicRef) genDynamicRefE(schema, v, pathExpr, lines, ctx, schemaPrefix)
1106
+
1107
+ const types = schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : null
1108
+ if (types) {
1109
+ const conds = types.map(t => {
1110
+ switch (t) {
1111
+ case 'object': return `(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
1112
+ case 'array': return `Array.isArray(${v})`
1113
+ case 'string': return `typeof ${v}==='string'`
1114
+ case 'number': return `Number.isFinite(${v})`
1115
+ case 'integer': return `Number.isInteger(${v})`
1116
+ case 'boolean': return `typeof ${v}==='boolean'`
1117
+ case 'null': return `${v}===null`
1118
+ default: return 'true'
1119
+ }
1120
+ })
1121
+ const expected = types.join(', ')
1122
+ // As written: a one-element array stays an array, as the interpreted engine
1123
+ // reports it.
1124
+ const expectedParam = !Array.isArray(schema.type) ? `'${types[0]}'` : JSON.stringify(types)
1125
+ {
1126
+ const typeSp = `${schemaPrefix}/type`
1127
+ const lit = buildErrorLiteral({ keyword: 'type', schemaPath: typeSp, sourceMap: ctx.sourceMap })
1128
+ lines.push(`if(!(${conds.join('||')})){_e.push({code:'${lit.codeStr}',keyword:'type',instancePath:${pathExpr||'""'},schemaPath:'${typeSp}'${ordinalField(ctx, `${typeSp}`)},params:{type:${expectedParam}},message:'must be ${expected}',docUrl:'${lit.docUrl}'${lit.frame}});if(!_all)return{valid:false,errors:_e}}`)
1129
+ }
1130
+ }
1131
+
1132
+ // In error mode, never assume type — always guard (data may have failed type check but allErrors continues)
1133
+ const isObj = false
1134
+ const isArr = false
1135
+ const isStr = false
1136
+ const isNum = false
1137
+
1138
+ const fail = (keyword, schemaSuffix, paramsCode, msgCode, fmt) => {
1139
+ const sp = schemaPrefix + '/' + schemaSuffix
1140
+ const lit = buildErrorLiteral({ keyword, format: fmt, schemaPath: sp, sourceMap: ctx.sourceMap })
1141
+ return `_e.push({code:'${lit.codeStr}',keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode},docUrl:'${lit.docUrl}'${lit.frame}${ordinalField(ctx, sp)}});if(!_all)return{valid:false,errors:_e}`
1142
+ }
1143
+
1144
+ // enum
1145
+ if (schema.enum) {
1146
+ lines.push(`if(!(${enumCondition(ctx, schema.enum, v)})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
1147
+ }
1148
+
1149
+ // const — use canonical (sorted-key) comparison for objects
1150
+ if (schema.const !== undefined) {
1151
+ const cv = schema.const
1152
+ if (cv === null || typeof cv !== 'object') {
1153
+ lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
1154
+ } else {
1155
+ lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)})){${fail('const', 'const', `{allowedValue:${emitParamConstant(ctx, schema.const)}}`, "'must be equal to constant'")}}`)
1156
+ }
1157
+ }
1158
+
1159
+ // required — no destructuring in error mode (data might not be an object)
1160
+ const requiredSet = new Set(schema.required || [])
1161
+ const hoisted = {}
1162
+ if (schema.required) {
1163
+ const reqSp = `${schemaPrefix}/required`
1164
+ const reqLit = buildErrorLiteral({ keyword: 'required', schemaPath: reqSp, sourceMap: ctx.sourceMap })
1165
+ for (const key of schema.required) {
1166
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!${ownKeyExpr(ctx, v, key)}){_e.push({code:'${reqLit.codeStr}',keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${reqSp}'${ordinalField(ctx, `${reqSp}`)},params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'",docUrl:'${reqLit.docUrl}'${reqLit.frame}});if(!_all)return{valid:false,errors:_e}}`)
1167
+ }
1168
+ }
1169
+
1170
+ // numeric
1171
+ if (schema.minimum !== undefined) {
1172
+ const c = isNum ? `${v}<${schema.minimum}` : `typeof ${v}==='number'&&${v}<${schema.minimum}`
1173
+ lines.push(`if(${c}){${fail('minimum', 'minimum', `{comparison:'>=',limit:${schema.minimum}}`, `'must be >= ${schema.minimum}'`)}}`)
1174
+ }
1175
+ if (schema.maximum !== undefined) {
1176
+ const c = isNum ? `${v}>${schema.maximum}` : `typeof ${v}==='number'&&${v}>${schema.maximum}`
1177
+ lines.push(`if(${c}){${fail('maximum', 'maximum', `{comparison:'<=',limit:${schema.maximum}}`, `'must be <= ${schema.maximum}'`)}}`)
1178
+ }
1179
+ if (schema.exclusiveMinimum !== undefined) {
1180
+ const c = isNum ? `${v}<=${schema.exclusiveMinimum}` : `typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum}`
1181
+ lines.push(`if(${c}){${fail('exclusiveMinimum', 'exclusiveMinimum', `{comparison:'>',limit:${schema.exclusiveMinimum}}`, `'must be > ${schema.exclusiveMinimum}'`)}}`)
1182
+ }
1183
+ if (schema.exclusiveMaximum !== undefined) {
1184
+ const c = isNum ? `${v}>=${schema.exclusiveMaximum}` : `typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum}`
1185
+ lines.push(`if(${c}){${fail('exclusiveMaximum', 'exclusiveMaximum', `{comparison:'<',limit:${schema.exclusiveMaximum}}`, `'must be < ${schema.exclusiveMaximum}'`)}}`)
1186
+ }
1187
+ if (schema.multipleOf !== undefined) {
1188
+ const m = schema.multipleOf
1189
+ const ci = ctx.varCounter++
1190
+ // Use tolerance-based check for floating point (matches C++ behavior)
1191
+ lines.push(`{if(typeof ${v}==='number'&&${multipleOfBad(v, m)}){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
1192
+ }
1193
+
1194
+ // string length — same s.length fast paths as the boolean codegen above.
1195
+ // length < M → certain fail; length > 2*X → certain fail; sweet spot
1196
+ // 2*M <= length <= X passes without scanning. Only call _cpLen in the
1197
+ // uncertain band (caches it so it isn't called twice when both bounds are set).
1198
+ if (schema.minLength !== undefined) {
1199
+ const M = schema.minLength
1200
+ const inner = `${v}.length<${M}||(${v}.length<${M * 2}&&_cpLen(${v})<${M})`
1201
+ const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
1202
+ lines.push(`if(${c}){${fail('minLength', 'minLength', `{limit:${M}}`, `'must NOT have fewer than ${M} characters'`)}}`)
1203
+ }
1204
+ if (schema.maxLength !== undefined) {
1205
+ const X = schema.maxLength
1206
+ const inner = `${v}.length>${X * 2}||(${v}.length>${X}&&_cpLen(${v})>${X})`
1207
+ const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
1208
+ lines.push(`if(${c}){${fail('maxLength', 'maxLength', `{limit:${X}}`, `'must NOT have more than ${X} characters'`)}}`)
1209
+ }
1210
+ if (schema.pattern) {
1211
+ const inlineCheck = compilePatternInline(schema.pattern, v)
1212
+ if (inlineCheck) {
1213
+ const c = isStr ? `!(${inlineCheck})` : `typeof ${v}==='string'&&!(${inlineCheck})`
1214
+ lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
1215
+ } else {
1216
+ const pattern = JSON.stringify(schema.pattern);
1217
+ if (!ctx.regExpMap.has(pattern)) {
1218
+ const ri = ctx.varCounter++
1219
+ ctx.regExpMap.set(pattern, ri)
1220
+ if (useSafeEngine(schema.pattern)) {
1221
+ ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
1222
+ ctx.usesSafeRe = true
1223
+ } else {
1224
+ ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
1225
+ }
1226
+ }
1227
+ const ri = ctx.regExpMap.get(pattern);
1228
+ const c = isStr ? `!_re${ri}.test(${v})` : `typeof ${v}==='string'&&!_re${ri}.test(${v})`
1229
+ lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
1230
+ }
1231
+ }
1232
+ if (schema.format) {
1233
+ const fc = FORMAT_CODEGEN[schema.format]
1234
+ const fmtSp = `${schemaPrefix}/format`
1235
+ const fmtLit = buildErrorLiteral({ keyword: 'format', format: schema.format, schemaPath: fmtSp, sourceMap: ctx.sourceMap })
1236
+ const failPush = `_e.push({code:'${fmtLit.codeStr}',keyword:'format',instancePath:${pathExpr||'""'},schemaPath:'${fmtSp}'${ordinalField(ctx, `${fmtSp}`)},params:{format:'${esc(schema.format)}'},message:'must match format "${esc(schema.format)}"',docUrl:'${fmtLit.docUrl}'${fmtLit.frame}});if(!_all)return{valid:false,errors:_e}`
1237
+ // Format errors use the boolean codegen, each `return false` becoming an
1238
+ // error push. One violation reports once: the first failing statement
1239
+ // pushes and leaves the labeled block, where it used to fall through and
1240
+ // let every later statement of the same check push the same error again.
1241
+ if (fc) {
1242
+ const ri = ctx.varCounter++
1243
+ const fmtCode = fc(v, isStr, ctx).replace(/return false/g, `{${failPush};break _fmt${ri}}`)
1244
+ lines.push(`_fmt${ri}:{${fmtCode}}`)
1245
+ } else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
1246
+ // User-supplied format checker on the error path: the same closure
1247
+ // plumbing as the boolean codegen. The function is bound as a parameter
1248
+ // of the compiled error function; bundle output serializes it via
1249
+ // Function#toString separately.
1250
+ const safeName = schema.format.replace(/[^a-zA-Z0-9_]/g, '_')
1251
+ const closureName = `_uf_${safeName}`
1252
+ if (ctx.closureVars && !ctx.closureVars.includes(closureName)) {
1253
+ ctx.closureVars.push(closureName)
1254
+ ctx.closureVals.push(ctx.userFormats[schema.format])
1255
+ }
1256
+ const guard = isStr ? '' : `typeof ${v}==='string'&&`
1257
+ lines.push(`if(${guard}!${closureName}(${v})){${failPush}}`)
1258
+ }
1259
+ }
1260
+
1261
+ // array size
1262
+ if (schema.minItems !== undefined) {
1263
+ const c = isArr ? `${v}.length<${schema.minItems}` : `Array.isArray(${v})&&${v}.length<${schema.minItems}`
1264
+ lines.push(`if(${c}){${fail('minItems', 'minItems', `{limit:${schema.minItems}}`, `'must NOT have fewer than ${schema.minItems} items'`)}}`)
1265
+ }
1266
+ if (schema.maxItems !== undefined) {
1267
+ const c = isArr ? `${v}.length>${schema.maxItems}` : `Array.isArray(${v})&&${v}.length>${schema.maxItems}`
1268
+ lines.push(`if(${c}){${fail('maxItems', 'maxItems', `{limit:${schema.maxItems}}`, `'must NOT have more than ${schema.maxItems} items'`)}}`)
1269
+ }
1270
+
1271
+ // uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
1272
+ if (schema.uniqueItems) genUniqueItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr)
1273
+
1274
+ // object size
1275
+ if (schema.minProperties !== undefined) {
1276
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${schema.minProperties}){${fail('minProperties', 'minProperties', `{limit:${schema.minProperties}}`, `'must NOT have fewer than ${schema.minProperties} properties'`)}}`)
1277
+ }
1278
+ if (schema.maxProperties !== undefined) {
1279
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${schema.maxProperties}){${fail('maxProperties', 'maxProperties', `{limit:${schema.maxProperties}}`, `'must NOT have more than ${schema.maxProperties} properties'`)}}`)
1280
+ }
1281
+
1282
+ // additionalProperties: false. A key matched by any patternProperties
1283
+ // entry is not additional, so those patterns are consulted here as well.
1284
+ if (schema.additionalProperties === false) {
1285
+ const allowed = Object.keys(schema.properties || {}).map(k => `${JSON.stringify(k)}`).join(',')
1286
+ const ci = ctx.varCounter++
1287
+ const apSp = `${schemaPrefix}/additionalProperties`
1288
+ const apLit = buildErrorLiteral({ keyword: 'additionalProperties', schemaPath: apSp, sourceMap: ctx.sourceMap })
1289
+ const patChecks = []
1290
+ for (const pat of Object.keys(schema.patternProperties || {})) {
1291
+ const pattern = JSON.stringify(pat)
1292
+ if (!ctx.regExpMap.has(pattern)) {
1293
+ const ri = ctx.varCounter++
1294
+ ctx.regExpMap.set(pattern, ri)
1295
+ if (useSafeEngine(pat)) {
1296
+ ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
1297
+ ctx.usesSafeRe = true
1298
+ } else {
1299
+ ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
1300
+ }
1301
+ }
1302
+ patChecks.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ci}[_i])`)
1303
+ }
1304
+ const isAdditional = patChecks.length ? `!_a${ci}.has(_k${ci}[_i])&&!(${patChecks.join('||')})` : `!_a${ci}.has(_k${ci}[_i])`
1305
+ // Same shape as the combined generator: a for-in says whether anything is
1306
+ // extra without allocating, and the keys are materialised only to name one.
1307
+ // This function walks every node of a document that failed anywhere, so on a
1308
+ // large array the clean items were each paying for a key array.
1309
+ const gateChecks = patChecks.map((c) => c.split(`_k${ci}[_i]`).join(`_f${ci}`))
1310
+ const gateTest = gateChecks.length ? `!_a${ci}.has(_f${ci})&&!(${gateChecks.join('||')})` : `!_a${ci}.has(_f${ci})`
1311
+ const detail = `const _k${ci}=Object.keys(${v});for(let _i=0;_i<_k${ci}.length;_i++){if(${isAdditional}){_e.push({code:'${apLit.codeStr}',keyword:'additionalProperties',instancePath:${pathExpr||'""'},schemaPath:'${apSp}'${ordinalField(ctx, `${apSp}`)},params:{additionalProperty:_k${ci}[_i]},message:'must NOT have additional properties',docUrl:'${apLit.docUrl}'${apLit.frame}});if(!_all)return{valid:false,errors:_e}}}`
1312
+ // The name set is built once per call of the error function, not at every
1313
+ // object it checks: on a list of 1723 tokens it was 1723 Sets.
1314
+ ctx.helperCode.push(`const _a${ci}=new Set([${allowed}])`)
1315
+ const inner = `let _x${ci}=0;for(const _f${ci} in ${v}){if(${gateTest}){_x${ci}=1;break}}if(_x${ci}){${detail}}`
1316
+ lines.push(isObj ? `{${inner}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
1317
+ }
1318
+
1319
+ // unevaluatedProperties: false, on a node where it is provably its plain
1320
+ // counterpart (the bail above guaranteed that for every occurrence). With
1321
+ // additionalProperties present everything is evaluated and there is nothing
1322
+ // to emit. The error keeps the keyword's own identity: the interpreted
1323
+ // engine reports these as unevaluatedProperties with the key named, and the
1324
+ // two engines are pinned to identical output.
1325
+ // Inside the combined function (a oneOf or anyOf branch, see
1326
+ // emitBranchCollapse) this generator only has the static form. A branch
1327
+ // whose evaluated set depends on its own branches would report the wrong
1328
+ // errors, and the collapse would pick the wrong closest branch; declined.
1329
+ if (ctx.inCombined && schema.unevaluatedProperties !== undefined && schema.unevaluatedProperties !== true && !unevalLocalOk(schema, 'unevaluatedProperties')) throw DECLINE
1330
+ if (ctx.inCombined && schema.unevaluatedItems !== undefined && schema.unevaluatedItems !== true && !unevalLocalOk(schema, 'unevaluatedItems')) throw DECLINE
1331
+ if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined) genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj)
1332
+
1333
+ // unevaluatedItems: false under the same rule. The interpreted engine
1334
+ // reports one error for the array with the first unevaluated position as
1335
+ // the limit; with adjacent `items` everything is evaluated. The draft-07
1336
+ // array form of items acts as the prefix.
1337
+ if (schema.unevaluatedItems === false) genUnevaluatedItemsFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix)
1338
+
1339
+ // dependentRequired
1340
+ if (schema.dependentRequired) {
1341
+ const drSp = `${schemaPrefix}/dependentRequired`
1342
+ const drLit = buildErrorLiteral({ keyword: 'dependentRequired', schemaPath: drSp, sourceMap: ctx.sourceMap })
1343
+ for (const [key, deps] of Object.entries(schema.dependentRequired)) {
1344
+ for (const dep of deps) {
1345
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){_e.push({code:'${drLit.codeStr}',keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${drSp}'${ordinalField(ctx, `${drSp}`)},params:{missingProperty:'${esc(dep)}'},message:"must have required property '${esc(dep)}'",docUrl:'${drLit.docUrl}'${drLit.frame}});if(!_all)return{valid:false,errors:_e}}`)
1346
+ }
1347
+ }
1348
+ }
1349
+
1350
+ // properties — always guard (error mode, data may not be an object or may be array)
1351
+ if (schema.properties) {
1352
+ for (const [key, prop] of Object.entries(schema.properties)) {
1353
+ const childPath = childPathExpr(pathExpr, ptrSeg(key))
1354
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
1355
+ genCodeE(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
1356
+ lines.push(`}`)
1357
+ }
1358
+ }
1359
+
1360
+ // patternProperties
1361
+ if (schema.patternProperties) genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix)
1362
+
1363
+ // additionalProperties as a schema — validate each property not declared in
1364
+ // `properties` against the subschema, with a dynamic /<key> path. codegenSafe
1365
+ // guarantees no patternProperties or composition here, so "additional" is
1366
+ // simply "not declared".
1367
+ if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null) genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix)
1368
+
1369
+ // dependentSchemas
1370
+ if (schema.dependentSchemas) genDependentSchemasE(schema, v, pathExpr, lines, ctx, schemaPrefix)
1371
+
1372
+ // propertyNames: false reports every key, at the key's own path, the way the
1373
+ // interpreter does (plan-compiler: instancePath + '/' + key).
1374
+ if (schema.propertyNames === false) genPropertyNamesFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix)
1375
+
1376
+ // propertyNames
1377
+ if (schema.propertyNames && typeof schema.propertyNames === 'object') genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
1378
+
1379
+ // items — starts after prefixItems (Draft 2020-12 semantics)
1380
+ if (schema.items !== undefined && schema.items !== true) {
1381
+ const startIdx = schema.prefixItems ? schema.prefixItems.length : 0
1382
+ const idx = `_j${ctx.varCounter}`
1383
+ const elem = `_ei${ctx.varCounter}`
1384
+ ctx.varCounter++
1385
+ const childPath = childPathDynExpr(pathExpr, idx)
1386
+ lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
1387
+ genCodeE(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
1388
+ lines.push(`}}`)
1389
+ }
1390
+
1391
+ // prefixItems
1392
+ if (schema.prefixItems) genPrefixItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix)
1393
+
1394
+ // contains
1395
+ if (schema.contains !== undefined) genContainsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
1396
+
1397
+ // allOf
1398
+ if (schema.allOf) {
1399
+ for (let _ai = 0; _ai < schema.allOf.length; _ai++) {
1400
+ genCodeE(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai)
1401
+ }
1402
+ }
1403
+
1404
+ // anyOf - collapse to single best-branch error via __ataCollapse
1405
+ if (schema.anyOf) genAnyOfE(schema, v, pathExpr, lines, ctx, schemaPrefix)
1406
+
1407
+ // oneOf - collapse to single best-branch error via __ataCollapse
1408
+ if (schema.oneOf) genOneOfE(schema, v, pathExpr, lines, ctx, schemaPrefix)
1409
+
1410
+ // not
1411
+ if (schema.not !== undefined) genNotE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
1412
+
1413
+ // if/then/else
1414
+ if (schema.if !== undefined) genIfE(schema, v, pathExpr, lines, ctx, schemaPrefix)
1415
+ }
1416
+
1417
+ // Rarely used keyword families of genCodeENode, kept out of it so their compile is
1418
+ // paid only by schemas that use them.
1419
+
1420
+ function genDynamicRefE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1421
+ const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
1422
+ if (ctx.anchors && ctx.anchors[anchorKey]) {
1423
+ const target = ctx.anchors[anchorKey]
1424
+ if (target === ctx.rootSchema) {
1425
+ // Self-recursive: generate _validateE call
1426
+ ctx.usesRecursion = true
1427
+ lines.push(`_validateE(${v},_all,_e)`)
1428
+ } else {
1429
+ const refKey = '$dynamicRef:' + anchorKey
1430
+ if (!ctx.refStack.has(refKey)) {
1431
+ ctx.refStack.add(refKey)
1432
+ genCodeE(target, v, pathExpr, lines, ctx, schemaPrefix)
1433
+ ctx.refStack.delete(refKey)
1434
+ }
1435
+ }
1436
+ }
1437
+ }
1438
+
1439
+ function genUniqueItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr) {
1440
+ const si = ctx.varCounter++
1441
+ const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
1442
+ // Only reached once the verdict has rejected, or when errors are wanted, so
1443
+ // one strategy for every item type: the hoisted pair finder. See UQP_HELPER.
1444
+ const inner = `const _j${si}=${emitUqp(ctx)}(${v});if(_j${si}>=0){const _i${si}=_uqpI;${failExpr('_i' + si, '_j' + si)}}`
1445
+ lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
1446
+ }
1447
+
1448
+ function genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj) {
1449
+ const allowedU = Object.keys(schema.properties || {}).map(k => `${JSON.stringify(k)}`).join(',')
1450
+ const ui = ctx.varCounter++
1451
+ const upSp = `${schemaPrefix}/unevaluatedProperties`
1452
+ const upLit = buildErrorLiteral({ keyword: 'unevaluatedProperties', schemaPath: upSp, sourceMap: ctx.sourceMap })
1453
+ const patChecksU = []
1454
+ for (const pat of Object.keys(schema.patternProperties || {})) {
1455
+ const pattern = JSON.stringify(pat)
1456
+ if (!ctx.regExpMap.has(pattern)) {
1457
+ const ri = ctx.varCounter++
1458
+ ctx.regExpMap.set(pattern, ri)
1459
+ if (useSafeEngine(pat)) {
1460
+ ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
1461
+ ctx.usesSafeRe = true
1462
+ } else {
1463
+ ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
1464
+ }
1465
+ }
1466
+ patChecksU.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ui}[_i])`)
1467
+ }
1468
+ const isUneval = patChecksU.length ? `!_a${ui}.has(_k${ui}[_i])&&!(${patChecksU.join('||')})` : `!_a${ui}.has(_k${ui}[_i])`
1469
+ ctx.helperCode.push(`const _a${ui}=new Set([${allowedU}])`)
1470
+ const innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++){if(${isUneval}){_e.push({code:'${upLit.codeStr}',keyword:'unevaluatedProperties',instancePath:${pathExpr||'""'},schemaPath:'${upSp}'${ordinalField(ctx, `${upSp}`)},params:{unevaluatedProperty:_k${ui}[_i]},message:'must NOT have unevaluated properties',docUrl:'${upLit.docUrl}'${upLit.frame}});if(!_all)return{valid:false,errors:_e}}}`
1471
+ lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
1472
+ }
1473
+
1474
+ function genUnevaluatedItemsFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1475
+ const itemsIsPrefix = Array.isArray(schema.items)
1476
+ const allEval = schema.items !== undefined && !itemsIsPrefix
1477
+ if (!allEval) {
1478
+ const plen = itemsIsPrefix ? schema.items.length : (Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0)
1479
+ const uiSp = `${schemaPrefix}/unevaluatedItems`
1480
+ const uiLit = buildErrorLiteral({ keyword: 'unevaluatedItems', schemaPath: uiSp, sourceMap: ctx.sourceMap })
1481
+ lines.push(`if(Array.isArray(${v})&&${v}.length>${plen}){_e.push({code:'${uiLit.codeStr}',keyword:'unevaluatedItems',instancePath:${pathExpr||'""'},schemaPath:'${uiSp}'${ordinalField(ctx, `${uiSp}`)},params:{limit:${plen}},message:'must NOT have more than ${plen} items',docUrl:'${uiLit.docUrl}'${uiLit.frame}});if(!_all)return{valid:false,errors:_e}}`)
1482
+ }
1483
+ }
1484
+
1485
+ function genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1486
+ for (const [pat, sub] of Object.entries(schema.patternProperties)) {
1487
+ const pattern = JSON.stringify(pat);
1488
+ if (!ctx.regExpMap.has(pattern)) {
1489
+ const ri = ctx.varCounter++
1490
+ ctx.regExpMap.set(pattern, ri)
1491
+ if (useSafeEngine(pat)) {
1492
+ ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
1493
+ ctx.usesSafeRe = true
1494
+ } else {
1495
+ ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`);
1496
+ }
1497
+ }
1498
+ const ri = ctx.regExpMap.get(pattern);
1499
+ const ki = ctx.varCounter++
1500
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){if(_re${ri}.test(_k${ki})){`)
1501
+ const _peFn = emitPtrEsc(ctx)
1502
+ const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
1503
+ // The real pointer, so source maps can locate the failing keyword.
1504
+ genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(pat))
1505
+ lines.push(`}}}`)
1506
+ }
1507
+ }
1508
+
1509
+ function genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1510
+ const ki = ctx.varCounter++
1511
+ const known = Object.keys(schema.properties || {})
1512
+ if (known.length) ctx.helperCode.push(`const _ak${ki}=new Set([${known.map((k) => JSON.stringify(k)).join(',')}])`)
1513
+ const guard = ''
1514
+ // A key matched by any patternProperties entry is not additional.
1515
+ const patTests = []
1516
+ for (const pat of Object.keys(schema.patternProperties || {})) {
1517
+ const pattern = JSON.stringify(pat)
1518
+ if (!ctx.regExpMap.has(pattern)) {
1519
+ const ri = ctx.varCounter++
1520
+ ctx.regExpMap.set(pattern, ri)
1521
+ if (useSafeEngine(pat)) {
1522
+ ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
1523
+ ctx.usesSafeRe = true
1524
+ } else {
1525
+ ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
1526
+ }
1527
+ }
1528
+ patTests.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ki})`)
1529
+ }
1530
+ const conds = []
1531
+ if (known.length) conds.push(`!_ak${ki}.has(_k${ki})`)
1532
+ if (patTests.length) conds.push(`!(${patTests.join('||')})`)
1533
+ const keep = conds.length ? `if(${conds.join('&&')}){` : `{`
1534
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${guard}for(const _k${ki} in ${v}){${keep}`)
1535
+ const _peFn = emitPtrEsc(ctx)
1536
+ const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
1537
+ genCodeE(schema.additionalProperties, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/additionalProperties')
1538
+ lines.push(`}}}`)
1539
+ }
1540
+
1541
+ function genDependentSchemasE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1542
+ for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
1543
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
1544
+ genCodeE(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
1545
+ lines.push(`}`)
1546
+ }
1547
+ }
1548
+
1549
+ function genPropertyNamesFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1550
+ const ki = ctx.varCounter++
1551
+ // The subschema is applied to the property NAME, so the failure belongs to
1552
+ // the object that carries the name, not to `/<key>`, which is where the
1553
+ // value lives. Same placement as the object-valued propertyNames branch.
1554
+ const p = pathExpr || '""'
1555
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){_e.push({keyword:'not',instancePath:${p},schemaPath:'${schemaPrefix}/propertyNames'${ordinalField(ctx, `${schemaPrefix}/propertyNames`)},params:{},message:'boolean schema is false'});if(!_all)return{valid:false,errors:_e}}}`)
1556
+ }
1557
+
1558
+ function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
1559
+ const pn = schema.propertyNames
1560
+ const ki = ctx.varCounter++
1561
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
1562
+ // The subschema checks the key as a value, and its errors are reported at
1563
+ // the object, as the interpreter reports them.
1564
+ if (!isSimplePN(pn)) {
1565
+ genCodeE(pn, `_k${ki}`, pathExpr, lines, ctx, schemaPrefix + '/propertyNames')
1566
+ lines.push(`}}`)
1567
+ return
1568
+ }
1569
+ if (pn.minLength !== undefined) {
1570
+ lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
1571
+ }
1572
+ if (pn.maxLength !== undefined) {
1573
+ lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
1574
+ }
1575
+ if (pn.pattern) {
1576
+ const pattern = JSON.stringify(pn.pattern);
1577
+ if (!ctx.regExpMap.has(pattern)) {
1578
+ const ri = ctx.varCounter++
1579
+ ctx.regExpMap.set(pattern, ri)
1580
+ if (useSafeEngine(pn.pattern)) {
1581
+ ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
1582
+ ctx.usesSafeRe = true
1583
+ } else {
1584
+ ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`);
1585
+ }
1586
+ }
1587
+ const ri = ctx.regExpMap.get(pattern);
1588
+ lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
1589
+ }
1590
+ if (pn.const !== undefined) {
1591
+ lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
1592
+ }
1593
+ if (pn.enum) {
1594
+ const ei = ctx.varCounter++
1595
+ ctx.helperCode.push(`const _es${ei}=new Set(${JSON.stringify(pn.enum)})`)
1596
+ lines.push(`if(!_es${ei}.has(_k${ki})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
1597
+ }
1598
+ lines.push(`}}`)
1599
+ }
1600
+
1601
+ function genPrefixItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1602
+ for (let i = 0; i < schema.prefixItems.length; i++) {
1603
+ const childPath = childPathExpr(pathExpr, String(i))
1604
+ lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
1605
+ genCodeE(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
1606
+ lines.push(`}`)
1607
+ }
1608
+ }
1609
+
1610
+ function genContainsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
1611
+ const ci = ctx.varCounter++
1612
+ const subLines = []
1613
+ nestedGenCode(schema.contains, `_cv`, subLines, ctx)
1614
+ const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
1615
+ const minC = schema.minContains !== undefined ? schema.minContains : 1
1616
+ const maxC = schema.maxContains
1617
+ lines.push(`if(Array.isArray(${v})){const _cf${ci}=function(_cv){${fnBody}};let _cc${ci}=0;for(let _ci${ci}=0;_ci${ci}<${v}.length;_ci${ci}++){if(_cf${ci}(${v}[_ci${ci}]))_cc${ci}++}`)
1618
+ lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'must contain at least ${minC} valid item(s)'`)}}`)
1619
+ if (maxC !== undefined) {
1620
+ lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'must NOT contain more than ${maxC} valid item(s)'`)}}`)
1621
+ }
1622
+ lines.push(`}`)
1623
+ }
1624
+
1625
+ function genAnyOfE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1626
+ emitBranchCollapse(schema.anyOf, 'anyOf', v, pathExpr, lines, ctx, schemaPrefix)
1627
+ }
1628
+
1629
+ function genOneOfE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1630
+ emitBranchCollapse(schema.oneOf, 'oneOf', v, pathExpr, lines, ctx, schemaPrefix)
1631
+ }
1632
+
1633
+ function genNotE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
1634
+ const subLines = []
1635
+ nestedGenCode(schema.not, '_nv', subLines, ctx)
1636
+ const nfn = subLines.length === 0 ? `function(_nv){return true}` : `function(_nv){${subLines.join(';')};return true}`
1637
+ const fi = ctx.varCounter++
1638
+ lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
1639
+ }
1640
+
1641
+ function genIfE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1642
+ const ifLines = []
1643
+ nestedGenCode(schema.if, '_iv', ifLines, ctx)
1644
+ const fi = ctx.varCounter++
1645
+ const ifFn = ifLines.length === 0
1646
+ ? `function(_iv){return true}`
1647
+ : `function(_iv){${ifLines.join(';')};return true}`
1648
+ lines.push(`{const _if${fi}=${ifFn}`)
1649
+ if (schema.then !== undefined) {
1650
+ lines.push(`if(_if${fi}(${v})){`)
1651
+ genCodeE(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then')
1652
+ lines.push(`}`)
1653
+ }
1654
+ if (schema.else !== undefined) {
1655
+ lines.push(`${schema.then !== undefined ? 'else' : `if(!_if${fi}(${v}))`}{`)
1656
+ genCodeE(schema.else, v, pathExpr, lines, ctx, schemaPrefix+'/else')
1657
+ lines.push(`}`)
1658
+ }
1659
+ lines.push(`}`)
1660
+ }
1661
+
1662
+ // --- Combined validator: single pass, validates + collects errors ---
1663
+ // Returns VALID_RESULT for valid data, {valid:false, errors} for invalid.
1664
+ // Avoids double-pass (jsFn → false → errFn runs same checks again).
1665
+ // Uses type-aware optimizations: after type check passes, skip guards.
1666
+ // An error literal the combined function shares between calls, frozen. Two
1667
+ // non-enumerable fields ride along, invisible to Object.keys, spread and
1668
+ // JSON: `_s` says the object is shared, so the reader copies it before handing
1669
+ // it out (Object.isFrozen, the check it replaces, cost more than the copy),
1670
+ // and `_o` is its schema ordinal, known here and otherwise looked up by path
1671
+ // on every read. An `_o` the literal already carries, for standalone output,
1672
+ // is kept. `_t` is an empty slot lib/enrich-error.js fills once.
1673
+ function sharedErr (lit, rootSchema) {
1674
+ Object.defineProperty(lit, '_s', { value: true })
1675
+ // A slot the enrichment fills on the first read with the fields that depend
1676
+ // only on this literal (code, expected, rank), so later reads skip them.
1677
+ Object.defineProperty(lit, '_t', { value: {} })
1678
+ if (lit._o === undefined) {
1679
+ const o = ordinalFor(rootSchema, lit.schemaPath)
1680
+ if (typeof o === 'number') Object.defineProperty(lit, '_o', { value: o })
1681
+ }
1682
+ return Object.freeze(lit)
1683
+ }
1684
+
1685
+ // The push for one error in the combined generator. With `ctx.rich` (the
1686
+ // validator's errors are being read and enriched, see validator-core) the
1687
+ // error is built in its final enriched shape where it happens, by makeRich in
1688
+ // lib/enrich-site.js with a site record made here at compile time; otherwise
1689
+ // it is the plain literal `lean` the generator always emitted. `valueExpr` is
1690
+ // the value at `pathExpr`, which enrichment otherwise finds again by walking
1691
+ // the document. `paramsShared` names a frozen params object hoisted for a
1692
+ // static site, so a static error's params stay one shared object as before.
1693
+ // The ordinal `_ap` is handed with each error of the combined function: the
1694
+ // error's place in schema order when its path is fixed at compile time, -1
1695
+ // when it is not (a path through a shared definition, a collapsed branch
1696
+ // error, an enriched error). `_ap` keeps whether the list is still in order
1697
+ // as it grows; a list that came out in order, which is most of them, is
1698
+ // handed out without the sort that looked every error's path up in a map.
1699
+ function apOrd (ctx, sp, coarse) {
1700
+ // Inside a shared definition's function every error takes the ordinal of
1701
+ // the reference that reached it, handed in as `_so`: errors behind one
1702
+ // `$ref` sort where the reference is written, in the order the definition
1703
+ // reports them.
1704
+ if (typeof sp === 'string' && sp.includes(SP_PLACEHOLDER) && ctx && ctx.inDefC) return '_so'
1705
+ if (typeof sp !== 'string' || sp.includes(SP_PLACEHOLDER) || sp.includes('+')) return -1
1706
+ if (!ctx || !ctx.rootSchema) return -1
1707
+ const path = unescapeSp(sp)
1708
+ const o = ordinalFor(ctx.rootSchema, path)
1709
+ if (typeof o !== 'number') return -1
1710
+ // The reader orders by ordinal and, between equal ordinals, by the rank of
1711
+ // the path (tieOrder in lib/rejections.js). Both are known here, so a site
1712
+ // gets a token that assignOrdKeys turns into a dense integer in exactly that
1713
+ // order when the program is assembled: two errors with one key are ones the
1714
+ // reader keeps as they came. Keyed by the ordinal alone, two errors under
1715
+ // one synthetic definition shared a key and sent every such list to the
1716
+ // reader, which ranked the paths again: 45 percent of reading a list
1717
+ // schema's two errors.
1718
+ if (!ctx.ordSites) ctx.ordSites = new Map()
1719
+ let site = ctx.ordSites.get(path)
1720
+ if (site === undefined) { site = { i: ctx.ordSites.size, o, r: rankFor(ctx.rootSchema, path), key: -1 }; ctx.ordSites.set(path, site) }
1721
+ // A coarse key, for the errors behind a reference into a shared definition
1722
+ // (see `ap` in lib/combined-runtime.js): -(k+2), written when k is known.
1723
+ return coarse ? `@@C${site.i}@@` : `@@O${site.i}@@`
1724
+ }
1725
+
1726
+ // The dense keys of a program's error sites, in the reader's order: ordinal,
1727
+ // then rank as tieOrder compares it, element by element and the shorter
1728
+ // first; equal pairs share a key, and a site without a rank keeps -1, which
1729
+ // hands its list to the reader. Replaces the tokens apOrd wrote.
1730
+ function assignOrdKeys (ctx, src) {
1731
+ if (!ctx.ordSites || ctx.ordSites.size === 0) return src
1732
+ const sites = [...ctx.ordSites.values()]
1733
+ const cmp = (a, b) => {
1734
+ if (a.o !== b.o) return a.o - b.o
1735
+ const m = Math.min(a.r.length, b.r.length)
1736
+ for (let k = 0; k < m; k++) if (a.r[k] !== b.r[k]) return a.r[k] - b.r[k]
1737
+ return a.r.length - b.r.length
1738
+ }
1739
+ const ranked = sites.filter((x) => x.r !== null).sort(cmp)
1740
+ let key = 0
1741
+ for (let i = 0; i < ranked.length; i++) { if (i > 0 && cmp(ranked[i - 1], ranked[i]) !== 0) key++; ranked[i].key = key }
1742
+ return src.replace(/@@([OC])(\d+)@@/g, (_, t, i) => { const k = sites[+i].key; return String(t === 'O' || k < 0 ? k : -(k + 2)) })
1743
+ }
1744
+
1745
+ // The error list and its order live in lib/combined-runtime.js (`ap`, `sr`):
1746
+ // `_A` is this program's state, reset at the top of each call.
1747
+ const AP_RESET = '_A.mo=true;_A.lo=-1;_A.on=0;'
1748
+ function errPushC(ctx, keyword, sp, pathExpr, paramsCode, msgCode, valueExpr, lean, paramsShared) {
1749
+ if (!ctx.rich) return `(_e=_ap(_A,_e,${lean},${apOrd(ctx, sp)}))`
1750
+ if (!ctx.richMk) {
1751
+ ctx.richMk = true
1752
+ const { makeRich } = require('./enrich-site')
1753
+ ctx.closureVars.push('_mk'); ctx.closureVals.push(makeRich)
1754
+ }
1755
+ let paramsVal
1756
+ try { paramsVal = Function('return ' + paramsCode)() } catch { paramsVal = undefined }
1757
+ const fmt = keyword === 'format' && paramsVal ? paramsVal.format : undefined
1758
+ const { errorSite } = require('./enrich-site')
1759
+ const siteVar = `_S${ctx.varCounter++}`
1760
+ ctx.closureVars.push(siteVar)
1761
+ // Inside a shared definition (emitSharedDefC) the site holds the path past
1762
+ // the definition and the reference's own comes in as `_sp`.
1763
+ const rel = sp.startsWith(SP_PLACEHOLDER)
1764
+ ctx.closureVals.push(errorSite(keyword, unescapeSp(rel ? sp.slice(SP_PLACEHOLDER.length) : sp), paramsVal, fmt))
1765
+ return `(_e=_ap(_A,_e,_mk(${siteVar},${pathExpr || '""'},${paramsShared || paramsCode},${msgCode},${valueExpr},d${rel ? ',_sp' : ''}),-1))`
1766
+ }
1767
+
1768
+ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts) {
1769
+ const inputSchema = schema
1770
+ schema = prepareForCodegen(schema, schemaMap)
1771
+ // Same provably-local rule as the error generator above.
1772
+ if (typeof schema === 'object' && schema !== null) {
1773
+ const s = JSON.stringify(schema)
1774
+ // unevaluatedProperties and unevaluatedItems that are not provably local
1775
+ // are worked out at run time (genUnevaluatedDynamicC,
1776
+ // genUnevaluatedItemsDynamicC).
1777
+ // A schema that references itself, through `$ref: "#"` or a definition on
1778
+ // a cycle, has those references written as calls under the cycle guard
1779
+ // (emitDefFnC).
1780
+ // Bail on oneOf/anyOf — branch-collapse aggregation lives in genCodeE only.
1781
+ // Letting genCodeC emit the legacy "must match exactly one" placeholder
1782
+ // would short-circuit the ATA4001/ATA4002/ATA4003 contract. The boolean
1783
+ // fast path plus errFn fallback handles these schemas with collapse.
1784
+ // Branches are collapsed into one error by the error generator's emitter
1785
+ // (emitBranchCollapse). Its errors are plain, so the rich form, which
1786
+ // builds every error enriched where it fails, declines them.
1787
+ // In the rich form the collapsed error and its branch errors come out
1788
+ // plain (the branches are the error generator's) and are enriched when
1789
+ // presented; every other error is built enriched. See richBuilder.
1790
+ }
1791
+ if (typeof schema === 'boolean') {
1792
+ // A `false` root has one fixed error; the rich form leaves it to enrich.
1793
+ if (!schema && opts && (opts.rich || opts.resultShape)) return null
1794
+ if (opts && opts.resultShape) return null
1795
+ return schema
1796
+ ? () => VALID_RESULT
1797
+ : () => ({ valid: false, errors: [{ keyword: 'not', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
1798
+ }
1799
+ if (typeof schema !== 'object' || schema === null) return null
1800
+ if (!sharedCodegenGate(schema, schemaMap)) return null
1801
+ if (schema.patternProperties) {
1802
+ for (const [pat, sub] of Object.entries(schema.patternProperties)) {
1803
+ if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
1804
+ }
1805
+ }
1806
+ if (schema.dependentSchemas) {
1807
+ for (const sub of Object.values(schema.dependentSchemas)) {
1808
+ if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
1809
+ }
1810
+ }
1811
+ if (schema.propertyNames && typeof schema.propertyNames === 'object' && !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return null
1812
+
1813
+ // Build anchors map for $ref/#anchor and $dynamicRef resolution
1814
+ const cRootDefs = schema.$defs || schema.definitions || null
1815
+ const cAnchors = {}
1816
+ if (schema.$dynamicAnchor) cAnchors['#' + schema.$dynamicAnchor] = schema
1817
+ if (schema.$anchor) cAnchors['#' + schema.$anchor] = schema
1818
+ if (typeof schema.$id === 'string' && schema.$id.startsWith('#')) cAnchors[schema.$id] = schema
1819
+ if (cRootDefs) {
1820
+ for (const def of Object.values(cRootDefs)) {
1821
+ if (def && typeof def === 'object') {
1822
+ if (def.$dynamicAnchor) cAnchors['#' + def.$dynamicAnchor] = def
1823
+ if (def.$anchor) cAnchors['#' + def.$anchor] = def
1824
+ if (typeof def.$id === 'string' && def.$id.startsWith('#')) cAnchors[def.$id] = def
1825
+ }
1826
+ }
1827
+ }
1828
+ if (schemaMap) {
1829
+ for (const ext of schemaMap.values()) {
1830
+ if (ext && typeof ext === 'object') {
1831
+ if (ext.$dynamicAnchor && !cAnchors['#' + ext.$dynamicAnchor]) cAnchors['#' + ext.$dynamicAnchor] = ext
1832
+ if (ext.$anchor && !cAnchors['#' + ext.$anchor]) cAnchors['#' + ext.$anchor] = ext
1833
+ if (typeof ext.$id === 'string' && ext.$id.startsWith('#') && !cAnchors[ext.$id]) cAnchors[ext.$id] = ext
1834
+ }
1835
+ }
1836
+ }
1837
+
1838
+ // The same two bails the boolean generator makes. Without them this path
1839
+ // emits nothing for a reference it cannot resolve or follow, and an empty
1840
+ // program is returned below as `() => VALID_RESULT`, so every constraint
1841
+ // behind the reference is dropped instead of the schema routing to the
1842
+ // interpreted engine.
1843
+ if (hasUnresolvableRef(schema, cRootDefs, cAnchors, schemaMap, new Set())) return null
1844
+ if (needsBaseTracking(schema, schemaMap, new Set())) return null
1845
+
1846
+ // runtimeShape: errors without the ordinal, see compileToJSCodegenWithErrors.
1847
+ // `_ap` adds an error to a list that may not exist yet: the first one
1848
+ // creates it as a one-element literal. Pushing onto an empty `[]` grew its
1849
+ // backing store on every rejection, about 7% of reading errors across the
1850
+ // official suite.
1851
+ const ctx = { noCounting: !!(opts && opts.noCounting), 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,
1852
+ rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: inputSchema, userFormats: userFormats || null }
1853
+ const lines = []
1854
+ try {
1855
+ genCodeC(schema, 'd', '', lines, ctx, '#')
1856
+ } catch (e) {
1857
+ if (e === DECLINE) return null
1858
+ throw e
1859
+ }
1860
+ if (lines.length === 0) { if (hasConstraints(schema, false)) return null; return () => VALID_RESULT }
1861
+
1862
+ // Use factory pattern: closure vars (regexes, etc.) created once, not per call.
1863
+ // The helpers are constants too and are declared in the factory: inside the
1864
+ // function they were rebuilt on every call. `__ataSafeRe` is bound when a
1865
+ // helper compiles a pattern through it, which the boolean generator does for
1866
+ // subschemas under not/if/contains; without the binding those threw a
1867
+ // ReferenceError on the first document that reached them.
1868
+ if (opts && opts.resultShape) {
1869
+ ctx.closureVars.push('_ER', '_EE', '_SRT', '_FB', '_VF')
1870
+ ctx.closureVals.push(opts.resultShape.Rejection, opts.resultShape.empty, opts.resultShape.sort, opts.resultShape.fallback, opts.resultShape.verdict || null)
1871
+ }
1872
+ if (ctx.usesBranchCollapse) bindRt(ctx, ['__ataCollapse', '__ataMulti'])
1873
+ const closureNames = ctx.usesSafeRe ? [...ctx.closureVars, '__ataSafeRe'] : ctx.closureVars
1874
+ const closureArgs = ctx.usesSafeRe ? [...ctx.closureVals, compileSafe] : ctx.closureVals
1875
+ const closureParams = closureNames.join(',')
1876
+ // A definition the error generator wrote as a named function inside a
1877
+ // branch (one on a cycle, or referenced from several places) reads the
1878
+ // cycle-guard state the error function declares. Here that state is
1879
+ // declared too, and the checks run as the error function runs them: in a
1880
+ // body of their own, again in the guarded mode when data turns out to
1881
+ // point back at itself. This used to decline, which left every schema
1882
+ // whose oneOf or anyOf branches reach a shared definition without one
1883
+ // pass.
1884
+ const guardList = [...(ctx.defFns ? ctx.defFns.values() : []), ...(ctx.cycFnsC || [])]
1885
+ const guardDefs = guardList.length ? guardList : null
1886
+ if (guardDefs) ctx.helperCode.unshift(emitGuardState())
1887
+ // Collapsed oneOf/anyOf: the helper the error generator embeds, `_all` for
1888
+ // the branch functions (this function collects everything), and the
1889
+ // runtime shape the error generator gives its literals, without code and
1890
+ // docUrl. The collapsed error itself keeps its code, as everywhere.
1891
+ if (ctx.usesBranchCollapse && opts && opts.runtimeShape) {
1892
+ const shape = (src) => src.replace(/code:'ATA\d{4}',/g, '').replace(/,docUrl:'https:\/\/ata-validator\.com\/e\/ATA\d{4}'/g, '')
1893
+ for (let i = 0; i < ctx.helperCode.length; i++) ctx.helperCode[i] = shape(ctx.helperCode[i])
1894
+ for (let i = 0; i < lines.length; i++) lines[i] = shape(lines[i])
1895
+ }
1896
+ // Lazy error array: no allocation for valid data (the common case)
1897
+ // resultShape: validate() itself for a validator whose errors are being read
1898
+ // (see validator-core): the result objects built here, so one pass decides,
1899
+ // collects and answers, with nothing between the caller and the checks.
1900
+ const rs = opts && opts.resultShape
1901
+ // Counts shared between a node's unevaluatedProperties and its anyOf and
1902
+ // oneOf (genUnevaluatedDynamicC), visible to every line of the function.
1903
+ let hoistDecl = ctx.hoisted.length ? `let ${ctx.hoisted.map((n) => n + '=null').join(',')};` : ''
1904
+ let checks = lines.join('\n ')
1905
+ let allDecl = ctx.usesBranchCollapse ? 'const _all=true;' : ''
1906
+ if (guardDefs) {
1907
+ const clears = ['_stk.clear()'].concat(guardDefs.map((n) => `${n}_s.clear()`)).join(';')
1908
+ ctx.helperCode.push(`function _bodyC(d){${hoistDecl}${allDecl}let _e;\n ${checks}\n return _e}`)
1909
+ hoistDecl = ''
1910
+ allDecl = ''
1911
+ 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}}`
1912
+ }
1913
+ // In the result shape this function is validate() itself, so a document
1914
+ // that makes it throw is answered by the caller's previous path instead
1915
+ // (`_FB`). A thrown error reaching the caller is the one thing this form
1916
+ // must not add; a try that does not throw costs nothing measurable.
1917
+ const inner = rs
1918
+ // With collapsed branches, collecting runs every branch of every oneOf
1919
+ // and anyOf in full, which an accepted document does not need: the
1920
+ // verdict answers it first.
1921
+ // Not when every collapse is guarded by a count (emitBranchCollapse): an
1922
+ // accepted document then runs no branch twice, and a rejected one would
1923
+ // pay for the verdict for nothing.
1924
+ ? `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 +
1925
+ // A collapsed branch error is presented even alone (its ordinal and
1926
+ // raw branch errors stay inside); otherwise only a list of two or more
1927
+ // needs ordering.
1928
+ // The checked sort (_srX) compares with the reader, which a module
1929
+ // does not carry: there `_SRT` keeps the list as it is.
1930
+ `\n return _e?new _ER(_A.mo?_e:${env('ATA_CHECK_SORT') && !(opts && opts.emit) ? '_srX(_A,_e,_SRT)' : '_sr(_A,_e,_SRT)'}):${opts && opts.moduleShape ? 'R' : '{valid:true,data:d,errors:_EE}'}}catch(_x){return _FB(d)}`
1931
+ : hoistDecl + allDecl + `let _e;${AP_RESET}\n ` + checks + `\n return _e?{valid:false,errors:_e}:R`
1932
+ // The verdict generator's hoisted functions (subtreeGuard writes guards
1933
+ // through it) come first: a oneOf branch it could not hoist was a closure
1934
+ // built at every call of the guard, six per element of a json-patch list.
1935
+ const helpers = (ctx.preamble.length ? ctx.preamble.join('\n ') + '\n ' : '') + (ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : '')
1936
+
1937
+ try {
1938
+ if (env('ATA_DUMP_CODEGEN')) console.log('=== COMBINED CODEGEN ===\n' + helpers + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
1939
+ if (env('ATA_DUMP_PARTS')) {
1940
+ const cls = (t) => (t.match(/^\s*(?:const|function|let)\s+([A-Za-z_$]+?)(?=\d|\b|_)/) || [, 'other'])[1]
1941
+ const acc = {}
1942
+ for (const h of ctx.helperCode) { const k = 'helper:' + cls(h); acc[k] = (acc[k] || 0) + h.length }
1943
+ for (const h of ctx.preamble) { const k = 'preamble:' + cls(h); acc[k] = (acc[k] || 0) + h.length }
1944
+ acc.inner = inner.length
1945
+ console.log('=== COMBINED PARTS ===', JSON.stringify(Object.entries(acc).sort((a, b) => b[1] - a[1])))
1946
+ }
1947
+ const programSrc = assignOrdKeys(ctx, `${helpers}return function _vC(d){${inner}}`)
1948
+ const factory = compileFunction('R' + (closureParams ? ',' + closureParams : ''), programSrc)
1949
+ const made = factory(VALID_RESULT, ...closureArgs)
1950
+ // For a standalone module (lib/aot-impl.js): the program's source and the
1951
+ // closure values it takes, after the function was built and tried on
1952
+ // three values, so a program that throws at once is never emitted.
1953
+ if (opts && opts.emit) {
1954
+ made({}); made(null); made(0)
1955
+ return { source: programSrc, names: ['R', ...closureNames], values: [VALID_RESULT, ...closureArgs], usesSafeRe: !!ctx.usesSafeRe }
1956
+ }
1957
+ return made
1958
+ } catch (e) {
1959
+ if (env('ATA_DEBUG')) console.error('compileToJSCombined error:', e.message, '\n', inner.slice(0, 500))
1960
+ return null
1961
+ }
1962
+ }
1963
+
1964
+ // Combined code generator: type-aware like genCode, error-collecting like genCodeE.
1965
+ // After type check passes → use optimizations (destructuring, no guards).
1966
+ // If type check fails → push error, skip property checks (they'd crash).
1967
+ // What of a node still applies to a value that failed its `type`, for the
1968
+ // combined generator: the node without `type`, without what it emits outside
1969
+ // the type block anyway (enum, const, anyOf, oneOf), and without the keywords
1970
+ // of the declared types, which no value of another type meets. Null when
1971
+ // nothing that validates is left.
1972
+ const TYPE_KEYWORDS = {
1973
+ object: ['properties', 'patternProperties', 'additionalProperties', 'required', 'propertyNames', 'minProperties', 'maxProperties', 'dependentRequired', 'dependentSchemas', 'dependencies', 'unevaluatedProperties'],
1974
+ array: ['items', 'prefixItems', 'additionalItems', 'contains', 'minContains', 'maxContains', 'minItems', 'maxItems', 'uniqueItems', 'unevaluatedItems'],
1975
+ string: ['minLength', 'maxLength', 'pattern'],
1976
+ number: ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'],
1977
+ }
1978
+ TYPE_KEYWORDS.integer = TYPE_KEYWORDS.number
1979
+ const NOT_VALIDATING = new Set(['$schema', '$id', '$anchor', '$comment', '$defs', 'definitions', 'title', 'description', 'default', 'examples', 'deprecated', 'readOnly', 'writeOnly', '$vocabulary'])
1980
+ function typeMismatchRest(schema, types) {
1981
+ const drop = new Set(['type', 'enum', 'const', 'anyOf', 'oneOf'])
1982
+ for (const t of types) for (const k of (TYPE_KEYWORDS[t] || [])) drop.add(k)
1983
+ // A non-integer number reaches the type block already (numberToo).
1984
+ let rest = null
1985
+ for (const k of Object.keys(schema)) {
1986
+ if (drop.has(k)) continue
1987
+ if (rest === null) rest = {}
1988
+ setOwn(rest, k, schema[k])
1989
+ }
1990
+ if (rest === null) return null
1991
+ // A node whose reference was already written (genCodeCNode) keeps that
1992
+ // mark, or the reference is written again here and its errors twice.
1993
+ if (schema[REF_DONE]) Object.defineProperty(rest, REF_DONE, { value: true })
1994
+ for (const k of Object.keys(rest)) if (!NOT_VALIDATING.has(k) && !k.startsWith('x-')) return rest
1995
+ return null
1996
+ }
1997
+
1998
+ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
1999
+ return withPlain(schema, v, lines, ctx, () => genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix))
2000
+ }
2001
+ // A definition referenced from several places, written once as a function
2002
+ // and called at each reference, instead of its whole code at every one: the
2003
+ // combined function of SchemaStore's cloud-run (48 KB of schema) came to
2004
+ // 1.1 MB of source and sarif's (79 KB) to 2.8 MB, and compiling those was most
2005
+ // of the 50 to 300 ms a first rejection with its errors read took. The body is
2006
+ // generated against a placeholder schema path that becomes the `_sp` argument,
2007
+ // as the error generator does (genCodeENode), so an error names the path it
2008
+ // was reached by. It collects into its own list, returned to the caller, and
2009
+ // declares its own branch counts (genUnevaluatedDynamicC). The document root
2010
+ // comes in as `d` for the rich errors that read it. Not for a definition on a
2011
+ // reference cycle, which keeps its existing path. False when not shared.
2012
+ const COMBINED_DEF_FN_MIN = 160
2013
+ const _defOwnChars = new WeakMap()
2014
+ // The definition's own size, its annotations left out, measured once.
2015
+ function defOwnChars (def) {
2016
+ if (def === null || typeof def !== 'object') return 0
2017
+ let n = _defOwnChars.get(def)
2018
+ if (n === undefined) {
2019
+ n = 0
2020
+ 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]) } }
2021
+ walk(def)
2022
+ _defOwnChars.set(def, n)
2023
+ }
2024
+ return n
2025
+ }
2026
+
2027
+ function emitSharedDefC(defName, v, pathExpr, lines, ctx, schemaPrefix) {
2028
+ if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
2029
+ if (!ctx.sharedDefs) ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs)
2030
+ const cyclic = ctx.cyclicDefs.has(defName)
2031
+ // In the combined function every definition of some size is a function of
2032
+ // its own, referenced once or not: expanded, a 25-definition API schema
2033
+ // was one 122 KB function, which the optimizing compiler took tens of
2034
+ // thousands of calls to reach, so its first twenty thousand answers cost
2035
+ // 89 ns each where the hundred-thousandth costs 10. Functions the size of
2036
+ // one definition tier up early, and the hot ones first.
2037
+ if (!cyclic && !ctx.sharedDefs.has(defName) && !(ctx.inCombined && defOwnChars(ctx.rootDefs[defName]) >= COMBINED_DEF_FN_MIN)) return false
2038
+ emitDefFnC(defName, ctx.rootDefs[defName], cyclic, v, pathExpr, lines, ctx, schemaPrefix)
2039
+ return true
2040
+ }
2041
+
2042
+ // One schema as a function of its own in the combined function (see
2043
+ // emitSharedDefC). The name is registered before the body is written, so a
2044
+ // reference back to it inside becomes a call. One that can re-enter itself,
2045
+ // a definition on a cycle or the root through `$ref: "#"`, goes through the
2046
+ // cycle guard the error generator's definitions use: depth counted, and in
2047
+ // the guarded pass a value already being checked is not checked again.
2048
+ function emitDefFnC(key, target, cyclic, v, pathExpr, lines, ctx, schemaPrefix) {
2049
+ if (!ctx.defFnsC) ctx.defFnsC = new Map()
2050
+ let fnName = ctx.defFnsC.get(key)
2051
+ if (!fnName) {
2052
+ fnName = '_defC' + ctx.defFnsC.size + '_' + (key === '#' ? 'root' : key.replace(/[^A-Za-z0-9_]/g, '_'))
2053
+ ctx.defFnsC.set(key, fnName)
2054
+ const outerHoisted = ctx.hoisted
2055
+ ctx.hoisted = []
2056
+ // The body is a function of its own: a reference the caller was in the
2057
+ // middle of expanding is not on the stack inside it, where reaching it
2058
+ // again is a call (this function, or another one) rather than a loop.
2059
+ // With the caller's stack, a definition expanded once on the way into
2060
+ // the root's function was taken for a cycle inside it, and the schema
2061
+ // (a tree whose nodes hold trees) lost its one-pass function.
2062
+ const outerRefs = ctx.refStack
2063
+ ctx.refStack = new Set()
2064
+ const bodyLines = []
2065
+ let hoisted
2066
+ try {
2067
+ ctx.inDefC = (ctx.inDefC || 0) + 1
2068
+ genCodeC(target, '_dv', '_p', bodyLines, ctx, SP_PLACEHOLDER)
2069
+ } finally {
2070
+ ctx.inDefC--
2071
+ hoisted = ctx.hoisted
2072
+ ctx.hoisted = outerHoisted
2073
+ ctx.refStack = outerRefs
2074
+ }
2075
+ let body = bodyLines.join('\n ')
2076
+ body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
2077
+ if (body.includes(SP_PLACEHOLDER)) throw DECLINE
2078
+ const decl = hoisted.length ? `let ${hoisted.map((n) => n + '=null').join(',')};` : ''
2079
+ const fn = `function ${cyclic ? fnName + '_b' : fnName}(_dv,_p,_sp,d,_so){const _all=true;${decl}let _e;\n ${body}\n return _e}`
2080
+ if (cyclic) {
2081
+ if (!ctx.cycFnsC) ctx.cycFnsC = []
2082
+ ctx.cycFnsC.push(fnName)
2083
+ ctx.helperCode.push(
2084
+ `const ${fnName}_s=new Set()\n function ${fnName}(_dv,_p,_sp,d,_so){\n ` +
2085
+ `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 ` +
2086
+ `if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(_dv,_p,_sp,d,_so)\n _sd--\n return _r\n }\n ` + fn)
2087
+ } else {
2088
+ ctx.helperCode.push(fn)
2089
+ }
2090
+ }
2091
+ 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])}}`)
2092
+ }
2093
+
2094
+ const REF_DONE = Symbol('ata.refDone')
2095
+ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
2096
+ if (!schemaPrefix) schemaPrefix = '#'
2097
+ // Per-node, for the reason spelled out in genCode.
2098
+ let ppHandledPropertyNames = false
2099
+ if (schema === false) {
2100
+ // Leading semicolon: the previous generated line may end in an
2101
+ // expression, and a line starting with `(` would be parsed as a call.
2102
+ lines.push(';' + errPushC(ctx, 'not', schemaPrefix, pathExpr, '{}', "'boolean schema is false'", v, `{keyword:'not',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}'${ordinalField(ctx, `${schemaPrefix}`)},params:{},message:'boolean schema is false'}`))
2103
+ return
2104
+ }
2105
+ if (schema === true) return
2106
+ if (typeof schema !== 'object' || schema === null) return
2107
+
2108
+ // $ref — resolve local, anchor, and cross-schema refs
2109
+ if (schema.$ref && !schema[REF_DONE]) {
2110
+ // A keyword beside $ref that validates applies too (2020-12; draft 7
2111
+ // drops such siblings when the schema is read). The reference is written
2112
+ // on its own, then the node again with the reference marked done, on a
2113
+ // fresh copy so the mark cannot reach the same schema object met through
2114
+ // another path; the copy keeps `$ref` for unevaluatedProperties and
2115
+ // unevaluatedItems, which read its annotations. Dropping the siblings
2116
+ // once accepted what they reject, which is why this declined. Not in a
2117
+ // branch function, which the error generator writes.
2118
+ if (Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))) {
2119
+ if (!ctx.inCombined || ctx.inBranchFn) throw DECLINE
2120
+ genCodeCNode({ $ref: schema.$ref }, v, pathExpr, lines, ctx, schemaPrefix)
2121
+ const rest = { ...schema }
2122
+ Object.defineProperty(rest, REF_DONE, { value: true })
2123
+ genCodeCNode(rest, v, pathExpr, lines, ctx, schemaPrefix)
2124
+ return
2125
+ }
2126
+ // A reference back into what is being expanded is a call to that
2127
+ // schema's function (emitDefFnC), never nothing: an expansion that stops
2128
+ // and emits no lines reads as valid.
2129
+ if (schema.$ref === '#') {
2130
+ if (ctx.inCombined && !ctx.inBranchFn && ctx.rootC) { emitDefFnC('#', ctx.rootC, true, v, pathExpr, lines, ctx, schemaPrefix); return }
2131
+ throw DECLINE
2132
+ }
2133
+ const m = schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
2134
+ if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
2135
+ if (ctx.inCombined && !ctx.inBranchFn && emitSharedDefC(m[1], v, pathExpr, lines, ctx, schemaPrefix)) return
2136
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
2137
+ ctx.refStack.add(schema.$ref)
2138
+ genCodeC(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx, schemaPrefix)
2139
+ ctx.refStack.delete(schema.$ref)
2140
+ return
2141
+ }
2142
+ // Anchor ref: "#foo" — resolve via rootDefs or anchors map
2143
+ if (!m && schema.$ref.startsWith('#') && !schema.$ref.startsWith('#/')) {
2144
+ const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
2145
+ const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
2146
+ if (anchorTarget) {
2147
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
2148
+ ctx.refStack.add(schema.$ref)
2149
+ genCodeC(anchorTarget, v, pathExpr, lines, ctx, schemaPrefix)
2150
+ ctx.refStack.delete(schema.$ref)
2151
+ return
2152
+ }
2153
+ }
2154
+ if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
2155
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
2156
+ ctx.refStack.add(schema.$ref)
2157
+ genCodeC(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
2158
+ ctx.refStack.delete(schema.$ref)
2159
+ return
2160
+ }
2161
+ // Cross-schema ref with JSON pointer fragment ("<id>#/<path>")
2162
+ if (ctx.schemaMap && schema.$ref.includes('#') && !schema.$ref.startsWith('#')) {
2163
+ const r = resolveCrossSchemaRef(schema.$ref, ctx.schemaMap)
2164
+ if (r) {
2165
+ if (ctx.refStack.has(schema.$ref)) throw DECLINE
2166
+ ctx.refStack.add(schema.$ref)
2167
+ genCodeC(r.schema, v, pathExpr, lines, ctx, schemaPrefix)
2168
+ ctx.refStack.delete(schema.$ref)
2169
+ return
2170
+ }
2171
+ }
2172
+ }
2173
+
2174
+ // $dynamicRef — resolve via anchors map
2175
+ if (schema.$dynamicRef) genDynamicRefC(schema, v, pathExpr, lines, ctx, schemaPrefix)
2176
+
2177
+ const types = schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : null
2178
+ let isObj = false, isArr = false, isStr = false, isNum = false
2179
+
2180
+ // Pre-allocate error objects as closure variables for static paths.
2181
+ // This shrinks the generated function body → better V8 JIT on valid path.
2182
+ const isStaticPath = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
2183
+ const fail = (keyword, schemaSuffix, paramsCode, msgCode) => {
2184
+ const sp = schemaPrefix + '/' + schemaSuffix
2185
+ if (ctx.rich) {
2186
+ // A static site shares one frozen params object between calls, as its
2187
+ // hoisted literal does below; a dynamic one builds its params per call.
2188
+ let shared = null
2189
+ if (isStaticPath && msgCode.startsWith("'") && !msgCode.includes('+')) {
2190
+ let pv
2191
+ try { pv = Function('return ' + paramsCode)() } catch { pv = undefined }
2192
+ if (pv !== undefined) {
2193
+ shared = `_P${ctx.varCounter++}`
2194
+ ctx.closureVars.push(shared); ctx.closureVals.push(Object.freeze(pv))
2195
+ }
2196
+ }
2197
+ return errPushC(ctx, keyword, sp, pathExpr, paramsCode, msgCode, v, null, shared)
2198
+ }
2199
+ // Hoisting a static error as one shared literal keeps a function that
2200
+ // also answers accepted documents small. In the runtime shape this
2201
+ // function only runs for documents being rejected (the verdict is its own
2202
+ // function), and a shared literal has to be copied for the caller on
2203
+ // every read, so there it is written fresh.
2204
+ if (isStaticPath && !ctx.noOrdinal && msgCode.startsWith("'") && !msgCode.includes('+')) {
2205
+ // Try to evaluate paramsCode as a static constant
2206
+ let paramsVal
2207
+ try { paramsVal = Function('return ' + paramsCode)() } catch { /* dynamic params — fall through */ }
2208
+ if (paramsVal !== undefined) {
2209
+ // Static error: pre-allocate as frozen closure variable
2210
+ const ei = ctx.varCounter++
2211
+ const errVar = `_E${ei}`
2212
+ // The path and message are source literals; evaluate them rather than
2213
+ // strip the quotes, so an escaped quote or backslash in a key comes
2214
+ // out as the character and not as the escape.
2215
+ let pathVal, msgVal
2216
+ try {
2217
+ pathVal = pathExpr ? Function('return ' + pathExpr)() : ''
2218
+ msgVal = Function('return ' + msgCode)()
2219
+ } catch {
2220
+ // Not a literal that parses on its own (a key with a raw line break):
2221
+ // leave it to the inline form below, which declines the same way the
2222
+ // whole function would.
2223
+ pathVal = undefined
2224
+ }
2225
+ if (pathVal !== undefined) {
2226
+ const spVal = unescapeSp(sp)
2227
+ ctx.closureVars.push(errVar)
2228
+ const o = ctx.noOrdinal ? null : ordinalFor(ctx.rootSchema, spVal)
2229
+ const lit = {keyword, instancePath: pathVal, schemaPath: spVal, params: Object.freeze(paramsVal), message: msgVal}
2230
+ if (o !== null) lit._o = o
2231
+ ctx.closureVals.push(sharedErr(lit, ctx.rootSchema))
2232
+ return `(_e=_ap(_A,_e,${errVar},${apOrd(ctx, sp)}))`
2233
+ }
2234
+ }
2235
+ }
2236
+ // Dynamic path (e.g., array index): inline as before
2237
+ return `(_e=_ap(_A,_e,{keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode}${ordinalField(ctx, sp)}},${apOrd(ctx, sp)}))`
2238
+ }
2239
+
2240
+ // enum and const apply to a value of any type, so a value that fails
2241
+ // `type` is still checked against them, as the other engines do. They sat
2242
+ // inside the type-success block, and `{type: 'number', enum: [1]}` reported
2243
+ // only `type` for 'x' where the interpreter reports both.
2244
+ const emitEnumConst = () => {
2245
+ // enum
2246
+ if (schema.enum) {
2247
+ lines.push(`if(!(${enumCondition(ctx, schema.enum, v)})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
2248
+ }
2249
+
2250
+ // const
2251
+ if (schema.const !== undefined) {
2252
+ const cv = schema.const
2253
+ if (cv === null || typeof cv !== 'object') {
2254
+ lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
2255
+ } else {
2256
+ lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)})){${fail('const', 'const', `{allowedValue:${emitParamConstant(ctx, schema.const)}}`, "'must be equal to constant'")}}`)
2257
+ }
2258
+ }
2259
+ }
2260
+
2261
+ if (types) {
2262
+ const conds = types.map(t => {
2263
+ switch (t) {
2264
+ case 'object': return `(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
2265
+ case 'array': return `Array.isArray(${v})`
2266
+ case 'string': return `typeof ${v}==='string'`
2267
+ case 'number': return `Number.isFinite(${v})`
2268
+ case 'integer': return `Number.isInteger(${v})`
2269
+ case 'boolean': return `typeof ${v}==='boolean'`
2270
+ case 'null': return `${v}===null`
2271
+ default: return 'true'
2272
+ }
2273
+ })
2274
+ const expected = types.join(', ')
2275
+ // As written: a one-element array stays an array, as the interpreted engine
2276
+ // reports it.
2277
+ const expectedParam = !Array.isArray(schema.type) ? `'${types[0]}'` : JSON.stringify(types)
2278
+ // Type check: push error but continue — wrap remaining in type-success block
2279
+ const typeOk = `_tok${ctx.varCounter++}`
2280
+ lines.push(`const ${typeOk}=${conds.join('||')}`)
2281
+ lines.push(`if(!${typeOk}){${fail('type', 'type', `{type:${expectedParam}}`, `'must be ${expected}'`)}}`)
2282
+ // Subsequent optimized code runs inside if(typeOk){...}
2283
+ if (types.length === 1) {
2284
+ isObj = types[0] === 'object'
2285
+ isArr = types[0] === 'array'
2286
+ isStr = types[0] === 'string'
2287
+ isNum = types[0] === 'number' || types[0] === 'integer'
2288
+ }
2289
+ emitEnumConst()
2290
+ // A number that is not an integer fails `integer` and is still a number:
2291
+ // minimum, multipleOf and the rest apply to it, and the other engines
2292
+ // report them next to the type error. The block opens for any number.
2293
+ const numberToo = types.includes('integer') && !types.includes('number')
2294
+ lines.push(numberToo ? `if(${typeOk}||typeof ${v}==='number'){` : `if(${typeOk}){`)
2295
+ } else {
2296
+ emitEnumConst()
2297
+ }
2298
+
2299
+ // required — use destructuring when type is object (SAFE because type check already passed)
2300
+ const requiredSet = new Set(schema.required || [])
2301
+ const hoisted = {}
2302
+ if (schema.required && schema.properties && isObj) {
2303
+ const destructKeys = []
2304
+ for (const key of schema.required) {
2305
+ if (schema.properties[key]) {
2306
+ const lv = `_h${ctx.varCounter++}`
2307
+ hoisted[key] = lv
2308
+ destructKeys.push(`${JSON.stringify(key)}:${lv}`)
2309
+ }
2310
+ }
2311
+ if (destructKeys.length > 0) {
2312
+ lines.push(`let{${destructKeys.join(',')}}=${v}`)
2313
+ // A value the object only inherits reads as absent, here and in the
2314
+ // property checks that use the same local below.
2315
+ for (const key of schema.required) {
2316
+ if (hoisted[key]) lines.push(`if(${hoisted[key]}!==undefined&&!${ownGuard(ctx, v, key)})${hoisted[key]}=undefined`)
2317
+ }
2318
+ }
2319
+ for (const key of schema.required) {
2320
+ const check = hoisted[key] ? `${hoisted[key]}===undefined` : `!${ownKeyExpr(ctx, v, key)}`
2321
+ if (isStaticPath && !ctx.rich && !ctx.noOrdinal) {
2322
+ const ei = ctx.varCounter++
2323
+ const errVar = `_E${ei}`
2324
+ const pathVal = pathExpr ? literalValue(pathExpr) : ''
2325
+ ctx.closureVars.push(errVar)
2326
+ 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))
2327
+ lines.push(`if(${check}){(_e=_ap(_A,_e,${errVar},${apOrd(ctx, `${schemaPrefix}/required`)}))}`)
2328
+ } else {
2329
+ 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)}'"}`)}}`)
2330
+ }
2331
+ }
2332
+ } else if (schema.required) {
2333
+ for (const key of schema.required) {
2334
+ const isStatic = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
2335
+ if (isStatic && !ctx.rich && !ctx.noOrdinal) {
2336
+ const ei = ctx.varCounter++
2337
+ const errVar = `_E${ei}`
2338
+ const pathVal = pathExpr ? literalValue(pathExpr) : ''
2339
+ ctx.closureVars.push(errVar)
2340
+ 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))
2341
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!${ownKeyExpr(ctx, v, key)}){(_e=_ap(_A,_e,${errVar},${apOrd(ctx, `${schemaPrefix}/required`)}))}`)
2342
+ } else {
2343
+ 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)}'"}`)}}`)
2344
+ }
2345
+ }
2346
+ }
2347
+
2348
+ // numeric — skip type guard if known
2349
+ if (schema.minimum !== undefined) { const c = isNum ? `${v}<${schema.minimum}` : `typeof ${v}==='number'&&${v}<${schema.minimum}`; lines.push(`if(${c}){${fail('minimum', 'minimum', `{comparison:'>=',limit:${schema.minimum}}`, `'must be >= ${schema.minimum}'`)}}`) }
2350
+ if (schema.maximum !== undefined) { const c = isNum ? `${v}>${schema.maximum}` : `typeof ${v}==='number'&&${v}>${schema.maximum}`; lines.push(`if(${c}){${fail('maximum', 'maximum', `{comparison:'<=',limit:${schema.maximum}}`, `'must be <= ${schema.maximum}'`)}}`) }
2351
+ if (schema.exclusiveMinimum !== undefined) { const c = isNum ? `${v}<=${schema.exclusiveMinimum}` : `typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum}`; lines.push(`if(${c}){${fail('exclusiveMinimum', 'exclusiveMinimum', `{comparison:'>',limit:${schema.exclusiveMinimum}}`, `'must be > ${schema.exclusiveMinimum}'`)}}`) }
2352
+ if (schema.exclusiveMaximum !== undefined) { const c = isNum ? `${v}>=${schema.exclusiveMaximum}` : `typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum}`; lines.push(`if(${c}){${fail('exclusiveMaximum', 'exclusiveMaximum', `{comparison:'<',limit:${schema.exclusiveMaximum}}`, `'must be < ${schema.exclusiveMaximum}'`)}}`) }
2353
+ if (schema.multipleOf !== undefined) {
2354
+ const m = schema.multipleOf
2355
+ const ci = ctx.varCounter++
2356
+ lines.push(`{if(typeof ${v}==='number'&&${multipleOfBad(v, m)}){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
2357
+ }
2358
+
2359
+ // string length — s.length fast paths, _cpLen only in the uncertain band.
2360
+ if (schema.minLength !== undefined) {
2361
+ const M = schema.minLength
2362
+ const inner = `${v}.length<${M}||(${v}.length<${M * 2}&&_cpLen(${v})<${M})`
2363
+ const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
2364
+ lines.push(`if(${c}){${fail('minLength', 'minLength', `{limit:${M}}`, `'must NOT have fewer than ${M} characters'`)}}`)
2365
+ }
2366
+ if (schema.maxLength !== undefined) {
2367
+ const X = schema.maxLength
2368
+ const inner = `${v}.length>${X * 2}||(${v}.length>${X}&&_cpLen(${v})>${X})`
2369
+ const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
2370
+ lines.push(`if(${c}){${fail('maxLength', 'maxLength', `{limit:${X}}`, `'must NOT have more than ${X} characters'`)}}`)
2371
+ }
2372
+ if (schema.pattern) {
2373
+ const inlineCheck = compilePatternInline(schema.pattern, v)
2374
+ if (inlineCheck) {
2375
+ const c = isStr ? `!(${inlineCheck})` : `typeof ${v}==='string'&&!(${inlineCheck})`
2376
+ lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
2377
+ } else {
2378
+ const ri = ctx.varCounter++
2379
+ const reVar = `_re${ri}`
2380
+ ctx.closureVars.push(reVar)
2381
+ ctx.closureVals.push(useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern, reFlags(schema.pattern)))
2382
+ const c = isStr ? `!${reVar}.test(${v})` : `typeof ${v}==='string'&&!${reVar}.test(${v})`
2383
+ lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
2384
+ }
2385
+ }
2386
+ if (schema.format) {
2387
+ const fc = FORMAT_CODEGEN[schema.format]
2388
+ if (fc) {
2389
+ // Same rule as the error codegen: one violation, one error. The first
2390
+ // failing statement pushes and leaves the labeled block.
2391
+ const ri = ctx.varCounter++
2392
+ const code = fc(v, isStr, ctx).replace(/return false/g, `{${fail('format', 'format', `{format:'${esc(schema.format)}'}`, `'must match format "${esc(schema.format)}"'`)};break _fmt${ri}}`)
2393
+ lines.push(`_fmt${ri}:{${code}}`)
2394
+ } else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
2395
+ const safeName = schema.format.replace(/[^a-zA-Z0-9_]/g, '_')
2396
+ const closureName = `_uf_${safeName}`
2397
+ if (!ctx.closureVars.includes(closureName)) {
2398
+ ctx.closureVars.push(closureName)
2399
+ ctx.closureVals.push(ctx.userFormats[schema.format])
2400
+ }
2401
+ const guard = isStr ? '' : `typeof ${v}==='string'&&`
2402
+ lines.push(`if(${guard}!${closureName}(${v})){${fail('format', 'format', `{format:'${esc(schema.format)}'}`, `'must match format "${esc(schema.format)}"'`)}}`)
2403
+ }
2404
+ }
2405
+
2406
+ // array size
2407
+ if (schema.minItems !== undefined) { const c = isArr ? `${v}.length<${schema.minItems}` : `Array.isArray(${v})&&${v}.length<${schema.minItems}`; lines.push(`if(${c}){${fail('minItems', 'minItems', `{limit:${schema.minItems}}`, `'must NOT have fewer than ${schema.minItems} items'`)}}`) }
2408
+ if (schema.maxItems !== undefined) { const c = isArr ? `${v}.length>${schema.maxItems}` : `Array.isArray(${v})&&${v}.length>${schema.maxItems}`; lines.push(`if(${c}){${fail('maxItems', 'maxItems', `{limit:${schema.maxItems}}`, `'must NOT have more than ${schema.maxItems} items'`)}}`) }
2409
+
2410
+ // uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
2411
+ if (schema.uniqueItems) genUniqueItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr)
2412
+
2413
+ // object size
2414
+ if (schema.minProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${schema.minProperties}){${fail('minProperties', 'minProperties', `{limit:${schema.minProperties}}`, `'must NOT have fewer than ${schema.minProperties} properties'`)}}`)
2415
+ if (schema.maxProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${schema.maxProperties}){${fail('maxProperties', 'maxProperties', `{limit:${schema.maxProperties}}`, `'must NOT have more than ${schema.maxProperties} properties'`)}}`)
2416
+
2417
+ // additionalProperties — skip if patternProperties present (handled in unified loop below)
2418
+ // Small property sets: direct === chain (no Set allocation)
2419
+ if (schema.additionalProperties === false && !schema.patternProperties) {
2420
+ const propKeys = Object.keys(schema.properties || {})
2421
+ const ci = ctx.varCounter++
2422
+ // The error has to name the offending property, so the keys have to be
2423
+ // materialised to report one. They do not have to be materialised to find
2424
+ // out whether there is one: a for-in answers that with no allocation, and
2425
+ // an accepted document is the common case. The verdict generator pays a
2426
+ // bare key count here, and this kept the combined function 2.8x that on a
2427
+ // valid document, which is what stopped it from replacing the
2428
+ // verdict-then-revalidate pair on the error path.
2429
+ const detail = (test) =>
2430
+ `const _k${ci}=Object.keys(${v});for(let _i=0;_i<_k${ci}.length;_i++)if(${test}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:_k${ci}[_i]}`, "'must NOT have additional properties'")}}`
2431
+ let body
2432
+ if (propKeys.length <= 8) {
2433
+ // Direct chain: no Set allocation for small schemas
2434
+ // With nothing declared every key is additional: an empty chain would
2435
+ // leave `if()`, so the test is `true`.
2436
+ const gate = propKeys.map(k => `_f${ci}!==${JSON.stringify(k)}`).join('&&') || 'true'
2437
+ const checks = propKeys.map(k => `_k${ci}[_i]!==${JSON.stringify(k)}`).join('&&') || 'true'
2438
+ body = `let _x${ci}=0;for(const _f${ci} in ${v}){if(${gate}){_x${ci}=1;break}}if(_x${ci}){${detail(checks)}}`
2439
+ } else {
2440
+ // The name set lives in the closure, built once with the function: built
2441
+ // in the body it was a 33-name Set on every call of this function, which
2442
+ // is where a rejected npm-badges document spent most of its 701 ns.
2443
+ ctx.closureVars.push(`_a${ci}`)
2444
+ ctx.closureVals.push(new Set(propKeys))
2445
+ 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])`)}}`
2446
+ }
2447
+ // When every declared property is required, a clean object has exactly the
2448
+ // declared keys: if the enumerated count equals the declared count and
2449
+ // every declared name is there, the enumerated set is the declared set and
2450
+ // nothing is extra. That is the verdict generator's count, and it skips
2451
+ // comparing each key against every name, which kept this function over
2452
+ // twice the verdict's cost on an accepted document. Anything else, a
2453
+ // different count or a missing name, falls through to the scan, which
2454
+ // names the extra keys. A name Object.prototype carries answers `in`
2455
+ // through the prototype without being enumerated, so those stay on the
2456
+ // scan.
2457
+ const req = new Set(Array.isArray(schema.required) ? schema.required : [])
2458
+ if (propKeys.length > 0 && propKeys.every((k) => req.has(k) && !PROTO_NAMES.has(k))) {
2459
+ const present = propKeys.map((k) => `${JSON.stringify(k)} in ${v}`).join('&&')
2460
+ body = `let _n${ci}=0;for(const _f${ci} in ${v})_n${ci}++;if(_n${ci}!==${propKeys.length}||!(${present})){${body}}`
2461
+ }
2462
+ lines.push(isObj
2463
+ ? `{${body}}`
2464
+ : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body}}`)
2465
+ }
2466
+
2467
+ // unevaluatedProperties: false where it is provably additionalProperties
2468
+ // (the entry bail guaranteed that), keeping the keyword's own identity so
2469
+ // the two engines report the same error.
2470
+ if (schema.unevaluatedProperties !== undefined && schema.unevaluatedProperties !== true) {
2471
+ if (unevalLocalOk(schema, 'unevaluatedProperties')) {
2472
+ if (schema.additionalProperties === undefined && !schema.patternProperties) genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj)
2473
+ } else {
2474
+ genUnevaluatedDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj)
2475
+ }
2476
+ }
2477
+
2478
+ // unevaluatedItems: false under the same rule: one error for the array,
2479
+ // the first uncovered position as the limit. Adjacent schema-form `items`
2480
+ // evaluates everything; the draft-07 array form acts as the prefix.
2481
+ if (schema.unevaluatedItems !== undefined && schema.unevaluatedItems !== true) {
2482
+ if (unevalLocalOk(schema, 'unevaluatedItems')) genUnevaluatedItemsFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
2483
+ else genUnevaluatedItemsDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
2484
+ }
2485
+
2486
+ // dependentRequired
2487
+ if (schema.dependentRequired) {
2488
+ for (const [key, deps] of Object.entries(schema.dependentRequired)) {
2489
+ for (const dep of deps) {
2490
+ const isStatic = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
2491
+ if (isStatic && !ctx.rich && !ctx.noOrdinal) {
2492
+ const ei = ctx.varCounter++
2493
+ const errVar = `_E${ei}`
2494
+ const pathVal = pathExpr ? literalValue(pathExpr) : ''
2495
+ ctx.closureVars.push(errVar)
2496
+ 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))
2497
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){(_e=_ap(_A,_e,${errVar},-1))}`)
2498
+ } else {
2499
+ 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)}'"}`)}}`)
2500
+ }
2501
+ }
2502
+ }
2503
+ }
2504
+
2505
+ // properties — use hoisted vars for required+known-object, full guard otherwise
2506
+ if (schema.properties) {
2507
+ for (const [key, prop] of Object.entries(schema.properties)) {
2508
+ const pv = hoisted[key] || `${v}[${JSON.stringify(key)}]`
2509
+ const childPath = childPathExpr(pathExpr, ptrSeg(key))
2510
+ if (requiredSet.has(key) && isObj) {
2511
+ // A hoisted local was already cleared if the value is only inherited.
2512
+ lines.push(`if(${pv}!==undefined${hoisted[key] ? '' : '&&' + ownGuard(ctx, v, key)}){`)
2513
+ genGuardedC(prop, pv, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
2514
+ lines.push(`}`)
2515
+ } else if (isObj) {
2516
+ const oi = ctx.varCounter++
2517
+ lines.push(`{const _o${oi}=${v}[${JSON.stringify(key)}];if(_o${oi}!==undefined&&${ownGuard(ctx, v, key)}){`)
2518
+ genGuardedC(prop, `_o${oi}`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
2519
+ lines.push(`}}`)
2520
+ } else {
2521
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
2522
+ genGuardedC(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
2523
+ lines.push(`}`)
2524
+ }
2525
+ }
2526
+ }
2527
+
2528
+ // patternProperties — same optimizations as genCode: charCodeAt + inline key comparison + merged propertyNames
2529
+ if (schema.patternProperties) {
2530
+ if (genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj)) ppHandledPropertyNames = true
2531
+ }
2532
+ // additionalProperties as a schema: each key that is neither declared nor
2533
+ // matched by a pattern, validated where it is (see genAdditionalSchemaC).
2534
+ if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null) genAdditionalSchemaC(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj)
2535
+
2536
+
2537
+ // dependentSchemas
2538
+ if (schema.dependentSchemas) genDependentSchemasC(schema, v, pathExpr, lines, ctx, schemaPrefix)
2539
+
2540
+ if (schema.propertyNames === false) genPropertyNamesFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix)
2541
+
2542
+ // propertyNames — skip if already merged into patternProperties loop
2543
+ if (schema.propertyNames && typeof schema.propertyNames === 'object' && !ppHandledPropertyNames) genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
2544
+
2545
+ // items
2546
+ if (schema.items !== undefined && schema.items !== true) {
2547
+ const startIdx = schema.prefixItems ? schema.prefixItems.length : 0
2548
+ const idx = `_j${ctx.varCounter}`, elem = `_ei${ctx.varCounter}`
2549
+ ctx.varCounter++
2550
+ const childPath = childPathDynExpr(pathExpr, idx)
2551
+ lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
2552
+ genGuardedC(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
2553
+ lines.push(`}}`)
2554
+ }
2555
+
2556
+ // prefixItems
2557
+ if (schema.prefixItems) genPrefixItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix)
2558
+
2559
+ // contains
2560
+ if (schema.contains !== undefined) genContainsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
2561
+
2562
+ // allOf
2563
+ if (schema.allOf) { for (let _ai = 0; _ai < schema.allOf.length; _ai++) genCodeC(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai) }
2564
+
2565
+ // anyOf, oneOf. A collapsed branch applies to a value of any type, as enum
2566
+ // and const do: the other engines report it next to a failed `type`, so
2567
+ // in the combined function it goes after the type-success block below.
2568
+ if (schema.anyOf && !ctx.inCombined) genAnyOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
2569
+ if (schema.oneOf && !ctx.inCombined) genOneOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
2570
+
2571
+ // not
2572
+ if (schema.not !== undefined) genNotC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
2573
+
2574
+ // if/then/else
2575
+ if (schema.if !== undefined) genIfC(schema, v, pathExpr, lines, ctx, schemaPrefix)
2576
+
2577
+ // Close type-success block if opened
2578
+ if (types) {
2579
+ lines.push(`}`)
2580
+ // A value of another type still meets every keyword that applies to its
2581
+ // own type, and the applicators (allOf, not, if/then/else, $ref): the
2582
+ // other engines report those next to the type error, and the block above
2583
+ // skipped them all. The keywords of the declared types cannot apply to
2584
+ // it and are left out, so the else is small and does not repeat the
2585
+ // declared type's subtree.
2586
+ const rest = typeMismatchRest(schema, types)
2587
+ if (rest !== null) {
2588
+ lines.push(`else{`)
2589
+ genCodeC(rest, v, pathExpr, lines, ctx, schemaPrefix)
2590
+ lines.push(`}`)
2591
+ }
2592
+ }
2593
+ if (schema.anyOf && ctx.inCombined) genAnyOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
2594
+ if (schema.oneOf && ctx.inCombined) genOneOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
2595
+
2596
+ }
2597
+
2598
+ // Rarely used keyword families of genCodeCNode, kept out of it so their compile is
2599
+ // paid only by schemas that use them.
2600
+
2601
+ function genDynamicRefC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
2602
+ const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
2603
+ if (ctx.anchors && ctx.anchors[anchorKey]) {
2604
+ const target = ctx.anchors[anchorKey]
2605
+ // This generator has no named functions, so it cannot follow a reference
2606
+ // back to the root or around a cycle. It used to emit nothing in both
2607
+ // cases, which the function then answered as valid: a document the other
2608
+ // generators reject passed through validateJSON. It declines instead,
2609
+ // and the schema takes the error generator.
2610
+ if (target === ctx.rootSchema) throw DECLINE
2611
+ const refKey = '$dynamicRef:' + anchorKey
2612
+ if (ctx.refStack.has(refKey)) throw DECLINE
2613
+ ctx.refStack.add(refKey)
2614
+ genCodeC(target, v, pathExpr, lines, ctx, schemaPrefix)
2615
+ ctx.refStack.delete(refKey)
2616
+ }
2617
+ }
2618
+
2619
+ function genUniqueItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr) {
2620
+ const si = ctx.varCounter++
2621
+ const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
2622
+ // Only reached once the verdict has rejected, or when errors are wanted, so
2623
+ // one strategy for every item type: the hoisted pair finder. See UQP_HELPER.
2624
+ const inner = `const _j${si}=${emitUqp(ctx)}(${v});if(_j${si}>=0){const _i${si}=_uqpI;${failExpr('_i' + si, '_j' + si)}}`
2625
+ lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
2626
+ }
2627
+
2628
+ // unevaluatedProperties where what is evaluated depends on which branches
2629
+ // pass: the set is built at run time with the interpreted engine's rules.
2630
+ // The node's own properties, patternProperties and additionalProperties
2631
+ // count whether or not their values pass; an allOf, anyOf or dependentSchemas
2632
+ // branch, an if, then or else, or a $ref target counts only when it passes,
2633
+ // a oneOf only when exactly one branch passes, and `not` never. Each branch is
2634
+ // asked through an annotation function (emitAnnot).
2635
+ // Run-time unevaluatedProperties written as straight-line code. The names a
2636
+ // node and its in-place applicators can evaluate are known when the code is
2637
+ // written (their `properties`), so the evaluated set is a bit per name in an
2638
+ // integer, each branch's verdict a labelled block, and a failed oneOf's
2639
+ // rollback the integer it started from. The annotation functions this
2640
+ // replaces (emitAnnot) pushed names onto an array, asked it with `includes`
2641
+ // for every key, and were a call per branch per level: ten functions and
2642
+ // twenty calls for the suite's nested-reference case, which in the first
2643
+ // thousands of calls of a process, before anything is inlined, cost 548 ns
2644
+ // against 131. Null when the shape is not modelled: names that are only known
2645
+ // at run time (patternProperties, additionalProperties), a nested
2646
+ // unevaluatedProperties, a reference cycle, more than 30 names, or too much
2647
+ // code; the annotation functions then stand in as before.
2648
+ const ANNOT_INLINE_MAX = 8192
2649
+ const ANNOT_NAME_MAX = 30
2650
+ const ANNOT_BLOCKERS = ['unevaluatedItems', '$dynamicRef', '$recursiveRef', 'dependencies']
2651
+ // Besides a bit per declared name, the mask has a bit per pattern of a
2652
+ // patternProperties (set when the subschema holding it holds: the keys it
2653
+ // matches are evaluated) and one bit, `all`, for a subschema that evaluates
2654
+ // every key when it holds (additionalProperties of any value, or a nested
2655
+ // `unevaluatedProperties: true`). A nested `unevaluatedProperties` that
2656
+ // checks something is not modelled.
2657
+ const ALL_BIT_KEY = '\u0000all'
2658
+ function annotSubs (node, ctx) {
2659
+ const subs = []
2660
+ for (const b of node.allOf || []) subs.push(b)
2661
+ for (const b of node.anyOf || []) subs.push(b)
2662
+ for (const b of node.oneOf || []) subs.push(b)
2663
+ if (node.if !== undefined) subs.push(node.if)
2664
+ if (node.then !== undefined) subs.push(node.then)
2665
+ if (node.else !== undefined) subs.push(node.else)
2666
+ if (node.dependentSchemas && typeof node.dependentSchemas === 'object') for (const ds of Object.values(node.dependentSchemas)) subs.push(ds)
2667
+ if (typeof node.$ref === 'string') subs.push(refTargetFor(node.$ref, ctx))
2668
+ return subs
2669
+ }
2670
+ function annotNames (node, ctx, names, seen, top) {
2671
+ if (node === true || node === false) return true
2672
+ if (node === null || typeof node !== 'object' || Array.isArray(node)) return false
2673
+ if (seen.has(node)) return false
2674
+ seen.add(node)
2675
+ try {
2676
+ for (const k of ANNOT_BLOCKERS) if (node[k] !== undefined) return false
2677
+ if (!top && node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== true) return false
2678
+ const add = (key) => { if (!names.has(key)) { if (names.size >= ANNOT_NAME_MAX) return false; names.set(key, names.size) } return true }
2679
+ if (node.properties && typeof node.properties === 'object') for (const k of Object.keys(node.properties)) if (!add(k)) return false
2680
+ if (node.patternProperties && typeof node.patternProperties === 'object') for (const pat of Object.keys(node.patternProperties)) if (!add('/' + pat)) return false
2681
+ if ((!top && node.additionalProperties !== undefined) || (!top && node.unevaluatedProperties === true)) if (!add(ALL_BIT_KEY)) return false
2682
+ let subs
2683
+ try { subs = annotSubs(node, ctx) } catch { return false }
2684
+ for (const b of subs) if (!annotNames(b, ctx, names, seen, false)) return false
2685
+ return true
2686
+ } finally { seen.delete(node) }
2687
+ }
2688
+ // The bits a subschema's own keywords evaluate when it holds: its declared
2689
+ // names that are present, every pattern it declares, and `all` for a
2690
+ // subschema that evaluates every key.
2691
+ function ownBits (sub, v, names, ctx, t, top) {
2692
+ const out = []
2693
+ for (const k of Object.keys(sub.properties || {})) out.push(`if(${ownKeyExpr(ctx, v, k)})${t}|=${1 << names.get(k)}`)
2694
+ let fixed = 0
2695
+ for (const pat of Object.keys(sub.patternProperties || {})) fixed |= 1 << names.get('/' + pat)
2696
+ if (!top && (sub.additionalProperties !== undefined || sub.unevaluatedProperties === true)) fixed |= 1 << names.get(ALL_BIT_KEY)
2697
+ if (fixed !== 0) out.push(`${t}|=${fixed}`)
2698
+ return out
2699
+ }
2700
+ // The test for one pattern bit against a key variable, a closure regex or a
2701
+ // fixed prefix test (fastPrefixCheck) as addLocalAnnotations writes them.
2702
+ function patternKeyTest (ctx, pat, kv) {
2703
+ const fast = fastPrefixCheck(pat, kv)
2704
+ if (fast) return fast
2705
+ const ri = ctx.varCounter++
2706
+ ctx.closureVars.push(`_re${ri}`)
2707
+ ctx.closureVals.push(safeReClosure(ctx, pat))
2708
+ return `_re${ri}.test(${kv})`
2709
+ }
2710
+ // Factory-scope count variables for one anyOf or oneOf array (by identity),
2711
+ // registered in ctx.branchCounts for emitBranchCollapse: `of` the object
2712
+ // they were counted for, `count` how many branches held, `mask` which (oneOf
2713
+ // up to 30 branches). Declared once per program; a stale value is never
2714
+ // taken, since every reader checks `of` against the value at hand.
2715
+ function factoryCount (ctx, arr, withMask) {
2716
+ if (!ctx.inCombined) return null
2717
+ if (!ctx.branchCounts) ctx.branchCounts = new Map()
2718
+ const hit = ctx.branchCounts.get(arr)
2719
+ if (hit !== undefined) return hit.factory ? hit : null
2720
+ const id = ctx.varCounter++
2721
+ const fc = { of: `_fa${id}`, count: `_fn${id}`, mask: withMask ? `_fm${id}` : null, factory: true }
2722
+ ctx.helperCode.push(`let ${fc.of}=null,${fc.count}=0${withMask ? `,${fc.mask}=0` : ''}`)
2723
+ ctx.branchCounts.set(arr, fc)
2724
+ return fc
2725
+ }
2726
+ // Lines that set `ok` (declared by the caller, 1 or 0) for `sub` at `v`, an
2727
+ // object, and OR the names it evaluated into `tv` when it holds.
2728
+ function emitAnnotInline (sub, v, names, ctx, out, ok, tv) {
2729
+ if (sub === true) { out.push(`${ok}=1`); return }
2730
+ if (sub === false) { out.push(`${ok}=0`); return }
2731
+ const id = ctx.varCounter++
2732
+ const t = `_t${id}`
2733
+ const local = {}
2734
+ for (const k of Object.keys(sub)) if (!ANNOT_APPLICATORS.has(k)) setOwn(local, k, sub[k])
2735
+ const vl = []
2736
+ // The value is known to be an object here (the block runs for objects
2737
+ // only), which the verdict is told when the branch does not say otherwise:
2738
+ // told a type, the generator takes it for the value's and skips the
2739
+ // branch's own `type`, so a `type: "string"` branch has to keep its check.
2740
+ ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
2741
+ try { genCode(local, v, vl, ctx, local.type === undefined || local.type === 'object' ? 'object' : undefined) } finally { ctx.nestedBoolean-- }
2742
+ assertEmitted(local, vl.length, local.type === undefined || local.type === 'object')
2743
+ out.push(`let ${t}=0;${ok}=1`)
2744
+ if (vl.length) out.push(`a${id}:{${replaceTopLevel(vl.join(';'), `break a${id}`, `{${ok}=0;break a${id}}`)}}`)
2745
+ if (sub.not !== undefined) {
2746
+ const nl = []
2747
+ nestedGenCode(sub.not, v, nl, ctx)
2748
+ 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}`)
2749
+ else out.push(`${ok}=0`)
2750
+ }
2751
+ const own = ownBits(sub, v, names, ctx, t, false)
2752
+ if (own.length) out.push(`if(${ok}){${own.join(';')}}`)
2753
+ let bi = 0
2754
+ 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] }
2755
+ for (const b of sub.allOf || []) { const [o, tb] = child(b); out.push(`if(!${o})${ok}=0;else ${t}|=${tb}`) }
2756
+ // The counts of a nested anyOf or oneOf go to factory-scope variables as
2757
+ // well (factoryCount), where the keyword's own error code in a branch
2758
+ // function, and the counting copies, read them instead of deciding the
2759
+ // branches again: the suite's nested-reference case decided each branch
2760
+ // four times over (this pass, the verdict closures, the counting copies,
2761
+ // the chosen branch's error function).
2762
+ if (Array.isArray(sub.anyOf)) {
2763
+ const na = `_na${id}`
2764
+ const fc = factoryCount(ctx, sub.anyOf, false)
2765
+ out.push(`let ${na}=0${fc ? `;${fc.of}=${v};${fc.count}=0` : ''}`)
2766
+ for (const b of sub.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${t}|=${tb}${fc ? `;${fc.count}++` : ''}}`) }
2767
+ out.push(`if(${na}===0)${ok}=0`)
2768
+ }
2769
+ if (Array.isArray(sub.oneOf)) {
2770
+ const nc = `_nc${id}`, tc = `_tc${id}`
2771
+ const fc = factoryCount(ctx, sub.oneOf, sub.oneOf.length <= 30)
2772
+ out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
2773
+ 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}` : ''}` : ''}}`) })
2774
+ out.push(`if(${nc}===1)${t}|=${tc};else ${ok}=0`)
2775
+ }
2776
+ if (sub.if !== undefined) {
2777
+ const [oi, ti] = child(sub.if)
2778
+ const thenPart = []
2779
+ 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}`) }
2780
+ const elsePart = []
2781
+ 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}`) }
2782
+ out.push(`if(${oi}){${t}|=${ti};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
2783
+ }
2784
+ if (sub.dependentSchemas && typeof sub.dependentSchemas === 'object') {
2785
+ 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}}`) }
2786
+ }
2787
+ if (typeof sub.$ref === 'string') { const [o, tb] = child(refTargetFor(sub.$ref, ctx)); out.push(`if(!${o})${ok}=0;else ${t}|=${tb}`) }
2788
+ out.push(`if(${ok})${tv}|=${t}`)
2789
+ }
2790
+ // The whole run-time unevaluatedProperties block for `schema` at `v`, or
2791
+ // null. `counts` as in genUnevaluatedDynamicC: how many anyOf and oneOf
2792
+ // branches held, for the keywords' own code to read.
2793
+ function inlineUnevalBlock (schema, v, pathExpr, ctx, schemaPrefix, fail, counts) {
2794
+ const names = new Map()
2795
+ if (!annotNames(schema, ctx, names, new Set(), true)) return null
2796
+ const saved = snapshotCtx(ctx)
2797
+ try {
2798
+ const id = ctx.varCounter++
2799
+ const m = `_m${id}`
2800
+ const out = [`let ${m}=0`]
2801
+ // One prototype flag for the object, read by every presence test below.
2802
+ if (!ctx.plainOf) ctx.plainOf = new Map()
2803
+ const prevPk = ctx.plainOf.get(v)
2804
+ const pk = prevPk !== undefined ? prevPk : `_pk${id}`
2805
+ const prevReuse = ctx.plainReuse
2806
+ if (prevPk === undefined) { out.push(`const ${pk}=${protoIs(ctx, v)}`); ctx.plainOf.set(v, pk); (ctx.plainUsed || (ctx.plainUsed = new Set())).add(pk) }
2807
+ ctx.plainReuse = v
2808
+ out.push(...ownBits(schema, v, names, ctx, m, true))
2809
+ let bi = 0
2810
+ 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] }
2811
+ for (const b of schema.allOf || []) { const [, tb] = child(b); out.push(`${m}|=${tb}`) }
2812
+ if (Array.isArray(schema.anyOf)) {
2813
+ const na = counts ? counts.anyOf : `_na${id}`
2814
+ out.push(counts ? `${na}=0` : `let ${na}=0`)
2815
+ for (const b of schema.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${m}|=${tb}}`) }
2816
+ }
2817
+ if (Array.isArray(schema.oneOf)) {
2818
+ const nc = counts ? counts.oneOf : `_nc${id}`, tc = `_tc${id}`
2819
+ out.push(counts ? `${nc}=0;let ${tc}=0` : `let ${nc}=0,${tc}=0`)
2820
+ 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};` : ''}}`) })
2821
+ out.push(`if(${nc}===1)${m}|=${tc}`)
2822
+ }
2823
+ if (schema.if !== undefined) {
2824
+ const [oi, ti] = child(schema.if)
2825
+ const thenPart = []
2826
+ 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}`) }
2827
+ const elsePart = []
2828
+ 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}`) }
2829
+ out.push(`if(${oi}){${m}|=${ti};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
2830
+ }
2831
+ if (schema.dependentSchemas && typeof schema.dependentSchemas === 'object') {
2832
+ 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}}`) }
2833
+ }
2834
+ if (typeof schema.$ref === 'string') { const [o, tb] = child(refTargetFor(schema.$ref, ctx)); out.push(`if(${o})${m}|=${tb}`) }
2835
+ // What is left: every own key whose bit is not set.
2836
+ const kVar = `_uk${id}`
2837
+ const cases = [...names].filter(([k]) => k !== ALL_BIT_KEY && k[0] !== '/').map(([k, i]) => `case ${JSON.stringify(k)}:_ub${id}=${1 << i};break`).join(';')
2838
+ const allBit = names.has(ALL_BIT_KEY) ? `if((${m}&${1 << names.get(ALL_BIT_KEY)})!==0)continue;` : ''
2839
+ const patBits = [...names].filter(([k]) => k[0] === '/').map(([k, i]) => `if((${m}&${1 << i})!==0&&${patternKeyTest(ctx, k.slice(1), `_uk${id}`)})continue;`).join('')
2840
+ const up = schema.unevaluatedProperties
2841
+ let body
2842
+ if (up === false) {
2843
+ body = fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:${kVar}}`, "'must NOT have unevaluated properties'")
2844
+ } else if (typeof up === 'object' && up !== null) {
2845
+ const pe = emitPtrEsc(ctx)
2846
+ const p = pathExpr ? `${pathExpr}+'/'+${pe}(${kVar})` : `'/'+${pe}(${kVar})`
2847
+ const sub = []
2848
+ genCodeC(up, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/unevaluatedProperties')
2849
+ body = sub.join('\n ')
2850
+ } else {
2851
+ throw DECLINE
2852
+ }
2853
+ 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}}`)
2854
+ const text = out.join(';')
2855
+ ctx.plainReuse = prevReuse
2856
+ if (prevPk === undefined) ctx.plainOf.delete(v)
2857
+ if (text.length > ANNOT_INLINE_MAX || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(text)) { restoreCtx(ctx, saved); return null }
2858
+ return text
2859
+ } catch (e) {
2860
+ if (e !== DECLINE) throw e
2861
+ restoreCtx(ctx, saved)
2862
+ return null
2863
+ }
2864
+ }
2865
+
2866
+ function genUnevaluatedDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
2867
+ // additionalProperties evaluates every key the others do not, pass or fail.
2868
+ if (schema.additionalProperties !== undefined) return
2869
+ const ui = ctx.varCounter++
2870
+ const S = `_ev${ui}`
2871
+ const body = [`const ${S}=[]`]
2872
+ addLocalAnnotations(schema, v, S, body, ctx)
2873
+ // The anyOf and oneOf counts are handed to the keywords' own error code
2874
+ // (emitBranchCollapse), with the object they were counted for: they are
2875
+ // declared once at the top of the combined function, where every later
2876
+ // line sees them, and read only when that object is the value at hand, so
2877
+ // a count left from another value, another loop iteration or another place
2878
+ // is never taken for this one. Only in the combined function proper.
2879
+ const share = ctx.inCombined && !ctx.inBranchFn && ctx.hoisted !== undefined &&
2880
+ (Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf))
2881
+ const counts = share ? { anyOf: `_ac${ui}`, oneOf: `_oc${ui}`, oneOfMask: `_om${ui}`, of: `_ao${ui}` } : null
2882
+ if (share) {
2883
+ ctx.hoisted.push(counts.anyOf, counts.oneOf, counts.oneOfMask, counts.of)
2884
+ body.push(`${counts.of}=${v};${counts.oneOfMask}=0`)
2885
+ if (!ctx.branchCounts) ctx.branchCounts = new Map()
2886
+ if (Array.isArray(schema.anyOf)) ctx.branchCounts.set(schema.anyOf, { of: counts.of, count: counts.anyOf, mask: null })
2887
+ if (Array.isArray(schema.oneOf)) ctx.branchCounts.set(schema.oneOf, { of: counts.of, count: counts.oneOf, mask: schema.oneOf.length <= 30 ? counts.oneOfMask : null })
2888
+ }
2889
+ const inline = inlineUnevalBlock(schema, v, pathExpr, ctx, schemaPrefix, fail, counts)
2890
+ if (inline !== null) {
2891
+ const pre = share ? `${counts.of}=${v};${counts.oneOfMask}=0;` : ''
2892
+ lines.push(isObj ? `{${pre}${inline}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${pre}${inline}}`)
2893
+ return
2894
+ }
2895
+ addTopApplicatorAnnotations(schema, v, S, body, ctx, counts)
2896
+ const kVar = `_uk${ui}`
2897
+ const up = schema.unevaluatedProperties
2898
+ if (up === false) {
2899
+ body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:${kVar}}`, "'must NOT have unevaluated properties'")}}`)
2900
+ } else if (typeof up === 'object' && up !== null) {
2901
+ const pe = emitPtrEsc(ctx)
2902
+ const p = pathExpr ? `${pathExpr}+'/'+${pe}(${kVar})` : `'/'+${pe}(${kVar})`
2903
+ const sub = []
2904
+ genCodeC(up, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/unevaluatedProperties')
2905
+ if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${sub.join('\n ')}\n }`)
2906
+ } else {
2907
+ throw DECLINE
2908
+ }
2909
+ lines.push(isObj ? `{${body.join(';')}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body.join(';')}}`)
2910
+ }
2911
+
2912
+ // unevaluatedItems worked out at run time, for the combined function. The
2913
+ // evaluated positions of an array are kept as the interpreter keeps them
2914
+ // (lib/plan-compiler.js): `n`, every index below it evaluated, and `x`, the
2915
+ // indexes past it that `contains` matched. This node's own prefixItems,
2916
+ // items and contains mark them whatever their own verdict, and each in-place
2917
+ // subschema marks them only when it passes (emitAnnotI); what is left must
2918
+ // pass unevaluatedItems. `false` reports once, at the first position left,
2919
+ // as the interpreter does.
2920
+ // Run-time unevaluatedItems written as straight-line code, the way
2921
+ // inlineUnevalBlock writes unevaluatedProperties. Without `contains` the
2922
+ // positions a node and its in-place applicators evaluate are a prefix: items
2923
+ // evaluates every position, prefixItems the first p, a nested
2924
+ // `unevaluatedItems: true` every position once its subschema holds; the
2925
+ // record is one integer, merged with max where a branch holds, and what is
2926
+ // left starts at that index. `contains` (positions that are not a prefix), a
2927
+ // tuple `items`, a nested unevaluatedItems that checks something, dynamic
2928
+ // references and reference cycles keep the annotation functions.
2929
+ const ANNOT_I_BLOCKERS = ['contains', 'additionalItems', '$dynamicRef', '$recursiveRef', 'dependencies']
2930
+ function annotItemsFit (node, ctx, seen, top) {
2931
+ if (node === true || node === false) return true
2932
+ if (node === null || typeof node !== 'object' || Array.isArray(node)) return false
2933
+ if (seen.has(node)) return false
2934
+ seen.add(node)
2935
+ try {
2936
+ for (const k of ANNOT_I_BLOCKERS) if (node[k] !== undefined) return false
2937
+ if (Array.isArray(node.items)) return false
2938
+ if (!top && node.unevaluatedItems !== undefined && node.unevaluatedItems !== true) return false
2939
+ if (node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== true) return false
2940
+ let subs
2941
+ try { subs = annotSubs(node, ctx) } catch { return false }
2942
+ for (const b of subs) if (!annotItemsFit(b, ctx, seen, false)) return false
2943
+ return true
2944
+ } finally { seen.delete(node) }
2945
+ }
2946
+ // The positions a node's own keywords evaluate, as an expression of the
2947
+ // array length `len`: null when none.
2948
+ function ownPrefix (node, len, top) {
2949
+ if (node.items !== undefined) return len
2950
+ if (!top && node.unevaluatedItems === true) return len
2951
+ if (Array.isArray(node.prefixItems) && node.prefixItems.length) return `(${len}<${node.prefixItems.length}?${len}:${node.prefixItems.length})`
2952
+ return null
2953
+ }
2954
+ // Lines that set `ok` for `sub` at `v`, an array of length `len`, and raise
2955
+ // `tn` to the positions it evaluated when it holds.
2956
+ function emitAnnotInlineI (sub, v, len, ctx, out, ok, tn) {
2957
+ if (sub === true) { out.push(`${ok}=1`); return }
2958
+ if (sub === false) { out.push(`${ok}=0`); return }
2959
+ const id = ctx.varCounter++
2960
+ const t = `_t${id}`
2961
+ const local = {}
2962
+ for (const k of Object.keys(sub)) if (!ANNOT_ITEM_APPLICATORS.has(k)) setOwn(local, k, sub[k])
2963
+ const vl = []
2964
+ ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
2965
+ try { genCode(local, v, vl, ctx, local.type === undefined || local.type === 'array' ? 'array' : undefined) } finally { ctx.nestedBoolean-- }
2966
+ assertEmitted(local, vl.length, local.type === undefined || local.type === 'array')
2967
+ out.push(`let ${t}=0;${ok}=1`)
2968
+ if (vl.length) out.push(`a${id}:{${replaceTopLevel(vl.join(';'), `break a${id}`, `{${ok}=0;break a${id}}`)}}`)
2969
+ if (sub.not !== undefined) {
2970
+ const nl = []
2971
+ nestedGenCode(sub.not, v, nl, ctx)
2972
+ 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}`)
2973
+ else out.push(`${ok}=0`)
2974
+ }
2975
+ const own = ownPrefix(sub, len, false)
2976
+ if (own !== null) out.push(`if(${ok})${t}=${own}`)
2977
+ let bi = 0
2978
+ 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] }
2979
+ const raise = (tb) => `if(${tb}>${t})${t}=${tb}`
2980
+ for (const b of sub.allOf || []) { const [o, tb] = child(b); out.push(`if(!${o})${ok}=0;else ${raise(tb)}`) }
2981
+ if (Array.isArray(sub.anyOf)) {
2982
+ const na = `_na${id}`
2983
+ const fc = factoryCount(ctx, sub.anyOf, false)
2984
+ out.push(`let ${na}=0${fc ? `;${fc.of}=${v};${fc.count}=0` : ''}`)
2985
+ for (const b of sub.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${raise(tb)}${fc ? `;${fc.count}++` : ''}}`) }
2986
+ out.push(`if(${na}===0)${ok}=0`)
2987
+ }
2988
+ if (Array.isArray(sub.oneOf)) {
2989
+ const nc = `_nc${id}`, tc = `_tc${id}`
2990
+ const fc = factoryCount(ctx, sub.oneOf, sub.oneOf.length <= 30)
2991
+ out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
2992
+ 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}` : ''}` : ''}}`) })
2993
+ out.push(`if(${nc}===1){${raise(tc)}}else ${ok}=0`)
2994
+ }
2995
+ if (sub.if !== undefined) {
2996
+ const [oi, ti] = child(sub.if)
2997
+ const thenPart = []
2998
+ 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)}`) }
2999
+ const elsePart = []
3000
+ 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)}`) }
3001
+ out.push(`if(${oi}){${raise(ti)};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
3002
+ }
3003
+ if (typeof sub.$ref === 'string') { const [o, tb] = child(refTargetFor(sub.$ref, ctx)); out.push(`if(!${o})${ok}=0;else ${raise(tb)}`) }
3004
+ out.push(`if(${ok}&&${t}>${tn})${tn}=${t}`)
3005
+ }
3006
+ function inlineUnevalItemsBlock (schema, v, pathExpr, ctx, schemaPrefix, fail) {
3007
+ if (!ctx.inCombined || !annotItemsFit(schema, ctx, new Set(), true)) return null
3008
+ const saved = snapshotCtx(ctx)
3009
+ try {
3010
+ const id = ctx.varCounter++
3011
+ const n = `_un${id}`, len = `_ul${id}`
3012
+ const out = [`const ${len}=${v}.length;let ${n}=0`]
3013
+ const own = ownPrefix(schema, len, true)
3014
+ if (own !== null) out.push(`${n}=${own}`)
3015
+ let bi = 0
3016
+ 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] }
3017
+ const raise = (tb) => `if(${tb}>${n})${n}=${tb}`
3018
+ for (const b of schema.allOf || []) { const [, tb] = child(b); out.push(raise(tb)) }
3019
+ if (Array.isArray(schema.anyOf)) {
3020
+ const fc = factoryCount(ctx, schema.anyOf, false)
3021
+ if (fc) out.push(`${fc.of}=${v};${fc.count}=0`)
3022
+ for (const b of schema.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${raise(tb)}${fc ? `;${fc.count}++` : ''}}`) }
3023
+ }
3024
+ if (Array.isArray(schema.oneOf)) {
3025
+ const fc = factoryCount(ctx, schema.oneOf, schema.oneOf.length <= 30)
3026
+ const nc = `_nc${id}`, tc = `_tc${id}`
3027
+ out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
3028
+ 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}` : ''}` : ''}}`) })
3029
+ out.push(`if(${nc}===1)${raise(tc)}`)
3030
+ }
3031
+ if (schema.if !== undefined) {
3032
+ const [oi, ti] = child(schema.if)
3033
+ const thenPart = []
3034
+ 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)}`) }
3035
+ const elsePart = []
3036
+ 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)}`) }
3037
+ out.push(`if(${oi}){${raise(ti)};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
3038
+ }
3039
+ if (typeof schema.$ref === 'string') { const [o, tb] = child(refTargetFor(schema.$ref, ctx)); out.push(`if(${o})${raise(tb)}`) }
3040
+ const iv = `_ix${id}`
3041
+ const ui2 = schema.unevaluatedItems
3042
+ if (ui2 === false) {
3043
+ // Reported once, at the first position left, as the interpreter does.
3044
+ out.push(`if(${n}<${len}){const ${iv}=${n};${fail('unevaluatedItems', 'unevaluatedItems', `{limit:${iv}}`, `'must NOT have more than '+${iv}+' items'`)}}`)
3045
+ } else if (typeof ui2 === 'object' && ui2 !== null) {
3046
+ const pe = pathExpr ? `${pathExpr}+'/'+${iv}` : `'/'+${iv}`
3047
+ const sub = []
3048
+ genCodeC(ui2, `${v}[${iv}]`, pe, sub, ctx, schemaPrefix + '/unevaluatedItems')
3049
+ if (sub.length) out.push(`for(let ${iv}=${n};${iv}<${len};${iv}++){${sub.join('\n ')}\n }`)
3050
+ } else {
3051
+ throw DECLINE
3052
+ }
3053
+ const text = out.join(';')
3054
+ if (text.length > ANNOT_INLINE_MAX || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(text)) { restoreCtx(ctx, saved); return null }
3055
+ return text
3056
+ } catch (e) {
3057
+ if (e !== DECLINE) throw e
3058
+ restoreCtx(ctx, saved)
3059
+ return null
3060
+ }
3061
+ }
3062
+
3063
+ function genUnevaluatedItemsDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
3064
+ if (schema.$dynamicRef !== undefined || schema.$recursiveRef !== undefined) throw DECLINE
3065
+ if (Array.isArray(schema.items) || schema.additionalItems !== undefined) throw DECLINE
3066
+ const inline = inlineUnevalItemsBlock(schema, v, pathExpr, ctx, schemaPrefix, fail)
3067
+ if (inline !== null) { lines.push(`if(Array.isArray(${v})){${inline}}`); return }
3068
+ annItemHelpers(ctx)
3069
+ const ui = ctx.varCounter++
3070
+ const S = `_es${ui}`
3071
+ const body = [`const ${S}={n:0,x:null}`]
3072
+ addLocalItemAnnotations(schema, v, S, body, ctx)
3073
+ for (const b of schema.allOf || []) body.push(`${emitAnnotI(b, ctx)}(${v},${S})`)
3074
+ if (Array.isArray(schema.anyOf)) for (const b of schema.anyOf) body.push(`${emitAnnotI(b, ctx)}(${v},${S})`)
3075
+ if (Array.isArray(schema.oneOf)) {
3076
+ body.push(`{const _n0=${S}.n,_x0=${S}.x===null?0:${S}.x.length;let _c=0;${schema.oneOf.map((b) => `if(${emitAnnotI(b, ctx)}(${v},${S}))_c++;`).join('')}if(_c!==1)_rbI(${S},_n0,_x0)}`)
3077
+ }
3078
+ if (schema.if !== undefined) {
3079
+ const thenCall = schema.then !== undefined ? `${emitAnnotI(schema.then, ctx)}(${v},${S})` : ''
3080
+ const elseCall = schema.else !== undefined ? `${emitAnnotI(schema.else, ctx)}(${v},${S})` : ''
3081
+ body.push(`if(${emitAnnotI(schema.if, ctx)}(${v},${S})){${thenCall}}else{${elseCall}}`)
3082
+ }
3083
+ if (typeof schema.$ref === 'string') body.push(`${emitAnnotI(refTargetFor(schema.$ref, ctx), ctx)}(${v},${S})`)
3084
+ const iv = `_ix${ui}`
3085
+ const ui2 = schema.unevaluatedItems
3086
+ if (ui2 === false) {
3087
+ const failI = fail('unevaluatedItems', 'unevaluatedItems', `{limit:${iv}}`, `'must NOT have more than '+${iv}+' items'`)
3088
+ body.push(`for(let ${iv}=0;${iv}<${v}.length;${iv}++){if(_hI(${S},${iv}))continue;${failI};break}`)
3089
+ } else if (typeof ui2 === 'object' && ui2 !== null) {
3090
+ const pe = pathExpr ? `${pathExpr}+'/'+${iv}` : `'/'+${iv}`
3091
+ const sub = []
3092
+ genCodeC(ui2, `${v}[${iv}]`, pe, sub, ctx, schemaPrefix + '/unevaluatedItems')
3093
+ if (sub.length) body.push(`for(let ${iv}=0;${iv}<${v}.length;${iv}++){if(_hI(${S},${iv}))continue;${sub.join('\n ')}\n }`)
3094
+ } else {
3095
+ throw DECLINE
3096
+ }
3097
+ lines.push(`if(Array.isArray(${v})){${body.join(';')}}`)
3098
+ }
3099
+
3100
+ function annItemHelpers(ctx) {
3101
+ if (ctx.annItemHelpers) return
3102
+ ctx.annItemHelpers = true
3103
+ ;(ctx.preamble || ctx.helperCode).push(
3104
+ 'function _aI(S,i){if(i<S.n)return;if(i===S.n){S.n=i+1;return}(S.x||(S.x=[])).push(i)}' +
3105
+ 'function _mrI(S,f,t){if(f>=t)return;if(f<=S.n){if(t>S.n)S.n=t;return}const x=S.x||(S.x=[]);for(let i=f;i<t;i++)x.push(i)}' +
3106
+ 'function _hI(S,i){if(i<S.n)return true;const x=S.x;if(x===null)return false;for(let j=0;j<x.length;j++)if(x[j]===i)return true;return false}' +
3107
+ 'function _rbI(S,n,l){S.n=n;if(S.x!==null)S.x.length=l}' +
3108
+ 'function _anIT(){return true}function _anIF(){return false}')
3109
+ }
3110
+
3111
+ // The positions a node's own keywords evaluate, marked on `S`, for an array.
3112
+ function addLocalItemAnnotations(node, v, S, out, ctx) {
3113
+ if (Array.isArray(node.items) || node.additionalItems !== undefined) throw DECLINE
3114
+ const prefix = Array.isArray(node.prefixItems) ? node.prefixItems.length : 0
3115
+ if (prefix) out.push(`_mrI(${S},0,Math.min(${v}.length,${prefix}))`)
3116
+ if (node.items !== undefined) out.push(`_mrI(${S},${prefix},${v}.length)`)
3117
+ if (node.contains !== undefined) {
3118
+ const cl = []
3119
+ nestedGenCode(node.contains, '_cv', cl, ctx)
3120
+ const ci = ctx.varCounter++
3121
+ ;(ctx.preamble || ctx.helperCode).push(`function _cI${ci}(_cv){${cl.join('\n ')}\n return true}`)
3122
+ out.push(`for(let _i=0;_i<${v}.length;_i++)if(_cI${ci}(${v}[_i]))_aI(${S},_i)`)
3123
+ }
3124
+ }
3125
+
3126
+ // The keywords an item annotation function evaluates through its children;
3127
+ // the rest of a subschema is checked by a plain verdict.
3128
+ const ANNOT_ITEM_APPLICATORS = new Set(['allOf', 'anyOf', 'oneOf', 'if', 'then', 'else', '$ref', 'not', 'unevaluatedItems'])
3129
+
3130
+ // A function `(value, S) => valid` for one subschema, marking on S the array
3131
+ // positions it evaluated when it passes, and leaving S as it was when it does
3132
+ // not. A subschema with an unevaluatedItems of its own evaluates every
3133
+ // position once that passes, so it works on a record of its own and then
3134
+ // marks them all.
3135
+ function emitAnnotI(sub, ctx) {
3136
+ if (sub === true) return '_anIT'
3137
+ if (sub === false) return '_anIF'
3138
+ if (sub === null || typeof sub !== 'object') throw DECLINE
3139
+ if (!ctx.annFnsI) ctx.annFnsI = new Map()
3140
+ const hit = ctx.annFnsI.get(sub)
3141
+ if (hit !== undefined) {
3142
+ if (hit === null) throw DECLINE
3143
+ return hit
3144
+ }
3145
+ if (sub.$dynamicRef !== undefined || sub.$recursiveRef !== undefined) throw DECLINE
3146
+ if (Array.isArray(sub.items) || sub.additionalItems !== undefined) throw DECLINE
3147
+ // unevaluatedProperties reads the applicators too, which the local verdict
3148
+ // below leaves out.
3149
+ if (sub.unevaluatedProperties !== undefined && sub.unevaluatedProperties !== true) throw DECLINE
3150
+ ctx.annFnsI.set(sub, null)
3151
+ annItemHelpers(ctx)
3152
+ const name = `_ai${ctx.varCounter++}`
3153
+ const local = {}
3154
+ for (const k of Object.keys(sub)) if (!ANNOT_ITEM_APPLICATORS.has(k)) setOwn(local, k, sub[k])
3155
+ const vl = []
3156
+ nestedGenCode(local, '_av', vl, ctx)
3157
+ const decls = [`function ${name}_v(_av){${vl.join('\n ')}\n return true}`]
3158
+ const body = [`if(!${name}_v(d))return false`]
3159
+ if (sub.not !== undefined) {
3160
+ const nl = []
3161
+ nestedGenCode(sub.not, '_nv', nl, ctx)
3162
+ decls.push(`function ${name}_n(_nv){${nl.join('\n ')}\n return true}`)
3163
+ body.push(`if(${name}_n(d))return false`)
3164
+ }
3165
+ const own = sub.unevaluatedItems !== undefined && sub.unevaluatedItems !== true
3166
+ const R = own ? '_R' : 'S'
3167
+ body.push(own ? `const _R={n:0,x:null}` : `const _n0=S.n,_x0=S.x===null?0:S.x.length`)
3168
+ const failOut = own ? 'return false' : '{_rbI(S,_n0,_x0);return false}'
3169
+ const locals = []
3170
+ addLocalItemAnnotations(sub, 'd', R, locals, ctx)
3171
+ if (locals.length) body.push(`if(Array.isArray(d)){${locals.join(';')}}`)
3172
+ for (const b of sub.allOf || []) body.push(`if(!${emitAnnotI(b, ctx)}(d,${R}))${failOut}`)
3173
+ 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}}`)
3174
+ 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}}`)
3175
+ if (sub.if !== undefined) {
3176
+ const thenCheck = sub.then !== undefined ? `if(!${emitAnnotI(sub.then, ctx)}(d,${R}))${failOut}` : ''
3177
+ const elseCheck = sub.else !== undefined ? `if(!${emitAnnotI(sub.else, ctx)}(d,${R}))${failOut}` : ''
3178
+ body.push(`if(${emitAnnotI(sub.if, ctx)}(d,${R})){${thenCheck}}else{${elseCheck}}`)
3179
+ }
3180
+ if (typeof sub.$ref === 'string') body.push(`if(!${emitAnnotI(refTargetFor(sub.$ref, ctx), ctx)}(d,${R}))${failOut}`)
3181
+ if (own) {
3182
+ const u = sub.unevaluatedItems
3183
+ if (u === false) {
3184
+ body.push(`if(Array.isArray(d)){for(let _i=0;_i<d.length;_i++)if(!_hI(_R,_i))return false}`)
3185
+ } else if (typeof u === 'object' && u !== null) {
3186
+ const ul = []
3187
+ nestedGenCode(u, '_uv', ul, ctx)
3188
+ decls.push(`function ${name}_u(_uv){${ul.join('\n ')}\n return true}`)
3189
+ body.push(`if(Array.isArray(d)){for(let _i=0;_i<d.length;_i++)if(!_hI(_R,_i)&&!${name}_u(d[_i]))return false}`)
3190
+ } else {
3191
+ throw DECLINE
3192
+ }
3193
+ body.push(`if(Array.isArray(d))_mrI(S,0,d.length)`)
3194
+ }
3195
+ // `unevaluatedItems: true` evaluates every position once the subschema
3196
+ // has passed, with nothing to check; marked last, so a failure above has
3197
+ // nothing of it to undo.
3198
+ if (sub.unevaluatedItems === true) body.push(`if(Array.isArray(d))_mrI(S,0,d.length)`)
3199
+ decls.push(`function ${name}(d,S){${body.join(';')};return true}`)
3200
+ ;(ctx.preamble || ctx.helperCode).push(decls.join(''))
3201
+ ctx.annFnsI.set(sub, name)
3202
+ return name
3203
+ }
3204
+
3205
+ function genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
3206
+ const propKeysU = Object.keys(schema.properties || {})
3207
+ const ui = ctx.varCounter++
3208
+ const failU = fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:_k${ui}[_i]}`, "'must NOT have unevaluated properties'")
3209
+ let innerU
3210
+ if (propKeysU.length === 0) {
3211
+ innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++){${failU}}`
3212
+ } else if (propKeysU.length <= 8) {
3213
+ const checksU = propKeysU.map(k => `_k${ui}[_i]!==${JSON.stringify(k)}`).join('&&')
3214
+ innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++)if(${checksU}){${failU}}`
3215
+ } else {
3216
+ ctx.closureVars.push(`_a${ui}`)
3217
+ ctx.closureVals.push(new Set(propKeysU))
3218
+ innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++)if(!_a${ui}.has(_k${ui}[_i])){${failU}}`
3219
+ }
3220
+ lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
3221
+ }
3222
+
3223
+ function genUnevaluatedItemsFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
3224
+ const itemsIsPrefixC = Array.isArray(schema.items)
3225
+ const allEvalC = schema.items !== undefined && !itemsIsPrefixC
3226
+ if (!allEvalC) {
3227
+ const plenC = itemsIsPrefixC ? schema.items.length : (Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0)
3228
+ const failI = fail('unevaluatedItems', 'unevaluatedItems', `{limit:${plenC}}`, `'must NOT have more than ${plenC} items'`)
3229
+ lines.push(`if(Array.isArray(${v})&&${v}.length>${plenC}){${failI}}`)
3230
+ }
3231
+ }
3232
+
3233
+ // The error generator's per-key loop (genAdditionalSchemaE), in the combined
3234
+ // generator: the subschema compiled by genCodeC at each additional key, with
3235
+ // that key's path. Patterns go through the same matchers patternProperties
3236
+ // uses here.
3237
+ function genAdditionalSchemaC(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj) {
3238
+ const ki = ctx.varCounter++
3239
+ const kVar = `_ak${ki}`
3240
+ const known = Object.keys(schema.properties || {})
3241
+ const conds = []
3242
+ if (known.length) {
3243
+ const set = `_akn${ki}`
3244
+ ctx.closureVars.push(set)
3245
+ ctx.closureVals.push(new Set(known))
3246
+ conds.push(`!${set}.has(${kVar})`)
3247
+ }
3248
+ const pats = []
3249
+ for (const pat of Object.keys(schema.patternProperties || {})) {
3250
+ const fast = fastPrefixCheck(pat, kVar)
3251
+ if (fast) { pats.push(fast); continue }
3252
+ const ri = ctx.varCounter++
3253
+ ctx.closureVars.push(`_re${ri}`)
3254
+ ctx.closureVals.push(safeReClosure(ctx, pat))
3255
+ pats.push(`_re${ri}.test(${kVar})`)
3256
+ }
3257
+ if (pats.length) conds.push(`!(${pats.join('||')})`)
3258
+ const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
3259
+ const keep = conds.length ? `if(${conds.join('&&')})` : ''
3260
+ const pe = emitPtrEsc(ctx)
3261
+ const p = pathExpr ? `${pathExpr}+'/'+${pe}(${kVar})` : `'/'+${pe}(${kVar})`
3262
+ const sub = []
3263
+ genCodeC(schema.additionalProperties, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/additionalProperties')
3264
+ if (sub.length === 0) return
3265
+ lines.push(`${guard}{for(const ${kVar} in ${v}){${keep}{${sub.join('\n ')}\n }}}`)
3266
+ }
3267
+
3268
+ function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
3269
+ let handledPropertyNames = false
3270
+ const ppEntries = Object.entries(schema.patternProperties)
3271
+ const pn = isSimplePN(schema.propertyNames) ? schema.propertyNames : null
3272
+ const pi = ctx.varCounter++
3273
+
3274
+ // Build pattern matchers: prefer charCodeAt for simple prefixes
3275
+ const matchers = []
3276
+ for (const [pat] of ppEntries) {
3277
+ const kVar = `_k${pi}`
3278
+ const fast = fastPrefixCheck(pat, kVar)
3279
+ if (fast) {
3280
+ matchers.push({ check: fast })
3281
+ } else {
3282
+ const ri = ctx.varCounter++
3283
+ ctx.closureVars.push(`_re${ri}`)
3284
+ ctx.closureVals.push(safeReClosure(ctx, pat))
3285
+ matchers.push({ check: `_re${ri}.test(_k${pi})` })
3286
+ }
3287
+ }
3288
+
3289
+ const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
3290
+ const kVar = `_k${pi}`
3291
+
3292
+ if (schema.additionalProperties === false) {
3293
+ handledPropertyNames = !!pn
3294
+ const propKeys = Object.keys(schema.properties || {})
3295
+ // Inline key comparison for small property sets
3296
+ const keyCheck = propKeys.length <= 8
3297
+ ? propKeys.map(k => `${kVar}===${JSON.stringify(k)}`).join('||')
3298
+ : null
3299
+ if (!keyCheck) {
3300
+ const allowedSet = `_as${pi}`
3301
+ ctx.closureVars.push(allowedSet)
3302
+ ctx.closureVals.push(new Set(propKeys))
3303
+ }
3304
+
3305
+ lines.push(`${guard}{for(const ${kVar} in ${v}){`)
3306
+ // propertyNames checks (merged)
3307
+ if (pn) {
3308
+ if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
3309
+ if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
3310
+ if (pn.pattern) {
3311
+ const fast = fastPrefixCheck(pn.pattern, kVar)
3312
+ if (fast) {
3313
+ lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
3314
+ } else {
3315
+ const ri = ctx.varCounter++
3316
+ ctx.closureVars.push(`_re${ri}`)
3317
+ ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
3318
+ lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
3319
+ }
3320
+ }
3321
+ if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
3322
+ if (pn.enum) {
3323
+ const ei = ctx.varCounter++
3324
+ ctx.closureVars.push(`_es${ei}`)
3325
+ ctx.closureVals.push(new Set(pn.enum))
3326
+ lines.push(`if(!_es${ei}.has(${kVar})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
3327
+ }
3328
+ }
3329
+ const matchExpr = keyCheck || `_as${pi}.has(${kVar})`
3330
+ lines.push(`let _m${pi}=${matchExpr}`)
3331
+ for (let i = 0; i < ppEntries.length; i++) {
3332
+ // The subschema is generated in place so its own keywords report
3333
+ // the real instance path and schema pointer.
3334
+ lines.push(`if(${matchers[i].check}){_m${pi}=true;{const _ppv${pi}_${i}=${v}[${kVar}]`)
3335
+ genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, `${emitPtrEsc(ctx)}(${kVar})`), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
3336
+ lines.push(`}}`)
3337
+ }
3338
+ lines.push(`if(!_m${pi}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:${kVar}}`, "'must NOT have additional properties'")}}`)
3339
+ lines.push(`}}`)
3340
+ } else {
3341
+ handledPropertyNames = !!pn
3342
+ lines.push(`${guard}{for(const ${kVar} in ${v}){`)
3343
+ if (pn) {
3344
+ if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
3345
+ if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
3346
+ if (pn.pattern) {
3347
+ const fast = fastPrefixCheck(pn.pattern, kVar)
3348
+ if (fast) {
3349
+ lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
3350
+ } else {
3351
+ const ri = ctx.varCounter++
3352
+ ctx.closureVars.push(`_re${ri}`)
3353
+ ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
3354
+ lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
3355
+ }
3356
+ }
3357
+ if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
3358
+ if (pn.enum) {
3359
+ const ei = ctx.varCounter++
3360
+ ctx.closureVars.push(`_es${ei}`)
3361
+ ctx.closureVals.push(new Set(pn.enum))
3362
+ lines.push(`if(!_es${ei}.has(${kVar})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
3363
+ }
3364
+ }
3365
+ for (let i = 0; i < ppEntries.length; i++) {
3366
+ lines.push(`if(${matchers[i].check}){const _ppv${pi}_${i}=${v}[${kVar}]`)
3367
+ genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, `${emitPtrEsc(ctx)}(${kVar})`), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
3368
+ lines.push(`}`)
3369
+ }
3370
+ lines.push(`}}`)
3371
+ }
3372
+ return handledPropertyNames
3373
+ }
3374
+
3375
+ function genDependentSchemasC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3376
+ for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
3377
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
3378
+ genCodeC(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
3379
+ lines.push(`}`)
3380
+ }
3381
+ }
3382
+
3383
+ function genPropertyNamesFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3384
+ const ki = ctx.varCounter++
3385
+ const p = pathExpr || '""'
3386
+ lines.push(`;if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){${errPushC(ctx, 'not', `${schemaPrefix}/propertyNames`, pathExpr, '{}', "'boolean schema is false'", v, `{keyword:'not',instancePath:${p},schemaPath:'${schemaPrefix}/propertyNames'${ordinalField(ctx, `${schemaPrefix}/propertyNames`)},params:{},message:'boolean schema is false'}`)}}}`)
3387
+ }
3388
+
3389
+ function genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
3390
+ const pn = schema.propertyNames
3391
+ const ki = ctx.varCounter++
3392
+ // In the rich function an error carries the value it was raised on, and
3393
+ // here that would be the key where the interpreter's error points at the
3394
+ // object, so that one is left to the plain function and enrichment.
3395
+ if (!isSimplePN(pn) && ctx.rich) throw DECLINE
3396
+ lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
3397
+ if (!isSimplePN(pn)) {
3398
+ genCodeC(pn, `_k${ki}`, pathExpr, lines, ctx, schemaPrefix + '/propertyNames')
3399
+ lines.push(`}}`)
3400
+ return
3401
+ }
3402
+ if (pn.minLength !== undefined) {
3403
+ lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
3404
+ }
3405
+ if (pn.maxLength !== undefined) {
3406
+ lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
3407
+ }
3408
+ if (pn.pattern) {
3409
+ const ri = ctx.varCounter++
3410
+ ctx.closureVars.push(`_re${ri}`)
3411
+ ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
3412
+ lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
3413
+ }
3414
+ if (pn.const !== undefined) {
3415
+ lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
3416
+ }
3417
+ if (pn.enum) {
3418
+ const ei = ctx.varCounter++
3419
+ ctx.closureVars.push(`_es${ei}`)
3420
+ ctx.closureVals.push(new Set(pn.enum))
3421
+ lines.push(`if(!_es${ei}.has(_k${ki})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
3422
+ }
3423
+ lines.push(`}}`)
3424
+ }
3425
+
3426
+ function genPrefixItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3427
+ for (let i = 0; i < schema.prefixItems.length; i++) {
3428
+ const childPath = childPathExpr(pathExpr, String(i))
3429
+ lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
3430
+ genCodeC(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
3431
+ lines.push(`}`)
3432
+ }
3433
+ }
3434
+
3435
+ function genContainsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
3436
+ const ci = ctx.varCounter++
3437
+ const subLines = []
3438
+ nestedGenCode(schema.contains, `_cv`, subLines, ctx)
3439
+ const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
3440
+ const minC = schema.minContains !== undefined ? schema.minContains : 1
3441
+ const maxC = schema.maxContains
3442
+ lines.push(`if(Array.isArray(${v})){const _cf${ci}=function(_cv){${fnBody}};let _cc${ci}=0;for(let _ci${ci}=0;_ci${ci}<${v}.length;_ci${ci}++){if(_cf${ci}(${v}[_ci${ci}]))_cc${ci}++}`)
3443
+ lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'must contain at least ${minC} valid item(s)'`)}}`)
3444
+ if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'must NOT contain more than ${maxC} valid item(s)'`)}}`)
3445
+ lines.push(`}`)
3446
+ }
3447
+
3448
+ function genAnyOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
3449
+ // The collapsed branch error the other engines report (ATA4001-4003),
3450
+ // built by the error generator's emitter; see compileToJSCombined.
3451
+ if (ctx.inCombined) return emitBranchCollapse(schema.anyOf, 'anyOf', v, pathExpr, lines, ctx, schemaPrefix)
3452
+ const fi = ctx.varCounter++
3453
+ const fns = schema.anyOf.map(sub => { const sl = []; nestedGenCode(sub, '_av', sl, ctx); return sl.length === 0 ? `function(_av){return true}` : `function(_av){${sl.join(';')};return true}` })
3454
+ lines.push(`{const _af${fi}=[${fns.join(',')}];let _am=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am=true;break}}if(!_am){${fail('anyOf', 'anyOf', '{}', "'must match a schema in anyOf'")}}}`)
3455
+ }
3456
+
3457
+ function genOneOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
3458
+ // The collapsed branch error the other engines report (ATA4001-4003),
3459
+ // built by the error generator's emitter; see compileToJSCombined.
3460
+ if (ctx.inCombined) return emitBranchCollapse(schema.oneOf, 'oneOf', v, pathExpr, lines, ctx, schemaPrefix)
3461
+ const fi = ctx.varCounter++
3462
+ const fns = schema.oneOf.map(sub => { const sl = []; nestedGenCode(sub, '_ov', sl, ctx); return sl.length === 0 ? `function(_ov){return true}` : `function(_ov){${sl.join(';')};return true}` })
3463
+ lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc++;if(_oc>1)break}if(_oc!==1){${fail('oneOf', 'oneOf', '{}', "'must match exactly one schema in oneOf'")}}}`)
3464
+ }
3465
+
3466
+ function genNotC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
3467
+ const sl = []; nestedGenCode(schema.not, '_nv', sl, ctx)
3468
+ const nfn = sl.length === 0 ? `function(_nv){return true}` : `function(_nv){${sl.join(';')};return true}`
3469
+ const fi = ctx.varCounter++
3470
+ lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
3471
+ }
3472
+
3473
+ function genIfC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3474
+ const sl = []; nestedGenCode(schema.if, '_iv', sl, ctx)
3475
+ const fi = ctx.varCounter++
3476
+ const ifFn = sl.length === 0 ? `function(_iv){return true}` : `function(_iv){${sl.join(';')};return true}`
3477
+ lines.push(`{const _if${fi}=${ifFn}`)
3478
+ if (schema.then !== undefined) { lines.push(`if(_if${fi}(${v})){`); genCodeC(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then'); lines.push(`}`) }
3479
+ if (schema.else !== undefined) { lines.push(`${schema.then !== undefined ? 'else' : `if(!_if${fi}(${v}))`}{`); genCodeC(schema.else, v, pathExpr, lines, ctx, schemaPrefix+'/else'); lines.push(`}`) }
3480
+ lines.push(`}`)
3481
+ }
3482
+
3483
+ module.exports = { compileToJSCodegenWithErrors, compileToJSCombined }