ata-validator 1.32.2 → 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/README.md +18 -0
- package/index.js +29 -21
- package/lib/clone-emit.js +26 -4
- package/lib/js-compiler.js +1533 -1338
- 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,445 +2481,887 @@ 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}}`)
|
|
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}}`)
|
|
2997
|
+
}
|
|
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;`
|
|
2976
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)
|
|
2977
3024
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
if (
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
const ifLines2 = []
|
|
2986
|
-
genCode(schema.if, '_iv2', ifLines2, ctx)
|
|
2987
|
-
const ufi = ctx.varCounter++
|
|
2988
|
-
const ifFn2 = ifLines2.length === 0
|
|
2989
|
-
? `function(_iv2){return true}`
|
|
2990
|
-
: `function(_iv2){${ifLines2.join(';')};return true}`
|
|
2991
|
-
|
|
2992
|
-
// if props are only evaluated when if matches (spec: failed applicators produce no annotations)
|
|
2993
|
-
const ifProps = []
|
|
2994
|
-
if (schema.if && schema.if.properties) ifProps.push(...Object.keys(schema.if.properties))
|
|
2995
|
-
const thenEval = schema.then ? collectEvaluated(schema.then, ctx.schemaMap, ctx.rootDefs) : { props: [] }
|
|
2996
|
-
const elseEval = schema.else ? collectEvaluated(schema.else, ctx.schemaMap, ctx.rootDefs) : { props: [] }
|
|
2997
|
-
const uniqueThen = [...new Set([...baseProps, ...ifProps, ...(thenEval.props || [])])]
|
|
2998
|
-
const uniqueElse = [...new Set([...baseProps, ...(elseEval.props || [])])]
|
|
2999
|
-
|
|
3000
|
-
const thenCheck = genCharCodeSwitch(uniqueThen, v)
|
|
3001
|
-
const elseCheck = genCharCodeSwitch(uniqueElse, v)
|
|
3002
|
-
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
3003
|
-
lines.push(`${guard}{const _uif${ufi}=${ifFn2};if(_uif${ufi}(${v})){${thenCheck}}else{${elseCheck}}}`)
|
|
3004
|
-
} else if (branchKeyword) {
|
|
3005
|
-
// Tier 3: anyOf/oneOf — runtime tracking
|
|
3006
|
-
const branches = schema[branchKeyword]
|
|
3007
|
-
const branchProps = []
|
|
3008
|
-
for (const sub of branches) {
|
|
3009
|
-
const subResult = collectEvaluated(sub, ctx.schemaMap, ctx.rootDefs)
|
|
3010
|
-
branchProps.push(subResult.props || [])
|
|
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
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3073
|
+
if (dynamicOnly.length > 0 && dynamicOnly.length <= 32) {
|
|
3074
|
+
// TRICK 5: bit-packed evaluated set — SINGLE PASS (validation + tracking combined)
|
|
3075
|
+
const ei = ctx.varCounter++
|
|
3076
|
+
const evVar = `_ev${ei}`
|
|
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))
|
|
3064
3083
|
}
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
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}`)
|
|
3098
|
+
}
|
|
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}`)
|
|
3069
3104
|
}
|
|
3070
|
-
|
|
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}}}`)
|
|
3105
|
+
lines.push(`if(_oc${bfi}!==1)return false`)
|
|
3075
3106
|
} else {
|
|
3076
|
-
//
|
|
3077
|
-
|
|
3078
|
-
const evVar = `_ev${ei}`
|
|
3079
|
-
const fns = []
|
|
3107
|
+
// anyOf: at least one must match — direct calls, collect all
|
|
3108
|
+
lines.push(`let _am${bfi}=false`)
|
|
3080
3109
|
for (let i = 0; i < branches.length; i++) {
|
|
3081
|
-
|
|
3082
|
-
genCode(branches[i], '_bv', subLines2, ctx)
|
|
3083
|
-
fns.push(subLines2.length === 0 ? `function(_bv){return true}` : `function(_bv){${subLines2.join(';')};return true}`)
|
|
3110
|
+
lines.push(`if(${fnVars[i]}(${v})){_am${bfi}=true;${evVar}|=${branchMasks[i]}}`)
|
|
3084
3111
|
}
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3112
|
+
lines.push(`if(!_am${bfi})return false`)
|
|
3113
|
+
}
|
|
3114
|
+
|
|
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)
|
|
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}`
|
|
3137
|
+
const fns = []
|
|
3138
|
+
for (let i = 0; i < branches.length; i++) {
|
|
3139
|
+
const subLines2 = []
|
|
3140
|
+
genCode(branches[i], '_bv', subLines2, ctx)
|
|
3141
|
+
fns.push(subLines2.length === 0 ? `function(_bv){return true}` : `function(_bv){${subLines2.join(';')};return true}`)
|
|
3142
|
+
}
|
|
3143
|
+
const bfi = ctx.varCounter++
|
|
3144
|
+
ctx.closureVars.push(`_bk${bfi}`)
|
|
3145
|
+
ctx.closureVals.push(branchProps)
|
|
3146
|
+
lines.push(`{const ${evVar}={}`)
|
|
3147
|
+
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
3148
|
+
lines.push(`const _bf${bfi}=[${fns.join(',')}]`)
|
|
3149
|
+
if (branchKeyword === 'oneOf') {
|
|
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`)
|
|
3152
|
+
} else {
|
|
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`)
|
|
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}}}`)
|
|
3158
|
+
}
|
|
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
|
+
}
|
|
3170
|
+
}
|
|
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))
|
|
3179
|
+
}
|
|
3180
|
+
if (schema.allOf) {
|
|
3181
|
+
for (const sub of schema.allOf) {
|
|
3182
|
+
if (sub && sub.patternProperties) {
|
|
3183
|
+
allPatterns.push(...Object.keys(sub.patternProperties))
|
|
3097
3184
|
}
|
|
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
3185
|
}
|
|
3101
|
-
}
|
|
3102
|
-
|
|
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) {
|
|
3103
3192
|
const ei = ctx.varCounter++
|
|
3104
3193
|
const evVar = `_ev${ei}`
|
|
3105
3194
|
lines.push(`{const ${evVar}={}`)
|
|
3106
3195
|
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
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
|
-
}
|
|
3127
|
-
}
|
|
3128
|
-
}
|
|
3129
|
-
// lone if (no then/else) still contributes annotations when it passes
|
|
3130
|
-
if (schema.if && !schema.then && !schema.else && schema.if.patternProperties) {
|
|
3131
|
-
allPatterns.push(...Object.keys(schema.if.patternProperties))
|
|
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}`)
|
|
3132
3202
|
}
|
|
3133
|
-
if (
|
|
3134
|
-
|
|
3135
|
-
const
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
const
|
|
3139
|
-
|
|
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) {
|
|
3140
3215
|
const ri = ctx.varCounter++
|
|
3141
3216
|
ctx.closureVars.push(`_ure${ri}`)
|
|
3142
3217
|
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
3143
|
-
|
|
3218
|
+
ifReVars.push(`_ure${ri}`)
|
|
3144
3219
|
}
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
const
|
|
3148
|
-
genCode(schema.if, '_iv2', ifLines2, ctx)
|
|
3149
|
-
const ufi = ctx.varCounter++
|
|
3150
|
-
const ifFn = ifLines2.length === 0
|
|
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) {
|
|
3220
|
+
const rootReVars = []
|
|
3221
|
+
if (schema.patternProperties) {
|
|
3222
|
+
for (const pat of Object.keys(schema.patternProperties)) {
|
|
3157
3223
|
const ri = ctx.varCounter++
|
|
3158
3224
|
ctx.closureVars.push(`_ure${ri}`)
|
|
3159
3225
|
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
3160
|
-
|
|
3226
|
+
rootReVars.push(`_ure${ri}`)
|
|
3161
3227
|
}
|
|
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}}}`)
|
|
3178
3228
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
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
|
-
|
|
3189
|
-
if (branchKeyword) {
|
|
3190
|
-
const branches = schema[branchKeyword]
|
|
3191
|
-
const branchProps = []
|
|
3192
|
-
for (const sub of branches) {
|
|
3193
|
-
const subResult = collectEvaluated(sub, ctx.schemaMap, ctx.rootDefs)
|
|
3194
|
-
branchProps.push(subResult.props || [])
|
|
3195
|
-
}
|
|
3196
|
-
const fns = []
|
|
3197
|
-
for (let i = 0; i < branches.length; i++) {
|
|
3198
|
-
const subLines2 = []
|
|
3199
|
-
genCode(branches[i], '_bv', subLines2, ctx)
|
|
3200
|
-
fns.push(subLines2.length === 0 ? `function(_bv){return true}` : `function(_bv){${subLines2.join(';')};return true}`)
|
|
3201
|
-
}
|
|
3202
|
-
const bfi = ctx.varCounter++
|
|
3203
|
-
ctx.closureVars.push(`_bk${bfi}`)
|
|
3204
|
-
ctx.closureVals.push(branchProps)
|
|
3205
|
-
lines.push(`const _bf${bfi}=[${fns.join(',')}]`)
|
|
3206
|
-
if (branchKeyword === 'oneOf') {
|
|
3207
|
-
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}}`)
|
|
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}}}`)
|
|
3208
3233
|
} else {
|
|
3209
|
-
|
|
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}}}`)
|
|
3210
3236
|
}
|
|
3211
3237
|
}
|
|
3238
|
+
}
|
|
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`)
|
|
3212
3246
|
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
const
|
|
3218
|
-
|
|
3247
|
+
if (branchKeyword) {
|
|
3248
|
+
const branches = schema[branchKeyword]
|
|
3249
|
+
const branchProps = []
|
|
3250
|
+
for (const sub of branches) {
|
|
3251
|
+
const subResult = collectEvaluated(sub, ctx.schemaMap, ctx.rootDefs)
|
|
3252
|
+
branchProps.push(subResult.props || [])
|
|
3253
|
+
}
|
|
3254
|
+
const fns = []
|
|
3255
|
+
for (let i = 0; i < branches.length; i++) {
|
|
3256
|
+
const subLines2 = []
|
|
3257
|
+
genCode(branches[i], '_bv', subLines2, ctx)
|
|
3258
|
+
fns.push(subLines2.length === 0 ? `function(_bv){return true}` : `function(_bv){${subLines2.join(';')};return true}`)
|
|
3259
|
+
}
|
|
3260
|
+
const bfi = ctx.varCounter++
|
|
3261
|
+
ctx.closureVars.push(`_bk${bfi}`)
|
|
3262
|
+
ctx.closureVals.push(branchProps)
|
|
3263
|
+
lines.push(`const _bf${bfi}=[${fns.join(',')}]`)
|
|
3264
|
+
if (branchKeyword === 'oneOf') {
|
|
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}}`)
|
|
3219
3266
|
} else {
|
|
3220
|
-
lines.push(
|
|
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}}`)
|
|
3221
3268
|
}
|
|
3222
3269
|
}
|
|
3270
|
+
|
|
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
|
+
}
|
|
3223
3280
|
}
|
|
3224
3281
|
}
|
|
3282
|
+
}
|
|
3225
3283
|
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
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
|
+
}
|
|
3239
3361
|
if (schema.unevaluatedItems === false) {
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
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
|
|
3362
|
+
const inner = `if(${v}.length>${evVar})return false`
|
|
3363
|
+
_deferOrInline(ctx, lines, v, isArr ? inner + '}' : `if(Array.isArray(${v})){${inner}}}`)
|
|
3364
|
+
} else {
|
|
3246
3365
|
const ui = ctx.varCounter++
|
|
3247
3366
|
const elemVar = `_ue${ui}`
|
|
3248
3367
|
const idxVar = `_ui${ui}`
|
|
@@ -3250,146 +3369,83 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3250
3369
|
genCode(schema.unevaluatedItems, elemVar, subLines, ctx)
|
|
3251
3370
|
if (subLines.length > 0) {
|
|
3252
3371
|
const check = subLines.join(';')
|
|
3253
|
-
const inner = `for(let ${idxVar}=${
|
|
3254
|
-
|
|
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('}')
|
|
3255
3376
|
}
|
|
3256
3377
|
}
|
|
3257
|
-
} else {
|
|
3258
|
-
//
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
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)
|
|
3263
3403
|
if (schema.allOf) {
|
|
3264
3404
|
for (const sub of schema.allOf) {
|
|
3265
|
-
|
|
3266
|
-
if (subR.items !== null) baseIdx = Math.max(baseIdx, subR.items)
|
|
3267
|
-
if (subR.allItems) baseIdx = Infinity
|
|
3405
|
+
if (sub && sub.contains) allContains.push(sub.contains)
|
|
3268
3406
|
}
|
|
3269
3407
|
}
|
|
3270
|
-
|
|
3271
|
-
const
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
for
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
const
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
const
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
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}}}`)
|
|
3307
|
-
} else {
|
|
3308
|
-
const ui = ctx.varCounter++
|
|
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
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
} else if (schema.if && (schema.unevaluatedItems === false || typeof schema.unevaluatedItems === 'object')) {
|
|
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)
|
|
3329
|
-
|
|
3330
|
-
const ifLines2 = []
|
|
3331
|
-
genCode(schema.if, '_iv3', ifLines2, ctx)
|
|
3332
|
-
const ufi = ctx.varCounter++
|
|
3333
|
-
const ifFn3 = ifLines2.length === 0
|
|
3334
|
-
? `function(_iv3){return true}`
|
|
3335
|
-
: `function(_iv3){${ifLines2.join(';')};return true}`
|
|
3336
|
-
|
|
3337
|
-
if (schema.unevaluatedItems === false) {
|
|
3338
|
-
const guard = isArr ? '' : `if(Array.isArray(${v}))`
|
|
3339
|
-
lines.push(`${guard}{const _uif${ufi}=${ifFn3};if(_uif${ufi}(${v})){if(${v}.length>${thenIdx})return false}else{if(${v}.length>${elseIdx})return false}}`)
|
|
3340
|
-
}
|
|
3341
|
-
} else if ((schema.contains || (schema.allOf && schema.allOf.some(s => s && s.contains))) && (schema.unevaluatedItems === false || typeof schema.unevaluatedItems === 'object')) {
|
|
3342
|
-
// contains + unevaluatedItems: per-item tracking of which items are matched by contains
|
|
3343
|
-
// Collect contains from root and allOf sub-schemas
|
|
3344
|
-
const allContains = []
|
|
3345
|
-
if (schema.contains) allContains.push(schema.contains)
|
|
3346
|
-
if (schema.allOf) {
|
|
3347
|
-
for (const sub of schema.allOf) {
|
|
3348
|
-
if (sub && sub.contains) allContains.push(sub.contains)
|
|
3349
|
-
}
|
|
3350
|
-
}
|
|
3351
|
-
const ci = ctx.varCounter++
|
|
3352
|
-
const evArr = `_cev${ci}`
|
|
3353
|
-
const containsFns = []
|
|
3354
|
-
for (const c of allContains) {
|
|
3355
|
-
const cLines = []
|
|
3356
|
-
genCode(c, '_cv', cLines, ctx)
|
|
3357
|
-
containsFns.push(cLines.length === 0
|
|
3358
|
-
? `function(_cv){return true}`
|
|
3359
|
-
: `function(_cv){${cLines.join(';')};return true}`)
|
|
3360
|
-
}
|
|
3361
|
-
const cfnArr = `_cfn${ci}`
|
|
3362
|
-
lines.push(`{const ${cfnArr}=[${containsFns.join(',')}]`)
|
|
3363
|
-
// Mark items evaluated by prefixItems
|
|
3364
|
-
lines.push(`const ${evArr}=[]`)
|
|
3365
|
-
if (baseIdx > 0) {
|
|
3366
|
-
lines.push(`for(let _i=0;_i<${Math.min(baseIdx, 1000)};_i++)${evArr}[_i]=true`)
|
|
3367
|
-
}
|
|
3368
|
-
// Mark items matched by each contains function
|
|
3369
|
-
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}}}}`)
|
|
3370
|
-
if (schema.unevaluatedItems === false) {
|
|
3371
|
-
const inner = `if(Array.isArray(${v})){for(let _ci=0;_ci<${v}.length;_ci++){if(!${evArr}[_ci])return false}}`
|
|
3372
|
-
_deferOrInline(ctx, lines, v, inner + '}')
|
|
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
|
}
|
|
@@ -3509,7 +3565,75 @@ function compilePatternInline(pattern, varName) {
|
|
|
3509
3565
|
const min = parseInt(m[2]), max = parseInt(m[3])
|
|
3510
3566
|
return `${varName}.length>=${min}&&${varName}.length<=${max}&&(()=>{for(let _pi=0;_pi<${varName}.length;_pi++){if(!(${rangeCheck}))return false}return true})()`
|
|
3511
3567
|
}
|
|
3512
|
-
return
|
|
3568
|
+
return fixedSequenceInline(pattern, varName)
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
// A pattern that matches exactly one fixed-length sequence of single
|
|
3572
|
+
// characters, such as `^[A-Z]{3}-\d{4}$` for a SKU or `^\d{3}-\d{4}$` for a
|
|
3573
|
+
// code, unrolled into one character test per position. Each piece is a class
|
|
3574
|
+
// of letters, digits, `_` and `-`, a `\d`, or a literal character, repeated a
|
|
3575
|
+
// fixed number of times; anything else, including every variable-length
|
|
3576
|
+
// quantifier and any negated or escaped class, returns null and keeps the
|
|
3577
|
+
// regular path. Each piece matches exactly one UTF-16 unit in the ASCII range,
|
|
3578
|
+
// so comparing the length and each code unit is the same test the regex
|
|
3579
|
+
// makes. Such patterns went through the safe-regex matcher, which is
|
|
3580
|
+
// linear but slower than both this and the native engine: a quarter of the
|
|
3581
|
+
// time of an order body with one SKU per line item.
|
|
3582
|
+
const FIXED_LITERAL = /[A-Za-z0-9 _\-:@#%&=,;'"<>!~`\/]/
|
|
3583
|
+
function fixedSequenceInline(pattern, varName) {
|
|
3584
|
+
if (pattern.length < 3 || pattern[0] !== '^' || pattern[pattern.length - 1] !== '$' || pattern[pattern.length - 2] === '\\') return null
|
|
3585
|
+
const end = pattern.length - 1
|
|
3586
|
+
const pieces = []
|
|
3587
|
+
let i = 1
|
|
3588
|
+
while (i < end) {
|
|
3589
|
+
let piece
|
|
3590
|
+
const c = pattern[i]
|
|
3591
|
+
if (c === '[') {
|
|
3592
|
+
const j = pattern.indexOf(']', i)
|
|
3593
|
+
if (j < 0 || j >= end) return null
|
|
3594
|
+
const cls = pattern.slice(i, j + 1)
|
|
3595
|
+
if (!/^\[[\w-]+\]$/.test(cls)) return null
|
|
3596
|
+
piece = { cls }
|
|
3597
|
+
i = j + 1
|
|
3598
|
+
} else if (c === '\\') {
|
|
3599
|
+
const e = pattern[i + 1]
|
|
3600
|
+
if (e === 'd') piece = { cls: '[0-9]' }
|
|
3601
|
+
// Only the escapes that stay valid under the u flag: an escaped `-`
|
|
3602
|
+
// outside a class is a syntax error there, and the regex path is the
|
|
3603
|
+
// one that decides what to do about it.
|
|
3604
|
+
else if (e !== undefined && './()[]{}+*?^$|\\'.includes(e)) piece = { lit: e }
|
|
3605
|
+
else return null
|
|
3606
|
+
i += 2
|
|
3607
|
+
} else if (FIXED_LITERAL.test(c)) {
|
|
3608
|
+
piece = { lit: c }
|
|
3609
|
+
i += 1
|
|
3610
|
+
} else {
|
|
3611
|
+
return null
|
|
3612
|
+
}
|
|
3613
|
+
let count = 1
|
|
3614
|
+
if (pattern[i] === '{') {
|
|
3615
|
+
const q = /^\{(\d+)\}/.exec(pattern.slice(i))
|
|
3616
|
+
if (!q) return null
|
|
3617
|
+
count = Number(q[1])
|
|
3618
|
+
i += q[0].length
|
|
3619
|
+
} else if (i < end && '*+?'.includes(pattern[i])) {
|
|
3620
|
+
return null
|
|
3621
|
+
}
|
|
3622
|
+
for (let k = 0; k < count; k++) pieces.push(piece)
|
|
3623
|
+
if (pieces.length > 32) return null
|
|
3624
|
+
}
|
|
3625
|
+
if (pieces.length === 0) return null
|
|
3626
|
+
const checks = []
|
|
3627
|
+
for (let k = 0; k < pieces.length; k++) {
|
|
3628
|
+
const at = `${varName}.charCodeAt(${k})`
|
|
3629
|
+
if (pieces[k].lit !== undefined) checks.push(`${at}===${pieces[k].lit.charCodeAt(0)}`)
|
|
3630
|
+
else {
|
|
3631
|
+
const ck = charClassToCheck(pieces[k].cls, at)
|
|
3632
|
+
if (!ck) return null
|
|
3633
|
+
checks.push(`(${ck})`)
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
return `${varName}.length===${pieces.length}&&${checks.join('&&')}`
|
|
3513
3637
|
}
|
|
3514
3638
|
|
|
3515
3639
|
// Convert [charclass] to charCode range check expression.
|
|
@@ -3534,7 +3658,10 @@ function charClassToCheck(charClass, codeExpr) {
|
|
|
3534
3658
|
const checks = ranges.map(([lo, hi]) =>
|
|
3535
3659
|
lo === hi ? `${codeExpr}===${lo}` : `(${codeExpr}>=${lo}&&${codeExpr}<=${hi})`
|
|
3536
3660
|
)
|
|
3537
|
-
|
|
3661
|
+
// Parenthesised as a whole: the unrolled form joins one of these per
|
|
3662
|
+
// position with &&, and a bare a||b there bound as (len&&a)||(b&&...), so
|
|
3663
|
+
// ^[A-Za-z]{2}$ accepted "xyz" and "1a" from 0.12.1 on.
|
|
3664
|
+
return `(${checks.join('||')})`
|
|
3538
3665
|
}
|
|
3539
3666
|
|
|
3540
3667
|
// Same but for dynamic segments (array indices)
|
|
@@ -3993,24 +4120,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3993
4120
|
}
|
|
3994
4121
|
|
|
3995
4122
|
// $dynamicRef — resolve via anchors map
|
|
3996
|
-
if (schema.$dynamicRef)
|
|
3997
|
-
const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
|
|
3998
|
-
if (ctx.anchors && ctx.anchors[anchorKey]) {
|
|
3999
|
-
const target = ctx.anchors[anchorKey]
|
|
4000
|
-
if (target === ctx.rootSchema) {
|
|
4001
|
-
// Self-recursive: generate _validateE call
|
|
4002
|
-
ctx.usesRecursion = true
|
|
4003
|
-
lines.push(`_validateE(${v},_all,_e)`)
|
|
4004
|
-
} else {
|
|
4005
|
-
const refKey = '$dynamicRef:' + anchorKey
|
|
4006
|
-
if (!ctx.refStack.has(refKey)) {
|
|
4007
|
-
ctx.refStack.add(refKey)
|
|
4008
|
-
genCodeE(target, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4009
|
-
ctx.refStack.delete(refKey)
|
|
4010
|
-
}
|
|
4011
|
-
}
|
|
4012
|
-
}
|
|
4013
|
-
}
|
|
4123
|
+
if (schema.$dynamicRef) genDynamicRefE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4014
4124
|
|
|
4015
4125
|
const types = schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : null
|
|
4016
4126
|
if (types) {
|
|
@@ -4175,22 +4285,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4175
4285
|
}
|
|
4176
4286
|
|
|
4177
4287
|
// uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
|
|
4178
|
-
if (schema.uniqueItems)
|
|
4179
|
-
const si = ctx.varCounter++
|
|
4180
|
-
const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
|
|
4181
|
-
const isPrim = itemType === 'string' || itemType === 'number' || itemType === 'integer'
|
|
4182
|
-
const maxItems = schema.maxItems
|
|
4183
|
-
const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
|
|
4184
|
-
let inner
|
|
4185
|
-
if (isPrim && maxItems && maxItems <= 16) {
|
|
4186
|
-
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}}}`
|
|
4187
|
-
} else if (isPrim) {
|
|
4188
|
-
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)}`
|
|
4189
|
-
} else {
|
|
4190
|
-
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)}`
|
|
4191
|
-
}
|
|
4192
|
-
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
4193
|
-
}
|
|
4288
|
+
if (schema.uniqueItems) genUniqueItemsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr)
|
|
4194
4289
|
|
|
4195
4290
|
// object size
|
|
4196
4291
|
if (schema.minProperties !== undefined) {
|
|
@@ -4240,45 +4335,13 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4240
4335
|
// to emit. The error keeps the keyword's own identity: the interpreted
|
|
4241
4336
|
// engine reports these as unevaluatedProperties with the key named, and the
|
|
4242
4337
|
// two engines are pinned to identical output.
|
|
4243
|
-
if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined)
|
|
4244
|
-
const allowedU = Object.keys(schema.properties || {}).map(k => `${JSON.stringify(k)}`).join(',')
|
|
4245
|
-
const ui = ctx.varCounter++
|
|
4246
|
-
const upSp = `${schemaPrefix}/unevaluatedProperties`
|
|
4247
|
-
const upLit = buildErrorLiteral({ keyword: 'unevaluatedProperties', schemaPath: upSp, sourceMap: ctx.sourceMap })
|
|
4248
|
-
const patChecksU = []
|
|
4249
|
-
for (const pat of Object.keys(schema.patternProperties || {})) {
|
|
4250
|
-
const pattern = JSON.stringify(pat)
|
|
4251
|
-
if (!ctx.regExpMap.has(pattern)) {
|
|
4252
|
-
const ri = ctx.varCounter++
|
|
4253
|
-
ctx.regExpMap.set(pattern, ri)
|
|
4254
|
-
if (patternIsSafe(pat)) {
|
|
4255
|
-
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4256
|
-
ctx.usesSafeRe = true
|
|
4257
|
-
} else {
|
|
4258
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
4259
|
-
}
|
|
4260
|
-
}
|
|
4261
|
-
patChecksU.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ui}[_i])`)
|
|
4262
|
-
}
|
|
4263
|
-
const isUneval = patChecksU.length ? `!_a${ui}.has(_k${ui}[_i])&&!(${patChecksU.join('||')})` : `!_a${ui}.has(_k${ui}[_i])`
|
|
4264
|
-
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}}}`
|
|
4265
|
-
lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
|
|
4266
|
-
}
|
|
4338
|
+
if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined) genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj)
|
|
4267
4339
|
|
|
4268
4340
|
// unevaluatedItems: false under the same rule. The interpreted engine
|
|
4269
4341
|
// reports one error for the array with the first unevaluated position as
|
|
4270
4342
|
// the limit; with adjacent `items` everything is evaluated. The draft-07
|
|
4271
4343
|
// array form of items acts as the prefix.
|
|
4272
|
-
if (schema.unevaluatedItems === false)
|
|
4273
|
-
const itemsIsPrefix = Array.isArray(schema.items)
|
|
4274
|
-
const allEval = schema.items !== undefined && !itemsIsPrefix
|
|
4275
|
-
if (!allEval) {
|
|
4276
|
-
const plen = itemsIsPrefix ? schema.items.length : (Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0)
|
|
4277
|
-
const uiSp = `${schemaPrefix}/unevaluatedItems`
|
|
4278
|
-
const uiLit = buildErrorLiteral({ keyword: 'unevaluatedItems', schemaPath: uiSp, sourceMap: ctx.sourceMap })
|
|
4279
|
-
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}}`)
|
|
4280
|
-
}
|
|
4281
|
-
}
|
|
4344
|
+
if (schema.unevaluatedItems === false) genUnevaluatedItemsFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4282
4345
|
|
|
4283
4346
|
// dependentRequired
|
|
4284
4347
|
if (schema.dependentRequired) {
|
|
@@ -4302,207 +4365,304 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4302
4365
|
}
|
|
4303
4366
|
|
|
4304
4367
|
// patternProperties
|
|
4305
|
-
if (schema.patternProperties)
|
|
4306
|
-
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
4307
|
-
const pattern = JSON.stringify(pat);
|
|
4308
|
-
if (!ctx.regExpMap.has(pattern)) {
|
|
4309
|
-
const ri = ctx.varCounter++
|
|
4310
|
-
ctx.regExpMap.set(pattern, ri)
|
|
4311
|
-
if (patternIsSafe(pat)) {
|
|
4312
|
-
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
4313
|
-
ctx.usesSafeRe = true
|
|
4314
|
-
} else {
|
|
4315
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`);
|
|
4316
|
-
}
|
|
4317
|
-
}
|
|
4318
|
-
const ri = ctx.regExpMap.get(pattern);
|
|
4319
|
-
const ki = ctx.varCounter++
|
|
4320
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){if(_re${ri}.test(_k${ki})){`)
|
|
4321
|
-
const _peFn = emitPtrEsc(ctx)
|
|
4322
|
-
const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
|
|
4323
|
-
// The real pointer, so source maps can locate the failing keyword.
|
|
4324
|
-
genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/patternProperties/' + pat.replace(/~/g, '~0').replace(/\//g, '~1'))
|
|
4325
|
-
lines.push(`}}}`)
|
|
4326
|
-
}
|
|
4327
|
-
}
|
|
4368
|
+
if (schema.patternProperties) genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4328
4369
|
|
|
4329
4370
|
// additionalProperties as a schema — validate each property not declared in
|
|
4330
4371
|
// `properties` against the subschema, with a dynamic /<key> path. codegenSafe
|
|
4331
4372
|
// guarantees no patternProperties or composition here, so "additional" is
|
|
4332
4373
|
// simply "not declared".
|
|
4333
|
-
if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null)
|
|
4334
|
-
const ki = ctx.varCounter++
|
|
4335
|
-
const known = Object.keys(schema.properties || {})
|
|
4336
|
-
const guard = known.length ? `const _ak${ki}=new Set([${known.map((k) => JSON.stringify(k)).join(',')}]);` : ''
|
|
4337
|
-
// A key matched by any patternProperties entry is not additional.
|
|
4338
|
-
const patTests = []
|
|
4339
|
-
for (const pat of Object.keys(schema.patternProperties || {})) {
|
|
4340
|
-
const pattern = JSON.stringify(pat)
|
|
4341
|
-
if (!ctx.regExpMap.has(pattern)) {
|
|
4342
|
-
const ri = ctx.varCounter++
|
|
4343
|
-
ctx.regExpMap.set(pattern, ri)
|
|
4344
|
-
if (patternIsSafe(pat)) {
|
|
4345
|
-
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4346
|
-
ctx.usesSafeRe = true
|
|
4347
|
-
} else {
|
|
4348
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
4349
|
-
}
|
|
4350
|
-
}
|
|
4351
|
-
patTests.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ki})`)
|
|
4352
|
-
}
|
|
4353
|
-
const conds = []
|
|
4354
|
-
if (known.length) conds.push(`!_ak${ki}.has(_k${ki})`)
|
|
4355
|
-
if (patTests.length) conds.push(`!(${patTests.join('||')})`)
|
|
4356
|
-
const keep = conds.length ? `if(${conds.join('&&')}){` : `{`
|
|
4357
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${guard}for(const _k${ki} in ${v}){${keep}`)
|
|
4358
|
-
const _peFn = emitPtrEsc(ctx)
|
|
4359
|
-
const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
|
|
4360
|
-
genCodeE(schema.additionalProperties, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/additionalProperties')
|
|
4361
|
-
lines.push(`}}}`)
|
|
4362
|
-
}
|
|
4374
|
+
if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null) genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4363
4375
|
|
|
4364
4376
|
// dependentSchemas
|
|
4365
|
-
if (schema.dependentSchemas)
|
|
4366
|
-
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
4367
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
4368
|
-
genCodeE(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
|
|
4369
|
-
lines.push(`}`)
|
|
4370
|
-
}
|
|
4371
|
-
}
|
|
4377
|
+
if (schema.dependentSchemas) genDependentSchemasE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4372
4378
|
|
|
4373
4379
|
// propertyNames: false reports every key, at the key's own path, the way the
|
|
4374
4380
|
// interpreter does (plan-compiler: instancePath + '/' + key).
|
|
4375
|
-
if (schema.propertyNames === false)
|
|
4376
|
-
const ki = ctx.varCounter++
|
|
4377
|
-
// The subschema is applied to the property NAME, so the failure belongs to
|
|
4378
|
-
// the object that carries the name, not to `/<key>`, which is where the
|
|
4379
|
-
// value lives. Same placement as the object-valued propertyNames branch.
|
|
4380
|
-
const p = pathExpr || '""'
|
|
4381
|
-
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}}}`)
|
|
4382
|
-
}
|
|
4381
|
+
if (schema.propertyNames === false) genPropertyNamesFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4383
4382
|
|
|
4384
4383
|
// propertyNames
|
|
4385
|
-
if (schema.propertyNames && typeof schema.propertyNames === 'object')
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
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)
|
|
4391
4408
|
}
|
|
4392
|
-
|
|
4393
|
-
|
|
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
|
+
}
|
|
4394
4442
|
}
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
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})`)
|
|
4406
4479
|
}
|
|
4407
|
-
const ri = ctx.regExpMap.get(pattern);
|
|
4408
|
-
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
4409
|
-
}
|
|
4410
|
-
if (pn.const !== undefined) {
|
|
4411
|
-
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
4412
|
-
}
|
|
4413
|
-
if (pn.enum) {
|
|
4414
|
-
const ei = ctx.varCounter++
|
|
4415
|
-
ctx.helperCode.push(`const _es${ei}=new Set(${JSON.stringify(pn.enum)})`)
|
|
4416
|
-
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'")}}`)
|
|
4417
4480
|
}
|
|
4418
|
-
|
|
4481
|
+
patChecksU.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ui}[_i])`)
|
|
4419
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
|
+
}
|
|
4420
4487
|
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
const
|
|
4426
|
-
|
|
4427
|
-
const
|
|
4428
|
-
lines.push(`if(Array.isArray(${v})){
|
|
4429
|
-
genCodeE(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
|
|
4430
|
-
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}}`)
|
|
4431
4496
|
}
|
|
4497
|
+
}
|
|
4432
4498
|
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
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
|
+
}
|
|
4440
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(`}}}`)
|
|
4441
4520
|
}
|
|
4521
|
+
}
|
|
4442
4522
|
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
const
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
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
|
+
}
|
|
4455
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))
|
|
4456
4558
|
lines.push(`}`)
|
|
4457
4559
|
}
|
|
4560
|
+
}
|
|
4458
4561
|
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
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
|
+
}
|
|
4463
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}"'`)}}`)
|
|
4464
4595
|
}
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
if (schema.anyOf) {
|
|
4468
|
-
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'")}}`)
|
|
4469
4598
|
}
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
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'")}}`)
|
|
4474
4603
|
}
|
|
4604
|
+
lines.push(`}}`)
|
|
4605
|
+
}
|
|
4475
4606
|
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
const
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
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(`}`)
|
|
4483
4613
|
}
|
|
4614
|
+
}
|
|
4484
4615
|
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
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')
|
|
4504
4663
|
lines.push(`}`)
|
|
4505
4664
|
}
|
|
4665
|
+
lines.push(`}`)
|
|
4506
4666
|
}
|
|
4507
4667
|
|
|
4508
4668
|
// --- Combined validator: single pass, validates + collects errors ---
|
|
@@ -4688,23 +4848,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4688
4848
|
}
|
|
4689
4849
|
|
|
4690
4850
|
// $dynamicRef — resolve via anchors map
|
|
4691
|
-
if (schema.$dynamicRef)
|
|
4692
|
-
const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
|
|
4693
|
-
if (ctx.anchors && ctx.anchors[anchorKey]) {
|
|
4694
|
-
const target = ctx.anchors[anchorKey]
|
|
4695
|
-
// This generator has no named functions, so it cannot follow a reference
|
|
4696
|
-
// back to the root or around a cycle. It used to emit nothing in both
|
|
4697
|
-
// cases, which the function then answered as valid: a document the other
|
|
4698
|
-
// generators reject passed through validateJSON. It declines instead,
|
|
4699
|
-
// and the schema takes the error generator.
|
|
4700
|
-
if (target === ctx.rootSchema) throw DECLINE
|
|
4701
|
-
const refKey = '$dynamicRef:' + anchorKey
|
|
4702
|
-
if (ctx.refStack.has(refKey)) throw DECLINE
|
|
4703
|
-
ctx.refStack.add(refKey)
|
|
4704
|
-
genCodeC(target, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4705
|
-
ctx.refStack.delete(refKey)
|
|
4706
|
-
}
|
|
4707
|
-
}
|
|
4851
|
+
if (schema.$dynamicRef) genDynamicRefC(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4708
4852
|
|
|
4709
4853
|
const types = schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : null
|
|
4710
4854
|
let isObj = false, isArr = false, isStr = false, isNum = false
|
|
@@ -4906,23 +5050,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4906
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'`)}}`) }
|
|
4907
5051
|
|
|
4908
5052
|
// uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
|
|
4909
|
-
if (schema.uniqueItems)
|
|
4910
|
-
const si = ctx.varCounter++
|
|
4911
|
-
const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
|
|
4912
|
-
const isPrim = itemType === 'string' || itemType === 'number' || itemType === 'integer'
|
|
4913
|
-
const maxItems = schema.maxItems
|
|
4914
|
-
const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
|
|
4915
|
-
let inner
|
|
4916
|
-
if (isPrim && maxItems && maxItems <= 16) {
|
|
4917
|
-
// Small primitive arrays: O(n²) nested loop, zero allocation
|
|
4918
|
-
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}}}`
|
|
4919
|
-
} else if (isPrim) {
|
|
4920
|
-
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)}`
|
|
4921
|
-
} else {
|
|
4922
|
-
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)}`
|
|
4923
|
-
}
|
|
4924
|
-
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
4925
|
-
}
|
|
5053
|
+
if (schema.uniqueItems) genUniqueItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isArr)
|
|
4926
5054
|
|
|
4927
5055
|
// object size
|
|
4928
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'`)}}`)
|
|
@@ -4975,35 +5103,12 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4975
5103
|
// unevaluatedProperties: false where it is provably additionalProperties
|
|
4976
5104
|
// (the entry bail guaranteed that), keeping the keyword's own identity so
|
|
4977
5105
|
// the two engines report the same error.
|
|
4978
|
-
if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined && !schema.patternProperties)
|
|
4979
|
-
const propKeysU = Object.keys(schema.properties || {})
|
|
4980
|
-
const ui = ctx.varCounter++
|
|
4981
|
-
const failU = fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:_k${ui}[_i]}`, "'must NOT have unevaluated properties'")
|
|
4982
|
-
let innerU
|
|
4983
|
-
if (propKeysU.length === 0) {
|
|
4984
|
-
innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++){${failU}}`
|
|
4985
|
-
} else if (propKeysU.length <= 8) {
|
|
4986
|
-
const checksU = propKeysU.map(k => `_k${ui}[_i]!==${JSON.stringify(k)}`).join('&&')
|
|
4987
|
-
innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++)if(${checksU}){${failU}}`
|
|
4988
|
-
} else {
|
|
4989
|
-
const allowedU = propKeysU.map(k => JSON.stringify(k)).join(',')
|
|
4990
|
-
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}}`
|
|
4991
|
-
}
|
|
4992
|
-
lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
|
|
4993
|
-
}
|
|
5106
|
+
if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined && !schema.patternProperties) genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj)
|
|
4994
5107
|
|
|
4995
5108
|
// unevaluatedItems: false under the same rule: one error for the array,
|
|
4996
5109
|
// the first uncovered position as the limit. Adjacent schema-form `items`
|
|
4997
5110
|
// evaluates everything; the draft-07 array form acts as the prefix.
|
|
4998
|
-
if (schema.unevaluatedItems === false)
|
|
4999
|
-
const itemsIsPrefixC = Array.isArray(schema.items)
|
|
5000
|
-
const allEvalC = schema.items !== undefined && !itemsIsPrefixC
|
|
5001
|
-
if (!allEvalC) {
|
|
5002
|
-
const plenC = itemsIsPrefixC ? schema.items.length : (Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0)
|
|
5003
|
-
const failI = fail('unevaluatedItems', 'unevaluatedItems', `{limit:${plenC}}`, `'must NOT have more than ${plenC} items'`)
|
|
5004
|
-
lines.push(`if(Array.isArray(${v})&&${v}.length>${plenC}){${failI}}`)
|
|
5005
|
-
}
|
|
5006
|
-
}
|
|
5111
|
+
if (schema.unevaluatedItems === false) genUnevaluatedItemsFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5007
5112
|
|
|
5008
5113
|
// dependentRequired
|
|
5009
5114
|
if (schema.dependentRequired) {
|
|
@@ -5049,153 +5154,16 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5049
5154
|
|
|
5050
5155
|
// patternProperties — same optimizations as genCode: charCodeAt + inline key comparison + merged propertyNames
|
|
5051
5156
|
if (schema.patternProperties) {
|
|
5052
|
-
|
|
5053
|
-
const pn = schema.propertyNames && typeof schema.propertyNames === 'object' ? schema.propertyNames : null
|
|
5054
|
-
const pi = ctx.varCounter++
|
|
5055
|
-
|
|
5056
|
-
// Build pattern matchers: prefer charCodeAt for simple prefixes
|
|
5057
|
-
const matchers = []
|
|
5058
|
-
for (const [pat] of ppEntries) {
|
|
5059
|
-
const kVar = `_k${pi}`
|
|
5060
|
-
const fast = fastPrefixCheck(pat, kVar)
|
|
5061
|
-
if (fast) {
|
|
5062
|
-
matchers.push({ check: fast })
|
|
5063
|
-
} else {
|
|
5064
|
-
const ri = ctx.varCounter++
|
|
5065
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
5066
|
-
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
5067
|
-
matchers.push({ check: `_re${ri}.test(_k${pi})` })
|
|
5068
|
-
}
|
|
5069
|
-
}
|
|
5070
|
-
|
|
5071
|
-
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
5072
|
-
const kVar = `_k${pi}`
|
|
5073
|
-
|
|
5074
|
-
if (schema.additionalProperties === false && schema.properties) {
|
|
5075
|
-
ppHandledPropertyNames = !!pn
|
|
5076
|
-
const propKeys = Object.keys(schema.properties)
|
|
5077
|
-
// Inline key comparison for small property sets
|
|
5078
|
-
const keyCheck = propKeys.length <= 8
|
|
5079
|
-
? propKeys.map(k => `${kVar}===${JSON.stringify(k)}`).join('||')
|
|
5080
|
-
: null
|
|
5081
|
-
if (!keyCheck) {
|
|
5082
|
-
const allowedSet = `_as${pi}`
|
|
5083
|
-
ctx.closureVars.push(allowedSet)
|
|
5084
|
-
ctx.closureVals.push(new Set(propKeys))
|
|
5085
|
-
}
|
|
5086
|
-
|
|
5087
|
-
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
5088
|
-
// propertyNames checks (merged)
|
|
5089
|
-
if (pn) {
|
|
5090
|
-
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'`)}}`)
|
|
5091
|
-
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'`)}}`)
|
|
5092
|
-
if (pn.pattern) {
|
|
5093
|
-
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
5094
|
-
if (fast) {
|
|
5095
|
-
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5096
|
-
} else {
|
|
5097
|
-
const ri = ctx.varCounter++
|
|
5098
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
5099
|
-
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5100
|
-
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5101
|
-
}
|
|
5102
|
-
}
|
|
5103
|
-
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'")}}`)
|
|
5104
|
-
if (pn.enum) {
|
|
5105
|
-
const ei = ctx.varCounter++
|
|
5106
|
-
ctx.closureVars.push(`_es${ei}`)
|
|
5107
|
-
ctx.closureVals.push(new Set(pn.enum))
|
|
5108
|
-
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'")}}`)
|
|
5109
|
-
}
|
|
5110
|
-
}
|
|
5111
|
-
const matchExpr = keyCheck || `_as${pi}.has(${kVar})`
|
|
5112
|
-
lines.push(`let _m${pi}=${matchExpr}`)
|
|
5113
|
-
for (let i = 0; i < ppEntries.length; i++) {
|
|
5114
|
-
// The subschema is generated in place so its own keywords report
|
|
5115
|
-
// the real instance path and schema pointer.
|
|
5116
|
-
lines.push(`if(${matchers[i].check}){_m${pi}=true;{const _ppv${pi}_${i}=${v}[${kVar}]`)
|
|
5117
|
-
genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, `${emitPtrEsc(ctx)}(${kVar})`), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
|
|
5118
|
-
lines.push(`}}`)
|
|
5119
|
-
}
|
|
5120
|
-
lines.push(`if(!_m${pi}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:${kVar}}`, "'must NOT have additional properties'")}}`)
|
|
5121
|
-
lines.push(`}}`)
|
|
5122
|
-
} else {
|
|
5123
|
-
ppHandledPropertyNames = !!pn
|
|
5124
|
-
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
5125
|
-
if (pn) {
|
|
5126
|
-
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'`)}}`)
|
|
5127
|
-
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'`)}}`)
|
|
5128
|
-
if (pn.pattern) {
|
|
5129
|
-
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
5130
|
-
if (fast) {
|
|
5131
|
-
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5132
|
-
} else {
|
|
5133
|
-
const ri = ctx.varCounter++
|
|
5134
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
5135
|
-
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5136
|
-
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5137
|
-
}
|
|
5138
|
-
}
|
|
5139
|
-
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'")}}`)
|
|
5140
|
-
if (pn.enum) {
|
|
5141
|
-
const ei = ctx.varCounter++
|
|
5142
|
-
ctx.closureVars.push(`_es${ei}`)
|
|
5143
|
-
ctx.closureVals.push(new Set(pn.enum))
|
|
5144
|
-
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'")}}`)
|
|
5145
|
-
}
|
|
5146
|
-
}
|
|
5147
|
-
for (let i = 0; i < ppEntries.length; i++) {
|
|
5148
|
-
lines.push(`if(${matchers[i].check}){const _ppv${pi}_${i}=${v}[${kVar}]`)
|
|
5149
|
-
genCodeC(ppEntries[i][1], `_ppv${pi}_${i}`, childPathDynExpr(pathExpr, `${emitPtrEsc(ctx)}(${kVar})`), lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(ppEntries[i][0]))
|
|
5150
|
-
lines.push(`}`)
|
|
5151
|
-
}
|
|
5152
|
-
lines.push(`}}`)
|
|
5153
|
-
}
|
|
5157
|
+
if (genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj)) ppHandledPropertyNames = true
|
|
5154
5158
|
}
|
|
5155
5159
|
|
|
5156
5160
|
// dependentSchemas
|
|
5157
|
-
if (schema.dependentSchemas)
|
|
5158
|
-
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
5159
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
5160
|
-
genCodeC(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
|
|
5161
|
-
lines.push(`}`)
|
|
5162
|
-
}
|
|
5163
|
-
}
|
|
5161
|
+
if (schema.dependentSchemas) genDependentSchemasC(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5164
5162
|
|
|
5165
|
-
if (schema.propertyNames === false)
|
|
5166
|
-
const ki = ctx.varCounter++
|
|
5167
|
-
const p = pathExpr || '""'
|
|
5168
|
-
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'})}}`)
|
|
5169
|
-
}
|
|
5163
|
+
if (schema.propertyNames === false) genPropertyNamesFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5170
5164
|
|
|
5171
5165
|
// propertyNames — skip if already merged into patternProperties loop
|
|
5172
|
-
if (schema.propertyNames && typeof schema.propertyNames === 'object' && !ppHandledPropertyNames)
|
|
5173
|
-
const pn = schema.propertyNames
|
|
5174
|
-
const ki = ctx.varCounter++
|
|
5175
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
|
|
5176
|
-
if (pn.minLength !== undefined) {
|
|
5177
|
-
lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
5178
|
-
}
|
|
5179
|
-
if (pn.maxLength !== undefined) {
|
|
5180
|
-
lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
|
|
5181
|
-
}
|
|
5182
|
-
if (pn.pattern) {
|
|
5183
|
-
const ri = ctx.varCounter++
|
|
5184
|
-
ctx.closureVars.push(`_re${ri}`)
|
|
5185
|
-
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5186
|
-
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
5187
|
-
}
|
|
5188
|
-
if (pn.const !== undefined) {
|
|
5189
|
-
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
5190
|
-
}
|
|
5191
|
-
if (pn.enum) {
|
|
5192
|
-
const ei = ctx.varCounter++
|
|
5193
|
-
ctx.closureVars.push(`_es${ei}`)
|
|
5194
|
-
ctx.closureVals.push(new Set(pn.enum))
|
|
5195
|
-
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'")}}`)
|
|
5196
|
-
}
|
|
5197
|
-
lines.push(`}}`)
|
|
5198
|
-
}
|
|
5166
|
+
if (schema.propertyNames && typeof schema.propertyNames === 'object' && !ppHandledPropertyNames) genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5199
5167
|
|
|
5200
5168
|
// items
|
|
5201
5169
|
if (schema.items !== undefined && schema.items !== true) {
|
|
@@ -5209,64 +5177,25 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5209
5177
|
}
|
|
5210
5178
|
|
|
5211
5179
|
// prefixItems
|
|
5212
|
-
if (schema.prefixItems)
|
|
5213
|
-
for (let i = 0; i < schema.prefixItems.length; i++) {
|
|
5214
|
-
const childPath = childPathExpr(pathExpr, String(i))
|
|
5215
|
-
lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
|
|
5216
|
-
genCodeC(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
|
|
5217
|
-
lines.push(`}`)
|
|
5218
|
-
}
|
|
5219
|
-
}
|
|
5180
|
+
if (schema.prefixItems) genPrefixItemsC(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5220
5181
|
|
|
5221
5182
|
// contains
|
|
5222
|
-
if (schema.contains !== undefined)
|
|
5223
|
-
const ci = ctx.varCounter++
|
|
5224
|
-
const subLines = []
|
|
5225
|
-
nestedGenCode(schema.contains, `_cv`, subLines, ctx)
|
|
5226
|
-
const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
|
|
5227
|
-
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
5228
|
-
const maxC = schema.maxContains
|
|
5229
|
-
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}++}`)
|
|
5230
|
-
lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'contains: need at least ${minC} match(es)'`)}}`)
|
|
5231
|
-
if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'contains: at most ${maxC} match(es)'`)}}`)
|
|
5232
|
-
lines.push(`}`)
|
|
5233
|
-
}
|
|
5183
|
+
if (schema.contains !== undefined) genContainsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5234
5184
|
|
|
5235
5185
|
// allOf
|
|
5236
5186
|
if (schema.allOf) { for (let _ai = 0; _ai < schema.allOf.length; _ai++) genCodeC(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai) }
|
|
5237
5187
|
|
|
5238
5188
|
// anyOf
|
|
5239
|
-
if (schema.anyOf)
|
|
5240
|
-
const fi = ctx.varCounter++
|
|
5241
|
-
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}` })
|
|
5242
|
-
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'")}}}`)
|
|
5243
|
-
}
|
|
5189
|
+
if (schema.anyOf) genAnyOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5244
5190
|
|
|
5245
5191
|
// oneOf
|
|
5246
|
-
if (schema.oneOf)
|
|
5247
|
-
const fi = ctx.varCounter++
|
|
5248
|
-
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}` })
|
|
5249
|
-
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'")}}}`)
|
|
5250
|
-
}
|
|
5192
|
+
if (schema.oneOf) genOneOfC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5251
5193
|
|
|
5252
5194
|
// not
|
|
5253
|
-
if (schema.not !== undefined)
|
|
5254
|
-
const sl = []; nestedGenCode(schema.not, '_nv', sl, ctx)
|
|
5255
|
-
const nfn = sl.length === 0 ? `function(_nv){return true}` : `function(_nv){${sl.join(';')};return true}`
|
|
5256
|
-
const fi = ctx.varCounter++
|
|
5257
|
-
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
5258
|
-
}
|
|
5195
|
+
if (schema.not !== undefined) genNotC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
5259
5196
|
|
|
5260
5197
|
// if/then/else
|
|
5261
|
-
if (schema.if !== undefined)
|
|
5262
|
-
const sl = []; nestedGenCode(schema.if, '_iv', sl, ctx)
|
|
5263
|
-
const fi = ctx.varCounter++
|
|
5264
|
-
const ifFn = sl.length === 0 ? `function(_iv){return true}` : `function(_iv){${sl.join(';')};return true}`
|
|
5265
|
-
lines.push(`{const _if${fi}=${ifFn}`)
|
|
5266
|
-
if (schema.then !== undefined) { lines.push(`if(_if${fi}(${v})){`); genCodeC(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then'); lines.push(`}`) }
|
|
5267
|
-
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(`}`) }
|
|
5268
|
-
lines.push(`}`)
|
|
5269
|
-
}
|
|
5198
|
+
if (schema.if !== undefined) genIfC(schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5270
5199
|
|
|
5271
5200
|
// Close type-success block if opened
|
|
5272
5201
|
if (types) {
|
|
@@ -5274,6 +5203,272 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5274
5203
|
}
|
|
5275
5204
|
}
|
|
5276
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
|
+
|
|
5277
5472
|
// Collect statically-known evaluated properties/items from a schema.
|
|
5278
5473
|
// Returns { props: string[], items: number|null, allProps: bool, allItems: bool, dynamic: bool }
|
|
5279
5474
|
function collectEvaluated(schema, schemaMap, rootDefs) {
|