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