ata-validator 1.42.0 → 1.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +2 -2
- package/lib/branch-collapse.js +18 -12
- package/lib/enrich-site.js +2 -2
- package/lib/js-compiler.js +641 -125
- package/lib/scan-compiler.js +210 -25
- package/lib/scan-runtime.js +13 -1
- package/lib/validator-core.js +23 -4
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/lib/js-compiler.js
CHANGED
|
@@ -176,7 +176,24 @@ const { compileSafe, patternIsSafe } = require('./safe-regex')
|
|
|
176
176
|
let _nativeIsLinear = null
|
|
177
177
|
// The linearity check only parses; it runs first so a pattern bound for the
|
|
178
178
|
// RegExp is never compiled for the engine just to be told it could have been.
|
|
179
|
+
// Unicode property escapes need a real RegExp in unicode mode, as the
|
|
180
|
+
// interpreter compiles them (Interpreter#pattern): the linear engine does not
|
|
181
|
+
// know them, and without the flag `\p{L}` reads as the letters p, {, L, }.
|
|
182
|
+
// The flag only where the pattern is valid with it, again as the interpreter
|
|
183
|
+
// does. These patterns used to send the whole schema to the interpreter.
|
|
184
|
+
const PROP_ESCAPE = /\\[pP]\{/
|
|
185
|
+
function reFlags (src) {
|
|
186
|
+
if (!PROP_ESCAPE.test(src)) return ''
|
|
187
|
+
try { new RegExp(src, 'u'); return 'u' } catch { return '' }
|
|
188
|
+
}
|
|
189
|
+
// The flag argument for generated `new RegExp(<json literal>)` source.
|
|
190
|
+
function reFlagArg (jsonLit) {
|
|
191
|
+
let raw
|
|
192
|
+
try { raw = JSON.parse(jsonLit) } catch { return '' }
|
|
193
|
+
return typeof raw === 'string' && reFlags(raw) ? ",'u'" : ''
|
|
194
|
+
}
|
|
179
195
|
const useSafeEngine = (src) => {
|
|
196
|
+
if (PROP_ESCAPE.test(src)) return false
|
|
180
197
|
if (_nativeIsLinear === null) _nativeIsLinear = require('./regex-linear').nativeIsLinear
|
|
181
198
|
if (_nativeIsLinear(src)) return false
|
|
182
199
|
return patternIsSafe(src)
|
|
@@ -187,7 +204,7 @@ const useSafeEngine = (src) => {
|
|
|
187
204
|
// standalone embeds the engine), else a plain RegExp. Both expose `.test`.
|
|
188
205
|
function safeReClosure (ctx, src) {
|
|
189
206
|
if (useSafeEngine(src)) { ctx.usesSafeRe = true; return compileSafe(src) }
|
|
190
|
-
return new RegExp(src)
|
|
207
|
+
return new RegExp(src, reFlags(src))
|
|
191
208
|
}
|
|
192
209
|
|
|
193
210
|
// Compile a JSON Schema into a pure JS validator function.
|
|
@@ -228,7 +245,7 @@ function literalValue (lit) {
|
|
|
228
245
|
}
|
|
229
246
|
|
|
230
247
|
function unescapeSp (sp) {
|
|
231
|
-
return sp.indexOf('\\') === -1 ? sp : sp.replace(/\\(.)/g,
|
|
248
|
+
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))
|
|
232
249
|
}
|
|
233
250
|
|
|
234
251
|
const FRAME_INLINE_MAX = 120
|
|
@@ -338,6 +355,7 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
338
355
|
// A $ref cycle on one value recurses here as it does in generated code; see
|
|
339
356
|
// hasSameInstanceRefCycle.
|
|
340
357
|
if (!defs && hasSameInstanceRefCycle(schema)) return null
|
|
358
|
+
if (!defs && bothDefsContainers(schema)) return null
|
|
341
359
|
|
|
342
360
|
// unevaluatedProperties / unevaluatedItems need annotation tracking this
|
|
343
361
|
// path does not do. Decline so the schema reaches an engine that does.
|
|
@@ -589,7 +607,7 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
589
607
|
}
|
|
590
608
|
if (schema.pattern) {
|
|
591
609
|
try {
|
|
592
|
-
const re = useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern)
|
|
610
|
+
const re = useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern, reFlags(schema.pattern))
|
|
593
611
|
checks.push((d) => typeof d !== 'string' || re.test(d))
|
|
594
612
|
} catch {
|
|
595
613
|
return null
|
|
@@ -1481,7 +1499,6 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1481
1499
|
}
|
|
1482
1500
|
|
|
1483
1501
|
// Unicode property escapes in pattern need 'u' flag — codegen uses RegExp without it
|
|
1484
|
-
if (schema.pattern && /\\[pP]\{/.test(schema.pattern)) return false
|
|
1485
1502
|
|
|
1486
1503
|
// $ref — allow local refs (#/$defs/Name) and non-local refs if in schemaMap
|
|
1487
1504
|
if (schema.$ref) {
|
|
@@ -1516,17 +1533,11 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1516
1533
|
!resolvedTarget.propertyNames
|
|
1517
1534
|
if (!targetSimple && schema.unevaluatedProperties === undefined && schema.unevaluatedItems === undefined) return false
|
|
1518
1535
|
}
|
|
1519
|
-
// In Draft 2020-12, $ref with siblings
|
|
1520
|
-
//
|
|
1521
|
-
//
|
|
1522
|
-
//
|
|
1523
|
-
//
|
|
1524
|
-
// inferred from what is not a validation keyword, so a keyword this list
|
|
1525
|
-
// does not know still declines instead of being skipped. Measured on the
|
|
1526
|
-
// 977 SchemaStore schemas: a $ref with a description or title beside it sent
|
|
1527
|
-
// 223 of them to the interpreted engine.
|
|
1528
|
-
const siblings = Object.keys(schema).filter(k => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))
|
|
1529
|
-
if (siblings.length > 0 && schema.unevaluatedProperties === undefined && schema.unevaluatedItems === undefined) return false
|
|
1536
|
+
// In Draft 2020-12, $ref with siblings applies both. The verdict
|
|
1537
|
+
// generator compiles the siblings after the reference (genCodeNode), the
|
|
1538
|
+
// combined one too (genCodeCNode), and the error generator declines such
|
|
1539
|
+
// a node, so a sibling is never dropped. This used to decline the whole
|
|
1540
|
+
// schema unless an unevaluated keyword was beside the reference.
|
|
1530
1541
|
}
|
|
1531
1542
|
|
|
1532
1543
|
// additionalProperties as schema — supported when no composition (allOf/oneOf/anyOf)
|
|
@@ -1608,7 +1619,6 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1608
1619
|
// compiled to code that ignored it.
|
|
1609
1620
|
if (schema.patternProperties) {
|
|
1610
1621
|
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
1611
|
-
if (/\\[pP]\{/.test(pat)) return false
|
|
1612
1622
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return false
|
|
1613
1623
|
}
|
|
1614
1624
|
}
|
|
@@ -1947,8 +1957,20 @@ function hasSameInstanceRefCycle(root) {
|
|
|
1947
1957
|
return found
|
|
1948
1958
|
}
|
|
1949
1959
|
|
|
1960
|
+
// The generators look a definition up by name in one table, `$defs` or else
|
|
1961
|
+
// `definitions`, whichever container the pointer named. With both at the root,
|
|
1962
|
+
// `#/definitions/x` read `$defs/x`: a draft 7 schema carrying both, with
|
|
1963
|
+
// different schemas under one name, rejected what it allowed and accepted
|
|
1964
|
+
// what it rejected. The interpreter resolves the pointer as written, so a
|
|
1965
|
+
// schema with both goes there. None of SchemaStore's 980 has both.
|
|
1966
|
+
function bothDefsContainers(schema) {
|
|
1967
|
+
return schema.$defs !== null && typeof schema.$defs === 'object' &&
|
|
1968
|
+
schema.definitions !== null && typeof schema.definitions === 'object'
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1950
1971
|
function sharedCodegenGate(schema, schemaMap) {
|
|
1951
1972
|
if (typeof schema !== 'object' || schema === null) return true
|
|
1973
|
+
if (bothDefsContainers(schema)) return false
|
|
1952
1974
|
if (hasSameInstanceRefCycle(schema)) return false
|
|
1953
1975
|
if (!codegenSafe(schema, schemaMap)) return false
|
|
1954
1976
|
if (needsBaseTracking(schema, schemaMap, new Set())) return false
|
|
@@ -2276,6 +2298,15 @@ function prepareForCodegen(schema, schemaMap) {
|
|
|
2276
2298
|
return normalizePropertyNames(normalizeRefs(schema, schemaMap))
|
|
2277
2299
|
}
|
|
2278
2300
|
|
|
2301
|
+
const VERDICT_SPLIT = 64 * 1024
|
|
2302
|
+
function sourceSize (lines, ctx) {
|
|
2303
|
+
let n = 0
|
|
2304
|
+
for (const l of lines) n += l.length
|
|
2305
|
+
for (const l of ctx.preamble) n += l.length
|
|
2306
|
+
if (ctx.deferredChecks) for (const l of ctx.deferredChecks) n += l.length
|
|
2307
|
+
return n
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2279
2310
|
function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
|
|
2280
2311
|
const inputSchema = schema
|
|
2281
2312
|
schema = prepareForCodegen(schema, schemaMap)
|
|
@@ -2300,7 +2331,6 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
|
|
|
2300
2331
|
// patternProperties: bail only on boolean sub-schemas or unicode property escapes
|
|
2301
2332
|
if (schema.patternProperties) {
|
|
2302
2333
|
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
2303
|
-
if (/\\[pP]\{/.test(pat)) return null
|
|
2304
2334
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
|
|
2305
2335
|
}
|
|
2306
2336
|
}
|
|
@@ -2349,20 +2379,28 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
|
|
|
2349
2379
|
if (hasUnresolvableRef(schema, rootDefs, anchors, schemaMap, new Set())) return null
|
|
2350
2380
|
if (needsBaseTracking(schema, schemaMap, new Set())) return null
|
|
2351
2381
|
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
if (
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2382
|
+
// Macro keywords expand into a schema of their own, which this generator
|
|
2383
|
+
// does not apply; such a schema stays on the interpreted engine.
|
|
2384
|
+
if (opts && opts.keywords && usesMacroKeyword(schema, schemaMap, opts.keywords)) return null
|
|
2385
|
+
// Every reference is first expanded where it is used, which is the fastest
|
|
2386
|
+
// form for the checks. Past VERDICT_SPLIT of source the function is
|
|
2387
|
+
// generated again with each definition referenced from several places
|
|
2388
|
+
// written once, as a function in the preamble: expanded, SchemaStore's sarif
|
|
2389
|
+
// (79 KB of schema) came to 2.8 MB of verdict source, and compiling it was
|
|
2390
|
+
// half of the first validate() call.
|
|
2391
|
+
let ctx, lines
|
|
2392
|
+
for (const share of [false, true]) {
|
|
2393
|
+
ctx = { varCounter: 0, helpers: [], helperCode: [], preamble: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen], rootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors, rootSchema: inputSchema, userFormats: userFormats || null, removeNodes,
|
|
2394
|
+
keywords: (opts && opts.keywords) || null, kwEmitted: null, shareDefs: share }
|
|
2395
|
+
if (ctx.keywords) ctx.kwEmitted = new Set()
|
|
2396
|
+
lines = []
|
|
2397
|
+
try {
|
|
2398
|
+
genCode(schema, 'd', lines, ctx)
|
|
2399
|
+
} catch (e) {
|
|
2400
|
+
if (e === DECLINE) return null
|
|
2401
|
+
throw e
|
|
2402
|
+
}
|
|
2403
|
+
if (share || sourceSize(lines, ctx) <= VERDICT_SPLIT) break
|
|
2366
2404
|
}
|
|
2367
2405
|
// Every schema node that carries a custom keyword must have had its check
|
|
2368
2406
|
// written. A path that inlined a node without going through genCodeNode
|
|
@@ -2396,10 +2434,10 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
|
|
|
2396
2434
|
closureValues.push(compileSafe(JSON.parse(safeMatch[2])))
|
|
2397
2435
|
continue
|
|
2398
2436
|
}
|
|
2399
|
-
const match = code.match(/^const (_re\d+)=new RegExp\((
|
|
2437
|
+
const match = code.match(/^const (_re\d+)=new RegExp\((.+?)(?:,'(u)')?\)$/)
|
|
2400
2438
|
if (match) {
|
|
2401
2439
|
closureNames.push(match[1])
|
|
2402
|
-
closureValues.push(new RegExp(JSON.parse(match[2])))
|
|
2440
|
+
closureValues.push(new RegExp(JSON.parse(match[2]), match[3] || ''))
|
|
2403
2441
|
}
|
|
2404
2442
|
}
|
|
2405
2443
|
|
|
@@ -2913,7 +2951,9 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2913
2951
|
// $ref — guard against circular references
|
|
2914
2952
|
// In 2020-12 with unevaluated*, $ref can coexist with siblings — don't early return
|
|
2915
2953
|
// Only when THIS schema has unevaluated keywords directly (not via $ref target)
|
|
2916
|
-
|
|
2954
|
+
// Any keyword beside $ref that validates is compiled after the reference
|
|
2955
|
+
// (2020-12 applies both; draft 7 drops such siblings when it is read).
|
|
2956
|
+
const hasSiblings = !!schema.$ref && Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))
|
|
2917
2957
|
if (schema.$ref) {
|
|
2918
2958
|
if (genRefNode(schema, v, lines, ctx, knownType, hasSiblings)) return
|
|
2919
2959
|
}
|
|
@@ -3121,7 +3161,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3121
3161
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
3122
3162
|
ctx.usesSafeRe = true
|
|
3123
3163
|
} else {
|
|
3124
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`);
|
|
3164
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`);
|
|
3125
3165
|
}
|
|
3126
3166
|
}
|
|
3127
3167
|
const ri = ctx.regExpMap.get(pattern);
|
|
@@ -3643,6 +3683,31 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
3643
3683
|
}
|
|
3644
3684
|
lines.push(`if(!${fnName}(${v}))return false`)
|
|
3645
3685
|
if (!hasSiblings) return true
|
|
3686
|
+
} else if (ctx.shareDefs && !ctx.refStack.has(schema.$ref) && (ctx.sharedDefs || (ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs))).has(defName)) {
|
|
3687
|
+
// A shared definition off any cycle, as a plain function in the
|
|
3688
|
+
// preamble (see compileToJSCodegen): no guard, since it cannot re-enter
|
|
3689
|
+
// itself. Generated over its own `d` like the cyclic ones above.
|
|
3690
|
+
if (!ctx.sharedDefFns) ctx.sharedDefFns = new Map()
|
|
3691
|
+
let fnName = ctx.sharedDefFns.get(defName)
|
|
3692
|
+
if (!fnName) {
|
|
3693
|
+
fnName = '_dv' + ctx.sharedDefFns.size + '_' + defName.replace(/[^A-Za-z0-9_]/g, '_')
|
|
3694
|
+
ctx.sharedDefFns.set(defName, fnName)
|
|
3695
|
+
const bodyLines = []
|
|
3696
|
+
const outerDeferred = ctx.deferredChecks
|
|
3697
|
+
ctx.deferredChecks = null
|
|
3698
|
+
ctx.refStack.add(schema.$ref)
|
|
3699
|
+
try {
|
|
3700
|
+
genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
|
|
3701
|
+
} finally {
|
|
3702
|
+
ctx.refStack.delete(schema.$ref)
|
|
3703
|
+
}
|
|
3704
|
+
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
3705
|
+
ctx.deferredChecks = outerDeferred
|
|
3706
|
+
if (bodyLines.some((l) => /\b_validate\b/.test(l))) throw DECLINE
|
|
3707
|
+
hoist(ctx, `function ${fnName}(d){${bodyLines.join('\n ')}\n return true}`)
|
|
3708
|
+
}
|
|
3709
|
+
lines.push(`if(!${fnName}(${v}))return false`)
|
|
3710
|
+
if (!hasSiblings) return true
|
|
3646
3711
|
} else if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return true }
|
|
3647
3712
|
else {
|
|
3648
3713
|
ctx.refStack.add(schema.$ref)
|
|
@@ -4417,7 +4482,11 @@ const FORMAT_CODEGEN = {
|
|
|
4417
4482
|
function esc(s) { return JSON.stringify(s).slice(1, -1).replace(/'/g, "\\'") }
|
|
4418
4483
|
// A schema pointer segment for a pattern: JSON Pointer escaping, then made
|
|
4419
4484
|
// safe for the single-quoted literal the error emitters use.
|
|
4420
|
-
|
|
4485
|
+
// A pointer segment as text for a single-quoted literal. Control characters
|
|
4486
|
+
// and the two line separators go in as \u escapes: written raw, a property
|
|
4487
|
+
// named "foo\nbar" broke the literal, the combined function failed to compile
|
|
4488
|
+
// and every schema with such a name lost it.
|
|
4489
|
+
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')) }
|
|
4421
4490
|
|
|
4422
4491
|
// Resolve child path at codegen time when parent is a static string literal.
|
|
4423
4492
|
// This enables frozen pre-allocation for ALL nested error objects.
|
|
@@ -4744,7 +4813,6 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
4744
4813
|
if (!sharedCodegenGate(schema, schemaMap)) return null
|
|
4745
4814
|
if (schema.patternProperties) {
|
|
4746
4815
|
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
4747
|
-
if (/\\[pP]\{/.test(pat)) return null
|
|
4748
4816
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
|
|
4749
4817
|
}
|
|
4750
4818
|
}
|
|
@@ -4941,6 +5009,50 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
4941
5009
|
// Emit codegen for oneOf/anyOf that runs each branch with error collection,
|
|
4942
5010
|
// aggregates results, and uses __ataCollapse to surface a single best-branch
|
|
4943
5011
|
// error. Wraps the existing per-branch evaluation; does not replace it.
|
|
5012
|
+
const BRANCH_VERDICT_MAX = 4096
|
|
5013
|
+
|
|
5014
|
+
// What undoing a trial emission needs: during one, the context's arrays only
|
|
5015
|
+
// grow and its maps and sets only gain keys (a reference pushed onto refStack
|
|
5016
|
+
// by a declined branch is such a key), so their lengths and sizes are enough,
|
|
5017
|
+
// and what was added is cut off the end, in insertion order. Copying them
|
|
5018
|
+
// instead made the trial quadratic: 297 oneOf and anyOf on apollo-router,
|
|
5019
|
+
// each copying a helper list thousands long.
|
|
5020
|
+
function snapshotCtx (ctx) {
|
|
5021
|
+
const out = new Map()
|
|
5022
|
+
for (const k of Object.keys(ctx)) {
|
|
5023
|
+
const x = ctx[k]
|
|
5024
|
+
if (Array.isArray(x)) out.set(k, { kind: 'a', ref: x, n: x.length })
|
|
5025
|
+
else if (x instanceof Map || x instanceof Set) out.set(k, { kind: 'c', ref: x, n: x.size })
|
|
5026
|
+
else out.set(k, { kind: 'v', ref: x })
|
|
5027
|
+
}
|
|
5028
|
+
return out
|
|
5029
|
+
}
|
|
5030
|
+
function restoreCtx (ctx, saved) {
|
|
5031
|
+
for (const k of Object.keys(ctx)) if (!saved.has(k)) delete ctx[k]
|
|
5032
|
+
for (const [k, e] of saved) {
|
|
5033
|
+
if (e.kind === 'a') { e.ref.length = Math.min(e.ref.length, e.n); ctx[k] = e.ref }
|
|
5034
|
+
else if (e.kind === 'c') {
|
|
5035
|
+
const extra = e.ref.size - e.n
|
|
5036
|
+
if (extra > 0) {
|
|
5037
|
+
const added = []
|
|
5038
|
+
let i = 0
|
|
5039
|
+
for (const key of e.ref.keys()) if (i++ >= e.n) added.push(key)
|
|
5040
|
+
for (const key of added) e.ref.delete(key)
|
|
5041
|
+
}
|
|
5042
|
+
ctx[k] = e.ref
|
|
5043
|
+
} else ctx[k] = e.ref
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
5046
|
+
|
|
5047
|
+
// Whether a node holds a reference anywhere below it.
|
|
5048
|
+
function holdsRef (node, seen = new Set()) {
|
|
5049
|
+
if (node === null || typeof node !== 'object' || seen.has(node)) return false
|
|
5050
|
+
seen.add(node)
|
|
5051
|
+
if (typeof node.$ref === 'string' || typeof node.$dynamicRef === 'string') return true
|
|
5052
|
+
for (const k of Object.keys(node)) if (holdsRef(node[k], seen)) return true
|
|
5053
|
+
return false
|
|
5054
|
+
}
|
|
5055
|
+
|
|
4944
5056
|
function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4945
5057
|
ctx.usesBranchCollapse = true
|
|
4946
5058
|
const fi = ctx.varCounter++
|
|
@@ -5004,6 +5116,88 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
|
|
|
5004
5116
|
const report = ctx.inCombined && !ctx.inBranchFn
|
|
5005
5117
|
? `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed})(_e||(_e=[])).push(${collapsed})`
|
|
5006
5118
|
: `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed}){_e.push(${collapsed});if(!_all)return{valid:false,errors:_e}}`
|
|
5119
|
+
// The node's runtime unevaluatedProperties already counted how many of
|
|
5120
|
+
// these branches pass, through the annotation functions, which answer the
|
|
5121
|
+
// same validity. A oneOf with exactly one, an anyOf with any, holds and
|
|
5122
|
+
// reports nothing, so its branches are not run a second time to say so.
|
|
5123
|
+
// Only in the combined function proper, where that count is in scope.
|
|
5124
|
+
const counted = ctx.inCombined && !ctx.inBranchFn && ctx.branchCounts ? ctx.branchCounts.get(branches) : undefined
|
|
5125
|
+
const same = counted !== undefined ? `${v}!==null&&typeof ${v}==='object'&&${counted.of}===${v}` : ''
|
|
5126
|
+
let guard = counted !== undefined
|
|
5127
|
+
? (keyword === 'anyOf' ? `if(!(${same}&&${counted.count}>=1))` : `if(!(${same}&&${counted.count}===1))`)
|
|
5128
|
+
: ''
|
|
5129
|
+
// More than one oneOf branch passed: the error names them and carries no
|
|
5130
|
+
// branch errors, so it is built from the bits the count left.
|
|
5131
|
+
if (guard !== '' && keyword === 'oneOf' && counted.mask && ctx.inCombined && !ctx.inBranchFn) {
|
|
5132
|
+
const ord0 = ctx.noOrdinal ? 'null' : (() => { const o = ordinalFor(ctx.rootSchema, unescapeSp(branchSp)); return o === null ? 'null' : o })()
|
|
5133
|
+
guard = `if(${same}&&${counted.count}>1){const _pi${fi}=[];for(let _k=0;_k<${branches.length};_k++)if((${counted.mask}>>_k)&1)_pi${fi}.push(_k);(_e||(_e=[])).push(__ataMulti(_pi${fi},${branches.length},${pp},'${branchSp}',${ord0}))}else if(!(${same}&&${counted.count}===1))`
|
|
5134
|
+
}
|
|
5135
|
+
// Without such a count, each branch also gets a verdict function, and the
|
|
5136
|
+
// passing branches are counted with those: a oneOf with exactly one and an
|
|
5137
|
+
// anyOf with any hold, and a oneOf with more than one is reported from the
|
|
5138
|
+
// bits, so the error-collecting branches run only when no branch passes
|
|
5139
|
+
// and the collapse needs their errors to pick the closest. Running them to
|
|
5140
|
+
// learn the count built a result object and an error list per branch,
|
|
5141
|
+
// which made a oneOf matched twice cost 69 ns where counting costs about
|
|
5142
|
+
// 12. A branch the verdict generator declines keeps the old path.
|
|
5143
|
+
let vfArr = null
|
|
5144
|
+
// Branches that hold a reference are left to the error branches: their
|
|
5145
|
+
// verdicts would expand every definition again, as a second copy, where the
|
|
5146
|
+
// error branches call the shared one.
|
|
5147
|
+
if (guard === '' && !branches.some((b) => holdsRef(b))) {
|
|
5148
|
+
// A trial: the generators are not exception-safe on the context (a
|
|
5149
|
+
// reference left on refStack by a declined branch made the next error
|
|
5150
|
+
// function write that branch as nothing, which read as valid), so the
|
|
5151
|
+
// context is put back as it was when the verdicts decline.
|
|
5152
|
+
const saved = snapshotCtx(ctx)
|
|
5153
|
+
try {
|
|
5154
|
+
const vfs = branches.map((sub) => {
|
|
5155
|
+
if (sub === true) return 'function(){return true}'
|
|
5156
|
+
if (sub === false) return 'function(){return false}'
|
|
5157
|
+
const bl = []
|
|
5158
|
+
nestedGenCode(sub, '_bv', bl, ctx)
|
|
5159
|
+
return bl.length === 0 ? 'function(){return true}' : `function(_bv){${bl.join(';')};return true}`
|
|
5160
|
+
})
|
|
5161
|
+
// Only while the verdicts are small: they are a second copy of the
|
|
5162
|
+
// branches, with every reference expanded, and on large branches that
|
|
5163
|
+
// copy cost more to compile than counting saves (apollo-router's error
|
|
5164
|
+
// function grew from 1.5 to 2.5 MB of source).
|
|
5165
|
+
if (!vfs.some((b) => b.includes(SP_PLACEHOLDER)) && vfs.reduce((n, b) => n + b.length, 0) <= BRANCH_VERDICT_MAX) {
|
|
5166
|
+
// One constant per branch, called directly: through an array the one
|
|
5167
|
+
// call site sees every branch and none of them is inlined.
|
|
5168
|
+
vfArr = `_bvf${fi}`
|
|
5169
|
+
ctx.helperCode.push(vfs.map((b, i) => `const ${vfArr}_${i}=${b}`).join(';'))
|
|
5170
|
+
} else {
|
|
5171
|
+
restoreCtx(ctx, saved)
|
|
5172
|
+
}
|
|
5173
|
+
} catch (e) {
|
|
5174
|
+
if (e !== DECLINE) throw e
|
|
5175
|
+
restoreCtx(ctx, saved)
|
|
5176
|
+
}
|
|
5177
|
+
}
|
|
5178
|
+
if (vfArr !== null) {
|
|
5179
|
+
const L = branches.length
|
|
5180
|
+
const top = ctx.inCombined && !ctx.inBranchFn
|
|
5181
|
+
// An accepted document is still answered by the verdict first (see the
|
|
5182
|
+
// result shape in compileToJSCombined): that is the fastest path to it,
|
|
5183
|
+
// and dropping it measured 20 ns slower per accepted oneOf.
|
|
5184
|
+
if (top) ctx.unguardedCollapse = true
|
|
5185
|
+
const tests = branches.map((_, i) => `${vfArr}_${i}(${v})`)
|
|
5186
|
+
if (keyword === 'anyOf') {
|
|
5187
|
+
lines.push(`{const _pp${fi}=${pp};if(!(${tests.join('||')})){if(_all===0){_e.push(null);return{valid:false,errors:_e}}const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}}}`)
|
|
5188
|
+
} else {
|
|
5189
|
+
const mk = L <= 30
|
|
5190
|
+
const collect = mk
|
|
5191
|
+
? `const _pi${fi}=[];for(let _k=0;_k<${L};_k++)if((_m${fi}>>_k)&1)_pi${fi}.push(_k);`
|
|
5192
|
+
: ''
|
|
5193
|
+
const multi = `__ataMulti(_pi${fi},${L},_pp${fi},'${branchSp}',${ord})`
|
|
5194
|
+
const pushMulti = top ? `(_e||(_e=[])).push(${multi})` : `_e.push(${multi});if(!_all)return{valid:false,errors:_e}`
|
|
5195
|
+
const count = tests.map((t, i) => `if(${t}){_n${fi}++;${mk ? `_m${fi}|=${1 << i}` : `_pi${fi}.push(${i})`}}`).join('')
|
|
5196
|
+
lines.push(`{const _pp${fi}=${pp};let _n${fi}=0${mk ? `,_m${fi}=0` : `,_pi${fi}=[]`};${count}if(_n${fi}!==1){if(_all===0){_e.push(null);return{valid:false,errors:_e}}if(_n${fi}>1){${collect}${pushMulti}}else{const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++)${resArr}.push(${call});${report}}}}`)
|
|
5197
|
+
}
|
|
5198
|
+
return
|
|
5199
|
+
}
|
|
5200
|
+
if (guard === '' && ctx.inCombined && !ctx.inBranchFn) ctx.unguardedCollapse = true
|
|
5007
5201
|
if (keyword === 'anyOf') {
|
|
5008
5202
|
// anyOf reports nothing once a branch passes, so the branches are first
|
|
5009
5203
|
// only asked whether they pass. They are called with `_all` set to 0, a
|
|
@@ -5017,12 +5211,12 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
|
|
|
5017
5211
|
// no one read. Only when no branch passes do they run again with the
|
|
5018
5212
|
// caller's `_all`, and the collapse sees them all, as before.
|
|
5019
5213
|
const probe = `${fnArr}[_bi](${v},_pp${fi},0${usesSp ? ',_sp' : ''})`
|
|
5020
|
-
lines.push(
|
|
5214
|
+
lines.push(`${guard}{const _pp${fi}=${pp};let _bp${fi}=false;for(let _bi=0;_bi<${fnArr}.length;_bi++){if(${probe}.valid){_bp${fi}=true;break}}if(!_bp${fi}){if(_all===0){_e.push(null);return{valid:false,errors:_e}}const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}}}`)
|
|
5021
5215
|
} else {
|
|
5022
5216
|
// Asked only whether it passes (`_all` 0, see anyOf above), oneOf counts
|
|
5023
5217
|
// the passing branches and answers; the branch errors of a probe are not
|
|
5024
5218
|
// errors anyone reads and are not collapsed.
|
|
5025
|
-
lines.push(
|
|
5219
|
+
lines.push(`${guard}{const _pp${fi}=${pp};const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++)${resArr}.push(${call});if(_all===0){let _n=0;for(const _r of ${resArr})if(_r.valid)_n++;if(_n!==1){_e.push(null);return{valid:false,errors:_e}}}else{${report}}}`)
|
|
5026
5220
|
}
|
|
5027
5221
|
}
|
|
5028
5222
|
|
|
@@ -5259,7 +5453,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5259
5453
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
5260
5454
|
ctx.usesSafeRe = true
|
|
5261
5455
|
} else {
|
|
5262
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
5456
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
|
|
5263
5457
|
}
|
|
5264
5458
|
}
|
|
5265
5459
|
const ri = ctx.regExpMap.get(pattern);
|
|
@@ -5334,7 +5528,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5334
5528
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
5335
5529
|
ctx.usesSafeRe = true
|
|
5336
5530
|
} else {
|
|
5337
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
5531
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
|
|
5338
5532
|
}
|
|
5339
5533
|
}
|
|
5340
5534
|
patChecks.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ci}[_i])`)
|
|
@@ -5365,6 +5559,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5365
5559
|
// whose evaluated set depends on its own branches would report the wrong
|
|
5366
5560
|
// errors, and the collapse would pick the wrong closest branch; declined.
|
|
5367
5561
|
if (ctx.inCombined && schema.unevaluatedProperties !== undefined && schema.unevaluatedProperties !== true && !unevalLocalOk(schema, 'unevaluatedProperties')) throw DECLINE
|
|
5562
|
+
if (ctx.inCombined && schema.unevaluatedItems !== undefined && schema.unevaluatedItems !== true && !unevalLocalOk(schema, 'unevaluatedItems')) throw DECLINE
|
|
5368
5563
|
if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined) genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj)
|
|
5369
5564
|
|
|
5370
5565
|
// unevaluatedItems: false under the same rule. The interpreted engine
|
|
@@ -5497,7 +5692,7 @@ function genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isO
|
|
|
5497
5692
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
5498
5693
|
ctx.usesSafeRe = true
|
|
5499
5694
|
} else {
|
|
5500
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
5695
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
|
|
5501
5696
|
}
|
|
5502
5697
|
}
|
|
5503
5698
|
patChecksU.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ui}[_i])`)
|
|
@@ -5529,7 +5724,7 @@ function genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5529
5724
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
5530
5725
|
ctx.usesSafeRe = true
|
|
5531
5726
|
} else {
|
|
5532
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`);
|
|
5727
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`);
|
|
5533
5728
|
}
|
|
5534
5729
|
}
|
|
5535
5730
|
const ri = ctx.regExpMap.get(pattern);
|
|
@@ -5559,7 +5754,7 @@ function genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5559
5754
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
5560
5755
|
ctx.usesSafeRe = true
|
|
5561
5756
|
} else {
|
|
5562
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
5757
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
|
|
5563
5758
|
}
|
|
5564
5759
|
}
|
|
5565
5760
|
patTests.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ki})`)
|
|
@@ -5611,7 +5806,7 @@ function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
|
5611
5806
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
5612
5807
|
ctx.usesSafeRe = true
|
|
5613
5808
|
} else {
|
|
5614
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`);
|
|
5809
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`);
|
|
5615
5810
|
}
|
|
5616
5811
|
}
|
|
5617
5812
|
const ri = ctx.regExpMap.get(pattern);
|
|
@@ -5733,8 +5928,11 @@ function errPushC(ctx, keyword, sp, pathExpr, paramsCode, msgCode, valueExpr, le
|
|
|
5733
5928
|
const { errorSite } = require('./enrich-site')
|
|
5734
5929
|
const siteVar = `_S${ctx.varCounter++}`
|
|
5735
5930
|
ctx.closureVars.push(siteVar)
|
|
5736
|
-
|
|
5737
|
-
|
|
5931
|
+
// Inside a shared definition (emitSharedDefC) the site holds the path past
|
|
5932
|
+
// the definition and the reference's own comes in as `_sp`.
|
|
5933
|
+
const rel = sp.startsWith(SP_PLACEHOLDER)
|
|
5934
|
+
ctx.closureVals.push(errorSite(keyword, unescapeSp(rel ? sp.slice(SP_PLACEHOLDER.length) : sp), paramsVal, fmt))
|
|
5935
|
+
return `(_e||(_e=[])).push(_mk(${siteVar},${pathExpr || '""'},${paramsShared || paramsCode},${msgCode},${valueExpr},d${rel ? ',_sp' : ''}))`
|
|
5738
5936
|
}
|
|
5739
5937
|
|
|
5740
5938
|
function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts) {
|
|
@@ -5743,15 +5941,12 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5743
5941
|
// Same provably-local rule as the error generator above.
|
|
5744
5942
|
if (typeof schema === 'object' && schema !== null) {
|
|
5745
5943
|
const s = JSON.stringify(schema)
|
|
5746
|
-
// unevaluatedProperties that
|
|
5747
|
-
//
|
|
5748
|
-
|
|
5749
|
-
//
|
|
5750
|
-
|
|
5751
|
-
//
|
|
5752
|
-
// errors generators; this generator has no preamble for them, so it
|
|
5753
|
-
// declines and the router uses the errors generator.
|
|
5754
|
-
if (cyclicDefNames(schema.$defs || schema.definitions).size) return null
|
|
5944
|
+
// unevaluatedProperties and unevaluatedItems that are not provably local
|
|
5945
|
+
// are worked out at run time (genUnevaluatedDynamicC,
|
|
5946
|
+
// genUnevaluatedItemsDynamicC).
|
|
5947
|
+
// A schema that references itself, through `$ref: "#"` or a definition on
|
|
5948
|
+
// a cycle, has those references written as calls under the cycle guard
|
|
5949
|
+
// (emitDefFnC).
|
|
5755
5950
|
// Bail on oneOf/anyOf — branch-collapse aggregation lives in genCodeE only.
|
|
5756
5951
|
// Letting genCodeC emit the legacy "must match exactly one" placeholder
|
|
5757
5952
|
// would short-circuit the ATA4001/ATA4002/ATA4003 contract. The boolean
|
|
@@ -5775,7 +5970,6 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5775
5970
|
if (!sharedCodegenGate(schema, schemaMap)) return null
|
|
5776
5971
|
if (schema.patternProperties) {
|
|
5777
5972
|
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
5778
|
-
if (/\\[pP]\{/.test(pat)) return null
|
|
5779
5973
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
|
|
5780
5974
|
}
|
|
5781
5975
|
}
|
|
@@ -5825,7 +6019,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5825
6019
|
if (needsBaseTracking(schema, schemaMap, new Set())) return null
|
|
5826
6020
|
|
|
5827
6021
|
// runtimeShape: errors without the ordinal, see compileToJSCodegenWithErrors.
|
|
5828
|
-
const ctx = { noOrdinal: !!(opts && opts.runtimeShape), rich: !!(opts && opts.rich), inCombined: true, varCounter: 0, helperCode: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen],
|
|
6022
|
+
const ctx = { noOrdinal: !!(opts && opts.runtimeShape), rich: !!(opts && opts.rich), inCombined: true, hoisted: [], rootC: schema, varCounter: 0, helperCode: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen],
|
|
5829
6023
|
rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: inputSchema, userFormats: userFormats || null }
|
|
5830
6024
|
const lines = []
|
|
5831
6025
|
try {
|
|
@@ -5851,10 +6045,15 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5851
6045
|
const closureParams = closureNames.join(',')
|
|
5852
6046
|
// A definition the error generator wrote as a named function inside a
|
|
5853
6047
|
// branch (one on a cycle, or referenced from several places) reads the
|
|
5854
|
-
// cycle-guard state
|
|
5855
|
-
// the
|
|
5856
|
-
//
|
|
5857
|
-
|
|
6048
|
+
// cycle-guard state the error function declares. Here that state is
|
|
6049
|
+
// declared too, and the checks run as the error function runs them: in a
|
|
6050
|
+
// body of their own, again in the guarded mode when data turns out to
|
|
6051
|
+
// point back at itself. This used to decline, which left every schema
|
|
6052
|
+
// whose oneOf or anyOf branches reach a shared definition without one
|
|
6053
|
+
// pass.
|
|
6054
|
+
const guardList = [...(ctx.defFns ? ctx.defFns.values() : []), ...(ctx.cycFnsC || [])]
|
|
6055
|
+
const guardDefs = guardList.length ? guardList : null
|
|
6056
|
+
if (guardDefs) ctx.helperCode.unshift(emitGuardState())
|
|
5858
6057
|
// Collapsed oneOf/anyOf: the helper the error generator embeds, `_all` for
|
|
5859
6058
|
// the branch functions (this function collects everything), and the
|
|
5860
6059
|
// runtime shape the error generator gives its literals, without code and
|
|
@@ -5865,12 +6064,23 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5865
6064
|
for (let i = 0; i < ctx.helperCode.length; i++) ctx.helperCode[i] = shape(ctx.helperCode[i])
|
|
5866
6065
|
for (let i = 0; i < lines.length; i++) lines[i] = shape(lines[i])
|
|
5867
6066
|
}
|
|
5868
|
-
const helpers = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
|
|
5869
6067
|
// Lazy error array: no allocation for valid data (the common case)
|
|
5870
6068
|
// resultShape: validate() itself for a validator whose errors are being read
|
|
5871
6069
|
// (see validator-core): the result objects built here, so one pass decides,
|
|
5872
6070
|
// collects and answers, with nothing between the caller and the checks.
|
|
5873
6071
|
const rs = opts && opts.resultShape
|
|
6072
|
+
// Counts shared between a node's unevaluatedProperties and its anyOf and
|
|
6073
|
+
// oneOf (genUnevaluatedDynamicC), visible to every line of the function.
|
|
6074
|
+
let hoistDecl = ctx.hoisted.length ? `let ${ctx.hoisted.map((n) => n + '=null').join(',')};` : ''
|
|
6075
|
+
let checks = lines.join('\n ')
|
|
6076
|
+
let allDecl = ctx.usesBranchCollapse ? 'const _all=true;' : ''
|
|
6077
|
+
if (guardDefs) {
|
|
6078
|
+
const clears = ['_stk.clear()'].concat(guardDefs.map((n) => `${n}_s.clear()`)).join(';')
|
|
6079
|
+
ctx.helperCode.push(`function _bodyC(d){${hoistDecl}${allDecl}let _e;\n ${checks}\n return _e}`)
|
|
6080
|
+
hoistDecl = ''
|
|
6081
|
+
allDecl = ''
|
|
6082
|
+
checks = `_sd=0;try{_e=_bodyC(d)}catch(_err){if(_err!==_CYC)throw _err;_sg=true;${clears};try{_e=_bodyC(d)}finally{_sg=false}}`
|
|
6083
|
+
}
|
|
5874
6084
|
// In the result shape this function is validate() itself, so a document
|
|
5875
6085
|
// that makes it throw is answered by the caller's previous path instead
|
|
5876
6086
|
// (`_FB`). A thrown error reaching the caller is the one thing this form
|
|
@@ -5879,12 +6089,16 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5879
6089
|
// With collapsed branches, collecting runs every branch of every oneOf
|
|
5880
6090
|
// and anyOf in full, which an accepted document does not need: the
|
|
5881
6091
|
// verdict answers it first.
|
|
5882
|
-
|
|
6092
|
+
// Not when every collapse is guarded by a count (emitBranchCollapse): an
|
|
6093
|
+
// accepted document then runs no branch twice, and a rejected one would
|
|
6094
|
+
// pay for the verdict for nothing.
|
|
6095
|
+
? `try{${hoistDecl}${ctx.usesBranchCollapse && ctx.unguardedCollapse ? 'if(_VF!==null&&_VF(d))return{valid:true,data:d,errors:_EE};' : ''}${allDecl}let _e;\n ` + checks +
|
|
5883
6096
|
// A collapsed branch error is presented even alone (its ordinal and
|
|
5884
6097
|
// raw branch errors stay inside); otherwise only a list of two or more
|
|
5885
6098
|
// needs ordering.
|
|
5886
6099
|
`\n return _e?new _ER(${ctx.usesBranchCollapse ? '_SRT(_e)' : '_e.length>1?_SRT(_e):_e'}):{valid:true,data:d,errors:_EE}}catch(_x){return _FB(d)}`
|
|
5887
|
-
:
|
|
6100
|
+
: hoistDecl + allDecl + `let _e;\n ` + checks + `\n return _e?{valid:false,errors:_e}:R`
|
|
6101
|
+
const helpers = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
|
|
5888
6102
|
|
|
5889
6103
|
try {
|
|
5890
6104
|
if (typeof process !== 'undefined' && process.env && process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + helpers + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
|
|
@@ -5924,6 +6138,9 @@ function typeMismatchRest(schema, types) {
|
|
|
5924
6138
|
rest[k] = schema[k]
|
|
5925
6139
|
}
|
|
5926
6140
|
if (rest === null) return null
|
|
6141
|
+
// A node whose reference was already written (genCodeCNode) keeps that
|
|
6142
|
+
// mark, or the reference is written again here and its errors twice.
|
|
6143
|
+
if (schema[REF_DONE]) Object.defineProperty(rest, REF_DONE, { value: true })
|
|
5927
6144
|
for (const k of Object.keys(rest)) if (!NOT_VALIDATING.has(k) && !k.startsWith('x-')) return rest
|
|
5928
6145
|
return null
|
|
5929
6146
|
}
|
|
@@ -5931,6 +6148,68 @@ function typeMismatchRest(schema, types) {
|
|
|
5931
6148
|
function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5932
6149
|
return withPlain(schema, v, lines, ctx, () => genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix))
|
|
5933
6150
|
}
|
|
6151
|
+
// A definition referenced from several places, written once as a function
|
|
6152
|
+
// and called at each reference, instead of its whole code at every one: the
|
|
6153
|
+
// combined function of SchemaStore's cloud-run (48 KB of schema) came to
|
|
6154
|
+
// 1.1 MB of source and sarif's (79 KB) to 2.8 MB, and compiling those was most
|
|
6155
|
+
// of the 50 to 300 ms a first rejection with its errors read took. The body is
|
|
6156
|
+
// generated against a placeholder schema path that becomes the `_sp` argument,
|
|
6157
|
+
// as the error generator does (genCodeENode), so an error names the path it
|
|
6158
|
+
// was reached by. It collects into its own list, returned to the caller, and
|
|
6159
|
+
// declares its own branch counts (genUnevaluatedDynamicC). The document root
|
|
6160
|
+
// comes in as `d` for the rich errors that read it. Not for a definition on a
|
|
6161
|
+
// reference cycle, which keeps its existing path. False when not shared.
|
|
6162
|
+
function emitSharedDefC(defName, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
6163
|
+
if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
|
|
6164
|
+
if (!ctx.sharedDefs) ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs)
|
|
6165
|
+
const cyclic = ctx.cyclicDefs.has(defName)
|
|
6166
|
+
if (!cyclic && !ctx.sharedDefs.has(defName)) return false
|
|
6167
|
+
emitDefFnC(defName, ctx.rootDefs[defName], cyclic, v, pathExpr, lines, ctx, schemaPrefix)
|
|
6168
|
+
return true
|
|
6169
|
+
}
|
|
6170
|
+
|
|
6171
|
+
// One schema as a function of its own in the combined function (see
|
|
6172
|
+
// emitSharedDefC). The name is registered before the body is written, so a
|
|
6173
|
+
// reference back to it inside becomes a call. One that can re-enter itself,
|
|
6174
|
+
// a definition on a cycle or the root through `$ref: "#"`, goes through the
|
|
6175
|
+
// cycle guard the error generator's definitions use: depth counted, and in
|
|
6176
|
+
// the guarded pass a value already being checked is not checked again.
|
|
6177
|
+
function emitDefFnC(key, target, cyclic, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
6178
|
+
if (!ctx.defFnsC) ctx.defFnsC = new Map()
|
|
6179
|
+
let fnName = ctx.defFnsC.get(key)
|
|
6180
|
+
if (!fnName) {
|
|
6181
|
+
fnName = '_defC' + ctx.defFnsC.size + '_' + (key === '#' ? 'root' : key.replace(/[^A-Za-z0-9_]/g, '_'))
|
|
6182
|
+
ctx.defFnsC.set(key, fnName)
|
|
6183
|
+
const outerHoisted = ctx.hoisted
|
|
6184
|
+
ctx.hoisted = []
|
|
6185
|
+
const bodyLines = []
|
|
6186
|
+
let hoisted
|
|
6187
|
+
try {
|
|
6188
|
+
genCodeC(target, '_dv', '_p', bodyLines, ctx, SP_PLACEHOLDER)
|
|
6189
|
+
} finally {
|
|
6190
|
+
hoisted = ctx.hoisted
|
|
6191
|
+
ctx.hoisted = outerHoisted
|
|
6192
|
+
}
|
|
6193
|
+
let body = bodyLines.join('\n ')
|
|
6194
|
+
body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
|
|
6195
|
+
if (body.includes(SP_PLACEHOLDER)) throw DECLINE
|
|
6196
|
+
const decl = hoisted.length ? `let ${hoisted.map((n) => n + '=null').join(',')};` : ''
|
|
6197
|
+
const fn = `function ${cyclic ? fnName + '_b' : fnName}(_dv,_p,_sp,d){const _all=true;${decl}let _e;\n ${body}\n return _e}`
|
|
6198
|
+
if (cyclic) {
|
|
6199
|
+
if (!ctx.cycFnsC) ctx.cycFnsC = []
|
|
6200
|
+
ctx.cycFnsC.push(fnName)
|
|
6201
|
+
ctx.helperCode.push(
|
|
6202
|
+
`const ${fnName}_s=new Set()\n function ${fnName}(_dv,_p,_sp,d){\n ` +
|
|
6203
|
+
`if(_sg){if(typeof _dv!=='object'||_dv===null)return ${fnName}_b(_dv,_p,_sp,d);if(${fnName}_s.has(_dv))return;${fnName}_s.add(_dv);try{return ${fnName}_b(_dv,_p,_sp,d)}finally{${fnName}_s.delete(_dv)}}\n ` +
|
|
6204
|
+
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(_dv,_p,_sp,d)\n _sd--\n return _r\n }\n ` + fn)
|
|
6205
|
+
} else {
|
|
6206
|
+
ctx.helperCode.push(fn)
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
lines.push(`{const _r=${fnName}(${v},${pathExpr || '""'},'${schemaPrefix}',d);if(_r!==undefined){if(_e===undefined)_e=_r;else for(let _i=0;_i<_r.length;_i++)_e.push(_r[_i])}}`)
|
|
6210
|
+
}
|
|
6211
|
+
|
|
6212
|
+
const REF_DONE = Symbol('ata.refDone')
|
|
5934
6213
|
function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5935
6214
|
if (!schemaPrefix) schemaPrefix = '#'
|
|
5936
6215
|
// Per-node, for the reason spelled out in genCode.
|
|
@@ -5945,18 +6224,34 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5945
6224
|
if (typeof schema !== 'object' || schema === null) return
|
|
5946
6225
|
|
|
5947
6226
|
// $ref — resolve local, anchor, and cross-schema refs
|
|
5948
|
-
if (schema.$ref) {
|
|
5949
|
-
//
|
|
5950
|
-
//
|
|
5951
|
-
//
|
|
5952
|
-
//
|
|
5953
|
-
//
|
|
5954
|
-
|
|
5955
|
-
//
|
|
5956
|
-
|
|
6227
|
+
if (schema.$ref && !schema[REF_DONE]) {
|
|
6228
|
+
// A keyword beside $ref that validates applies too (2020-12; draft 7
|
|
6229
|
+
// drops such siblings when the schema is read). The reference is written
|
|
6230
|
+
// on its own, then the node again with the reference marked done, on a
|
|
6231
|
+
// fresh copy so the mark cannot reach the same schema object met through
|
|
6232
|
+
// another path; the copy keeps `$ref` for unevaluatedProperties and
|
|
6233
|
+
// unevaluatedItems, which read its annotations. Dropping the siblings
|
|
6234
|
+
// once accepted what they reject, which is why this declined. Not in a
|
|
6235
|
+
// branch function, which the error generator writes.
|
|
6236
|
+
if (Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))) {
|
|
6237
|
+
if (!ctx.inCombined || ctx.inBranchFn) throw DECLINE
|
|
6238
|
+
genCodeCNode({ $ref: schema.$ref }, v, pathExpr, lines, ctx, schemaPrefix)
|
|
6239
|
+
const rest = Object.assign({}, schema)
|
|
6240
|
+
Object.defineProperty(rest, REF_DONE, { value: true })
|
|
6241
|
+
genCodeCNode(rest, v, pathExpr, lines, ctx, schemaPrefix)
|
|
6242
|
+
return
|
|
6243
|
+
}
|
|
6244
|
+
// A reference back into what is being expanded is a call to that
|
|
6245
|
+
// schema's function (emitDefFnC), never nothing: an expansion that stops
|
|
6246
|
+
// and emits no lines reads as valid.
|
|
6247
|
+
if (schema.$ref === '#') {
|
|
6248
|
+
if (ctx.inCombined && !ctx.inBranchFn && ctx.rootC) { emitDefFnC('#', ctx.rootC, true, v, pathExpr, lines, ctx, schemaPrefix); return }
|
|
6249
|
+
throw DECLINE
|
|
6250
|
+
}
|
|
5957
6251
|
const m = schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
|
|
5958
6252
|
if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
|
|
5959
|
-
if (ctx.
|
|
6253
|
+
if (ctx.inCombined && !ctx.inBranchFn && emitSharedDefC(m[1], v, pathExpr, lines, ctx, schemaPrefix)) return
|
|
6254
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5960
6255
|
ctx.refStack.add(schema.$ref)
|
|
5961
6256
|
genCodeC(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx, schemaPrefix)
|
|
5962
6257
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -5967,7 +6262,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5967
6262
|
const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
|
|
5968
6263
|
const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
|
|
5969
6264
|
if (anchorTarget) {
|
|
5970
|
-
if (ctx.refStack.has(schema.$ref))
|
|
6265
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5971
6266
|
ctx.refStack.add(schema.$ref)
|
|
5972
6267
|
genCodeC(anchorTarget, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5973
6268
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -5975,7 +6270,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5975
6270
|
}
|
|
5976
6271
|
}
|
|
5977
6272
|
if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
|
|
5978
|
-
if (ctx.refStack.has(schema.$ref))
|
|
6273
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5979
6274
|
ctx.refStack.add(schema.$ref)
|
|
5980
6275
|
genCodeC(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
|
|
5981
6276
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -5985,7 +6280,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5985
6280
|
if (ctx.schemaMap && schema.$ref.includes('#') && !schema.$ref.startsWith('#')) {
|
|
5986
6281
|
const r = resolveCrossSchemaRef(schema.$ref, ctx.schemaMap)
|
|
5987
6282
|
if (r) {
|
|
5988
|
-
if (ctx.refStack.has(schema.$ref))
|
|
6283
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5989
6284
|
ctx.refStack.add(schema.$ref)
|
|
5990
6285
|
genCodeC(r.schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5991
6286
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -6201,7 +6496,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6201
6496
|
const ri = ctx.varCounter++
|
|
6202
6497
|
const reVar = `_re${ri}`
|
|
6203
6498
|
ctx.closureVars.push(reVar)
|
|
6204
|
-
ctx.closureVals.push(useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern))
|
|
6499
|
+
ctx.closureVals.push(useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern, reFlags(schema.pattern)))
|
|
6205
6500
|
const c = isStr ? `!${reVar}.test(${v})` : `typeof ${v}==='string'&&!${reVar}.test(${v})`
|
|
6206
6501
|
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
|
|
6207
6502
|
}
|
|
@@ -6297,7 +6592,10 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6297
6592
|
// unevaluatedItems: false under the same rule: one error for the array,
|
|
6298
6593
|
// the first uncovered position as the limit. Adjacent schema-form `items`
|
|
6299
6594
|
// evaluates everything; the draft-07 array form acts as the prefix.
|
|
6300
|
-
if (schema.unevaluatedItems
|
|
6595
|
+
if (schema.unevaluatedItems !== undefined && schema.unevaluatedItems !== true) {
|
|
6596
|
+
if (unevalLocalOk(schema, 'unevaluatedItems')) genUnevaluatedItemsFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
6597
|
+
else genUnevaluatedItemsDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
6598
|
+
}
|
|
6301
6599
|
|
|
6302
6600
|
// dependentRequired
|
|
6303
6601
|
if (schema.dependentRequired) {
|
|
@@ -6447,17 +6745,17 @@ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj) {
|
|
|
6447
6745
|
if (schema.additionalProperties !== undefined) return
|
|
6448
6746
|
const ui = ctx.varCounter++
|
|
6449
6747
|
const S = `_ev${ui}`
|
|
6450
|
-
const body = [`const ${S}=
|
|
6748
|
+
const body = [`const ${S}=[]`]
|
|
6451
6749
|
addLocalAnnotations(schema, v, S, body, ctx)
|
|
6452
|
-
|
|
6750
|
+
addTopApplicatorAnnotations(schema, v, S, body, ctx)
|
|
6453
6751
|
const kVar = `_uk${ui}`
|
|
6454
6752
|
const up = schema.unevaluatedProperties
|
|
6455
6753
|
if (up === false) {
|
|
6456
|
-
body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.
|
|
6754
|
+
body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar}))return false`)
|
|
6457
6755
|
} else if (typeof up === 'object' && up !== null) {
|
|
6458
6756
|
const sub = []
|
|
6459
6757
|
genCode(up, `${v}[${kVar}]`, sub, ctx)
|
|
6460
|
-
if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.
|
|
6758
|
+
if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${sub.join('\n ')}\n }`)
|
|
6461
6759
|
} else {
|
|
6462
6760
|
throw DECLINE
|
|
6463
6761
|
}
|
|
@@ -6470,36 +6768,56 @@ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj) {
|
|
|
6470
6768
|
// count whether or not their values pass; an allOf, anyOf or dependentSchemas
|
|
6471
6769
|
// branch, an if, then or else, or a $ref target counts only when it passes,
|
|
6472
6770
|
// a oneOf only when exactly one branch passes, and `not` never. Each branch is
|
|
6473
|
-
// asked through an annotation function (emitAnnot)
|
|
6474
|
-
// only then adds what it evaluated, so nothing has to be rolled back.
|
|
6771
|
+
// asked through an annotation function (emitAnnot).
|
|
6475
6772
|
function genUnevaluatedDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
|
|
6476
6773
|
// additionalProperties evaluates every key the others do not, pass or fail.
|
|
6477
6774
|
if (schema.additionalProperties !== undefined) return
|
|
6478
6775
|
const ui = ctx.varCounter++
|
|
6479
6776
|
const S = `_ev${ui}`
|
|
6480
|
-
const body = [`const ${S}=
|
|
6777
|
+
const body = [`const ${S}=[]`]
|
|
6481
6778
|
addLocalAnnotations(schema, v, S, body, ctx)
|
|
6482
|
-
|
|
6779
|
+
// The anyOf and oneOf counts are handed to the keywords' own error code
|
|
6780
|
+
// (emitBranchCollapse), with the object they were counted for: they are
|
|
6781
|
+
// declared once at the top of the combined function, where every later
|
|
6782
|
+
// line sees them, and read only when that object is the value at hand, so
|
|
6783
|
+
// a count left from another value, another loop iteration or another place
|
|
6784
|
+
// is never taken for this one. Only in the combined function proper.
|
|
6785
|
+
const share = ctx.inCombined && !ctx.inBranchFn && ctx.hoisted !== undefined &&
|
|
6786
|
+
(Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf))
|
|
6787
|
+
const counts = share ? { anyOf: `_ac${ui}`, oneOf: `_oc${ui}`, oneOfMask: `_om${ui}`, of: `_ao${ui}` } : null
|
|
6788
|
+
if (share) {
|
|
6789
|
+
ctx.hoisted.push(counts.anyOf, counts.oneOf, counts.oneOfMask, counts.of)
|
|
6790
|
+
body.push(`${counts.of}=${v};${counts.oneOfMask}=0`)
|
|
6791
|
+
if (!ctx.branchCounts) ctx.branchCounts = new Map()
|
|
6792
|
+
if (Array.isArray(schema.anyOf)) ctx.branchCounts.set(schema.anyOf, { of: counts.of, count: counts.anyOf, mask: null })
|
|
6793
|
+
if (Array.isArray(schema.oneOf)) ctx.branchCounts.set(schema.oneOf, { of: counts.of, count: counts.oneOf, mask: schema.oneOf.length <= 30 ? counts.oneOfMask : null })
|
|
6794
|
+
}
|
|
6795
|
+
addTopApplicatorAnnotations(schema, v, S, body, ctx, counts)
|
|
6483
6796
|
const kVar = `_uk${ui}`
|
|
6484
6797
|
const up = schema.unevaluatedProperties
|
|
6485
6798
|
if (up === false) {
|
|
6486
|
-
body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.
|
|
6799
|
+
body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:${kVar}}`, "'must NOT have unevaluated properties'")}}`)
|
|
6487
6800
|
} else if (typeof up === 'object' && up !== null) {
|
|
6488
6801
|
const pe = emitPtrEsc(ctx)
|
|
6489
6802
|
const p = pathExpr ? `${pathExpr}+'/'+${pe}(${kVar})` : `'/'+${pe}(${kVar})`
|
|
6490
6803
|
const sub = []
|
|
6491
6804
|
genCodeC(up, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/unevaluatedProperties')
|
|
6492
|
-
if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.
|
|
6805
|
+
if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${sub.join('\n ')}\n }`)
|
|
6493
6806
|
} else {
|
|
6494
6807
|
throw DECLINE
|
|
6495
6808
|
}
|
|
6496
6809
|
lines.push(isObj ? `{${body.join(';')}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body.join(';')}}`)
|
|
6497
6810
|
}
|
|
6498
6811
|
|
|
6499
|
-
// The names a node's own keywords evaluate,
|
|
6500
|
-
// properties, keys matching a pattern
|
|
6812
|
+
// The names a node's own keywords evaluate, pushed onto the array `S`:
|
|
6813
|
+
// present declared properties, keys matching a pattern, and every key when
|
|
6814
|
+
// additionalProperties is there.
|
|
6501
6815
|
function addLocalAnnotations(node, v, S, out, ctx) {
|
|
6502
|
-
|
|
6816
|
+
if (node.additionalProperties !== undefined) {
|
|
6817
|
+
out.push(`for(const _ka in ${v})${S}.push(_ka)`)
|
|
6818
|
+
return
|
|
6819
|
+
}
|
|
6820
|
+
for (const k of Object.keys(node.properties || {})) out.push(`if(Object.prototype.hasOwnProperty.call(${v},${JSON.stringify(k)}))${S}.push(${JSON.stringify(k)})`)
|
|
6503
6821
|
const pats = Object.keys(node.patternProperties || {})
|
|
6504
6822
|
if (pats.length) {
|
|
6505
6823
|
const tests = []
|
|
@@ -6512,24 +6830,31 @@ function addLocalAnnotations(node, v, S, out, ctx) {
|
|
|
6512
6830
|
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
6513
6831
|
tests.push(`_re${ri}.test(${kv})`)
|
|
6514
6832
|
}
|
|
6515
|
-
out.push(`for(const ${kv} in ${v})if(${tests.join('||')})${S}.
|
|
6833
|
+
out.push(`for(const ${kv} in ${v})if(${tests.join('||')})${S}.push(${kv})`)
|
|
6516
6834
|
}
|
|
6517
6835
|
}
|
|
6518
6836
|
|
|
6519
|
-
// What
|
|
6520
|
-
//
|
|
6521
|
-
//
|
|
6522
|
-
|
|
6837
|
+
// What the applicators of the node that owns the unevaluatedProperties add.
|
|
6838
|
+
// Their validity is checked by the code around this; here a branch only
|
|
6839
|
+
// contributes, through its annotation function, when it passes, and a oneOf
|
|
6840
|
+
// only with exactly one passing branch. An annotation function that fails
|
|
6841
|
+
// leaves `S` as it found it.
|
|
6842
|
+
function addTopApplicatorAnnotations(node, v, S, out, ctx, counts) {
|
|
6843
|
+
if (node.$dynamicRef !== undefined || node.$recursiveRef !== undefined || node.dependencies !== undefined) throw DECLINE
|
|
6523
6844
|
for (const b of node.allOf || []) out.push(`${emitAnnot(b, ctx)}(${v},${S})`)
|
|
6524
|
-
|
|
6845
|
+
// With `counts`, the passing branches of anyOf and oneOf are counted into
|
|
6846
|
+
// variables the caller declared, so the keyword's own error code further
|
|
6847
|
+
// down can skip its branches when the count already says it holds (see
|
|
6848
|
+
// emitBranchCollapse).
|
|
6849
|
+
if (Array.isArray(node.anyOf)) {
|
|
6850
|
+
if (counts) out.push(`${counts.anyOf}=0;${node.anyOf.map((b) => `if(${emitAnnot(b, ctx)}(${v},${S}))${counts.anyOf}++;`).join('')}`)
|
|
6851
|
+
else for (const b of node.anyOf) out.push(`${emitAnnot(b, ctx)}(${v},${S})`)
|
|
6852
|
+
}
|
|
6525
6853
|
if (Array.isArray(node.oneOf)) {
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
} else {
|
|
6531
|
-
for (const b of node.oneOf) out.push(`${emitAnnot(b, ctx)}(${v},${S})`)
|
|
6532
|
-
}
|
|
6854
|
+
const t = counts ? counts.oneOf : `_oc${ctx.varCounter++}`
|
|
6855
|
+
// Which branches passed, as bits, for a collapse that has to name them.
|
|
6856
|
+
const mask = counts && node.oneOf.length <= 30 ? counts.oneOfMask : null
|
|
6857
|
+
out.push(`{const ${t}m=${S}.length;${counts ? '' : 'let '}${t}=0;${node.oneOf.map((b, i) => `if(${emitAnnot(b, ctx)}(${v},${S})){${t}++;${mask ? `${mask}|=${1 << i};` : ''}}`).join('')}if(${t}!==1)_rb(${S},${t}m)}`)
|
|
6533
6858
|
}
|
|
6534
6859
|
if (node.if !== undefined) {
|
|
6535
6860
|
const thenCall = node.then !== undefined ? `${emitAnnot(node.then, ctx)}(${v},${S})` : ''
|
|
@@ -6540,7 +6865,6 @@ function addApplicatorAnnotations(node, v, S, out, ctx, top) {
|
|
|
6540
6865
|
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})`)
|
|
6541
6866
|
}
|
|
6542
6867
|
if (typeof node.$ref === 'string') out.push(`${emitAnnot(refTargetFor(node.$ref, ctx), ctx)}(${v},${S})`)
|
|
6543
|
-
if (node.$dynamicRef !== undefined || node.$recursiveRef !== undefined || node.dependencies !== undefined) throw DECLINE
|
|
6544
6868
|
}
|
|
6545
6869
|
|
|
6546
6870
|
function refTargetFor(ref, ctx) {
|
|
@@ -6550,44 +6874,236 @@ function refTargetFor(ref, ctx) {
|
|
|
6550
6874
|
return t
|
|
6551
6875
|
}
|
|
6552
6876
|
|
|
6553
|
-
//
|
|
6554
|
-
//
|
|
6877
|
+
// The keywords an annotation function evaluates itself, through its children;
|
|
6878
|
+
// the rest of a subschema is checked by a plain verdict.
|
|
6879
|
+
const ANNOT_APPLICATORS = new Set(['allOf', 'anyOf', 'oneOf', 'if', 'then', 'else', 'dependentSchemas', '$ref', 'not', 'unevaluatedProperties'])
|
|
6880
|
+
|
|
6881
|
+
// A function `(value, S) => valid` for one subschema: whether it passes, and
|
|
6882
|
+
// when it does, what it evaluated pushed onto S; when it does not, S is left
|
|
6883
|
+
// as it was. Validity is worked out bottom up: the subschema's own keywords
|
|
6884
|
+
// by a verdict of the subschema without its applicators, each applicator from
|
|
6885
|
+
// its branches' annotation functions. Asking each branch for a full verdict
|
|
6886
|
+
// and then again for its annotations repeated every nested branch once per
|
|
6887
|
+
// level, which made nested references the slowest shape there was.
|
|
6555
6888
|
function emitAnnot(sub, ctx) {
|
|
6556
6889
|
if (sub === true) return '_anT'
|
|
6557
6890
|
if (sub === false) return '_anF'
|
|
6558
6891
|
if (sub === null || typeof sub !== 'object') throw DECLINE
|
|
6559
6892
|
if (!ctx.annFns) {
|
|
6560
6893
|
ctx.annFns = new Map()
|
|
6561
|
-
;(ctx.preamble || ctx.helperCode).push('function _anT(){return true}function _anF(){return false}')
|
|
6894
|
+
;(ctx.preamble || ctx.helperCode).push('function _anT(){return true}function _anF(){return false}function _rb(S,m){if(S.length!==m)S.length=m}')
|
|
6562
6895
|
}
|
|
6563
6896
|
const hit = ctx.annFns.get(sub)
|
|
6564
6897
|
if (hit !== undefined) {
|
|
6565
6898
|
if (hit === null) throw DECLINE // a reference cycle: not modelled here
|
|
6566
6899
|
return hit
|
|
6567
6900
|
}
|
|
6901
|
+
if (sub.$dynamicRef !== undefined || sub.$recursiveRef !== undefined || sub.dependencies !== undefined) throw DECLINE
|
|
6902
|
+
// unevaluatedItems reads what the applicators evaluate too; the local
|
|
6903
|
+
// verdict would see it without them.
|
|
6904
|
+
if (sub.unevaluatedItems !== undefined) throw DECLINE
|
|
6568
6905
|
ctx.annFns.set(sub, null)
|
|
6569
6906
|
const name = `_an${ctx.varCounter++}`
|
|
6907
|
+
const local = {}
|
|
6908
|
+
for (const k of Object.keys(sub)) if (!ANNOT_APPLICATORS.has(k)) local[k] = sub[k]
|
|
6570
6909
|
const vl = []
|
|
6571
|
-
nestedGenCode(
|
|
6572
|
-
const
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
body.push(`
|
|
6579
|
-
}
|
|
6580
|
-
|
|
6581
|
-
|
|
6910
|
+
nestedGenCode(local, '_av', vl, ctx)
|
|
6911
|
+
const decls = [`function ${name}_v(_av){${vl.join('\n ')}\n return true}`]
|
|
6912
|
+
const body = [`if(!${name}_v(d))return false`]
|
|
6913
|
+
if (sub.not !== undefined) {
|
|
6914
|
+
const nl = []
|
|
6915
|
+
nestedGenCode(sub.not, '_nv', nl, ctx)
|
|
6916
|
+
decls.push(`function ${name}_n(_nv){${nl.join('\n ')}\n return true}`)
|
|
6917
|
+
body.push(`if(${name}_n(d))return false`)
|
|
6918
|
+
}
|
|
6919
|
+
body.push(`const _m=S.length;const _o=typeof d==='object'&&d!==null&&!Array.isArray(d)`)
|
|
6920
|
+
const locals = []
|
|
6921
|
+
addLocalAnnotations(sub, 'd', 'S', locals, ctx)
|
|
6922
|
+
if (locals.length) body.push(`if(_o){${locals.join(';')}}`)
|
|
6923
|
+
const failOut = '{_rb(S,_m);return false}'
|
|
6924
|
+
for (const b of sub.allOf || []) body.push(`if(!${emitAnnot(b, ctx)}(d,S))${failOut}`)
|
|
6925
|
+
if (Array.isArray(sub.anyOf)) body.push(`{let _a=false;${sub.anyOf.map((b) => `if(${emitAnnot(b, ctx)}(d,S))_a=true;`).join('')}if(!_a)${failOut}}`)
|
|
6926
|
+
if (Array.isArray(sub.oneOf)) body.push(`{let _c=0;${sub.oneOf.map((b) => `if(${emitAnnot(b, ctx)}(d,S))_c++;`).join('')}if(_c!==1)${failOut}}`)
|
|
6927
|
+
if (sub.if !== undefined) {
|
|
6928
|
+
const thenCheck = sub.then !== undefined ? `if(!${emitAnnot(sub.then, ctx)}(d,S))${failOut}` : ''
|
|
6929
|
+
const elseCheck = sub.else !== undefined ? `if(!${emitAnnot(sub.else, ctx)}(d,S))${failOut}` : ''
|
|
6930
|
+
body.push(`if(${emitAnnot(sub.if, ctx)}(d,S)){${thenCheck}}else{${elseCheck}}`)
|
|
6931
|
+
}
|
|
6932
|
+
if (sub.dependentSchemas && typeof sub.dependentSchemas === 'object') {
|
|
6933
|
+
for (const [k, ds] of Object.entries(sub.dependentSchemas)) body.push(`if(_o&&Object.prototype.hasOwnProperty.call(d,${JSON.stringify(k)})&&!${emitAnnot(ds, ctx)}(d,S))${failOut}`)
|
|
6934
|
+
}
|
|
6935
|
+
if (typeof sub.$ref === 'string') body.push(`if(!${emitAnnot(refTargetFor(sub.$ref, ctx), ctx)}(d,S))${failOut}`)
|
|
6936
|
+
// An unevaluatedProperties of its own: the keys this subschema evaluated
|
|
6937
|
+
// are the ones pushed since _m; what is left must pass it, and then every
|
|
6938
|
+
// key is evaluated.
|
|
6939
|
+
const up = sub.unevaluatedProperties
|
|
6940
|
+
if (up !== undefined && up !== true && sub.additionalProperties === undefined) {
|
|
6941
|
+
if (up === false) {
|
|
6942
|
+
body.push(`if(_o){for(const _k of Object.keys(d))if(S.indexOf(_k,_m)<0)${failOut}}`)
|
|
6943
|
+
} else if (typeof up === 'object' && up !== null) {
|
|
6944
|
+
const ul = []
|
|
6945
|
+
nestedGenCode(up, '_uv', ul, ctx)
|
|
6946
|
+
decls.push(`function ${name}_u(_uv){${ul.join('\n ')}\n return true}`)
|
|
6947
|
+
body.push(`if(_o){for(const _k of Object.keys(d))if(S.indexOf(_k,_m)<0&&!${name}_u(d[_k]))${failOut}}`)
|
|
6948
|
+
} else {
|
|
6949
|
+
throw DECLINE
|
|
6950
|
+
}
|
|
6582
6951
|
}
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
;(ctx.preamble || ctx.helperCode).push(`function ${name}_v(_av){${vl.join('\n ')}\n return true}function ${name}(d,S){if(!${name}_v(d))return false;if(typeof d!=='object'||d===null||Array.isArray(d))return true;${body.join(';')};return true}`)
|
|
6952
|
+
if (up !== undefined && sub.additionalProperties === undefined) body.push(`if(_o)for(const _ka in d)S.push(_ka)`)
|
|
6953
|
+
decls.push(`function ${name}(d,S){${body.join(';')};return true}`)
|
|
6954
|
+
;(ctx.preamble || ctx.helperCode).push(decls.join(''))
|
|
6587
6955
|
ctx.annFns.set(sub, name)
|
|
6588
6956
|
return name
|
|
6589
6957
|
}
|
|
6590
6958
|
|
|
6959
|
+
// unevaluatedItems worked out at run time, for the combined function. The
|
|
6960
|
+
// evaluated positions of an array are kept as the interpreter keeps them
|
|
6961
|
+
// (lib/plan-compiler.js): `n`, every index below it evaluated, and `x`, the
|
|
6962
|
+
// indexes past it that `contains` matched. This node's own prefixItems,
|
|
6963
|
+
// items and contains mark them whatever their own verdict, and each in-place
|
|
6964
|
+
// subschema marks them only when it passes (emitAnnotI); what is left must
|
|
6965
|
+
// pass unevaluatedItems. `false` reports once, at the first position left,
|
|
6966
|
+
// as the interpreter does.
|
|
6967
|
+
function genUnevaluatedItemsDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
6968
|
+
if (schema.$dynamicRef !== undefined || schema.$recursiveRef !== undefined) throw DECLINE
|
|
6969
|
+
if (Array.isArray(schema.items) || schema.additionalItems !== undefined) throw DECLINE
|
|
6970
|
+
annItemHelpers(ctx)
|
|
6971
|
+
const ui = ctx.varCounter++
|
|
6972
|
+
const S = `_es${ui}`
|
|
6973
|
+
const body = [`const ${S}={n:0,x:null}`]
|
|
6974
|
+
addLocalItemAnnotations(schema, v, S, body, ctx)
|
|
6975
|
+
for (const b of schema.allOf || []) body.push(`${emitAnnotI(b, ctx)}(${v},${S})`)
|
|
6976
|
+
if (Array.isArray(schema.anyOf)) for (const b of schema.anyOf) body.push(`${emitAnnotI(b, ctx)}(${v},${S})`)
|
|
6977
|
+
if (Array.isArray(schema.oneOf)) {
|
|
6978
|
+
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)}`)
|
|
6979
|
+
}
|
|
6980
|
+
if (schema.if !== undefined) {
|
|
6981
|
+
const thenCall = schema.then !== undefined ? `${emitAnnotI(schema.then, ctx)}(${v},${S})` : ''
|
|
6982
|
+
const elseCall = schema.else !== undefined ? `${emitAnnotI(schema.else, ctx)}(${v},${S})` : ''
|
|
6983
|
+
body.push(`if(${emitAnnotI(schema.if, ctx)}(${v},${S})){${thenCall}}else{${elseCall}}`)
|
|
6984
|
+
}
|
|
6985
|
+
if (typeof schema.$ref === 'string') body.push(`${emitAnnotI(refTargetFor(schema.$ref, ctx), ctx)}(${v},${S})`)
|
|
6986
|
+
const iv = `_ix${ui}`
|
|
6987
|
+
const ui2 = schema.unevaluatedItems
|
|
6988
|
+
if (ui2 === false) {
|
|
6989
|
+
const failI = fail('unevaluatedItems', 'unevaluatedItems', `{limit:${iv}}`, `'must NOT have more than '+${iv}+' items'`)
|
|
6990
|
+
body.push(`for(let ${iv}=0;${iv}<${v}.length;${iv}++){if(_hI(${S},${iv}))continue;${failI};break}`)
|
|
6991
|
+
} else if (typeof ui2 === 'object' && ui2 !== null) {
|
|
6992
|
+
const pe = pathExpr ? `${pathExpr}+'/'+${iv}` : `'/'+${iv}`
|
|
6993
|
+
const sub = []
|
|
6994
|
+
genCodeC(ui2, `${v}[${iv}]`, pe, sub, ctx, schemaPrefix + '/unevaluatedItems')
|
|
6995
|
+
if (sub.length) body.push(`for(let ${iv}=0;${iv}<${v}.length;${iv}++){if(_hI(${S},${iv}))continue;${sub.join('\n ')}\n }`)
|
|
6996
|
+
} else {
|
|
6997
|
+
throw DECLINE
|
|
6998
|
+
}
|
|
6999
|
+
lines.push(`if(Array.isArray(${v})){${body.join(';')}}`)
|
|
7000
|
+
}
|
|
7001
|
+
|
|
7002
|
+
function annItemHelpers(ctx) {
|
|
7003
|
+
if (ctx.annItemHelpers) return
|
|
7004
|
+
ctx.annItemHelpers = true
|
|
7005
|
+
;(ctx.preamble || ctx.helperCode).push(
|
|
7006
|
+
'function _aI(S,i){if(i<S.n)return;if(i===S.n){S.n=i+1;return}(S.x||(S.x=[])).push(i)}' +
|
|
7007
|
+
'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)}' +
|
|
7008
|
+
'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}' +
|
|
7009
|
+
'function _rbI(S,n,l){S.n=n;if(S.x!==null)S.x.length=l}' +
|
|
7010
|
+
'function _anIT(){return true}function _anIF(){return false}')
|
|
7011
|
+
}
|
|
7012
|
+
|
|
7013
|
+
// The positions a node's own keywords evaluate, marked on `S`, for an array.
|
|
7014
|
+
function addLocalItemAnnotations(node, v, S, out, ctx) {
|
|
7015
|
+
if (Array.isArray(node.items) || node.additionalItems !== undefined) throw DECLINE
|
|
7016
|
+
const prefix = Array.isArray(node.prefixItems) ? node.prefixItems.length : 0
|
|
7017
|
+
if (prefix) out.push(`_mrI(${S},0,Math.min(${v}.length,${prefix}))`)
|
|
7018
|
+
if (node.items !== undefined) out.push(`_mrI(${S},${prefix},${v}.length)`)
|
|
7019
|
+
if (node.contains !== undefined) {
|
|
7020
|
+
const cl = []
|
|
7021
|
+
nestedGenCode(node.contains, '_cv', cl, ctx)
|
|
7022
|
+
const ci = ctx.varCounter++
|
|
7023
|
+
;(ctx.preamble || ctx.helperCode).push(`function _cI${ci}(_cv){${cl.join('\n ')}\n return true}`)
|
|
7024
|
+
out.push(`for(let _i=0;_i<${v}.length;_i++)if(_cI${ci}(${v}[_i]))_aI(${S},_i)`)
|
|
7025
|
+
}
|
|
7026
|
+
}
|
|
7027
|
+
|
|
7028
|
+
// The keywords an item annotation function evaluates through its children;
|
|
7029
|
+
// the rest of a subschema is checked by a plain verdict.
|
|
7030
|
+
const ANNOT_ITEM_APPLICATORS = new Set(['allOf', 'anyOf', 'oneOf', 'if', 'then', 'else', '$ref', 'not', 'unevaluatedItems'])
|
|
7031
|
+
|
|
7032
|
+
// A function `(value, S) => valid` for one subschema, marking on S the array
|
|
7033
|
+
// positions it evaluated when it passes, and leaving S as it was when it does
|
|
7034
|
+
// not. A subschema with an unevaluatedItems of its own evaluates every
|
|
7035
|
+
// position once that passes, so it works on a record of its own and then
|
|
7036
|
+
// marks them all.
|
|
7037
|
+
function emitAnnotI(sub, ctx) {
|
|
7038
|
+
if (sub === true) return '_anIT'
|
|
7039
|
+
if (sub === false) return '_anIF'
|
|
7040
|
+
if (sub === null || typeof sub !== 'object') throw DECLINE
|
|
7041
|
+
if (!ctx.annFnsI) ctx.annFnsI = new Map()
|
|
7042
|
+
const hit = ctx.annFnsI.get(sub)
|
|
7043
|
+
if (hit !== undefined) {
|
|
7044
|
+
if (hit === null) throw DECLINE
|
|
7045
|
+
return hit
|
|
7046
|
+
}
|
|
7047
|
+
if (sub.$dynamicRef !== undefined || sub.$recursiveRef !== undefined) throw DECLINE
|
|
7048
|
+
if (Array.isArray(sub.items) || sub.additionalItems !== undefined) throw DECLINE
|
|
7049
|
+
// unevaluatedProperties reads the applicators too, which the local verdict
|
|
7050
|
+
// below leaves out.
|
|
7051
|
+
if (sub.unevaluatedProperties !== undefined && sub.unevaluatedProperties !== true) throw DECLINE
|
|
7052
|
+
ctx.annFnsI.set(sub, null)
|
|
7053
|
+
annItemHelpers(ctx)
|
|
7054
|
+
const name = `_ai${ctx.varCounter++}`
|
|
7055
|
+
const local = {}
|
|
7056
|
+
for (const k of Object.keys(sub)) if (!ANNOT_ITEM_APPLICATORS.has(k)) local[k] = sub[k]
|
|
7057
|
+
const vl = []
|
|
7058
|
+
nestedGenCode(local, '_av', vl, ctx)
|
|
7059
|
+
const decls = [`function ${name}_v(_av){${vl.join('\n ')}\n return true}`]
|
|
7060
|
+
const body = [`if(!${name}_v(d))return false`]
|
|
7061
|
+
if (sub.not !== undefined) {
|
|
7062
|
+
const nl = []
|
|
7063
|
+
nestedGenCode(sub.not, '_nv', nl, ctx)
|
|
7064
|
+
decls.push(`function ${name}_n(_nv){${nl.join('\n ')}\n return true}`)
|
|
7065
|
+
body.push(`if(${name}_n(d))return false`)
|
|
7066
|
+
}
|
|
7067
|
+
const own = sub.unevaluatedItems !== undefined && sub.unevaluatedItems !== true
|
|
7068
|
+
const R = own ? '_R' : 'S'
|
|
7069
|
+
body.push(own ? `const _R={n:0,x:null}` : `const _n0=S.n,_x0=S.x===null?0:S.x.length`)
|
|
7070
|
+
const failOut = own ? 'return false' : '{_rbI(S,_n0,_x0);return false}'
|
|
7071
|
+
const locals = []
|
|
7072
|
+
addLocalItemAnnotations(sub, 'd', R, locals, ctx)
|
|
7073
|
+
if (locals.length) body.push(`if(Array.isArray(d)){${locals.join(';')}}`)
|
|
7074
|
+
for (const b of sub.allOf || []) body.push(`if(!${emitAnnotI(b, ctx)}(d,${R}))${failOut}`)
|
|
7075
|
+
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}}`)
|
|
7076
|
+
if (Array.isArray(sub.oneOf)) body.push(`{let _c=0;${sub.oneOf.map((b) => `if(${emitAnnotI(b, ctx)}(d,${R}))_c++;`).join('')}if(_c!==1)${failOut}}`)
|
|
7077
|
+
if (sub.if !== undefined) {
|
|
7078
|
+
const thenCheck = sub.then !== undefined ? `if(!${emitAnnotI(sub.then, ctx)}(d,${R}))${failOut}` : ''
|
|
7079
|
+
const elseCheck = sub.else !== undefined ? `if(!${emitAnnotI(sub.else, ctx)}(d,${R}))${failOut}` : ''
|
|
7080
|
+
body.push(`if(${emitAnnotI(sub.if, ctx)}(d,${R})){${thenCheck}}else{${elseCheck}}`)
|
|
7081
|
+
}
|
|
7082
|
+
if (typeof sub.$ref === 'string') body.push(`if(!${emitAnnotI(refTargetFor(sub.$ref, ctx), ctx)}(d,${R}))${failOut}`)
|
|
7083
|
+
if (own) {
|
|
7084
|
+
const u = sub.unevaluatedItems
|
|
7085
|
+
if (u === false) {
|
|
7086
|
+
body.push(`if(Array.isArray(d)){for(let _i=0;_i<d.length;_i++)if(!_hI(_R,_i))return false}`)
|
|
7087
|
+
} else if (typeof u === 'object' && u !== null) {
|
|
7088
|
+
const ul = []
|
|
7089
|
+
nestedGenCode(u, '_uv', ul, ctx)
|
|
7090
|
+
decls.push(`function ${name}_u(_uv){${ul.join('\n ')}\n return true}`)
|
|
7091
|
+
body.push(`if(Array.isArray(d)){for(let _i=0;_i<d.length;_i++)if(!_hI(_R,_i)&&!${name}_u(d[_i]))return false}`)
|
|
7092
|
+
} else {
|
|
7093
|
+
throw DECLINE
|
|
7094
|
+
}
|
|
7095
|
+
body.push(`if(Array.isArray(d))_mrI(S,0,d.length)`)
|
|
7096
|
+
}
|
|
7097
|
+
// `unevaluatedItems: true` evaluates every position once the subschema
|
|
7098
|
+
// has passed, with nothing to check; marked last, so a failure above has
|
|
7099
|
+
// nothing of it to undo.
|
|
7100
|
+
if (sub.unevaluatedItems === true) body.push(`if(Array.isArray(d))_mrI(S,0,d.length)`)
|
|
7101
|
+
decls.push(`function ${name}(d,S){${body.join(';')};return true}`)
|
|
7102
|
+
;(ctx.preamble || ctx.helperCode).push(decls.join(''))
|
|
7103
|
+
ctx.annFnsI.set(sub, name)
|
|
7104
|
+
return name
|
|
7105
|
+
}
|
|
7106
|
+
|
|
6591
7107
|
function genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
|
|
6592
7108
|
const propKeysU = Object.keys(schema.properties || {})
|
|
6593
7109
|
const ui = ctx.varCounter++
|