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.
@@ -1,4 +1,5 @@
1
1
  'use strict'
2
+ const { compileFunction } = require('./compile-fn')
2
3
 
3
4
  // uniqueItems helpers for the boolean generator, hoisted once per compiled
4
5
  // function (and carried into standalone output through the preamble).
@@ -111,11 +112,6 @@ function emitDeq(ctx) {
111
112
  // regex passes for the keys that need nothing, which is almost all of them.
112
113
  const PTR_ESC_HELPER = "function _pe(s){for(let i=0;i<s.length;i++){const c=s.charCodeAt(i);if(c===126||c===47)return s.replace(/~/g,'~0').replace(/\\//g,'~1')}return s}"
113
114
 
114
- function emitPtrEsc(ctx) {
115
- hoistOnce(ctx, '_peHoisted', PTR_ESC_HELPER)
116
- return '_pe'
117
- }
118
-
119
115
  // `_uq` compares items with `_deq`, so it can never be hoisted alone.
120
116
  function emitUq(ctx) {
121
117
  emitDeq(ctx)
@@ -123,34 +119,6 @@ function emitUq(ctx) {
123
119
  return '_uq'
124
120
  }
125
121
 
126
- // The error and combined generators need the duplicate pair, not only the
127
- // verdict. `_uqp` returns the index of the duplicate (-1 when every item is
128
- // distinct) and leaves the item it repeats in `_uqpI`: the first item that has
129
- // a later equal, paired with its first later equal. That is the pair the
130
- // interpreted engine reports, and the generators used to report a different
131
- // one (the earliest repeat, paired with what it repeats), so the same document
132
- // named a different pair depending on the engine that answered. See
133
- // lib/unique-items.js for how it finds the pair.
134
- function emitUqp(ctx) {
135
- emitUq(ctx)
136
- hoistOnce(ctx, '_uqpHoisted', uqSources().uqp)
137
- return '_uqp'
138
- }
139
-
140
- // An object or array a const error reports as `allowedValue`, parsed once per
141
- // compiled function rather than on every error: parsing it per error made
142
- // reading a const error two to five times what the check costs. A copy of its
143
- // own, not the compared constant, so an error a caller edits cannot change
144
- // what the validator compares against. Declared in the function's helper
145
- // code, so standalone output carries it the same way.
146
- function emitParamConstant(ctx, value) {
147
- const name = `_pc${ctx.varCounter++}`
148
- const decl = `const ${name}=JSON.parse(${JSON.stringify(JSON.stringify(value))})`
149
- if (ctx.preamble) ctx.preamble.push(decl)
150
- else ctx.helperCode.push(decl)
151
- return name
152
- }
153
-
154
122
  // The compared value, parsed once at compile time. `JSON.parse` rather than a
155
123
  // JS object literal so a `__proto__` member stays an ordinary property.
156
124
  function emitConstant(ctx, value) {
@@ -180,11 +148,14 @@ function enumCondition(ctx, vals, v) {
180
148
  }
181
149
  return parts.filter(Boolean).join('||') || 'false'
182
150
  }
183
-
184
-
185
- const { codeFor } = require('./error-codes')
186
- const { ordinalFor, rankFor } = require('./schema-order')
187
- const { compileSafe, patternIsSafe } = require('./safe-regex')
151
+ // The linear regex engine (safe-regex.js) is read on the first pattern a
152
+ // schema brings, not with this module: most schemas have none, and loading
153
+ // it cost every process part of its cold start. The two names keep their
154
+ // identity (compileSafe is handed to generated code as a closure value).
155
+ let _safeRe = null
156
+ const safeRe = () => _safeRe || (_safeRe = require('./safe-regex'))
157
+ function compileSafe (src) { return safeRe().compileSafe(src) }
158
+ function patternIsSafe (src) { return safeRe().patternIsSafe(src) }
188
159
  // Whether a pattern goes to the linear-time engine. A pattern the platform
189
160
  // RegExp also runs in linear time (nativeIsLinear) takes the RegExp: it answers
190
161
  // the same, runs faster, and a compiled module that needs no other pattern
@@ -225,88 +196,6 @@ function safeReClosure (ctx, src) {
225
196
  return new RegExp(src, reFlags(src))
226
197
  }
227
198
 
228
- // Compile a JSON Schema into a pure JS validator function.
229
- // Closure-based validator — no new Function() or eval().
230
- // Returns null if the schema is too complex for JS compilation.
231
-
232
- const DOC_BASE = 'https://ata-validator.com/e/'
233
-
234
- // Build the literal fragments injected into an emitted _e.push({...}) call.
235
- // Returns { codeStr, docUrl, frame } where `frame` is a JS source fragment
236
- // like ",schemaSource:Object.freeze({...})" or "" if no source map hit.
237
- // The fragment always starts with a leading comma when non-empty so callers
238
- // can splice it inside an existing object literal.
239
- // The error's position in schema declaration order, as a literal field the
240
- // reader sorts by without deriving it from the path. Empty when the path is
241
- // not into the root document.
242
- function ordinalField (ctx, schemaPath) {
243
- if (ctx && ctx.noOrdinal) return ''
244
- const o = ctx && ctx.rootSchema ? ordinalFor(ctx.rootSchema, unescapeSp(schemaPath)) : null
245
- return o === null ? '' : `,_o:${o}`
246
- }
247
-
248
- // A schema path as the generators hold it is text for a single-quoted source
249
- // literal: ptrSeg put a backslash before every backslash and quote in a key.
250
- // Anything that uses the path as a value, the ordinal lookup and the frozen
251
- // error objects below, has to take that backslash off again, or a key such as
252
- // `it's` reads as `it\'s` and the error names a path that does not exist.
253
- // The value of a string literal the generators built for source. One that does
254
- // not parse on its own (a key with a raw line break) would not parse inside the
255
- // generated function either, so the generator declines, as it would have when
256
- // that function failed to build.
257
- function literalValue (lit) {
258
- try {
259
- return Function('return ' + lit)()
260
- } catch {
261
- throw DECLINE
262
- }
263
- }
264
-
265
- function unescapeSp (sp) {
266
- 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))
267
- }
268
-
269
- const FRAME_INLINE_MAX = 120
270
-
271
- function buildErrorLiteral (opts) {
272
- const { keyword, format, schemaPath, sourceMap } = opts
273
- let code = (keyword === 'format' && format) ? codeFor('format', format) : codeFor(keyword)
274
- if (!code) code = 'ATA9001'
275
- const docUrl = DOC_BASE + code
276
- let frame = ''
277
- if (sourceMap && sourceMap.file && sourceMap.map) {
278
- const ptr = schemaPath && schemaPath.charAt(0) === '#' ? schemaPath.slice(1) : (schemaPath || '')
279
- let hit = sourceMap.map[ptr + '#key']
280
- if (!hit) hit = sourceMap.map[ptr]
281
- if (hit) {
282
- // Support both {line,col,text} (raw output of buildPositionMap) and
283
- // [line,col,text] (compact form passed by callers that pre-flattened).
284
- const line = Array.isArray(hit) ? hit[0] : hit.line
285
- const col = Array.isArray(hit) ? hit[1] : hit.col
286
- const text = Array.isArray(hit) ? hit[2] : hit.text
287
- if (sourceMap.frames && text.length > FRAME_INLINE_MAX) {
288
- // A long source line is written once at module scope (see
289
- // sourceFrameDecls in aot-impl.js) and the literal names it. Inlined,
290
- // every error site carried the whole line, and a schema kept on one
291
- // line is one line of all of it: 181 KB minified for a 2 KB schema.
292
- // Short lines stay inline: gzip folds the repeats, and measured on
293
- // the 50-field fixture the table cost 1206 gzipped bytes where the
294
- // inline frames cost 696.
295
- const key = line + ':' + col
296
- let f = sourceMap.frames.get(key)
297
- if (!f) { f = { line, col, text, index: sourceMap.frames.size }; sourceMap.frames.set(key, f) }
298
- frame = ',schemaSource:__ataSS[' + f.index + ']'
299
- } else {
300
- frame = ',schemaSource:Object.freeze({file:' + JSON.stringify(sourceMap.file) +
301
- ',line:' + line +
302
- ',col:' + col +
303
- ',text:' + JSON.stringify(text) + '})'
304
- }
305
- }
306
- }
307
- return { codeStr: code, docUrl, frame }
308
- }
309
-
310
199
  // Count Unicode code points, not UTF-16 code units (surrogate pairs).
311
200
  // JSON Schema: minLength/maxLength count characters per RFC 8259.
312
201
  // Fast path: if no surrogate pairs exist, .length is correct (covers >99% of real data).
@@ -929,30 +818,35 @@ function _canonical(x) {
929
818
  return '{' + Object.keys(x).sort().map((k) => JSON.stringify(k) + ':' + _canonical(x[k])).join(',') + '}'
930
819
  }
931
820
 
932
- const _formats = { ...require('./formats'), ...require('./formats-source') }
821
+ // The format checkers' source (formats-source.js, 15 KB) is read only when a
822
+ // schema with a `format` is compiled; loading it with this module cost every
823
+ // process 0.8 ms of its cold start.
824
+ let _formatsCache = null
825
+ function getFormats () { return _formatsCache || (_formatsCache = { ...require('./formats'), ...require('./formats-source') }) }
933
826
  // The closure path's formats. It carried its own idea of an email and knew
934
827
  // five formats in all, so a schema that reached it was told "valid" for
935
828
  // anything the other engines turned away; the first call on a fresh validator
936
829
  // is the one that lands here. Every entry now names the same function the
937
830
  // other two paths use, and the list is the list they implement.
831
+ const _fmtFns = require('./formats')
938
832
  const FORMAT_CHECKS = {
939
- email: _formats.email,
940
- 'idn-email': _formats.idnEmail,
941
- date: _formats.date,
942
- 'date-time': _formats.dateTime,
943
- time: _formats.time,
944
- duration: _formats.duration,
945
- uuid: _formats.uuid,
946
- uri: _formats.uri,
947
- 'uri-reference': _formats.uriReference,
948
- 'uri-template': _formats.uriTemplate,
949
- iri: _formats.iri,
950
- 'iri-reference': _formats.iriReference,
951
- ipv4: _formats.ipv4,
952
- ipv6: _formats.ipv6,
953
- hostname: _formats.hostname,
954
- 'json-pointer': _formats.jsonPointer,
955
- 'relative-json-pointer': _formats.relativeJsonPointer,
833
+ email: _fmtFns.email,
834
+ 'idn-email': _fmtFns.idnEmail,
835
+ date: _fmtFns.date,
836
+ 'date-time': _fmtFns.dateTime,
837
+ time: _fmtFns.time,
838
+ duration: _fmtFns.duration,
839
+ uuid: _fmtFns.uuid,
840
+ uri: _fmtFns.uri,
841
+ 'uri-reference': _fmtFns.uriReference,
842
+ 'uri-template': _fmtFns.uriTemplate,
843
+ iri: _fmtFns.iri,
844
+ 'iri-reference': _fmtFns.iriReference,
845
+ ipv4: _fmtFns.ipv4,
846
+ ipv6: _fmtFns.ipv6,
847
+ hostname: _fmtFns.hostname,
848
+ 'json-pointer': _fmtFns.jsonPointer,
849
+ 'relative-json-pointer': _fmtFns.relativeJsonPointer,
956
850
  regex: (s) => { try { new RegExp(s, 'u'); return true } catch { return false } },
957
851
  }
958
852
 
@@ -1269,11 +1163,51 @@ function _setCodeBudget (n) { codeBudget = n === undefined ? CODE_BUDGET : n }
1269
1163
  // points instead, and the caller's generator declines the schema.
1270
1164
  function nestedGenCode(schema, v, lines, ctx) {
1271
1165
  ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
1166
+ const before = lines.length
1272
1167
  try {
1273
1168
  genCode(schema, v, lines, ctx)
1274
1169
  } finally {
1275
1170
  ctx.nestedBoolean--
1276
1171
  }
1172
+ assertEmitted(schema, lines.length - before, false)
1173
+ }
1174
+
1175
+ // Silent acceptance is the failure this generator must never produce, and
1176
+ // its usual shape is a subschema that constrains something for which no line
1177
+ // was written: the caller then reads "no lines" as "holds". That has happened
1178
+ // through a dropped deferred check, a reference expanded to nothing, and a
1179
+ // generator that returned early. So every place that turns a subschema into
1180
+ // lines asserts that a schema with a validating keyword produced at least one,
1181
+ // and declines otherwise, which routes the schema to the interpreted engine.
1182
+ // `format` is left out (not asserted under every option), and so is `type`
1183
+ // when the caller already knows the type (`ignoreType`). Unknown keywords do
1184
+ // not count: validators ignore them.
1185
+ const HC_SUBSCHEMA_KEYS = ['additionalProperties', 'items', 'unevaluatedProperties', 'unevaluatedItems', 'additionalItems', 'propertyNames', 'contains', 'not']
1186
+ const HC_SUBSCHEMA_MAPS = ['properties', 'patternProperties', 'dependentSchemas']
1187
+ const HC_SUBSCHEMA_LISTS = ['allOf', 'anyOf', 'oneOf', 'prefixItems']
1188
+ const HC_ALWAYS = new Set(['enum', 'const', 'multipleOf', 'maximum', 'exclusiveMaximum', 'minimum', 'exclusiveMinimum', 'maxLength', 'pattern', 'maxItems', 'maxProperties', '$ref', '$dynamicRef', '$recursiveRef'])
1189
+ function hasConstraints (sub, ignoreType) {
1190
+ if (sub === false) return true
1191
+ if (sub === true || sub === null || typeof sub !== 'object' || Array.isArray(sub)) return false
1192
+ for (const k of Object.keys(sub)) {
1193
+ const v = sub[k]
1194
+ if (HC_ALWAYS.has(k)) return true
1195
+ if (k === 'type') { if (!ignoreType) return true; continue }
1196
+ if (k === 'required' || k === 'dependentRequired') { if (Array.isArray(v) ? v.length > 0 : (v && typeof v === 'object' && Object.values(v).some((a) => Array.isArray(a) && a.length))) return true; continue }
1197
+ if (k === 'dependencies') { if (v && typeof v === 'object' && Object.values(v).some((a) => Array.isArray(a) ? a.length > 0 : hasConstraints(a, false))) return true; continue }
1198
+ if (k === 'uniqueItems') { if (v === true) return true; continue }
1199
+ if (k === 'minItems' || k === 'minProperties' || k === 'minLength' || k === 'minContains') { if (v > 0 && (k !== 'minContains' || sub.contains !== undefined)) return true; continue }
1200
+ if (k === 'maxContains') { if (sub.contains !== undefined) return true; continue }
1201
+ if (k === 'if') { if ((sub.then !== undefined && hasConstraints(sub.then, false)) || (sub.else !== undefined && hasConstraints(sub.else, false))) return true; continue }
1202
+ if (HC_SUBSCHEMA_KEYS.includes(k)) { if (hasConstraints(v, false)) return true; continue }
1203
+ if (HC_SUBSCHEMA_MAPS.includes(k)) { if (v && typeof v === 'object' && Object.values(v).some((x) => hasConstraints(x, false))) return true; continue }
1204
+ if (HC_SUBSCHEMA_LISTS.includes(k)) { if (Array.isArray(v) && v.some((x) => hasConstraints(x, false))) return true; continue }
1205
+ if (k === 'items' && Array.isArray(v)) { if (v.some((x) => hasConstraints(x, false))) return true; continue }
1206
+ }
1207
+ return false
1208
+ }
1209
+ function assertEmitted (sub, emitted, ignoreType) {
1210
+ if (emitted === 0 && hasConstraints(sub, ignoreType)) throw DECLINE
1277
1211
  }
1278
1212
 
1279
1213
  const CYCLE_DEPTH = 512
@@ -1379,11 +1313,6 @@ const REF_NEUTRAL_SIBLINGS = new Set([
1379
1313
  'markdownEnumDescriptions', 'defaultSnippets', 'doNotSuggest', 'suggestSortText',
1380
1314
  ])
1381
1315
 
1382
- // Stands for the schema path of a call to a recursive definition's error
1383
- // helper while its body is generated; see genCodeENode. Not a pointer, so no
1384
- // ordinal or source frame is looked up for it at compile time.
1385
- const SP_PLACEHOLDER = '@@ata-sp@@'
1386
-
1387
1316
  // Follows a definition that is a local $ref to another definition until it
1388
1317
  // reaches one that is not. False for a chain that loops, and for one whose
1389
1318
  // next hop is not a definition in this document; codegenSafe decides whether
@@ -2684,7 +2613,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
2684
2613
  for (const dc of ctx.deferredChecks) lines.push(dc)
2685
2614
  }
2686
2615
 
2687
- if (lines.length === 0) return () => true
2616
+ if (lines.length === 0) { if (hasConstraints(schema, false)) return null; return () => true }
2688
2617
 
2689
2618
  const checkStr = lines.join('\n ')
2690
2619
 
@@ -2753,122 +2682,158 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
2753
2682
  try {
2754
2683
  let boolFn
2755
2684
  if (closureNames.length > 0) {
2756
- const factory = new Function(...closureNames, `${preambleStr}return function(d){${body}}`)
2685
+ const factory = compileFunction(...closureNames, `${preambleStr}return function(d){${body}}`)
2757
2686
  boolFn = factory(...closureValues)
2758
2687
  } else if (preambleStr) {
2759
- const factory = new Function(`${preambleStr}return function(d){${body}}`)
2688
+ const factory = compileFunction(`${preambleStr}return function(d){${body}}`)
2760
2689
  boolFn = factory()
2761
2690
  } else {
2762
- boolFn = new Function('d', body)
2763
- }
2691
+ boolFn = compileFunction('d', body)
2692
+ }
2693
+
2694
+ // Light by default: the pieces the tiering and standalone paths need
2695
+ // (the hybrid, result and tail factories, the source and its parts) are
2696
+ // not attached here. They are closures over the generated text, and a
2697
+ // validator kept them for as long as it lived: 2.2 of the 3.4 KB a
2698
+ // compiled verdict retained, measured over two thousand validators used
2699
+ // once. A validator that tiers, or a build that emits it, asks for them
2700
+ // through the lazy properties below, which compile the same schema once
2701
+ // more in full (`opts.full`), a few hundred microseconds once, and only
2702
+ // for the validators that get there. The generator is deterministic, so
2703
+ // the full compile writes the same program.
2704
+ // A schema with custom keywords compiles in full at once: a keyword's
2705
+ // compile form runs per compile, and a second compile at the tier would
2706
+ // run it again.
2707
+ if (!(opts && opts.full) && !(opts && opts.keywords)) {
2708
+ attachLazyHeavy(boolFn, inputSchema, schemaMap, userFormats, opts, !!ctx.usesSafeRe)
2709
+ return boolFn
2710
+ }
2711
+ attachHeavy(boolFn, { ctx, closureNames, closureValues, preambleStr, guardStr, hybridBody, resultThunk, tailThunk, body })
2712
+ return boolFn
2713
+ } catch {
2714
+ return null
2715
+ }
2716
+ }
2764
2717
 
2718
+ const HEAVY_DATA = ['_source', '_preambleSource', '_preambleGuard', '_preambleParts', '_sharedHelpers', '_hybridSource', '_formatClosures', '_closures']
2719
+ function attachLazyHeavy (boolFn, schema, schemaMap, userFormats, opts, usesSafeRe) {
2720
+ boolFn._usesSafeRe = usesSafeRe
2721
+ let fullFn
2722
+ const full = () => fullFn || (fullFn = compileToJSCodegen(schema, schemaMap, userFormats, Object.assign({}, opts || {}, { full: true })))
2723
+ // The factories are functions that compile in full when called: the
2724
+ // callers test for their presence at construction and call them at the
2725
+ // tier, and a getter would have compiled at the test.
2726
+ boolFn._hybridFactory = (R, E) => { const f = full(); return f && f._hybridFactory ? f._hybridFactory(R, E) : null }
2727
+ boolFn._resultFactory = (LR, B, W, EE) => { const f = full(); return f && f._resultFactory ? f._resultFactory(LR, B, W, EE) : null }
2728
+ boolFn._withTail = (tail) => { const f = full(); return f && f._withTail ? f._withTail(tail) : null }
2729
+ for (const k of HEAVY_DATA) Object.defineProperty(boolFn, k, { get () { const f = full(); return f ? f[k] : undefined }, configurable: true, enumerable: false })
2730
+ }
2731
+ // The factories and the source, as the full compile attaches them.
2732
+ function attachHeavy (boolFn, P) {
2733
+ const { ctx, closureNames, closureValues, preambleStr, guardStr, hybridBody, resultThunk, tailThunk, body } = P
2765
2734
  // The hybrid: same body, return R instead of true, return E(d) instead of
2766
- // false. Compiled on first request; null when that compile is refused, and
2767
- // the caller then assembles from the error functions instead.
2768
- let hybridFactory
2769
- boolFn._hybridFactory = (R, E) => {
2770
- if (hybridFactory === undefined) {
2771
- try {
2772
- hybridFactory = new Function(...closureNames, 'R', 'E', `${preambleStr}return function(d){${hybridBody()}}`)
2773
- } catch {
2774
- hybridFactory = null
2775
- }
2735
+ // false. Compiled on first request; null when that compile is refused, and
2736
+ // the caller then assembles from the error functions instead.
2737
+ let hybridFactory
2738
+ boolFn._hybridFactory = (R, E) => {
2739
+ if (hybridFactory === undefined) {
2740
+ try {
2741
+ hybridFactory = compileFunction(...closureNames, 'R', 'E', `${preambleStr}return function(d){${hybridBody()}}`)
2742
+ } catch {
2743
+ hybridFactory = null
2776
2744
  }
2777
- return hybridFactory === null ? null : hybridFactory(...closureValues, R, E)
2778
- }
2779
-
2780
- // validate() itself: the same checks returning the result objects, for a
2781
- // validator past its first calls (see validator-core). One function per
2782
- // schema means the result is built at an allocation site of its own and
2783
- // nothing between the caller and the checks is shared with other schemas.
2784
- // Shared wrappers are what made reading results slow across many schemas:
2785
- // every validator's calls went through the same few call sites. The
2786
- // rejection is the lazy one, built from the arguments.
2787
- let resultFactory
2788
- boolFn._resultFactory = (LR, B, W, EE) => {
2789
- if (resultFactory === undefined) {
2790
- try {
2791
- resultFactory = new Function(...closureNames, '__ataLR', '__ataB', '__ataW', '__ataEE', `${preambleStr}return function(d){${resultThunk()}}`)
2792
- } catch {
2793
- resultFactory = null
2794
- }
2745
+ }
2746
+ return hybridFactory === null ? null : hybridFactory(...closureValues, R, E)
2747
+ }
2748
+
2749
+ // validate() itself: the same checks returning the result objects, for a
2750
+ // validator past its first calls (see validator-core). One function per
2751
+ // schema means the result is built at an allocation site of its own and
2752
+ // nothing between the caller and the checks is shared with other schemas.
2753
+ // Shared wrappers are what made reading results slow across many schemas:
2754
+ // every validator's calls went through the same few call sites. The
2755
+ // rejection is the lazy one, built from the arguments.
2756
+ let resultFactory
2757
+ boolFn._resultFactory = (LR, B, W, EE) => {
2758
+ if (resultFactory === undefined) {
2759
+ try {
2760
+ resultFactory = compileFunction(...closureNames, '__ataLR', '__ataB', '__ataW', '__ataEE', `${preambleStr}return function(d){${resultThunk()}}`)
2761
+ } catch {
2762
+ resultFactory = null
2795
2763
  }
2796
- return resultFactory === null ? null : resultFactory(...closureValues, LR, B, W, EE)
2797
- }
2798
-
2799
- // The same function with one more check where it would return true, for a
2800
- // wrapper that enforces something the schema does not carry (see
2801
- // Validator#_extendVerdict). Calling the check from here rather than from a
2802
- // function around this one is the point: this function is too large for
2803
- // the engine to inline, so a wrapper is a second call on every document,
2804
- // and a rejection never reaches the check at all. Built on first request,
2805
- // since almost no validator is extended. Null when code generation is
2806
- // refused, and the caller composes instead.
2807
- let tailFactory
2808
- boolFn._withTail = (tail) => {
2809
- if (tailFactory === undefined) {
2810
- try {
2811
- tailFactory = new Function(...closureNames, '__ataTail', `${preambleStr}return function(d){${tailThunk()}}`)
2812
- } catch {
2813
- tailFactory = null
2814
- }
2764
+ }
2765
+ return resultFactory === null ? null : resultFactory(...closureValues, LR, B, W, EE)
2766
+ }
2767
+
2768
+ // The same function with one more check where it would return true, for a
2769
+ // wrapper that enforces something the schema does not carry (see
2770
+ // Validator#_extendVerdict). Calling the check from here rather than from a
2771
+ // function around this one is the point: this function is too large for
2772
+ // the engine to inline, so a wrapper is a second call on every document,
2773
+ // and a rejection never reaches the check at all. Built on first request,
2774
+ // since almost no validator is extended. Null when code generation is
2775
+ // refused, and the caller composes instead.
2776
+ let tailFactory
2777
+ boolFn._withTail = (tail) => {
2778
+ if (tailFactory === undefined) {
2779
+ try {
2780
+ tailFactory = compileFunction(...closureNames, '__ataTail', `${preambleStr}return function(d){${tailThunk()}}`)
2781
+ } catch {
2782
+ tailFactory = null
2815
2783
  }
2816
- return tailFactory === null ? null : tailFactory(...closureValues, tail)
2817
- }
2818
-
2819
- // Store source for standalone compilation. Regex declarations are NOT
2820
- // inlined into the body: they are carried by _closures and the emitters
2821
- // declare them once at load scope. Inlining them here put a
2822
- // `const _reN=__ataSafeRe(...)` inside the emitted function, which
2823
- // shadowed the module-scope binding and recompiled the pattern on every
2824
- // call, 25x on a schema with one pattern.
2825
- const emitHelpers = ctx.helperCode.filter((c) => !/^const _re\d+=(?:__ataSafeRe|new RegExp)\(/.test(c))
2826
- const helperStr = emitHelpers.length ? emitHelpers.join('\n ') + '\n ' : ''
2827
- boolFn._source = helperStr + body
2828
- boolFn._preambleSource = preambleStr
2829
- boolFn._preambleGuard = guardStr
2830
- boolFn._preambleParts = ctx.preamble ? ctx.preamble.slice() : []
2831
- boolFn._sharedHelpers = ctx.shared ? ctx.shared.slice() : []
2832
- Object.defineProperty(boolFn, '_hybridSource', {
2833
- get: () => helperStr + hybridBody(),
2834
- configurable: true,
2835
- enumerable: false,
2836
- })
2837
- boolFn._usesSafeRe = !!ctx.usesSafeRe
2838
- // Custom-format closure entries that the bundle output needs to recreate.
2839
- // Stored as { name, fn } so consumers can serialize via Function#toString.
2840
- if (ctx.userFormats) {
2841
- const fmtEntries = []
2842
- for (let i = 0; i < closureNames.length; i++) {
2843
- if (closureNames[i].startsWith('_uf_')) {
2844
- // The format's own name, so an emitter can look it up at runtime
2845
- // instead of embedding the function.
2846
- let format = null
2847
- for (const key of Object.keys(ctx.userFormats)) {
2848
- if (ctx.userFormats[key] === closureValues[i]) { format = key; break }
2849
- }
2850
- fmtEntries.push({ name: closureNames[i], fn: closureValues[i], format })
2784
+ }
2785
+ return tailFactory === null ? null : tailFactory(...closureValues, tail)
2786
+ }
2787
+
2788
+ // Store source for standalone compilation. Regex declarations are NOT
2789
+ // inlined into the body: they are carried by _closures and the emitters
2790
+ // declare them once at load scope. Inlining them here put a
2791
+ // `const _reN=__ataSafeRe(...)` inside the emitted function, which
2792
+ // shadowed the module-scope binding and recompiled the pattern on every
2793
+ // call, 25x on a schema with one pattern.
2794
+ const emitHelpers = ctx.helperCode.filter((c) => !/^const _re\d+=(?:__ataSafeRe|new RegExp)\(/.test(c))
2795
+ const helperStr = emitHelpers.length ? emitHelpers.join('\n ') + '\n ' : ''
2796
+ boolFn._source = helperStr + body
2797
+ boolFn._preambleSource = preambleStr
2798
+ boolFn._preambleGuard = guardStr
2799
+ boolFn._preambleParts = ctx.preamble ? ctx.preamble.slice() : []
2800
+ boolFn._sharedHelpers = ctx.shared ? ctx.shared.slice() : []
2801
+ Object.defineProperty(boolFn, '_hybridSource', {
2802
+ get: () => helperStr + hybridBody(),
2803
+ configurable: true,
2804
+ enumerable: false,
2805
+ })
2806
+ boolFn._usesSafeRe = !!ctx.usesSafeRe
2807
+ // Custom-format closure entries that the bundle output needs to recreate.
2808
+ // Stored as { name, fn } so consumers can serialize via Function#toString.
2809
+ if (ctx.userFormats) {
2810
+ const fmtEntries = []
2811
+ for (let i = 0; i < closureNames.length; i++) {
2812
+ if (closureNames[i].startsWith('_uf_')) {
2813
+ // The format's own name, so an emitter can look it up at runtime
2814
+ // instead of embedding the function.
2815
+ let format = null
2816
+ for (const key of Object.keys(ctx.userFormats)) {
2817
+ if (ctx.userFormats[key] === closureValues[i]) { format = key; break }
2851
2818
  }
2819
+ fmtEntries.push({ name: closureNames[i], fn: closureValues[i], format })
2852
2820
  }
2853
- if (fmtEntries.length) boolFn._formatClosures = fmtEntries
2854
- }
2855
- // Closure variables (regex, sub-validators, sets) referenced in _source that
2856
- // standalone module output must declare. Excludes _cpLen (emitted by _CP_LEN_SOURCE)
2857
- // and _uf_* (emitted via _formatClosures).
2858
- {
2859
- const entries = []
2860
- for (let i = 0; i < closureNames.length; i++) {
2861
- const name = closureNames[i]
2862
- if (name === '_cpLen' || name.startsWith('_uf_')) continue
2863
- entries.push({ name, val: closureValues[i] })
2864
- }
2865
- if (entries.length) boolFn._closures = entries
2866
2821
  }
2867
-
2868
- return boolFn
2869
- } catch {
2870
- return null
2822
+ if (fmtEntries.length) boolFn._formatClosures = fmtEntries
2823
+ }
2824
+ // Closure variables (regex, sub-validators, sets) referenced in _source that
2825
+ // standalone module output must declare. Excludes _cpLen (emitted by _CP_LEN_SOURCE)
2826
+ // and _uf_* (emitted via _formatClosures).
2827
+ {
2828
+ const entries = []
2829
+ for (let i = 0; i < closureNames.length; i++) {
2830
+ const name = closureNames[i]
2831
+ if (name === '_cpLen' || name.startsWith('_uf_')) continue
2832
+ entries.push({ name, val: closureValues[i] })
2833
+ }
2834
+ if (entries.length) boolFn._closures = entries
2871
2835
  }
2836
+
2872
2837
  }
2873
2838
 
2874
2839
  // Replace top-level `return false` → `return E(d)` and `return true` → `return R`.
@@ -4718,86 +4683,59 @@ function genUnevaluatedItemsNode(schema, v, lines, ctx, knownType, isArr) {
4718
4683
  // costs more than the call. They are hoisted once per compiled function and
4719
4684
  // invoked by name: one real schema with four email fields grew by 20 KB when
4720
4685
  // the check was inlined at each of them.
4721
- const EMAIL_HELPER = `function _em(_s){${_formats.emailSource('_s', true)}return true}`
4686
+ const emailHelper = () => `function _em(_s){${getFormats().emailSource('_s', true)}return true}`
4722
4687
 
4723
4688
  // `uri` is the most expensive format an ordinary document carries: a schema
4724
4689
  // with a handful of URL fields spent more time here than on every structural
4725
4690
  // check put together. The walk reads its character classes out of hoisted
4726
4691
  // tables, so it is declared once per compiled function rather than inlined at
4727
4692
  // each call site, where the tables would have to be rebuilt.
4728
- const URI_HELPER = _formats.uriHelperSource('_uri')
4693
+ const uriHelper = () => getFormats().uriHelperSource('_uri')
4729
4694
 
4730
4695
  const FORMAT_CODEGEN = {
4731
4696
  email: (v, isStr, ctx) => {
4732
- if (!ctx) return _formats.emailSource(v, isStr)
4733
- hoistOnce(ctx, '_emHoisted', EMAIL_HELPER)
4697
+ if (!ctx) return getFormats().emailSource(v, isStr)
4698
+ hoistOnce(ctx, '_emHoisted', emailHelper())
4734
4699
  return isStr ? `if(!_em(${v}))return false` : `if(typeof ${v}==='string'&&!_em(${v}))return false`
4735
4700
  },
4736
- 'json-pointer': _formats.jsonPointerSource,
4737
- 'relative-json-pointer': _formats.relativeJsonPointerSource,
4738
- 'uri-template': _formats.uriTemplateSource,
4739
- iri: _formats.iriSource,
4740
- 'iri-reference': _formats.iriReferenceSource,
4741
- 'idn-email': _formats.idnEmailSource,
4701
+ 'json-pointer': (...a) => getFormats().jsonPointerSource(...a),
4702
+ 'relative-json-pointer': (...a) => getFormats().relativeJsonPointerSource(...a),
4703
+ 'uri-template': (...a) => getFormats().uriTemplateSource(...a),
4704
+ iri: (...a) => getFormats().iriSource(...a),
4705
+ 'iri-reference': (...a) => getFormats().iriReferenceSource(...a),
4706
+ 'idn-email': (...a) => getFormats().idnEmailSource(...a),
4742
4707
  regex: (v, isStr) => {
4743
4708
  const inner = `try{new RegExp(${v},'u')}catch(_er){return false}`
4744
4709
  return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`
4745
4710
  },
4746
- date: _formats.dateSource,
4747
- uuid: _formats.uuidSource,
4748
- 'date-time': _formats.dateTimeSource,
4749
- time: _formats.timeSource,
4750
- duration: _formats.durationSource,
4711
+ date: (...a) => getFormats().dateSource(...a),
4712
+ uuid: (...a) => getFormats().uuidSource(...a),
4713
+ 'date-time': (...a) => getFormats().dateTimeSource(...a),
4714
+ time: (...a) => getFormats().timeSource(...a),
4715
+ duration: (...a) => getFormats().durationSource(...a),
4751
4716
  uri: (v, isStr, ctx) => {
4752
- if (!ctx) return _formats.uriSource(v, isStr)
4753
- hoistOnce(ctx, '_uriHoisted', URI_HELPER)
4717
+ if (!ctx) return getFormats().uriSource(v, isStr)
4718
+ hoistOnce(ctx, '_uriHoisted', uriHelper())
4754
4719
  return isStr ? `if(!_uri(${v}))return false` : `if(typeof ${v}==='string'&&!_uri(${v}))return false`
4755
4720
  },
4756
4721
  'uri-reference': (v, isStr) => isStr
4757
- ? `{${_formats.uriCharsSource(v, '0')}}`
4758
- : `if(typeof ${v}==='string'){${_formats.uriCharsSource(v, '0')}}`,
4722
+ ? `{${getFormats().uriCharsSource(v, '0')}}`
4723
+ : `if(typeof ${v}==='string'){${getFormats().uriCharsSource(v, '0')}}`,
4759
4724
  ipv4: (v, isStr, ctx) => {
4760
- if (!ctx) return _formats.ipv4Source(v, isStr)
4725
+ if (!ctx) return getFormats().ipv4Source(v, isStr)
4761
4726
  // Hoisted: a regex literal in the body would build a new RegExp per call.
4762
- hoistOnce(ctx, '_ip4Hoisted', 'const _ip4=/' + _formats.IPV4.source + '/')
4727
+ hoistOnce(ctx, '_ip4Hoisted', 'const _ip4=/' + getFormats().IPV4.source + '/')
4763
4728
  return isStr ? `if(!_ip4.test(${v}))return false` : `if(typeof ${v}==='string'&&!_ip4.test(${v}))return false`
4764
4729
  },
4765
4730
  ipv6: (v, isStr, ctx) => {
4766
- if (!ctx) return _formats.ipv6Source(v, isStr)
4767
- hoistOnce(ctx, '_ip6Hoisted', 'const _ip6f=/' + _formats.IPV6_FULL.source + '/')
4731
+ if (!ctx) return getFormats().ipv6Source(v, isStr)
4732
+ hoistOnce(ctx, '_ip6Hoisted', 'const _ip6f=/' + getFormats().IPV6_FULL.source + '/')
4768
4733
  // The fast accept jumps past the walk; the walk's `return false`s are what
4769
4734
  // the error generators rewrite, so they stay as they are.
4770
- const body = `_ip6:{if(${v}.length>=15&&_ip6f.test(${v}))break _ip6;${_formats.ipv6Source(v, true).replace(/^\{|\}$/g, '')}}`
4735
+ const body = `_ip6:{if(${v}.length>=15&&_ip6f.test(${v}))break _ip6;${getFormats().ipv6Source(v, true).replace(/^\{|\}$/g, '')}}`
4771
4736
  return isStr ? body : `if(typeof ${v}==='string'){${body}}`
4772
4737
  },
4773
- hostname: _formats.hostnameSource,
4774
- }
4775
-
4776
- // Safe key escaping: use JSON.stringify to handle all special chars (newlines, null bytes, etc.)
4777
- // A schema string as the body of a JavaScript string literal, single- or
4778
- // double-quoted. JSON.stringify escapes the double quote and the backslash;
4779
- // the single quote needs its own escape, because the error emitters put these
4780
- // in '...' literals. Without it a property name holding a quote closed the
4781
- // literal, and the rest of the name ran as code in the generated validator.
4782
- function esc(s) { return JSON.stringify(s).slice(1, -1).replace(/'/g, "\\'") }
4783
- // A schema pointer segment for a pattern: JSON Pointer escaping, then made
4784
- // safe for the single-quoted literal the error emitters use.
4785
- // A pointer segment as text for a single-quoted literal. Control characters
4786
- // and the two line separators go in as \u escapes: written raw, a property
4787
- // named "foo\nbar" broke the literal, the combined function failed to compile
4788
- // and every schema with such a name lost it.
4789
- 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')) }
4790
-
4791
- // Resolve child path at codegen time when parent is a static string literal.
4792
- // This enables frozen pre-allocation for ALL nested error objects.
4793
- function childPathExpr(parentExpr, suffix) {
4794
- if (!parentExpr) return `'/${suffix}'`
4795
- if (parentExpr.startsWith("'") && !parentExpr.includes('+')) {
4796
- // Static parent: resolve at codegen time → '/parent/child' (single literal)
4797
- return `'${parentExpr.slice(1, -1)}/${suffix}'`
4798
- }
4799
- // Dynamic parent: keep as concat expression
4800
- return `${parentExpr}+'/${suffix}'`
4738
+ hostname: (...a) => getFormats().hostnameSource(...a),
4801
4739
  }
4802
4740
 
4803
4741
  // Compile simple regex patterns to inline charCode checks — avoids RegExp engine overhead.
@@ -4937,12 +4875,6 @@ function charClassToCheck(charClass, codeExpr) {
4937
4875
  return `(${checks.join('||')})`
4938
4876
  }
4939
4877
 
4940
- // Same but for dynamic segments (array indices)
4941
- function childPathDynExpr(parentExpr, indexExpr) {
4942
- if (!parentExpr) return `'/'+${indexExpr}`
4943
- return `${parentExpr}+'/'+${indexExpr}`
4944
- }
4945
-
4946
4878
  // unevaluated* differs from additionalProperties/items only through
4947
4879
  // annotations contributed by in-place applicators at the same schema object.
4948
4880
  // When every applicator subschema is provably free of the annotating keywords
@@ -4989,64 +4921,6 @@ function unevalContributions (sub, kind, names, state, depth) {
4989
4921
  return true
4990
4922
  }
4991
4923
 
4992
- // A contribution that names only what the node's own adjacent keywords
4993
- // already evaluate unconditionally changes nothing: a key in the node's own
4994
- // `properties` is evaluated whether or not the branch that also names it
4995
- // passes, and a branch prefix no longer than the node's own is covered
4996
- // position for position. Anything beyond those bounds is conditional
4997
- // evaluation the plain counterpart cannot express, and the node stays with
4998
- // the interpreted engine.
4999
- function unevalLocalOk (node, key) {
5000
- if (node[key] !== false) return false
5001
- if (node.$ref !== undefined || node.$dynamicRef !== undefined || node.$recursiveRef !== undefined) return false
5002
- if (node.dependentSchemas !== undefined || node.dependencies !== undefined) return false
5003
- const kind = key === 'unevaluatedProperties' ? 'props' : 'items'
5004
- if (kind === 'props' && node.patternProperties !== undefined) return false
5005
- if (kind === 'items' && node.contains !== undefined) return false
5006
- const names = new Set()
5007
- const state = { prefix: 0 }
5008
- for (const k of ['allOf', 'anyOf', 'oneOf']) {
5009
- if (node[k] !== undefined) {
5010
- if (!Array.isArray(node[k])) return false
5011
- for (const b of node[k]) if (!unevalContributions(b, kind, names, state, 0)) return false
5012
- }
5013
- }
5014
- for (const k of ['if', 'then', 'else']) {
5015
- if (node[k] !== undefined && !unevalContributions(node[k], kind, names, state, 0)) return false
5016
- }
5017
- if (kind === 'props') {
5018
- const own = node.properties && typeof node.properties === 'object' ? node.properties : {}
5019
- for (const n of names) if (!Object.prototype.hasOwnProperty.call(own, n)) return false
5020
- } else {
5021
- const ownPrefix = Array.isArray(node.prefixItems) ? node.prefixItems.length
5022
- : (Array.isArray(node.items) ? node.items.length : 0)
5023
- if (node.items !== undefined && !Array.isArray(node.items)) { /* all evaluated; any prefix is covered */ }
5024
- else if (state.prefix > ownPrefix) return false
5025
- }
5026
- return true
5027
- }
5028
-
5029
- // Every occurrence in the document must qualify or the generator declines the
5030
- // whole schema, exactly as the string scan it replaces did. The walk is
5031
- // generic and may visit data positions (an enum value, say); judging those is
5032
- // harmless, since a data node that fails the test only makes the answer more
5033
- // conservative and the generators never read data positions as schemas.
5034
- function unevalAllProvablyLocal (root, only) {
5035
- const seen = new Set()
5036
- const stack = [root]
5037
- while (stack.length) {
5038
- const n = stack.pop()
5039
- if (n === null || typeof n !== 'object' || seen.has(n)) continue
5040
- seen.add(n)
5041
- if (!Array.isArray(n)) {
5042
- if (only !== 'items' && n.unevaluatedProperties !== undefined && !unevalLocalOk(n, 'unevaluatedProperties')) return false
5043
- if (n.unevaluatedItems !== undefined && !unevalLocalOk(n, 'unevaluatedItems')) return false
5044
- }
5045
- for (const k of Array.isArray(n) ? n : Object.values(n)) stack.push(k)
5046
- }
5047
- return true
5048
- }
5049
-
5050
4924
  // Detect simple prefix patterns like "^x-", "^_", "^prefix" and generate fast charCodeAt checks
5051
4925
  // Returns a JS expression string or null if pattern is too complex
5052
4926
  function fastPrefixCheck(pattern, keyVar) {
@@ -5091,2794 +4965,135 @@ function genCharCodeSwitch(keys, v) {
5091
4965
  return `for(var _k in ${v}){switch(_k.charCodeAt(0)){${cases}default:break}return false}`
5092
4966
  }
5093
4967
 
5094
- // --- Error-collecting codegen: same checks, but pushes errors instead of returning false ---
5095
- // Returns a function: (data, allErrors) => { valid, errors }
5096
- // Valid path is still fast — only error path does extra work.
5097
- function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts) {
5098
- const inputSchema = schema
5099
- schema = prepareForCodegen(schema, schemaMap)
5100
- // unevaluated* is generated only where it is provably its plain counterpart
5101
- // (see unevalLocalOk); any occurrence that is not keeps the whole schema on
5102
- // the interpreted engine's error path, exactly as before.
5103
- if (typeof schema === 'object' && schema !== null) {
5104
- const s = JSON.stringify(schema)
5105
- if ((s.includes('unevaluatedProperties') || s.includes('unevaluatedItems')) && !unevalAllProvablyLocal(schema)) return null
5106
- }
5107
- if (typeof schema === 'boolean') {
5108
- return schema
5109
- ? () => ({ valid: true, errors: [] })
5110
- : () => ({ valid: false, errors: [{ keyword: 'not', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
4968
+ // The verdict form of genUnevaluatedDynamicC: the same set, built the same
4969
+ // way, and the answer false at the first key outside it.
4970
+ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj, nonObjApplicators) {
4971
+ if (schema.additionalProperties !== undefined) {
4972
+ if (nonObjApplicators) throw DECLINE
4973
+ return
5111
4974
  }
5112
- if (typeof schema !== 'object' || schema === null) return null
5113
- if (!sharedCodegenGate(schema, schemaMap)) return null
5114
- if (schema.patternProperties) {
5115
- for (const [pat, sub] of Object.entries(schema.patternProperties)) {
5116
- if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
5117
- }
4975
+ const ui = ctx.varCounter++
4976
+ const S = `_ev${ui}`
4977
+ const body = [`const ${S}=[]`]
4978
+ addLocalAnnotations(schema, v, S, body, ctx)
4979
+ // With the anyOf and oneOf blocks moved here (see genCodeNode), their
4980
+ // passing branches are counted by the annotation pass and judged from the
4981
+ // count: a oneOf holds with exactly one, an anyOf with at least one.
4982
+ const counts = nonObjApplicators ? { anyOf: `_ac${ui}`, oneOf: `_oc${ui}`, oneOfMask: null, of: null, verdict: true } : null
4983
+ if (counts) body.push(`let ${counts.anyOf}=0,${counts.oneOf}=0`)
4984
+ addTopApplicatorAnnotations(schema, v, S, body, ctx, counts)
4985
+ if (counts) {
4986
+ if (Array.isArray(schema.anyOf)) body.push(`if(${counts.anyOf}<1)return false`)
4987
+ if (Array.isArray(schema.oneOf)) body.push(`if(${counts.oneOf}!==1)return false`)
5118
4988
  }
5119
- if (schema.dependentSchemas) {
5120
- for (const sub of Object.values(schema.dependentSchemas)) {
5121
- if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
5122
- }
4989
+ const kVar = `_uk${ui}`
4990
+ const up = schema.unevaluatedProperties
4991
+ if (up === false) {
4992
+ body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar}))return false`)
4993
+ } else if (typeof up === 'object' && up !== null) {
4994
+ const sub = []
4995
+ genCode(up, `${v}[${kVar}]`, sub, ctx)
4996
+ if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${sub.join('\n ')}\n }`)
4997
+ } else {
4998
+ throw DECLINE
5123
4999
  }
5124
- if (schema.propertyNames && typeof schema.propertyNames === 'object' && !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return null
5000
+ // A value that is not an object skips the pass, so the blocks moved here
5001
+ // run for it in the `else`; a value known to be an object has no `else`.
5002
+ const other = nonObjApplicators && nonObjApplicators.length && !isObj ? `else{${nonObjApplicators.join('\n ')}}` : ''
5003
+ lines.push(isObj ? `{${body.join(';')}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body.join(';')}}${other}`)
5004
+ }
5125
5005
 
5126
- // Build anchors map for $ref/#anchor and $dynamicRef resolution
5127
- const eRootDefs = schema.$defs || schema.definitions || null
5128
- const eAnchors = {}
5129
- if (schema.$dynamicAnchor) eAnchors['#' + schema.$dynamicAnchor] = schema
5130
- if (schema.$anchor) eAnchors['#' + schema.$anchor] = schema
5131
- if (typeof schema.$id === 'string' && schema.$id.startsWith('#')) eAnchors[schema.$id] = schema
5132
- if (eRootDefs) {
5133
- for (const def of Object.values(eRootDefs)) {
5134
- if (def && typeof def === 'object') {
5135
- if (def.$dynamicAnchor) eAnchors['#' + def.$dynamicAnchor] = def
5136
- if (def.$anchor) eAnchors['#' + def.$anchor] = def
5137
- if (typeof def.$id === 'string' && def.$id.startsWith('#')) eAnchors[def.$id] = def
5138
- }
5139
- }
5006
+ // The names a node's own keywords evaluate, pushed onto the array `S`:
5007
+ // present declared properties, keys matching a pattern, and every key when
5008
+ // additionalProperties is there.
5009
+ function addLocalAnnotations(node, v, S, out, ctx) {
5010
+ if (node.additionalProperties !== undefined) {
5011
+ out.push(`for(const _ka in ${v})${S}.push(_ka)`)
5012
+ return
5140
5013
  }
5141
- if (schemaMap) {
5142
- for (const ext of schemaMap.values()) {
5143
- if (ext && typeof ext === 'object') {
5144
- if (ext.$dynamicAnchor && !eAnchors['#' + ext.$dynamicAnchor]) eAnchors['#' + ext.$dynamicAnchor] = ext
5145
- if (ext.$anchor && !eAnchors['#' + ext.$anchor]) eAnchors['#' + ext.$anchor] = ext
5146
- if (typeof ext.$id === 'string' && ext.$id.startsWith('#') && !eAnchors[ext.$id]) eAnchors[ext.$id] = ext
5147
- }
5014
+ for (const k of Object.keys(node.properties || {})) out.push(`if(Object.prototype.hasOwnProperty.call(${v},${JSON.stringify(k)}))${S}.push(${JSON.stringify(k)})`)
5015
+ const pats = Object.keys(node.patternProperties || {})
5016
+ if (pats.length) {
5017
+ const tests = []
5018
+ const kv = `_pk${ctx.varCounter++}`
5019
+ for (const pat of pats) {
5020
+ const fast = fastPrefixCheck(pat, kv)
5021
+ if (fast) { tests.push(fast); continue }
5022
+ const ri = ctx.varCounter++
5023
+ ctx.closureVars.push(`_re${ri}`)
5024
+ ctx.closureVals.push(safeReClosure(ctx, pat))
5025
+ tests.push(`_re${ri}.test(${kv})`)
5148
5026
  }
5027
+ out.push(`for(const ${kv} in ${v})if(${tests.join('||')})${S}.push(${kv})`)
5149
5028
  }
5029
+ }
5150
5030
 
5151
- // As in the boolean generator: a reference this path cannot resolve or follow
5152
- // emits no lines, and an empty program is returned below as always-valid.
5153
- if (hasUnresolvableRef(schema, eRootDefs, eAnchors, schemaMap, new Set())) return null
5154
- if (needsBaseTracking(schema, schemaMap, new Set())) return null
5155
-
5156
- const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, shared: [], refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootE: schema, rootSchema: inputSchema, userFormats: userFormats || null,
5157
- // Custom format checkers referenced by the body, bound as
5158
- // closure parameters below the way the other entry points do.
5159
- closureVars: [], closureVals: [],
5160
- sourceMap: (sourceOpts && sourceOpts.sourceMap && sourceOpts.schemaFile)
5161
- ? { file: sourceOpts.schemaFile, map: sourceOpts.sourceMap, frames: sourceOpts.frames || null }
5162
- : null }
5163
- ctx.helperCode.push('const _cpLen=s=>{let n=0;for(const _ of s)n++;return n}')
5164
- const lines = []
5165
- try {
5166
- genCodeE(schema, 'd', '', lines, ctx, '#')
5167
- } catch (e) {
5168
- if (e === DECLINE) return null
5169
- throw e
5031
+ // What the applicators of the node that owns the unevaluatedProperties add.
5032
+ // Their validity is checked by the code around this; here a branch only
5033
+ // contributes, through its annotation function, when it passes, and a oneOf
5034
+ // only with exactly one passing branch. An annotation function that fails
5035
+ // leaves `S` as it found it.
5036
+ function addTopApplicatorAnnotations(node, v, S, out, ctx, counts) {
5037
+ if (node.$dynamicRef !== undefined || node.$recursiveRef !== undefined || node.dependencies !== undefined) throw DECLINE
5038
+ for (const b of node.allOf || []) out.push(`${emitAnnot(b, ctx)}(${v},${S})`)
5039
+ // With `counts`, the passing branches of anyOf and oneOf are counted into
5040
+ // variables the caller declared, so the keyword's own error code further
5041
+ // down can skip its branches when the count already says it holds (see
5042
+ // emitBranchCollapse).
5043
+ if (Array.isArray(node.anyOf)) {
5044
+ if (counts) out.push(`${counts.anyOf}=0;${node.anyOf.map((b) => `if(${emitAnnot(b, ctx)}(${v},${S}))${counts.anyOf}++;`).join('')}`)
5045
+ else for (const b of node.anyOf) out.push(`${emitAnnot(b, ctx)}(${v},${S})`)
5170
5046
  }
5171
- // Inline branch-collapse helper only when the schema actually used a
5172
- // oneOf/anyOf branch path (tracked via ctx.usesBranchCollapse). Keeps the
5173
- // AOT output small for schemas that don't compose. Standalone modules can't
5174
- // require('./branch-collapse'), so the helper body is embedded verbatim.
5175
- if (ctx.usesBranchCollapse) {
5176
- ctx.helperCode.push(require('./branch-collapse').embedSource())
5047
+ if (Array.isArray(node.oneOf)) {
5048
+ const t = counts ? counts.oneOf : `_oc${ctx.varCounter++}`
5049
+ // Which branches passed, as bits, for a collapse that has to name them.
5050
+ const mask = counts && node.oneOf.length <= 30 ? counts.oneOfMask : null
5051
+ // In a verdict (counts.verdict), a second passing branch is the answer.
5052
+ const over = counts && counts.verdict ? `if(${t}>1)return false;` : ''
5053
+ out.push(`{const ${t}m=${S}.length;${counts ? '' : 'let '}${t}=0;${node.oneOf.map((b, i) => `if(${emitAnnot(b, ctx)}(${v},${S})){${t}++;${over}${mask ? `${mask}|=${1 << i};` : ''}}`).join('')}if(${t}!==1)_rb(${S},${t}m)}`)
5177
5054
  }
5178
- if (lines.length === 0) return (d) => ({ valid: true, errors: [] })
5179
-
5180
- const checkStr = lines.join('\n ')
5181
- const defSetsE = ctx.defFns ? Array.from(ctx.defFns.values()) : []
5182
- const needsGuardE = ctx.usesRecursion || defSetsE.length > 0
5183
- const guardStrE = needsGuardE ? emitGuardState() : ''
5184
- const helpersE = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
5185
-
5186
- let body
5187
- if (ctx.usesRecursion) {
5188
- // The abandoned first pass may have pushed errors, so the retry starts
5189
- // from an empty list and the reported errors come from the guarded run
5190
- // alone.
5191
- body = `const _e=[];\n ` + guardStrE + helpersE +
5192
- emitRootGuard('_validateE', 'd,_all,_e', '') +
5193
- `function _validateE_b(d,_all,_e){\n ${checkStr}\n }\n ` +
5194
- emitGuardedRun('_validateE(d,_all,_e)', '_e.length=0;', defSetsE) +
5195
- `return{valid:_e.length===0,errors:_e}`
5196
- } else if (defSetsE.length > 0) {
5197
- body = `const _e=[];\n ` + guardStrE + helpersE +
5198
- `function _bodyE(d,_all,_e){\n ${checkStr}\n }\n ` +
5199
- emitGuardedRun('_bodyE(d,_all,_e)', '_e.length=0;', defSetsE) +
5200
- `return{valid:_e.length===0,errors:_e}`
5201
- } else {
5202
- body = `const _e=[];\n ` + helpersE + checkStr +
5203
- `\n return{valid:_e.length===0,errors:_e}`
5055
+ if (node.if !== undefined) {
5056
+ const thenCall = node.then !== undefined ? `${emitAnnot(node.then, ctx)}(${v},${S})` : ''
5057
+ const elseCall = node.else !== undefined ? `${emitAnnot(node.else, ctx)}(${v},${S})` : ''
5058
+ out.push(`if(${emitAnnot(node.if, ctx)}(${v},${S})){${thenCall}}else{${elseCall}}`)
5204
5059
  }
5205
- try {
5206
- let fn
5207
- // Custom format checkers come in as leading parameters, bound once here,
5208
- // so the body's `_uf_<name>(...)` calls resolve. Without a binding the
5209
- // first invalid document threw a ReferenceError instead of reporting.
5210
- const cvars = ctx.closureVars
5211
- const cvals = ctx.closureVals
5212
- // The helpers are constants: the uri tables, the compiled patterns, the
5213
- // branch-collapse functions, the name sets. In `body` they sit inside the
5214
- // function and were rebuilt on every call, which on a product schema with
5215
- // six URLs and one anyOf cost about 450 ns before a single check ran, more
5216
- // than every check and every error together. Here they run once, in a
5217
- // factory, as the verdict function's preamble does; with $defs or
5218
- // recursion too, below. `_errSource` stays `body` either way, for the
5219
- // emitters.
5220
- // The factory builds the helpers once and returns the error function.
5221
- // With $defs or recursion the helpers are the definitions' functions and
5222
- // the cycle guard's state lives beside them; the guarded run resets that
5223
- // state at its start, and a call made while one is running (a custom
5224
- // keyword validating with the same validator) gets a function from a fresh
5225
- // factory call instead of sharing it. `_errFactory` carries the same text
5226
- // to the standalone emitter.
5227
- let factoryBody
5228
- if (needsGuardE) {
5229
- const attempt = ctx.usesRecursion ? '_validateE(d,_all,_e)' : '_bodyE(d,_all,_e)'
5230
- const runner = ctx.usesRecursion
5231
- ? emitRootGuard('_validateE', 'd,_all,_e', '') + `function _validateE_b(d,_all,_e){\n ${checkStr}\n }\n `
5232
- : `function _bodyE(d,_all,_e){\n ${checkStr}\n }\n `
5233
- factoryBody = guardStrE + helpersE + runner + `return function(d,_all){const _e=[];\n ` +
5234
- emitGuardedRun(attempt, '_e.length=0;', defSetsE) + `return{valid:_e.length===0,errors:_e}}`
5235
- } else {
5236
- factoryBody = helpersE + 'return function(d,_all){const _e=[];\n ' + checkStr +
5237
- '\n return{valid:_e.length===0,errors:_e}}'
5238
- }
5239
- // For the runtime, the literals take the legacy shape directly. `code` and
5240
- // `docUrl` are there for standalone modules, which return errors as they
5241
- // are; the runtime derives both from the keyword when it enriches, and
5242
- // orders errors by schemaPath, so the literals carried three fields that
5243
- // every read then copied the error to remove. The patterns are this
5244
- // generator's own spelling; the collapse helper writes its fields with a
5245
- // space and keeps them.
5246
- if (sourceOpts && sourceOpts.runtimeShape) {
5247
- 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, '')
5248
- body = shape(body)
5249
- factoryBody = shape(factoryBody)
5250
- }
5251
- const hoistable = !ctx.usesRecursion && defSetsE.length === 0 && helpersE !== ''
5252
- if (hoistable || needsGuardE) {
5253
- const params = [...cvars, '__ataSafeRe']
5254
- const args = [...cvals, compileSafe]
5255
- const make = new Function(...params, factoryBody)
5256
- const main = make(...args)
5257
- if (needsGuardE) {
5258
- let busy = false
5259
- fn = (d, _all) => {
5260
- if (busy) return make(...args)(d, _all)
5261
- busy = true
5262
- try { return main(d, _all) } finally { busy = false }
5263
- }
5264
- } else {
5265
- fn = main
5266
- }
5267
- } else if (ctx.usesSafeRe) {
5268
- // Inlined helperCode references __ataSafeRe; bind the safe-regex factory.
5269
- // Standalone keeps the inline _errSource — the embed defines __ataSafeRe.
5270
- const built = new Function(...cvars, '__ataSafeRe', 'd', '_all', body)
5271
- fn = cvars.length
5272
- ? (d, _all) => built(...cvals, compileSafe, d, _all)
5273
- : (d, _all) => built(compileSafe, d, _all)
5274
- } else if (cvars.length) {
5275
- const built = new Function(...cvars, 'd', '_all', body)
5276
- fn = (d, _all) => built(...cvals, d, _all)
5277
- } else {
5278
- fn = new Function('d', '_all', body)
5279
- }
5280
- fn._errSource = body
5281
- fn._errFactory = factoryBody
5282
- fn._errGuarded = needsGuardE
5283
- fn._sharedHelpers = ctx.shared ? ctx.shared.slice() : []
5284
- fn._usesSafeRe = !!ctx.usesSafeRe
5285
- if (cvars.length) {
5286
- fn._formatClosures = cvars.map((name, i) => {
5287
- let format = null
5288
- for (const key of Object.keys(ctx.userFormats)) {
5289
- if (ctx.userFormats[key] === cvals[i]) { format = key; break }
5290
- }
5291
- return { name, fn: cvals[i], format }
5292
- })
5293
- }
5294
- return fn
5295
- } catch (e) {
5296
- // Declining here is safe (the interpreter takes over) but silent, which
5297
- // is how every past regression in this file hid. The env hook makes the
5298
- // decline visible without changing behavior.
5299
- if (process.env.ATA_DEBUG_ERRGEN) console.error('error codegen declined:', e.message)
5300
- return null
5060
+ if (node.dependentSchemas && typeof node.dependentSchemas === 'object') {
5061
+ for (const [k, ds] of Object.entries(node.dependentSchemas)) out.push(`if(Object.prototype.hasOwnProperty.call(${v},${JSON.stringify(k)}))${emitAnnot(ds, ctx)}(${v},${S})`)
5301
5062
  }
5063
+ if (typeof node.$ref === 'string') out.push(`${emitAnnot(refTargetFor(node.$ref, ctx), ctx)}(${v},${S})`)
5302
5064
  }
5303
5065
 
5304
- // Emit codegen for oneOf/anyOf that runs each branch with error collection,
5305
- // aggregates results, and uses __ataCollapse to surface a single best-branch
5306
- // error. Wraps the existing per-branch evaluation; does not replace it.
5307
- const BRANCH_VERDICT_MAX = 4096
5308
-
5309
- // What undoing a trial emission needs: during one, the context's arrays only
5310
- // grow and its maps and sets only gain keys (a reference pushed onto refStack
5311
- // by a declined branch is such a key), so their lengths and sizes are enough,
5312
- // and what was added is cut off the end, in insertion order. Copying them
5313
- // instead made the trial quadratic: 297 oneOf and anyOf on apollo-router,
5314
- // each copying a helper list thousands long.
5315
- function snapshotCtx (ctx) {
5316
- const out = new Map()
5317
- for (const k of Object.keys(ctx)) {
5318
- const x = ctx[k]
5319
- if (Array.isArray(x)) out.set(k, { kind: 'a', ref: x, n: x.length })
5320
- else if (x instanceof Map || x instanceof Set) out.set(k, { kind: 'c', ref: x, n: x.size })
5321
- else out.set(k, { kind: 'v', ref: x })
5322
- }
5323
- return out
5324
- }
5325
- function restoreCtx (ctx, saved) {
5326
- for (const k of Object.keys(ctx)) if (!saved.has(k)) delete ctx[k]
5327
- for (const [k, e] of saved) {
5328
- if (e.kind === 'a') { e.ref.length = Math.min(e.ref.length, e.n); ctx[k] = e.ref }
5329
- else if (e.kind === 'c') {
5330
- const extra = e.ref.size - e.n
5331
- if (extra > 0) {
5332
- const added = []
5333
- let i = 0
5334
- for (const key of e.ref.keys()) if (i++ >= e.n) added.push(key)
5335
- for (const key of added) e.ref.delete(key)
5336
- }
5337
- ctx[k] = e.ref
5338
- } else ctx[k] = e.ref
5339
- }
5066
+ function refTargetFor(ref, ctx) {
5067
+ const m = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
5068
+ const t = m && ctx.rootDefs ? ctx.rootDefs[m[1]] : undefined
5069
+ if (t === undefined) throw DECLINE
5070
+ return t
5340
5071
  }
5341
5072
 
5342
- // Whether a node holds a reference anywhere below it.
5343
- function holdsRef (node, seen = new Set()) {
5344
- if (node === null || typeof node !== 'object' || seen.has(node)) return false
5345
- seen.add(node)
5346
- if (typeof node.$ref === 'string' || typeof node.$dynamicRef === 'string') return true
5347
- for (const k of Object.keys(node)) if (holdsRef(node[k], seen)) return true
5348
- return false
5349
- }
5073
+ // The keywords an annotation function evaluates itself, through its children;
5074
+ // the rest of a subschema is checked by a plain verdict.
5075
+ const ANNOT_APPLICATORS = new Set(['allOf', 'anyOf', 'oneOf', 'if', 'then', 'else', 'dependentSchemas', '$ref', 'not', 'unevaluatedProperties'])
5350
5076
 
5351
- // Every `$ref` under `node`, outside the values of enum, const, default and
5352
- // examples. Null when one is not a plain pointer into the root's definitions
5353
- // (after prepareForCodegen those are what remain, besides `#` itself) or
5354
- // when the node carries a scope keyword of its own, which would not mean the
5355
- // same thing under a wrapper.
5356
- const BRANCH_SCOPE_KEYS = new Set(['$id', '$anchor', '$dynamicAnchor', '$dynamicRef', '$defs', 'definitions', '$schema'])
5357
- function branchRefsLocal (node, defsKey, seen) {
5358
- if (node === null || typeof node !== 'object') return true
5359
- if (seen.has(node)) return true
5360
- seen.add(node)
5361
- if (Array.isArray(node)) { for (const x of node) if (!branchRefsLocal(x, defsKey, seen)) return false; return true }
5362
- for (const k of Object.keys(node)) {
5363
- if (SKIP_VALUE_KEYS.has(k)) continue
5364
- if (BRANCH_SCOPE_KEYS.has(k)) return false
5365
- const v = node[k]
5366
- if (k === '$ref') { if (typeof v !== 'string' || !v.startsWith('#/' + defsKey + '/')) return false; continue }
5367
- if (!branchRefsLocal(v, defsKey, seen)) return false
5368
- }
5369
- return true
5370
- }
5371
- // One verdict function per branch, compiled on its own with the root's
5372
- // definitions under it, or null when a branch cannot be wrapped that way
5373
- // (then the site keeps its error functions for the decision). The
5374
- // definitions themselves are checked once per root.
5375
- const _defsLocal = new WeakMap()
5376
- function compileBranchVerdicts (branches, ctx) {
5377
- const root = ctx.rootC
5378
- if (root === null || typeof root !== 'object') return null
5379
- const defsKey = root.$defs && typeof root.$defs === 'object' ? '$defs' : (root.definitions && typeof root.definitions === 'object' ? 'definitions' : null)
5380
- if (defsKey !== null) {
5381
- let ok = _defsLocal.get(root)
5382
- if (ok === undefined) { ok = branchRefsLocal(root[defsKey], defsKey, new Set()); _defsLocal.set(root, ok) }
5383
- if (!ok) return null
5077
+ // A function `(value, S) => valid` for one subschema: whether it passes, and
5078
+ // when it does, what it evaluated pushed onto S; when it does not, S is left
5079
+ // as it was. Validity is worked out bottom up: the subschema's own keywords
5080
+ // by a verdict of the subschema without its applicators, each applicator from
5081
+ // its branches' annotation functions. Asking each branch for a full verdict
5082
+ // and then again for its annotations repeated every nested branch once per
5083
+ // level, which made nested references the slowest shape there was.
5084
+ function emitAnnot(sub, ctx) {
5085
+ if (sub === true) return '_anT'
5086
+ if (sub === false) return '_anF'
5087
+ if (sub === null || typeof sub !== 'object') throw DECLINE
5088
+ if (!ctx.annFns) {
5089
+ ctx.annFns = new Map()
5090
+ if (ctx.sharedRuntime) bindRt(ctx, ['_anT', '_anF', '_rb'])
5091
+ else (ctx.preamble || ctx.helperCode).push('function _anT(){return true}function _anF(){return false}function _rb(S,m){if(S.length!==m)S.length=m}')
5384
5092
  }
5385
- const out = []
5386
- for (const sub of branches) {
5387
- if (sub === true) { out.push(() => true); continue }
5388
- if (sub === false) { out.push(() => false); continue }
5389
- if (sub === null || typeof sub !== 'object' || Array.isArray(sub)) return null
5390
- if (!branchRefsLocal(sub, defsKey === null ? '$defs' : defsKey, new Set())) return null
5391
- const wrapped = Object.assign({}, sub)
5392
- if (defsKey !== null) wrapped[defsKey] = root[defsKey]
5393
- if (typeof ctx.rootSchema === 'object' && ctx.rootSchema !== null && typeof ctx.rootSchema.$schema === 'string') wrapped.$schema = ctx.rootSchema.$schema
5394
- let fn = null
5395
- try { fn = compileToJSCodegen(wrapped, ctx.schemaMap, ctx.userFormats) } catch (e) { if (e !== DECLINE) throw e }
5396
- if (typeof fn !== 'function') return null
5397
- out.push(fn)
5398
- }
5399
- return out
5400
- }
5401
-
5402
- // A verdict for one subtree of the combined function, as a function in the
5403
- // preamble, so the collecting code of that subtree runs only for a value the
5404
- // verdict rejects. The collecting code is slower than the verdict on an
5405
- // accepted value (it keeps paths, builds nothing it can avoid but tests more
5406
- // than the verdict's cheapest-first order), so without this the whole
5407
- // function was put behind one verdict of the whole document (`_VF`), and a
5408
- // rejected document was validated twice in full. With a guard at every
5409
- // array element and every object-valued property, an accepted value costs
5410
- // its verdict and a rejected one costs its verdict plus the collection of
5411
- // the subtree that failed. Only in the combined function, the runtime path;
5412
- // null for a subtree whose verdict declines, is a leaf, or is too large.
5413
- const GUARD_KEYS = ['oneOf', 'anyOf', 'allOf', '$ref', 'items', 'prefixItems', 'properties', 'patternProperties', 'additionalProperties', 'not', 'if', 'contains', 'dependentSchemas', 'dependencies', 'propertyNames', 'unevaluatedProperties', 'unevaluatedItems']
5414
- const GUARD_MAX = 16 * 1024
5415
- const GUARD_BUDGET = 512 * 1024
5416
- // Whether a schema node is worth a guard of its own: it applies something
5417
- // beyond leaf checks. A node whose children are themselves worth guarding is
5418
- // not guarded; the guards go on the children. Guarding both ran the parent's
5419
- // verdict in full and then, when it failed, every child's verdict again: on a
5420
- // list of 1000 users with one bad one that was two verdicts of the list
5421
- // before the one user was collected.
5422
- const weighty = (n) => n !== null && typeof n === 'object' && !Array.isArray(n) && GUARD_KEYS.some((k) => n[k] !== undefined)
5423
- function childrenWeighty (sub) {
5424
- if (weighty(sub.items)) return true
5425
- if (Array.isArray(sub.prefixItems) && sub.prefixItems.some(weighty)) return true
5426
- if (weighty(sub.additionalProperties)) return true
5427
- if (sub.properties && typeof sub.properties === 'object') for (const k of Object.keys(sub.properties)) if (weighty(sub.properties[k])) return true
5428
- if (sub.patternProperties && typeof sub.patternProperties === 'object') for (const k of Object.keys(sub.patternProperties)) if (weighty(sub.patternProperties[k])) return true
5429
- return false
5430
- }
5431
- // Below this much verdict source the guard is written into the function
5432
- // itself as a labelled block (`gN:{...break gN...}`), not as a function it
5433
- // calls: in the first thousands of calls of a process, before the optimizing
5434
- // compiler has inlined anything, each call is paid in full, and a schema
5435
- // with six small guards paid six. Above it the guard stays a function, so
5436
- // one large subtree does not swell the function around it.
5437
- const GUARD_INLINE_MAX = 2048
5438
- function subtreeGuard (sub, ctx, gv) {
5439
- if (!ctx.inCombined || !weighty(sub) || childrenWeighty(sub)) return null
5440
- if ((ctx.guardChars || 0) >= GUARD_BUDGET) return null
5441
- // The verdict generator registers the definition functions it writes under
5442
- // `defFns`, and so does the error generator for the branch functions of
5443
- // this same program: shared, the verdict found the error generator's
5444
- // `_defE` function under the definition's name and called it with one
5445
- // argument. The guards keep a registry of their own.
5446
- if (!ctx.guardDefFns) ctx.guardDefFns = new Map()
5447
- const saved = snapshotCtx(ctx)
5448
- const outerDefFns = ctx.defFns
5449
- const outerShare = ctx.shareDefs
5450
- ctx.defFns = ctx.guardDefFns
5451
- // Definitions referenced from several places are written once, as
5452
- // functions in the preamble (the verdict generator's shared mode), not
5453
- // expanded into every guard that reaches them: expanded, the guards of a
5454
- // 25-definition API schema came to 55 KB of source in a 133 KB function,
5455
- // and a function that size reaches the optimizing compiler late.
5456
- ctx.shareDefs = true
5457
- try {
5458
- const bl = []
5459
- try { nestedGenCode(sub, gv, bl, ctx) } finally { ctx.defFns = outerDefFns; ctx.shareDefs = outerShare }
5460
- if (bl.length === 0) { restoreCtx(ctx, saved); return null }
5461
- const body = bl.join(';')
5462
- // The verdict generator's recursion names (`_validate`, the cycle guard
5463
- // state) are not this function's; a subtree that reaches them stays
5464
- // unguarded.
5465
- const added = ctx.preamble.slice(saved.get('preamble').n).join(';')
5466
- if (body.length > GUARD_MAX || body.includes(SP_PLACEHOLDER) || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(body + added)) { restoreCtx(ctx, saved); return null }
5467
- ctx.guardChars = (ctx.guardChars || 0) + body.length
5468
- if (body.length <= GUARD_INLINE_MAX) return { body }
5469
- const name = `_vg${ctx.varCounter++}`
5470
- ctx.helperCode.push(`const ${name}=function(${gv}){${body};return true}`)
5471
- return { name }
5472
- } catch (e) {
5473
- if (e !== DECLINE) throw e
5474
- restoreCtx(ctx, saved)
5475
- return null
5476
- }
5477
- }
5478
- // The collecting code for `sub` at `v`, behind its guard when there is one.
5479
- function genGuardedC (sub, v, pathExpr, lines, ctx, schemaPrefix) {
5480
- const gi = ctx.varCounter++
5481
- const gv = `_gv${gi}`
5482
- const g = subtreeGuard(sub, ctx, gv)
5483
- if (g === null) { genCodeC(sub, v, pathExpr, lines, ctx, schemaPrefix); return }
5484
- if (g.name !== undefined) lines.push(`if(!${g.name}(${v})){`)
5485
- else {
5486
- // The verdict's exits become jumps out of the block: a failure leaves
5487
- // the flag set, success clears it. replaceTopLevel keeps nested
5488
- // closures' returns as they are (see the hybrid rewrite).
5489
- lines.push(`{const ${gv}=${v};let _g${gi}=1;g${gi}:{${replaceTopLevel(g.body, `{_g${gi}=0;break g${gi}}`, `break g${gi}`)};_g${gi}=0}if(_g${gi}){`)
5490
- }
5491
- ctx.guardDepth = (ctx.guardDepth || 0) + 1
5492
- try { genCodeC(sub, v, pathExpr, lines, ctx, schemaPrefix) } finally { ctx.guardDepth-- }
5493
- lines.push(g.name !== undefined ? '}' : '}}')
5494
- }
5495
-
5496
- // A branch's error-collecting body rewritten to count what it would have
5497
- // collected: each `_e.push({keyword:'k',...})` becomes a count and a severity
5498
- // (lib/branch-collapse.js, __ataScore), and the body returns the score the
5499
- // collapse would compute from the errors. When no branch of a oneOf or anyOf
5500
- // holds, the collapse reports the closest branch's errors; it used to run
5501
- // every branch's error function to score them, building every branch's
5502
- // errors to keep one branch's. Now the counting bodies score, and only the
5503
- // closest branch's error function runs. Null for a body the rewrite cannot
5504
- // follow (a shared definition call, a nested collapse, a push that is not a
5505
- // literal); that site keeps the old path.
5506
- function countingBody (body) {
5507
- // A nested oneOf or anyOf that does not hold is one collapsed error in the
5508
- // branch's list (keyword oneOf or anyOf, severity 4): its marked section,
5509
- // which would run the inner branches to build it, becomes that count.
5510
- // Two kinds of section: CB is one error whatever the branches would say;
5511
- // CU is a section the copy cannot score (the value is not the one the
5512
- // counts were taken for), so the copy answers -1 and the picker runs the
5513
- // branches as before.
5514
- while (true) {
5515
- const b1 = body.indexOf('/*CB*/'), b2 = body.indexOf('/*CU*/')
5516
- const b = b1 < 0 ? b2 : b2 < 0 ? b1 : Math.min(b1, b2)
5517
- if (b < 0) break
5518
- const unknown = b === b2
5519
- let depth = 1
5520
- let j = b + 6
5521
- while (j < body.length && depth > 0) {
5522
- if (body.startsWith('/*CB*/', j) || body.startsWith('/*CU*/', j)) { depth++; j += 6; continue }
5523
- if (body.startsWith('/*CE*/', j)) { depth--; j += 6; continue }
5524
- j++
5525
- }
5526
- if (depth !== 0) return null
5527
- body = body.slice(0, b) + (unknown ? 'return -1' : '(_n++,_s+=4)') + body.slice(j)
5528
- }
5529
- if (/\b(?:_defE\d|__ataCollapse|__ataMulti|_brf\d|_bsf\d)/.test(body)) return null
5530
- const SEV = require('./branch-collapse').SEVERITY
5531
- let out = ''
5532
- let i = 0
5533
- const head = 'const _e=[];'
5534
- if (!body.startsWith(head)) return null
5535
- out += 'let _n=0,_s=0;'
5536
- i = head.length
5537
- while (i < body.length) {
5538
- const ch = body[i]
5539
- if (ch === '"' || ch === "'" || ch === '`') { const j = skipString(body, i); out += body.slice(i, j); i = j; continue }
5540
- if (body.startsWith('_e.push(', i)) {
5541
- let j = i + 8
5542
- let depth = 1
5543
- while (j < body.length && depth > 0) {
5544
- const c = body[j]
5545
- if (c === '"' || c === "'" || c === '`') { j = skipString(body, j); continue }
5546
- if (c === '(' || c === '{' || c === '[') depth++
5547
- else if (c === ')' || c === '}' || c === ']') depth--
5548
- j++
5549
- }
5550
- if (depth !== 0) return null
5551
- const arg = body.slice(i + 8, j - 1)
5552
- // The literal may still carry its `code` (the shape pass strips it).
5553
- const m = /^\{(?:code:'ATA\d{4}',)?keyword:'([A-Za-z$][\w$]*)',/.exec(arg)
5554
- if (m === null) return null
5555
- out += `(_n++,_s+=${SEV[m[1]] || 4})`
5556
- i = j
5557
- continue
5558
- }
5559
- if (body.startsWith('return{valid:false,errors:_e}', i)) { out += 'return _n*100+_s'; i += 29; continue }
5560
- if (body.startsWith('return{valid:_e.length===0,errors:_e}', i)) { out += 'return _n*100+_s'; i += 37; continue }
5561
- if (body.startsWith('_e.length', i)) { out += '_n'; i += 9; continue }
5562
- out += ch
5563
- i++
5564
- }
5565
- if (/\b_e\b/.test(out.replace(/'[^']*'|"[^"]*"/g, ''))) return null
5566
- return out
5567
- }
5568
-
5569
- function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaPrefix) {
5570
- ctx.usesBranchCollapse = true
5571
- const fi = ctx.varCounter++
5572
- const branchKw = keyword
5573
- const branchSp = schemaPrefix + '/' + branchKw
5574
- // Per-branch error-collecting functions, each returning {valid,errors}.
5575
- // They take the value, its instance path, `_all` and, inside a definition
5576
- // written once for all its references, that reference's schema path, so
5577
- // they can be built once per call of the error function rather than each
5578
- // time the keyword is reached: on a list of 1723 tokens that is thousands
5579
- // of closure arrays per validation.
5580
- const fns = []
5581
- const titles = []
5582
- let usesSp = false
5583
- for (let i = 0; i < branches.length; i++) {
5584
- const sub = branches[i]
5585
- const subLines = []
5586
- const subSp = branchSp + '/' + i
5587
- if (typeof sub === 'object' && sub !== null) {
5588
- // A branch is a function of its own with a local `_e`; a collapse
5589
- // inside it reports there, not into the combined function's lists.
5590
- ctx.inBranchFn = (ctx.inBranchFn || 0) + 1
5591
- try {
5592
- genCodeE(sub, '_bv', '_bpth', subLines, ctx, subSp)
5593
- } finally {
5594
- ctx.inBranchFn--
5595
- }
5596
- } else if (sub === false) {
5597
- // A false branch never matches; it reports the same error the
5598
- // interpreter attaches to a false schema.
5599
- subLines.push(`_e.push({keyword:'not',instancePath:_bpth,schemaPath:'${subSp}'${ordinalField(ctx, `${subSp}`)},params:{},message:'boolean schema is false'})`)
5600
- }
5601
- const title = (sub && typeof sub === 'object' && typeof sub.title === 'string') ? sub.title : ''
5602
- titles.push(title)
5603
- let body = subLines.length === 0 ? '' : `const _e=[];${subLines.join(';')};return{valid:_e.length===0,errors:_e}`
5604
- if (body.includes(SP_PLACEHOLDER)) {
5605
- body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
5606
- if (body.includes(SP_PLACEHOLDER)) throw DECLINE
5607
- usesSp = true
5608
- }
5609
- fns.push(body)
5610
- }
5611
- const params = usesSp ? '_bv,_bpth,_all,_sp' : '_bv,_bpth,_all'
5612
- const fnArr = `_brf${fi}`
5613
- const resArr = `_brr${fi}`
5614
- const titleArr = JSON.stringify(titles)
5615
- const collapsed = `_brc${fi}`
5616
- const pp = pathExpr || '""'
5617
- ctx.helperCode.push(`const ${fnArr}=[${fns.map((b) => b === '' ? `function(){return{valid:true,errors:[]}}` : `function(${params}){${b}}`).join(',')}];const _bt${fi}=${titleArr}`)
5618
- const call = `${fnArr}[_bi](${v},_pp${fi},_all${usesSp ? ',_sp' : ''})`
5619
- // branchSp is source text for a quoted literal, with backslashes and quotes
5620
- // escaped; the ordinal is looked up by the path itself. Looked up by the
5621
- // text, a path through a pattern such as `^\\d+$` found no node, or the
5622
- // wrong one, and the collapsed error sorted out of place against the other
5623
- // engines.
5624
- // In the runtime shape there is no ordering key (the reader looks the path
5625
- // up), so the collapsed error is in its final shape as it is built.
5626
- const ord = ctx.noOrdinal ? 'null' : (() => { const o = ordinalFor(ctx.rootSchema, unescapeSp(branchSp)); return o === null ? 'null' : o })()
5627
- // In the combined function `_e` is created on the first error and every
5628
- // error is collected (`_all` is true there), so the report only pushes.
5629
- const report = ctx.inCombined && !ctx.inBranchFn
5630
- ? `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed})(_e=_ap(_A,_e,${collapsed},${apOrd(ctx, branchSp)}))`
5631
- : `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed}){_e.push(${collapsed});if(!_all)return{valid:false,errors:_e}}`
5632
- // Score first, collect once (countingBody), in the combined function where
5633
- // the collapsed error is built in its final shape (no ordering key).
5634
- let noneReport = null
5635
- if (ctx.inCombined && (ctx.noOrdinal ? 'null' : '') === 'null') {
5636
- const cbs = fns.map((b) => b === '' ? 'return 0' : countingBody(b))
5637
- if (!cbs.some((c) => c === null)) {
5638
- const sfx = usesSp ? ',_sp' : ''
5639
- const code = branchKw === 'oneOf' ? 'ATA4001' : 'ATA4003'
5640
- // `code: ` with the space: the runtime shape strips this generator's own
5641
- // `code:'ATA....',` from the literals and keeps the collapse's, which
5642
- // the reader expects to carry it (see the shape pass below).
5643
- const lit = `{code: '${code}',keyword:'${branchKw}',instancePath:_pp${fi}||'',path:_pp${fi}||'',schemaPath:'${branchSp}',message:'value matched 0 of ${branches.length} ${branchKw} variants',params:{variants:${branches.length},closest:_bi${fi},closestName:_bt${fi}[_bi${fi}]||('variant '+(_bi${fi}+1))},branchErrors:_br${fi}.errors}`
5644
- // In a branch function the error joins that function's list (the
5645
- // combined function's `_ap` and ordering state are not its).
5646
- const push = ctx.inBranchFn ? `_e.push(${lit});if(!_all)return{valid:false,errors:_e}` : `(_e=_ap(_A,_e,${lit},${apOrd(ctx, branchSp)}))`
5647
- if (cbs.length === 1) {
5648
- // One branch is the closest one: nothing to score, no counting copy.
5649
- // Scoring it ran the branch a third time (verdict, count, errors).
5650
- noneReport = `{let _bi${fi}=0;const _br${fi}=${fnArr}[0](${v},_pp${fi},_all${sfx});${push}}`
5651
- } else {
5652
- ctx.helperCode.push(cbs.map((b, i) => `const _bsf${fi}_${i}=function(${params}){${b}}`).join(';'))
5653
- const picks = cbs.map((_, i) => `{const _s=_bsf${fi}_${i}(${v},_pp${fi},_all${sfx});if(_s<0)_ng${fi}=1;else if(_s<_bs${fi}){_bs${fi}=_s;_bi${fi}=${i}}}`).join('')
5654
- // A copy that could not score (-1): every branch runs, as before.
5655
- const noneOld = branchKw === 'anyOf'
5656
- ? `const ${resArr}=[];for(let _bi=0;_bi<${branches.length};_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}`
5657
- : `const ${resArr}=[];for(let _bi=0;_bi<${branches.length};_bi++)${resArr}.push(${call});${report}`
5658
- noneReport = `{let _bi${fi}=0,_bs${fi}=Infinity,_ng${fi}=0;${picks}if(_ng${fi}){${noneOld}}else{const _br${fi}=${fnArr}[_bi${fi}](${v},_pp${fi},_all${sfx});${push}}}`
5659
- }
5660
- }
5661
- }
5662
- // The node's runtime unevaluatedProperties already counted how many of
5663
- // these branches pass, through the annotation functions, which answer the
5664
- // same validity. A oneOf with exactly one, an anyOf with any, holds and
5665
- // reports nothing, so its branches are not run a second time to say so.
5666
- // Only in the combined function proper, where that count is in scope.
5667
- // Inside a branch function only a factory-scope count (factoryCount) is
5668
- // in reach; the node's own counts are locals of the combined function.
5669
- const countedRaw = ctx.inCombined && ctx.branchCounts ? ctx.branchCounts.get(branches) : undefined
5670
- const counted = countedRaw !== undefined && (!ctx.inBranchFn || countedRaw.factory) ? countedRaw : undefined
5671
- const same = counted !== undefined ? `${v}!==null&&typeof ${v}==='object'&&${counted.of}===${v}` : ''
5672
- let guard = counted !== undefined
5673
- ? (keyword === 'anyOf' ? `if(!(${same}&&${counted.count}>=1))` : `if(!(${same}&&${counted.count}===1))`)
5674
- : ''
5675
- // More than one oneOf branch passed: the error names them and carries no
5676
- // branch errors, so it is built from the bits the count left.
5677
- if (guard !== '' && keyword === 'oneOf' && counted.mask && ctx.inCombined) {
5678
- const ord0 = ctx.noOrdinal ? 'null' : (() => { const o = ordinalFor(ctx.rootSchema, unescapeSp(branchSp)); return o === null ? 'null' : o })()
5679
- const multiC = `__ataMulti(_pi${fi},${branches.length},${pp},'${branchSp}',${ord0})`
5680
- const pushC = ctx.inBranchFn ? `_e.push(${multiC});if(!_all)return{valid:false,errors:_e}` : `(_e=_ap(_A,_e,${multiC},${apOrd(ctx, branchSp)}))`
5681
- // The marked section is one error in a counting copy (countingBody).
5682
- guard = `if(${same}&&${counted.count}>1){/*CB*/const _pi${fi}=[];for(let _k=0;_k<${branches.length};_k++)if((${counted.mask}>>_k)&1)_pi${fi}.push(_k);${pushC}/*CE*/}else if(!(${same}&&${counted.count}===1))`
5683
- }
5684
- // Without such a count, each branch also gets a verdict function, and the
5685
- // passing branches are counted with those: a oneOf with exactly one and an
5686
- // anyOf with any hold, and a oneOf with more than one is reported from the
5687
- // bits, so the error-collecting branches run only when no branch passes
5688
- // and the collapse needs their errors to pick the closest. Running them to
5689
- // learn the count built a result object and an error list per branch,
5690
- // which made a oneOf matched twice cost 69 ns where counting costs about
5691
- // 12. A branch the verdict generator declines keeps the old path.
5692
- let vfArr = null
5693
- // Branches that hold a reference are left to the error branches: their
5694
- // verdicts would expand every definition again, as a second copy, where the
5695
- // error branches call the shared one.
5696
- if (guard === '' && !branches.some((b) => holdsRef(b))) {
5697
- // A trial: the generators are not exception-safe on the context (a
5698
- // reference left on refStack by a declined branch made the next error
5699
- // function write that branch as nothing, which read as valid), so the
5700
- // context is put back as it was when the verdicts decline.
5701
- const saved = snapshotCtx(ctx)
5702
- try {
5703
- const vfs = branches.map((sub) => {
5704
- if (sub === true) return 'function(){return true}'
5705
- if (sub === false) return 'function(){return false}'
5706
- const bl = []
5707
- nestedGenCode(sub, '_bv', bl, ctx)
5708
- return bl.length === 0 ? 'function(){return true}' : `function(_bv){${bl.join(';')};return true}`
5709
- })
5710
- // Only while the verdicts are small: they are a second copy of the
5711
- // branches, with every reference expanded, and on large branches that
5712
- // copy cost more to compile than counting saves (apollo-router's error
5713
- // function grew from 1.5 to 2.5 MB of source).
5714
- if (!vfs.some((b) => b.includes(SP_PLACEHOLDER)) && vfs.reduce((n, b) => n + b.length, 0) <= BRANCH_VERDICT_MAX) {
5715
- // One constant per branch, called directly: through an array the one
5716
- // call site sees every branch and none of them is inlined.
5717
- vfArr = `_bvf${fi}`
5718
- ctx.helperCode.push(vfs.map((b, i) => `const ${vfArr}_${i}=${b}`).join(';'))
5719
- } else {
5720
- restoreCtx(ctx, saved)
5721
- }
5722
- } catch (e) {
5723
- if (e !== DECLINE) throw e
5724
- restoreCtx(ctx, saved)
5725
- }
5726
- }
5727
- // In the combined function (the runtime path; a standalone module cannot
5728
- // carry a compiled function) a branch that holds a reference, or a site
5729
- // whose inline verdicts were too large, gets a verdict compiled on its own
5730
- // and handed in as a closure value: the branch with the root's definitions
5731
- // under it, through compileToJSCodegen, with its own shared definitions and
5732
- // its own regex engines. Without one, every branch's error function ran on
5733
- // every value the keyword saw, accepted or not: json-patch's six-way oneOf
5734
- // cost 1363 ns on a valid list of patches where its verdict costs 261.
5735
- if (vfArr === null && guard === '' && ctx.inCombined) {
5736
- const compiled = compileBranchVerdicts(branches, ctx)
5737
- if (compiled !== null) {
5738
- vfArr = `_bvf${fi}`
5739
- for (let i = 0; i < compiled.length; i++) { ctx.closureVars.push(`${vfArr}_${i}`); ctx.closureVals.push(compiled[i]) }
5740
- }
5741
- }
5742
- if (vfArr !== null) {
5743
- const L = branches.length
5744
- const top = ctx.inCombined && !ctx.inBranchFn
5745
- // An accepted document is still answered by the verdict first (see the
5746
- // result shape in compileToJSCombined): that is the fastest path to it,
5747
- // and dropping it measured 20 ns slower per accepted oneOf.
5748
- if (top && !ctx.guardDepth) ctx.unguardedCollapse = true
5749
- const tests = branches.map((_, i) => `${vfArr}_${i}(${v})`)
5750
- if (keyword === 'anyOf') {
5751
- const none = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}`
5752
- // In a branch function the section a counting copy replaces is marked
5753
- // (countingBody): when the keyword does not hold, the error function
5754
- // reports one collapsed error, whatever the branches would say.
5755
- lines.push(`{const _pp${fi}=${pp};if(!(${tests.join('||')})){${top ? '' : '/*CB*/'}if(_all===0){_e.push(null);return{valid:false,errors:_e}}${none}${top ? '' : '/*CE*/'}}}`)
5756
- } else {
5757
- const mk = L <= 30
5758
- const collect = mk
5759
- ? `const _pi${fi}=[];for(let _k=0;_k<${L};_k++)if((_m${fi}>>_k)&1)_pi${fi}.push(_k);`
5760
- : ''
5761
- const multi = `__ataMulti(_pi${fi},${L},_pp${fi},'${branchSp}',${ord})`
5762
- const pushMulti = top ? `(_e=_ap(_A,_e,${multi},${apOrd(ctx, branchSp)}))` : `_e.push(${multi});if(!_all)return{valid:false,errors:_e}`
5763
- const count = tests.map((t, i) => `if(${t}){_n${fi}++;${mk ? `_m${fi}|=${1 << i}` : `_pi${fi}.push(${i})`}}`).join('')
5764
- const none = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++)${resArr}.push(${call});${report}`
5765
- lines.push(`{const _pp${fi}=${pp};let _n${fi}=0${mk ? `,_m${fi}=0` : `,_pi${fi}=[]`};${count}if(_n${fi}!==1){${top ? '' : '/*CB*/'}if(_all===0){_e.push(null);return{valid:false,errors:_e}}if(_n${fi}>1){${collect}${pushMulti}}else{${none}}${top ? '' : '/*CE*/'}}}`)
5766
- }
5767
- return
5768
- }
5769
- if (guard === '' && ctx.inCombined && !ctx.inBranchFn && !ctx.guardDepth) ctx.unguardedCollapse = true
5770
- if (keyword === 'anyOf') {
5771
- // anyOf reports nothing once a branch passes, so the branches are first
5772
- // only asked whether they pass. They are called with `_all` set to 0, a
5773
- // third value next to true (collect everything) and false (stop at the
5774
- // first error, for abortEarly): every check reads it as `!_all` and stops
5775
- // at its first error, and an anyOf reached that way that fails returns
5776
- // at once instead of collecting its branches and collapsing them, since
5777
- // the caller only wants to know. An object tried against a branch that is
5778
- // an anyOf of primitive types used to collapse that anyOf in full and then
5779
- // pass the next branch, which on a list of 1723 tokens was 2353 collapses
5780
- // no one read. Only when no branch passes do they run again with the
5781
- // caller's `_all`, and the collapse sees them all, as before.
5782
- const probe = `${fnArr}[_bi](${v},_pp${fi},0${usesSp ? ',_sp' : ''})`
5783
- const none = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}`
5784
- const probeOld = `let _bp${fi}=false;for(let _bi=0;_bi<${fnArr}.length;_bi++){if(${probe}.valid){_bp${fi}=true;break}}if(!_bp${fi}){if(_all===0){_e.push(null);return{valid:false,errors:_e}}${none}}`
5785
- // With a count: on the counted object the guard already said no branch
5786
- // held (CB, one error in a counting copy); on another object the branches
5787
- // are probed as before (CU, a counting copy cannot know).
5788
- if (counted !== undefined) lines.push(`${guard}{const _pp${fi}=${pp};if(${same}){/*CB*/if(_all===0){_e.push(null);return{valid:false,errors:_e}}${none}/*CE*/}else{/*CU*/${probeOld}/*CE*/}}`)
5789
- else lines.push(`${guard}{const _pp${fi}=${pp};${probeOld}}`)
5790
- } else {
5791
- // Asked only whether it passes (`_all` 0, see anyOf above), oneOf counts
5792
- // the passing branches and answers; the branch errors of a probe are not
5793
- // errors anyone reads and are not collapsed.
5794
- // With a count from the node's unevaluatedProperties (`counted`), a count
5795
- // of zero on the counted object means no branch held, which is the case
5796
- // the closest-branch path answers without running every branch.
5797
- const oldO = `const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++)${resArr}.push(${call});if(_all===0){let _n=0;for(const _r of ${resArr})if(_r.valid)_n++;if(_n!==1){_e.push(null);return{valid:false,errors:_e}}}else{${report}}`
5798
- const noneO = noneReport !== null ? noneReport : `const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++)${resArr}.push(${call});${report}`
5799
- if (counted !== undefined) lines.push(`${guard}{const _pp${fi}=${pp};if(${same}){/*CB*/if(_all===0){_e.push(null);return{valid:false,errors:_e}}${noneO}/*CE*/}else{/*CU*/${oldO}/*CE*/}}`)
5800
- else lines.push(`${guard}{const _pp${fi}=${pp};${oldO}}`)
5801
- }
5802
- }
5803
-
5804
- // Error-collecting code generator.
5805
- // Instead of `return false`, pushes to `_e` array and optionally early-returns.
5806
- // `_all` parameter: if falsy, return after first error.
5807
- function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
5808
- return withPlain(schema, v, lines, ctx, () => genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix))
5809
- }
5810
- function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
5811
- if (!schemaPrefix) schemaPrefix = '#'
5812
- // A false schema reports one error at the value that reached it, with the
5813
- // keyword the interpreter has always used for it, `not` (lib/plan-compiler.js
5814
- // line 58; its `err()` discards the 'false schema' label). Other validators
5815
- // name this keyword `false schema`; changing it here would change output
5816
- // the interpreter path already produces, so both engines say `not`.
5817
- if (schema === false) {
5818
- 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}`)
5819
- return
5820
- }
5821
- if (schema === true) return
5822
- if (typeof schema !== 'object' || schema === null) return
5823
- if (!ctx.regExpMap) {
5824
- ctx.regExpMap = new Map();
5825
- }
5826
- // $ref — resolve local and cross-schema refs
5827
- if (schema.$ref && !schema[REF_DONE]) {
5828
- // A keyword beside $ref that validates applies too (2020-12): the
5829
- // reference is written on its own, then the node again with the
5830
- // reference marked done, on a fresh copy that keeps `$ref` for the
5831
- // annotation readers, as genCodeCNode does. This used to decline, which
5832
- // kept the 2020-12 meta-schema (every vocabulary schema is a reference
5833
- // with siblings once unrolled by scope) off both error generators and on
5834
- // the interpreter for its errors, about 2 microseconds a rejection.
5835
- if (Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))) {
5836
- genCodeENode({ $ref: schema.$ref }, v, pathExpr, lines, ctx, schemaPrefix)
5837
- const rest = { ...schema }
5838
- Object.defineProperty(rest, REF_DONE, { value: true })
5839
- genCodeENode(rest, v, pathExpr, lines, ctx, schemaPrefix)
5840
- return
5841
- }
5842
- // A self-reference is a call to a helper over the root schema that takes
5843
- // the path and the schema path as arguments, as a definition on a cycle
5844
- // does below: the root helper takes no path, so errors raised through it
5845
- // carried the root's path, and this declined, which left a standalone
5846
- // module reporting one stub error for a schema with `$ref: "#"` anywhere
5847
- // in it. This used to fall through as a permissive no-op, which accepted
5848
- // whatever the reference guarded.
5849
- if (schema.$ref === '#') {
5850
- // The body comes from the prepared root (`rootE`), whose references
5851
- // are the resolvable ones; from the schema as given, a reference by
5852
- // `$id` resolved to nothing and the helper for the suite's recursive
5853
- // tree checked no node.
5854
- if (!ctx.rootE || typeof ctx.rootE !== 'object') throw DECLINE
5855
- if (!ctx.defFns) ctx.defFns = new Map()
5856
- let fnName = ctx.defFns.get('#')
5857
- if (!fnName) {
5858
- fnName = '_defE' + ctx.defFns.size + '_root'
5859
- ctx.defFns.set('#', fnName)
5860
- // The helper is its own function, so the references expanded in it
5861
- // start from nothing: inside the expansion of a definition that led
5862
- // here, the definition's own reference would otherwise read as
5863
- // already being expanded.
5864
- const bodyLines = []
5865
- const outerStack = ctx.refStack
5866
- ctx.refStack = new Set()
5867
- try { genCodeE(ctx.rootE, 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER) } finally { ctx.refStack = outerStack }
5868
- let body = bodyLines.join('\n ')
5869
- body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
5870
- if (body.includes(SP_PLACEHOLDER)) throw DECLINE
5871
- ctx.helperCode.push(
5872
- `const ${fnName}_s=new Set()\n function ${fnName}(d,_p,_all,_e,_sp){\n ` +
5873
- `if(_sg){if(typeof d!=='object'||d===null)return ${fnName}_b(d,_p,_all,_e,_sp);if(${fnName}_s.has(d))return;${fnName}_s.add(d);try{return ${fnName}_b(d,_p,_all,_e,_sp)}finally{${fnName}_s.delete(d)}}\n ` +
5874
- `if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d,_p,_all,_e,_sp)\n _sd--\n return _r\n }\n ` +
5875
- `function ${fnName}_b(d,_p,_all,_e,_sp){${body}}`,
5876
- )
5877
- }
5878
- lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e,'${schemaPrefix}');if(!_all&&_e.length)return{valid:false,errors:_e}`)
5879
- return
5880
- }
5881
- const m = schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
5882
- if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
5883
- const defName = m[1]
5884
- if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
5885
- if (!ctx.sharedDefs) ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs)
5886
- if (ctx.cyclicDefs.has(defName) || ctx.sharedDefs.has(defName)) {
5887
- // Named helper for a def on a cycle, and for one referenced from
5888
- // several places, which inlined would repeat its whole error code at
5889
- // each: Uniswap's token-list schema has 16 references to 10
5890
- // definitions. The path comes in as an argument because every call
5891
- // site has its own. Only the error path runs these; the verdict
5892
- // function, which answers valid documents, is unchanged.
5893
- if (!ctx.defFns) ctx.defFns = new Map()
5894
- let fnName = ctx.defFns.get(defName)
5895
- if (!fnName) {
5896
- fnName = '_defE' + ctx.defFns.size + '_' + defName.replace(/[^A-Za-z0-9_]/g, '_')
5897
- ctx.defFns.set(defName, fnName)
5898
- // The schema path comes in as an argument too, `_sp`, so an error
5899
- // names the path it was reached by, as it does when the definition
5900
- // is inlined and as the interpreted engine reports it; a fixed
5901
- // `#/$defs/<name>` named the definition instead. The body is
5902
- // generated against a placeholder prefix that becomes `_sp` in every
5903
- // string it starts. One left anywhere else declines, rather than
5904
- // emitting a path the placeholder stands in for.
5905
- const bodyLines = []
5906
- genCodeE(ctx.rootDefs[defName], 'd', '_p', bodyLines, ctx, SP_PLACEHOLDER)
5907
- let body = bodyLines.join('\n ')
5908
- body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
5909
- if (body.includes(SP_PLACEHOLDER)) throw DECLINE
5910
- ctx.helperCode.push(
5911
- `const ${fnName}_s=new Set()\n function ${fnName}(d,_p,_all,_e,_sp){\n ` +
5912
- `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 ` +
5913
- `if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d,_p,_all,_e,_sp)\n _sd--\n return _r\n }\n ` +
5914
- `function ${fnName}_b(d,_p,_all,_e,_sp){${body}}`,
5915
- )
5916
- }
5917
- lines.push(`${fnName}(${v},${pathExpr || '""'},_all,_e,'${schemaPrefix}');if(!_all&&_e.length)return{valid:false,errors:_e}`)
5918
- return
5919
- }
5920
- // A reference back into what is being expanded, on a cycle the
5921
- // definitions alone do not show (through the root, or another
5922
- // document): declined. Returning emitted nothing, which read as valid.
5923
- if (ctx.refStack.has(schema.$ref)) throw DECLINE
5924
- ctx.refStack.add(schema.$ref)
5925
- genCodeE(ctx.rootDefs[defName], v, pathExpr, lines, ctx, schemaPrefix)
5926
- ctx.refStack.delete(schema.$ref)
5927
- return
5928
- }
5929
- // Anchor ref: "#foo" — resolve via rootDefs or anchors map
5930
- if (!m && schema.$ref.startsWith('#') && !schema.$ref.startsWith('#/')) {
5931
- const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
5932
- const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
5933
- if (anchorTarget) {
5934
- if (ctx.refStack.has(schema.$ref)) throw DECLINE
5935
- ctx.refStack.add(schema.$ref)
5936
- genCodeE(anchorTarget, v, pathExpr, lines, ctx, schemaPrefix)
5937
- ctx.refStack.delete(schema.$ref)
5938
- return
5939
- }
5940
- }
5941
- if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
5942
- if (ctx.refStack.has(schema.$ref)) throw DECLINE
5943
- ctx.refStack.add(schema.$ref)
5944
- genCodeE(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
5945
- ctx.refStack.delete(schema.$ref)
5946
- return
5947
- }
5948
- // Cross-schema ref with JSON pointer fragment ("<id>#/<path>")
5949
- if (ctx.schemaMap && schema.$ref.includes('#') && !schema.$ref.startsWith('#')) {
5950
- const r = resolveCrossSchemaRef(schema.$ref, ctx.schemaMap)
5951
- if (r) {
5952
- if (ctx.refStack.has(schema.$ref)) throw DECLINE
5953
- ctx.refStack.add(schema.$ref)
5954
- genCodeE(r.schema, v, pathExpr, lines, ctx, schemaPrefix)
5955
- ctx.refStack.delete(schema.$ref)
5956
- return
5957
- }
5958
- }
5959
- // A reference none of the above resolved: declined. Falling through
5960
- // emitted nothing for it, which read as valid.
5961
- throw DECLINE
5962
- }
5963
-
5964
- // $dynamicRef — resolve via anchors map
5965
- if (schema.$dynamicRef) genDynamicRefE(schema, v, pathExpr, lines, ctx, schemaPrefix)
5966
-
5967
- const types = schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : null
5968
- if (types) {
5969
- const conds = types.map(t => {
5970
- switch (t) {
5971
- case 'object': return `(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
5972
- case 'array': return `Array.isArray(${v})`
5973
- case 'string': return `typeof ${v}==='string'`
5974
- case 'number': return `Number.isFinite(${v})`
5975
- case 'integer': return `Number.isInteger(${v})`
5976
- case 'boolean': return `typeof ${v}==='boolean'`
5977
- case 'null': return `${v}===null`
5978
- default: return 'true'
5979
- }
5980
- })
5981
- const expected = types.join(', ')
5982
- // As written: a one-element array stays an array, as the interpreted engine
5983
- // reports it.
5984
- const expectedParam = !Array.isArray(schema.type) ? `'${types[0]}'` : JSON.stringify(types)
5985
- {
5986
- const typeSp = `${schemaPrefix}/type`
5987
- const lit = buildErrorLiteral({ keyword: 'type', schemaPath: typeSp, sourceMap: ctx.sourceMap })
5988
- 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}}`)
5989
- }
5990
- }
5991
-
5992
- // In error mode, never assume type — always guard (data may have failed type check but allErrors continues)
5993
- const isObj = false
5994
- const isArr = false
5995
- const isStr = false
5996
- const isNum = false
5997
-
5998
- const fail = (keyword, schemaSuffix, paramsCode, msgCode, fmt) => {
5999
- const sp = schemaPrefix + '/' + schemaSuffix
6000
- const lit = buildErrorLiteral({ keyword, format: fmt, schemaPath: sp, sourceMap: ctx.sourceMap })
6001
- 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}`
6002
- }
6003
-
6004
- // enum
6005
- if (schema.enum) {
6006
- 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'")}}`)
6007
- }
6008
-
6009
- // const — use canonical (sorted-key) comparison for objects
6010
- if (schema.const !== undefined) {
6011
- const cv = schema.const
6012
- if (cv === null || typeof cv !== 'object') {
6013
- lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
6014
- } else {
6015
- lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)})){${fail('const', 'const', `{allowedValue:${emitParamConstant(ctx, schema.const)}}`, "'must be equal to constant'")}}`)
6016
- }
6017
- }
6018
-
6019
- // required — no destructuring in error mode (data might not be an object)
6020
- const requiredSet = new Set(schema.required || [])
6021
- const hoisted = {}
6022
- if (schema.required) {
6023
- const reqSp = `${schemaPrefix}/required`
6024
- const reqLit = buildErrorLiteral({ keyword: 'required', schemaPath: reqSp, sourceMap: ctx.sourceMap })
6025
- for (const key of schema.required) {
6026
- 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}}`)
6027
- }
6028
- }
6029
-
6030
- // numeric
6031
- if (schema.minimum !== undefined) {
6032
- const c = isNum ? `${v}<${schema.minimum}` : `typeof ${v}==='number'&&${v}<${schema.minimum}`
6033
- lines.push(`if(${c}){${fail('minimum', 'minimum', `{comparison:'>=',limit:${schema.minimum}}`, `'must be >= ${schema.minimum}'`)}}`)
6034
- }
6035
- if (schema.maximum !== undefined) {
6036
- const c = isNum ? `${v}>${schema.maximum}` : `typeof ${v}==='number'&&${v}>${schema.maximum}`
6037
- lines.push(`if(${c}){${fail('maximum', 'maximum', `{comparison:'<=',limit:${schema.maximum}}`, `'must be <= ${schema.maximum}'`)}}`)
6038
- }
6039
- if (schema.exclusiveMinimum !== undefined) {
6040
- const c = isNum ? `${v}<=${schema.exclusiveMinimum}` : `typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum}`
6041
- lines.push(`if(${c}){${fail('exclusiveMinimum', 'exclusiveMinimum', `{comparison:'>',limit:${schema.exclusiveMinimum}}`, `'must be > ${schema.exclusiveMinimum}'`)}}`)
6042
- }
6043
- if (schema.exclusiveMaximum !== undefined) {
6044
- const c = isNum ? `${v}>=${schema.exclusiveMaximum}` : `typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum}`
6045
- lines.push(`if(${c}){${fail('exclusiveMaximum', 'exclusiveMaximum', `{comparison:'<',limit:${schema.exclusiveMaximum}}`, `'must be < ${schema.exclusiveMaximum}'`)}}`)
6046
- }
6047
- if (schema.multipleOf !== undefined) {
6048
- const m = schema.multipleOf
6049
- const ci = ctx.varCounter++
6050
- // Use tolerance-based check for floating point (matches C++ behavior)
6051
- lines.push(`{if(typeof ${v}==='number'&&${multipleOfBad(v, m)}){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
6052
- }
6053
-
6054
- // string length — same s.length fast paths as the boolean codegen above.
6055
- // length < M → certain fail; length > 2*X → certain fail; sweet spot
6056
- // 2*M <= length <= X passes without scanning. Only call _cpLen in the
6057
- // uncertain band (caches it so it isn't called twice when both bounds are set).
6058
- if (schema.minLength !== undefined) {
6059
- const M = schema.minLength
6060
- const inner = `${v}.length<${M}||(${v}.length<${M * 2}&&_cpLen(${v})<${M})`
6061
- const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
6062
- lines.push(`if(${c}){${fail('minLength', 'minLength', `{limit:${M}}`, `'must NOT have fewer than ${M} characters'`)}}`)
6063
- }
6064
- if (schema.maxLength !== undefined) {
6065
- const X = schema.maxLength
6066
- const inner = `${v}.length>${X * 2}||(${v}.length>${X}&&_cpLen(${v})>${X})`
6067
- const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
6068
- lines.push(`if(${c}){${fail('maxLength', 'maxLength', `{limit:${X}}`, `'must NOT have more than ${X} characters'`)}}`)
6069
- }
6070
- if (schema.pattern) {
6071
- const inlineCheck = compilePatternInline(schema.pattern, v)
6072
- if (inlineCheck) {
6073
- const c = isStr ? `!(${inlineCheck})` : `typeof ${v}==='string'&&!(${inlineCheck})`
6074
- lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
6075
- } else {
6076
- const pattern = JSON.stringify(schema.pattern);
6077
- if (!ctx.regExpMap.has(pattern)) {
6078
- const ri = ctx.varCounter++
6079
- ctx.regExpMap.set(pattern, ri)
6080
- if (useSafeEngine(schema.pattern)) {
6081
- ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
6082
- ctx.usesSafeRe = true
6083
- } else {
6084
- ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
6085
- }
6086
- }
6087
- const ri = ctx.regExpMap.get(pattern);
6088
- const c = isStr ? `!_re${ri}.test(${v})` : `typeof ${v}==='string'&&!_re${ri}.test(${v})`
6089
- lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
6090
- }
6091
- }
6092
- if (schema.format) {
6093
- const fc = FORMAT_CODEGEN[schema.format]
6094
- const fmtSp = `${schemaPrefix}/format`
6095
- const fmtLit = buildErrorLiteral({ keyword: 'format', format: schema.format, schemaPath: fmtSp, sourceMap: ctx.sourceMap })
6096
- 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}`
6097
- // Format errors use the boolean codegen, each `return false` becoming an
6098
- // error push. One violation reports once: the first failing statement
6099
- // pushes and leaves the labeled block, where it used to fall through and
6100
- // let every later statement of the same check push the same error again.
6101
- if (fc) {
6102
- const ri = ctx.varCounter++
6103
- const fmtCode = fc(v, isStr, ctx).replace(/return false/g, `{${failPush};break _fmt${ri}}`)
6104
- lines.push(`_fmt${ri}:{${fmtCode}}`)
6105
- } else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
6106
- // User-supplied format checker on the error path: the same closure
6107
- // plumbing as the boolean codegen. The function is bound as a parameter
6108
- // of the compiled error function; bundle output serializes it via
6109
- // Function#toString separately.
6110
- const safeName = schema.format.replace(/[^a-zA-Z0-9_]/g, '_')
6111
- const closureName = `_uf_${safeName}`
6112
- if (ctx.closureVars && !ctx.closureVars.includes(closureName)) {
6113
- ctx.closureVars.push(closureName)
6114
- ctx.closureVals.push(ctx.userFormats[schema.format])
6115
- }
6116
- const guard = isStr ? '' : `typeof ${v}==='string'&&`
6117
- lines.push(`if(${guard}!${closureName}(${v})){${failPush}}`)
6118
- }
6119
- }
6120
-
6121
- // array size
6122
- if (schema.minItems !== undefined) {
6123
- const c = isArr ? `${v}.length<${schema.minItems}` : `Array.isArray(${v})&&${v}.length<${schema.minItems}`
6124
- lines.push(`if(${c}){${fail('minItems', 'minItems', `{limit:${schema.minItems}}`, `'must NOT have fewer than ${schema.minItems} items'`)}}`)
6125
- }
6126
- if (schema.maxItems !== undefined) {
6127
- const c = isArr ? `${v}.length>${schema.maxItems}` : `Array.isArray(${v})&&${v}.length>${schema.maxItems}`
6128
- lines.push(`if(${c}){${fail('maxItems', 'maxItems', `{limit:${schema.maxItems}}`, `'must NOT have more than ${schema.maxItems} items'`)}}`)
6129
- }
6130
-
6131
- // uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
6132
- if (schema.uniqueItems) genUniqueItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr)
6133
-
6134
- // object size
6135
- if (schema.minProperties !== undefined) {
6136
- 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'`)}}`)
6137
- }
6138
- if (schema.maxProperties !== undefined) {
6139
- 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'`)}}`)
6140
- }
6141
-
6142
- // additionalProperties: false. A key matched by any patternProperties
6143
- // entry is not additional, so those patterns are consulted here as well.
6144
- if (schema.additionalProperties === false) {
6145
- const allowed = Object.keys(schema.properties || {}).map(k => `${JSON.stringify(k)}`).join(',')
6146
- const ci = ctx.varCounter++
6147
- const apSp = `${schemaPrefix}/additionalProperties`
6148
- const apLit = buildErrorLiteral({ keyword: 'additionalProperties', schemaPath: apSp, sourceMap: ctx.sourceMap })
6149
- const patChecks = []
6150
- for (const pat of Object.keys(schema.patternProperties || {})) {
6151
- const pattern = JSON.stringify(pat)
6152
- if (!ctx.regExpMap.has(pattern)) {
6153
- const ri = ctx.varCounter++
6154
- ctx.regExpMap.set(pattern, ri)
6155
- if (useSafeEngine(pat)) {
6156
- ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
6157
- ctx.usesSafeRe = true
6158
- } else {
6159
- ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
6160
- }
6161
- }
6162
- patChecks.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ci}[_i])`)
6163
- }
6164
- const isAdditional = patChecks.length ? `!_a${ci}.has(_k${ci}[_i])&&!(${patChecks.join('||')})` : `!_a${ci}.has(_k${ci}[_i])`
6165
- // Same shape as the combined generator: a for-in says whether anything is
6166
- // extra without allocating, and the keys are materialised only to name one.
6167
- // This function walks every node of a document that failed anywhere, so on a
6168
- // large array the clean items were each paying for a key array.
6169
- const gateChecks = patChecks.map((c) => c.split(`_k${ci}[_i]`).join(`_f${ci}`))
6170
- const gateTest = gateChecks.length ? `!_a${ci}.has(_f${ci})&&!(${gateChecks.join('||')})` : `!_a${ci}.has(_f${ci})`
6171
- 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}}}`
6172
- // The name set is built once per call of the error function, not at every
6173
- // object it checks: on a list of 1723 tokens it was 1723 Sets.
6174
- ctx.helperCode.push(`const _a${ci}=new Set([${allowed}])`)
6175
- const inner = `let _x${ci}=0;for(const _f${ci} in ${v}){if(${gateTest}){_x${ci}=1;break}}if(_x${ci}){${detail}}`
6176
- lines.push(isObj ? `{${inner}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
6177
- }
6178
-
6179
- // unevaluatedProperties: false, on a node where it is provably its plain
6180
- // counterpart (the bail above guaranteed that for every occurrence). With
6181
- // additionalProperties present everything is evaluated and there is nothing
6182
- // to emit. The error keeps the keyword's own identity: the interpreted
6183
- // engine reports these as unevaluatedProperties with the key named, and the
6184
- // two engines are pinned to identical output.
6185
- // Inside the combined function (a oneOf or anyOf branch, see
6186
- // emitBranchCollapse) this generator only has the static form. A branch
6187
- // whose evaluated set depends on its own branches would report the wrong
6188
- // errors, and the collapse would pick the wrong closest branch; declined.
6189
- if (ctx.inCombined && schema.unevaluatedProperties !== undefined && schema.unevaluatedProperties !== true && !unevalLocalOk(schema, 'unevaluatedProperties')) throw DECLINE
6190
- if (ctx.inCombined && schema.unevaluatedItems !== undefined && schema.unevaluatedItems !== true && !unevalLocalOk(schema, 'unevaluatedItems')) throw DECLINE
6191
- if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined) genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj)
6192
-
6193
- // unevaluatedItems: false under the same rule. The interpreted engine
6194
- // reports one error for the array with the first unevaluated position as
6195
- // the limit; with adjacent `items` everything is evaluated. The draft-07
6196
- // array form of items acts as the prefix.
6197
- if (schema.unevaluatedItems === false) genUnevaluatedItemsFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix)
6198
-
6199
- // dependentRequired
6200
- if (schema.dependentRequired) {
6201
- const drSp = `${schemaPrefix}/dependentRequired`
6202
- const drLit = buildErrorLiteral({ keyword: 'dependentRequired', schemaPath: drSp, sourceMap: ctx.sourceMap })
6203
- for (const [key, deps] of Object.entries(schema.dependentRequired)) {
6204
- for (const dep of deps) {
6205
- 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}}`)
6206
- }
6207
- }
6208
- }
6209
-
6210
- // properties — always guard (error mode, data may not be an object or may be array)
6211
- if (schema.properties) {
6212
- for (const [key, prop] of Object.entries(schema.properties)) {
6213
- const childPath = childPathExpr(pathExpr, ptrSeg(key))
6214
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
6215
- genCodeE(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
6216
- lines.push(`}`)
6217
- }
6218
- }
6219
-
6220
- // patternProperties
6221
- if (schema.patternProperties) genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix)
6222
-
6223
- // additionalProperties as a schema — validate each property not declared in
6224
- // `properties` against the subschema, with a dynamic /<key> path. codegenSafe
6225
- // guarantees no patternProperties or composition here, so "additional" is
6226
- // simply "not declared".
6227
- if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null) genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix)
6228
-
6229
- // dependentSchemas
6230
- if (schema.dependentSchemas) genDependentSchemasE(schema, v, pathExpr, lines, ctx, schemaPrefix)
6231
-
6232
- // propertyNames: false reports every key, at the key's own path, the way the
6233
- // interpreter does (plan-compiler: instancePath + '/' + key).
6234
- if (schema.propertyNames === false) genPropertyNamesFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix)
6235
-
6236
- // propertyNames
6237
- if (schema.propertyNames && typeof schema.propertyNames === 'object') genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
6238
-
6239
- // items — starts after prefixItems (Draft 2020-12 semantics)
6240
- if (schema.items !== undefined && schema.items !== true) {
6241
- const startIdx = schema.prefixItems ? schema.prefixItems.length : 0
6242
- const idx = `_j${ctx.varCounter}`
6243
- const elem = `_ei${ctx.varCounter}`
6244
- ctx.varCounter++
6245
- const childPath = childPathDynExpr(pathExpr, idx)
6246
- lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
6247
- genCodeE(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
6248
- lines.push(`}}`)
6249
- }
6250
-
6251
- // prefixItems
6252
- if (schema.prefixItems) genPrefixItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix)
6253
-
6254
- // contains
6255
- if (schema.contains !== undefined) genContainsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
6256
-
6257
- // allOf
6258
- if (schema.allOf) {
6259
- for (let _ai = 0; _ai < schema.allOf.length; _ai++) {
6260
- genCodeE(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai)
6261
- }
6262
- }
6263
-
6264
- // anyOf - collapse to single best-branch error via __ataCollapse
6265
- if (schema.anyOf) genAnyOfE(schema, v, pathExpr, lines, ctx, schemaPrefix)
6266
-
6267
- // oneOf - collapse to single best-branch error via __ataCollapse
6268
- if (schema.oneOf) genOneOfE(schema, v, pathExpr, lines, ctx, schemaPrefix)
6269
-
6270
- // not
6271
- if (schema.not !== undefined) genNotE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
6272
-
6273
- // if/then/else
6274
- if (schema.if !== undefined) genIfE(schema, v, pathExpr, lines, ctx, schemaPrefix)
6275
- }
6276
-
6277
- // Rarely used keyword families of genCodeENode, kept out of it so their compile is
6278
- // paid only by schemas that use them.
6279
-
6280
- function genDynamicRefE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6281
- const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
6282
- if (ctx.anchors && ctx.anchors[anchorKey]) {
6283
- const target = ctx.anchors[anchorKey]
6284
- if (target === ctx.rootSchema) {
6285
- // Self-recursive: generate _validateE call
6286
- ctx.usesRecursion = true
6287
- lines.push(`_validateE(${v},_all,_e)`)
6288
- } else {
6289
- const refKey = '$dynamicRef:' + anchorKey
6290
- if (!ctx.refStack.has(refKey)) {
6291
- ctx.refStack.add(refKey)
6292
- genCodeE(target, v, pathExpr, lines, ctx, schemaPrefix)
6293
- ctx.refStack.delete(refKey)
6294
- }
6295
- }
6296
- }
6297
- }
6298
-
6299
- function genUniqueItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr) {
6300
- const si = ctx.varCounter++
6301
- const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
6302
- // Only reached once the verdict has rejected, or when errors are wanted, so
6303
- // one strategy for every item type: the hoisted pair finder. See UQP_HELPER.
6304
- const inner = `const _j${si}=${emitUqp(ctx)}(${v});if(_j${si}>=0){const _i${si}=_uqpI;${failExpr('_i' + si, '_j' + si)}}`
6305
- lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
6306
- }
6307
-
6308
- function genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj) {
6309
- const allowedU = Object.keys(schema.properties || {}).map(k => `${JSON.stringify(k)}`).join(',')
6310
- const ui = ctx.varCounter++
6311
- const upSp = `${schemaPrefix}/unevaluatedProperties`
6312
- const upLit = buildErrorLiteral({ keyword: 'unevaluatedProperties', schemaPath: upSp, sourceMap: ctx.sourceMap })
6313
- const patChecksU = []
6314
- for (const pat of Object.keys(schema.patternProperties || {})) {
6315
- const pattern = JSON.stringify(pat)
6316
- if (!ctx.regExpMap.has(pattern)) {
6317
- const ri = ctx.varCounter++
6318
- ctx.regExpMap.set(pattern, ri)
6319
- if (useSafeEngine(pat)) {
6320
- ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
6321
- ctx.usesSafeRe = true
6322
- } else {
6323
- ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
6324
- }
6325
- }
6326
- patChecksU.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ui}[_i])`)
6327
- }
6328
- const isUneval = patChecksU.length ? `!_a${ui}.has(_k${ui}[_i])&&!(${patChecksU.join('||')})` : `!_a${ui}.has(_k${ui}[_i])`
6329
- ctx.helperCode.push(`const _a${ui}=new Set([${allowedU}])`)
6330
- 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}}}`
6331
- lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
6332
- }
6333
-
6334
- function genUnevaluatedItemsFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6335
- const itemsIsPrefix = Array.isArray(schema.items)
6336
- const allEval = schema.items !== undefined && !itemsIsPrefix
6337
- if (!allEval) {
6338
- const plen = itemsIsPrefix ? schema.items.length : (Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0)
6339
- const uiSp = `${schemaPrefix}/unevaluatedItems`
6340
- const uiLit = buildErrorLiteral({ keyword: 'unevaluatedItems', schemaPath: uiSp, sourceMap: ctx.sourceMap })
6341
- 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}}`)
6342
- }
6343
- }
6344
-
6345
- function genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6346
- for (const [pat, sub] of Object.entries(schema.patternProperties)) {
6347
- const pattern = JSON.stringify(pat);
6348
- if (!ctx.regExpMap.has(pattern)) {
6349
- const ri = ctx.varCounter++
6350
- ctx.regExpMap.set(pattern, ri)
6351
- if (useSafeEngine(pat)) {
6352
- ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
6353
- ctx.usesSafeRe = true
6354
- } else {
6355
- ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`);
6356
- }
6357
- }
6358
- const ri = ctx.regExpMap.get(pattern);
6359
- const ki = ctx.varCounter++
6360
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){if(_re${ri}.test(_k${ki})){`)
6361
- const _peFn = emitPtrEsc(ctx)
6362
- const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
6363
- // The real pointer, so source maps can locate the failing keyword.
6364
- genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(pat))
6365
- lines.push(`}}}`)
6366
- }
6367
- }
6368
-
6369
- function genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6370
- const ki = ctx.varCounter++
6371
- const known = Object.keys(schema.properties || {})
6372
- if (known.length) ctx.helperCode.push(`const _ak${ki}=new Set([${known.map((k) => JSON.stringify(k)).join(',')}])`)
6373
- const guard = ''
6374
- // A key matched by any patternProperties entry is not additional.
6375
- const patTests = []
6376
- for (const pat of Object.keys(schema.patternProperties || {})) {
6377
- const pattern = JSON.stringify(pat)
6378
- if (!ctx.regExpMap.has(pattern)) {
6379
- const ri = ctx.varCounter++
6380
- ctx.regExpMap.set(pattern, ri)
6381
- if (useSafeEngine(pat)) {
6382
- ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
6383
- ctx.usesSafeRe = true
6384
- } else {
6385
- ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
6386
- }
6387
- }
6388
- patTests.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ki})`)
6389
- }
6390
- const conds = []
6391
- if (known.length) conds.push(`!_ak${ki}.has(_k${ki})`)
6392
- if (patTests.length) conds.push(`!(${patTests.join('||')})`)
6393
- const keep = conds.length ? `if(${conds.join('&&')}){` : `{`
6394
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${guard}for(const _k${ki} in ${v}){${keep}`)
6395
- const _peFn = emitPtrEsc(ctx)
6396
- const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
6397
- genCodeE(schema.additionalProperties, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/additionalProperties')
6398
- lines.push(`}}}`)
6399
- }
6400
-
6401
- function genDependentSchemasE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6402
- for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
6403
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
6404
- genCodeE(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
6405
- lines.push(`}`)
6406
- }
6407
- }
6408
-
6409
- function genPropertyNamesFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6410
- const ki = ctx.varCounter++
6411
- // The subschema is applied to the property NAME, so the failure belongs to
6412
- // the object that carries the name, not to `/<key>`, which is where the
6413
- // value lives. Same placement as the object-valued propertyNames branch.
6414
- const p = pathExpr || '""'
6415
- 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}}}`)
6416
- }
6417
-
6418
- function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
6419
- const pn = schema.propertyNames
6420
- const ki = ctx.varCounter++
6421
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
6422
- // The subschema checks the key as a value, and its errors are reported at
6423
- // the object, as the interpreter reports them.
6424
- if (!isSimplePN(pn)) {
6425
- genCodeE(pn, `_k${ki}`, pathExpr, lines, ctx, schemaPrefix + '/propertyNames')
6426
- lines.push(`}}`)
6427
- return
6428
- }
6429
- if (pn.minLength !== undefined) {
6430
- lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
6431
- }
6432
- if (pn.maxLength !== undefined) {
6433
- lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
6434
- }
6435
- if (pn.pattern) {
6436
- const pattern = JSON.stringify(pn.pattern);
6437
- if (!ctx.regExpMap.has(pattern)) {
6438
- const ri = ctx.varCounter++
6439
- ctx.regExpMap.set(pattern, ri)
6440
- if (useSafeEngine(pn.pattern)) {
6441
- ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
6442
- ctx.usesSafeRe = true
6443
- } else {
6444
- ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`);
6445
- }
6446
- }
6447
- const ri = ctx.regExpMap.get(pattern);
6448
- lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
6449
- }
6450
- if (pn.const !== undefined) {
6451
- lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
6452
- }
6453
- if (pn.enum) {
6454
- const ei = ctx.varCounter++
6455
- ctx.helperCode.push(`const _es${ei}=new Set(${JSON.stringify(pn.enum)})`)
6456
- 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'")}}`)
6457
- }
6458
- lines.push(`}}`)
6459
- }
6460
-
6461
- function genPrefixItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6462
- for (let i = 0; i < schema.prefixItems.length; i++) {
6463
- const childPath = childPathExpr(pathExpr, String(i))
6464
- lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
6465
- genCodeE(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
6466
- lines.push(`}`)
6467
- }
6468
- }
6469
-
6470
- function genContainsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
6471
- const ci = ctx.varCounter++
6472
- const subLines = []
6473
- nestedGenCode(schema.contains, `_cv`, subLines, ctx)
6474
- const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
6475
- const minC = schema.minContains !== undefined ? schema.minContains : 1
6476
- const maxC = schema.maxContains
6477
- 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}++}`)
6478
- lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'must contain at least ${minC} valid item(s)'`)}}`)
6479
- if (maxC !== undefined) {
6480
- lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'must NOT contain more than ${maxC} valid item(s)'`)}}`)
6481
- }
6482
- lines.push(`}`)
6483
- }
6484
-
6485
- function genAnyOfE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6486
- emitBranchCollapse(schema.anyOf, 'anyOf', v, pathExpr, lines, ctx, schemaPrefix)
6487
- }
6488
-
6489
- function genOneOfE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6490
- emitBranchCollapse(schema.oneOf, 'oneOf', v, pathExpr, lines, ctx, schemaPrefix)
6491
- }
6492
-
6493
- function genNotE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
6494
- const subLines = []
6495
- nestedGenCode(schema.not, '_nv', subLines, ctx)
6496
- const nfn = subLines.length === 0 ? `function(_nv){return true}` : `function(_nv){${subLines.join(';')};return true}`
6497
- const fi = ctx.varCounter++
6498
- lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
6499
- }
6500
-
6501
- function genIfE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6502
- const ifLines = []
6503
- nestedGenCode(schema.if, '_iv', ifLines, ctx)
6504
- const fi = ctx.varCounter++
6505
- const ifFn = ifLines.length === 0
6506
- ? `function(_iv){return true}`
6507
- : `function(_iv){${ifLines.join(';')};return true}`
6508
- lines.push(`{const _if${fi}=${ifFn}`)
6509
- if (schema.then !== undefined) {
6510
- lines.push(`if(_if${fi}(${v})){`)
6511
- genCodeE(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then')
6512
- lines.push(`}`)
6513
- }
6514
- if (schema.else !== undefined) {
6515
- lines.push(`${schema.then !== undefined ? 'else' : `if(!_if${fi}(${v}))`}{`)
6516
- genCodeE(schema.else, v, pathExpr, lines, ctx, schemaPrefix+'/else')
6517
- lines.push(`}`)
6518
- }
6519
- lines.push(`}`)
6520
- }
6521
-
6522
- // --- Combined validator: single pass, validates + collects errors ---
6523
- // Returns VALID_RESULT for valid data, {valid:false, errors} for invalid.
6524
- // Avoids double-pass (jsFn → false → errFn runs same checks again).
6525
- // Uses type-aware optimizations: after type check passes, skip guards.
6526
- // An error literal the combined function shares between calls, frozen. Two
6527
- // non-enumerable fields ride along, invisible to Object.keys, spread and
6528
- // JSON: `_s` says the object is shared, so the reader copies it before handing
6529
- // it out (Object.isFrozen, the check it replaces, cost more than the copy),
6530
- // and `_o` is its schema ordinal, known here and otherwise looked up by path
6531
- // on every read. An `_o` the literal already carries, for standalone output,
6532
- // is kept. `_t` is an empty slot lib/enrich-error.js fills once.
6533
- function sharedErr (lit, rootSchema) {
6534
- Object.defineProperty(lit, '_s', { value: true })
6535
- // A slot the enrichment fills on the first read with the fields that depend
6536
- // only on this literal (code, expected, rank), so later reads skip them.
6537
- Object.defineProperty(lit, '_t', { value: {} })
6538
- if (lit._o === undefined) {
6539
- const o = ordinalFor(rootSchema, lit.schemaPath)
6540
- if (typeof o === 'number') Object.defineProperty(lit, '_o', { value: o })
6541
- }
6542
- return Object.freeze(lit)
6543
- }
6544
-
6545
- // The push for one error in the combined generator. With `ctx.rich` (the
6546
- // validator's errors are being read and enriched, see validator-core) the
6547
- // error is built in its final enriched shape where it happens, by makeRich in
6548
- // lib/enrich-site.js with a site record made here at compile time; otherwise
6549
- // it is the plain literal `lean` the generator always emitted. `valueExpr` is
6550
- // the value at `pathExpr`, which enrichment otherwise finds again by walking
6551
- // the document. `paramsShared` names a frozen params object hoisted for a
6552
- // static site, so a static error's params stay one shared object as before.
6553
- // The ordinal `_ap` is handed with each error of the combined function: the
6554
- // error's place in schema order when its path is fixed at compile time, -1
6555
- // when it is not (a path through a shared definition, a collapsed branch
6556
- // error, an enriched error). `_ap` keeps whether the list is still in order
6557
- // as it grows; a list that came out in order, which is most of them, is
6558
- // handed out without the sort that looked every error's path up in a map.
6559
- function apOrd (ctx, sp, coarse) {
6560
- // Inside a shared definition's function every error takes the ordinal of
6561
- // the reference that reached it, handed in as `_so`: errors behind one
6562
- // `$ref` sort where the reference is written, in the order the definition
6563
- // reports them.
6564
- if (typeof sp === 'string' && sp.includes(SP_PLACEHOLDER) && ctx && ctx.inDefC) return '_so'
6565
- if (typeof sp !== 'string' || sp.includes(SP_PLACEHOLDER) || sp.includes('+')) return -1
6566
- if (!ctx || !ctx.rootSchema) return -1
6567
- const path = unescapeSp(sp)
6568
- const o = ordinalFor(ctx.rootSchema, path)
6569
- if (typeof o !== 'number') return -1
6570
- // The reader orders by ordinal and, between equal ordinals, by the rank of
6571
- // the path (tieOrder in lib/rejections.js). Both are known here, so a site
6572
- // gets a token that assignOrdKeys turns into a dense integer in exactly that
6573
- // order when the program is assembled: two errors with one key are ones the
6574
- // reader keeps as they came. Keyed by the ordinal alone, two errors under
6575
- // one synthetic definition shared a key and sent every such list to the
6576
- // reader, which ranked the paths again: 45 percent of reading a list
6577
- // schema's two errors.
6578
- if (!ctx.ordSites) ctx.ordSites = new Map()
6579
- let site = ctx.ordSites.get(path)
6580
- if (site === undefined) { site = { i: ctx.ordSites.size, o, r: rankFor(ctx.rootSchema, path), key: -1 }; ctx.ordSites.set(path, site) }
6581
- // A coarse key, for the errors behind a reference into a shared definition
6582
- // (see `ap` in lib/combined-runtime.js): -(k+2), written when k is known.
6583
- return coarse ? `@@C${site.i}@@` : `@@O${site.i}@@`
6584
- }
6585
-
6586
- // The dense keys of a program's error sites, in the reader's order: ordinal,
6587
- // then rank as tieOrder compares it, element by element and the shorter
6588
- // first; equal pairs share a key, and a site without a rank keeps -1, which
6589
- // hands its list to the reader. Replaces the tokens apOrd wrote.
6590
- function assignOrdKeys (ctx, src) {
6591
- if (!ctx.ordSites || ctx.ordSites.size === 0) return src
6592
- const sites = [...ctx.ordSites.values()]
6593
- const cmp = (a, b) => {
6594
- if (a.o !== b.o) return a.o - b.o
6595
- const m = Math.min(a.r.length, b.r.length)
6596
- for (let k = 0; k < m; k++) if (a.r[k] !== b.r[k]) return a.r[k] - b.r[k]
6597
- return a.r.length - b.r.length
6598
- }
6599
- const ranked = sites.filter((x) => x.r !== null).sort(cmp)
6600
- let key = 0
6601
- for (let i = 0; i < ranked.length; i++) { if (i > 0 && cmp(ranked[i - 1], ranked[i]) !== 0) key++; ranked[i].key = key }
6602
- return src.replace(/@@([OC])(\d+)@@/g, (_, t, i) => { const k = sites[+i].key; return String(t === 'O' || k < 0 ? k : -(k + 2)) })
6603
- }
6604
-
6605
- // The error list and its order live in lib/combined-runtime.js (`ap`, `sr`):
6606
- // `_A` is this program's state, reset at the top of each call.
6607
- const AP_RESET = '_A.mo=true;_A.lo=-1;_A.on=0;'
6608
- function errPushC(ctx, keyword, sp, pathExpr, paramsCode, msgCode, valueExpr, lean, paramsShared) {
6609
- if (!ctx.rich) return `(_e=_ap(_A,_e,${lean},${apOrd(ctx, sp)}))`
6610
- if (!ctx.richMk) {
6611
- ctx.richMk = true
6612
- const { makeRich } = require('./enrich-site')
6613
- ctx.closureVars.push('_mk'); ctx.closureVals.push(makeRich)
6614
- }
6615
- let paramsVal
6616
- try { paramsVal = Function('return ' + paramsCode)() } catch { paramsVal = undefined }
6617
- const fmt = keyword === 'format' && paramsVal ? paramsVal.format : undefined
6618
- const { errorSite } = require('./enrich-site')
6619
- const siteVar = `_S${ctx.varCounter++}`
6620
- ctx.closureVars.push(siteVar)
6621
- // Inside a shared definition (emitSharedDefC) the site holds the path past
6622
- // the definition and the reference's own comes in as `_sp`.
6623
- const rel = sp.startsWith(SP_PLACEHOLDER)
6624
- ctx.closureVals.push(errorSite(keyword, unescapeSp(rel ? sp.slice(SP_PLACEHOLDER.length) : sp), paramsVal, fmt))
6625
- return `(_e=_ap(_A,_e,_mk(${siteVar},${pathExpr || '""'},${paramsShared || paramsCode},${msgCode},${valueExpr},d${rel ? ',_sp' : ''}),-1))`
6626
- }
6627
-
6628
- function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts) {
6629
- const inputSchema = schema
6630
- schema = prepareForCodegen(schema, schemaMap)
6631
- // Same provably-local rule as the error generator above.
6632
- if (typeof schema === 'object' && schema !== null) {
6633
- const s = JSON.stringify(schema)
6634
- // unevaluatedProperties and unevaluatedItems that are not provably local
6635
- // are worked out at run time (genUnevaluatedDynamicC,
6636
- // genUnevaluatedItemsDynamicC).
6637
- // A schema that references itself, through `$ref: "#"` or a definition on
6638
- // a cycle, has those references written as calls under the cycle guard
6639
- // (emitDefFnC).
6640
- // Bail on oneOf/anyOf — branch-collapse aggregation lives in genCodeE only.
6641
- // Letting genCodeC emit the legacy "must match exactly one" placeholder
6642
- // would short-circuit the ATA4001/ATA4002/ATA4003 contract. The boolean
6643
- // fast path plus errFn fallback handles these schemas with collapse.
6644
- // Branches are collapsed into one error by the error generator's emitter
6645
- // (emitBranchCollapse). Its errors are plain, so the rich form, which
6646
- // builds every error enriched where it fails, declines them.
6647
- // In the rich form the collapsed error and its branch errors come out
6648
- // plain (the branches are the error generator's) and are enriched when
6649
- // presented; every other error is built enriched. See richBuilder.
6650
- }
6651
- if (typeof schema === 'boolean') {
6652
- // A `false` root has one fixed error; the rich form leaves it to enrich.
6653
- if (!schema && opts && (opts.rich || opts.resultShape)) return null
6654
- if (opts && opts.resultShape) return null
6655
- return schema
6656
- ? () => VALID_RESULT
6657
- : () => ({ valid: false, errors: [{ keyword: 'not', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
6658
- }
6659
- if (typeof schema !== 'object' || schema === null) return null
6660
- if (!sharedCodegenGate(schema, schemaMap)) return null
6661
- if (schema.patternProperties) {
6662
- for (const [pat, sub] of Object.entries(schema.patternProperties)) {
6663
- if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
6664
- }
6665
- }
6666
- if (schema.dependentSchemas) {
6667
- for (const sub of Object.values(schema.dependentSchemas)) {
6668
- if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
6669
- }
6670
- }
6671
- if (schema.propertyNames && typeof schema.propertyNames === 'object' && !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return null
6672
-
6673
- // Build anchors map for $ref/#anchor and $dynamicRef resolution
6674
- const cRootDefs = schema.$defs || schema.definitions || null
6675
- const cAnchors = {}
6676
- if (schema.$dynamicAnchor) cAnchors['#' + schema.$dynamicAnchor] = schema
6677
- if (schema.$anchor) cAnchors['#' + schema.$anchor] = schema
6678
- if (typeof schema.$id === 'string' && schema.$id.startsWith('#')) cAnchors[schema.$id] = schema
6679
- if (cRootDefs) {
6680
- for (const def of Object.values(cRootDefs)) {
6681
- if (def && typeof def === 'object') {
6682
- if (def.$dynamicAnchor) cAnchors['#' + def.$dynamicAnchor] = def
6683
- if (def.$anchor) cAnchors['#' + def.$anchor] = def
6684
- if (typeof def.$id === 'string' && def.$id.startsWith('#')) cAnchors[def.$id] = def
6685
- }
6686
- }
6687
- }
6688
- if (schemaMap) {
6689
- for (const ext of schemaMap.values()) {
6690
- if (ext && typeof ext === 'object') {
6691
- if (ext.$dynamicAnchor && !cAnchors['#' + ext.$dynamicAnchor]) cAnchors['#' + ext.$dynamicAnchor] = ext
6692
- if (ext.$anchor && !cAnchors['#' + ext.$anchor]) cAnchors['#' + ext.$anchor] = ext
6693
- if (typeof ext.$id === 'string' && ext.$id.startsWith('#') && !cAnchors[ext.$id]) cAnchors[ext.$id] = ext
6694
- }
6695
- }
6696
- }
6697
-
6698
- // The same two bails the boolean generator makes. Without them this path
6699
- // emits nothing for a reference it cannot resolve or follow, and an empty
6700
- // program is returned below as `() => VALID_RESULT`, so every constraint
6701
- // behind the reference is dropped instead of the schema routing to the
6702
- // interpreted engine.
6703
- if (hasUnresolvableRef(schema, cRootDefs, cAnchors, schemaMap, new Set())) return null
6704
- if (needsBaseTracking(schema, schemaMap, new Set())) return null
6705
-
6706
- // runtimeShape: errors without the ordinal, see compileToJSCodegenWithErrors.
6707
- // `_ap` adds an error to a list that may not exist yet: the first one
6708
- // creates it as a one-element literal. Pushing onto an empty `[]` grew its
6709
- // backing store on every rejection, about 7% of reading errors across the
6710
- // official suite.
6711
- const ctx = { noOrdinal: !!(opts && opts.runtimeShape), rich: !!(opts && opts.rich), inCombined: true, hoisted: [], rootC: schema, varCounter: 0, preamble: [], helperCode: [], shared: [], closureVars: ['_cpLen', '_A', '_ap', '_sr', '_srX'], closureVals: [_cpLen, sharedRt().apState(), sharedRt().ap, sharedRt().sr, sharedRt().srX], sharedRuntime: true,
6712
- rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: inputSchema, userFormats: userFormats || null }
6713
- const lines = []
6714
- try {
6715
- genCodeC(schema, 'd', '', lines, ctx, '#')
6716
- } catch (e) {
6717
- if (e === DECLINE) return null
6718
- throw e
6719
- }
6720
- if (lines.length === 0) return () => VALID_RESULT
6721
-
6722
- // Use factory pattern: closure vars (regexes, etc.) created once, not per call.
6723
- // The helpers are constants too and are declared in the factory: inside the
6724
- // function they were rebuilt on every call. `__ataSafeRe` is bound when a
6725
- // helper compiles a pattern through it, which the boolean generator does for
6726
- // subschemas under not/if/contains; without the binding those threw a
6727
- // ReferenceError on the first document that reached them.
6728
- if (opts && opts.resultShape) {
6729
- ctx.closureVars.push('_ER', '_EE', '_SRT', '_FB', '_VF')
6730
- ctx.closureVals.push(opts.resultShape.Rejection, opts.resultShape.empty, opts.resultShape.sort, opts.resultShape.fallback, opts.resultShape.verdict || null)
6731
- }
6732
- if (ctx.usesBranchCollapse) bindRt(ctx, ['__ataCollapse', '__ataMulti'])
6733
- const closureNames = ctx.usesSafeRe ? [...ctx.closureVars, '__ataSafeRe'] : ctx.closureVars
6734
- const closureArgs = ctx.usesSafeRe ? [...ctx.closureVals, compileSafe] : ctx.closureVals
6735
- const closureParams = closureNames.join(',')
6736
- // A definition the error generator wrote as a named function inside a
6737
- // branch (one on a cycle, or referenced from several places) reads the
6738
- // cycle-guard state the error function declares. Here that state is
6739
- // declared too, and the checks run as the error function runs them: in a
6740
- // body of their own, again in the guarded mode when data turns out to
6741
- // point back at itself. This used to decline, which left every schema
6742
- // whose oneOf or anyOf branches reach a shared definition without one
6743
- // pass.
6744
- const guardList = [...(ctx.defFns ? ctx.defFns.values() : []), ...(ctx.cycFnsC || [])]
6745
- const guardDefs = guardList.length ? guardList : null
6746
- if (guardDefs) ctx.helperCode.unshift(emitGuardState())
6747
- // Collapsed oneOf/anyOf: the helper the error generator embeds, `_all` for
6748
- // the branch functions (this function collects everything), and the
6749
- // runtime shape the error generator gives its literals, without code and
6750
- // docUrl. The collapsed error itself keeps its code, as everywhere.
6751
- if (ctx.usesBranchCollapse && opts && opts.runtimeShape) {
6752
- const shape = (src) => src.replace(/code:'ATA\d{4}',/g, '').replace(/,docUrl:'https:\/\/ata-validator\.com\/e\/ATA\d{4}'/g, '')
6753
- for (let i = 0; i < ctx.helperCode.length; i++) ctx.helperCode[i] = shape(ctx.helperCode[i])
6754
- for (let i = 0; i < lines.length; i++) lines[i] = shape(lines[i])
6755
- }
6756
- // Lazy error array: no allocation for valid data (the common case)
6757
- // resultShape: validate() itself for a validator whose errors are being read
6758
- // (see validator-core): the result objects built here, so one pass decides,
6759
- // collects and answers, with nothing between the caller and the checks.
6760
- const rs = opts && opts.resultShape
6761
- // Counts shared between a node's unevaluatedProperties and its anyOf and
6762
- // oneOf (genUnevaluatedDynamicC), visible to every line of the function.
6763
- let hoistDecl = ctx.hoisted.length ? `let ${ctx.hoisted.map((n) => n + '=null').join(',')};` : ''
6764
- let checks = lines.join('\n ')
6765
- let allDecl = ctx.usesBranchCollapse ? 'const _all=true;' : ''
6766
- if (guardDefs) {
6767
- const clears = ['_stk.clear()'].concat(guardDefs.map((n) => `${n}_s.clear()`)).join(';')
6768
- ctx.helperCode.push(`function _bodyC(d){${hoistDecl}${allDecl}let _e;\n ${checks}\n return _e}`)
6769
- hoistDecl = ''
6770
- allDecl = ''
6771
- checks = `_sd=0;try{_e=_bodyC(d)}catch(_err){if(_err!==_CYC)throw _err;_sg=true;${clears};${AP_RESET}try{_e=_bodyC(d)}finally{_sg=false}}`
6772
- }
6773
- // In the result shape this function is validate() itself, so a document
6774
- // that makes it throw is answered by the caller's previous path instead
6775
- // (`_FB`). A thrown error reaching the caller is the one thing this form
6776
- // must not add; a try that does not throw costs nothing measurable.
6777
- const inner = rs
6778
- // With collapsed branches, collecting runs every branch of every oneOf
6779
- // and anyOf in full, which an accepted document does not need: the
6780
- // verdict answers it first.
6781
- // Not when every collapse is guarded by a count (emitBranchCollapse): an
6782
- // accepted document then runs no branch twice, and a rejected one would
6783
- // pay for the verdict for nothing.
6784
- ? `try{${hoistDecl}${ctx.usesBranchCollapse && ctx.unguardedCollapse ? 'if(_VF!==null&&_VF(d))return{valid:true,data:d,errors:_EE};' : ''}${allDecl}let _e;${AP_RESET}\n ` + checks +
6785
- // A collapsed branch error is presented even alone (its ordinal and
6786
- // raw branch errors stay inside); otherwise only a list of two or more
6787
- // needs ordering.
6788
- `\n return _e?new _ER(_A.mo?_e:${process.env.ATA_CHECK_SORT ? '_srX(_A,_e,_SRT)' : '_sr(_A,_e,_SRT)'}):{valid:true,data:d,errors:_EE}}catch(_x){return _FB(d)}`
6789
- : hoistDecl + allDecl + `let _e;${AP_RESET}\n ` + checks + `\n return _e?{valid:false,errors:_e}:R`
6790
- // The verdict generator's hoisted functions (subtreeGuard writes guards
6791
- // through it) come first: a oneOf branch it could not hoist was a closure
6792
- // built at every call of the guard, six per element of a json-patch list.
6793
- const helpers = (ctx.preamble.length ? ctx.preamble.join('\n ') + '\n ' : '') + (ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : '')
6794
-
6795
- try {
6796
- if (typeof process !== 'undefined' && process.env && process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + helpers + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
6797
- const factory = new Function('R' + (closureParams ? ',' + closureParams : ''),
6798
- assignOrdKeys(ctx, `${helpers}return function _vC(d){${inner}}`))
6799
- return factory(VALID_RESULT, ...closureArgs)
6800
- } catch (e) {
6801
- if (typeof process !== 'undefined' && process.env && process.env.ATA_DEBUG) console.error('compileToJSCombined error:', e.message, '\n', inner.slice(0, 500))
6802
- return null
6803
- }
6804
- }
6805
-
6806
- // Combined code generator: type-aware like genCode, error-collecting like genCodeE.
6807
- // After type check passes → use optimizations (destructuring, no guards).
6808
- // If type check fails → push error, skip property checks (they'd crash).
6809
- // What of a node still applies to a value that failed its `type`, for the
6810
- // combined generator: the node without `type`, without what it emits outside
6811
- // the type block anyway (enum, const, anyOf, oneOf), and without the keywords
6812
- // of the declared types, which no value of another type meets. Null when
6813
- // nothing that validates is left.
6814
- const TYPE_KEYWORDS = {
6815
- object: ['properties', 'patternProperties', 'additionalProperties', 'required', 'propertyNames', 'minProperties', 'maxProperties', 'dependentRequired', 'dependentSchemas', 'dependencies', 'unevaluatedProperties'],
6816
- array: ['items', 'prefixItems', 'additionalItems', 'contains', 'minContains', 'maxContains', 'minItems', 'maxItems', 'uniqueItems', 'unevaluatedItems'],
6817
- string: ['minLength', 'maxLength', 'pattern'],
6818
- number: ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'],
6819
- }
6820
- TYPE_KEYWORDS.integer = TYPE_KEYWORDS.number
6821
- const NOT_VALIDATING = new Set(['$schema', '$id', '$anchor', '$comment', '$defs', 'definitions', 'title', 'description', 'default', 'examples', 'deprecated', 'readOnly', 'writeOnly', '$vocabulary'])
6822
- function typeMismatchRest(schema, types) {
6823
- const drop = new Set(['type', 'enum', 'const', 'anyOf', 'oneOf'])
6824
- for (const t of types) for (const k of (TYPE_KEYWORDS[t] || [])) drop.add(k)
6825
- // A non-integer number reaches the type block already (numberToo).
6826
- let rest = null
6827
- for (const k of Object.keys(schema)) {
6828
- if (drop.has(k)) continue
6829
- if (rest === null) rest = {}
6830
- setOwn(rest, k, schema[k])
6831
- }
6832
- if (rest === null) return null
6833
- // A node whose reference was already written (genCodeCNode) keeps that
6834
- // mark, or the reference is written again here and its errors twice.
6835
- if (schema[REF_DONE]) Object.defineProperty(rest, REF_DONE, { value: true })
6836
- for (const k of Object.keys(rest)) if (!NOT_VALIDATING.has(k) && !k.startsWith('x-')) return rest
6837
- return null
6838
- }
6839
-
6840
- function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6841
- return withPlain(schema, v, lines, ctx, () => genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix))
6842
- }
6843
- // A definition referenced from several places, written once as a function
6844
- // and called at each reference, instead of its whole code at every one: the
6845
- // combined function of SchemaStore's cloud-run (48 KB of schema) came to
6846
- // 1.1 MB of source and sarif's (79 KB) to 2.8 MB, and compiling those was most
6847
- // of the 50 to 300 ms a first rejection with its errors read took. The body is
6848
- // generated against a placeholder schema path that becomes the `_sp` argument,
6849
- // as the error generator does (genCodeENode), so an error names the path it
6850
- // was reached by. It collects into its own list, returned to the caller, and
6851
- // declares its own branch counts (genUnevaluatedDynamicC). The document root
6852
- // comes in as `d` for the rich errors that read it. Not for a definition on a
6853
- // reference cycle, which keeps its existing path. False when not shared.
6854
- const COMBINED_DEF_FN_MIN = 160
6855
- const _defOwnChars = new WeakMap()
6856
- // The definition's own size, its annotations left out, measured once.
6857
- function defOwnChars (def) {
6858
- if (def === null || typeof def !== 'object') return 0
6859
- let n = _defOwnChars.get(def)
6860
- if (n === undefined) {
6861
- n = 0
6862
- const walk = (x) => { if (x === null || typeof x !== 'object') { n += 6; return } if (Array.isArray(x)) { for (const y of x) walk(y); return } for (const k of Object.keys(x)) { if (SIZE_SKIPS.has(k)) continue; n += k.length + 4; walk(x[k]) } }
6863
- walk(def)
6864
- _defOwnChars.set(def, n)
6865
- }
6866
- return n
6867
- }
6868
-
6869
- function emitSharedDefC(defName, v, pathExpr, lines, ctx, schemaPrefix) {
6870
- if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
6871
- if (!ctx.sharedDefs) ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs)
6872
- const cyclic = ctx.cyclicDefs.has(defName)
6873
- // In the combined function every definition of some size is a function of
6874
- // its own, referenced once or not: expanded, a 25-definition API schema
6875
- // was one 122 KB function, which the optimizing compiler took tens of
6876
- // thousands of calls to reach, so its first twenty thousand answers cost
6877
- // 89 ns each where the hundred-thousandth costs 10. Functions the size of
6878
- // one definition tier up early, and the hot ones first.
6879
- if (!cyclic && !ctx.sharedDefs.has(defName) && !(ctx.inCombined && defOwnChars(ctx.rootDefs[defName]) >= COMBINED_DEF_FN_MIN)) return false
6880
- emitDefFnC(defName, ctx.rootDefs[defName], cyclic, v, pathExpr, lines, ctx, schemaPrefix)
6881
- return true
6882
- }
6883
-
6884
- // One schema as a function of its own in the combined function (see
6885
- // emitSharedDefC). The name is registered before the body is written, so a
6886
- // reference back to it inside becomes a call. One that can re-enter itself,
6887
- // a definition on a cycle or the root through `$ref: "#"`, goes through the
6888
- // cycle guard the error generator's definitions use: depth counted, and in
6889
- // the guarded pass a value already being checked is not checked again.
6890
- function emitDefFnC(key, target, cyclic, v, pathExpr, lines, ctx, schemaPrefix) {
6891
- if (!ctx.defFnsC) ctx.defFnsC = new Map()
6892
- let fnName = ctx.defFnsC.get(key)
6893
- if (!fnName) {
6894
- fnName = '_defC' + ctx.defFnsC.size + '_' + (key === '#' ? 'root' : key.replace(/[^A-Za-z0-9_]/g, '_'))
6895
- ctx.defFnsC.set(key, fnName)
6896
- const outerHoisted = ctx.hoisted
6897
- ctx.hoisted = []
6898
- // The body is a function of its own: a reference the caller was in the
6899
- // middle of expanding is not on the stack inside it, where reaching it
6900
- // again is a call (this function, or another one) rather than a loop.
6901
- // With the caller's stack, a definition expanded once on the way into
6902
- // the root's function was taken for a cycle inside it, and the schema
6903
- // (a tree whose nodes hold trees) lost its one-pass function.
6904
- const outerRefs = ctx.refStack
6905
- ctx.refStack = new Set()
6906
- const bodyLines = []
6907
- let hoisted
6908
- try {
6909
- ctx.inDefC = (ctx.inDefC || 0) + 1
6910
- genCodeC(target, '_dv', '_p', bodyLines, ctx, SP_PLACEHOLDER)
6911
- } finally {
6912
- ctx.inDefC--
6913
- hoisted = ctx.hoisted
6914
- ctx.hoisted = outerHoisted
6915
- ctx.refStack = outerRefs
6916
- }
6917
- let body = bodyLines.join('\n ')
6918
- body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
6919
- if (body.includes(SP_PLACEHOLDER)) throw DECLINE
6920
- const decl = hoisted.length ? `let ${hoisted.map((n) => n + '=null').join(',')};` : ''
6921
- const fn = `function ${cyclic ? fnName + '_b' : fnName}(_dv,_p,_sp,d,_so){const _all=true;${decl}let _e;\n ${body}\n return _e}`
6922
- if (cyclic) {
6923
- if (!ctx.cycFnsC) ctx.cycFnsC = []
6924
- ctx.cycFnsC.push(fnName)
6925
- ctx.helperCode.push(
6926
- `const ${fnName}_s=new Set()\n function ${fnName}(_dv,_p,_sp,d,_so){\n ` +
6927
- `if(_sg){if(typeof _dv!=='object'||_dv===null)return ${fnName}_b(_dv,_p,_sp,d,_so);if(${fnName}_s.has(_dv))return;${fnName}_s.add(_dv);try{return ${fnName}_b(_dv,_p,_sp,d,_so)}finally{${fnName}_s.delete(_dv)}}\n ` +
6928
- `if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(_dv,_p,_sp,d,_so)\n _sd--\n return _r\n }\n ` + fn)
6929
- } else {
6930
- ctx.helperCode.push(fn)
6931
- }
6932
- }
6933
- lines.push(`{const _r=${fnName}(${v},${pathExpr || '""'},'${schemaPrefix}',d,${apOrd(ctx, `${schemaPrefix}/$ref`, true)});if(_r!==undefined){if(_e===undefined)_e=_r;else for(let _i=0;_i<_r.length;_i++)_e.push(_r[_i])}}`)
6934
- }
6935
-
6936
- const REF_DONE = Symbol('ata.refDone')
6937
- function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6938
- if (!schemaPrefix) schemaPrefix = '#'
6939
- // Per-node, for the reason spelled out in genCode.
6940
- let ppHandledPropertyNames = false
6941
- if (schema === false) {
6942
- // Leading semicolon: the previous generated line may end in an
6943
- // expression, and a line starting with `(` would be parsed as a call.
6944
- 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'}`))
6945
- return
6946
- }
6947
- if (schema === true) return
6948
- if (typeof schema !== 'object' || schema === null) return
6949
-
6950
- // $ref — resolve local, anchor, and cross-schema refs
6951
- if (schema.$ref && !schema[REF_DONE]) {
6952
- // A keyword beside $ref that validates applies too (2020-12; draft 7
6953
- // drops such siblings when the schema is read). The reference is written
6954
- // on its own, then the node again with the reference marked done, on a
6955
- // fresh copy so the mark cannot reach the same schema object met through
6956
- // another path; the copy keeps `$ref` for unevaluatedProperties and
6957
- // unevaluatedItems, which read its annotations. Dropping the siblings
6958
- // once accepted what they reject, which is why this declined. Not in a
6959
- // branch function, which the error generator writes.
6960
- if (Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))) {
6961
- if (!ctx.inCombined || ctx.inBranchFn) throw DECLINE
6962
- genCodeCNode({ $ref: schema.$ref }, v, pathExpr, lines, ctx, schemaPrefix)
6963
- const rest = { ...schema }
6964
- Object.defineProperty(rest, REF_DONE, { value: true })
6965
- genCodeCNode(rest, v, pathExpr, lines, ctx, schemaPrefix)
6966
- return
6967
- }
6968
- // A reference back into what is being expanded is a call to that
6969
- // schema's function (emitDefFnC), never nothing: an expansion that stops
6970
- // and emits no lines reads as valid.
6971
- if (schema.$ref === '#') {
6972
- if (ctx.inCombined && !ctx.inBranchFn && ctx.rootC) { emitDefFnC('#', ctx.rootC, true, v, pathExpr, lines, ctx, schemaPrefix); return }
6973
- throw DECLINE
6974
- }
6975
- const m = schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
6976
- if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
6977
- if (ctx.inCombined && !ctx.inBranchFn && emitSharedDefC(m[1], v, pathExpr, lines, ctx, schemaPrefix)) return
6978
- if (ctx.refStack.has(schema.$ref)) throw DECLINE
6979
- ctx.refStack.add(schema.$ref)
6980
- genCodeC(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx, schemaPrefix)
6981
- ctx.refStack.delete(schema.$ref)
6982
- return
6983
- }
6984
- // Anchor ref: "#foo" — resolve via rootDefs or anchors map
6985
- if (!m && schema.$ref.startsWith('#') && !schema.$ref.startsWith('#/')) {
6986
- const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
6987
- const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
6988
- if (anchorTarget) {
6989
- if (ctx.refStack.has(schema.$ref)) throw DECLINE
6990
- ctx.refStack.add(schema.$ref)
6991
- genCodeC(anchorTarget, v, pathExpr, lines, ctx, schemaPrefix)
6992
- ctx.refStack.delete(schema.$ref)
6993
- return
6994
- }
6995
- }
6996
- if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
6997
- if (ctx.refStack.has(schema.$ref)) throw DECLINE
6998
- ctx.refStack.add(schema.$ref)
6999
- genCodeC(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
7000
- ctx.refStack.delete(schema.$ref)
7001
- return
7002
- }
7003
- // Cross-schema ref with JSON pointer fragment ("<id>#/<path>")
7004
- if (ctx.schemaMap && schema.$ref.includes('#') && !schema.$ref.startsWith('#')) {
7005
- const r = resolveCrossSchemaRef(schema.$ref, ctx.schemaMap)
7006
- if (r) {
7007
- if (ctx.refStack.has(schema.$ref)) throw DECLINE
7008
- ctx.refStack.add(schema.$ref)
7009
- genCodeC(r.schema, v, pathExpr, lines, ctx, schemaPrefix)
7010
- ctx.refStack.delete(schema.$ref)
7011
- return
7012
- }
7013
- }
7014
- }
7015
-
7016
- // $dynamicRef — resolve via anchors map
7017
- if (schema.$dynamicRef) genDynamicRefC(schema, v, pathExpr, lines, ctx, schemaPrefix)
7018
-
7019
- const types = schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : null
7020
- let isObj = false, isArr = false, isStr = false, isNum = false
7021
-
7022
- // Pre-allocate error objects as closure variables for static paths.
7023
- // This shrinks the generated function body → better V8 JIT on valid path.
7024
- const isStaticPath = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
7025
- const fail = (keyword, schemaSuffix, paramsCode, msgCode) => {
7026
- const sp = schemaPrefix + '/' + schemaSuffix
7027
- if (ctx.rich) {
7028
- // A static site shares one frozen params object between calls, as its
7029
- // hoisted literal does below; a dynamic one builds its params per call.
7030
- let shared = null
7031
- if (isStaticPath && msgCode.startsWith("'") && !msgCode.includes('+')) {
7032
- let pv
7033
- try { pv = Function('return ' + paramsCode)() } catch { pv = undefined }
7034
- if (pv !== undefined) {
7035
- shared = `_P${ctx.varCounter++}`
7036
- ctx.closureVars.push(shared); ctx.closureVals.push(Object.freeze(pv))
7037
- }
7038
- }
7039
- return errPushC(ctx, keyword, sp, pathExpr, paramsCode, msgCode, v, null, shared)
7040
- }
7041
- // Hoisting a static error as one shared literal keeps a function that
7042
- // also answers accepted documents small. In the runtime shape this
7043
- // function only runs for documents being rejected (the verdict is its own
7044
- // function), and a shared literal has to be copied for the caller on
7045
- // every read, so there it is written fresh.
7046
- if (isStaticPath && !ctx.noOrdinal && msgCode.startsWith("'") && !msgCode.includes('+')) {
7047
- // Try to evaluate paramsCode as a static constant
7048
- let paramsVal
7049
- try { paramsVal = Function('return ' + paramsCode)() } catch { /* dynamic params — fall through */ }
7050
- if (paramsVal !== undefined) {
7051
- // Static error: pre-allocate as frozen closure variable
7052
- const ei = ctx.varCounter++
7053
- const errVar = `_E${ei}`
7054
- // The path and message are source literals; evaluate them rather than
7055
- // strip the quotes, so an escaped quote or backslash in a key comes
7056
- // out as the character and not as the escape.
7057
- let pathVal, msgVal
7058
- try {
7059
- pathVal = pathExpr ? Function('return ' + pathExpr)() : ''
7060
- msgVal = Function('return ' + msgCode)()
7061
- } catch {
7062
- // Not a literal that parses on its own (a key with a raw line break):
7063
- // leave it to the inline form below, which declines the same way the
7064
- // whole function would.
7065
- pathVal = undefined
7066
- }
7067
- if (pathVal !== undefined) {
7068
- const spVal = unescapeSp(sp)
7069
- ctx.closureVars.push(errVar)
7070
- const o = ctx.noOrdinal ? null : ordinalFor(ctx.rootSchema, spVal)
7071
- const lit = {keyword, instancePath: pathVal, schemaPath: spVal, params: Object.freeze(paramsVal), message: msgVal}
7072
- if (o !== null) lit._o = o
7073
- ctx.closureVals.push(sharedErr(lit, ctx.rootSchema))
7074
- return `(_e=_ap(_A,_e,${errVar},${apOrd(ctx, sp)}))`
7075
- }
7076
- }
7077
- }
7078
- // Dynamic path (e.g., array index): inline as before
7079
- return `(_e=_ap(_A,_e,{keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode}${ordinalField(ctx, sp)}},${apOrd(ctx, sp)}))`
7080
- }
7081
-
7082
- // enum and const apply to a value of any type, so a value that fails
7083
- // `type` is still checked against them, as the other engines do. They sat
7084
- // inside the type-success block, and `{type: 'number', enum: [1]}` reported
7085
- // only `type` for 'x' where the interpreter reports both.
7086
- const emitEnumConst = () => {
7087
- // enum
7088
- if (schema.enum) {
7089
- 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'")}}`)
7090
- }
7091
-
7092
- // const
7093
- if (schema.const !== undefined) {
7094
- const cv = schema.const
7095
- if (cv === null || typeof cv !== 'object') {
7096
- lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
7097
- } else {
7098
- lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)})){${fail('const', 'const', `{allowedValue:${emitParamConstant(ctx, schema.const)}}`, "'must be equal to constant'")}}`)
7099
- }
7100
- }
7101
- }
7102
-
7103
- if (types) {
7104
- const conds = types.map(t => {
7105
- switch (t) {
7106
- case 'object': return `(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
7107
- case 'array': return `Array.isArray(${v})`
7108
- case 'string': return `typeof ${v}==='string'`
7109
- case 'number': return `Number.isFinite(${v})`
7110
- case 'integer': return `Number.isInteger(${v})`
7111
- case 'boolean': return `typeof ${v}==='boolean'`
7112
- case 'null': return `${v}===null`
7113
- default: return 'true'
7114
- }
7115
- })
7116
- const expected = types.join(', ')
7117
- // As written: a one-element array stays an array, as the interpreted engine
7118
- // reports it.
7119
- const expectedParam = !Array.isArray(schema.type) ? `'${types[0]}'` : JSON.stringify(types)
7120
- // Type check: push error but continue — wrap remaining in type-success block
7121
- const typeOk = `_tok${ctx.varCounter++}`
7122
- lines.push(`const ${typeOk}=${conds.join('||')}`)
7123
- lines.push(`if(!${typeOk}){${fail('type', 'type', `{type:${expectedParam}}`, `'must be ${expected}'`)}}`)
7124
- // Subsequent optimized code runs inside if(typeOk){...}
7125
- if (types.length === 1) {
7126
- isObj = types[0] === 'object'
7127
- isArr = types[0] === 'array'
7128
- isStr = types[0] === 'string'
7129
- isNum = types[0] === 'number' || types[0] === 'integer'
7130
- }
7131
- emitEnumConst()
7132
- // A number that is not an integer fails `integer` and is still a number:
7133
- // minimum, multipleOf and the rest apply to it, and the other engines
7134
- // report them next to the type error. The block opens for any number.
7135
- const numberToo = types.includes('integer') && !types.includes('number')
7136
- lines.push(numberToo ? `if(${typeOk}||typeof ${v}==='number'){` : `if(${typeOk}){`)
7137
- } else {
7138
- emitEnumConst()
7139
- }
7140
-
7141
- // required — use destructuring when type is object (SAFE because type check already passed)
7142
- const requiredSet = new Set(schema.required || [])
7143
- const hoisted = {}
7144
- if (schema.required && schema.properties && isObj) {
7145
- const destructKeys = []
7146
- for (const key of schema.required) {
7147
- if (schema.properties[key]) {
7148
- const lv = `_h${ctx.varCounter++}`
7149
- hoisted[key] = lv
7150
- destructKeys.push(`${JSON.stringify(key)}:${lv}`)
7151
- }
7152
- }
7153
- if (destructKeys.length > 0) {
7154
- lines.push(`let{${destructKeys.join(',')}}=${v}`)
7155
- // A value the object only inherits reads as absent, here and in the
7156
- // property checks that use the same local below.
7157
- for (const key of schema.required) {
7158
- if (hoisted[key]) lines.push(`if(${hoisted[key]}!==undefined&&!${ownGuard(ctx, v, key)})${hoisted[key]}=undefined`)
7159
- }
7160
- }
7161
- for (const key of schema.required) {
7162
- const check = hoisted[key] ? `${hoisted[key]}===undefined` : `!${ownKeyExpr(ctx, v, key)}`
7163
- if (isStaticPath && !ctx.rich && !ctx.noOrdinal) {
7164
- const ei = ctx.varCounter++
7165
- const errVar = `_E${ei}`
7166
- const pathVal = pathExpr ? literalValue(pathExpr) : ''
7167
- ctx.closureVars.push(errVar)
7168
- ctx.closureVals.push(sharedErr({keyword: 'required', instancePath: pathVal, schemaPath: unescapeSp(`${schemaPrefix}/required`), params: Object.freeze({missingProperty: key}), message: `must have required property '${key}'`}, ctx.rootSchema))
7169
- lines.push(`if(${check}){(_e=_ap(_A,_e,${errVar},${apOrd(ctx, `${schemaPrefix}/required`)}))}`)
7170
- } else {
7171
- lines.push(`if(${check}){${errPushC(ctx, 'required', `${schemaPrefix}/required`, pathExpr, `{missingProperty:'${esc(key)}'}`, `"must have required property '${esc(key)}'"`, v, `{keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/required'${ordinalField(ctx, `${schemaPrefix}/required`)},params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'"}`)}}`)
7172
- }
7173
- }
7174
- } else if (schema.required) {
7175
- for (const key of schema.required) {
7176
- const isStatic = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
7177
- if (isStatic && !ctx.rich && !ctx.noOrdinal) {
7178
- const ei = ctx.varCounter++
7179
- const errVar = `_E${ei}`
7180
- const pathVal = pathExpr ? literalValue(pathExpr) : ''
7181
- ctx.closureVars.push(errVar)
7182
- ctx.closureVals.push(sharedErr({keyword: 'required', instancePath: pathVal, schemaPath: unescapeSp(`${schemaPrefix}/required`), params: Object.freeze({missingProperty: key}), message: `must have required property '${key}'`}, ctx.rootSchema))
7183
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!${ownKeyExpr(ctx, v, key)}){(_e=_ap(_A,_e,${errVar},${apOrd(ctx, `${schemaPrefix}/required`)}))}`)
7184
- } else {
7185
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!${ownKeyExpr(ctx, v, key)}){${errPushC(ctx, 'required', `${schemaPrefix}/required`, pathExpr, `{missingProperty:'${esc(key)}'}`, `"must have required property '${esc(key)}'"`, v, `{keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/required'${ordinalField(ctx, `${schemaPrefix}/required`)},params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'"}`)}}`)
7186
- }
7187
- }
7188
- }
7189
-
7190
- // numeric — skip type guard if known
7191
- 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}'`)}}`) }
7192
- 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}'`)}}`) }
7193
- 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}'`)}}`) }
7194
- 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}'`)}}`) }
7195
- if (schema.multipleOf !== undefined) {
7196
- const m = schema.multipleOf
7197
- const ci = ctx.varCounter++
7198
- lines.push(`{if(typeof ${v}==='number'&&${multipleOfBad(v, m)}){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
7199
- }
7200
-
7201
- // string length — s.length fast paths, _cpLen only in the uncertain band.
7202
- if (schema.minLength !== undefined) {
7203
- const M = schema.minLength
7204
- const inner = `${v}.length<${M}||(${v}.length<${M * 2}&&_cpLen(${v})<${M})`
7205
- const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
7206
- lines.push(`if(${c}){${fail('minLength', 'minLength', `{limit:${M}}`, `'must NOT have fewer than ${M} characters'`)}}`)
7207
- }
7208
- if (schema.maxLength !== undefined) {
7209
- const X = schema.maxLength
7210
- const inner = `${v}.length>${X * 2}||(${v}.length>${X}&&_cpLen(${v})>${X})`
7211
- const c = isStr ? inner : `typeof ${v}==='string'&&(${inner})`
7212
- lines.push(`if(${c}){${fail('maxLength', 'maxLength', `{limit:${X}}`, `'must NOT have more than ${X} characters'`)}}`)
7213
- }
7214
- if (schema.pattern) {
7215
- const inlineCheck = compilePatternInline(schema.pattern, v)
7216
- if (inlineCheck) {
7217
- const c = isStr ? `!(${inlineCheck})` : `typeof ${v}==='string'&&!(${inlineCheck})`
7218
- lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
7219
- } else {
7220
- const ri = ctx.varCounter++
7221
- const reVar = `_re${ri}`
7222
- ctx.closureVars.push(reVar)
7223
- ctx.closureVals.push(useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern, reFlags(schema.pattern)))
7224
- const c = isStr ? `!${reVar}.test(${v})` : `typeof ${v}==='string'&&!${reVar}.test(${v})`
7225
- lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
7226
- }
7227
- }
7228
- if (schema.format) {
7229
- const fc = FORMAT_CODEGEN[schema.format]
7230
- if (fc) {
7231
- // Same rule as the error codegen: one violation, one error. The first
7232
- // failing statement pushes and leaves the labeled block.
7233
- const ri = ctx.varCounter++
7234
- 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}}`)
7235
- lines.push(`_fmt${ri}:{${code}}`)
7236
- } else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
7237
- const safeName = schema.format.replace(/[^a-zA-Z0-9_]/g, '_')
7238
- const closureName = `_uf_${safeName}`
7239
- if (!ctx.closureVars.includes(closureName)) {
7240
- ctx.closureVars.push(closureName)
7241
- ctx.closureVals.push(ctx.userFormats[schema.format])
7242
- }
7243
- const guard = isStr ? '' : `typeof ${v}==='string'&&`
7244
- lines.push(`if(${guard}!${closureName}(${v})){${fail('format', 'format', `{format:'${esc(schema.format)}'}`, `'must match format "${esc(schema.format)}"'`)}}`)
7245
- }
7246
- }
7247
-
7248
- // array size
7249
- 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'`)}}`) }
7250
- 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'`)}}`) }
7251
-
7252
- // uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
7253
- if (schema.uniqueItems) genUniqueItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr)
7254
-
7255
- // object size
7256
- 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'`)}}`)
7257
- 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'`)}}`)
7258
-
7259
- // additionalProperties — skip if patternProperties present (handled in unified loop below)
7260
- // Small property sets: direct === chain (no Set allocation)
7261
- if (schema.additionalProperties === false && !schema.patternProperties) {
7262
- const propKeys = Object.keys(schema.properties || {})
7263
- const ci = ctx.varCounter++
7264
- // The error has to name the offending property, so the keys have to be
7265
- // materialised to report one. They do not have to be materialised to find
7266
- // out whether there is one: a for-in answers that with no allocation, and
7267
- // an accepted document is the common case. The verdict generator pays a
7268
- // bare key count here, and this kept the combined function 2.8x that on a
7269
- // valid document, which is what stopped it from replacing the
7270
- // verdict-then-revalidate pair on the error path.
7271
- const detail = (test) =>
7272
- `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'")}}`
7273
- let body
7274
- if (propKeys.length <= 8) {
7275
- // Direct chain: no Set allocation for small schemas
7276
- // With nothing declared every key is additional: an empty chain would
7277
- // leave `if()`, so the test is `true`.
7278
- const gate = propKeys.map(k => `_f${ci}!==${JSON.stringify(k)}`).join('&&') || 'true'
7279
- const checks = propKeys.map(k => `_k${ci}[_i]!==${JSON.stringify(k)}`).join('&&') || 'true'
7280
- body = `let _x${ci}=0;for(const _f${ci} in ${v}){if(${gate}){_x${ci}=1;break}}if(_x${ci}){${detail(checks)}}`
7281
- } else {
7282
- // The name set lives in the closure, built once with the function: built
7283
- // in the body it was a 33-name Set on every call of this function, which
7284
- // is where a rejected npm-badges document spent most of its 701 ns.
7285
- ctx.closureVars.push(`_a${ci}`)
7286
- ctx.closureVals.push(new Set(propKeys))
7287
- body = `let _x${ci}=0;for(const _f${ci} in ${v}){if(!_a${ci}.has(_f${ci})){_x${ci}=1;break}}if(_x${ci}){${detail(`!_a${ci}.has(_k${ci}[_i])`)}}`
7288
- }
7289
- // When every declared property is required, a clean object has exactly the
7290
- // declared keys: if the enumerated count equals the declared count and
7291
- // every declared name is there, the enumerated set is the declared set and
7292
- // nothing is extra. That is the verdict generator's count, and it skips
7293
- // comparing each key against every name, which kept this function over
7294
- // twice the verdict's cost on an accepted document. Anything else, a
7295
- // different count or a missing name, falls through to the scan, which
7296
- // names the extra keys. A name Object.prototype carries answers `in`
7297
- // through the prototype without being enumerated, so those stay on the
7298
- // scan.
7299
- const req = new Set(Array.isArray(schema.required) ? schema.required : [])
7300
- if (propKeys.length > 0 && propKeys.every((k) => req.has(k) && !PROTO_NAMES.has(k))) {
7301
- const present = propKeys.map((k) => `${JSON.stringify(k)} in ${v}`).join('&&')
7302
- body = `let _n${ci}=0;for(const _f${ci} in ${v})_n${ci}++;if(_n${ci}!==${propKeys.length}||!(${present})){${body}}`
7303
- }
7304
- lines.push(isObj
7305
- ? `{${body}}`
7306
- : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body}}`)
7307
- }
7308
-
7309
- // unevaluatedProperties: false where it is provably additionalProperties
7310
- // (the entry bail guaranteed that), keeping the keyword's own identity so
7311
- // the two engines report the same error.
7312
- if (schema.unevaluatedProperties !== undefined && schema.unevaluatedProperties !== true) {
7313
- if (unevalLocalOk(schema, 'unevaluatedProperties')) {
7314
- if (schema.additionalProperties === undefined && !schema.patternProperties) genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj)
7315
- } else {
7316
- genUnevaluatedDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj)
7317
- }
7318
- }
7319
-
7320
- // unevaluatedItems: false under the same rule: one error for the array,
7321
- // the first uncovered position as the limit. Adjacent schema-form `items`
7322
- // evaluates everything; the draft-07 array form acts as the prefix.
7323
- if (schema.unevaluatedItems !== undefined && schema.unevaluatedItems !== true) {
7324
- if (unevalLocalOk(schema, 'unevaluatedItems')) genUnevaluatedItemsFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
7325
- else genUnevaluatedItemsDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
7326
- }
7327
-
7328
- // dependentRequired
7329
- if (schema.dependentRequired) {
7330
- for (const [key, deps] of Object.entries(schema.dependentRequired)) {
7331
- for (const dep of deps) {
7332
- const isStatic = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
7333
- if (isStatic && !ctx.rich && !ctx.noOrdinal) {
7334
- const ei = ctx.varCounter++
7335
- const errVar = `_E${ei}`
7336
- const pathVal = pathExpr ? literalValue(pathExpr) : ''
7337
- ctx.closureVars.push(errVar)
7338
- ctx.closureVals.push(sharedErr({keyword: 'required', instancePath: pathVal, schemaPath: unescapeSp(`${schemaPrefix}/dependentRequired`), params: Object.freeze({missingProperty: dep}), message: `must have required property '${dep}'`}, ctx.rootSchema))
7339
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){(_e=_ap(_A,_e,${errVar},-1))}`)
7340
- } else {
7341
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){${errPushC(ctx, 'required', `${schemaPrefix}/dependentRequired`, pathExpr, `{missingProperty:'${esc(dep)}'}`, `"must have required property '${esc(dep)}'"`, v, `{keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/dependentRequired'${ordinalField(ctx, `${schemaPrefix}/dependentRequired`)},params:{missingProperty:'${esc(dep)}'},message:"must have required property '${esc(dep)}'"}`)}}`)
7342
- }
7343
- }
7344
- }
7345
- }
7346
-
7347
- // properties — use hoisted vars for required+known-object, full guard otherwise
7348
- if (schema.properties) {
7349
- for (const [key, prop] of Object.entries(schema.properties)) {
7350
- const pv = hoisted[key] || `${v}[${JSON.stringify(key)}]`
7351
- const childPath = childPathExpr(pathExpr, ptrSeg(key))
7352
- if (requiredSet.has(key) && isObj) {
7353
- // A hoisted local was already cleared if the value is only inherited.
7354
- lines.push(`if(${pv}!==undefined${hoisted[key] ? '' : '&&' + ownGuard(ctx, v, key)}){`)
7355
- genGuardedC(prop, pv, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
7356
- lines.push(`}`)
7357
- } else if (isObj) {
7358
- const oi = ctx.varCounter++
7359
- lines.push(`{const _o${oi}=${v}[${JSON.stringify(key)}];if(_o${oi}!==undefined&&${ownGuard(ctx, v, key)}){`)
7360
- genGuardedC(prop, `_o${oi}`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
7361
- lines.push(`}}`)
7362
- } else {
7363
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
7364
- genGuardedC(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
7365
- lines.push(`}`)
7366
- }
7367
- }
7368
- }
7369
-
7370
- // patternProperties — same optimizations as genCode: charCodeAt + inline key comparison + merged propertyNames
7371
- if (schema.patternProperties) {
7372
- if (genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj)) ppHandledPropertyNames = true
7373
- }
7374
- // additionalProperties as a schema: each key that is neither declared nor
7375
- // matched by a pattern, validated where it is (see genAdditionalSchemaC).
7376
- if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null) genAdditionalSchemaC(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj)
7377
-
7378
-
7379
- // dependentSchemas
7380
- if (schema.dependentSchemas) genDependentSchemasC(schema, v, pathExpr, lines, ctx, schemaPrefix)
7381
-
7382
- if (schema.propertyNames === false) genPropertyNamesFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix)
7383
-
7384
- // propertyNames — skip if already merged into patternProperties loop
7385
- if (schema.propertyNames && typeof schema.propertyNames === 'object' && !ppHandledPropertyNames) genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
7386
-
7387
- // items
7388
- if (schema.items !== undefined && schema.items !== true) {
7389
- const startIdx = schema.prefixItems ? schema.prefixItems.length : 0
7390
- const idx = `_j${ctx.varCounter}`, elem = `_ei${ctx.varCounter}`
7391
- ctx.varCounter++
7392
- const childPath = childPathDynExpr(pathExpr, idx)
7393
- lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
7394
- genGuardedC(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
7395
- lines.push(`}}`)
7396
- }
7397
-
7398
- // prefixItems
7399
- if (schema.prefixItems) genPrefixItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix)
7400
-
7401
- // contains
7402
- if (schema.contains !== undefined) genContainsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
7403
-
7404
- // allOf
7405
- if (schema.allOf) { for (let _ai = 0; _ai < schema.allOf.length; _ai++) genCodeC(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai) }
7406
-
7407
- // anyOf, oneOf. A collapsed branch applies to a value of any type, as enum
7408
- // and const do: the other engines report it next to a failed `type`, so
7409
- // in the combined function it goes after the type-success block below.
7410
- if (schema.anyOf && !ctx.inCombined) genAnyOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
7411
- if (schema.oneOf && !ctx.inCombined) genOneOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
7412
-
7413
- // not
7414
- if (schema.not !== undefined) genNotC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
7415
-
7416
- // if/then/else
7417
- if (schema.if !== undefined) genIfC(schema, v, pathExpr, lines, ctx, schemaPrefix)
7418
-
7419
- // Close type-success block if opened
7420
- if (types) {
7421
- lines.push(`}`)
7422
- // A value of another type still meets every keyword that applies to its
7423
- // own type, and the applicators (allOf, not, if/then/else, $ref): the
7424
- // other engines report those next to the type error, and the block above
7425
- // skipped them all. The keywords of the declared types cannot apply to
7426
- // it and are left out, so the else is small and does not repeat the
7427
- // declared type's subtree.
7428
- const rest = typeMismatchRest(schema, types)
7429
- if (rest !== null) {
7430
- lines.push(`else{`)
7431
- genCodeC(rest, v, pathExpr, lines, ctx, schemaPrefix)
7432
- lines.push(`}`)
7433
- }
7434
- }
7435
- if (schema.anyOf && ctx.inCombined) genAnyOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
7436
- if (schema.oneOf && ctx.inCombined) genOneOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
7437
-
7438
- }
7439
-
7440
- // Rarely used keyword families of genCodeCNode, kept out of it so their compile is
7441
- // paid only by schemas that use them.
7442
-
7443
- function genDynamicRefC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
7444
- const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
7445
- if (ctx.anchors && ctx.anchors[anchorKey]) {
7446
- const target = ctx.anchors[anchorKey]
7447
- // This generator has no named functions, so it cannot follow a reference
7448
- // back to the root or around a cycle. It used to emit nothing in both
7449
- // cases, which the function then answered as valid: a document the other
7450
- // generators reject passed through validateJSON. It declines instead,
7451
- // and the schema takes the error generator.
7452
- if (target === ctx.rootSchema) throw DECLINE
7453
- const refKey = '$dynamicRef:' + anchorKey
7454
- if (ctx.refStack.has(refKey)) throw DECLINE
7455
- ctx.refStack.add(refKey)
7456
- genCodeC(target, v, pathExpr, lines, ctx, schemaPrefix)
7457
- ctx.refStack.delete(refKey)
7458
- }
7459
- }
7460
-
7461
- function genUniqueItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr) {
7462
- const si = ctx.varCounter++
7463
- const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
7464
- // Only reached once the verdict has rejected, or when errors are wanted, so
7465
- // one strategy for every item type: the hoisted pair finder. See UQP_HELPER.
7466
- const inner = `const _j${si}=${emitUqp(ctx)}(${v});if(_j${si}>=0){const _i${si}=_uqpI;${failExpr('_i' + si, '_j' + si)}}`
7467
- lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
7468
- }
7469
-
7470
- // The verdict form of genUnevaluatedDynamicC: the same set, built the same
7471
- // way, and the answer false at the first key outside it.
7472
- function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj, nonObjApplicators) {
7473
- if (schema.additionalProperties !== undefined) {
7474
- if (nonObjApplicators) throw DECLINE
7475
- return
7476
- }
7477
- const ui = ctx.varCounter++
7478
- const S = `_ev${ui}`
7479
- const body = [`const ${S}=[]`]
7480
- addLocalAnnotations(schema, v, S, body, ctx)
7481
- // With the anyOf and oneOf blocks moved here (see genCodeNode), their
7482
- // passing branches are counted by the annotation pass and judged from the
7483
- // count: a oneOf holds with exactly one, an anyOf with at least one.
7484
- const counts = nonObjApplicators ? { anyOf: `_ac${ui}`, oneOf: `_oc${ui}`, oneOfMask: null, of: null, verdict: true } : null
7485
- if (counts) body.push(`let ${counts.anyOf}=0,${counts.oneOf}=0`)
7486
- addTopApplicatorAnnotations(schema, v, S, body, ctx, counts)
7487
- if (counts) {
7488
- if (Array.isArray(schema.anyOf)) body.push(`if(${counts.anyOf}<1)return false`)
7489
- if (Array.isArray(schema.oneOf)) body.push(`if(${counts.oneOf}!==1)return false`)
7490
- }
7491
- const kVar = `_uk${ui}`
7492
- const up = schema.unevaluatedProperties
7493
- if (up === false) {
7494
- body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar}))return false`)
7495
- } else if (typeof up === 'object' && up !== null) {
7496
- const sub = []
7497
- genCode(up, `${v}[${kVar}]`, sub, ctx)
7498
- if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${sub.join('\n ')}\n }`)
7499
- } else {
7500
- throw DECLINE
7501
- }
7502
- // A value that is not an object skips the pass, so the blocks moved here
7503
- // run for it in the `else`; a value known to be an object has no `else`.
7504
- const other = nonObjApplicators && nonObjApplicators.length && !isObj ? `else{${nonObjApplicators.join('\n ')}}` : ''
7505
- lines.push(isObj ? `{${body.join(';')}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body.join(';')}}${other}`)
7506
- }
7507
-
7508
- // unevaluatedProperties where what is evaluated depends on which branches
7509
- // pass: the set is built at run time with the interpreted engine's rules.
7510
- // The node's own properties, patternProperties and additionalProperties
7511
- // count whether or not their values pass; an allOf, anyOf or dependentSchemas
7512
- // branch, an if, then or else, or a $ref target counts only when it passes,
7513
- // a oneOf only when exactly one branch passes, and `not` never. Each branch is
7514
- // asked through an annotation function (emitAnnot).
7515
- // Run-time unevaluatedProperties written as straight-line code. The names a
7516
- // node and its in-place applicators can evaluate are known when the code is
7517
- // written (their `properties`), so the evaluated set is a bit per name in an
7518
- // integer, each branch's verdict a labelled block, and a failed oneOf's
7519
- // rollback the integer it started from. The annotation functions this
7520
- // replaces (emitAnnot) pushed names onto an array, asked it with `includes`
7521
- // for every key, and were a call per branch per level: ten functions and
7522
- // twenty calls for the suite's nested-reference case, which in the first
7523
- // thousands of calls of a process, before anything is inlined, cost 548 ns
7524
- // against 131. Null when the shape is not modelled: names that are only known
7525
- // at run time (patternProperties, additionalProperties), a nested
7526
- // unevaluatedProperties, a reference cycle, more than 30 names, or too much
7527
- // code; the annotation functions then stand in as before.
7528
- const ANNOT_INLINE_MAX = 8192
7529
- const ANNOT_NAME_MAX = 30
7530
- const ANNOT_BLOCKERS = ['unevaluatedItems', '$dynamicRef', '$recursiveRef', 'dependencies']
7531
- // Besides a bit per declared name, the mask has a bit per pattern of a
7532
- // patternProperties (set when the subschema holding it holds: the keys it
7533
- // matches are evaluated) and one bit, `all`, for a subschema that evaluates
7534
- // every key when it holds (additionalProperties of any value, or a nested
7535
- // `unevaluatedProperties: true`). A nested `unevaluatedProperties` that
7536
- // checks something is not modelled.
7537
- const ALL_BIT_KEY = '\u0000all'
7538
- function annotSubs (node, ctx) {
7539
- const subs = []
7540
- for (const b of node.allOf || []) subs.push(b)
7541
- for (const b of node.anyOf || []) subs.push(b)
7542
- for (const b of node.oneOf || []) subs.push(b)
7543
- if (node.if !== undefined) subs.push(node.if)
7544
- if (node.then !== undefined) subs.push(node.then)
7545
- if (node.else !== undefined) subs.push(node.else)
7546
- if (node.dependentSchemas && typeof node.dependentSchemas === 'object') for (const ds of Object.values(node.dependentSchemas)) subs.push(ds)
7547
- if (typeof node.$ref === 'string') subs.push(refTargetFor(node.$ref, ctx))
7548
- return subs
7549
- }
7550
- function annotNames (node, ctx, names, seen, top) {
7551
- if (node === true || node === false) return true
7552
- if (node === null || typeof node !== 'object' || Array.isArray(node)) return false
7553
- if (seen.has(node)) return false
7554
- seen.add(node)
7555
- try {
7556
- for (const k of ANNOT_BLOCKERS) if (node[k] !== undefined) return false
7557
- if (!top && node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== true) return false
7558
- const add = (key) => { if (!names.has(key)) { if (names.size >= ANNOT_NAME_MAX) return false; names.set(key, names.size) } return true }
7559
- if (node.properties && typeof node.properties === 'object') for (const k of Object.keys(node.properties)) if (!add(k)) return false
7560
- if (node.patternProperties && typeof node.patternProperties === 'object') for (const pat of Object.keys(node.patternProperties)) if (!add('/' + pat)) return false
7561
- if ((!top && node.additionalProperties !== undefined) || (!top && node.unevaluatedProperties === true)) if (!add(ALL_BIT_KEY)) return false
7562
- let subs
7563
- try { subs = annotSubs(node, ctx) } catch { return false }
7564
- for (const b of subs) if (!annotNames(b, ctx, names, seen, false)) return false
7565
- return true
7566
- } finally { seen.delete(node) }
7567
- }
7568
- // The bits a subschema's own keywords evaluate when it holds: its declared
7569
- // names that are present, every pattern it declares, and `all` for a
7570
- // subschema that evaluates every key.
7571
- function ownBits (sub, v, names, ctx, t, top) {
7572
- const out = []
7573
- for (const k of Object.keys(sub.properties || {})) out.push(`if(${ownKeyExpr(ctx, v, k)})${t}|=${1 << names.get(k)}`)
7574
- let fixed = 0
7575
- for (const pat of Object.keys(sub.patternProperties || {})) fixed |= 1 << names.get('/' + pat)
7576
- if (!top && (sub.additionalProperties !== undefined || sub.unevaluatedProperties === true)) fixed |= 1 << names.get(ALL_BIT_KEY)
7577
- if (fixed !== 0) out.push(`${t}|=${fixed}`)
7578
- return out
7579
- }
7580
- // The test for one pattern bit against a key variable, a closure regex or a
7581
- // fixed prefix test (fastPrefixCheck) as addLocalAnnotations writes them.
7582
- function patternKeyTest (ctx, pat, kv) {
7583
- const fast = fastPrefixCheck(pat, kv)
7584
- if (fast) return fast
7585
- const ri = ctx.varCounter++
7586
- ctx.closureVars.push(`_re${ri}`)
7587
- ctx.closureVals.push(safeReClosure(ctx, pat))
7588
- return `_re${ri}.test(${kv})`
7589
- }
7590
- // Factory-scope count variables for one anyOf or oneOf array (by identity),
7591
- // registered in ctx.branchCounts for emitBranchCollapse: `of` the object
7592
- // they were counted for, `count` how many branches held, `mask` which (oneOf
7593
- // up to 30 branches). Declared once per program; a stale value is never
7594
- // taken, since every reader checks `of` against the value at hand.
7595
- function factoryCount (ctx, arr, withMask) {
7596
- if (!ctx.inCombined) return null
7597
- if (!ctx.branchCounts) ctx.branchCounts = new Map()
7598
- const hit = ctx.branchCounts.get(arr)
7599
- if (hit !== undefined) return hit.factory ? hit : null
7600
- const id = ctx.varCounter++
7601
- const fc = { of: `_fa${id}`, count: `_fn${id}`, mask: withMask ? `_fm${id}` : null, factory: true }
7602
- ctx.helperCode.push(`let ${fc.of}=null,${fc.count}=0${withMask ? `,${fc.mask}=0` : ''}`)
7603
- ctx.branchCounts.set(arr, fc)
7604
- return fc
7605
- }
7606
- // Lines that set `ok` (declared by the caller, 1 or 0) for `sub` at `v`, an
7607
- // object, and OR the names it evaluated into `tv` when it holds.
7608
- function emitAnnotInline (sub, v, names, ctx, out, ok, tv) {
7609
- if (sub === true) { out.push(`${ok}=1`); return }
7610
- if (sub === false) { out.push(`${ok}=0`); return }
7611
- const id = ctx.varCounter++
7612
- const t = `_t${id}`
7613
- const local = {}
7614
- for (const k of Object.keys(sub)) if (!ANNOT_APPLICATORS.has(k)) setOwn(local, k, sub[k])
7615
- const vl = []
7616
- // The value is known to be an object here (the block runs for objects
7617
- // only), which the verdict is told when the branch does not say otherwise:
7618
- // told a type, the generator takes it for the value's and skips the
7619
- // branch's own `type`, so a `type: "string"` branch has to keep its check.
7620
- ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
7621
- try { genCode(local, v, vl, ctx, local.type === undefined || local.type === 'object' ? 'object' : undefined) } finally { ctx.nestedBoolean-- }
7622
- out.push(`let ${t}=0;${ok}=1`)
7623
- if (vl.length) out.push(`a${id}:{${replaceTopLevel(vl.join(';'), `break a${id}`, `{${ok}=0;break a${id}}`)}}`)
7624
- if (sub.not !== undefined) {
7625
- const nl = []
7626
- nestedGenCode(sub.not, v, nl, ctx)
7627
- if (nl.length) out.push(`if(${ok}){let _nk${id}=1;n${id}:{${replaceTopLevel(nl.join(';'), `break n${id}`, `{_nk${id}=0;break n${id}}`)}};if(_nk${id})${ok}=0}`)
7628
- else out.push(`${ok}=0`)
7629
- }
7630
- const own = ownBits(sub, v, names, ctx, t, false)
7631
- if (own.length) out.push(`if(${ok}){${own.join(';')}}`)
7632
- let bi = 0
7633
- const child = (b) => { const o = `_ok${id}_${bi}`, tb = `_tb${id}_${bi}`; bi++; out.push(`let ${o}=1,${tb}=0`); emitAnnotInline(b, v, names, ctx, out, o, tb); return [o, tb] }
7634
- for (const b of sub.allOf || []) { const [o, tb] = child(b); out.push(`if(!${o})${ok}=0;else ${t}|=${tb}`) }
7635
- // The counts of a nested anyOf or oneOf go to factory-scope variables as
7636
- // well (factoryCount), where the keyword's own error code in a branch
7637
- // function, and the counting copies, read them instead of deciding the
7638
- // branches again: the suite's nested-reference case decided each branch
7639
- // four times over (this pass, the verdict closures, the counting copies,
7640
- // the chosen branch's error function).
7641
- if (Array.isArray(sub.anyOf)) {
7642
- const na = `_na${id}`
7643
- const fc = factoryCount(ctx, sub.anyOf, false)
7644
- out.push(`let ${na}=0${fc ? `;${fc.of}=${v};${fc.count}=0` : ''}`)
7645
- for (const b of sub.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${t}|=${tb}${fc ? `;${fc.count}++` : ''}}`) }
7646
- out.push(`if(${na}===0)${ok}=0`)
7647
- }
7648
- if (Array.isArray(sub.oneOf)) {
7649
- const nc = `_nc${id}`, tc = `_tc${id}`
7650
- const fc = factoryCount(ctx, sub.oneOf, sub.oneOf.length <= 30)
7651
- out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
7652
- sub.oneOf.forEach((b, i) => { const [o, tb] = child(b); out.push(`if(${o}){${nc}++;${tc}=${tb}${fc ? `;${fc.count}++${fc.mask ? `;${fc.mask}|=${1 << i}` : ''}` : ''}}`) })
7653
- out.push(`if(${nc}===1)${t}|=${tc};else ${ok}=0`)
7654
- }
7655
- if (sub.if !== undefined) {
7656
- const [oi, ti] = child(sub.if)
7657
- const thenPart = []
7658
- if (sub.then !== undefined) { const saved = out.length; const [ot, tt] = child(sub.then); thenPart.push(...out.splice(saved)); thenPart.push(`if(!${ot})${ok}=0;else ${t}|=${tt}`) }
7659
- const elsePart = []
7660
- if (sub.else !== undefined) { const saved = out.length; const [oe, te] = child(sub.else); elsePart.push(...out.splice(saved)); elsePart.push(`if(!${oe})${ok}=0;else ${t}|=${te}`) }
7661
- out.push(`if(${oi}){${t}|=${ti};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
7662
- }
7663
- if (sub.dependentSchemas && typeof sub.dependentSchemas === 'object') {
7664
- for (const [k, ds] of Object.entries(sub.dependentSchemas)) { const saved = out.length; const [o, tb] = child(ds); const body = out.splice(saved); out.push(`if(${ownKeyExpr(ctx, v, k)}){${body.join(';')};if(!${o})${ok}=0;else ${t}|=${tb}}`) }
7665
- }
7666
- if (typeof sub.$ref === 'string') { const [o, tb] = child(refTargetFor(sub.$ref, ctx)); out.push(`if(!${o})${ok}=0;else ${t}|=${tb}`) }
7667
- out.push(`if(${ok})${tv}|=${t}`)
7668
- }
7669
- // The whole run-time unevaluatedProperties block for `schema` at `v`, or
7670
- // null. `counts` as in genUnevaluatedDynamicC: how many anyOf and oneOf
7671
- // branches held, for the keywords' own code to read.
7672
- function inlineUnevalBlock (schema, v, pathExpr, ctx, schemaPrefix, fail, counts) {
7673
- const names = new Map()
7674
- if (!annotNames(schema, ctx, names, new Set(), true)) return null
7675
- const saved = snapshotCtx(ctx)
7676
- try {
7677
- const id = ctx.varCounter++
7678
- const m = `_m${id}`
7679
- const out = [`let ${m}=0`]
7680
- // One prototype flag for the object, read by every presence test below.
7681
- if (!ctx.plainOf) ctx.plainOf = new Map()
7682
- const prevPk = ctx.plainOf.get(v)
7683
- const pk = prevPk !== undefined ? prevPk : `_pk${id}`
7684
- const prevReuse = ctx.plainReuse
7685
- if (prevPk === undefined) { out.push(`const ${pk}=${protoIs(ctx, v)}`); ctx.plainOf.set(v, pk); (ctx.plainUsed || (ctx.plainUsed = new Set())).add(pk) }
7686
- ctx.plainReuse = v
7687
- out.push(...ownBits(schema, v, names, ctx, m, true))
7688
- let bi = 0
7689
- const child = (b) => { const o = `_ok${id}_${bi}`, tb = `_tb${id}_${bi}`; bi++; out.push(`let ${o}=1,${tb}=0`); emitAnnotInline(b, v, names, ctx, out, o, tb); return [o, tb] }
7690
- for (const b of schema.allOf || []) { const [, tb] = child(b); out.push(`${m}|=${tb}`) }
7691
- if (Array.isArray(schema.anyOf)) {
7692
- const na = counts ? counts.anyOf : `_na${id}`
7693
- out.push(counts ? `${na}=0` : `let ${na}=0`)
7694
- for (const b of schema.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${m}|=${tb}}`) }
7695
- }
7696
- if (Array.isArray(schema.oneOf)) {
7697
- const nc = counts ? counts.oneOf : `_nc${id}`, tc = `_tc${id}`
7698
- out.push(counts ? `${nc}=0;let ${tc}=0` : `let ${nc}=0,${tc}=0`)
7699
- schema.oneOf.forEach((b, i) => { const [o, tb] = child(b); out.push(`if(${o}){${nc}++;${tc}=${tb};${counts && counts.oneOfMask && schema.oneOf.length <= 30 ? `${counts.oneOfMask}|=${1 << i};` : ''}}`) })
7700
- out.push(`if(${nc}===1)${m}|=${tc}`)
7701
- }
7702
- if (schema.if !== undefined) {
7703
- const [oi, ti] = child(schema.if)
7704
- const thenPart = []
7705
- if (schema.then !== undefined) { const sv = out.length; const [ot, tt] = child(schema.then); thenPart.push(...out.splice(sv)); thenPart.push(`if(${ot})${m}|=${tt}`) }
7706
- const elsePart = []
7707
- if (schema.else !== undefined) { const sv = out.length; const [oe, te] = child(schema.else); elsePart.push(...out.splice(sv)); elsePart.push(`if(${oe})${m}|=${te}`) }
7708
- out.push(`if(${oi}){${m}|=${ti};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
7709
- }
7710
- if (schema.dependentSchemas && typeof schema.dependentSchemas === 'object') {
7711
- for (const [k, ds] of Object.entries(schema.dependentSchemas)) { const sv = out.length; const [o, tb] = child(ds); const body = out.splice(sv); out.push(`if(${ownKeyExpr(ctx, v, k)}){${body.join(';')};if(${o})${m}|=${tb}}`) }
7712
- }
7713
- if (typeof schema.$ref === 'string') { const [o, tb] = child(refTargetFor(schema.$ref, ctx)); out.push(`if(${o})${m}|=${tb}`) }
7714
- // What is left: every own key whose bit is not set.
7715
- const kVar = `_uk${id}`
7716
- const cases = [...names].filter(([k]) => k !== ALL_BIT_KEY && k[0] !== '/').map(([k, i]) => `case ${JSON.stringify(k)}:_ub${id}=${1 << i};break`).join(';')
7717
- const allBit = names.has(ALL_BIT_KEY) ? `if((${m}&${1 << names.get(ALL_BIT_KEY)})!==0)continue;` : ''
7718
- const patBits = [...names].filter(([k]) => k[0] === '/').map(([k, i]) => `if((${m}&${1 << i})!==0&&${patternKeyTest(ctx, k.slice(1), `_uk${id}`)})continue;`).join('')
7719
- const up = schema.unevaluatedProperties
7720
- let body
7721
- if (up === false) {
7722
- body = fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:${kVar}}`, "'must NOT have unevaluated properties'")
7723
- } else if (typeof up === 'object' && up !== null) {
7724
- const pe = emitPtrEsc(ctx)
7725
- const p = pathExpr ? `${pathExpr}+'/'+${pe}(${kVar})` : `'/'+${pe}(${kVar})`
7726
- const sub = []
7727
- genCodeC(up, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/unevaluatedProperties')
7728
- body = sub.join('\n ')
7729
- } else {
7730
- throw DECLINE
7731
- }
7732
- if (body) out.push(`for(const ${kVar} in ${v}){if(!${ownKeyExpr(ctx, v, kVar).split(JSON.stringify(kVar)).join(kVar)})continue;let _ub${id}=0;switch(${kVar}){${cases}}if(_ub${id}!==0&&(${m}&_ub${id})!==0)continue;${allBit}${patBits}${body}}`)
7733
- const text = out.join(';')
7734
- ctx.plainReuse = prevReuse
7735
- if (prevPk === undefined) ctx.plainOf.delete(v)
7736
- if (text.length > ANNOT_INLINE_MAX || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(text)) { restoreCtx(ctx, saved); return null }
7737
- return text
7738
- } catch (e) {
7739
- if (e !== DECLINE) throw e
7740
- restoreCtx(ctx, saved)
7741
- return null
7742
- }
7743
- }
7744
-
7745
- function genUnevaluatedDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
7746
- // additionalProperties evaluates every key the others do not, pass or fail.
7747
- if (schema.additionalProperties !== undefined) return
7748
- const ui = ctx.varCounter++
7749
- const S = `_ev${ui}`
7750
- const body = [`const ${S}=[]`]
7751
- addLocalAnnotations(schema, v, S, body, ctx)
7752
- // The anyOf and oneOf counts are handed to the keywords' own error code
7753
- // (emitBranchCollapse), with the object they were counted for: they are
7754
- // declared once at the top of the combined function, where every later
7755
- // line sees them, and read only when that object is the value at hand, so
7756
- // a count left from another value, another loop iteration or another place
7757
- // is never taken for this one. Only in the combined function proper.
7758
- const share = ctx.inCombined && !ctx.inBranchFn && ctx.hoisted !== undefined &&
7759
- (Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf))
7760
- const counts = share ? { anyOf: `_ac${ui}`, oneOf: `_oc${ui}`, oneOfMask: `_om${ui}`, of: `_ao${ui}` } : null
7761
- if (share) {
7762
- ctx.hoisted.push(counts.anyOf, counts.oneOf, counts.oneOfMask, counts.of)
7763
- body.push(`${counts.of}=${v};${counts.oneOfMask}=0`)
7764
- if (!ctx.branchCounts) ctx.branchCounts = new Map()
7765
- if (Array.isArray(schema.anyOf)) ctx.branchCounts.set(schema.anyOf, { of: counts.of, count: counts.anyOf, mask: null })
7766
- if (Array.isArray(schema.oneOf)) ctx.branchCounts.set(schema.oneOf, { of: counts.of, count: counts.oneOf, mask: schema.oneOf.length <= 30 ? counts.oneOfMask : null })
7767
- }
7768
- const inline = inlineUnevalBlock(schema, v, pathExpr, ctx, schemaPrefix, fail, counts)
7769
- if (inline !== null) {
7770
- const pre = share ? `${counts.of}=${v};${counts.oneOfMask}=0;` : ''
7771
- lines.push(isObj ? `{${pre}${inline}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${pre}${inline}}`)
7772
- return
7773
- }
7774
- addTopApplicatorAnnotations(schema, v, S, body, ctx, counts)
7775
- const kVar = `_uk${ui}`
7776
- const up = schema.unevaluatedProperties
7777
- if (up === false) {
7778
- body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:${kVar}}`, "'must NOT have unevaluated properties'")}}`)
7779
- } else if (typeof up === 'object' && up !== null) {
7780
- const pe = emitPtrEsc(ctx)
7781
- const p = pathExpr ? `${pathExpr}+'/'+${pe}(${kVar})` : `'/'+${pe}(${kVar})`
7782
- const sub = []
7783
- genCodeC(up, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/unevaluatedProperties')
7784
- if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${sub.join('\n ')}\n }`)
7785
- } else {
7786
- throw DECLINE
7787
- }
7788
- lines.push(isObj ? `{${body.join(';')}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body.join(';')}}`)
7789
- }
7790
-
7791
- // The names a node's own keywords evaluate, pushed onto the array `S`:
7792
- // present declared properties, keys matching a pattern, and every key when
7793
- // additionalProperties is there.
7794
- function addLocalAnnotations(node, v, S, out, ctx) {
7795
- if (node.additionalProperties !== undefined) {
7796
- out.push(`for(const _ka in ${v})${S}.push(_ka)`)
7797
- return
7798
- }
7799
- for (const k of Object.keys(node.properties || {})) out.push(`if(Object.prototype.hasOwnProperty.call(${v},${JSON.stringify(k)}))${S}.push(${JSON.stringify(k)})`)
7800
- const pats = Object.keys(node.patternProperties || {})
7801
- if (pats.length) {
7802
- const tests = []
7803
- const kv = `_pk${ctx.varCounter++}`
7804
- for (const pat of pats) {
7805
- const fast = fastPrefixCheck(pat, kv)
7806
- if (fast) { tests.push(fast); continue }
7807
- const ri = ctx.varCounter++
7808
- ctx.closureVars.push(`_re${ri}`)
7809
- ctx.closureVals.push(safeReClosure(ctx, pat))
7810
- tests.push(`_re${ri}.test(${kv})`)
7811
- }
7812
- out.push(`for(const ${kv} in ${v})if(${tests.join('||')})${S}.push(${kv})`)
7813
- }
7814
- }
7815
-
7816
- // What the applicators of the node that owns the unevaluatedProperties add.
7817
- // Their validity is checked by the code around this; here a branch only
7818
- // contributes, through its annotation function, when it passes, and a oneOf
7819
- // only with exactly one passing branch. An annotation function that fails
7820
- // leaves `S` as it found it.
7821
- function addTopApplicatorAnnotations(node, v, S, out, ctx, counts) {
7822
- if (node.$dynamicRef !== undefined || node.$recursiveRef !== undefined || node.dependencies !== undefined) throw DECLINE
7823
- for (const b of node.allOf || []) out.push(`${emitAnnot(b, ctx)}(${v},${S})`)
7824
- // With `counts`, the passing branches of anyOf and oneOf are counted into
7825
- // variables the caller declared, so the keyword's own error code further
7826
- // down can skip its branches when the count already says it holds (see
7827
- // emitBranchCollapse).
7828
- if (Array.isArray(node.anyOf)) {
7829
- if (counts) out.push(`${counts.anyOf}=0;${node.anyOf.map((b) => `if(${emitAnnot(b, ctx)}(${v},${S}))${counts.anyOf}++;`).join('')}`)
7830
- else for (const b of node.anyOf) out.push(`${emitAnnot(b, ctx)}(${v},${S})`)
7831
- }
7832
- if (Array.isArray(node.oneOf)) {
7833
- const t = counts ? counts.oneOf : `_oc${ctx.varCounter++}`
7834
- // Which branches passed, as bits, for a collapse that has to name them.
7835
- const mask = counts && node.oneOf.length <= 30 ? counts.oneOfMask : null
7836
- // In a verdict (counts.verdict), a second passing branch is the answer.
7837
- const over = counts && counts.verdict ? `if(${t}>1)return false;` : ''
7838
- out.push(`{const ${t}m=${S}.length;${counts ? '' : 'let '}${t}=0;${node.oneOf.map((b, i) => `if(${emitAnnot(b, ctx)}(${v},${S})){${t}++;${over}${mask ? `${mask}|=${1 << i};` : ''}}`).join('')}if(${t}!==1)_rb(${S},${t}m)}`)
7839
- }
7840
- if (node.if !== undefined) {
7841
- const thenCall = node.then !== undefined ? `${emitAnnot(node.then, ctx)}(${v},${S})` : ''
7842
- const elseCall = node.else !== undefined ? `${emitAnnot(node.else, ctx)}(${v},${S})` : ''
7843
- out.push(`if(${emitAnnot(node.if, ctx)}(${v},${S})){${thenCall}}else{${elseCall}}`)
7844
- }
7845
- if (node.dependentSchemas && typeof node.dependentSchemas === 'object') {
7846
- for (const [k, ds] of Object.entries(node.dependentSchemas)) out.push(`if(Object.prototype.hasOwnProperty.call(${v},${JSON.stringify(k)}))${emitAnnot(ds, ctx)}(${v},${S})`)
7847
- }
7848
- if (typeof node.$ref === 'string') out.push(`${emitAnnot(refTargetFor(node.$ref, ctx), ctx)}(${v},${S})`)
7849
- }
7850
-
7851
- function refTargetFor(ref, ctx) {
7852
- const m = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
7853
- const t = m && ctx.rootDefs ? ctx.rootDefs[m[1]] : undefined
7854
- if (t === undefined) throw DECLINE
7855
- return t
7856
- }
7857
-
7858
- // The keywords an annotation function evaluates itself, through its children;
7859
- // the rest of a subschema is checked by a plain verdict.
7860
- const ANNOT_APPLICATORS = new Set(['allOf', 'anyOf', 'oneOf', 'if', 'then', 'else', 'dependentSchemas', '$ref', 'not', 'unevaluatedProperties'])
7861
-
7862
- // A function `(value, S) => valid` for one subschema: whether it passes, and
7863
- // when it does, what it evaluated pushed onto S; when it does not, S is left
7864
- // as it was. Validity is worked out bottom up: the subschema's own keywords
7865
- // by a verdict of the subschema without its applicators, each applicator from
7866
- // its branches' annotation functions. Asking each branch for a full verdict
7867
- // and then again for its annotations repeated every nested branch once per
7868
- // level, which made nested references the slowest shape there was.
7869
- function emitAnnot(sub, ctx) {
7870
- if (sub === true) return '_anT'
7871
- if (sub === false) return '_anF'
7872
- if (sub === null || typeof sub !== 'object') throw DECLINE
7873
- if (!ctx.annFns) {
7874
- ctx.annFns = new Map()
7875
- if (ctx.sharedRuntime) bindRt(ctx, ['_anT', '_anF', '_rb'])
7876
- else (ctx.preamble || ctx.helperCode).push('function _anT(){return true}function _anF(){return false}function _rb(S,m){if(S.length!==m)S.length=m}')
7877
- }
7878
- const hit = ctx.annFns.get(sub)
7879
- if (hit !== undefined) {
7880
- if (hit === null) throw DECLINE // a reference cycle: not modelled here
7881
- return hit
5093
+ const hit = ctx.annFns.get(sub)
5094
+ if (hit !== undefined) {
5095
+ if (hit === null) throw DECLINE // a reference cycle: not modelled here
5096
+ return hit
7882
5097
  }
7883
5098
  if (sub.$dynamicRef !== undefined || sub.$recursiveRef !== undefined || sub.dependencies !== undefined) throw DECLINE
7884
5099
  // unevaluatedItems reads what the applicators evaluate too; the local
@@ -7939,576 +5154,6 @@ function emitAnnot(sub, ctx) {
7939
5154
  return name
7940
5155
  }
7941
5156
 
7942
- // unevaluatedItems worked out at run time, for the combined function. The
7943
- // evaluated positions of an array are kept as the interpreter keeps them
7944
- // (lib/plan-compiler.js): `n`, every index below it evaluated, and `x`, the
7945
- // indexes past it that `contains` matched. This node's own prefixItems,
7946
- // items and contains mark them whatever their own verdict, and each in-place
7947
- // subschema marks them only when it passes (emitAnnotI); what is left must
7948
- // pass unevaluatedItems. `false` reports once, at the first position left,
7949
- // as the interpreter does.
7950
- // Run-time unevaluatedItems written as straight-line code, the way
7951
- // inlineUnevalBlock writes unevaluatedProperties. Without `contains` the
7952
- // positions a node and its in-place applicators evaluate are a prefix: items
7953
- // evaluates every position, prefixItems the first p, a nested
7954
- // `unevaluatedItems: true` every position once its subschema holds; the
7955
- // record is one integer, merged with max where a branch holds, and what is
7956
- // left starts at that index. `contains` (positions that are not a prefix), a
7957
- // tuple `items`, a nested unevaluatedItems that checks something, dynamic
7958
- // references and reference cycles keep the annotation functions.
7959
- const ANNOT_I_BLOCKERS = ['contains', 'additionalItems', '$dynamicRef', '$recursiveRef', 'dependencies']
7960
- function annotItemsFit (node, ctx, seen, top) {
7961
- if (node === true || node === false) return true
7962
- if (node === null || typeof node !== 'object' || Array.isArray(node)) return false
7963
- if (seen.has(node)) return false
7964
- seen.add(node)
7965
- try {
7966
- for (const k of ANNOT_I_BLOCKERS) if (node[k] !== undefined) return false
7967
- if (Array.isArray(node.items)) return false
7968
- if (!top && node.unevaluatedItems !== undefined && node.unevaluatedItems !== true) return false
7969
- if (node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== true) return false
7970
- let subs
7971
- try { subs = annotSubs(node, ctx) } catch { return false }
7972
- for (const b of subs) if (!annotItemsFit(b, ctx, seen, false)) return false
7973
- return true
7974
- } finally { seen.delete(node) }
7975
- }
7976
- // The positions a node's own keywords evaluate, as an expression of the
7977
- // array length `len`: null when none.
7978
- function ownPrefix (node, len, top) {
7979
- if (node.items !== undefined) return len
7980
- if (!top && node.unevaluatedItems === true) return len
7981
- if (Array.isArray(node.prefixItems) && node.prefixItems.length) return `(${len}<${node.prefixItems.length}?${len}:${node.prefixItems.length})`
7982
- return null
7983
- }
7984
- // Lines that set `ok` for `sub` at `v`, an array of length `len`, and raise
7985
- // `tn` to the positions it evaluated when it holds.
7986
- function emitAnnotInlineI (sub, v, len, ctx, out, ok, tn) {
7987
- if (sub === true) { out.push(`${ok}=1`); return }
7988
- if (sub === false) { out.push(`${ok}=0`); return }
7989
- const id = ctx.varCounter++
7990
- const t = `_t${id}`
7991
- const local = {}
7992
- for (const k of Object.keys(sub)) if (!ANNOT_ITEM_APPLICATORS.has(k)) setOwn(local, k, sub[k])
7993
- const vl = []
7994
- ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
7995
- try { genCode(local, v, vl, ctx, local.type === undefined || local.type === 'array' ? 'array' : undefined) } finally { ctx.nestedBoolean-- }
7996
- out.push(`let ${t}=0;${ok}=1`)
7997
- if (vl.length) out.push(`a${id}:{${replaceTopLevel(vl.join(';'), `break a${id}`, `{${ok}=0;break a${id}}`)}}`)
7998
- if (sub.not !== undefined) {
7999
- const nl = []
8000
- nestedGenCode(sub.not, v, nl, ctx)
8001
- if (nl.length) out.push(`if(${ok}){let _nk${id}=1;n${id}:{${replaceTopLevel(nl.join(';'), `break n${id}`, `{_nk${id}=0;break n${id}}`)}};if(_nk${id})${ok}=0}`)
8002
- else out.push(`${ok}=0`)
8003
- }
8004
- const own = ownPrefix(sub, len, false)
8005
- if (own !== null) out.push(`if(${ok})${t}=${own}`)
8006
- let bi = 0
8007
- const child = (b) => { const o = `_ok${id}_${bi}`, tb = `_tb${id}_${bi}`; bi++; out.push(`let ${o}=1,${tb}=0`); emitAnnotInlineI(b, v, len, ctx, out, o, tb); return [o, tb] }
8008
- const raise = (tb) => `if(${tb}>${t})${t}=${tb}`
8009
- for (const b of sub.allOf || []) { const [o, tb] = child(b); out.push(`if(!${o})${ok}=0;else ${raise(tb)}`) }
8010
- if (Array.isArray(sub.anyOf)) {
8011
- const na = `_na${id}`
8012
- const fc = factoryCount(ctx, sub.anyOf, false)
8013
- out.push(`let ${na}=0${fc ? `;${fc.of}=${v};${fc.count}=0` : ''}`)
8014
- for (const b of sub.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${na}++;${raise(tb)}${fc ? `;${fc.count}++` : ''}}`) }
8015
- out.push(`if(${na}===0)${ok}=0`)
8016
- }
8017
- if (Array.isArray(sub.oneOf)) {
8018
- const nc = `_nc${id}`, tc = `_tc${id}`
8019
- const fc = factoryCount(ctx, sub.oneOf, sub.oneOf.length <= 30)
8020
- out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
8021
- sub.oneOf.forEach((b, i) => { const [o, tb] = child(b); out.push(`if(${o}){${nc}++;${tc}=${tb}${fc ? `;${fc.count}++${fc.mask ? `;${fc.mask}|=${1 << i}` : ''}` : ''}}`) })
8022
- out.push(`if(${nc}===1){${raise(tc)}}else ${ok}=0`)
8023
- }
8024
- if (sub.if !== undefined) {
8025
- const [oi, ti] = child(sub.if)
8026
- const thenPart = []
8027
- if (sub.then !== undefined) { const sv = out.length; const [ot, tt] = child(sub.then); thenPart.push(...out.splice(sv)); thenPart.push(`if(!${ot})${ok}=0;else ${raise(tt)}`) }
8028
- const elsePart = []
8029
- if (sub.else !== undefined) { const sv = out.length; const [oe, te] = child(sub.else); elsePart.push(...out.splice(sv)); elsePart.push(`if(!${oe})${ok}=0;else ${raise(te)}`) }
8030
- out.push(`if(${oi}){${raise(ti)};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
8031
- }
8032
- if (typeof sub.$ref === 'string') { const [o, tb] = child(refTargetFor(sub.$ref, ctx)); out.push(`if(!${o})${ok}=0;else ${raise(tb)}`) }
8033
- out.push(`if(${ok}&&${t}>${tn})${tn}=${t}`)
8034
- }
8035
- function inlineUnevalItemsBlock (schema, v, pathExpr, ctx, schemaPrefix, fail) {
8036
- if (!ctx.inCombined || !annotItemsFit(schema, ctx, new Set(), true)) return null
8037
- const saved = snapshotCtx(ctx)
8038
- try {
8039
- const id = ctx.varCounter++
8040
- const n = `_un${id}`, len = `_ul${id}`
8041
- const out = [`const ${len}=${v}.length;let ${n}=0`]
8042
- const own = ownPrefix(schema, len, true)
8043
- if (own !== null) out.push(`${n}=${own}`)
8044
- let bi = 0
8045
- const child = (b) => { const o = `_ok${id}_${bi}`, tb = `_tb${id}_${bi}`; bi++; out.push(`let ${o}=1,${tb}=0`); emitAnnotInlineI(b, v, len, ctx, out, o, tb); return [o, tb] }
8046
- const raise = (tb) => `if(${tb}>${n})${n}=${tb}`
8047
- for (const b of schema.allOf || []) { const [, tb] = child(b); out.push(raise(tb)) }
8048
- if (Array.isArray(schema.anyOf)) {
8049
- const fc = factoryCount(ctx, schema.anyOf, false)
8050
- if (fc) out.push(`${fc.of}=${v};${fc.count}=0`)
8051
- for (const b of schema.anyOf) { const [o, tb] = child(b); out.push(`if(${o}){${raise(tb)}${fc ? `;${fc.count}++` : ''}}`) }
8052
- }
8053
- if (Array.isArray(schema.oneOf)) {
8054
- const fc = factoryCount(ctx, schema.oneOf, schema.oneOf.length <= 30)
8055
- const nc = `_nc${id}`, tc = `_tc${id}`
8056
- out.push(`let ${nc}=0,${tc}=0${fc ? `;${fc.of}=${v};${fc.count}=0${fc.mask ? `;${fc.mask}=0` : ''}` : ''}`)
8057
- schema.oneOf.forEach((b, i) => { const [o, tb] = child(b); out.push(`if(${o}){${nc}++;${tc}=${tb}${fc ? `;${fc.count}++${fc.mask ? `;${fc.mask}|=${1 << i}` : ''}` : ''}}`) })
8058
- out.push(`if(${nc}===1)${raise(tc)}`)
8059
- }
8060
- if (schema.if !== undefined) {
8061
- const [oi, ti] = child(schema.if)
8062
- const thenPart = []
8063
- if (schema.then !== undefined) { const sv = out.length; const [ot, tt] = child(schema.then); thenPart.push(...out.splice(sv)); thenPart.push(`if(${ot})${raise(tt)}`) }
8064
- const elsePart = []
8065
- if (schema.else !== undefined) { const sv = out.length; const [oe, te] = child(schema.else); elsePart.push(...out.splice(sv)); elsePart.push(`if(${oe})${raise(te)}`) }
8066
- out.push(`if(${oi}){${raise(ti)};${thenPart.join(';')}}else{${elsePart.join(';')}}`)
8067
- }
8068
- if (typeof schema.$ref === 'string') { const [o, tb] = child(refTargetFor(schema.$ref, ctx)); out.push(`if(${o})${raise(tb)}`) }
8069
- const iv = `_ix${id}`
8070
- const ui2 = schema.unevaluatedItems
8071
- if (ui2 === false) {
8072
- // Reported once, at the first position left, as the interpreter does.
8073
- out.push(`if(${n}<${len}){const ${iv}=${n};${fail('unevaluatedItems', 'unevaluatedItems', `{limit:${iv}}`, `'must NOT have more than '+${iv}+' items'`)}}`)
8074
- } else if (typeof ui2 === 'object' && ui2 !== null) {
8075
- const pe = pathExpr ? `${pathExpr}+'/'+${iv}` : `'/'+${iv}`
8076
- const sub = []
8077
- genCodeC(ui2, `${v}[${iv}]`, pe, sub, ctx, schemaPrefix + '/unevaluatedItems')
8078
- if (sub.length) out.push(`for(let ${iv}=${n};${iv}<${len};${iv}++){${sub.join('\n ')}\n }`)
8079
- } else {
8080
- throw DECLINE
8081
- }
8082
- const text = out.join(';')
8083
- if (text.length > ANNOT_INLINE_MAX || /\b(?:_validate|_stk|_sd|_sg|_CYC)\b/.test(text)) { restoreCtx(ctx, saved); return null }
8084
- return text
8085
- } catch (e) {
8086
- if (e !== DECLINE) throw e
8087
- restoreCtx(ctx, saved)
8088
- return null
8089
- }
8090
- }
8091
-
8092
- function genUnevaluatedItemsDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
8093
- if (schema.$dynamicRef !== undefined || schema.$recursiveRef !== undefined) throw DECLINE
8094
- if (Array.isArray(schema.items) || schema.additionalItems !== undefined) throw DECLINE
8095
- const inline = inlineUnevalItemsBlock(schema, v, pathExpr, ctx, schemaPrefix, fail)
8096
- if (inline !== null) { lines.push(`if(Array.isArray(${v})){${inline}}`); return }
8097
- annItemHelpers(ctx)
8098
- const ui = ctx.varCounter++
8099
- const S = `_es${ui}`
8100
- const body = [`const ${S}={n:0,x:null}`]
8101
- addLocalItemAnnotations(schema, v, S, body, ctx)
8102
- for (const b of schema.allOf || []) body.push(`${emitAnnotI(b, ctx)}(${v},${S})`)
8103
- if (Array.isArray(schema.anyOf)) for (const b of schema.anyOf) body.push(`${emitAnnotI(b, ctx)}(${v},${S})`)
8104
- if (Array.isArray(schema.oneOf)) {
8105
- 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)}`)
8106
- }
8107
- if (schema.if !== undefined) {
8108
- const thenCall = schema.then !== undefined ? `${emitAnnotI(schema.then, ctx)}(${v},${S})` : ''
8109
- const elseCall = schema.else !== undefined ? `${emitAnnotI(schema.else, ctx)}(${v},${S})` : ''
8110
- body.push(`if(${emitAnnotI(schema.if, ctx)}(${v},${S})){${thenCall}}else{${elseCall}}`)
8111
- }
8112
- if (typeof schema.$ref === 'string') body.push(`${emitAnnotI(refTargetFor(schema.$ref, ctx), ctx)}(${v},${S})`)
8113
- const iv = `_ix${ui}`
8114
- const ui2 = schema.unevaluatedItems
8115
- if (ui2 === false) {
8116
- const failI = fail('unevaluatedItems', 'unevaluatedItems', `{limit:${iv}}`, `'must NOT have more than '+${iv}+' items'`)
8117
- body.push(`for(let ${iv}=0;${iv}<${v}.length;${iv}++){if(_hI(${S},${iv}))continue;${failI};break}`)
8118
- } else if (typeof ui2 === 'object' && ui2 !== null) {
8119
- const pe = pathExpr ? `${pathExpr}+'/'+${iv}` : `'/'+${iv}`
8120
- const sub = []
8121
- genCodeC(ui2, `${v}[${iv}]`, pe, sub, ctx, schemaPrefix + '/unevaluatedItems')
8122
- if (sub.length) body.push(`for(let ${iv}=0;${iv}<${v}.length;${iv}++){if(_hI(${S},${iv}))continue;${sub.join('\n ')}\n }`)
8123
- } else {
8124
- throw DECLINE
8125
- }
8126
- lines.push(`if(Array.isArray(${v})){${body.join(';')}}`)
8127
- }
8128
-
8129
- function annItemHelpers(ctx) {
8130
- if (ctx.annItemHelpers) return
8131
- ctx.annItemHelpers = true
8132
- ;(ctx.preamble || ctx.helperCode).push(
8133
- 'function _aI(S,i){if(i<S.n)return;if(i===S.n){S.n=i+1;return}(S.x||(S.x=[])).push(i)}' +
8134
- '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)}' +
8135
- '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}' +
8136
- 'function _rbI(S,n,l){S.n=n;if(S.x!==null)S.x.length=l}' +
8137
- 'function _anIT(){return true}function _anIF(){return false}')
8138
- }
8139
-
8140
- // The positions a node's own keywords evaluate, marked on `S`, for an array.
8141
- function addLocalItemAnnotations(node, v, S, out, ctx) {
8142
- if (Array.isArray(node.items) || node.additionalItems !== undefined) throw DECLINE
8143
- const prefix = Array.isArray(node.prefixItems) ? node.prefixItems.length : 0
8144
- if (prefix) out.push(`_mrI(${S},0,Math.min(${v}.length,${prefix}))`)
8145
- if (node.items !== undefined) out.push(`_mrI(${S},${prefix},${v}.length)`)
8146
- if (node.contains !== undefined) {
8147
- const cl = []
8148
- nestedGenCode(node.contains, '_cv', cl, ctx)
8149
- const ci = ctx.varCounter++
8150
- ;(ctx.preamble || ctx.helperCode).push(`function _cI${ci}(_cv){${cl.join('\n ')}\n return true}`)
8151
- out.push(`for(let _i=0;_i<${v}.length;_i++)if(_cI${ci}(${v}[_i]))_aI(${S},_i)`)
8152
- }
8153
- }
8154
-
8155
- // The keywords an item annotation function evaluates through its children;
8156
- // the rest of a subschema is checked by a plain verdict.
8157
- const ANNOT_ITEM_APPLICATORS = new Set(['allOf', 'anyOf', 'oneOf', 'if', 'then', 'else', '$ref', 'not', 'unevaluatedItems'])
8158
-
8159
- // A function `(value, S) => valid` for one subschema, marking on S the array
8160
- // positions it evaluated when it passes, and leaving S as it was when it does
8161
- // not. A subschema with an unevaluatedItems of its own evaluates every
8162
- // position once that passes, so it works on a record of its own and then
8163
- // marks them all.
8164
- function emitAnnotI(sub, ctx) {
8165
- if (sub === true) return '_anIT'
8166
- if (sub === false) return '_anIF'
8167
- if (sub === null || typeof sub !== 'object') throw DECLINE
8168
- if (!ctx.annFnsI) ctx.annFnsI = new Map()
8169
- const hit = ctx.annFnsI.get(sub)
8170
- if (hit !== undefined) {
8171
- if (hit === null) throw DECLINE
8172
- return hit
8173
- }
8174
- if (sub.$dynamicRef !== undefined || sub.$recursiveRef !== undefined) throw DECLINE
8175
- if (Array.isArray(sub.items) || sub.additionalItems !== undefined) throw DECLINE
8176
- // unevaluatedProperties reads the applicators too, which the local verdict
8177
- // below leaves out.
8178
- if (sub.unevaluatedProperties !== undefined && sub.unevaluatedProperties !== true) throw DECLINE
8179
- ctx.annFnsI.set(sub, null)
8180
- annItemHelpers(ctx)
8181
- const name = `_ai${ctx.varCounter++}`
8182
- const local = {}
8183
- for (const k of Object.keys(sub)) if (!ANNOT_ITEM_APPLICATORS.has(k)) setOwn(local, k, sub[k])
8184
- const vl = []
8185
- nestedGenCode(local, '_av', vl, ctx)
8186
- const decls = [`function ${name}_v(_av){${vl.join('\n ')}\n return true}`]
8187
- const body = [`if(!${name}_v(d))return false`]
8188
- if (sub.not !== undefined) {
8189
- const nl = []
8190
- nestedGenCode(sub.not, '_nv', nl, ctx)
8191
- decls.push(`function ${name}_n(_nv){${nl.join('\n ')}\n return true}`)
8192
- body.push(`if(${name}_n(d))return false`)
8193
- }
8194
- const own = sub.unevaluatedItems !== undefined && sub.unevaluatedItems !== true
8195
- const R = own ? '_R' : 'S'
8196
- body.push(own ? `const _R={n:0,x:null}` : `const _n0=S.n,_x0=S.x===null?0:S.x.length`)
8197
- const failOut = own ? 'return false' : '{_rbI(S,_n0,_x0);return false}'
8198
- const locals = []
8199
- addLocalItemAnnotations(sub, 'd', R, locals, ctx)
8200
- if (locals.length) body.push(`if(Array.isArray(d)){${locals.join(';')}}`)
8201
- for (const b of sub.allOf || []) body.push(`if(!${emitAnnotI(b, ctx)}(d,${R}))${failOut}`)
8202
- if (Array.isArray(sub.anyOf)) body.push(`{let _a=false;${sub.anyOf.map((b) => `if(${emitAnnotI(b, ctx)}(d,${R}))_a=true;`).join('')}if(!_a)${failOut}}`)
8203
- if (Array.isArray(sub.oneOf)) body.push(`{let _c=0;${sub.oneOf.map((b) => `if(${emitAnnotI(b, ctx)}(d,${R})&&++_c>1)${failOut}`).join('')}if(_c!==1)${failOut}}`)
8204
- if (sub.if !== undefined) {
8205
- const thenCheck = sub.then !== undefined ? `if(!${emitAnnotI(sub.then, ctx)}(d,${R}))${failOut}` : ''
8206
- const elseCheck = sub.else !== undefined ? `if(!${emitAnnotI(sub.else, ctx)}(d,${R}))${failOut}` : ''
8207
- body.push(`if(${emitAnnotI(sub.if, ctx)}(d,${R})){${thenCheck}}else{${elseCheck}}`)
8208
- }
8209
- if (typeof sub.$ref === 'string') body.push(`if(!${emitAnnotI(refTargetFor(sub.$ref, ctx), ctx)}(d,${R}))${failOut}`)
8210
- if (own) {
8211
- const u = sub.unevaluatedItems
8212
- if (u === false) {
8213
- body.push(`if(Array.isArray(d)){for(let _i=0;_i<d.length;_i++)if(!_hI(_R,_i))return false}`)
8214
- } else if (typeof u === 'object' && u !== null) {
8215
- const ul = []
8216
- nestedGenCode(u, '_uv', ul, ctx)
8217
- decls.push(`function ${name}_u(_uv){${ul.join('\n ')}\n return true}`)
8218
- body.push(`if(Array.isArray(d)){for(let _i=0;_i<d.length;_i++)if(!_hI(_R,_i)&&!${name}_u(d[_i]))return false}`)
8219
- } else {
8220
- throw DECLINE
8221
- }
8222
- body.push(`if(Array.isArray(d))_mrI(S,0,d.length)`)
8223
- }
8224
- // `unevaluatedItems: true` evaluates every position once the subschema
8225
- // has passed, with nothing to check; marked last, so a failure above has
8226
- // nothing of it to undo.
8227
- if (sub.unevaluatedItems === true) body.push(`if(Array.isArray(d))_mrI(S,0,d.length)`)
8228
- decls.push(`function ${name}(d,S){${body.join(';')};return true}`)
8229
- ;(ctx.preamble || ctx.helperCode).push(decls.join(''))
8230
- ctx.annFnsI.set(sub, name)
8231
- return name
8232
- }
8233
-
8234
- function genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
8235
- const propKeysU = Object.keys(schema.properties || {})
8236
- const ui = ctx.varCounter++
8237
- const failU = fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:_k${ui}[_i]}`, "'must NOT have unevaluated properties'")
8238
- let innerU
8239
- if (propKeysU.length === 0) {
8240
- innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++){${failU}}`
8241
- } else if (propKeysU.length <= 8) {
8242
- const checksU = propKeysU.map(k => `_k${ui}[_i]!==${JSON.stringify(k)}`).join('&&')
8243
- innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++)if(${checksU}){${failU}}`
8244
- } else {
8245
- ctx.closureVars.push(`_a${ui}`)
8246
- ctx.closureVals.push(new Set(propKeysU))
8247
- innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++)if(!_a${ui}.has(_k${ui}[_i])){${failU}}`
8248
- }
8249
- lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
8250
- }
8251
-
8252
- function genUnevaluatedItemsFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
8253
- const itemsIsPrefixC = Array.isArray(schema.items)
8254
- const allEvalC = schema.items !== undefined && !itemsIsPrefixC
8255
- if (!allEvalC) {
8256
- const plenC = itemsIsPrefixC ? schema.items.length : (Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0)
8257
- const failI = fail('unevaluatedItems', 'unevaluatedItems', `{limit:${plenC}}`, `'must NOT have more than ${plenC} items'`)
8258
- lines.push(`if(Array.isArray(${v})&&${v}.length>${plenC}){${failI}}`)
8259
- }
8260
- }
8261
-
8262
- // The error generator's per-key loop (genAdditionalSchemaE), in the combined
8263
- // generator: the subschema compiled by genCodeC at each additional key, with
8264
- // that key's path. Patterns go through the same matchers patternProperties
8265
- // uses here.
8266
- function genAdditionalSchemaC(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj) {
8267
- const ki = ctx.varCounter++
8268
- const kVar = `_ak${ki}`
8269
- const known = Object.keys(schema.properties || {})
8270
- const conds = []
8271
- if (known.length) {
8272
- const set = `_akn${ki}`
8273
- ctx.closureVars.push(set)
8274
- ctx.closureVals.push(new Set(known))
8275
- conds.push(`!${set}.has(${kVar})`)
8276
- }
8277
- const pats = []
8278
- for (const pat of Object.keys(schema.patternProperties || {})) {
8279
- const fast = fastPrefixCheck(pat, kVar)
8280
- if (fast) { pats.push(fast); continue }
8281
- const ri = ctx.varCounter++
8282
- ctx.closureVars.push(`_re${ri}`)
8283
- ctx.closureVals.push(safeReClosure(ctx, pat))
8284
- pats.push(`_re${ri}.test(${kVar})`)
8285
- }
8286
- if (pats.length) conds.push(`!(${pats.join('||')})`)
8287
- const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
8288
- const keep = conds.length ? `if(${conds.join('&&')})` : ''
8289
- const pe = emitPtrEsc(ctx)
8290
- const p = pathExpr ? `${pathExpr}+'/'+${pe}(${kVar})` : `'/'+${pe}(${kVar})`
8291
- const sub = []
8292
- genCodeC(schema.additionalProperties, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/additionalProperties')
8293
- if (sub.length === 0) return
8294
- lines.push(`${guard}{for(const ${kVar} in ${v}){${keep}{${sub.join('\n ')}\n }}}`)
8295
- }
8296
-
8297
- function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
8298
- let handledPropertyNames = false
8299
- const ppEntries = Object.entries(schema.patternProperties)
8300
- const pn = isSimplePN(schema.propertyNames) ? schema.propertyNames : null
8301
- const pi = ctx.varCounter++
8302
-
8303
- // Build pattern matchers: prefer charCodeAt for simple prefixes
8304
- const matchers = []
8305
- for (const [pat] of ppEntries) {
8306
- const kVar = `_k${pi}`
8307
- const fast = fastPrefixCheck(pat, kVar)
8308
- if (fast) {
8309
- matchers.push({ check: fast })
8310
- } else {
8311
- const ri = ctx.varCounter++
8312
- ctx.closureVars.push(`_re${ri}`)
8313
- ctx.closureVals.push(safeReClosure(ctx, pat))
8314
- matchers.push({ check: `_re${ri}.test(_k${pi})` })
8315
- }
8316
- }
8317
-
8318
- const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
8319
- const kVar = `_k${pi}`
8320
-
8321
- if (schema.additionalProperties === false) {
8322
- handledPropertyNames = !!pn
8323
- const propKeys = Object.keys(schema.properties || {})
8324
- // Inline key comparison for small property sets
8325
- const keyCheck = propKeys.length <= 8
8326
- ? propKeys.map(k => `${kVar}===${JSON.stringify(k)}`).join('||')
8327
- : null
8328
- if (!keyCheck) {
8329
- const allowedSet = `_as${pi}`
8330
- ctx.closureVars.push(allowedSet)
8331
- ctx.closureVals.push(new Set(propKeys))
8332
- }
8333
-
8334
- lines.push(`${guard}{for(const ${kVar} in ${v}){`)
8335
- // propertyNames checks (merged)
8336
- if (pn) {
8337
- 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'`)}}`)
8338
- 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'`)}}`)
8339
- if (pn.pattern) {
8340
- const fast = fastPrefixCheck(pn.pattern, kVar)
8341
- if (fast) {
8342
- lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
8343
- } else {
8344
- const ri = ctx.varCounter++
8345
- ctx.closureVars.push(`_re${ri}`)
8346
- ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
8347
- lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
8348
- }
8349
- }
8350
- 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'")}}`)
8351
- if (pn.enum) {
8352
- const ei = ctx.varCounter++
8353
- ctx.closureVars.push(`_es${ei}`)
8354
- ctx.closureVals.push(new Set(pn.enum))
8355
- 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'")}}`)
8356
- }
8357
- }
8358
- const matchExpr = keyCheck || `_as${pi}.has(${kVar})`
8359
- lines.push(`let _m${pi}=${matchExpr}`)
8360
- for (let i = 0; i < ppEntries.length; i++) {
8361
- // The subschema is generated in place so its own keywords report
8362
- // the real instance path and schema pointer.
8363
- lines.push(`if(${matchers[i].check}){_m${pi}=true;{const _ppv${pi}_${i}=${v}[${kVar}]`)
8364
- genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, `${emitPtrEsc(ctx)}(${kVar})`), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
8365
- lines.push(`}}`)
8366
- }
8367
- lines.push(`if(!_m${pi}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:${kVar}}`, "'must NOT have additional properties'")}}`)
8368
- lines.push(`}}`)
8369
- } else {
8370
- handledPropertyNames = !!pn
8371
- lines.push(`${guard}{for(const ${kVar} in ${v}){`)
8372
- if (pn) {
8373
- 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'`)}}`)
8374
- 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'`)}}`)
8375
- if (pn.pattern) {
8376
- const fast = fastPrefixCheck(pn.pattern, kVar)
8377
- if (fast) {
8378
- lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
8379
- } else {
8380
- const ri = ctx.varCounter++
8381
- ctx.closureVars.push(`_re${ri}`)
8382
- ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
8383
- lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
8384
- }
8385
- }
8386
- 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'")}}`)
8387
- if (pn.enum) {
8388
- const ei = ctx.varCounter++
8389
- ctx.closureVars.push(`_es${ei}`)
8390
- ctx.closureVals.push(new Set(pn.enum))
8391
- 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'")}}`)
8392
- }
8393
- }
8394
- for (let i = 0; i < ppEntries.length; i++) {
8395
- lines.push(`if(${matchers[i].check}){const _ppv${pi}_${i}=${v}[${kVar}]`)
8396
- genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, `${emitPtrEsc(ctx)}(${kVar})`), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
8397
- lines.push(`}`)
8398
- }
8399
- lines.push(`}}`)
8400
- }
8401
- return handledPropertyNames
8402
- }
8403
-
8404
- function genDependentSchemasC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
8405
- for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
8406
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
8407
- genCodeC(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
8408
- lines.push(`}`)
8409
- }
8410
- }
8411
-
8412
- function genPropertyNamesFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
8413
- const ki = ctx.varCounter++
8414
- const p = pathExpr || '""'
8415
- 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'}`)}}}`)
8416
- }
8417
-
8418
- function genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
8419
- const pn = schema.propertyNames
8420
- const ki = ctx.varCounter++
8421
- // In the rich function an error carries the value it was raised on, and
8422
- // here that would be the key where the interpreter's error points at the
8423
- // object, so that one is left to the plain function and enrichment.
8424
- if (!isSimplePN(pn) && ctx.rich) throw DECLINE
8425
- lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
8426
- if (!isSimplePN(pn)) {
8427
- genCodeC(pn, `_k${ki}`, pathExpr, lines, ctx, schemaPrefix + '/propertyNames')
8428
- lines.push(`}}`)
8429
- return
8430
- }
8431
- if (pn.minLength !== undefined) {
8432
- lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
8433
- }
8434
- if (pn.maxLength !== undefined) {
8435
- lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
8436
- }
8437
- if (pn.pattern) {
8438
- const ri = ctx.varCounter++
8439
- ctx.closureVars.push(`_re${ri}`)
8440
- ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
8441
- lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
8442
- }
8443
- if (pn.const !== undefined) {
8444
- lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
8445
- }
8446
- if (pn.enum) {
8447
- const ei = ctx.varCounter++
8448
- ctx.closureVars.push(`_es${ei}`)
8449
- ctx.closureVals.push(new Set(pn.enum))
8450
- 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'")}}`)
8451
- }
8452
- lines.push(`}}`)
8453
- }
8454
-
8455
- function genPrefixItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
8456
- for (let i = 0; i < schema.prefixItems.length; i++) {
8457
- const childPath = childPathExpr(pathExpr, String(i))
8458
- lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
8459
- genCodeC(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
8460
- lines.push(`}`)
8461
- }
8462
- }
8463
-
8464
- function genContainsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
8465
- const ci = ctx.varCounter++
8466
- const subLines = []
8467
- nestedGenCode(schema.contains, `_cv`, subLines, ctx)
8468
- const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
8469
- const minC = schema.minContains !== undefined ? schema.minContains : 1
8470
- const maxC = schema.maxContains
8471
- 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}++}`)
8472
- lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'must contain at least ${minC} valid item(s)'`)}}`)
8473
- 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)'`)}}`)
8474
- lines.push(`}`)
8475
- }
8476
-
8477
- function genAnyOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
8478
- // The collapsed branch error the other engines report (ATA4001-4003),
8479
- // built by the error generator's emitter; see compileToJSCombined.
8480
- if (ctx.inCombined) return emitBranchCollapse(schema.anyOf, 'anyOf', v, pathExpr, lines, ctx, schemaPrefix)
8481
- const fi = ctx.varCounter++
8482
- 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}` })
8483
- 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'")}}}`)
8484
- }
8485
-
8486
- function genOneOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
8487
- // The collapsed branch error the other engines report (ATA4001-4003),
8488
- // built by the error generator's emitter; see compileToJSCombined.
8489
- if (ctx.inCombined) return emitBranchCollapse(schema.oneOf, 'oneOf', v, pathExpr, lines, ctx, schemaPrefix)
8490
- const fi = ctx.varCounter++
8491
- 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}` })
8492
- 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'")}}}`)
8493
- }
8494
-
8495
- function genNotC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
8496
- const sl = []; nestedGenCode(schema.not, '_nv', sl, ctx)
8497
- const nfn = sl.length === 0 ? `function(_nv){return true}` : `function(_nv){${sl.join(';')};return true}`
8498
- const fi = ctx.varCounter++
8499
- lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
8500
- }
8501
-
8502
- function genIfC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
8503
- const sl = []; nestedGenCode(schema.if, '_iv', sl, ctx)
8504
- const fi = ctx.varCounter++
8505
- const ifFn = sl.length === 0 ? `function(_iv){return true}` : `function(_iv){${sl.join(';')};return true}`
8506
- lines.push(`{const _if${fi}=${ifFn}`)
8507
- if (schema.then !== undefined) { lines.push(`if(_if${fi}(${v})){`); genCodeC(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then'); lines.push(`}`) }
8508
- 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(`}`) }
8509
- lines.push(`}`)
8510
- }
8511
-
8512
5157
  // Collect statically-known evaluated properties/items from a schema.
8513
5158
  // Returns { props: string[], items: number|null, allProps: bool, allItems: bool, dynamic: bool }
8514
5159
  function collectEvaluated(schema, schemaMap, rootDefs) {
@@ -8644,9 +5289,22 @@ function _collectEval(schema, result, defs, schemaMap, refStack, isRoot) {
8644
5289
 
8645
5290
  // not → contributes nothing (spec: annotations from not are discarded)
8646
5291
  }
5292
+ // The error generator and the one-pass (combined) generator live in
5293
+ // lib/js-compiler-errors.js, read on the first call that needs them: a
5294
+ // validator's errors are compiled when they are first read, and most
5295
+ // processes load this module to answer verdicts. The two files were one; the
5296
+ // generators share this module's helpers through _internal below, which is
5297
+ // an implementation detail and not part of the package's interface.
5298
+ let _errorsModule = null
5299
+ function errorsModule () { return _errorsModule || (_errorsModule = require('./js-compiler-errors')) }
5300
+ function compileToJSCodegenWithErrors (schema, schemaMap, userFormats, sourceOpts) { return errorsModule().compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts) }
5301
+ function compileToJSCombined (schema, VALID_RESULT, schemaMap, userFormats, opts) { return errorsModule().compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts) }
8647
5302
 
8648
- module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, expandedChars, AJV_MESSAGES, _setCodeBudget }
5303
+
5304
+ module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, expandedChars, AJV_MESSAGES, _setCodeBudget, _helperSources: { DEQ_HELPER, PTR_ESC_HELPER, OWN_HELPER_CODE } }
8649
5305
  // For tests: the dynamic-scope unrolling, to hold its output to the interpreter.
8650
5306
  Object.defineProperty(module.exports, '_expandDynamicScopes', { value: expandDynamicScopes, enumerable: false })
8651
5307
  // For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
8652
5308
  Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })
5309
+
5310
+ module.exports._internal = { 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 }