ata-validator 1.32.3 → 1.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +39 -25
- package/lib/clone-emit.js +26 -15
- package/lib/js-compiler.js +1465 -1337
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/lib/js-compiler.js
CHANGED
|
@@ -1002,9 +1002,13 @@ function withPlain (schema, v, lines, ctx, run) {
|
|
|
1002
1002
|
// The same test at run time, for the closure compiler.
|
|
1003
1003
|
const _hop = Object.prototype.hasOwnProperty
|
|
1004
1004
|
const _PN = ({}).__proto__ === Object.prototype
|
|
1005
|
+
// One hasOwnProperty call. From 1.31.2 to 1.33.0 this first tested the name
|
|
1006
|
+
// against Object.prototype's names, then `in`, then read the prototype through
|
|
1007
|
+
// the __proto__ accessor, on every property of every object the eval-free
|
|
1008
|
+
// engine checked: a schema of 14 properties went from 641 to 828 ns a verdict.
|
|
1009
|
+
// hasOwnProperty gives the same answer and V8 inlines it.
|
|
1005
1010
|
function hasOwnKey (d, key) {
|
|
1006
|
-
|
|
1007
|
-
return key in d && ((_PN ? d.__proto__ : Object.getPrototypeOf(d)) === Object.prototype || _hop.call(d, key))
|
|
1011
|
+
return _hop.call(d, key)
|
|
1008
1012
|
}
|
|
1009
1013
|
|
|
1010
1014
|
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'toString', 'valueOf',
|
|
@@ -1961,6 +1965,17 @@ function replaceTopLevel(code, onTrue = 'return R', onFalse = 'return E(d)') {
|
|
|
1961
1965
|
|
|
1962
1966
|
// Returns true if a property sub-schema will generate 2+ lines that each access v,
|
|
1963
1967
|
// meaning a local variable hoist is worthwhile.
|
|
1968
|
+
// A required object or array whose checks read it many times: into a local,
|
|
1969
|
+
// once. Reading \`d["customer"]\` afresh on every line cost a plain order
|
|
1970
|
+
// verdict 42 percent, since a helper call between two reads (a prototype
|
|
1971
|
+
// check, a length count) keeps V8 from reusing the first. Composition keeps
|
|
1972
|
+
// the direct access, the same line needsLocal draws.
|
|
1973
|
+
function isPlainContainer(schema) {
|
|
1974
|
+
if (typeof schema !== 'object' || schema === null) return false
|
|
1975
|
+
if (schema.$ref || schema.$dynamicRef || schema.allOf || schema.anyOf || schema.oneOf || schema.if || schema.not) return false
|
|
1976
|
+
return !!(schema.properties || schema.items || schema.prefixItems)
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1964
1979
|
function needsLocal(schema) {
|
|
1965
1980
|
if (typeof schema !== 'object' || schema === null) return false
|
|
1966
1981
|
// If it has $ref, allOf, anyOf etc., genCode handles it — don't hoist
|
|
@@ -2109,121 +2124,11 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2109
2124
|
// Only when THIS schema has unevaluated keywords directly (not via $ref target)
|
|
2110
2125
|
const hasSiblings = schema.$ref && (schema.unevaluatedProperties !== undefined || schema.unevaluatedItems !== undefined)
|
|
2111
2126
|
if (schema.$ref) {
|
|
2112
|
-
|
|
2113
|
-
if (schema.$ref === '#') {
|
|
2114
|
-
if (ctx.nestedBoolean) throw DECLINE
|
|
2115
|
-
ctx.usesRecursion = true
|
|
2116
|
-
lines.push(`if(!_validate(${v}))return false`)
|
|
2117
|
-
if (!hasSiblings) return
|
|
2118
|
-
}
|
|
2119
|
-
// 1. Local ref
|
|
2120
|
-
const m = schema.$ref !== '#' && schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
|
|
2121
|
-
if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
|
|
2122
|
-
const defName = m[1]
|
|
2123
|
-
if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
|
|
2124
|
-
if (ctx.cyclicDefs.has(defName) && ctx.preamble) {
|
|
2125
|
-
// A def on a cycle is a named function in the preamble, emitted once
|
|
2126
|
-
// and called by name, so recursion terminates the way `$ref: "#"`
|
|
2127
|
-
// does at the root. The name is registered before the body is
|
|
2128
|
-
// generated so a self-reference inside it becomes a call.
|
|
2129
|
-
if (!ctx.defFns) ctx.defFns = new Map()
|
|
2130
|
-
let fnName = ctx.defFns.get(defName)
|
|
2131
|
-
if (!fnName) {
|
|
2132
|
-
fnName = '_def' + ctx.defFns.size + '_' + defName.replace(/[^A-Za-z0-9_]/g, '_')
|
|
2133
|
-
ctx.defFns.set(defName, fnName)
|
|
2134
|
-
const bodyLines = []
|
|
2135
|
-
// The def is compiled as its own function over its own `d`, so the
|
|
2136
|
-
// checks _deferOrInline holds back belong at the end of this body.
|
|
2137
|
-
// The list hangs off ctx, so without the swap they are flushed into
|
|
2138
|
-
// whatever function is compiling, carrying this def's key set.
|
|
2139
|
-
const outerDeferred = ctx.deferredChecks
|
|
2140
|
-
ctx.deferredChecks = null
|
|
2141
|
-
genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
|
|
2142
|
-
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
2143
|
-
ctx.deferredChecks = outerDeferred
|
|
2144
|
-
// Entered through the cycle guard: see emitGuardState. In the fast
|
|
2145
|
-
// pass the wrapper only counts depth; the set is used by the second,
|
|
2146
|
-
// guarded pass that runs when data turns out to point back at
|
|
2147
|
-
// itself.
|
|
2148
|
-
ctx.preamble.push(
|
|
2149
|
-
`const ${fnName}_s=new Set()\n function ${fnName}(d){\n ` +
|
|
2150
|
-
`if(_sg){if(typeof d!=='object'||d===null)return ${fnName}_b(d);if(${fnName}_s.has(d))return true;${fnName}_s.add(d);try{return ${fnName}_b(d)}finally{${fnName}_s.delete(d)}}\n ` +
|
|
2151
|
-
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d)\n _sd--\n return _r\n }\n ` +
|
|
2152
|
-
`function ${fnName}_b(d){${bodyLines.join('\n ')}\n return true}`,
|
|
2153
|
-
)
|
|
2154
|
-
}
|
|
2155
|
-
lines.push(`if(!${fnName}(${v}))return false`)
|
|
2156
|
-
if (!hasSiblings) return
|
|
2157
|
-
} else if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return }
|
|
2158
|
-
else {
|
|
2159
|
-
ctx.refStack.add(schema.$ref)
|
|
2160
|
-
genCode(ctx.rootDefs[defName], v, lines, ctx, knownType)
|
|
2161
|
-
ctx.refStack.delete(schema.$ref)
|
|
2162
|
-
if (!hasSiblings) return
|
|
2163
|
-
}
|
|
2164
|
-
} else if (schema.$ref !== '#' && !m && schema.$ref.startsWith('#') && !schema.$ref.startsWith('#/')) {
|
|
2165
|
-
// Anchor ref: "#foo" — resolve via rootDefs or anchors map
|
|
2166
|
-
const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
|
|
2167
|
-
const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
|
|
2168
|
-
if (anchorTarget) {
|
|
2169
|
-
if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return }
|
|
2170
|
-
else {
|
|
2171
|
-
ctx.refStack.add(schema.$ref)
|
|
2172
|
-
genCode(anchorTarget, v, lines, ctx, knownType)
|
|
2173
|
-
ctx.refStack.delete(schema.$ref)
|
|
2174
|
-
if (!hasSiblings) return
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
2177
|
-
} else if (schema.$ref !== '#' && ctx.schemaMap) {
|
|
2178
|
-
// 2. Cross-schema ref (exact match, relative URI, or JSON pointer fragment)
|
|
2179
|
-
let resolved = ctx.schemaMap.get(schema.$ref)
|
|
2180
|
-
if (!resolved && !schema.$ref.includes('://') && !schema.$ref.startsWith('#')) {
|
|
2181
|
-
for (const [id, s] of ctx.schemaMap) {
|
|
2182
|
-
if (id.endsWith('/' + schema.$ref)) { resolved = s; break }
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
if (!resolved && schema.$ref.includes('#') && !schema.$ref.startsWith('#')) {
|
|
2186
|
-
const r = resolveCrossSchemaRef(schema.$ref, ctx.schemaMap)
|
|
2187
|
-
if (r) resolved = r.schema
|
|
2188
|
-
}
|
|
2189
|
-
if (resolved) {
|
|
2190
|
-
if (ctx.refStack.has(schema.$ref)) { if (!hasSiblings) return }
|
|
2191
|
-
else {
|
|
2192
|
-
ctx.refStack.add(schema.$ref)
|
|
2193
|
-
genCode(resolved, v, lines, ctx, knownType)
|
|
2194
|
-
ctx.refStack.delete(schema.$ref)
|
|
2195
|
-
if (!hasSiblings) return
|
|
2196
|
-
}
|
|
2197
|
-
} else {
|
|
2198
|
-
if (!hasSiblings) return
|
|
2199
|
-
}
|
|
2200
|
-
} else {
|
|
2201
|
-
if (!hasSiblings) return
|
|
2202
|
-
}
|
|
2127
|
+
if (genRefNode(schema, v, lines, ctx, knownType, hasSiblings)) return
|
|
2203
2128
|
}
|
|
2204
2129
|
|
|
2205
2130
|
// $dynamicRef — resolve via anchors map
|
|
2206
|
-
if (schema.$dynamicRef)
|
|
2207
|
-
const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
|
|
2208
|
-
if (ctx.anchors && ctx.anchors[anchorKey]) {
|
|
2209
|
-
const target = ctx.anchors[anchorKey]
|
|
2210
|
-
if (target === ctx.rootSchema) {
|
|
2211
|
-
// Self-recursive: generate _validate(v) call
|
|
2212
|
-
if (ctx.nestedBoolean) throw DECLINE
|
|
2213
|
-
ctx.usesRecursion = true
|
|
2214
|
-
lines.push(`if(!_validate(${v}))return false`)
|
|
2215
|
-
} else {
|
|
2216
|
-
// Different schema: inline the target validation
|
|
2217
|
-
const refKey = '$dynamicRef:' + anchorKey
|
|
2218
|
-
if (ctx.refStack.has(refKey) && ctx.nestedBoolean) throw DECLINE
|
|
2219
|
-
if (!ctx.refStack.has(refKey)) {
|
|
2220
|
-
ctx.refStack.add(refKey)
|
|
2221
|
-
genCode(target, v, lines, ctx, knownType)
|
|
2222
|
-
ctx.refStack.delete(refKey)
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2131
|
+
if (schema.$dynamicRef) genDynamicRefNode(schema, v, lines, ctx, knownType)
|
|
2227
2132
|
|
|
2228
2133
|
// Determine the single known type after this schema's type check
|
|
2229
2134
|
const types = schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : null
|
|
@@ -2316,7 +2221,11 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2316
2221
|
// Read directly: the object type is already established here, and a
|
|
2317
2222
|
// flag declared for this alone costs more than it saves. The list is
|
|
2318
2223
|
// built only on that path, which an ordinary object never takes.
|
|
2319
|
-
|
|
2224
|
+
// When withPlain already holds the answer for this object in a flag,
|
|
2225
|
+
// read the flag: the prototype test is an accessor call, and making it
|
|
2226
|
+
// twice per object was a tenth of a plain order's verdict.
|
|
2227
|
+
const pk = ctx.plainOf ? ctx.plainOf.get(v) : undefined
|
|
2228
|
+
lines.push(`if(!${pk || protoIs(ctx, v)}&&!_hall(${v},${JSON.stringify(typed)}))return false`)
|
|
2320
2229
|
}
|
|
2321
2230
|
} else if (schema.required && schema.required.length > 0) {
|
|
2322
2231
|
const checks = schema.required.map(key => `!${ownKeyExpr(ctx, v, key)}`)
|
|
@@ -2447,31 +2356,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2447
2356
|
}
|
|
2448
2357
|
|
|
2449
2358
|
// uniqueItems — tiered strategy based on expected array size
|
|
2450
|
-
if (schema.uniqueItems)
|
|
2451
|
-
const si = ctx.varCounter++
|
|
2452
|
-
const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
|
|
2453
|
-
const isPrimItems = itemType === 'string' || itemType === 'number' || itemType === 'integer'
|
|
2454
|
-
const maxItems = schema.maxItems
|
|
2455
|
-
// Small primitive arrays (maxItems <= 16): nested loop is 6x faster than Set
|
|
2456
|
-
// No allocation, no hash computation — just direct === comparison
|
|
2457
|
-
let inner
|
|
2458
|
-
if (isPrimItems && maxItems && maxItems <= 16) {
|
|
2459
|
-
inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k])return false}}`
|
|
2460
|
-
} else if (isPrimItems) {
|
|
2461
|
-
inner = `const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){if(_s${si}.has(${v}[_i]))return false;_s${si}.add(${v}[_i])}`
|
|
2462
|
-
} else if (ctx.preamble) {
|
|
2463
|
-
// Unknown item types: one hoisted helper per compiled function. Short
|
|
2464
|
-
// arrays compare pairwise with a structural equality that ignores key
|
|
2465
|
-
// order and allocates nothing; longer ones fall back to a Set of
|
|
2466
|
-
// primitives, or to sorted-key canonical strings once an object shows
|
|
2467
|
-
// up. The previous form built the canonical closure and a Set on
|
|
2468
|
-
// every call, even for [1, 2, 3].
|
|
2469
|
-
inner = `if(!${emitUq(ctx)}(${v}))return false`
|
|
2470
|
-
} else {
|
|
2471
|
-
inner = `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);if(_s${si}.has(_k))return false;_s${si}.add(_k)}`
|
|
2472
|
-
}
|
|
2473
|
-
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
2474
|
-
}
|
|
2359
|
+
if (schema.uniqueItems) genUniqueItemsNode(schema, v, lines, ctx, knownType, hoisted, isArr, types)
|
|
2475
2360
|
|
|
2476
2361
|
// additionalProperties -- deferred to end of function for better V8 optimization
|
|
2477
2362
|
// (type checks run first in hot path, expensive prop count check last)
|
|
@@ -2504,28 +2389,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2504
2389
|
// additionalProperties as a schema: validate every non-declared property
|
|
2505
2390
|
// against that sub-schema. Skip if patternProperties is present (handled by
|
|
2506
2391
|
// the unified loop). Composition cases are filtered out by codegenSafe.
|
|
2507
|
-
if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null && !schema.patternProperties)
|
|
2508
|
-
// The loop variables carry a unique suffix: a record whose values are
|
|
2509
|
-
// themselves records nests this loop inside itself, and a fixed `_av`
|
|
2510
|
-
// made the inner `const _av=_av[_k]` a self-reference that threw at
|
|
2511
|
-
// validation time.
|
|
2512
|
-
const apId = ctx._apLoopId = (ctx._apLoopId || 0) + 1
|
|
2513
|
-
const kVar = `_k${apId}`
|
|
2514
|
-
const avVar = `_av${apId}`
|
|
2515
|
-
const declared = schema.properties ? Object.keys(schema.properties) : []
|
|
2516
|
-
const skipCheck = declared.length === 0
|
|
2517
|
-
? null
|
|
2518
|
-
: declared.map(k => `${kVar}===${JSON.stringify(k)}`).join('||')
|
|
2519
|
-
const subLines = []
|
|
2520
|
-
genCode(schema.additionalProperties, avVar, subLines, ctx)
|
|
2521
|
-
if (subLines.length > 0) {
|
|
2522
|
-
const body = subLines.join(';')
|
|
2523
|
-
const loop = skipCheck
|
|
2524
|
-
? `for(var ${kVar} in ${v}){if(${skipCheck})continue;const ${avVar}=${v}[${kVar}];${body}}`
|
|
2525
|
-
: `for(var ${kVar} in ${v}){const ${avVar}=${v}[${kVar}];${body}}`
|
|
2526
|
-
_deferOrInline(ctx, lines, v, isObj ? loop : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${loop}}`)
|
|
2527
|
-
}
|
|
2528
|
-
}
|
|
2392
|
+
if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null && !schema.patternProperties) genAdditionalSchemaNode(schema, v, lines, ctx, knownType, isObj)
|
|
2529
2393
|
|
|
2530
2394
|
// dependentRequired
|
|
2531
2395
|
if (schema.dependentRequired) {
|
|
@@ -2538,143 +2402,11 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2538
2402
|
// patternProperties + propertyNames + additionalProperties — unified key iteration
|
|
2539
2403
|
// Merges up to 3 separate for..in loops into one pass.
|
|
2540
2404
|
if (schema.patternProperties) {
|
|
2541
|
-
|
|
2542
|
-
const pn = schema.propertyNames && typeof schema.propertyNames === 'object' ? schema.propertyNames : null
|
|
2543
|
-
const pi = ctx.varCounter++
|
|
2544
|
-
const kVar = `_ppk${pi}`
|
|
2545
|
-
|
|
2546
|
-
// Build pattern matchers: prefer charCodeAt for simple prefixes, fall back to regex
|
|
2547
|
-
const matchers = []
|
|
2548
|
-
for (const [pat] of ppEntries) {
|
|
2549
|
-
const fast = fastPrefixCheck(pat, kVar)
|
|
2550
|
-
if (fast) {
|
|
2551
|
-
matchers.push({ check: fast })
|
|
2552
|
-
} else {
|
|
2553
|
-
const ri = ctx.varCounter++
|
|
2554
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
2555
|
-
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
2556
|
-
matchers.push({ check: `_re${ri}.test(${kVar})` })
|
|
2557
|
-
}
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
// Build sub-schema checks inline so they share the parent helper scope.
|
|
2561
|
-
const subChecks = []
|
|
2562
|
-
for (let i = 0; i < ppEntries.length; i++) {
|
|
2563
|
-
const [, sub] = ppEntries[i]
|
|
2564
|
-
const subLines = []
|
|
2565
|
-
genCode(sub, `_ppv${pi}`, subLines, ctx)
|
|
2566
|
-
subChecks.push(subLines.join(';'))
|
|
2567
|
-
}
|
|
2568
|
-
|
|
2569
|
-
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
2570
|
-
|
|
2571
|
-
const apSchema = typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null ? schema.additionalProperties : null
|
|
2572
|
-
if ((schema.additionalProperties === false && schema.properties) || apSchema) {
|
|
2573
|
-
// Unified loop: properties + patterns + propertyNames + additionalProperties.
|
|
2574
|
-
// A key that is neither declared nor matched by any pattern is
|
|
2575
|
-
// additional: rejected outright, or run through the additionalProperties
|
|
2576
|
-
// schema when there is one.
|
|
2577
|
-
ppHandledPropertyNames = !!pn
|
|
2578
|
-
const propKeys = Object.keys(schema.properties || {})
|
|
2579
|
-
let apCheck = null
|
|
2580
|
-
if (apSchema) {
|
|
2581
|
-
const apLines = []
|
|
2582
|
-
genCode(apSchema, `_apv${pi}`, apLines, ctx)
|
|
2583
|
-
apCheck = apLines.join(';')
|
|
2584
|
-
}
|
|
2585
|
-
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
2586
|
-
// propertyNames checks (merged into same loop)
|
|
2587
|
-
if (pn) {
|
|
2588
|
-
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength})return false`)
|
|
2589
|
-
if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength})return false`)
|
|
2590
|
-
if (pn.pattern) {
|
|
2591
|
-
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
2592
|
-
if (fast) {
|
|
2593
|
-
lines.push(`if(!(${fast}))return false`)
|
|
2594
|
-
} else {
|
|
2595
|
-
const ri = ctx.varCounter++
|
|
2596
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
2597
|
-
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
2598
|
-
lines.push(`if(!_re${ri}.test(${kVar}))return false`)
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)})return false`)
|
|
2602
|
-
if (pn.enum) {
|
|
2603
|
-
const ei = ctx.varCounter++
|
|
2604
|
-
ctx.closureVars.push(`_es${ei}`)
|
|
2605
|
-
ctx.closureVals.push(new Set(pn.enum))
|
|
2606
|
-
lines.push(`if(!_es${ei}.has(${kVar}))return false`)
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
// Every matching pattern applies to every key, declared or not: a
|
|
2610
|
-
// declared key that also matches a pattern is validated by both (the
|
|
2611
|
-
// properties emitter handles its own subschema). A flag rather than an
|
|
2612
|
-
// if/else chain, because a chain returned false at the first pattern
|
|
2613
|
-
// that missed and wrongly rejected a key only a later pattern matched.
|
|
2614
|
-
if (ppEntries.length > 0) {
|
|
2615
|
-
lines.push(`let _pm${pi}=false`)
|
|
2616
|
-
for (let i = 0; i < ppEntries.length; i++) {
|
|
2617
|
-
lines.push(`if(${matchers[i].check}){_pm${pi}=true;const _ppv${pi}=${v}[${kVar}];${subChecks[i]}}`)
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
// A key that is neither declared nor matched is additional. switch on
|
|
2621
|
-
// the declared names (V8 compiles string cases to a jump table); no
|
|
2622
|
-
// switch at all when nothing is declared, since a switch with no case
|
|
2623
|
-
// clause is a syntax error.
|
|
2624
|
-
const additional = apCheck !== null ? `const _apv${pi}=${v}[${kVar}];${apCheck}` : `return false`
|
|
2625
|
-
const notMatched = ppEntries.length > 0 ? `if(!_pm${pi}){${additional}}` : additional
|
|
2626
|
-
if (propKeys.length) {
|
|
2627
|
-
const switchCases = propKeys.map(k => `case ${JSON.stringify(k)}:`).join('')
|
|
2628
|
-
lines.push(`switch(${kVar}){${switchCases}break;default:{${notMatched}}}`)
|
|
2629
|
-
} else {
|
|
2630
|
-
lines.push(notMatched)
|
|
2631
|
-
}
|
|
2632
|
-
lines.push(`}}`)
|
|
2633
|
-
} else {
|
|
2634
|
-
// No additionalProperties: validate matching keys + propertyNames
|
|
2635
|
-
ppHandledPropertyNames = !!pn
|
|
2636
|
-
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
2637
|
-
// propertyNames checks (merged)
|
|
2638
|
-
if (pn) {
|
|
2639
|
-
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength})return false`)
|
|
2640
|
-
if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength})return false`)
|
|
2641
|
-
if (pn.pattern) {
|
|
2642
|
-
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
2643
|
-
if (fast) {
|
|
2644
|
-
lines.push(`if(!(${fast}))return false`)
|
|
2645
|
-
} else {
|
|
2646
|
-
const ri = ctx.varCounter++
|
|
2647
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
2648
|
-
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
2649
|
-
lines.push(`if(!_re${ri}.test(${kVar}))return false`)
|
|
2650
|
-
}
|
|
2651
|
-
}
|
|
2652
|
-
if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)})return false`)
|
|
2653
|
-
if (pn.enum) {
|
|
2654
|
-
const ei = ctx.varCounter++
|
|
2655
|
-
ctx.closureVars.push(`_es${ei}`)
|
|
2656
|
-
ctx.closureVals.push(new Set(pn.enum))
|
|
2657
|
-
lines.push(`if(!_es${ei}.has(${kVar}))return false`)
|
|
2658
|
-
}
|
|
2659
|
-
}
|
|
2660
|
-
for (let i = 0; i < ppEntries.length; i++) {
|
|
2661
|
-
lines.push(`if(${matchers[i].check}){const _ppv${pi}=${v}[${kVar}];${subChecks[i]}}`)
|
|
2662
|
-
}
|
|
2663
|
-
lines.push(`}}`)
|
|
2664
|
-
}
|
|
2405
|
+
if (genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj)) ppHandledPropertyNames = true
|
|
2665
2406
|
}
|
|
2666
2407
|
|
|
2667
2408
|
// dependentSchemas
|
|
2668
|
-
if (schema.dependentSchemas)
|
|
2669
|
-
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
2670
|
-
const guard = isObj ? '' : `typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&`
|
|
2671
|
-
lines.push(`if(${guard}${ownKeyExpr(ctx, v, key)}){`)
|
|
2672
|
-
ctx.condDepth = (ctx.condDepth || 0) + 1
|
|
2673
|
-
genCode(depSchema, v, lines, ctx, effectiveType)
|
|
2674
|
-
ctx.condDepth--
|
|
2675
|
-
lines.push(`}`)
|
|
2676
|
-
}
|
|
2677
|
-
}
|
|
2409
|
+
if (schema.dependentSchemas) genDependentSchemasNode(schema, v, lines, ctx, knownType, effectiveType, isObj)
|
|
2678
2410
|
|
|
2679
2411
|
// propertyNames: false rejects any key at all; true constrains nothing.
|
|
2680
2412
|
if (schema.propertyNames === false) {
|
|
@@ -2684,33 +2416,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2684
2416
|
}
|
|
2685
2417
|
|
|
2686
2418
|
// propertyNames — only emit if not already merged into patternProperties loop
|
|
2687
|
-
if (schema.propertyNames && typeof schema.propertyNames === 'object' && !ppHandledPropertyNames)
|
|
2688
|
-
const pn = schema.propertyNames
|
|
2689
|
-
const ki = ctx.varCounter++
|
|
2690
|
-
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
2691
|
-
lines.push(`${guard}{for(const _k${ki} in ${v}){`)
|
|
2692
|
-
if (pn.minLength !== undefined) lines.push(`if(_k${ki}.length<${pn.minLength})return false`)
|
|
2693
|
-
if (pn.maxLength !== undefined) lines.push(`if(_k${ki}.length>${pn.maxLength})return false`)
|
|
2694
|
-
if (pn.pattern) {
|
|
2695
|
-
const fast = fastPrefixCheck(pn.pattern, `_k${ki}`)
|
|
2696
|
-
if (fast) {
|
|
2697
|
-
lines.push(`if(!(${fast}))return false`)
|
|
2698
|
-
} else {
|
|
2699
|
-
const ri = ctx.varCounter++
|
|
2700
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
2701
|
-
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
2702
|
-
lines.push(`if(!_re${ri}.test(_k${ki}))return false`)
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
if (pn.const !== undefined) lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)})return false`)
|
|
2706
|
-
if (pn.enum) {
|
|
2707
|
-
const ei = ctx.varCounter++
|
|
2708
|
-
ctx.closureVars.push(`_es${ei}`)
|
|
2709
|
-
ctx.closureVals.push(new Set(pn.enum))
|
|
2710
|
-
lines.push(`if(!_es${ei}.has(_k${ki}))return false`)
|
|
2711
|
-
}
|
|
2712
|
-
lines.push(`}}`)
|
|
2713
|
-
}
|
|
2419
|
+
if (schema.propertyNames && typeof schema.propertyNames === 'object' && !ppHandledPropertyNames) genPropertyNamesNode(schema, v, lines, ctx, knownType, isObj)
|
|
2714
2420
|
|
|
2715
2421
|
// properties — use hoisted vars for required props, hoist optional to locals too
|
|
2716
2422
|
if (schema.properties) {
|
|
@@ -2721,7 +2427,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2721
2427
|
const combined = tryGenCombined(prop, access, ctx)
|
|
2722
2428
|
if (combined) {
|
|
2723
2429
|
lines.push(combined)
|
|
2724
|
-
} else if (needsLocal(prop)) {
|
|
2430
|
+
} else if (needsLocal(prop) || isPlainContainer(prop)) {
|
|
2725
2431
|
const oi = ctx.varCounter++
|
|
2726
2432
|
const local = `_r${oi}`
|
|
2727
2433
|
lines.push(`{const ${local}=${access}`)
|
|
@@ -2765,35 +2471,10 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2765
2471
|
}
|
|
2766
2472
|
|
|
2767
2473
|
// prefixItems
|
|
2768
|
-
if (schema.prefixItems)
|
|
2769
|
-
const pfxVar = ctx.varCounter++
|
|
2770
|
-
for (let i = 0; i < schema.prefixItems.length; i++) {
|
|
2771
|
-
const elem = `_p${pfxVar}_${i}`
|
|
2772
|
-
lines.push(isArr
|
|
2773
|
-
? `if(${v}.length>${i}){const ${elem}=${v}[${i}]`
|
|
2774
|
-
: `if(Array.isArray(${v})&&${v}.length>${i}){const ${elem}=${v}[${i}]`)
|
|
2775
|
-
genCode(schema.prefixItems[i], elem, lines, ctx)
|
|
2776
|
-
lines.push(`}`)
|
|
2777
|
-
}
|
|
2778
|
-
}
|
|
2474
|
+
if (schema.prefixItems) genPrefixItemsNode(schema, v, lines, ctx, knownType, isArr)
|
|
2779
2475
|
|
|
2780
2476
|
// contains — use helper function to avoid try/catch overhead
|
|
2781
|
-
if (schema.contains !== undefined)
|
|
2782
|
-
const ci = ctx.varCounter++
|
|
2783
|
-
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
2784
|
-
const maxC = schema.maxContains !== undefined ? schema.maxContains : Infinity
|
|
2785
|
-
const subLines = []
|
|
2786
|
-
genCode(schema.contains, `_cv`, subLines, ctx)
|
|
2787
|
-
const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
|
|
2788
|
-
const guard = isArr ? '' : `if(!Array.isArray(${v})){}else `
|
|
2789
|
-
lines.push(`${guard}{const _cf${ci}=function(_cv){${fnBody}};let _cc${ci}=0`)
|
|
2790
|
-
lines.push(`for(let _ci${ci}=0;_ci${ci}<${v}.length;_ci${ci}++){if(_cf${ci}(${v}[_ci${ci}]))_cc${ci}++}`)
|
|
2791
|
-
if (maxC === Infinity) {
|
|
2792
|
-
lines.push(`if(_cc${ci}<${minC})return false}`)
|
|
2793
|
-
} else {
|
|
2794
|
-
lines.push(`if(_cc${ci}<${minC}||_cc${ci}>${maxC})return false}`)
|
|
2795
|
-
}
|
|
2796
|
-
}
|
|
2477
|
+
if (schema.contains !== undefined) genContainsNode(schema, v, lines, ctx, knownType, isArr)
|
|
2797
2478
|
|
|
2798
2479
|
// allOf — pass known type through
|
|
2799
2480
|
if (schema.allOf) {
|
|
@@ -2804,395 +2485,659 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2804
2485
|
|
|
2805
2486
|
// anyOf — branch fns hoisted when safe, inline fallback when recursive.
|
|
2806
2487
|
// Skip standard anyOf if unevaluatedProperties will handle it (single-pass optimization)
|
|
2807
|
-
if (schema.anyOf && schema.unevaluatedProperties === undefined)
|
|
2808
|
-
const fi = ctx.varCounter++
|
|
2809
|
-
const branchBodies = []
|
|
2810
|
-
let canHoist = !!ctx.preamble
|
|
2811
|
-
for (let i = 0; i < schema.anyOf.length; i++) {
|
|
2812
|
-
const subLines = []
|
|
2813
|
-
genCode(schema.anyOf[i], '_av', subLines, ctx)
|
|
2814
|
-
const body = subLines.length === 0 ? 'return true' : `${subLines.join(';')};return true`
|
|
2815
|
-
if (/\b_validate\b/.test(body)) canHoist = false
|
|
2816
|
-
branchBodies.push(body)
|
|
2817
|
-
}
|
|
2818
|
-
if (canHoist) {
|
|
2819
|
-
const names = branchBodies.map((body, i) => {
|
|
2820
|
-
const name = `_af${fi}_b${i}`
|
|
2821
|
-
ctx.preamble.push(`function ${name}(_av){${body}}`)
|
|
2822
|
-
return name
|
|
2823
|
-
})
|
|
2824
|
-
const checks = names.map(n => `${n}(${v})`).join('||')
|
|
2825
|
-
lines.push(`if(!(${checks}))return false`)
|
|
2826
|
-
} else {
|
|
2827
|
-
const fns = branchBodies.map(body => `function(_av){${body}}`)
|
|
2828
|
-
lines.push(`{const _af${fi}=[${fns.join(',')}];let _am${fi}=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am${fi}=true;break}}if(!_am${fi})return false}`)
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2488
|
+
if (schema.anyOf && schema.unevaluatedProperties === undefined) genAnyOfNode(schema, v, lines, ctx, knownType)
|
|
2831
2489
|
|
|
2832
2490
|
// oneOf — branch fns hoisted to factory scope when safe (no recursion/ref).
|
|
2833
2491
|
// Falls back to inline closures if any branch touches recursive validation.
|
|
2834
|
-
if (schema.oneOf)
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2492
|
+
if (schema.oneOf) genOneOfNode(schema, v, lines, ctx, knownType)
|
|
2493
|
+
|
|
2494
|
+
// not
|
|
2495
|
+
if (schema.not !== undefined) genNotNode(schema, v, lines, ctx, knownType)
|
|
2496
|
+
|
|
2497
|
+
// if/then/else
|
|
2498
|
+
if (schema.if !== undefined) genIfNode(schema, v, lines, ctx, knownType)
|
|
2499
|
+
|
|
2500
|
+
// unevaluatedProperties
|
|
2501
|
+
if (schema.unevaluatedProperties !== undefined) genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount)
|
|
2502
|
+
|
|
2503
|
+
// unevaluatedItems
|
|
2504
|
+
if (schema.unevaluatedItems !== undefined) genUnevaluatedItemsNode(schema, v, lines, ctx, knownType, isArr)
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
// The rarely used keyword families live outside genCodeNode, so a schema that
|
|
2508
|
+
// never uses them does not pay V8 to compile them on its first check. Each is
|
|
2509
|
+
// called with the locals it reads; genRefNode returns true when the node is done.
|
|
2510
|
+
|
|
2511
|
+
function genDynamicRefNode(schema, v, lines, ctx, knownType) {
|
|
2512
|
+
const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
|
|
2513
|
+
if (ctx.anchors && ctx.anchors[anchorKey]) {
|
|
2514
|
+
const target = ctx.anchors[anchorKey]
|
|
2515
|
+
if (target === ctx.rootSchema) {
|
|
2516
|
+
// Self-recursive: generate _validate(v) call
|
|
2517
|
+
if (ctx.nestedBoolean) throw DECLINE
|
|
2518
|
+
ctx.usesRecursion = true
|
|
2519
|
+
lines.push(`if(!_validate(${v}))return false`)
|
|
2854
2520
|
} else {
|
|
2855
|
-
|
|
2856
|
-
|
|
2521
|
+
// Different schema: inline the target validation
|
|
2522
|
+
const refKey = '$dynamicRef:' + anchorKey
|
|
2523
|
+
if (ctx.refStack.has(refKey) && ctx.nestedBoolean) throw DECLINE
|
|
2524
|
+
if (!ctx.refStack.has(refKey)) {
|
|
2525
|
+
ctx.refStack.add(refKey)
|
|
2526
|
+
genCode(target, v, lines, ctx, knownType)
|
|
2527
|
+
ctx.refStack.delete(refKey)
|
|
2528
|
+
}
|
|
2857
2529
|
}
|
|
2858
2530
|
}
|
|
2531
|
+
}
|
|
2859
2532
|
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2533
|
+
function genUniqueItemsNode(schema, v, lines, ctx, knownType, hoisted, isArr, types) {
|
|
2534
|
+
const si = ctx.varCounter++
|
|
2535
|
+
const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
|
|
2536
|
+
const isPrimItems = itemType === 'string' || itemType === 'number' || itemType === 'integer'
|
|
2537
|
+
const maxItems = schema.maxItems
|
|
2538
|
+
// Small primitive arrays (maxItems <= 16): nested loop is 6x faster than Set
|
|
2539
|
+
// No allocation, no hash computation — just direct === comparison
|
|
2540
|
+
let inner
|
|
2541
|
+
if (isPrimItems && maxItems && maxItems <= 16) {
|
|
2542
|
+
inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k])return false}}`
|
|
2543
|
+
} else if (isPrimItems) {
|
|
2544
|
+
inner = `const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){if(_s${si}.has(${v}[_i]))return false;_s${si}.add(${v}[_i])}`
|
|
2545
|
+
} else if (ctx.preamble) {
|
|
2546
|
+
// Unknown item types: one hoisted helper per compiled function. Short
|
|
2547
|
+
// arrays compare pairwise with a structural equality that ignores key
|
|
2548
|
+
// order and allocates nothing; longer ones fall back to a Set of
|
|
2549
|
+
// primitives, or to sorted-key canonical strings once an object shows
|
|
2550
|
+
// up. The previous form built the canonical closure and a Set on
|
|
2551
|
+
// every call, even for [1, 2, 3].
|
|
2552
|
+
inner = `if(!${emitUq(ctx)}(${v}))return false`
|
|
2553
|
+
} else {
|
|
2554
|
+
inner = `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);if(_s${si}.has(_k))return false;_s${si}.add(_k)}`
|
|
2555
|
+
}
|
|
2556
|
+
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
function genAdditionalSchemaNode(schema, v, lines, ctx, knownType, isObj) {
|
|
2560
|
+
// The loop variables carry a unique suffix: a record whose values are
|
|
2561
|
+
// themselves records nests this loop inside itself, and a fixed `_av`
|
|
2562
|
+
// made the inner `const _av=_av[_k]` a self-reference that threw at
|
|
2563
|
+
// validation time.
|
|
2564
|
+
const apId = ctx._apLoopId = (ctx._apLoopId || 0) + 1
|
|
2565
|
+
const kVar = `_k${apId}`
|
|
2566
|
+
const avVar = `_av${apId}`
|
|
2567
|
+
const declared = schema.properties ? Object.keys(schema.properties) : []
|
|
2568
|
+
const skipCheck = declared.length === 0
|
|
2569
|
+
? null
|
|
2570
|
+
: declared.map(k => `${kVar}===${JSON.stringify(k)}`).join('||')
|
|
2571
|
+
const subLines = []
|
|
2572
|
+
genCode(schema.additionalProperties, avVar, subLines, ctx)
|
|
2573
|
+
if (subLines.length > 0) {
|
|
2574
|
+
const body = subLines.join(';')
|
|
2575
|
+
const loop = skipCheck
|
|
2576
|
+
? `for(var ${kVar} in ${v}){if(${skipCheck})continue;const ${avVar}=${v}[${kVar}];${body}}`
|
|
2577
|
+
: `for(var ${kVar} in ${v}){const ${avVar}=${v}[${kVar}];${body}}`
|
|
2578
|
+
_deferOrInline(ctx, lines, v, isObj ? loop : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${loop}}`)
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
function genDependentSchemasNode(schema, v, lines, ctx, knownType, effectiveType, isObj) {
|
|
2583
|
+
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
2584
|
+
const guard = isObj ? '' : `typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&`
|
|
2585
|
+
lines.push(`if(${guard}${ownKeyExpr(ctx, v, key)}){`)
|
|
2586
|
+
ctx.condDepth = (ctx.condDepth || 0) + 1
|
|
2587
|
+
genCode(depSchema, v, lines, ctx, effectiveType)
|
|
2588
|
+
ctx.condDepth--
|
|
2589
|
+
lines.push(`}`)
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
function genPropertyNamesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
2594
|
+
const pn = schema.propertyNames
|
|
2595
|
+
const ki = ctx.varCounter++
|
|
2596
|
+
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
2597
|
+
lines.push(`${guard}{for(const _k${ki} in ${v}){`)
|
|
2598
|
+
if (pn.minLength !== undefined) lines.push(`if(_k${ki}.length<${pn.minLength})return false`)
|
|
2599
|
+
if (pn.maxLength !== undefined) lines.push(`if(_k${ki}.length>${pn.maxLength})return false`)
|
|
2600
|
+
if (pn.pattern) {
|
|
2601
|
+
const fast = fastPrefixCheck(pn.pattern, `_k${ki}`)
|
|
2602
|
+
if (fast) {
|
|
2603
|
+
lines.push(`if(!(${fast}))return false`)
|
|
2866
2604
|
} else {
|
|
2867
|
-
const
|
|
2868
|
-
|
|
2605
|
+
const ri = ctx.varCounter++
|
|
2606
|
+
ctx.closureVars.push(`_re${ri}`)
|
|
2607
|
+
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
2608
|
+
lines.push(`if(!_re${ri}.test(_k${ki}))return false`)
|
|
2869
2609
|
}
|
|
2870
2610
|
}
|
|
2611
|
+
if (pn.const !== undefined) lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)})return false`)
|
|
2612
|
+
if (pn.enum) {
|
|
2613
|
+
const ei = ctx.varCounter++
|
|
2614
|
+
ctx.closureVars.push(`_es${ei}`)
|
|
2615
|
+
ctx.closureVals.push(new Set(pn.enum))
|
|
2616
|
+
lines.push(`if(!_es${ei}.has(_k${ki}))return false`)
|
|
2617
|
+
}
|
|
2618
|
+
lines.push(`}}`)
|
|
2619
|
+
}
|
|
2871
2620
|
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2621
|
+
function genPrefixItemsNode(schema, v, lines, ctx, knownType, isArr) {
|
|
2622
|
+
const pfxVar = ctx.varCounter++
|
|
2623
|
+
for (let i = 0; i < schema.prefixItems.length; i++) {
|
|
2624
|
+
const elem = `_p${pfxVar}_${i}`
|
|
2625
|
+
lines.push(isArr
|
|
2626
|
+
? `if(${v}.length>${i}){const ${elem}=${v}[${i}]`
|
|
2627
|
+
: `if(Array.isArray(${v})&&${v}.length>${i}){const ${elem}=${v}[${i}]`)
|
|
2628
|
+
genCode(schema.prefixItems[i], elem, lines, ctx)
|
|
2629
|
+
lines.push(`}`)
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
function genContainsNode(schema, v, lines, ctx, knownType, isArr) {
|
|
2634
|
+
const ci = ctx.varCounter++
|
|
2635
|
+
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
2636
|
+
const maxC = schema.maxContains !== undefined ? schema.maxContains : Infinity
|
|
2637
|
+
const subLines = []
|
|
2638
|
+
genCode(schema.contains, `_cv`, subLines, ctx)
|
|
2639
|
+
const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
|
|
2640
|
+
const guard = isArr ? '' : `if(!Array.isArray(${v})){}else `
|
|
2641
|
+
lines.push(`${guard}{const _cf${ci}=function(_cv){${fnBody}};let _cc${ci}=0`)
|
|
2642
|
+
lines.push(`for(let _ci${ci}=0;_ci${ci}<${v}.length;_ci${ci}++){if(_cf${ci}(${v}[_ci${ci}]))_cc${ci}++}`)
|
|
2643
|
+
if (maxC === Infinity) {
|
|
2644
|
+
lines.push(`if(_cc${ci}<${minC})return false}`)
|
|
2645
|
+
} else {
|
|
2646
|
+
lines.push(`if(_cc${ci}<${minC}||_cc${ci}>${maxC})return false}`)
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
function genAnyOfNode(schema, v, lines, ctx, knownType) {
|
|
2651
|
+
const fi = ctx.varCounter++
|
|
2652
|
+
const branchBodies = []
|
|
2653
|
+
let canHoist = !!ctx.preamble
|
|
2654
|
+
for (let i = 0; i < schema.anyOf.length; i++) {
|
|
2655
|
+
const subLines = []
|
|
2656
|
+
genCode(schema.anyOf[i], '_av', subLines, ctx)
|
|
2657
|
+
const body = subLines.length === 0 ? 'return true' : `${subLines.join(';')};return true`
|
|
2658
|
+
if (/\b_validate\b/.test(body)) canHoist = false
|
|
2659
|
+
branchBodies.push(body)
|
|
2660
|
+
}
|
|
2661
|
+
if (canHoist) {
|
|
2662
|
+
const names = branchBodies.map((body, i) => {
|
|
2663
|
+
const name = `_af${fi}_b${i}`
|
|
2664
|
+
ctx.preamble.push(`function ${name}(_av){${body}}`)
|
|
2665
|
+
return name
|
|
2666
|
+
})
|
|
2667
|
+
const checks = names.map(n => `${n}(${v})`).join('||')
|
|
2668
|
+
lines.push(`if(!(${checks}))return false`)
|
|
2669
|
+
} else {
|
|
2670
|
+
const fns = branchBodies.map(body => `function(_av){${body}}`)
|
|
2671
|
+
lines.push(`{const _af${fi}=[${fns.join(',')}];let _am${fi}=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am${fi}=true;break}}if(!_am${fi})return false}`)
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
function genOneOfNode(schema, v, lines, ctx, knownType) {
|
|
2676
|
+
const fi = ctx.varCounter++
|
|
2677
|
+
const branchBodies = []
|
|
2678
|
+
let canHoist = !!ctx.preamble
|
|
2679
|
+
for (let i = 0; i < schema.oneOf.length; i++) {
|
|
2680
|
+
const subLines = []
|
|
2681
|
+
genCode(schema.oneOf[i], '_ov', subLines, ctx)
|
|
2682
|
+
const body = subLines.length === 0 ? 'return true' : `${subLines.join(';')};return true`
|
|
2683
|
+
// _validate is the recursive entry — hoisting branches above it breaks scope.
|
|
2684
|
+
if (/\b_validate\b/.test(body)) canHoist = false
|
|
2685
|
+
branchBodies.push(body)
|
|
2686
|
+
}
|
|
2687
|
+
if (canHoist) {
|
|
2688
|
+
const names = branchBodies.map((body, i) => {
|
|
2689
|
+
const name = `_of${fi}_b${i}`
|
|
2690
|
+
ctx.preamble.push(`function ${name}(_ov){${body}}`)
|
|
2691
|
+
return name
|
|
2692
|
+
})
|
|
2693
|
+
const calls = names.map(n => `if(${n}(${v})){_oc${fi}++;if(_oc${fi}>1)return false}`).join(';')
|
|
2694
|
+
lines.push(`{let _oc${fi}=0;${calls};if(_oc${fi}!==1)return false}`)
|
|
2695
|
+
} else {
|
|
2696
|
+
const fns = branchBodies.map(body => `function(_ov){${body}}`)
|
|
2697
|
+
lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc${fi}=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc${fi}++;if(_oc${fi}>1)return false}if(_oc${fi}!==1)return false}`)
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
function genNotNode(schema, v, lines, ctx, knownType) {
|
|
2702
|
+
const subLines = []
|
|
2703
|
+
genCode(schema.not, '_nv', subLines, ctx)
|
|
2704
|
+
if (subLines.length === 0) {
|
|
2705
|
+
lines.push(`return false`) // not:{} means nothing is valid
|
|
2706
|
+
} else {
|
|
2876
2707
|
const fi = ctx.varCounter++
|
|
2877
|
-
const
|
|
2878
|
-
|
|
2879
|
-
|
|
2708
|
+
lines.push(`{const _nf${fi}=(function(_nv){${subLines.join(';')};return true});if(_nf${fi}(${v}))return false}`)
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
function genIfNode(schema, v, lines, ctx, knownType) {
|
|
2713
|
+
const ifLines = []
|
|
2714
|
+
genCode(schema.if, '_iv', ifLines, ctx)
|
|
2715
|
+
const fi = ctx.varCounter++
|
|
2716
|
+
const ifFn = ifLines.length === 0
|
|
2717
|
+
? `function(_iv){return true}`
|
|
2718
|
+
: `function(_iv){${ifLines.join(';')};return true}`
|
|
2719
|
+
|
|
2720
|
+
let thenFn = 'null', elseFn = 'null'
|
|
2721
|
+
if (schema.then !== undefined) {
|
|
2722
|
+
const thenLines = []
|
|
2723
|
+
genCode(schema.then, '_tv', thenLines, ctx)
|
|
2724
|
+
thenFn = thenLines.length === 0
|
|
2725
|
+
? `function(_tv){return true}`
|
|
2726
|
+
: `function(_tv){${thenLines.join(';')};return true}`
|
|
2727
|
+
}
|
|
2728
|
+
if (schema.else !== undefined) {
|
|
2729
|
+
const elseLines = []
|
|
2730
|
+
genCode(schema.else, '_ev', elseLines, ctx)
|
|
2731
|
+
elseFn = elseLines.length === 0
|
|
2732
|
+
? `function(_ev){return true}`
|
|
2733
|
+
: `function(_ev){${elseLines.join(';')};return true}`
|
|
2734
|
+
}
|
|
2735
|
+
lines.push(`{const _if${fi}=${ifFn};const _th${fi}=${thenFn};const _el${fi}=${elseFn}`)
|
|
2736
|
+
lines.push(`if(_if${fi}(${v})){if(_th${fi}&&!_th${fi}(${v}))return false}else{if(_el${fi}&&!_el${fi}(${v}))return false}}`)
|
|
2737
|
+
}
|
|
2880
2738
|
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2739
|
+
function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
2740
|
+
// Self-reference "#" — recursive call to root validator
|
|
2741
|
+
if (schema.$ref === '#') {
|
|
2742
|
+
if (ctx.nestedBoolean) throw DECLINE
|
|
2743
|
+
ctx.usesRecursion = true
|
|
2744
|
+
lines.push(`if(!_validate(${v}))return false`)
|
|
2745
|
+
if (!hasSiblings) return true
|
|
2746
|
+
}
|
|
2747
|
+
// 1. Local ref
|
|
2748
|
+
const m = schema.$ref !== '#' && schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
|
|
2749
|
+
if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
|
|
2750
|
+
const defName = m[1]
|
|
2751
|
+
if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
|
|
2752
|
+
if (ctx.cyclicDefs.has(defName) && ctx.preamble) {
|
|
2753
|
+
// A def on a cycle is a named function in the preamble, emitted once
|
|
2754
|
+
// and called by name, so recursion terminates the way `$ref: "#"`
|
|
2755
|
+
// does at the root. The name is registered before the body is
|
|
2756
|
+
// generated so a self-reference inside it becomes a call.
|
|
2757
|
+
if (!ctx.defFns) ctx.defFns = new Map()
|
|
2758
|
+
let fnName = ctx.defFns.get(defName)
|
|
2759
|
+
if (!fnName) {
|
|
2760
|
+
fnName = '_def' + ctx.defFns.size + '_' + defName.replace(/[^A-Za-z0-9_]/g, '_')
|
|
2761
|
+
ctx.defFns.set(defName, fnName)
|
|
2762
|
+
const bodyLines = []
|
|
2763
|
+
// The def is compiled as its own function over its own `d`, so the
|
|
2764
|
+
// checks _deferOrInline holds back belong at the end of this body.
|
|
2765
|
+
// The list hangs off ctx, so without the swap they are flushed into
|
|
2766
|
+
// whatever function is compiling, carrying this def's key set.
|
|
2767
|
+
const outerDeferred = ctx.deferredChecks
|
|
2768
|
+
ctx.deferredChecks = null
|
|
2769
|
+
genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
|
|
2770
|
+
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
2771
|
+
ctx.deferredChecks = outerDeferred
|
|
2772
|
+
// Entered through the cycle guard: see emitGuardState. In the fast
|
|
2773
|
+
// pass the wrapper only counts depth; the set is used by the second,
|
|
2774
|
+
// guarded pass that runs when data turns out to point back at
|
|
2775
|
+
// itself.
|
|
2776
|
+
ctx.preamble.push(
|
|
2777
|
+
`const ${fnName}_s=new Set()\n function ${fnName}(d){\n ` +
|
|
2778
|
+
`if(_sg){if(typeof d!=='object'||d===null)return ${fnName}_b(d);if(${fnName}_s.has(d))return true;${fnName}_s.add(d);try{return ${fnName}_b(d)}finally{${fnName}_s.delete(d)}}\n ` +
|
|
2779
|
+
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d)\n _sd--\n return _r\n }\n ` +
|
|
2780
|
+
`function ${fnName}_b(d){${bodyLines.join('\n ')}\n return true}`,
|
|
2781
|
+
)
|
|
2782
|
+
}
|
|
2783
|
+
lines.push(`if(!${fnName}(${v}))return false`)
|
|
2784
|
+
if (!hasSiblings) return true
|
|
2785
|
+
} else if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return true }
|
|
2786
|
+
else {
|
|
2787
|
+
ctx.refStack.add(schema.$ref)
|
|
2788
|
+
genCode(ctx.rootDefs[defName], v, lines, ctx, knownType)
|
|
2789
|
+
ctx.refStack.delete(schema.$ref)
|
|
2790
|
+
if (!hasSiblings) return true
|
|
2791
|
+
}
|
|
2792
|
+
} else if (schema.$ref !== '#' && !m && schema.$ref.startsWith('#') && !schema.$ref.startsWith('#/')) {
|
|
2793
|
+
// Anchor ref: "#foo" — resolve via rootDefs or anchors map
|
|
2794
|
+
const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
|
|
2795
|
+
const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
|
|
2796
|
+
if (anchorTarget) {
|
|
2797
|
+
if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return true }
|
|
2798
|
+
else {
|
|
2799
|
+
ctx.refStack.add(schema.$ref)
|
|
2800
|
+
genCode(anchorTarget, v, lines, ctx, knownType)
|
|
2801
|
+
ctx.refStack.delete(schema.$ref)
|
|
2802
|
+
if (!hasSiblings) return true
|
|
2803
|
+
}
|
|
2888
2804
|
}
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2805
|
+
} else if (schema.$ref !== '#' && ctx.schemaMap) {
|
|
2806
|
+
// 2. Cross-schema ref (exact match, relative URI, or JSON pointer fragment)
|
|
2807
|
+
let resolved = ctx.schemaMap.get(schema.$ref)
|
|
2808
|
+
if (!resolved && !schema.$ref.includes('://') && !schema.$ref.startsWith('#')) {
|
|
2809
|
+
for (const [id, s] of ctx.schemaMap) {
|
|
2810
|
+
if (id.endsWith('/' + schema.$ref)) { resolved = s; break }
|
|
2811
|
+
}
|
|
2895
2812
|
}
|
|
2896
|
-
|
|
2897
|
-
|
|
2813
|
+
if (!resolved && schema.$ref.includes('#') && !schema.$ref.startsWith('#')) {
|
|
2814
|
+
const r = resolveCrossSchemaRef(schema.$ref, ctx.schemaMap)
|
|
2815
|
+
if (r) resolved = r.schema
|
|
2816
|
+
}
|
|
2817
|
+
if (resolved) {
|
|
2818
|
+
if (ctx.refStack.has(schema.$ref)) { if (!hasSiblings) return true }
|
|
2819
|
+
else {
|
|
2820
|
+
ctx.refStack.add(schema.$ref)
|
|
2821
|
+
genCode(resolved, v, lines, ctx, knownType)
|
|
2822
|
+
ctx.refStack.delete(schema.$ref)
|
|
2823
|
+
if (!hasSiblings) return true
|
|
2824
|
+
}
|
|
2825
|
+
} else {
|
|
2826
|
+
if (!hasSiblings) return true
|
|
2827
|
+
}
|
|
2828
|
+
} else {
|
|
2829
|
+
if (!hasSiblings) return true
|
|
2898
2830
|
}
|
|
2831
|
+
return false
|
|
2832
|
+
}
|
|
2899
2833
|
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2834
|
+
function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
2835
|
+
let handledPropertyNames = false
|
|
2836
|
+
const ppEntries = Object.entries(schema.patternProperties)
|
|
2837
|
+
const pn = schema.propertyNames && typeof schema.propertyNames === 'object' ? schema.propertyNames : null
|
|
2838
|
+
const pi = ctx.varCounter++
|
|
2839
|
+
const kVar = `_ppk${pi}`
|
|
2840
|
+
|
|
2841
|
+
// Build pattern matchers: prefer charCodeAt for simple prefixes, fall back to regex
|
|
2842
|
+
const matchers = []
|
|
2843
|
+
for (const [pat] of ppEntries) {
|
|
2844
|
+
const fast = fastPrefixCheck(pat, kVar)
|
|
2845
|
+
if (fast) {
|
|
2846
|
+
matchers.push({ check: fast })
|
|
2847
|
+
} else {
|
|
2848
|
+
const ri = ctx.varCounter++
|
|
2849
|
+
ctx.closureVars.push(`_re${ri}`)
|
|
2850
|
+
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
2851
|
+
matchers.push({ check: `_re${ri}.test(${kVar})` })
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2910
2854
|
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2855
|
+
// Build sub-schema checks inline so they share the parent helper scope.
|
|
2856
|
+
const subChecks = []
|
|
2857
|
+
for (let i = 0; i < ppEntries.length; i++) {
|
|
2858
|
+
const [, sub] = ppEntries[i]
|
|
2859
|
+
const subLines = []
|
|
2860
|
+
genCode(sub, `_ppv${pi}`, subLines, ctx)
|
|
2861
|
+
subChecks.push(subLines.join(';'))
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
2865
|
+
|
|
2866
|
+
const apSchema = typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null ? schema.additionalProperties : null
|
|
2867
|
+
if ((schema.additionalProperties === false && schema.properties) || apSchema) {
|
|
2868
|
+
// Unified loop: properties + patterns + propertyNames + additionalProperties.
|
|
2869
|
+
// A key that is neither declared nor matched by any pattern is
|
|
2870
|
+
// additional: rejected outright, or run through the additionalProperties
|
|
2871
|
+
// schema when there is one.
|
|
2872
|
+
handledPropertyNames = !!pn
|
|
2873
|
+
const propKeys = Object.keys(schema.properties || {})
|
|
2874
|
+
let apCheck = null
|
|
2875
|
+
if (apSchema) {
|
|
2876
|
+
const apLines = []
|
|
2877
|
+
genCode(apSchema, `_apv${pi}`, apLines, ctx)
|
|
2878
|
+
apCheck = apLines.join(';')
|
|
2879
|
+
}
|
|
2880
|
+
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
2881
|
+
// propertyNames checks (merged into same loop)
|
|
2882
|
+
if (pn) {
|
|
2883
|
+
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength})return false`)
|
|
2884
|
+
if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength})return false`)
|
|
2885
|
+
if (pn.pattern) {
|
|
2886
|
+
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
2887
|
+
if (fast) {
|
|
2888
|
+
lines.push(`if(!(${fast}))return false`)
|
|
2936
2889
|
} else {
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
// unevaluatedProperties: {schema} — validate unknown keys
|
|
2942
|
-
const ui = ctx.varCounter++
|
|
2943
|
-
const ukVar = `_uk${ui}`
|
|
2944
|
-
const subLines = []
|
|
2945
|
-
genCode(schema.unevaluatedProperties, `${v}[${ukVar}]`, subLines, ctx)
|
|
2946
|
-
if (subLines.length > 0) {
|
|
2947
|
-
const check = subLines.join(';')
|
|
2948
|
-
let skipKnown = ''
|
|
2949
|
-
if (knownKeys.length >= AP_LOOKUP_MIN) {
|
|
2950
|
-
const id = emitNameLookup(ctx, knownKeys)
|
|
2951
|
-
skipKnown = id !== null ? `if(${id}[${ukVar}]!==undefined)continue;` : `if(${knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')})continue;`
|
|
2952
|
-
} else if (knownKeys.length > 0) {
|
|
2953
|
-
skipKnown = `if(${knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')})continue;`
|
|
2954
|
-
}
|
|
2955
|
-
const inner = `for(var ${ukVar} in ${v}){${skipKnown}${check}}`
|
|
2956
|
-
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
2890
|
+
const ri = ctx.varCounter++
|
|
2891
|
+
ctx.closureVars.push(`_re${ri}`)
|
|
2892
|
+
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
2893
|
+
lines.push(`if(!_re${ri}.test(${kVar}))return false`)
|
|
2957
2894
|
}
|
|
2958
2895
|
}
|
|
2896
|
+
if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)})return false`)
|
|
2897
|
+
if (pn.enum) {
|
|
2898
|
+
const ei = ctx.varCounter++
|
|
2899
|
+
ctx.closureVars.push(`_es${ei}`)
|
|
2900
|
+
ctx.closureVals.push(new Set(pn.enum))
|
|
2901
|
+
lines.push(`if(!_es${ei}.has(${kVar}))return false`)
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
// Every matching pattern applies to every key, declared or not: a
|
|
2905
|
+
// declared key that also matches a pattern is validated by both (the
|
|
2906
|
+
// properties emitter handles its own subschema). A flag rather than an
|
|
2907
|
+
// if/else chain, because a chain returned false at the first pattern
|
|
2908
|
+
// that missed and wrongly rejected a key only a later pattern matched.
|
|
2909
|
+
if (ppEntries.length > 0) {
|
|
2910
|
+
lines.push(`let _pm${pi}=false`)
|
|
2911
|
+
for (let i = 0; i < ppEntries.length; i++) {
|
|
2912
|
+
lines.push(`if(${matchers[i].check}){_pm${pi}=true;const _ppv${pi}=${v}[${kVar}];${subChecks[i]}}`)
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
// A key that is neither declared nor matched is additional. switch on
|
|
2916
|
+
// the declared names (V8 compiles string cases to a jump table); no
|
|
2917
|
+
// switch at all when nothing is declared, since a switch with no case
|
|
2918
|
+
// clause is a syntax error.
|
|
2919
|
+
const additional = apCheck !== null ? `const _apv${pi}=${v}[${kVar}];${apCheck}` : `return false`
|
|
2920
|
+
const notMatched = ppEntries.length > 0 ? `if(!_pm${pi}){${additional}}` : additional
|
|
2921
|
+
if (propKeys.length) {
|
|
2922
|
+
const switchCases = propKeys.map(k => `case ${JSON.stringify(k)}:`).join('')
|
|
2923
|
+
lines.push(`switch(${kVar}){${switchCases}break;default:{${notMatched}}}`)
|
|
2959
2924
|
} else {
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2925
|
+
lines.push(notMatched)
|
|
2926
|
+
}
|
|
2927
|
+
lines.push(`}}`)
|
|
2928
|
+
} else {
|
|
2929
|
+
// No additionalProperties: validate matching keys + propertyNames
|
|
2930
|
+
handledPropertyNames = !!pn
|
|
2931
|
+
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
2932
|
+
// propertyNames checks (merged)
|
|
2933
|
+
if (pn) {
|
|
2934
|
+
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength})return false`)
|
|
2935
|
+
if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength})return false`)
|
|
2936
|
+
if (pn.pattern) {
|
|
2937
|
+
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
2938
|
+
if (fast) {
|
|
2939
|
+
lines.push(`if(!(${fast}))return false`)
|
|
2940
|
+
} else {
|
|
2941
|
+
const ri = ctx.varCounter++
|
|
2942
|
+
ctx.closureVars.push(`_re${ri}`)
|
|
2943
|
+
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
2944
|
+
lines.push(`if(!_re${ri}.test(${kVar}))return false`)
|
|
2966
2945
|
}
|
|
2967
2946
|
}
|
|
2968
|
-
if (
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2947
|
+
if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)})return false`)
|
|
2948
|
+
if (pn.enum) {
|
|
2949
|
+
const ei = ctx.varCounter++
|
|
2950
|
+
ctx.closureVars.push(`_es${ei}`)
|
|
2951
|
+
ctx.closureVals.push(new Set(pn.enum))
|
|
2952
|
+
lines.push(`if(!_es${ei}.has(${kVar}))return false`)
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
for (let i = 0; i < ppEntries.length; i++) {
|
|
2956
|
+
lines.push(`if(${matchers[i].check}){const _ppv${pi}=${v}[${kVar}];${subChecks[i]}}`)
|
|
2957
|
+
}
|
|
2958
|
+
lines.push(`}}`)
|
|
2959
|
+
}
|
|
2960
|
+
return handledPropertyNames
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount) {
|
|
2964
|
+
const evalResult = collectEvaluated(schema, ctx.schemaMap, ctx.rootDefs)
|
|
2965
|
+
|
|
2966
|
+
if (evalResult.allProps || schema.unevaluatedProperties === true) {
|
|
2967
|
+
// All props evaluated or unevaluatedProperties:true — no-op
|
|
2968
|
+
} else if (!evalResult.dynamic) {
|
|
2969
|
+
// Tier 1-2: all evaluated props known at compile-time — ZERO COST
|
|
2970
|
+
const knownKeys = evalResult.props
|
|
2971
|
+
const propCount = knownKeys.length
|
|
2972
|
+
|
|
2973
|
+
if (schema.unevaluatedProperties === false) {
|
|
2974
|
+
const allRequired = schema.required && schema.required.length >= propCount &&
|
|
2975
|
+
knownKeys.every(k => schema.required.includes(k))
|
|
2976
|
+
|
|
2977
|
+
let inner
|
|
2978
|
+
if (allRequired && propCount > 0) {
|
|
2979
|
+
// TRICK 1: required covers all — key count check only
|
|
2980
|
+
if (!earlyKeyCount) {
|
|
2981
|
+
// Adaptive: for-in for <=15 keys, Object.keys for >15
|
|
2982
|
+
inner = propCount <= 15
|
|
2983
|
+
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount})return false`
|
|
2984
|
+
: `if(Object.keys(${v}).length!==${propCount})return false`
|
|
2985
|
+
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
2976
2986
|
}
|
|
2987
|
+
// else: already emitted early (before properties)
|
|
2988
|
+
} else if (propCount > 0) {
|
|
2989
|
+
// TRICK 3: charCodeAt switch tree. Above the lookup threshold it is
|
|
2990
|
+
// the additionalProperties quadratic wearing a disguise: keys that
|
|
2991
|
+
// share a first character, ENV_0 through ENV_999 say, all land in
|
|
2992
|
+
// one case whose body is a chain of every name, per key of the
|
|
2993
|
+
// document. The hoisted name lookup is the same cure it was there.
|
|
2994
|
+
inner = propCount >= AP_LOOKUP_MIN
|
|
2995
|
+
? apMembershipCheck(ctx, knownKeys, v)
|
|
2996
|
+
: genCharCodeSwitch(knownKeys, v)
|
|
2997
|
+
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
2998
|
+
} else {
|
|
2999
|
+
inner = `for(var _k in ${v})return false`
|
|
3000
|
+
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
2977
3001
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
const
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3002
|
+
} else if (typeof schema.unevaluatedProperties === 'object') {
|
|
3003
|
+
// unevaluatedProperties: {schema} — validate unknown keys
|
|
3004
|
+
const ui = ctx.varCounter++
|
|
3005
|
+
const ukVar = `_uk${ui}`
|
|
3006
|
+
const subLines = []
|
|
3007
|
+
genCode(schema.unevaluatedProperties, `${v}[${ukVar}]`, subLines, ctx)
|
|
3008
|
+
if (subLines.length > 0) {
|
|
3009
|
+
const check = subLines.join(';')
|
|
3010
|
+
let skipKnown = ''
|
|
3011
|
+
if (knownKeys.length >= AP_LOOKUP_MIN) {
|
|
3012
|
+
const id = emitNameLookup(ctx, knownKeys)
|
|
3013
|
+
skipKnown = id !== null ? `if(${id}[${ukVar}]!==undefined)continue;` : `if(${knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')})continue;`
|
|
3014
|
+
} else if (knownKeys.length > 0) {
|
|
3015
|
+
skipKnown = `if(${knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')})continue;`
|
|
3016
|
+
}
|
|
3017
|
+
const inner = `for(var ${ukVar} in ${v}){${skipKnown}${check}}`
|
|
3018
|
+
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
} else {
|
|
3022
|
+
// Tier 2.5 / Tier 3: dynamic — runtime tracking needed
|
|
3023
|
+
// Compute base props: only unconditionally evaluated (properties, allOf-static, $ref)
|
|
3024
|
+
const baseResult = { props: [], items: null, allProps: false, allItems: false, dynamic: false }
|
|
3025
|
+
if (schema.properties) {
|
|
3026
|
+
for (const k of Object.keys(schema.properties)) {
|
|
3027
|
+
if (!baseResult.props.includes(k)) baseResult.props.push(k)
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
if (schema.allOf) {
|
|
3031
|
+
for (const sub of schema.allOf) {
|
|
3032
|
+
const subR = collectEvaluated(sub, ctx.schemaMap, ctx.rootDefs)
|
|
3033
|
+
if (!subR.dynamic && subR.props) {
|
|
3034
|
+
for (const k of subR.props) {
|
|
3035
|
+
if (!baseResult.props.includes(k)) baseResult.props.push(k)
|
|
3011
3036
|
}
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
const ei = ctx.varCounter++
|
|
3018
|
-
const evVar = `_ev${ei}`
|
|
3019
|
-
const bitMap = new Map()
|
|
3020
|
-
dynamicOnly.forEach((k, i) => bitMap.set(k, i))
|
|
3021
|
-
const branchMasks = branchProps.map(props => {
|
|
3022
|
-
let mask = 0
|
|
3023
|
-
for (const p of props) {
|
|
3024
|
-
if (bitMap.has(p)) mask |= (1 << bitMap.get(p))
|
|
3025
|
-
}
|
|
3026
|
-
return mask
|
|
3027
|
-
})
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
const baseProps = baseResult.props
|
|
3041
|
+
const branchKeyword = schema.anyOf ? 'anyOf' : schema.oneOf ? 'oneOf' : null
|
|
3028
3042
|
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3043
|
+
if (schema.unevaluatedProperties === false) {
|
|
3044
|
+
if (schema.if && (schema.then || schema.else) && !branchKeyword && !schema.patternProperties && !schema.dependentSchemas) {
|
|
3045
|
+
// Tier 2.5: if/then/else — re-emit if function + branch-inline duplication
|
|
3046
|
+
// Can't reuse _if from above (block-scoped), so regenerate
|
|
3047
|
+
const ifLines2 = []
|
|
3048
|
+
genCode(schema.if, '_iv2', ifLines2, ctx)
|
|
3049
|
+
const ufi = ctx.varCounter++
|
|
3050
|
+
const ifFn2 = ifLines2.length === 0
|
|
3051
|
+
? `function(_iv2){return true}`
|
|
3052
|
+
: `function(_iv2){${ifLines2.join(';')};return true}`
|
|
3053
|
+
|
|
3054
|
+
// if props are only evaluated when if matches (spec: failed applicators produce no annotations)
|
|
3055
|
+
const ifProps = []
|
|
3056
|
+
if (schema.if && schema.if.properties) ifProps.push(...Object.keys(schema.if.properties))
|
|
3057
|
+
const thenEval = schema.then ? collectEvaluated(schema.then, ctx.schemaMap, ctx.rootDefs) : { props: [] }
|
|
3058
|
+
const elseEval = schema.else ? collectEvaluated(schema.else, ctx.schemaMap, ctx.rootDefs) : { props: [] }
|
|
3059
|
+
const uniqueThen = [...new Set([...baseProps, ...ifProps, ...(thenEval.props || [])])]
|
|
3060
|
+
const uniqueElse = [...new Set([...baseProps, ...(elseEval.props || [])])]
|
|
3061
|
+
|
|
3062
|
+
const thenCheck = genCharCodeSwitch(uniqueThen, v)
|
|
3063
|
+
const elseCheck = genCharCodeSwitch(uniqueElse, v)
|
|
3064
|
+
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
3065
|
+
lines.push(`${guard}{const _uif${ufi}=${ifFn2};if(_uif${ufi}(${v})){${thenCheck}}else{${elseCheck}}}`)
|
|
3066
|
+
} else if (branchKeyword) {
|
|
3067
|
+
// Tier 3: anyOf/oneOf — runtime tracking
|
|
3068
|
+
const branches = schema[branchKeyword]
|
|
3069
|
+
const branchProps = []
|
|
3070
|
+
for (const sub of branches) {
|
|
3071
|
+
const subResult = collectEvaluated(sub, ctx.schemaMap, ctx.rootDefs)
|
|
3072
|
+
branchProps.push(subResult.props || [])
|
|
3073
|
+
}
|
|
3074
|
+
const allDynamicKeys = [...new Set(branchProps.flat())]
|
|
3075
|
+
const dynamicOnly = allDynamicKeys.filter(k => !baseProps.includes(k))
|
|
3056
3076
|
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
const groups = new Map()
|
|
3060
|
-
for (const k of dynamicOnly) {
|
|
3061
|
-
const cc = k.charCodeAt(0)
|
|
3062
|
-
if (!groups.has(cc)) groups.set(cc, [])
|
|
3063
|
-
groups.get(cc).push(k)
|
|
3064
|
-
}
|
|
3065
|
-
let switchCases = ''
|
|
3066
|
-
for (const [cc, groupKeys] of groups) {
|
|
3067
|
-
const cond = groupKeys.map(k => `_k===${JSON.stringify(k)}&&(${evVar}&${1 << bitMap.get(k)})`).join('||')
|
|
3068
|
-
switchCases += `case ${cc}:if(${cond})continue;break;`
|
|
3069
|
-
}
|
|
3070
|
-
const dynamicCheck = `switch(_k.charCodeAt(0)){${switchCases}default:break}`
|
|
3071
|
-
const inner = staticCheck
|
|
3072
|
-
? `for(var _k in ${v}){if(${staticCheck})continue;${dynamicCheck}return false}`
|
|
3073
|
-
: `for(var _k in ${v}){${dynamicCheck}return false}`
|
|
3074
|
-
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3075
|
-
} else {
|
|
3076
|
-
// Fallback: plain object tracking
|
|
3077
|
-
const ei = ctx.varCounter++
|
|
3078
|
-
const evVar = `_ev${ei}`
|
|
3079
|
-
const fns = []
|
|
3080
|
-
for (let i = 0; i < branches.length; i++) {
|
|
3081
|
-
const subLines2 = []
|
|
3082
|
-
genCode(branches[i], '_bv', subLines2, ctx)
|
|
3083
|
-
fns.push(subLines2.length === 0 ? `function(_bv){return true}` : `function(_bv){${subLines2.join(';')};return true}`)
|
|
3084
|
-
}
|
|
3085
|
-
const bfi = ctx.varCounter++
|
|
3086
|
-
ctx.closureVars.push(`_bk${bfi}`)
|
|
3087
|
-
ctx.closureVals.push(branchProps)
|
|
3088
|
-
lines.push(`{const ${evVar}={}`)
|
|
3089
|
-
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
3090
|
-
lines.push(`const _bf${bfi}=[${fns.join(',')}]`)
|
|
3091
|
-
if (branchKeyword === 'oneOf') {
|
|
3092
|
-
// Single pass: validate oneOf (exactly one) + track evaluated
|
|
3093
|
-
lines.push(`let _oc${bfi}=0;for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){_oc${bfi}++;for(const _p of _bk${bfi}[_bi])${evVar}[_p]=1;if(_oc${bfi}>1)return false}}if(_oc${bfi}!==1)return false`)
|
|
3094
|
-
} else {
|
|
3095
|
-
// Single pass: validate anyOf (at least one) + track all matching
|
|
3096
|
-
lines.push(`let _am${bfi}=false;for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){_am${bfi}=true;for(const _p of _bk${bfi}[_bi])${evVar}[_p]=1}}if(!_am${bfi})return false`)
|
|
3097
|
-
}
|
|
3098
|
-
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
3099
|
-
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3100
|
-
}
|
|
3101
|
-
} else if (schema.dependentSchemas) {
|
|
3102
|
-
// dependentSchemas: conditional merge at runtime
|
|
3077
|
+
if (dynamicOnly.length > 0 && dynamicOnly.length <= 32) {
|
|
3078
|
+
// TRICK 5: bit-packed evaluated set — SINGLE PASS (validation + tracking combined)
|
|
3103
3079
|
const ei = ctx.varCounter++
|
|
3104
3080
|
const evVar = `_ev${ei}`
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
}
|
|
3112
|
-
}
|
|
3113
|
-
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
3114
|
-
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3115
|
-
} else {
|
|
3116
|
-
// General fallback: collect all patternProperties from root + allOf sub-schemas + if
|
|
3117
|
-
// and use runtime regex matching
|
|
3118
|
-
const allPatterns = []
|
|
3119
|
-
if (schema.patternProperties) {
|
|
3120
|
-
allPatterns.push(...Object.keys(schema.patternProperties))
|
|
3121
|
-
}
|
|
3122
|
-
if (schema.allOf) {
|
|
3123
|
-
for (const sub of schema.allOf) {
|
|
3124
|
-
if (sub && sub.patternProperties) {
|
|
3125
|
-
allPatterns.push(...Object.keys(sub.patternProperties))
|
|
3126
|
-
}
|
|
3081
|
+
const bitMap = new Map()
|
|
3082
|
+
dynamicOnly.forEach((k, i) => bitMap.set(k, i))
|
|
3083
|
+
const branchMasks = branchProps.map(props => {
|
|
3084
|
+
let mask = 0
|
|
3085
|
+
for (const p of props) {
|
|
3086
|
+
if (bitMap.has(p)) mask |= (1 << bitMap.get(p))
|
|
3127
3087
|
}
|
|
3088
|
+
return mask
|
|
3089
|
+
})
|
|
3090
|
+
|
|
3091
|
+
// TRICK 4: Direct function calls — no array, no loop, V8 can inline
|
|
3092
|
+
const bfi = ctx.varCounter++
|
|
3093
|
+
lines.push(`{let ${evVar}=0`)
|
|
3094
|
+
const fnVars = []
|
|
3095
|
+
for (let i = 0; i < branches.length; i++) {
|
|
3096
|
+
const subLines2 = []
|
|
3097
|
+
genCode(branches[i], '_bv', subLines2, ctx)
|
|
3098
|
+
const fnVar = `_bf${bfi}_${i}`
|
|
3099
|
+
fnVars.push(fnVar)
|
|
3100
|
+
const fnBody = subLines2.length === 0 ? `function(_bv){return true}` : `function(_bv){${subLines2.join(';')};return true}`
|
|
3101
|
+
lines.push(`const ${fnVar}=${fnBody}`)
|
|
3128
3102
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
const ei = ctx.varCounter++
|
|
3135
|
-
const evVar = `_ev${ei}`
|
|
3136
|
-
lines.push(`{const ${evVar}={}`)
|
|
3137
|
-
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
3138
|
-
const reVars = []
|
|
3139
|
-
for (const pat of allPatterns) {
|
|
3140
|
-
const ri = ctx.varCounter++
|
|
3141
|
-
ctx.closureVars.push(`_ure${ri}`)
|
|
3142
|
-
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
3143
|
-
reVars.push(`_ure${ri}`)
|
|
3103
|
+
if (branchKeyword === 'oneOf') {
|
|
3104
|
+
// oneOf: exactly one must match — direct calls
|
|
3105
|
+
lines.push(`let _oc${bfi}=0`)
|
|
3106
|
+
for (let i = 0; i < branches.length; i++) {
|
|
3107
|
+
lines.push(`if(${fnVars[i]}(${v})){_oc${bfi}++;${evVar}=${branchMasks[i]};if(_oc${bfi}>1)return false}`)
|
|
3144
3108
|
}
|
|
3145
|
-
if
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
? `function(_iv2){return true}`
|
|
3152
|
-
: `function(_iv2){${ifLines2.join(';')};return true}`
|
|
3153
|
-
// Mark keys matching if's patterns as evaluated only when if passes
|
|
3154
|
-
const ifPatterns = schema.if.patternProperties ? Object.keys(schema.if.patternProperties) : []
|
|
3155
|
-
const ifReVars = []
|
|
3156
|
-
for (const pat of ifPatterns) {
|
|
3157
|
-
const ri = ctx.varCounter++
|
|
3158
|
-
ctx.closureVars.push(`_ure${ri}`)
|
|
3159
|
-
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
3160
|
-
ifReVars.push(`_ure${ri}`)
|
|
3161
|
-
}
|
|
3162
|
-
const rootReVars = []
|
|
3163
|
-
if (schema.patternProperties) {
|
|
3164
|
-
for (const pat of Object.keys(schema.patternProperties)) {
|
|
3165
|
-
const ri = ctx.varCounter++
|
|
3166
|
-
ctx.closureVars.push(`_ure${ri}`)
|
|
3167
|
-
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
3168
|
-
rootReVars.push(`_ure${ri}`)
|
|
3169
|
-
}
|
|
3170
|
-
}
|
|
3171
|
-
const rootPatCheck = rootReVars.map(rv => `if(${rv}.test(_k))continue;`).join('')
|
|
3172
|
-
const ifPatCheck = ifReVars.map(rv => `if(${rv}.test(_k))continue;`).join('')
|
|
3173
|
-
const inner = `const _uif${ufi}=${ifFn};if(_uif${ufi}(${v})){for(var _k in ${v}){if(${evVar}[_k])continue;${rootPatCheck}${ifPatCheck}return false}}else{for(var _k in ${v}){if(${evVar}[_k])continue;${rootPatCheck}return false}}`
|
|
3174
|
-
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3175
|
-
} else {
|
|
3176
|
-
const inner = `for(var _k in ${v}){if(${evVar}[_k])continue;${reVars.map(rv => `if(${rv}.test(_k)){${evVar}[_k]=1;continue}`).join('')}return false}`
|
|
3177
|
-
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3109
|
+
lines.push(`if(_oc${bfi}!==1)return false`)
|
|
3110
|
+
} else {
|
|
3111
|
+
// anyOf: at least one must match — direct calls, collect all
|
|
3112
|
+
lines.push(`let _am${bfi}=false`)
|
|
3113
|
+
for (let i = 0; i < branches.length; i++) {
|
|
3114
|
+
lines.push(`if(${fnVars[i]}(${v})){_am${bfi}=true;${evVar}|=${branchMasks[i]}}`)
|
|
3178
3115
|
}
|
|
3116
|
+
lines.push(`if(!_am${bfi})return false`)
|
|
3179
3117
|
}
|
|
3180
|
-
}
|
|
3181
|
-
} else if (typeof schema.unevaluatedProperties === 'object') {
|
|
3182
|
-
// Tier 3 with schema: validate unknown keys against sub-schema
|
|
3183
|
-
const ei = ctx.varCounter++
|
|
3184
|
-
const evVar = `_ev${ei}`
|
|
3185
|
-
const ukVar = `_uk${ei}`
|
|
3186
|
-
lines.push(`{const ${evVar}={}`)
|
|
3187
|
-
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
3188
3118
|
|
|
3189
|
-
|
|
3190
|
-
const
|
|
3191
|
-
const
|
|
3192
|
-
for (const
|
|
3193
|
-
const
|
|
3194
|
-
|
|
3119
|
+
// Final check: static keys inline + dynamic keys via bitmask
|
|
3120
|
+
const staticCheck = baseProps.length > 0 ? baseProps.map(k => `_k===${JSON.stringify(k)}`).join('||') : ''
|
|
3121
|
+
const groups = new Map()
|
|
3122
|
+
for (const k of dynamicOnly) {
|
|
3123
|
+
const cc = k.charCodeAt(0)
|
|
3124
|
+
if (!groups.has(cc)) groups.set(cc, [])
|
|
3125
|
+
groups.get(cc).push(k)
|
|
3195
3126
|
}
|
|
3127
|
+
let switchCases = ''
|
|
3128
|
+
for (const [cc, groupKeys] of groups) {
|
|
3129
|
+
const cond = groupKeys.map(k => `_k===${JSON.stringify(k)}&&(${evVar}&${1 << bitMap.get(k)})`).join('||')
|
|
3130
|
+
switchCases += `case ${cc}:if(${cond})continue;break;`
|
|
3131
|
+
}
|
|
3132
|
+
const dynamicCheck = `switch(_k.charCodeAt(0)){${switchCases}default:break}`
|
|
3133
|
+
const inner = staticCheck
|
|
3134
|
+
? `for(var _k in ${v}){if(${staticCheck})continue;${dynamicCheck}return false}`
|
|
3135
|
+
: `for(var _k in ${v}){${dynamicCheck}return false}`
|
|
3136
|
+
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3137
|
+
} else {
|
|
3138
|
+
// Fallback: plain object tracking
|
|
3139
|
+
const ei = ctx.varCounter++
|
|
3140
|
+
const evVar = `_ev${ei}`
|
|
3196
3141
|
const fns = []
|
|
3197
3142
|
for (let i = 0; i < branches.length; i++) {
|
|
3198
3143
|
const subLines2 = []
|
|
@@ -3202,85 +3147,114 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3202
3147
|
const bfi = ctx.varCounter++
|
|
3203
3148
|
ctx.closureVars.push(`_bk${bfi}`)
|
|
3204
3149
|
ctx.closureVals.push(branchProps)
|
|
3150
|
+
lines.push(`{const ${evVar}={}`)
|
|
3151
|
+
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
3205
3152
|
lines.push(`const _bf${bfi}=[${fns.join(',')}]`)
|
|
3206
3153
|
if (branchKeyword === 'oneOf') {
|
|
3207
|
-
|
|
3154
|
+
// Single pass: validate oneOf (exactly one) + track evaluated
|
|
3155
|
+
lines.push(`let _oc${bfi}=0;for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){_oc${bfi}++;for(const _p of _bk${bfi}[_bi])${evVar}[_p]=1;if(_oc${bfi}>1)return false}}if(_oc${bfi}!==1)return false`)
|
|
3208
3156
|
} else {
|
|
3209
|
-
|
|
3157
|
+
// Single pass: validate anyOf (at least one) + track all matching
|
|
3158
|
+
lines.push(`let _am${bfi}=false;for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){_am${bfi}=true;for(const _p of _bk${bfi}[_bi])${evVar}[_p]=1}}if(!_am${bfi})return false`)
|
|
3210
3159
|
}
|
|
3160
|
+
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
3161
|
+
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3211
3162
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3163
|
+
} else if (schema.dependentSchemas) {
|
|
3164
|
+
// dependentSchemas: conditional merge at runtime
|
|
3165
|
+
const ei = ctx.varCounter++
|
|
3166
|
+
const evVar = `_ev${ei}`
|
|
3167
|
+
lines.push(`{const ${evVar}={}`)
|
|
3168
|
+
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
3169
|
+
for (const [trigger, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
3170
|
+
const depResult = collectEvaluated(depSchema, ctx.schemaMap, ctx.rootDefs)
|
|
3171
|
+
if (depResult.props && depResult.props.length > 0) {
|
|
3172
|
+
lines.push(`if(${ownKeyExpr(ctx, v, trigger)}){${depResult.props.map(k => `${evVar}[${JSON.stringify(k)}]=1`).join(';')}}`)
|
|
3173
|
+
}
|
|
3221
3174
|
}
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
// Check if allItems from anyOf/oneOf branches with `items` keyword needs dynamic tracking
|
|
3231
|
-
const branchKw = schema.anyOf ? 'anyOf' : schema.oneOf ? 'oneOf' : null
|
|
3232
|
-
const hasConditionalItems = evalResult.allItems && evalResult.dynamic && branchKw &&
|
|
3233
|
-
schema[branchKw].some(sub => sub && typeof sub === 'object' && ((sub.items && typeof sub.items === 'object') || sub.items === true))
|
|
3234
|
-
|
|
3235
|
-
if (schema.unevaluatedItems === true || (evalResult.allItems && !hasConditionalItems)) {
|
|
3236
|
-
// All items evaluated or unevaluatedItems:true — no-op
|
|
3237
|
-
} else if (!evalResult.dynamic) {
|
|
3238
|
-
// Static: all evaluated items known at compile-time
|
|
3239
|
-
if (schema.unevaluatedItems === false) {
|
|
3240
|
-
// TRICK 6: Array.length comparison only
|
|
3241
|
-
const maxIdx = evalResult.items || 0
|
|
3242
|
-
const inner = `if(${v}.length>${maxIdx})return false`
|
|
3243
|
-
_deferOrInline(ctx, lines, v, isArr ? inner : `if(Array.isArray(${v})){${inner}}`)
|
|
3244
|
-
} else if (typeof schema.unevaluatedItems === 'object') {
|
|
3245
|
-
const maxIdx = evalResult.items || 0
|
|
3246
|
-
const ui = ctx.varCounter++
|
|
3247
|
-
const elemVar = `_ue${ui}`
|
|
3248
|
-
const idxVar = `_ui${ui}`
|
|
3249
|
-
const subLines = []
|
|
3250
|
-
genCode(schema.unevaluatedItems, elemVar, subLines, ctx)
|
|
3251
|
-
if (subLines.length > 0) {
|
|
3252
|
-
const check = subLines.join(';')
|
|
3253
|
-
const inner = `for(let ${idxVar}=${maxIdx};${idxVar}<${v}.length;${idxVar}++){const ${elemVar}=${v}[${idxVar}];${check}}`
|
|
3254
|
-
_deferOrInline(ctx, lines, v, isArr ? inner : `if(Array.isArray(${v})){${inner}}`)
|
|
3175
|
+
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
3176
|
+
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3177
|
+
} else {
|
|
3178
|
+
// General fallback: collect all patternProperties from root + allOf sub-schemas + if
|
|
3179
|
+
// and use runtime regex matching
|
|
3180
|
+
const allPatterns = []
|
|
3181
|
+
if (schema.patternProperties) {
|
|
3182
|
+
allPatterns.push(...Object.keys(schema.patternProperties))
|
|
3255
3183
|
}
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3184
|
+
if (schema.allOf) {
|
|
3185
|
+
for (const sub of schema.allOf) {
|
|
3186
|
+
if (sub && sub.patternProperties) {
|
|
3187
|
+
allPatterns.push(...Object.keys(sub.patternProperties))
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
// lone if (no then/else) still contributes annotations when it passes
|
|
3192
|
+
if (schema.if && !schema.then && !schema.else && schema.if.patternProperties) {
|
|
3193
|
+
allPatterns.push(...Object.keys(schema.if.patternProperties))
|
|
3194
|
+
}
|
|
3195
|
+
if (allPatterns.length > 0) {
|
|
3196
|
+
const ei = ctx.varCounter++
|
|
3197
|
+
const evVar = `_ev${ei}`
|
|
3198
|
+
lines.push(`{const ${evVar}={}`)
|
|
3199
|
+
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
3200
|
+
const reVars = []
|
|
3201
|
+
for (const pat of allPatterns) {
|
|
3202
|
+
const ri = ctx.varCounter++
|
|
3203
|
+
ctx.closureVars.push(`_ure${ri}`)
|
|
3204
|
+
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
3205
|
+
reVars.push(`_ure${ri}`)
|
|
3206
|
+
}
|
|
3207
|
+
if (schema.if && !schema.then && !schema.else) {
|
|
3208
|
+
// Lone if: run the if check first; if it passes, its patternProperties contribute
|
|
3209
|
+
const ifLines2 = []
|
|
3210
|
+
genCode(schema.if, '_iv2', ifLines2, ctx)
|
|
3211
|
+
const ufi = ctx.varCounter++
|
|
3212
|
+
const ifFn = ifLines2.length === 0
|
|
3213
|
+
? `function(_iv2){return true}`
|
|
3214
|
+
: `function(_iv2){${ifLines2.join(';')};return true}`
|
|
3215
|
+
// Mark keys matching if's patterns as evaluated only when if passes
|
|
3216
|
+
const ifPatterns = schema.if.patternProperties ? Object.keys(schema.if.patternProperties) : []
|
|
3217
|
+
const ifReVars = []
|
|
3218
|
+
for (const pat of ifPatterns) {
|
|
3219
|
+
const ri = ctx.varCounter++
|
|
3220
|
+
ctx.closureVars.push(`_ure${ri}`)
|
|
3221
|
+
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
3222
|
+
ifReVars.push(`_ure${ri}`)
|
|
3223
|
+
}
|
|
3224
|
+
const rootReVars = []
|
|
3225
|
+
if (schema.patternProperties) {
|
|
3226
|
+
for (const pat of Object.keys(schema.patternProperties)) {
|
|
3227
|
+
const ri = ctx.varCounter++
|
|
3228
|
+
ctx.closureVars.push(`_ure${ri}`)
|
|
3229
|
+
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
3230
|
+
rootReVars.push(`_ure${ri}`)
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
const rootPatCheck = rootReVars.map(rv => `if(${rv}.test(_k))continue;`).join('')
|
|
3234
|
+
const ifPatCheck = ifReVars.map(rv => `if(${rv}.test(_k))continue;`).join('')
|
|
3235
|
+
const inner = `const _uif${ufi}=${ifFn};if(_uif${ufi}(${v})){for(var _k in ${v}){if(${evVar}[_k])continue;${rootPatCheck}${ifPatCheck}return false}}else{for(var _k in ${v}){if(${evVar}[_k])continue;${rootPatCheck}return false}}`
|
|
3236
|
+
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3237
|
+
} else {
|
|
3238
|
+
const inner = `for(var _k in ${v}){if(${evVar}[_k])continue;${reVars.map(rv => `if(${rv}.test(_k)){${evVar}[_k]=1;continue}`).join('')}return false}`
|
|
3239
|
+
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3240
|
+
}
|
|
3268
3241
|
}
|
|
3269
3242
|
}
|
|
3270
|
-
|
|
3271
|
-
|
|
3243
|
+
} else if (typeof schema.unevaluatedProperties === 'object') {
|
|
3244
|
+
// Tier 3 with schema: validate unknown keys against sub-schema
|
|
3245
|
+
const ei = ctx.varCounter++
|
|
3246
|
+
const evVar = `_ev${ei}`
|
|
3247
|
+
const ukVar = `_uk${ei}`
|
|
3248
|
+
lines.push(`{const ${evVar}={}`)
|
|
3249
|
+
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
3272
3250
|
|
|
3273
|
-
if (branchKeyword
|
|
3274
|
-
// anyOf/oneOf: each branch may evaluate different number of items
|
|
3251
|
+
if (branchKeyword) {
|
|
3275
3252
|
const branches = schema[branchKeyword]
|
|
3276
|
-
const
|
|
3277
|
-
const branchAllItems = []
|
|
3253
|
+
const branchProps = []
|
|
3278
3254
|
for (const sub of branches) {
|
|
3279
|
-
const
|
|
3280
|
-
|
|
3281
|
-
branchAllItems.push(subR.allItems)
|
|
3255
|
+
const subResult = collectEvaluated(sub, ctx.schemaMap, ctx.rootDefs)
|
|
3256
|
+
branchProps.push(subResult.props || [])
|
|
3282
3257
|
}
|
|
3283
|
-
// Runtime: find max evaluated index across all matching branches
|
|
3284
3258
|
const fns = []
|
|
3285
3259
|
for (let i = 0; i < branches.length; i++) {
|
|
3286
3260
|
const subLines2 = []
|
|
@@ -3288,108 +3262,194 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3288
3262
|
fns.push(subLines2.length === 0 ? `function(_bv){return true}` : `function(_bv){${subLines2.join(';')};return true}`)
|
|
3289
3263
|
}
|
|
3290
3264
|
const bfi = ctx.varCounter++
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
lines.push(`{let ${evVar}=${baseIdx}`)
|
|
3265
|
+
ctx.closureVars.push(`_bk${bfi}`)
|
|
3266
|
+
ctx.closureVals.push(branchProps)
|
|
3294
3267
|
lines.push(`const _bf${bfi}=[${fns.join(',')}]`)
|
|
3295
|
-
const maxExprs = branchMaxIdx.map((m, i) => {
|
|
3296
|
-
if (branchAllItems[i]) return `_bi===${i}?${v}.length`
|
|
3297
|
-
return `_bi===${i}?${Math.max(m, baseIdx)}`
|
|
3298
|
-
}).join(':') + `:${baseIdx}`
|
|
3299
3268
|
if (branchKeyword === 'oneOf') {
|
|
3300
|
-
lines.push(`for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){${
|
|
3301
|
-
} else {
|
|
3302
|
-
lines.push(`for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){const _m=${maxExprs};if(_m>${evVar})${evVar}=_m}}`)
|
|
3303
|
-
}
|
|
3304
|
-
if (schema.unevaluatedItems === false) {
|
|
3305
|
-
const inner = `if(${v}.length>${evVar})return false`
|
|
3306
|
-
_deferOrInline(ctx, lines, v, isArr ? inner + '}' : `if(Array.isArray(${v})){${inner}}}`)
|
|
3269
|
+
lines.push(`for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){for(const _p of _bk${bfi}[_bi])${evVar}[_p]=1;break}}`)
|
|
3307
3270
|
} else {
|
|
3308
|
-
const
|
|
3309
|
-
const elemVar = `_ue${ui}`
|
|
3310
|
-
const idxVar = `_ui${ui}`
|
|
3311
|
-
const subLines = []
|
|
3312
|
-
genCode(schema.unevaluatedItems, elemVar, subLines, ctx)
|
|
3313
|
-
if (subLines.length > 0) {
|
|
3314
|
-
const check = subLines.join(';')
|
|
3315
|
-
const inner = `for(let ${idxVar}=${evVar};${idxVar}<${v}.length;${idxVar}++){const ${elemVar}=${v}[${idxVar}];${check}}`
|
|
3316
|
-
_deferOrInline(ctx, lines, v, isArr ? inner + '}' : `if(Array.isArray(${v})){${inner}}}`)
|
|
3317
|
-
} else {
|
|
3318
|
-
lines.push('}')
|
|
3319
|
-
}
|
|
3271
|
+
lines.push(`for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){for(const _p of _bk${bfi}[_bi])${evVar}[_p]=1}}`)
|
|
3320
3272
|
}
|
|
3321
|
-
}
|
|
3322
|
-
// if/then/else (or lone if): branch-specific max index
|
|
3323
|
-
const ifEval = collectEvaluated(schema.if, ctx.schemaMap, ctx.rootDefs)
|
|
3324
|
-
const thenEval = schema.then ? collectEvaluated(schema.then, ctx.schemaMap, ctx.rootDefs) : { items: null }
|
|
3325
|
-
const elseEval = schema.else ? collectEvaluated(schema.else, ctx.schemaMap, ctx.rootDefs) : { items: null }
|
|
3326
|
-
const ifIdx = ifEval.items || 0
|
|
3327
|
-
const thenIdx = Math.max(baseIdx, ifIdx, thenEval.items || 0)
|
|
3328
|
-
const elseIdx = Math.max(baseIdx, elseEval.items || 0)
|
|
3273
|
+
}
|
|
3329
3274
|
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
const
|
|
3334
|
-
|
|
3335
|
-
|
|
3275
|
+
const subLines2 = []
|
|
3276
|
+
genCode(schema.unevaluatedProperties, `${v}[${ukVar}]`, subLines2, ctx)
|
|
3277
|
+
if (subLines2.length > 0) {
|
|
3278
|
+
const check = subLines2.join(';')
|
|
3279
|
+
const inner = `for(var ${ukVar} in ${v}){if(${evVar}[${ukVar}])continue;${check}}`
|
|
3280
|
+
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3281
|
+
} else {
|
|
3282
|
+
lines.push('}')
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3336
3287
|
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3288
|
+
function genUnevaluatedItemsNode(schema, v, lines, ctx, knownType, isArr) {
|
|
3289
|
+
const evalResult = collectEvaluated(schema, ctx.schemaMap, ctx.rootDefs)
|
|
3290
|
+
|
|
3291
|
+
// Check if allItems from anyOf/oneOf branches with `items` keyword needs dynamic tracking
|
|
3292
|
+
const branchKw = schema.anyOf ? 'anyOf' : schema.oneOf ? 'oneOf' : null
|
|
3293
|
+
const hasConditionalItems = evalResult.allItems && evalResult.dynamic && branchKw &&
|
|
3294
|
+
schema[branchKw].some(sub => sub && typeof sub === 'object' && ((sub.items && typeof sub.items === 'object') || sub.items === true))
|
|
3295
|
+
|
|
3296
|
+
if (schema.unevaluatedItems === true || (evalResult.allItems && !hasConditionalItems)) {
|
|
3297
|
+
// All items evaluated or unevaluatedItems:true — no-op
|
|
3298
|
+
} else if (!evalResult.dynamic) {
|
|
3299
|
+
// Static: all evaluated items known at compile-time
|
|
3300
|
+
if (schema.unevaluatedItems === false) {
|
|
3301
|
+
// TRICK 6: Array.length comparison only
|
|
3302
|
+
const maxIdx = evalResult.items || 0
|
|
3303
|
+
const inner = `if(${v}.length>${maxIdx})return false`
|
|
3304
|
+
_deferOrInline(ctx, lines, v, isArr ? inner : `if(Array.isArray(${v})){${inner}}`)
|
|
3305
|
+
} else if (typeof schema.unevaluatedItems === 'object') {
|
|
3306
|
+
const maxIdx = evalResult.items || 0
|
|
3307
|
+
const ui = ctx.varCounter++
|
|
3308
|
+
const elemVar = `_ue${ui}`
|
|
3309
|
+
const idxVar = `_ui${ui}`
|
|
3310
|
+
const subLines = []
|
|
3311
|
+
genCode(schema.unevaluatedItems, elemVar, subLines, ctx)
|
|
3312
|
+
if (subLines.length > 0) {
|
|
3313
|
+
const check = subLines.join(';')
|
|
3314
|
+
const inner = `for(let ${idxVar}=${maxIdx};${idxVar}<${v}.length;${idxVar}++){const ${elemVar}=${v}[${idxVar}];${check}}`
|
|
3315
|
+
_deferOrInline(ctx, lines, v, isArr ? inner : `if(Array.isArray(${v})){${inner}}`)
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
} else {
|
|
3319
|
+
// Dynamic: runtime tracking of max evaluated index
|
|
3320
|
+
// Compute baseIdx from unconditional sources only (root prefixItems/items, allOf)
|
|
3321
|
+
let baseIdx = 0
|
|
3322
|
+
if (schema.prefixItems) baseIdx = Math.max(baseIdx, schema.prefixItems.length)
|
|
3323
|
+
if (schema.items && typeof schema.items === 'object') baseIdx = Infinity // items: schema → all evaluated
|
|
3324
|
+
if (schema.allOf) {
|
|
3325
|
+
for (const sub of schema.allOf) {
|
|
3326
|
+
const subR = collectEvaluated(sub, ctx.schemaMap, ctx.rootDefs)
|
|
3327
|
+
if (subR.items !== null) baseIdx = Math.max(baseIdx, subR.items)
|
|
3328
|
+
if (subR.allItems) baseIdx = Infinity
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
if (baseIdx === Infinity) baseIdx = 0 // allItems already handled above
|
|
3332
|
+
const branchKeyword = schema.anyOf ? 'anyOf' : schema.oneOf ? 'oneOf' : null
|
|
3333
|
+
|
|
3334
|
+
if (branchKeyword && (schema.unevaluatedItems === false || typeof schema.unevaluatedItems === 'object')) {
|
|
3335
|
+
// anyOf/oneOf: each branch may evaluate different number of items
|
|
3336
|
+
const branches = schema[branchKeyword]
|
|
3337
|
+
const branchMaxIdx = []
|
|
3338
|
+
const branchAllItems = []
|
|
3339
|
+
for (const sub of branches) {
|
|
3340
|
+
const subR = collectEvaluated(sub, ctx.schemaMap, ctx.rootDefs)
|
|
3341
|
+
branchMaxIdx.push(subR.items || 0)
|
|
3342
|
+
branchAllItems.push(subR.allItems)
|
|
3343
|
+
}
|
|
3344
|
+
// Runtime: find max evaluated index across all matching branches
|
|
3345
|
+
const fns = []
|
|
3346
|
+
for (let i = 0; i < branches.length; i++) {
|
|
3347
|
+
const subLines2 = []
|
|
3348
|
+
genCode(branches[i], '_bv', subLines2, ctx)
|
|
3349
|
+
fns.push(subLines2.length === 0 ? `function(_bv){return true}` : `function(_bv){${subLines2.join(';')};return true}`)
|
|
3350
|
+
}
|
|
3351
|
+
const bfi = ctx.varCounter++
|
|
3352
|
+
const ei = ctx.varCounter++
|
|
3353
|
+
const evVar = `_eidx${ei}`
|
|
3354
|
+
lines.push(`{let ${evVar}=${baseIdx}`)
|
|
3355
|
+
lines.push(`const _bf${bfi}=[${fns.join(',')}]`)
|
|
3356
|
+
const maxExprs = branchMaxIdx.map((m, i) => {
|
|
3357
|
+
if (branchAllItems[i]) return `_bi===${i}?${v}.length`
|
|
3358
|
+
return `_bi===${i}?${Math.max(m, baseIdx)}`
|
|
3359
|
+
}).join(':') + `:${baseIdx}`
|
|
3360
|
+
if (branchKeyword === 'oneOf') {
|
|
3361
|
+
lines.push(`for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){${evVar}=${maxExprs};break}}`)
|
|
3362
|
+
} else {
|
|
3363
|
+
lines.push(`for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){const _m=${maxExprs};if(_m>${evVar})${evVar}=_m}}`)
|
|
3364
|
+
}
|
|
3365
|
+
if (schema.unevaluatedItems === false) {
|
|
3366
|
+
const inner = `if(${v}.length>${evVar})return false`
|
|
3367
|
+
_deferOrInline(ctx, lines, v, isArr ? inner + '}' : `if(Array.isArray(${v})){${inner}}}`)
|
|
3368
|
+
} else {
|
|
3369
|
+
const ui = ctx.varCounter++
|
|
3370
|
+
const elemVar = `_ue${ui}`
|
|
3371
|
+
const idxVar = `_ui${ui}`
|
|
3372
|
+
const subLines = []
|
|
3373
|
+
genCode(schema.unevaluatedItems, elemVar, subLines, ctx)
|
|
3374
|
+
if (subLines.length > 0) {
|
|
3375
|
+
const check = subLines.join(';')
|
|
3376
|
+
const inner = `for(let ${idxVar}=${evVar};${idxVar}<${v}.length;${idxVar}++){const ${elemVar}=${v}[${idxVar}];${check}}`
|
|
3377
|
+
_deferOrInline(ctx, lines, v, isArr ? inner + '}' : `if(Array.isArray(${v})){${inner}}}`)
|
|
3378
|
+
} else {
|
|
3379
|
+
lines.push('}')
|
|
3360
3380
|
}
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3381
|
+
}
|
|
3382
|
+
} else if (schema.if && (schema.unevaluatedItems === false || typeof schema.unevaluatedItems === 'object')) {
|
|
3383
|
+
// if/then/else (or lone if): branch-specific max index
|
|
3384
|
+
const ifEval = collectEvaluated(schema.if, ctx.schemaMap, ctx.rootDefs)
|
|
3385
|
+
const thenEval = schema.then ? collectEvaluated(schema.then, ctx.schemaMap, ctx.rootDefs) : { items: null }
|
|
3386
|
+
const elseEval = schema.else ? collectEvaluated(schema.else, ctx.schemaMap, ctx.rootDefs) : { items: null }
|
|
3387
|
+
const ifIdx = ifEval.items || 0
|
|
3388
|
+
const thenIdx = Math.max(baseIdx, ifIdx, thenEval.items || 0)
|
|
3389
|
+
const elseIdx = Math.max(baseIdx, elseEval.items || 0)
|
|
3390
|
+
|
|
3391
|
+
const ifLines2 = []
|
|
3392
|
+
genCode(schema.if, '_iv3', ifLines2, ctx)
|
|
3393
|
+
const ufi = ctx.varCounter++
|
|
3394
|
+
const ifFn3 = ifLines2.length === 0
|
|
3395
|
+
? `function(_iv3){return true}`
|
|
3396
|
+
: `function(_iv3){${ifLines2.join(';')};return true}`
|
|
3397
|
+
|
|
3398
|
+
if (schema.unevaluatedItems === false) {
|
|
3399
|
+
const guard = isArr ? '' : `if(Array.isArray(${v}))`
|
|
3400
|
+
lines.push(`${guard}{const _uif${ufi}=${ifFn3};if(_uif${ufi}(${v})){if(${v}.length>${thenIdx})return false}else{if(${v}.length>${elseIdx})return false}}`)
|
|
3401
|
+
}
|
|
3402
|
+
} else if ((schema.contains || (schema.allOf && schema.allOf.some(s => s && s.contains))) && (schema.unevaluatedItems === false || typeof schema.unevaluatedItems === 'object')) {
|
|
3403
|
+
// contains + unevaluatedItems: per-item tracking of which items are matched by contains
|
|
3404
|
+
// Collect contains from root and allOf sub-schemas
|
|
3405
|
+
const allContains = []
|
|
3406
|
+
if (schema.contains) allContains.push(schema.contains)
|
|
3407
|
+
if (schema.allOf) {
|
|
3408
|
+
for (const sub of schema.allOf) {
|
|
3409
|
+
if (sub && sub.contains) allContains.push(sub.contains)
|
|
3367
3410
|
}
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3411
|
+
}
|
|
3412
|
+
const ci = ctx.varCounter++
|
|
3413
|
+
const evArr = `_cev${ci}`
|
|
3414
|
+
const containsFns = []
|
|
3415
|
+
for (const c of allContains) {
|
|
3416
|
+
const cLines = []
|
|
3417
|
+
genCode(c, '_cv', cLines, ctx)
|
|
3418
|
+
containsFns.push(cLines.length === 0
|
|
3419
|
+
? `function(_cv){return true}`
|
|
3420
|
+
: `function(_cv){${cLines.join(';')};return true}`)
|
|
3421
|
+
}
|
|
3422
|
+
const cfnArr = `_cfn${ci}`
|
|
3423
|
+
lines.push(`{const ${cfnArr}=[${containsFns.join(',')}]`)
|
|
3424
|
+
// Mark items evaluated by prefixItems
|
|
3425
|
+
lines.push(`const ${evArr}=[]`)
|
|
3426
|
+
if (baseIdx > 0) {
|
|
3427
|
+
lines.push(`for(let _i=0;_i<${Math.min(baseIdx, 1000)};_i++)${evArr}[_i]=true`)
|
|
3428
|
+
}
|
|
3429
|
+
// Mark items matched by each contains function
|
|
3430
|
+
lines.push(`if(Array.isArray(${v})){for(let _ci=0;_ci<${v}.length;_ci++){for(let _cj=0;_cj<${cfnArr}.length;_cj++){if(${cfnArr}[_cj](${v}[_ci])){${evArr}[_ci]=true;break}}}}`)
|
|
3431
|
+
if (schema.unevaluatedItems === false) {
|
|
3432
|
+
const inner = `if(Array.isArray(${v})){for(let _ci=0;_ci<${v}.length;_ci++){if(!${evArr}[_ci])return false}}`
|
|
3433
|
+
_deferOrInline(ctx, lines, v, inner + '}')
|
|
3434
|
+
} else {
|
|
3435
|
+
// unevaluatedItems: {schema}
|
|
3436
|
+
const ui = ctx.varCounter++
|
|
3437
|
+
const elemVar = `_ue${ui}`
|
|
3438
|
+
const subLines = []
|
|
3439
|
+
genCode(schema.unevaluatedItems, elemVar, subLines, ctx)
|
|
3440
|
+
if (subLines.length > 0) {
|
|
3441
|
+
const check = subLines.join(';')
|
|
3442
|
+
const inner = `if(Array.isArray(${v})){for(let _ci=0;_ci<${v}.length;_ci++){if(!${evArr}[_ci]){const ${elemVar}=${v}[_ci];${check}}}}`
|
|
3443
|
+
_deferOrInline(ctx, lines, v, inner + '}')
|
|
3373
3444
|
} else {
|
|
3374
|
-
|
|
3375
|
-
const ui = ctx.varCounter++
|
|
3376
|
-
const elemVar = `_ue${ui}`
|
|
3377
|
-
const subLines = []
|
|
3378
|
-
genCode(schema.unevaluatedItems, elemVar, subLines, ctx)
|
|
3379
|
-
if (subLines.length > 0) {
|
|
3380
|
-
const check = subLines.join(';')
|
|
3381
|
-
const inner = `if(Array.isArray(${v})){for(let _ci=0;_ci<${v}.length;_ci++){if(!${evArr}[_ci]){const ${elemVar}=${v}[_ci];${check}}}}`
|
|
3382
|
-
_deferOrInline(ctx, lines, v, inner + '}')
|
|
3383
|
-
} else {
|
|
3384
|
-
lines.push('}')
|
|
3385
|
-
}
|
|
3445
|
+
lines.push('}')
|
|
3386
3446
|
}
|
|
3387
|
-
} else if (schema.unevaluatedItems === false) {
|
|
3388
|
-
// Fallback: use static base index (may not be fully correct for all dynamic cases)
|
|
3389
|
-
const maxIdx = evalResult.items || 0
|
|
3390
|
-
const inner = `if(${v}.length>${maxIdx})return false`
|
|
3391
|
-
_deferOrInline(ctx, lines, v, isArr ? inner : `if(Array.isArray(${v})){${inner}}`)
|
|
3392
3447
|
}
|
|
3448
|
+
} else if (schema.unevaluatedItems === false) {
|
|
3449
|
+
// Fallback: use static base index (may not be fully correct for all dynamic cases)
|
|
3450
|
+
const maxIdx = evalResult.items || 0
|
|
3451
|
+
const inner = `if(${v}.length>${maxIdx})return false`
|
|
3452
|
+
_deferOrInline(ctx, lines, v, isArr ? inner : `if(Array.isArray(${v})){${inner}}`)
|
|
3393
3453
|
}
|
|
3394
3454
|
}
|
|
3395
3455
|
}
|
|
@@ -4064,24 +4124,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4064
4124
|
}
|
|
4065
4125
|
|
|
4066
4126
|
// $dynamicRef — resolve via anchors map
|
|
4067
|
-
if (schema.$dynamicRef)
|
|
4068
|
-
const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
|
|
4069
|
-
if (ctx.anchors && ctx.anchors[anchorKey]) {
|
|
4070
|
-
const target = ctx.anchors[anchorKey]
|
|
4071
|
-
if (target === ctx.rootSchema) {
|
|
4072
|
-
// Self-recursive: generate _validateE call
|
|
4073
|
-
ctx.usesRecursion = true
|
|
4074
|
-
lines.push(`_validateE(${v},_all,_e)`)
|
|
4075
|
-
} else {
|
|
4076
|
-
const refKey = '$dynamicRef:' + anchorKey
|
|
4077
|
-
if (!ctx.refStack.has(refKey)) {
|
|
4078
|
-
ctx.refStack.add(refKey)
|
|
4079
|
-
genCodeE(target, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4080
|
-
ctx.refStack.delete(refKey)
|
|
4081
|
-
}
|
|
4082
|
-
}
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4127
|
+
if (schema.$dynamicRef) genDynamicRefE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4085
4128
|
|
|
4086
4129
|
const types = schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : null
|
|
4087
4130
|
if (types) {
|
|
@@ -4246,22 +4289,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4246
4289
|
}
|
|
4247
4290
|
|
|
4248
4291
|
// uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
|
|
4249
|
-
if (schema.uniqueItems)
|
|
4250
|
-
const si = ctx.varCounter++
|
|
4251
|
-
const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
|
|
4252
|
-
const isPrim = itemType === 'string' || itemType === 'number' || itemType === 'integer'
|
|
4253
|
-
const maxItems = schema.maxItems
|
|
4254
|
-
const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
|
|
4255
|
-
let inner
|
|
4256
|
-
if (isPrim && maxItems && maxItems <= 16) {
|
|
4257
|
-
inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k]){${failExpr('_k', '_i')};break}}}`
|
|
4258
|
-
} else if (isPrim) {
|
|
4259
|
-
inner = `const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _prev=_s${si}.get(${v}[_i]);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(${v}[_i],_i)}`
|
|
4260
|
-
} else {
|
|
4261
|
-
inner = `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);const _prev=_s${si}.get(_k);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(_k,_i)}`
|
|
4262
|
-
}
|
|
4263
|
-
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
4264
|
-
}
|
|
4292
|
+
if (schema.uniqueItems) genUniqueItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr)
|
|
4265
4293
|
|
|
4266
4294
|
// object size
|
|
4267
4295
|
if (schema.minProperties !== undefined) {
|
|
@@ -4311,45 +4339,13 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4311
4339
|
// to emit. The error keeps the keyword's own identity: the interpreted
|
|
4312
4340
|
// engine reports these as unevaluatedProperties with the key named, and the
|
|
4313
4341
|
// two engines are pinned to identical output.
|
|
4314
|
-
if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined)
|
|
4315
|
-
const allowedU = Object.keys(schema.properties || {}).map(k => `${JSON.stringify(k)}`).join(',')
|
|
4316
|
-
const ui = ctx.varCounter++
|
|
4317
|
-
const upSp = `${schemaPrefix}/unevaluatedProperties`
|
|
4318
|
-
const upLit = buildErrorLiteral({ keyword: 'unevaluatedProperties', schemaPath: upSp, sourceMap: ctx.sourceMap })
|
|
4319
|
-
const patChecksU = []
|
|
4320
|
-
for (const pat of Object.keys(schema.patternProperties || {})) {
|
|
4321
|
-
const pattern = JSON.stringify(pat)
|
|
4322
|
-
if (!ctx.regExpMap.has(pattern)) {
|
|
4323
|
-
const ri = ctx.varCounter++
|
|
4324
|
-
ctx.regExpMap.set(pattern, ri)
|
|
4325
|
-
if (patternIsSafe(pat)) {
|
|
4326
|
-
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4327
|
-
ctx.usesSafeRe = true
|
|
4328
|
-
} else {
|
|
4329
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
4330
|
-
}
|
|
4331
|
-
}
|
|
4332
|
-
patChecksU.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ui}[_i])`)
|
|
4333
|
-
}
|
|
4334
|
-
const isUneval = patChecksU.length ? `!_a${ui}.has(_k${ui}[_i])&&!(${patChecksU.join('||')})` : `!_a${ui}.has(_k${ui}[_i])`
|
|
4335
|
-
const innerU = `const _k${ui}=Object.keys(${v});const _a${ui}=new Set([${allowedU}]);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}}}`
|
|
4336
|
-
lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
|
|
4337
|
-
}
|
|
4342
|
+
if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined) genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj)
|
|
4338
4343
|
|
|
4339
4344
|
// unevaluatedItems: false under the same rule. The interpreted engine
|
|
4340
4345
|
// reports one error for the array with the first unevaluated position as
|
|
4341
4346
|
// the limit; with adjacent `items` everything is evaluated. The draft-07
|
|
4342
4347
|
// array form of items acts as the prefix.
|
|
4343
|
-
if (schema.unevaluatedItems === false)
|
|
4344
|
-
const itemsIsPrefix = Array.isArray(schema.items)
|
|
4345
|
-
const allEval = schema.items !== undefined && !itemsIsPrefix
|
|
4346
|
-
if (!allEval) {
|
|
4347
|
-
const plen = itemsIsPrefix ? schema.items.length : (Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0)
|
|
4348
|
-
const uiSp = `${schemaPrefix}/unevaluatedItems`
|
|
4349
|
-
const uiLit = buildErrorLiteral({ keyword: 'unevaluatedItems', schemaPath: uiSp, sourceMap: ctx.sourceMap })
|
|
4350
|
-
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}}`)
|
|
4351
|
-
}
|
|
4352
|
-
}
|
|
4348
|
+
if (schema.unevaluatedItems === false) genUnevaluatedItemsFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4353
4349
|
|
|
4354
4350
|
// dependentRequired
|
|
4355
4351
|
if (schema.dependentRequired) {
|
|
@@ -4373,207 +4369,304 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4373
4369
|
}
|
|
4374
4370
|
|
|
4375
4371
|
// patternProperties
|
|
4376
|
-
if (schema.patternProperties)
|
|
4377
|
-
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
4378
|
-
const pattern = JSON.stringify(pat);
|
|
4379
|
-
if (!ctx.regExpMap.has(pattern)) {
|
|
4380
|
-
const ri = ctx.varCounter++
|
|
4381
|
-
ctx.regExpMap.set(pattern, ri)
|
|
4382
|
-
if (patternIsSafe(pat)) {
|
|
4383
|
-
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
4384
|
-
ctx.usesSafeRe = true
|
|
4385
|
-
} else {
|
|
4386
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`);
|
|
4387
|
-
}
|
|
4388
|
-
}
|
|
4389
|
-
const ri = ctx.regExpMap.get(pattern);
|
|
4390
|
-
const ki = ctx.varCounter++
|
|
4391
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){if(_re${ri}.test(_k${ki})){`)
|
|
4392
|
-
const _peFn = emitPtrEsc(ctx)
|
|
4393
|
-
const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
|
|
4394
|
-
// The real pointer, so source maps can locate the failing keyword.
|
|
4395
|
-
genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/patternProperties/' + pat.replace(/~/g, '~0').replace(/\//g, '~1'))
|
|
4396
|
-
lines.push(`}}}`)
|
|
4397
|
-
}
|
|
4398
|
-
}
|
|
4372
|
+
if (schema.patternProperties) genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4399
4373
|
|
|
4400
4374
|
// additionalProperties as a schema — validate each property not declared in
|
|
4401
4375
|
// `properties` against the subschema, with a dynamic /<key> path. codegenSafe
|
|
4402
4376
|
// guarantees no patternProperties or composition here, so "additional" is
|
|
4403
4377
|
// simply "not declared".
|
|
4404
|
-
if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null)
|
|
4405
|
-
const ki = ctx.varCounter++
|
|
4406
|
-
const known = Object.keys(schema.properties || {})
|
|
4407
|
-
const guard = known.length ? `const _ak${ki}=new Set([${known.map((k) => JSON.stringify(k)).join(',')}]);` : ''
|
|
4408
|
-
// A key matched by any patternProperties entry is not additional.
|
|
4409
|
-
const patTests = []
|
|
4410
|
-
for (const pat of Object.keys(schema.patternProperties || {})) {
|
|
4411
|
-
const pattern = JSON.stringify(pat)
|
|
4412
|
-
if (!ctx.regExpMap.has(pattern)) {
|
|
4413
|
-
const ri = ctx.varCounter++
|
|
4414
|
-
ctx.regExpMap.set(pattern, ri)
|
|
4415
|
-
if (patternIsSafe(pat)) {
|
|
4416
|
-
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4417
|
-
ctx.usesSafeRe = true
|
|
4418
|
-
} else {
|
|
4419
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
4420
|
-
}
|
|
4421
|
-
}
|
|
4422
|
-
patTests.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ki})`)
|
|
4423
|
-
}
|
|
4424
|
-
const conds = []
|
|
4425
|
-
if (known.length) conds.push(`!_ak${ki}.has(_k${ki})`)
|
|
4426
|
-
if (patTests.length) conds.push(`!(${patTests.join('||')})`)
|
|
4427
|
-
const keep = conds.length ? `if(${conds.join('&&')}){` : `{`
|
|
4428
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${guard}for(const _k${ki} in ${v}){${keep}`)
|
|
4429
|
-
const _peFn = emitPtrEsc(ctx)
|
|
4430
|
-
const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
|
|
4431
|
-
genCodeE(schema.additionalProperties, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/additionalProperties')
|
|
4432
|
-
lines.push(`}}}`)
|
|
4433
|
-
}
|
|
4378
|
+
if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null) genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4434
4379
|
|
|
4435
4380
|
// dependentSchemas
|
|
4436
|
-
if (schema.dependentSchemas)
|
|
4437
|
-
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
4438
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
4439
|
-
genCodeE(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
|
|
4440
|
-
lines.push(`}`)
|
|
4441
|
-
}
|
|
4442
|
-
}
|
|
4381
|
+
if (schema.dependentSchemas) genDependentSchemasE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4443
4382
|
|
|
4444
4383
|
// propertyNames: false reports every key, at the key's own path, the way the
|
|
4445
4384
|
// interpreter does (plan-compiler: instancePath + '/' + key).
|
|
4446
|
-
if (schema.propertyNames === false)
|
|
4447
|
-
const ki = ctx.varCounter++
|
|
4448
|
-
// The subschema is applied to the property NAME, so the failure belongs to
|
|
4449
|
-
// the object that carries the name, not to `/<key>`, which is where the
|
|
4450
|
-
// value lives. Same placement as the object-valued propertyNames branch.
|
|
4451
|
-
const p = pathExpr || '""'
|
|
4452
|
-
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}}}`)
|
|
4453
|
-
}
|
|
4385
|
+
if (schema.propertyNames === false) genPropertyNamesFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4454
4386
|
|
|
4455
4387
|
// propertyNames
|
|
4456
|
-
if (schema.propertyNames && typeof schema.propertyNames === 'object')
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4388
|
+
if (schema.propertyNames && typeof schema.propertyNames === 'object') genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
4389
|
+
|
|
4390
|
+
// items — starts after prefixItems (Draft 2020-12 semantics)
|
|
4391
|
+
if (schema.items !== undefined && schema.items !== true) {
|
|
4392
|
+
const startIdx = schema.prefixItems ? schema.prefixItems.length : 0
|
|
4393
|
+
const idx = `_j${ctx.varCounter}`
|
|
4394
|
+
const elem = `_ei${ctx.varCounter}`
|
|
4395
|
+
ctx.varCounter++
|
|
4396
|
+
const childPath = childPathDynExpr(pathExpr, idx)
|
|
4397
|
+
lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
|
|
4398
|
+
genCodeE(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
|
|
4399
|
+
lines.push(`}}`)
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4402
|
+
// prefixItems
|
|
4403
|
+
if (schema.prefixItems) genPrefixItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4404
|
+
|
|
4405
|
+
// contains
|
|
4406
|
+
if (schema.contains !== undefined) genContainsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
4407
|
+
|
|
4408
|
+
// allOf
|
|
4409
|
+
if (schema.allOf) {
|
|
4410
|
+
for (let _ai = 0; _ai < schema.allOf.length; _ai++) {
|
|
4411
|
+
genCodeE(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai)
|
|
4462
4412
|
}
|
|
4463
|
-
|
|
4464
|
-
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
// anyOf - collapse to single best-branch error via __ataCollapse
|
|
4416
|
+
if (schema.anyOf) genAnyOfE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4417
|
+
|
|
4418
|
+
// oneOf - collapse to single best-branch error via __ataCollapse
|
|
4419
|
+
if (schema.oneOf) genOneOfE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4420
|
+
|
|
4421
|
+
// not
|
|
4422
|
+
if (schema.not !== undefined) genNotE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
4423
|
+
|
|
4424
|
+
// if/then/else
|
|
4425
|
+
if (schema.if !== undefined) genIfE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
// Rarely used keyword families of genCodeENode, kept out of it so their compile is
|
|
4429
|
+
// paid only by schemas that use them.
|
|
4430
|
+
|
|
4431
|
+
function genDynamicRefE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4432
|
+
const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
|
|
4433
|
+
if (ctx.anchors && ctx.anchors[anchorKey]) {
|
|
4434
|
+
const target = ctx.anchors[anchorKey]
|
|
4435
|
+
if (target === ctx.rootSchema) {
|
|
4436
|
+
// Self-recursive: generate _validateE call
|
|
4437
|
+
ctx.usesRecursion = true
|
|
4438
|
+
lines.push(`_validateE(${v},_all,_e)`)
|
|
4439
|
+
} else {
|
|
4440
|
+
const refKey = '$dynamicRef:' + anchorKey
|
|
4441
|
+
if (!ctx.refStack.has(refKey)) {
|
|
4442
|
+
ctx.refStack.add(refKey)
|
|
4443
|
+
genCodeE(target, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4444
|
+
ctx.refStack.delete(refKey)
|
|
4445
|
+
}
|
|
4465
4446
|
}
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
|
|
4450
|
+
function genUniqueItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr) {
|
|
4451
|
+
const si = ctx.varCounter++
|
|
4452
|
+
const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
|
|
4453
|
+
const isPrim = itemType === 'string' || itemType === 'number' || itemType === 'integer'
|
|
4454
|
+
const maxItems = schema.maxItems
|
|
4455
|
+
const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
|
|
4456
|
+
let inner
|
|
4457
|
+
if (isPrim && maxItems && maxItems <= 16) {
|
|
4458
|
+
inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k]){${failExpr('_k', '_i')};break}}}`
|
|
4459
|
+
} else if (isPrim) {
|
|
4460
|
+
inner = `const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _prev=_s${si}.get(${v}[_i]);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(${v}[_i],_i)}`
|
|
4461
|
+
} else {
|
|
4462
|
+
inner = `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);const _prev=_s${si}.get(_k);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(_k,_i)}`
|
|
4463
|
+
}
|
|
4464
|
+
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
function genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj) {
|
|
4468
|
+
const allowedU = Object.keys(schema.properties || {}).map(k => `${JSON.stringify(k)}`).join(',')
|
|
4469
|
+
const ui = ctx.varCounter++
|
|
4470
|
+
const upSp = `${schemaPrefix}/unevaluatedProperties`
|
|
4471
|
+
const upLit = buildErrorLiteral({ keyword: 'unevaluatedProperties', schemaPath: upSp, sourceMap: ctx.sourceMap })
|
|
4472
|
+
const patChecksU = []
|
|
4473
|
+
for (const pat of Object.keys(schema.patternProperties || {})) {
|
|
4474
|
+
const pattern = JSON.stringify(pat)
|
|
4475
|
+
if (!ctx.regExpMap.has(pattern)) {
|
|
4476
|
+
const ri = ctx.varCounter++
|
|
4477
|
+
ctx.regExpMap.set(pattern, ri)
|
|
4478
|
+
if (patternIsSafe(pat)) {
|
|
4479
|
+
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4480
|
+
ctx.usesSafeRe = true
|
|
4481
|
+
} else {
|
|
4482
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
4477
4483
|
}
|
|
4478
|
-
const ri = ctx.regExpMap.get(pattern);
|
|
4479
|
-
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
4480
|
-
}
|
|
4481
|
-
if (pn.const !== undefined) {
|
|
4482
|
-
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
4483
|
-
}
|
|
4484
|
-
if (pn.enum) {
|
|
4485
|
-
const ei = ctx.varCounter++
|
|
4486
|
-
ctx.helperCode.push(`const _es${ei}=new Set(${JSON.stringify(pn.enum)})`)
|
|
4487
|
-
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'")}}`)
|
|
4488
4484
|
}
|
|
4489
|
-
|
|
4485
|
+
patChecksU.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ui}[_i])`)
|
|
4490
4486
|
}
|
|
4487
|
+
const isUneval = patChecksU.length ? `!_a${ui}.has(_k${ui}[_i])&&!(${patChecksU.join('||')})` : `!_a${ui}.has(_k${ui}[_i])`
|
|
4488
|
+
const innerU = `const _k${ui}=Object.keys(${v});const _a${ui}=new Set([${allowedU}]);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}}}`
|
|
4489
|
+
lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
|
|
4490
|
+
}
|
|
4491
4491
|
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
const
|
|
4497
|
-
|
|
4498
|
-
const
|
|
4499
|
-
lines.push(`if(Array.isArray(${v})){
|
|
4500
|
-
genCodeE(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
|
|
4501
|
-
lines.push(`}}`)
|
|
4492
|
+
function genUnevaluatedItemsFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4493
|
+
const itemsIsPrefix = Array.isArray(schema.items)
|
|
4494
|
+
const allEval = schema.items !== undefined && !itemsIsPrefix
|
|
4495
|
+
if (!allEval) {
|
|
4496
|
+
const plen = itemsIsPrefix ? schema.items.length : (Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0)
|
|
4497
|
+
const uiSp = `${schemaPrefix}/unevaluatedItems`
|
|
4498
|
+
const uiLit = buildErrorLiteral({ keyword: 'unevaluatedItems', schemaPath: uiSp, sourceMap: ctx.sourceMap })
|
|
4499
|
+
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}}`)
|
|
4502
4500
|
}
|
|
4501
|
+
}
|
|
4503
4502
|
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4503
|
+
function genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4504
|
+
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
4505
|
+
const pattern = JSON.stringify(pat);
|
|
4506
|
+
if (!ctx.regExpMap.has(pattern)) {
|
|
4507
|
+
const ri = ctx.varCounter++
|
|
4508
|
+
ctx.regExpMap.set(pattern, ri)
|
|
4509
|
+
if (patternIsSafe(pat)) {
|
|
4510
|
+
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
4511
|
+
ctx.usesSafeRe = true
|
|
4512
|
+
} else {
|
|
4513
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`);
|
|
4514
|
+
}
|
|
4511
4515
|
}
|
|
4516
|
+
const ri = ctx.regExpMap.get(pattern);
|
|
4517
|
+
const ki = ctx.varCounter++
|
|
4518
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){if(_re${ri}.test(_k${ki})){`)
|
|
4519
|
+
const _peFn = emitPtrEsc(ctx)
|
|
4520
|
+
const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
|
|
4521
|
+
// The real pointer, so source maps can locate the failing keyword.
|
|
4522
|
+
genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/patternProperties/' + pat.replace(/~/g, '~0').replace(/\//g, '~1'))
|
|
4523
|
+
lines.push(`}}}`)
|
|
4512
4524
|
}
|
|
4525
|
+
}
|
|
4513
4526
|
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
const
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4527
|
+
function genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4528
|
+
const ki = ctx.varCounter++
|
|
4529
|
+
const known = Object.keys(schema.properties || {})
|
|
4530
|
+
const guard = known.length ? `const _ak${ki}=new Set([${known.map((k) => JSON.stringify(k)).join(',')}]);` : ''
|
|
4531
|
+
// A key matched by any patternProperties entry is not additional.
|
|
4532
|
+
const patTests = []
|
|
4533
|
+
for (const pat of Object.keys(schema.patternProperties || {})) {
|
|
4534
|
+
const pattern = JSON.stringify(pat)
|
|
4535
|
+
if (!ctx.regExpMap.has(pattern)) {
|
|
4536
|
+
const ri = ctx.varCounter++
|
|
4537
|
+
ctx.regExpMap.set(pattern, ri)
|
|
4538
|
+
if (patternIsSafe(pat)) {
|
|
4539
|
+
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4540
|
+
ctx.usesSafeRe = true
|
|
4541
|
+
} else {
|
|
4542
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
4543
|
+
}
|
|
4526
4544
|
}
|
|
4545
|
+
patTests.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ki})`)
|
|
4546
|
+
}
|
|
4547
|
+
const conds = []
|
|
4548
|
+
if (known.length) conds.push(`!_ak${ki}.has(_k${ki})`)
|
|
4549
|
+
if (patTests.length) conds.push(`!(${patTests.join('||')})`)
|
|
4550
|
+
const keep = conds.length ? `if(${conds.join('&&')}){` : `{`
|
|
4551
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${guard}for(const _k${ki} in ${v}){${keep}`)
|
|
4552
|
+
const _peFn = emitPtrEsc(ctx)
|
|
4553
|
+
const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
|
|
4554
|
+
genCodeE(schema.additionalProperties, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/additionalProperties')
|
|
4555
|
+
lines.push(`}}}`)
|
|
4556
|
+
}
|
|
4557
|
+
|
|
4558
|
+
function genDependentSchemasE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4559
|
+
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
4560
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
4561
|
+
genCodeE(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
|
|
4527
4562
|
lines.push(`}`)
|
|
4528
4563
|
}
|
|
4564
|
+
}
|
|
4529
4565
|
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4566
|
+
function genPropertyNamesFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4567
|
+
const ki = ctx.varCounter++
|
|
4568
|
+
// The subschema is applied to the property NAME, so the failure belongs to
|
|
4569
|
+
// the object that carries the name, not to `/<key>`, which is where the
|
|
4570
|
+
// value lives. Same placement as the object-valued propertyNames branch.
|
|
4571
|
+
const p = pathExpr || '""'
|
|
4572
|
+
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}}}`)
|
|
4573
|
+
}
|
|
4574
|
+
|
|
4575
|
+
function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
4576
|
+
const pn = schema.propertyNames
|
|
4577
|
+
const ki = ctx.varCounter++
|
|
4578
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
|
|
4579
|
+
if (pn.minLength !== undefined) {
|
|
4580
|
+
lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
4581
|
+
}
|
|
4582
|
+
if (pn.maxLength !== undefined) {
|
|
4583
|
+
lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
|
|
4584
|
+
}
|
|
4585
|
+
if (pn.pattern) {
|
|
4586
|
+
const pattern = JSON.stringify(pn.pattern);
|
|
4587
|
+
if (!ctx.regExpMap.has(pattern)) {
|
|
4588
|
+
const ri = ctx.varCounter++
|
|
4589
|
+
ctx.regExpMap.set(pattern, ri)
|
|
4590
|
+
if (patternIsSafe(pn.pattern)) {
|
|
4591
|
+
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
4592
|
+
ctx.usesSafeRe = true
|
|
4593
|
+
} else {
|
|
4594
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`);
|
|
4595
|
+
}
|
|
4534
4596
|
}
|
|
4597
|
+
const ri = ctx.regExpMap.get(pattern);
|
|
4598
|
+
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
4535
4599
|
}
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
if (schema.anyOf) {
|
|
4539
|
-
emitBranchCollapse(schema.anyOf, 'anyOf', v, pathExpr, lines, ctx, schemaPrefix)
|
|
4600
|
+
if (pn.const !== undefined) {
|
|
4601
|
+
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
4540
4602
|
}
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4603
|
+
if (pn.enum) {
|
|
4604
|
+
const ei = ctx.varCounter++
|
|
4605
|
+
ctx.helperCode.push(`const _es${ei}=new Set(${JSON.stringify(pn.enum)})`)
|
|
4606
|
+
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'")}}`)
|
|
4545
4607
|
}
|
|
4608
|
+
lines.push(`}}`)
|
|
4609
|
+
}
|
|
4546
4610
|
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
const
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
4611
|
+
function genPrefixItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4612
|
+
for (let i = 0; i < schema.prefixItems.length; i++) {
|
|
4613
|
+
const childPath = childPathExpr(pathExpr, String(i))
|
|
4614
|
+
lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
|
|
4615
|
+
genCodeE(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
|
|
4616
|
+
lines.push(`}`)
|
|
4554
4617
|
}
|
|
4618
|
+
}
|
|
4555
4619
|
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4620
|
+
function genContainsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
4621
|
+
const ci = ctx.varCounter++
|
|
4622
|
+
const subLines = []
|
|
4623
|
+
nestedGenCode(schema.contains, `_cv`, subLines, ctx)
|
|
4624
|
+
const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
|
|
4625
|
+
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
4626
|
+
const maxC = schema.maxContains
|
|
4627
|
+
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}++}`)
|
|
4628
|
+
lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'contains: need at least ${minC} match(es)'`)}}`)
|
|
4629
|
+
if (maxC !== undefined) {
|
|
4630
|
+
lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'contains: at most ${maxC} match(es)'`)}}`)
|
|
4631
|
+
}
|
|
4632
|
+
lines.push(`}`)
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4635
|
+
function genAnyOfE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4636
|
+
emitBranchCollapse(schema.anyOf, 'anyOf', v, pathExpr, lines, ctx, schemaPrefix)
|
|
4637
|
+
}
|
|
4638
|
+
|
|
4639
|
+
function genOneOfE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4640
|
+
emitBranchCollapse(schema.oneOf, 'oneOf', v, pathExpr, lines, ctx, schemaPrefix)
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
function genNotE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
4644
|
+
const subLines = []
|
|
4645
|
+
nestedGenCode(schema.not, '_nv', subLines, ctx)
|
|
4646
|
+
const nfn = subLines.length === 0 ? `function(_nv){return true}` : `function(_nv){${subLines.join(';')};return true}`
|
|
4647
|
+
const fi = ctx.varCounter++
|
|
4648
|
+
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4651
|
+
function genIfE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4652
|
+
const ifLines = []
|
|
4653
|
+
nestedGenCode(schema.if, '_iv', ifLines, ctx)
|
|
4654
|
+
const fi = ctx.varCounter++
|
|
4655
|
+
const ifFn = ifLines.length === 0
|
|
4656
|
+
? `function(_iv){return true}`
|
|
4657
|
+
: `function(_iv){${ifLines.join(';')};return true}`
|
|
4658
|
+
lines.push(`{const _if${fi}=${ifFn}`)
|
|
4659
|
+
if (schema.then !== undefined) {
|
|
4660
|
+
lines.push(`if(_if${fi}(${v})){`)
|
|
4661
|
+
genCodeE(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then')
|
|
4662
|
+
lines.push(`}`)
|
|
4663
|
+
}
|
|
4664
|
+
if (schema.else !== undefined) {
|
|
4665
|
+
lines.push(`${schema.then !== undefined ? 'else' : `if(!_if${fi}(${v}))`}{`)
|
|
4666
|
+
genCodeE(schema.else, v, pathExpr, lines, ctx, schemaPrefix+'/else')
|
|
4575
4667
|
lines.push(`}`)
|
|
4576
4668
|
}
|
|
4669
|
+
lines.push(`}`)
|
|
4577
4670
|
}
|
|
4578
4671
|
|
|
4579
4672
|
// --- Combined validator: single pass, validates + collects errors ---
|
|
@@ -4759,23 +4852,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4759
4852
|
}
|
|
4760
4853
|
|
|
4761
4854
|
// $dynamicRef — resolve via anchors map
|
|
4762
|
-
if (schema.$dynamicRef)
|
|
4763
|
-
const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
|
|
4764
|
-
if (ctx.anchors && ctx.anchors[anchorKey]) {
|
|
4765
|
-
const target = ctx.anchors[anchorKey]
|
|
4766
|
-
// This generator has no named functions, so it cannot follow a reference
|
|
4767
|
-
// back to the root or around a cycle. It used to emit nothing in both
|
|
4768
|
-
// cases, which the function then answered as valid: a document the other
|
|
4769
|
-
// generators reject passed through validateJSON. It declines instead,
|
|
4770
|
-
// and the schema takes the error generator.
|
|
4771
|
-
if (target === ctx.rootSchema) throw DECLINE
|
|
4772
|
-
const refKey = '$dynamicRef:' + anchorKey
|
|
4773
|
-
if (ctx.refStack.has(refKey)) throw DECLINE
|
|
4774
|
-
ctx.refStack.add(refKey)
|
|
4775
|
-
genCodeC(target, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4776
|
-
ctx.refStack.delete(refKey)
|
|
4777
|
-
}
|
|
4778
|
-
}
|
|
4855
|
+
if (schema.$dynamicRef) genDynamicRefC(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4779
4856
|
|
|
4780
4857
|
const types = schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : null
|
|
4781
4858
|
let isObj = false, isArr = false, isStr = false, isNum = false
|
|
@@ -4977,23 +5054,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4977
5054
|
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'`)}}`) }
|
|
4978
5055
|
|
|
4979
5056
|
// uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
|
|
4980
|
-
if (schema.uniqueItems)
|
|
4981
|
-
const si = ctx.varCounter++
|
|
4982
|
-
const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
|
|
4983
|
-
const isPrim = itemType === 'string' || itemType === 'number' || itemType === 'integer'
|
|
4984
|
-
const maxItems = schema.maxItems
|
|
4985
|
-
const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
|
|
4986
|
-
let inner
|
|
4987
|
-
if (isPrim && maxItems && maxItems <= 16) {
|
|
4988
|
-
// Small primitive arrays: O(n²) nested loop, zero allocation
|
|
4989
|
-
inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k]){${failExpr('_k', '_i')};break}}}`
|
|
4990
|
-
} else if (isPrim) {
|
|
4991
|
-
inner = `const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _prev=_s${si}.get(${v}[_i]);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(${v}[_i],_i)}`
|
|
4992
|
-
} else {
|
|
4993
|
-
inner = `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);const _prev=_s${si}.get(_k);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(_k,_i)}`
|
|
4994
|
-
}
|
|
4995
|
-
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
4996
|
-
}
|
|
5057
|
+
if (schema.uniqueItems) genUniqueItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr)
|
|
4997
5058
|
|
|
4998
5059
|
// object size
|
|
4999
5060
|
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'`)}}`)
|
|
@@ -5046,35 +5107,12 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5046
5107
|
// unevaluatedProperties: false where it is provably additionalProperties
|
|
5047
5108
|
// (the entry bail guaranteed that), keeping the keyword's own identity so
|
|
5048
5109
|
// the two engines report the same error.
|
|
5049
|
-
if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined && !schema.patternProperties)
|
|
5050
|
-
const propKeysU = Object.keys(schema.properties || {})
|
|
5051
|
-
const ui = ctx.varCounter++
|
|
5052
|
-
const failU = fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:_k${ui}[_i]}`, "'must NOT have unevaluated properties'")
|
|
5053
|
-
let innerU
|
|
5054
|
-
if (propKeysU.length === 0) {
|
|
5055
|
-
innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++){${failU}}`
|
|
5056
|
-
} else if (propKeysU.length <= 8) {
|
|
5057
|
-
const checksU = propKeysU.map(k => `_k${ui}[_i]!==${JSON.stringify(k)}`).join('&&')
|
|
5058
|
-
innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++)if(${checksU}){${failU}}`
|
|
5059
|
-
} else {
|
|
5060
|
-
const allowedU = propKeysU.map(k => JSON.stringify(k)).join(',')
|
|
5061
|
-
innerU = `const _k${ui}=Object.keys(${v});const _a${ui}=new Set([${allowedU}]);for(let _i=0;_i<_k${ui}.length;_i++)if(!_a${ui}.has(_k${ui}[_i])){${failU}}`
|
|
5062
|
-
}
|
|
5063
|
-
lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
|
|
5064
|
-
}
|
|
5110
|
+
if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined && !schema.patternProperties) genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj)
|
|
5065
5111
|
|
|
5066
5112
|
// unevaluatedItems: false under the same rule: one error for the array,
|
|
5067
5113
|
// the first uncovered position as the limit. Adjacent schema-form `items`
|
|
5068
5114
|
// evaluates everything; the draft-07 array form acts as the prefix.
|
|
5069
|
-
if (schema.unevaluatedItems === false)
|
|
5070
|
-
const itemsIsPrefixC = Array.isArray(schema.items)
|
|
5071
|
-
const allEvalC = schema.items !== undefined && !itemsIsPrefixC
|
|
5072
|
-
if (!allEvalC) {
|
|
5073
|
-
const plenC = itemsIsPrefixC ? schema.items.length : (Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0)
|
|
5074
|
-
const failI = fail('unevaluatedItems', 'unevaluatedItems', `{limit:${plenC}}`, `'must NOT have more than ${plenC} items'`)
|
|
5075
|
-
lines.push(`if(Array.isArray(${v})&&${v}.length>${plenC}){${failI}}`)
|
|
5076
|
-
}
|
|
5077
|
-
}
|
|
5115
|
+
if (schema.unevaluatedItems === false) genUnevaluatedItemsFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5078
5116
|
|
|
5079
5117
|
// dependentRequired
|
|
5080
5118
|
if (schema.dependentRequired) {
|
|
@@ -5120,153 +5158,16 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5120
5158
|
|
|
5121
5159
|
// patternProperties — same optimizations as genCode: charCodeAt + inline key comparison + merged propertyNames
|
|
5122
5160
|
if (schema.patternProperties) {
|
|
5123
|
-
|
|
5124
|
-
const pn = schema.propertyNames && typeof schema.propertyNames === 'object' ? schema.propertyNames : null
|
|
5125
|
-
const pi = ctx.varCounter++
|
|
5126
|
-
|
|
5127
|
-
// Build pattern matchers: prefer charCodeAt for simple prefixes
|
|
5128
|
-
const matchers = []
|
|
5129
|
-
for (const [pat] of ppEntries) {
|
|
5130
|
-
const kVar = `_k${pi}`
|
|
5131
|
-
const fast = fastPrefixCheck(pat, kVar)
|
|
5132
|
-
if (fast) {
|
|
5133
|
-
matchers.push({ check: fast })
|
|
5134
|
-
} else {
|
|
5135
|
-
const ri = ctx.varCounter++
|
|
5136
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
5137
|
-
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
5138
|
-
matchers.push({ check: `_re${ri}.test(_k${pi})` })
|
|
5139
|
-
}
|
|
5140
|
-
}
|
|
5141
|
-
|
|
5142
|
-
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
5143
|
-
const kVar = `_k${pi}`
|
|
5144
|
-
|
|
5145
|
-
if (schema.additionalProperties === false && schema.properties) {
|
|
5146
|
-
ppHandledPropertyNames = !!pn
|
|
5147
|
-
const propKeys = Object.keys(schema.properties)
|
|
5148
|
-
// Inline key comparison for small property sets
|
|
5149
|
-
const keyCheck = propKeys.length <= 8
|
|
5150
|
-
? propKeys.map(k => `${kVar}===${JSON.stringify(k)}`).join('||')
|
|
5151
|
-
: null
|
|
5152
|
-
if (!keyCheck) {
|
|
5153
|
-
const allowedSet = `_as${pi}`
|
|
5154
|
-
ctx.closureVars.push(allowedSet)
|
|
5155
|
-
ctx.closureVals.push(new Set(propKeys))
|
|
5156
|
-
}
|
|
5157
|
-
|
|
5158
|
-
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
5159
|
-
// propertyNames checks (merged)
|
|
5160
|
-
if (pn) {
|
|
5161
|
-
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'`)}}`)
|
|
5162
|
-
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'`)}}`)
|
|
5163
|
-
if (pn.pattern) {
|
|
5164
|
-
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
5165
|
-
if (fast) {
|
|
5166
|
-
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5167
|
-
} else {
|
|
5168
|
-
const ri = ctx.varCounter++
|
|
5169
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
5170
|
-
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5171
|
-
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5172
|
-
}
|
|
5173
|
-
}
|
|
5174
|
-
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'")}}`)
|
|
5175
|
-
if (pn.enum) {
|
|
5176
|
-
const ei = ctx.varCounter++
|
|
5177
|
-
ctx.closureVars.push(`_es${ei}`)
|
|
5178
|
-
ctx.closureVals.push(new Set(pn.enum))
|
|
5179
|
-
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'")}}`)
|
|
5180
|
-
}
|
|
5181
|
-
}
|
|
5182
|
-
const matchExpr = keyCheck || `_as${pi}.has(${kVar})`
|
|
5183
|
-
lines.push(`let _m${pi}=${matchExpr}`)
|
|
5184
|
-
for (let i = 0; i < ppEntries.length; i++) {
|
|
5185
|
-
// The subschema is generated in place so its own keywords report
|
|
5186
|
-
// the real instance path and schema pointer.
|
|
5187
|
-
lines.push(`if(${matchers[i].check}){_m${pi}=true;{const _ppv${pi}_${i}=${v}[${kVar}]`)
|
|
5188
|
-
genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, `${emitPtrEsc(ctx)}(${kVar})`), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
|
|
5189
|
-
lines.push(`}}`)
|
|
5190
|
-
}
|
|
5191
|
-
lines.push(`if(!_m${pi}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:${kVar}}`, "'must NOT have additional properties'")}}`)
|
|
5192
|
-
lines.push(`}}`)
|
|
5193
|
-
} else {
|
|
5194
|
-
ppHandledPropertyNames = !!pn
|
|
5195
|
-
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
5196
|
-
if (pn) {
|
|
5197
|
-
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'`)}}`)
|
|
5198
|
-
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'`)}}`)
|
|
5199
|
-
if (pn.pattern) {
|
|
5200
|
-
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
5201
|
-
if (fast) {
|
|
5202
|
-
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5203
|
-
} else {
|
|
5204
|
-
const ri = ctx.varCounter++
|
|
5205
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
5206
|
-
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5207
|
-
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5208
|
-
}
|
|
5209
|
-
}
|
|
5210
|
-
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'")}}`)
|
|
5211
|
-
if (pn.enum) {
|
|
5212
|
-
const ei = ctx.varCounter++
|
|
5213
|
-
ctx.closureVars.push(`_es${ei}`)
|
|
5214
|
-
ctx.closureVals.push(new Set(pn.enum))
|
|
5215
|
-
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'")}}`)
|
|
5216
|
-
}
|
|
5217
|
-
}
|
|
5218
|
-
for (let i = 0; i < ppEntries.length; i++) {
|
|
5219
|
-
lines.push(`if(${matchers[i].check}){const _ppv${pi}_${i}=${v}[${kVar}]`)
|
|
5220
|
-
genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, `${emitPtrEsc(ctx)}(${kVar})`), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
|
|
5221
|
-
lines.push(`}`)
|
|
5222
|
-
}
|
|
5223
|
-
lines.push(`}}`)
|
|
5224
|
-
}
|
|
5161
|
+
if (genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj)) ppHandledPropertyNames = true
|
|
5225
5162
|
}
|
|
5226
5163
|
|
|
5227
5164
|
// dependentSchemas
|
|
5228
|
-
if (schema.dependentSchemas)
|
|
5229
|
-
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
5230
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
5231
|
-
genCodeC(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
|
|
5232
|
-
lines.push(`}`)
|
|
5233
|
-
}
|
|
5234
|
-
}
|
|
5165
|
+
if (schema.dependentSchemas) genDependentSchemasC(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5235
5166
|
|
|
5236
|
-
if (schema.propertyNames === false)
|
|
5237
|
-
const ki = ctx.varCounter++
|
|
5238
|
-
const p = pathExpr || '""'
|
|
5239
|
-
lines.push(`;if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){(_e||(_e=[])).push({keyword:'not',instancePath:${p},schemaPath:'${schemaPrefix}/propertyNames'${ordinalField(ctx, `${schemaPrefix}/propertyNames`)},params:{},message:'boolean schema is false'})}}`)
|
|
5240
|
-
}
|
|
5167
|
+
if (schema.propertyNames === false) genPropertyNamesFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5241
5168
|
|
|
5242
5169
|
// propertyNames — skip if already merged into patternProperties loop
|
|
5243
|
-
if (schema.propertyNames && typeof schema.propertyNames === 'object' && !ppHandledPropertyNames)
|
|
5244
|
-
const pn = schema.propertyNames
|
|
5245
|
-
const ki = ctx.varCounter++
|
|
5246
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
|
|
5247
|
-
if (pn.minLength !== undefined) {
|
|
5248
|
-
lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
5249
|
-
}
|
|
5250
|
-
if (pn.maxLength !== undefined) {
|
|
5251
|
-
lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
|
|
5252
|
-
}
|
|
5253
|
-
if (pn.pattern) {
|
|
5254
|
-
const ri = ctx.varCounter++
|
|
5255
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
5256
|
-
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5257
|
-
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5258
|
-
}
|
|
5259
|
-
if (pn.const !== undefined) {
|
|
5260
|
-
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
5261
|
-
}
|
|
5262
|
-
if (pn.enum) {
|
|
5263
|
-
const ei = ctx.varCounter++
|
|
5264
|
-
ctx.closureVars.push(`_es${ei}`)
|
|
5265
|
-
ctx.closureVals.push(new Set(pn.enum))
|
|
5266
|
-
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'")}}`)
|
|
5267
|
-
}
|
|
5268
|
-
lines.push(`}}`)
|
|
5269
|
-
}
|
|
5170
|
+
if (schema.propertyNames && typeof schema.propertyNames === 'object' && !ppHandledPropertyNames) genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5270
5171
|
|
|
5271
5172
|
// items
|
|
5272
5173
|
if (schema.items !== undefined && schema.items !== true) {
|
|
@@ -5280,64 +5181,25 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5280
5181
|
}
|
|
5281
5182
|
|
|
5282
5183
|
// prefixItems
|
|
5283
|
-
if (schema.prefixItems)
|
|
5284
|
-
for (let i = 0; i < schema.prefixItems.length; i++) {
|
|
5285
|
-
const childPath = childPathExpr(pathExpr, String(i))
|
|
5286
|
-
lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
|
|
5287
|
-
genCodeC(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
|
|
5288
|
-
lines.push(`}`)
|
|
5289
|
-
}
|
|
5290
|
-
}
|
|
5184
|
+
if (schema.prefixItems) genPrefixItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5291
5185
|
|
|
5292
5186
|
// contains
|
|
5293
|
-
if (schema.contains !== undefined)
|
|
5294
|
-
const ci = ctx.varCounter++
|
|
5295
|
-
const subLines = []
|
|
5296
|
-
nestedGenCode(schema.contains, `_cv`, subLines, ctx)
|
|
5297
|
-
const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
|
|
5298
|
-
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
5299
|
-
const maxC = schema.maxContains
|
|
5300
|
-
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}++}`)
|
|
5301
|
-
lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'contains: need at least ${minC} match(es)'`)}}`)
|
|
5302
|
-
if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'contains: at most ${maxC} match(es)'`)}}`)
|
|
5303
|
-
lines.push(`}`)
|
|
5304
|
-
}
|
|
5187
|
+
if (schema.contains !== undefined) genContainsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5305
5188
|
|
|
5306
5189
|
// allOf
|
|
5307
5190
|
if (schema.allOf) { for (let _ai = 0; _ai < schema.allOf.length; _ai++) genCodeC(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai) }
|
|
5308
5191
|
|
|
5309
5192
|
// anyOf
|
|
5310
|
-
if (schema.anyOf)
|
|
5311
|
-
const fi = ctx.varCounter++
|
|
5312
|
-
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}` })
|
|
5313
|
-
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'")}}}`)
|
|
5314
|
-
}
|
|
5193
|
+
if (schema.anyOf) genAnyOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5315
5194
|
|
|
5316
5195
|
// oneOf
|
|
5317
|
-
if (schema.oneOf)
|
|
5318
|
-
const fi = ctx.varCounter++
|
|
5319
|
-
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}` })
|
|
5320
|
-
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'")}}}`)
|
|
5321
|
-
}
|
|
5196
|
+
if (schema.oneOf) genOneOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5322
5197
|
|
|
5323
5198
|
// not
|
|
5324
|
-
if (schema.not !== undefined)
|
|
5325
|
-
const sl = []; nestedGenCode(schema.not, '_nv', sl, ctx)
|
|
5326
|
-
const nfn = sl.length === 0 ? `function(_nv){return true}` : `function(_nv){${sl.join(';')};return true}`
|
|
5327
|
-
const fi = ctx.varCounter++
|
|
5328
|
-
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
5329
|
-
}
|
|
5199
|
+
if (schema.not !== undefined) genNotC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5330
5200
|
|
|
5331
5201
|
// if/then/else
|
|
5332
|
-
if (schema.if !== undefined)
|
|
5333
|
-
const sl = []; nestedGenCode(schema.if, '_iv', sl, ctx)
|
|
5334
|
-
const fi = ctx.varCounter++
|
|
5335
|
-
const ifFn = sl.length === 0 ? `function(_iv){return true}` : `function(_iv){${sl.join(';')};return true}`
|
|
5336
|
-
lines.push(`{const _if${fi}=${ifFn}`)
|
|
5337
|
-
if (schema.then !== undefined) { lines.push(`if(_if${fi}(${v})){`); genCodeC(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then'); lines.push(`}`) }
|
|
5338
|
-
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(`}`) }
|
|
5339
|
-
lines.push(`}`)
|
|
5340
|
-
}
|
|
5202
|
+
if (schema.if !== undefined) genIfC(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5341
5203
|
|
|
5342
5204
|
// Close type-success block if opened
|
|
5343
5205
|
if (types) {
|
|
@@ -5345,6 +5207,272 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5345
5207
|
}
|
|
5346
5208
|
}
|
|
5347
5209
|
|
|
5210
|
+
// Rarely used keyword families of genCodeCNode, kept out of it so their compile is
|
|
5211
|
+
// paid only by schemas that use them.
|
|
5212
|
+
|
|
5213
|
+
function genDynamicRefC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5214
|
+
const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
|
|
5215
|
+
if (ctx.anchors && ctx.anchors[anchorKey]) {
|
|
5216
|
+
const target = ctx.anchors[anchorKey]
|
|
5217
|
+
// This generator has no named functions, so it cannot follow a reference
|
|
5218
|
+
// back to the root or around a cycle. It used to emit nothing in both
|
|
5219
|
+
// cases, which the function then answered as valid: a document the other
|
|
5220
|
+
// generators reject passed through validateJSON. It declines instead,
|
|
5221
|
+
// and the schema takes the error generator.
|
|
5222
|
+
if (target === ctx.rootSchema) throw DECLINE
|
|
5223
|
+
const refKey = '$dynamicRef:' + anchorKey
|
|
5224
|
+
if (ctx.refStack.has(refKey)) throw DECLINE
|
|
5225
|
+
ctx.refStack.add(refKey)
|
|
5226
|
+
genCodeC(target, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5227
|
+
ctx.refStack.delete(refKey)
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
|
|
5231
|
+
function genUniqueItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr) {
|
|
5232
|
+
const si = ctx.varCounter++
|
|
5233
|
+
const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
|
|
5234
|
+
const isPrim = itemType === 'string' || itemType === 'number' || itemType === 'integer'
|
|
5235
|
+
const maxItems = schema.maxItems
|
|
5236
|
+
const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
|
|
5237
|
+
let inner
|
|
5238
|
+
if (isPrim && maxItems && maxItems <= 16) {
|
|
5239
|
+
// Small primitive arrays: O(n²) nested loop, zero allocation
|
|
5240
|
+
inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k]){${failExpr('_k', '_i')};break}}}`
|
|
5241
|
+
} else if (isPrim) {
|
|
5242
|
+
inner = `const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _prev=_s${si}.get(${v}[_i]);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(${v}[_i],_i)}`
|
|
5243
|
+
} else {
|
|
5244
|
+
inner = `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);const _prev=_s${si}.get(_k);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(_k,_i)}`
|
|
5245
|
+
}
|
|
5246
|
+
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
5247
|
+
}
|
|
5248
|
+
|
|
5249
|
+
function genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
|
|
5250
|
+
const propKeysU = Object.keys(schema.properties || {})
|
|
5251
|
+
const ui = ctx.varCounter++
|
|
5252
|
+
const failU = fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:_k${ui}[_i]}`, "'must NOT have unevaluated properties'")
|
|
5253
|
+
let innerU
|
|
5254
|
+
if (propKeysU.length === 0) {
|
|
5255
|
+
innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++){${failU}}`
|
|
5256
|
+
} else if (propKeysU.length <= 8) {
|
|
5257
|
+
const checksU = propKeysU.map(k => `_k${ui}[_i]!==${JSON.stringify(k)}`).join('&&')
|
|
5258
|
+
innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++)if(${checksU}){${failU}}`
|
|
5259
|
+
} else {
|
|
5260
|
+
const allowedU = propKeysU.map(k => JSON.stringify(k)).join(',')
|
|
5261
|
+
innerU = `const _k${ui}=Object.keys(${v});const _a${ui}=new Set([${allowedU}]);for(let _i=0;_i<_k${ui}.length;_i++)if(!_a${ui}.has(_k${ui}[_i])){${failU}}`
|
|
5262
|
+
}
|
|
5263
|
+
lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
|
|
5264
|
+
}
|
|
5265
|
+
|
|
5266
|
+
function genUnevaluatedItemsFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
5267
|
+
const itemsIsPrefixC = Array.isArray(schema.items)
|
|
5268
|
+
const allEvalC = schema.items !== undefined && !itemsIsPrefixC
|
|
5269
|
+
if (!allEvalC) {
|
|
5270
|
+
const plenC = itemsIsPrefixC ? schema.items.length : (Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0)
|
|
5271
|
+
const failI = fail('unevaluatedItems', 'unevaluatedItems', `{limit:${plenC}}`, `'must NOT have more than ${plenC} items'`)
|
|
5272
|
+
lines.push(`if(Array.isArray(${v})&&${v}.length>${plenC}){${failI}}`)
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
|
|
5276
|
+
function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
|
|
5277
|
+
let handledPropertyNames = false
|
|
5278
|
+
const ppEntries = Object.entries(schema.patternProperties)
|
|
5279
|
+
const pn = schema.propertyNames && typeof schema.propertyNames === 'object' ? schema.propertyNames : null
|
|
5280
|
+
const pi = ctx.varCounter++
|
|
5281
|
+
|
|
5282
|
+
// Build pattern matchers: prefer charCodeAt for simple prefixes
|
|
5283
|
+
const matchers = []
|
|
5284
|
+
for (const [pat] of ppEntries) {
|
|
5285
|
+
const kVar = `_k${pi}`
|
|
5286
|
+
const fast = fastPrefixCheck(pat, kVar)
|
|
5287
|
+
if (fast) {
|
|
5288
|
+
matchers.push({ check: fast })
|
|
5289
|
+
} else {
|
|
5290
|
+
const ri = ctx.varCounter++
|
|
5291
|
+
ctx.closureVars.push(`_re${ri}`)
|
|
5292
|
+
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
5293
|
+
matchers.push({ check: `_re${ri}.test(_k${pi})` })
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5296
|
+
|
|
5297
|
+
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
5298
|
+
const kVar = `_k${pi}`
|
|
5299
|
+
|
|
5300
|
+
if (schema.additionalProperties === false && schema.properties) {
|
|
5301
|
+
handledPropertyNames = !!pn
|
|
5302
|
+
const propKeys = Object.keys(schema.properties)
|
|
5303
|
+
// Inline key comparison for small property sets
|
|
5304
|
+
const keyCheck = propKeys.length <= 8
|
|
5305
|
+
? propKeys.map(k => `${kVar}===${JSON.stringify(k)}`).join('||')
|
|
5306
|
+
: null
|
|
5307
|
+
if (!keyCheck) {
|
|
5308
|
+
const allowedSet = `_as${pi}`
|
|
5309
|
+
ctx.closureVars.push(allowedSet)
|
|
5310
|
+
ctx.closureVals.push(new Set(propKeys))
|
|
5311
|
+
}
|
|
5312
|
+
|
|
5313
|
+
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
5314
|
+
// propertyNames checks (merged)
|
|
5315
|
+
if (pn) {
|
|
5316
|
+
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'`)}}`)
|
|
5317
|
+
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'`)}}`)
|
|
5318
|
+
if (pn.pattern) {
|
|
5319
|
+
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
5320
|
+
if (fast) {
|
|
5321
|
+
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5322
|
+
} else {
|
|
5323
|
+
const ri = ctx.varCounter++
|
|
5324
|
+
ctx.closureVars.push(`_re${ri}`)
|
|
5325
|
+
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5326
|
+
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5329
|
+
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'")}}`)
|
|
5330
|
+
if (pn.enum) {
|
|
5331
|
+
const ei = ctx.varCounter++
|
|
5332
|
+
ctx.closureVars.push(`_es${ei}`)
|
|
5333
|
+
ctx.closureVals.push(new Set(pn.enum))
|
|
5334
|
+
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'")}}`)
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
const matchExpr = keyCheck || `_as${pi}.has(${kVar})`
|
|
5338
|
+
lines.push(`let _m${pi}=${matchExpr}`)
|
|
5339
|
+
for (let i = 0; i < ppEntries.length; i++) {
|
|
5340
|
+
// The subschema is generated in place so its own keywords report
|
|
5341
|
+
// the real instance path and schema pointer.
|
|
5342
|
+
lines.push(`if(${matchers[i].check}){_m${pi}=true;{const _ppv${pi}_${i}=${v}[${kVar}]`)
|
|
5343
|
+
genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, `${emitPtrEsc(ctx)}(${kVar})`), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
|
|
5344
|
+
lines.push(`}}`)
|
|
5345
|
+
}
|
|
5346
|
+
lines.push(`if(!_m${pi}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:${kVar}}`, "'must NOT have additional properties'")}}`)
|
|
5347
|
+
lines.push(`}}`)
|
|
5348
|
+
} else {
|
|
5349
|
+
handledPropertyNames = !!pn
|
|
5350
|
+
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
5351
|
+
if (pn) {
|
|
5352
|
+
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'`)}}`)
|
|
5353
|
+
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'`)}}`)
|
|
5354
|
+
if (pn.pattern) {
|
|
5355
|
+
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
5356
|
+
if (fast) {
|
|
5357
|
+
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5358
|
+
} else {
|
|
5359
|
+
const ri = ctx.varCounter++
|
|
5360
|
+
ctx.closureVars.push(`_re${ri}`)
|
|
5361
|
+
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5362
|
+
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5363
|
+
}
|
|
5364
|
+
}
|
|
5365
|
+
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'")}}`)
|
|
5366
|
+
if (pn.enum) {
|
|
5367
|
+
const ei = ctx.varCounter++
|
|
5368
|
+
ctx.closureVars.push(`_es${ei}`)
|
|
5369
|
+
ctx.closureVals.push(new Set(pn.enum))
|
|
5370
|
+
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'")}}`)
|
|
5371
|
+
}
|
|
5372
|
+
}
|
|
5373
|
+
for (let i = 0; i < ppEntries.length; i++) {
|
|
5374
|
+
lines.push(`if(${matchers[i].check}){const _ppv${pi}_${i}=${v}[${kVar}]`)
|
|
5375
|
+
genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, `${emitPtrEsc(ctx)}(${kVar})`), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
|
|
5376
|
+
lines.push(`}`)
|
|
5377
|
+
}
|
|
5378
|
+
lines.push(`}}`)
|
|
5379
|
+
}
|
|
5380
|
+
return handledPropertyNames
|
|
5381
|
+
}
|
|
5382
|
+
|
|
5383
|
+
function genDependentSchemasC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5384
|
+
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
5385
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
5386
|
+
genCodeC(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
|
|
5387
|
+
lines.push(`}`)
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
|
|
5391
|
+
function genPropertyNamesFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5392
|
+
const ki = ctx.varCounter++
|
|
5393
|
+
const p = pathExpr || '""'
|
|
5394
|
+
lines.push(`;if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){(_e||(_e=[])).push({keyword:'not',instancePath:${p},schemaPath:'${schemaPrefix}/propertyNames'${ordinalField(ctx, `${schemaPrefix}/propertyNames`)},params:{},message:'boolean schema is false'})}}`)
|
|
5395
|
+
}
|
|
5396
|
+
|
|
5397
|
+
function genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
5398
|
+
const pn = schema.propertyNames
|
|
5399
|
+
const ki = ctx.varCounter++
|
|
5400
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
|
|
5401
|
+
if (pn.minLength !== undefined) {
|
|
5402
|
+
lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
5403
|
+
}
|
|
5404
|
+
if (pn.maxLength !== undefined) {
|
|
5405
|
+
lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
|
|
5406
|
+
}
|
|
5407
|
+
if (pn.pattern) {
|
|
5408
|
+
const ri = ctx.varCounter++
|
|
5409
|
+
ctx.closureVars.push(`_re${ri}`)
|
|
5410
|
+
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5411
|
+
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5412
|
+
}
|
|
5413
|
+
if (pn.const !== undefined) {
|
|
5414
|
+
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
5415
|
+
}
|
|
5416
|
+
if (pn.enum) {
|
|
5417
|
+
const ei = ctx.varCounter++
|
|
5418
|
+
ctx.closureVars.push(`_es${ei}`)
|
|
5419
|
+
ctx.closureVals.push(new Set(pn.enum))
|
|
5420
|
+
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'")}}`)
|
|
5421
|
+
}
|
|
5422
|
+
lines.push(`}}`)
|
|
5423
|
+
}
|
|
5424
|
+
|
|
5425
|
+
function genPrefixItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5426
|
+
for (let i = 0; i < schema.prefixItems.length; i++) {
|
|
5427
|
+
const childPath = childPathExpr(pathExpr, String(i))
|
|
5428
|
+
lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
|
|
5429
|
+
genCodeC(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
|
|
5430
|
+
lines.push(`}`)
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
|
|
5434
|
+
function genContainsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
5435
|
+
const ci = ctx.varCounter++
|
|
5436
|
+
const subLines = []
|
|
5437
|
+
nestedGenCode(schema.contains, `_cv`, subLines, ctx)
|
|
5438
|
+
const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
|
|
5439
|
+
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
5440
|
+
const maxC = schema.maxContains
|
|
5441
|
+
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}++}`)
|
|
5442
|
+
lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'contains: need at least ${minC} match(es)'`)}}`)
|
|
5443
|
+
if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'contains: at most ${maxC} match(es)'`)}}`)
|
|
5444
|
+
lines.push(`}`)
|
|
5445
|
+
}
|
|
5446
|
+
|
|
5447
|
+
function genAnyOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
5448
|
+
const fi = ctx.varCounter++
|
|
5449
|
+
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}` })
|
|
5450
|
+
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'")}}}`)
|
|
5451
|
+
}
|
|
5452
|
+
|
|
5453
|
+
function genOneOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
5454
|
+
const fi = ctx.varCounter++
|
|
5455
|
+
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}` })
|
|
5456
|
+
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'")}}}`)
|
|
5457
|
+
}
|
|
5458
|
+
|
|
5459
|
+
function genNotC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
5460
|
+
const sl = []; nestedGenCode(schema.not, '_nv', sl, ctx)
|
|
5461
|
+
const nfn = sl.length === 0 ? `function(_nv){return true}` : `function(_nv){${sl.join(';')};return true}`
|
|
5462
|
+
const fi = ctx.varCounter++
|
|
5463
|
+
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
5464
|
+
}
|
|
5465
|
+
|
|
5466
|
+
function genIfC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5467
|
+
const sl = []; nestedGenCode(schema.if, '_iv', sl, ctx)
|
|
5468
|
+
const fi = ctx.varCounter++
|
|
5469
|
+
const ifFn = sl.length === 0 ? `function(_iv){return true}` : `function(_iv){${sl.join(';')};return true}`
|
|
5470
|
+
lines.push(`{const _if${fi}=${ifFn}`)
|
|
5471
|
+
if (schema.then !== undefined) { lines.push(`if(_if${fi}(${v})){`); genCodeC(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then'); lines.push(`}`) }
|
|
5472
|
+
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(`}`) }
|
|
5473
|
+
lines.push(`}`)
|
|
5474
|
+
}
|
|
5475
|
+
|
|
5348
5476
|
// Collect statically-known evaluated properties/items from a schema.
|
|
5349
5477
|
// Returns { props: string[], items: number|null, allProps: bool, allItems: bool, dynamic: bool }
|
|
5350
5478
|
function collectEvaluated(schema, schemaMap, rootDefs) {
|