ata-validator 1.42.0 → 1.44.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 +10 -10
- package/index.js +56 -2
- package/lib/branch-collapse.js +18 -12
- package/lib/clone-emit.js +5 -2
- package/lib/data-position-cache.js +12 -6
- package/lib/draft7.js +5 -7
- package/lib/enrich-site.js +2 -2
- package/lib/js-compiler.js +788 -172
- package/lib/levenshtein.js +34 -22
- package/lib/native-positions.js +100 -0
- package/lib/rejections.js +14 -7
- package/lib/scan-compiler.js +210 -25
- package/lib/scan-runtime.js +13 -1
- package/lib/suggestions.js +27 -20
- package/lib/validator-core.js +94 -14
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/lib/js-compiler.js
CHANGED
|
@@ -176,7 +176,24 @@ const { compileSafe, patternIsSafe } = require('./safe-regex')
|
|
|
176
176
|
let _nativeIsLinear = null
|
|
177
177
|
// The linearity check only parses; it runs first so a pattern bound for the
|
|
178
178
|
// RegExp is never compiled for the engine just to be told it could have been.
|
|
179
|
+
// Unicode property escapes need a real RegExp in unicode mode, as the
|
|
180
|
+
// interpreter compiles them (Interpreter#pattern): the linear engine does not
|
|
181
|
+
// know them, and without the flag `\p{L}` reads as the letters p, {, L, }.
|
|
182
|
+
// The flag only where the pattern is valid with it, again as the interpreter
|
|
183
|
+
// does. These patterns used to send the whole schema to the interpreter.
|
|
184
|
+
const PROP_ESCAPE = /\\[pP]\{/
|
|
185
|
+
function reFlags (src) {
|
|
186
|
+
if (!PROP_ESCAPE.test(src)) return ''
|
|
187
|
+
try { new RegExp(src, 'u'); return 'u' } catch { return '' }
|
|
188
|
+
}
|
|
189
|
+
// The flag argument for generated `new RegExp(<json literal>)` source.
|
|
190
|
+
function reFlagArg (jsonLit) {
|
|
191
|
+
let raw
|
|
192
|
+
try { raw = JSON.parse(jsonLit) } catch { return '' }
|
|
193
|
+
return typeof raw === 'string' && reFlags(raw) ? ",'u'" : ''
|
|
194
|
+
}
|
|
179
195
|
const useSafeEngine = (src) => {
|
|
196
|
+
if (PROP_ESCAPE.test(src)) return false
|
|
180
197
|
if (_nativeIsLinear === null) _nativeIsLinear = require('./regex-linear').nativeIsLinear
|
|
181
198
|
if (_nativeIsLinear(src)) return false
|
|
182
199
|
return patternIsSafe(src)
|
|
@@ -187,7 +204,7 @@ const useSafeEngine = (src) => {
|
|
|
187
204
|
// standalone embeds the engine), else a plain RegExp. Both expose `.test`.
|
|
188
205
|
function safeReClosure (ctx, src) {
|
|
189
206
|
if (useSafeEngine(src)) { ctx.usesSafeRe = true; return compileSafe(src) }
|
|
190
|
-
return new RegExp(src)
|
|
207
|
+
return new RegExp(src, reFlags(src))
|
|
191
208
|
}
|
|
192
209
|
|
|
193
210
|
// Compile a JSON Schema into a pure JS validator function.
|
|
@@ -228,7 +245,7 @@ function literalValue (lit) {
|
|
|
228
245
|
}
|
|
229
246
|
|
|
230
247
|
function unescapeSp (sp) {
|
|
231
|
-
return sp.indexOf('\\') === -1 ? sp : sp.replace(/\\(.)/g,
|
|
248
|
+
return sp.indexOf('\\') === -1 ? sp : sp.replace(/\\u([0-9a-fA-F]{4})|\\(.)/g, (m, h, c) => (h !== undefined ? String.fromCharCode(parseInt(h, 16)) : c))
|
|
232
249
|
}
|
|
233
250
|
|
|
234
251
|
const FRAME_INLINE_MAX = 120
|
|
@@ -338,6 +355,7 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
338
355
|
// A $ref cycle on one value recurses here as it does in generated code; see
|
|
339
356
|
// hasSameInstanceRefCycle.
|
|
340
357
|
if (!defs && hasSameInstanceRefCycle(schema)) return null
|
|
358
|
+
if (!defs && bothDefsContainers(schema)) return null
|
|
341
359
|
|
|
342
360
|
// unevaluatedProperties / unevaluatedItems need annotation tracking this
|
|
343
361
|
// path does not do. Decline so the schema reaches an engine that does.
|
|
@@ -589,7 +607,7 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
589
607
|
}
|
|
590
608
|
if (schema.pattern) {
|
|
591
609
|
try {
|
|
592
|
-
const re = useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern)
|
|
610
|
+
const re = useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern, reFlags(schema.pattern))
|
|
593
611
|
checks.push((d) => typeof d !== 'string' || re.test(d))
|
|
594
612
|
} catch {
|
|
595
613
|
return null
|
|
@@ -1047,6 +1065,16 @@ function hasOwnKey (d, key) {
|
|
|
1047
1065
|
return _hop.call(d, key)
|
|
1048
1066
|
}
|
|
1049
1067
|
|
|
1068
|
+
// Writes an own key. A schema can name a property, a definition or even a
|
|
1069
|
+
// keyword "__proto__", and assigning that key sets the copy's prototype
|
|
1070
|
+
// instead: the property was gone from the copy, so a constraint on it was
|
|
1071
|
+
// generated as nothing and accepted anything (a schema with such a property
|
|
1072
|
+
// and a reference the resolver rewrites, 1.40.1 to 1.43.0).
|
|
1073
|
+
function setOwn (o, k, v) {
|
|
1074
|
+
if (k === '__proto__') Object.defineProperty(o, k, { value: v, writable: true, enumerable: true, configurable: true })
|
|
1075
|
+
else o[k] = v
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1050
1078
|
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'toString', 'valueOf',
|
|
1051
1079
|
'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString'])
|
|
1052
1080
|
|
|
@@ -1362,7 +1390,7 @@ function aliasChainEnds(def, defs) {
|
|
|
1362
1390
|
// source of evaluated properties the schema has; when it does not, the
|
|
1363
1391
|
// schema is not generated and the interpreter answers.
|
|
1364
1392
|
function unevalPropsModeled(schema, schemaMap) {
|
|
1365
|
-
const own =
|
|
1393
|
+
const own = { ...schema }
|
|
1366
1394
|
delete own.$defs
|
|
1367
1395
|
delete own.definitions
|
|
1368
1396
|
if (/"\$(?:ref|dynamicRef|recursiveRef)"/.test(JSON.stringify(own))) return false
|
|
@@ -1381,7 +1409,7 @@ function unevalPropsModeled(schema, schemaMap) {
|
|
|
1381
1409
|
if (typeof sub !== 'object' || sub === null) return false
|
|
1382
1410
|
const r = collectEvaluated(sub, schemaMap)
|
|
1383
1411
|
if (r.dynamic || r.allProps) return false
|
|
1384
|
-
const rest =
|
|
1412
|
+
const rest = { ...sub }
|
|
1385
1413
|
delete rest.patternProperties
|
|
1386
1414
|
const j = JSON.stringify(rest)
|
|
1387
1415
|
if (j.includes('"patternProperties"') || j.includes('"unevaluatedProperties"')) return false
|
|
@@ -1414,7 +1442,7 @@ function unevalPropsModeled(schema, schemaMap) {
|
|
|
1414
1442
|
if (typeof schema.if !== 'object' || schema.if === null) return false
|
|
1415
1443
|
const r = collectEvaluated(schema.if, schemaMap)
|
|
1416
1444
|
if (r.dynamic || r.allProps || r.props.length) return false
|
|
1417
|
-
const rest =
|
|
1445
|
+
const rest = { ...schema.if }
|
|
1418
1446
|
delete rest.patternProperties
|
|
1419
1447
|
const j = JSON.stringify(rest)
|
|
1420
1448
|
if (j.includes('"patternProperties"') || j.includes('"unevaluatedProperties"')) return false
|
|
@@ -1468,20 +1496,9 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1468
1496
|
}
|
|
1469
1497
|
|
|
1470
1498
|
// Keys that collide with Object.prototype
|
|
1471
|
-
|
|
1472
|
-
for (const k of schema.required) {
|
|
1473
|
-
if (UNSAFE_KEYS.has(k)) return false
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
if (schema.properties) {
|
|
1477
|
-
for (const k of Object.keys(schema.properties)) {
|
|
1478
|
-
if (UNSAFE_KEYS.has(k)) return false
|
|
1479
|
-
if (k === '$ref') return false // property named "$ref" — confusing
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1499
|
+
|
|
1482
1500
|
|
|
1483
1501
|
// Unicode property escapes in pattern need 'u' flag — codegen uses RegExp without it
|
|
1484
|
-
if (schema.pattern && /\\[pP]\{/.test(schema.pattern)) return false
|
|
1485
1502
|
|
|
1486
1503
|
// $ref — allow local refs (#/$defs/Name) and non-local refs if in schemaMap
|
|
1487
1504
|
if (schema.$ref) {
|
|
@@ -1516,17 +1533,11 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1516
1533
|
!resolvedTarget.propertyNames
|
|
1517
1534
|
if (!targetSimple && schema.unevaluatedProperties === undefined && schema.unevaluatedItems === undefined) return false
|
|
1518
1535
|
}
|
|
1519
|
-
// In Draft 2020-12, $ref with siblings
|
|
1520
|
-
//
|
|
1521
|
-
//
|
|
1522
|
-
//
|
|
1523
|
-
//
|
|
1524
|
-
// inferred from what is not a validation keyword, so a keyword this list
|
|
1525
|
-
// does not know still declines instead of being skipped. Measured on the
|
|
1526
|
-
// 977 SchemaStore schemas: a $ref with a description or title beside it sent
|
|
1527
|
-
// 223 of them to the interpreted engine.
|
|
1528
|
-
const siblings = Object.keys(schema).filter(k => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))
|
|
1529
|
-
if (siblings.length > 0 && schema.unevaluatedProperties === undefined && schema.unevaluatedItems === undefined) return false
|
|
1536
|
+
// In Draft 2020-12, $ref with siblings applies both. The verdict
|
|
1537
|
+
// generator compiles the siblings after the reference (genCodeNode), the
|
|
1538
|
+
// combined one too (genCodeCNode), and the error generator declines such
|
|
1539
|
+
// a node, so a sibling is never dropped. This used to decline the whole
|
|
1540
|
+
// schema unless an unevaluated keyword was beside the reference.
|
|
1530
1541
|
}
|
|
1531
1542
|
|
|
1532
1543
|
// additionalProperties as schema — supported when no composition (allOf/oneOf/anyOf)
|
|
@@ -1608,7 +1619,6 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1608
1619
|
// compiled to code that ignored it.
|
|
1609
1620
|
if (schema.patternProperties) {
|
|
1610
1621
|
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
1611
|
-
if (/\\[pP]\{/.test(pat)) return false
|
|
1612
1622
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return false
|
|
1613
1623
|
}
|
|
1614
1624
|
}
|
|
@@ -1617,10 +1627,8 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1617
1627
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return false
|
|
1618
1628
|
}
|
|
1619
1629
|
}
|
|
1620
|
-
if (typeof schema.propertyNames === 'object' && schema.propertyNames !== null
|
|
1621
|
-
|
|
1622
|
-
if (Object.keys(schema.propertyNames).some(k => k !== '$schema' && !supported.includes(k))) return false
|
|
1623
|
-
}
|
|
1630
|
+
if (typeof schema.propertyNames === 'object' && schema.propertyNames !== null &&
|
|
1631
|
+
!isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return false
|
|
1624
1632
|
|
|
1625
1633
|
// Recurse into sub-schemas — bail on boolean schemas in any position
|
|
1626
1634
|
// A boolean subschema is a constant that every generator emits at its
|
|
@@ -1947,8 +1955,33 @@ function hasSameInstanceRefCycle(root) {
|
|
|
1947
1955
|
return found
|
|
1948
1956
|
}
|
|
1949
1957
|
|
|
1958
|
+
// The generators look a definition up by name in one table, `$defs` or else
|
|
1959
|
+
// `definitions`, whichever container the pointer named. With both at the root,
|
|
1960
|
+
// `#/definitions/x` read `$defs/x`: a draft 7 schema carrying both, with
|
|
1961
|
+
// different schemas under one name, rejected what it allowed and accepted
|
|
1962
|
+
// what it rejected. The interpreter resolves the pointer as written, so a
|
|
1963
|
+
// schema with both goes there. None of SchemaStore's 980 has both.
|
|
1964
|
+
function bothDefsContainers(schema) {
|
|
1965
|
+
return schema.$defs !== null && typeof schema.$defs === 'object' &&
|
|
1966
|
+
schema.definitions !== null && typeof schema.definitions === 'object'
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
// The answer depends on the schema and the map alone, and the three
|
|
1970
|
+
// generators ask it about the same prepared schema in turn, so the last
|
|
1971
|
+
// answer is kept: each check walks the whole schema, and the second and third
|
|
1972
|
+
// asked again what the first had found. One entry, compared by identity,
|
|
1973
|
+
// because a WeakMap entry per schema cost more than the walk it saved on a
|
|
1974
|
+
// small schema that only ever gets its verdict generated.
|
|
1975
|
+
let _gateSchema = null, _gateMap = null, _gateOk = false
|
|
1950
1976
|
function sharedCodegenGate(schema, schemaMap) {
|
|
1951
1977
|
if (typeof schema !== 'object' || schema === null) return true
|
|
1978
|
+
if (schema === _gateSchema && schemaMap === _gateMap) return _gateOk
|
|
1979
|
+
const ok = sharedCodegenGateUncached(schema, schemaMap)
|
|
1980
|
+
_gateSchema = schema; _gateMap = schemaMap; _gateOk = ok
|
|
1981
|
+
return ok
|
|
1982
|
+
}
|
|
1983
|
+
function sharedCodegenGateUncached(schema, schemaMap) {
|
|
1984
|
+
if (bothDefsContainers(schema)) return false
|
|
1952
1985
|
if (hasSameInstanceRefCycle(schema)) return false
|
|
1953
1986
|
if (!codegenSafe(schema, schemaMap)) return false
|
|
1954
1987
|
if (needsBaseTracking(schema, schemaMap, new Set())) return false
|
|
@@ -1963,6 +1996,15 @@ function sharedCodegenGate(schema, schemaMap) {
|
|
|
1963
1996
|
// `type: 'string'` and annotations go, on a copy. Anything else is left as
|
|
1964
1997
|
// written and the gate declines. Errors keep the interpreted engine's path.
|
|
1965
1998
|
const PN_SUPPORTED = new Set(['maxLength', 'minLength', 'pattern', 'const', 'enum'])
|
|
1999
|
+
// Whether the generators can write `propertyNames` as checks on the key in the
|
|
2000
|
+
// object's own key loop. Any other subschema is applied to each key as a
|
|
2001
|
+
// value of its own (genPropertyNamesNode and its error and combined
|
|
2002
|
+
// counterparts), which is what the interpreter does.
|
|
2003
|
+
function isSimplePN (pn) {
|
|
2004
|
+
if (pn === null || typeof pn !== 'object' || Array.isArray(pn)) return false
|
|
2005
|
+
for (const k of Object.keys(pn)) if (k !== '$schema' && !PN_SUPPORTED.has(k)) return false
|
|
2006
|
+
return true
|
|
2007
|
+
}
|
|
1966
2008
|
const PN_ANNOTATIONS = new Set(['$schema', '$comment', 'title', 'description', 'examples', 'default', 'deprecated', 'readOnly', 'writeOnly'])
|
|
1967
2009
|
function simplePropertyNames(pn, defs) {
|
|
1968
2010
|
const seen = new Set()
|
|
@@ -1984,7 +2026,7 @@ function simplePropertyNames(pn, defs) {
|
|
|
1984
2026
|
return null
|
|
1985
2027
|
}
|
|
1986
2028
|
if (!PN_SUPPORTED.has(k)) return null
|
|
1987
|
-
out
|
|
2029
|
+
setOwn(out, k, val)
|
|
1988
2030
|
}
|
|
1989
2031
|
return out
|
|
1990
2032
|
}
|
|
@@ -2015,14 +2057,14 @@ function normalizePropertyNamesWalk(root) {
|
|
|
2015
2057
|
return out
|
|
2016
2058
|
}
|
|
2017
2059
|
let out = node
|
|
2018
|
-
const set = (k, v) => { if (out === node) out = { ...node }; out
|
|
2060
|
+
const set = (k, v) => { if (out === node) out = { ...node }; setOwn(out, k, v) }
|
|
2019
2061
|
for (const k of SUBSCHEMA_MAPS) {
|
|
2020
2062
|
const map = node[k]
|
|
2021
2063
|
if (!map || typeof map !== 'object') continue
|
|
2022
2064
|
let copy = map
|
|
2023
2065
|
for (const [name, sub] of Object.entries(map)) {
|
|
2024
2066
|
const w = walk(sub)
|
|
2025
|
-
if (w !== sub) { if (copy === map) copy = { ...map }; copy
|
|
2067
|
+
if (w !== sub) { if (copy === map) copy = { ...map }; setOwn(copy, name, w) }
|
|
2026
2068
|
}
|
|
2027
2069
|
if (copy !== map) set(k, copy)
|
|
2028
2070
|
}
|
|
@@ -2140,7 +2182,7 @@ function normalizeDeepRefsWalk(root) {
|
|
|
2140
2182
|
if (inner) return node
|
|
2141
2183
|
let out = node
|
|
2142
2184
|
memo.set(node, out)
|
|
2143
|
-
const own = (k, v) => { if (out === node) { out = { ...node }; memo.set(node, out) } out
|
|
2185
|
+
const own = (k, v) => { if (out === node) { out = { ...node }; memo.set(node, out) } setOwn(out, k, v) }
|
|
2144
2186
|
if (typeof node.$ref === 'string' && names.has(node.$ref)) own('$ref', '#/' + defsKey + '/' + names.get(node.$ref))
|
|
2145
2187
|
for (const k of Object.keys(node)) {
|
|
2146
2188
|
if (k === '$ref' || k === 'enum' || k === 'const' || k === 'default' || k === 'examples') continue
|
|
@@ -2189,10 +2231,39 @@ function normalizeRefs(root, schemaMap) {
|
|
|
2189
2231
|
return out
|
|
2190
2232
|
}
|
|
2191
2233
|
const SKIP_VALUE_KEYS = new Set(['enum', 'const', 'default', 'examples'])
|
|
2234
|
+
// A schema whose every reference is `#` or a plain `#/$defs/name` (or
|
|
2235
|
+
// `definitions`), with no `$id` or `$anchor` below the root to move the base
|
|
2236
|
+
// they resolve against, needs nothing from the resolver: each reference
|
|
2237
|
+
// already reads as the generators read it. Telling that from the text skips
|
|
2238
|
+
// loading the interpreted engine and indexing the schema, which was most of
|
|
2239
|
+
// what preparing a small schema for code generation cost.
|
|
2240
|
+
const LOCAL_ONLY_REF = /^#(?:\/(?:\$defs|definitions)\/[^/~%]+)?$/
|
|
2241
|
+
function refsAllPlainLocal (root, text) {
|
|
2242
|
+
const ids = text.split('"$id":').length - 1
|
|
2243
|
+
if (ids > (typeof root.$id === 'string' ? 1 : 0) || text.includes('"$anchor":')) return false
|
|
2244
|
+
const re = /"\$ref":("(?:[^"\\]|\\.)*")/g
|
|
2245
|
+
let m
|
|
2246
|
+
while ((m = re.exec(text)) !== null) {
|
|
2247
|
+
let ref
|
|
2248
|
+
try { ref = JSON.parse(m[1]) } catch { return false }
|
|
2249
|
+
if (!LOCAL_ONLY_REF.test(ref)) return false
|
|
2250
|
+
}
|
|
2251
|
+
return true
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
// What the text of a schema prepared for code generation says about its size,
|
|
2255
|
+
// kept from the stringify normalizeRefsWalk makes anyway: its length when it
|
|
2256
|
+
// holds no `$ref`, -1 when it does. expandedChars counts at most 8 for each
|
|
2257
|
+
// character of a schema without references, so a short one is known not to
|
|
2258
|
+
// reach EXPANDED_SHARE without walking it again at every compile.
|
|
2259
|
+
const _refFreeLength = new WeakMap()
|
|
2192
2260
|
function normalizeRefsWalk(root, schemaMap) {
|
|
2193
2261
|
const text = JSON.stringify(root)
|
|
2194
|
-
|
|
2262
|
+
const hasRef = text.includes('"$ref"')
|
|
2263
|
+
_refFreeLength.set(root, hasRef ? -1 : text.length)
|
|
2264
|
+
if (!hasRef) return root
|
|
2195
2265
|
if (/"\$(?:dynamicRef|recursiveRef|dynamicAnchor|recursiveAnchor)"/.test(text)) return null
|
|
2266
|
+
if (refsAllPlainLocal(root, text)) return root
|
|
2196
2267
|
const { indexSchemas, resolveRef } = require('./interpreter')._refInternals
|
|
2197
2268
|
const state = indexSchemas(root, schemaMap && schemaMap.size ? schemaMap : null)
|
|
2198
2269
|
const rootDialect = typeof root.$schema === 'string' ? root.$schema.replace(/#$/, '') : null
|
|
@@ -2258,7 +2329,7 @@ function normalizeRefsWalk(root, schemaMap) {
|
|
|
2258
2329
|
out.$ref = t === root ? '#' : '#/' + defsKey + '/' + names.get(t)
|
|
2259
2330
|
continue
|
|
2260
2331
|
}
|
|
2261
|
-
out
|
|
2332
|
+
setOwn(out, k, SKIP_VALUE_KEYS.has(k) ? node[k] : walk(node[k]))
|
|
2262
2333
|
}
|
|
2263
2334
|
return out
|
|
2264
2335
|
}
|
|
@@ -2276,6 +2347,55 @@ function prepareForCodegen(schema, schemaMap) {
|
|
|
2276
2347
|
return normalizePropertyNames(normalizeRefs(schema, schemaMap))
|
|
2277
2348
|
}
|
|
2278
2349
|
|
|
2350
|
+
const VERDICT_SPLIT = 64 * 1024
|
|
2351
|
+
// The schema's size with every local reference followed, each definition
|
|
2352
|
+
// sized once, annotations left out. A rough measure of the expanded verdict
|
|
2353
|
+
// source: on SchemaStore that runs about 1.4 times it at the median, and far
|
|
2354
|
+
// less where allOf merges. Past EXPANDED_SHARE the verdict is generated with
|
|
2355
|
+
// shared definitions from the start, instead of expanded first and thrown
|
|
2356
|
+
// away: SARIF comes to 2.1 million, and generating its 2.8 MB of expanded
|
|
2357
|
+
// source only to discard it was most of a cold first validate() (70 ms of the
|
|
2358
|
+
// 87 a command line run took to report one error). The bound is set where
|
|
2359
|
+
// only schemas that split anyway reach it: SARIF 2.1 measures about 706
|
|
2360
|
+
// thousand, while utcm-monitor (370 thousand) and accelerator (262 thousand)
|
|
2361
|
+
// stay expanded and keep their speed.
|
|
2362
|
+
const EXPANDED_SHARE = 400 * 1024
|
|
2363
|
+
const SIZE_SKIPS = new Set(['$defs', 'definitions', 'description', 'title', 'examples', 'default', '$comment', 'markdownDescription', 'deprecated', 'readOnly', 'writeOnly'])
|
|
2364
|
+
function expandedChars (root, limit) {
|
|
2365
|
+
const defs = (root && (root.$defs || root.definitions)) || {}
|
|
2366
|
+
const memo = new Map()
|
|
2367
|
+
const size = (n, stack) => {
|
|
2368
|
+
if (n === null || typeof n !== 'object') return 8
|
|
2369
|
+
let t = 2
|
|
2370
|
+
if (Array.isArray(n)) { for (const x of n) { t += size(x, stack) + 1; if (t > limit) return t } return t }
|
|
2371
|
+
for (const k of Object.keys(n)) {
|
|
2372
|
+
if (SIZE_SKIPS.has(k)) continue
|
|
2373
|
+
const v = n[k]
|
|
2374
|
+
if (k === '$ref' && typeof v === 'string') {
|
|
2375
|
+
const m = /^#\/(?:\$defs|definitions)\/([^/]+)$/.exec(v)
|
|
2376
|
+
if (m && defs[m[1]] !== undefined && !stack.has(m[1])) {
|
|
2377
|
+
let d = memo.get(m[1])
|
|
2378
|
+
if (d === undefined) { stack.add(m[1]); d = size(defs[m[1]], stack); stack.delete(m[1]); memo.set(m[1], d) }
|
|
2379
|
+
t += d
|
|
2380
|
+
if (t > limit) return t
|
|
2381
|
+
continue
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
t += k.length + 3 + size(v, stack)
|
|
2385
|
+
if (t > limit) return t
|
|
2386
|
+
}
|
|
2387
|
+
return t
|
|
2388
|
+
}
|
|
2389
|
+
return size(root, new Set())
|
|
2390
|
+
}
|
|
2391
|
+
function sourceSize (lines, ctx) {
|
|
2392
|
+
let n = 0
|
|
2393
|
+
for (const l of lines) n += l.length
|
|
2394
|
+
for (const l of ctx.preamble) n += l.length
|
|
2395
|
+
if (ctx.deferredChecks) for (const l of ctx.deferredChecks) n += l.length
|
|
2396
|
+
return n
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2279
2399
|
function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
|
|
2280
2400
|
const inputSchema = schema
|
|
2281
2401
|
schema = prepareForCodegen(schema, schemaMap)
|
|
@@ -2300,7 +2420,6 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
|
|
|
2300
2420
|
// patternProperties: bail only on boolean sub-schemas or unicode property escapes
|
|
2301
2421
|
if (schema.patternProperties) {
|
|
2302
2422
|
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
2303
|
-
if (/\\[pP]\{/.test(pat)) return null
|
|
2304
2423
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
|
|
2305
2424
|
}
|
|
2306
2425
|
}
|
|
@@ -2310,13 +2429,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
|
|
|
2310
2429
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
|
|
2311
2430
|
}
|
|
2312
2431
|
}
|
|
2313
|
-
|
|
2314
|
-
if (schema.propertyNames) {
|
|
2315
|
-
const pn = schema.propertyNames
|
|
2316
|
-
const supported = ['maxLength', 'minLength', 'pattern', 'const', 'enum']
|
|
2317
|
-
const keys = Object.keys(pn).filter(k => k !== '$schema')
|
|
2318
|
-
if (keys.some(k => !supported.includes(k))) return null
|
|
2319
|
-
}
|
|
2432
|
+
if (schema.propertyNames && typeof schema.propertyNames === 'object' && !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return null
|
|
2320
2433
|
|
|
2321
2434
|
// Build anchors map for $ref/#anchor and $dynamicRef resolution
|
|
2322
2435
|
const anchors = {}
|
|
@@ -2349,20 +2462,32 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
|
|
|
2349
2462
|
if (hasUnresolvableRef(schema, rootDefs, anchors, schemaMap, new Set())) return null
|
|
2350
2463
|
if (needsBaseTracking(schema, schemaMap, new Set())) return null
|
|
2351
2464
|
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
if (
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2465
|
+
// Macro keywords expand into a schema of their own, which this generator
|
|
2466
|
+
// does not apply; such a schema stays on the interpreted engine.
|
|
2467
|
+
if (opts && opts.keywords && usesMacroKeyword(schema, schemaMap, opts.keywords)) return null
|
|
2468
|
+
// Every reference is first expanded where it is used, which is the fastest
|
|
2469
|
+
// form for the checks. Past VERDICT_SPLIT of source the function is
|
|
2470
|
+
// generated again with each definition referenced from several places
|
|
2471
|
+
// written once, as a function in the preamble: expanded, SchemaStore's sarif
|
|
2472
|
+
// (79 KB of schema) came to 2.8 MB of verdict source, and compiling it was
|
|
2473
|
+
// half of the first validate() call.
|
|
2474
|
+
let ctx, lines
|
|
2475
|
+
const refFree = _refFreeLength.get(inputSchema)
|
|
2476
|
+
const shareFirst = refFree !== undefined && refFree >= 0 && refFree * 8 <= EXPANDED_SHARE
|
|
2477
|
+
? false
|
|
2478
|
+
: expandedChars(schema, EXPANDED_SHARE) > EXPANDED_SHARE
|
|
2479
|
+
for (const share of shareFirst ? [true] : [false, true]) {
|
|
2480
|
+
ctx = { varCounter: 0, helpers: [], helperCode: [], preamble: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen], rootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors, rootSchema: inputSchema, userFormats: userFormats || null, removeNodes,
|
|
2481
|
+
keywords: (opts && opts.keywords) || null, kwEmitted: null, shareDefs: share }
|
|
2482
|
+
if (ctx.keywords) ctx.kwEmitted = new Set()
|
|
2483
|
+
lines = []
|
|
2484
|
+
try {
|
|
2485
|
+
genCode(schema, 'd', lines, ctx)
|
|
2486
|
+
} catch (e) {
|
|
2487
|
+
if (e === DECLINE) return null
|
|
2488
|
+
throw e
|
|
2489
|
+
}
|
|
2490
|
+
if (share || sourceSize(lines, ctx) <= VERDICT_SPLIT) break
|
|
2366
2491
|
}
|
|
2367
2492
|
// Every schema node that carries a custom keyword must have had its check
|
|
2368
2493
|
// written. A path that inlined a node without going through genCodeNode
|
|
@@ -2396,10 +2521,10 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
|
|
|
2396
2521
|
closureValues.push(compileSafe(JSON.parse(safeMatch[2])))
|
|
2397
2522
|
continue
|
|
2398
2523
|
}
|
|
2399
|
-
const match = code.match(/^const (_re\d+)=new RegExp\((
|
|
2524
|
+
const match = code.match(/^const (_re\d+)=new RegExp\((.+?)(?:,'(u)')?\)$/)
|
|
2400
2525
|
if (match) {
|
|
2401
2526
|
closureNames.push(match[1])
|
|
2402
|
-
closureValues.push(new RegExp(JSON.parse(match[2])))
|
|
2527
|
+
closureValues.push(new RegExp(JSON.parse(match[2]), match[3] || ''))
|
|
2403
2528
|
}
|
|
2404
2529
|
}
|
|
2405
2530
|
|
|
@@ -2913,7 +3038,9 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2913
3038
|
// $ref — guard against circular references
|
|
2914
3039
|
// In 2020-12 with unevaluated*, $ref can coexist with siblings — don't early return
|
|
2915
3040
|
// Only when THIS schema has unevaluated keywords directly (not via $ref target)
|
|
2916
|
-
|
|
3041
|
+
// Any keyword beside $ref that validates is compiled after the reference
|
|
3042
|
+
// (2020-12 applies both; draft 7 drops such siblings when it is read).
|
|
3043
|
+
const hasSiblings = !!schema.$ref && Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))
|
|
2917
3044
|
if (schema.$ref) {
|
|
2918
3045
|
if (genRefNode(schema, v, lines, ctx, knownType, hasSiblings)) return
|
|
2919
3046
|
}
|
|
@@ -3121,7 +3248,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3121
3248
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
3122
3249
|
ctx.usesSafeRe = true
|
|
3123
3250
|
} else {
|
|
3124
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`);
|
|
3251
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`);
|
|
3125
3252
|
}
|
|
3126
3253
|
}
|
|
3127
3254
|
const ri = ctx.regExpMap.get(pattern);
|
|
@@ -3398,6 +3525,13 @@ function genPropertyNamesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
3398
3525
|
const pn = schema.propertyNames
|
|
3399
3526
|
const ki = ctx.varCounter++
|
|
3400
3527
|
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
3528
|
+
if (!isSimplePN(pn)) {
|
|
3529
|
+
const sub = []
|
|
3530
|
+
genCode(pn, '_pv', sub, ctx)
|
|
3531
|
+
if (sub.length === 0) return
|
|
3532
|
+
lines.push(`${guard}{const _pnf${ki}=function(_pv){${sub.join(';')};return true};for(const _k${ki} in ${v}){if(!_pnf${ki}(_k${ki}))return false}}`)
|
|
3533
|
+
return
|
|
3534
|
+
}
|
|
3401
3535
|
lines.push(`${guard}{for(const _k${ki} in ${v}){`)
|
|
3402
3536
|
if (pn.minLength !== undefined) lines.push(`if(_k${ki}.length<${pn.minLength})return false`)
|
|
3403
3537
|
if (pn.maxLength !== undefined) lines.push(`if(_k${ki}.length>${pn.maxLength})return false`)
|
|
@@ -3643,6 +3777,31 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
3643
3777
|
}
|
|
3644
3778
|
lines.push(`if(!${fnName}(${v}))return false`)
|
|
3645
3779
|
if (!hasSiblings) return true
|
|
3780
|
+
} else if (ctx.shareDefs && !ctx.refStack.has(schema.$ref) && (ctx.sharedDefs || (ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs))).has(defName)) {
|
|
3781
|
+
// A shared definition off any cycle, as a plain function in the
|
|
3782
|
+
// preamble (see compileToJSCodegen): no guard, since it cannot re-enter
|
|
3783
|
+
// itself. Generated over its own `d` like the cyclic ones above.
|
|
3784
|
+
if (!ctx.sharedDefFns) ctx.sharedDefFns = new Map()
|
|
3785
|
+
let fnName = ctx.sharedDefFns.get(defName)
|
|
3786
|
+
if (!fnName) {
|
|
3787
|
+
fnName = '_dv' + ctx.sharedDefFns.size + '_' + defName.replace(/[^A-Za-z0-9_]/g, '_')
|
|
3788
|
+
ctx.sharedDefFns.set(defName, fnName)
|
|
3789
|
+
const bodyLines = []
|
|
3790
|
+
const outerDeferred = ctx.deferredChecks
|
|
3791
|
+
ctx.deferredChecks = null
|
|
3792
|
+
ctx.refStack.add(schema.$ref)
|
|
3793
|
+
try {
|
|
3794
|
+
genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
|
|
3795
|
+
} finally {
|
|
3796
|
+
ctx.refStack.delete(schema.$ref)
|
|
3797
|
+
}
|
|
3798
|
+
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
3799
|
+
ctx.deferredChecks = outerDeferred
|
|
3800
|
+
if (bodyLines.some((l) => /\b_validate\b/.test(l))) throw DECLINE
|
|
3801
|
+
hoist(ctx, `function ${fnName}(d){${bodyLines.join('\n ')}\n return true}`)
|
|
3802
|
+
}
|
|
3803
|
+
lines.push(`if(!${fnName}(${v}))return false`)
|
|
3804
|
+
if (!hasSiblings) return true
|
|
3646
3805
|
} else if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return true }
|
|
3647
3806
|
else {
|
|
3648
3807
|
ctx.refStack.add(schema.$ref)
|
|
@@ -3695,7 +3854,7 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
3695
3854
|
function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
3696
3855
|
let handledPropertyNames = false
|
|
3697
3856
|
const ppEntries = Object.entries(schema.patternProperties)
|
|
3698
|
-
const pn = schema.propertyNames
|
|
3857
|
+
const pn = isSimplePN(schema.propertyNames) ? schema.propertyNames : null
|
|
3699
3858
|
const pi = ctx.varCounter++
|
|
3700
3859
|
const kVar = `_ppk${pi}`
|
|
3701
3860
|
|
|
@@ -4417,7 +4576,11 @@ const FORMAT_CODEGEN = {
|
|
|
4417
4576
|
function esc(s) { return JSON.stringify(s).slice(1, -1).replace(/'/g, "\\'") }
|
|
4418
4577
|
// A schema pointer segment for a pattern: JSON Pointer escaping, then made
|
|
4419
4578
|
// safe for the single-quoted literal the error emitters use.
|
|
4420
|
-
|
|
4579
|
+
// A pointer segment as text for a single-quoted literal. Control characters
|
|
4580
|
+
// and the two line separators go in as \u escapes: written raw, a property
|
|
4581
|
+
// named "foo\nbar" broke the literal, the combined function failed to compile
|
|
4582
|
+
// and every schema with such a name lost it.
|
|
4583
|
+
function ptrSeg(s) { return s.replace(/~/g, '~0').replace(/\//g, '~1').replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/[\u0000-\u001f\u2028\u2029]/g, (c) => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0')) }
|
|
4421
4584
|
|
|
4422
4585
|
// Resolve child path at codegen time when parent is a static string literal.
|
|
4423
4586
|
// This enables frozen pre-allocation for ALL nested error objects.
|
|
@@ -4744,7 +4907,6 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
4744
4907
|
if (!sharedCodegenGate(schema, schemaMap)) return null
|
|
4745
4908
|
if (schema.patternProperties) {
|
|
4746
4909
|
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
4747
|
-
if (/\\[pP]\{/.test(pat)) return null
|
|
4748
4910
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
|
|
4749
4911
|
}
|
|
4750
4912
|
}
|
|
@@ -4753,12 +4915,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
4753
4915
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
|
|
4754
4916
|
}
|
|
4755
4917
|
}
|
|
4756
|
-
if (schema.propertyNames)
|
|
4757
|
-
const pn = schema.propertyNames
|
|
4758
|
-
const supported = ['maxLength', 'minLength', 'pattern', 'const', 'enum']
|
|
4759
|
-
const keys = Object.keys(pn).filter(k => k !== '$schema')
|
|
4760
|
-
if (keys.some(k => !supported.includes(k))) return null
|
|
4761
|
-
}
|
|
4918
|
+
if (schema.propertyNames && typeof schema.propertyNames === 'object' && !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return null
|
|
4762
4919
|
|
|
4763
4920
|
// Build anchors map for $ref/#anchor and $dynamicRef resolution
|
|
4764
4921
|
const eRootDefs = schema.$defs || schema.definitions || null
|
|
@@ -4941,6 +5098,50 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
4941
5098
|
// Emit codegen for oneOf/anyOf that runs each branch with error collection,
|
|
4942
5099
|
// aggregates results, and uses __ataCollapse to surface a single best-branch
|
|
4943
5100
|
// error. Wraps the existing per-branch evaluation; does not replace it.
|
|
5101
|
+
const BRANCH_VERDICT_MAX = 4096
|
|
5102
|
+
|
|
5103
|
+
// What undoing a trial emission needs: during one, the context's arrays only
|
|
5104
|
+
// grow and its maps and sets only gain keys (a reference pushed onto refStack
|
|
5105
|
+
// by a declined branch is such a key), so their lengths and sizes are enough,
|
|
5106
|
+
// and what was added is cut off the end, in insertion order. Copying them
|
|
5107
|
+
// instead made the trial quadratic: 297 oneOf and anyOf on apollo-router,
|
|
5108
|
+
// each copying a helper list thousands long.
|
|
5109
|
+
function snapshotCtx (ctx) {
|
|
5110
|
+
const out = new Map()
|
|
5111
|
+
for (const k of Object.keys(ctx)) {
|
|
5112
|
+
const x = ctx[k]
|
|
5113
|
+
if (Array.isArray(x)) out.set(k, { kind: 'a', ref: x, n: x.length })
|
|
5114
|
+
else if (x instanceof Map || x instanceof Set) out.set(k, { kind: 'c', ref: x, n: x.size })
|
|
5115
|
+
else out.set(k, { kind: 'v', ref: x })
|
|
5116
|
+
}
|
|
5117
|
+
return out
|
|
5118
|
+
}
|
|
5119
|
+
function restoreCtx (ctx, saved) {
|
|
5120
|
+
for (const k of Object.keys(ctx)) if (!saved.has(k)) delete ctx[k]
|
|
5121
|
+
for (const [k, e] of saved) {
|
|
5122
|
+
if (e.kind === 'a') { e.ref.length = Math.min(e.ref.length, e.n); ctx[k] = e.ref }
|
|
5123
|
+
else if (e.kind === 'c') {
|
|
5124
|
+
const extra = e.ref.size - e.n
|
|
5125
|
+
if (extra > 0) {
|
|
5126
|
+
const added = []
|
|
5127
|
+
let i = 0
|
|
5128
|
+
for (const key of e.ref.keys()) if (i++ >= e.n) added.push(key)
|
|
5129
|
+
for (const key of added) e.ref.delete(key)
|
|
5130
|
+
}
|
|
5131
|
+
ctx[k] = e.ref
|
|
5132
|
+
} else ctx[k] = e.ref
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
|
|
5136
|
+
// Whether a node holds a reference anywhere below it.
|
|
5137
|
+
function holdsRef (node, seen = new Set()) {
|
|
5138
|
+
if (node === null || typeof node !== 'object' || seen.has(node)) return false
|
|
5139
|
+
seen.add(node)
|
|
5140
|
+
if (typeof node.$ref === 'string' || typeof node.$dynamicRef === 'string') return true
|
|
5141
|
+
for (const k of Object.keys(node)) if (holdsRef(node[k], seen)) return true
|
|
5142
|
+
return false
|
|
5143
|
+
}
|
|
5144
|
+
|
|
4944
5145
|
function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4945
5146
|
ctx.usesBranchCollapse = true
|
|
4946
5147
|
const fi = ctx.varCounter++
|
|
@@ -5004,6 +5205,88 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
|
|
|
5004
5205
|
const report = ctx.inCombined && !ctx.inBranchFn
|
|
5005
5206
|
? `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed})(_e||(_e=[])).push(${collapsed})`
|
|
5006
5207
|
: `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord},_bt${fi});if(${collapsed}){_e.push(${collapsed});if(!_all)return{valid:false,errors:_e}}`
|
|
5208
|
+
// The node's runtime unevaluatedProperties already counted how many of
|
|
5209
|
+
// these branches pass, through the annotation functions, which answer the
|
|
5210
|
+
// same validity. A oneOf with exactly one, an anyOf with any, holds and
|
|
5211
|
+
// reports nothing, so its branches are not run a second time to say so.
|
|
5212
|
+
// Only in the combined function proper, where that count is in scope.
|
|
5213
|
+
const counted = ctx.inCombined && !ctx.inBranchFn && ctx.branchCounts ? ctx.branchCounts.get(branches) : undefined
|
|
5214
|
+
const same = counted !== undefined ? `${v}!==null&&typeof ${v}==='object'&&${counted.of}===${v}` : ''
|
|
5215
|
+
let guard = counted !== undefined
|
|
5216
|
+
? (keyword === 'anyOf' ? `if(!(${same}&&${counted.count}>=1))` : `if(!(${same}&&${counted.count}===1))`)
|
|
5217
|
+
: ''
|
|
5218
|
+
// More than one oneOf branch passed: the error names them and carries no
|
|
5219
|
+
// branch errors, so it is built from the bits the count left.
|
|
5220
|
+
if (guard !== '' && keyword === 'oneOf' && counted.mask && ctx.inCombined && !ctx.inBranchFn) {
|
|
5221
|
+
const ord0 = ctx.noOrdinal ? 'null' : (() => { const o = ordinalFor(ctx.rootSchema, unescapeSp(branchSp)); return o === null ? 'null' : o })()
|
|
5222
|
+
guard = `if(${same}&&${counted.count}>1){const _pi${fi}=[];for(let _k=0;_k<${branches.length};_k++)if((${counted.mask}>>_k)&1)_pi${fi}.push(_k);(_e||(_e=[])).push(__ataMulti(_pi${fi},${branches.length},${pp},'${branchSp}',${ord0}))}else if(!(${same}&&${counted.count}===1))`
|
|
5223
|
+
}
|
|
5224
|
+
// Without such a count, each branch also gets a verdict function, and the
|
|
5225
|
+
// passing branches are counted with those: a oneOf with exactly one and an
|
|
5226
|
+
// anyOf with any hold, and a oneOf with more than one is reported from the
|
|
5227
|
+
// bits, so the error-collecting branches run only when no branch passes
|
|
5228
|
+
// and the collapse needs their errors to pick the closest. Running them to
|
|
5229
|
+
// learn the count built a result object and an error list per branch,
|
|
5230
|
+
// which made a oneOf matched twice cost 69 ns where counting costs about
|
|
5231
|
+
// 12. A branch the verdict generator declines keeps the old path.
|
|
5232
|
+
let vfArr = null
|
|
5233
|
+
// Branches that hold a reference are left to the error branches: their
|
|
5234
|
+
// verdicts would expand every definition again, as a second copy, where the
|
|
5235
|
+
// error branches call the shared one.
|
|
5236
|
+
if (guard === '' && !branches.some((b) => holdsRef(b))) {
|
|
5237
|
+
// A trial: the generators are not exception-safe on the context (a
|
|
5238
|
+
// reference left on refStack by a declined branch made the next error
|
|
5239
|
+
// function write that branch as nothing, which read as valid), so the
|
|
5240
|
+
// context is put back as it was when the verdicts decline.
|
|
5241
|
+
const saved = snapshotCtx(ctx)
|
|
5242
|
+
try {
|
|
5243
|
+
const vfs = branches.map((sub) => {
|
|
5244
|
+
if (sub === true) return 'function(){return true}'
|
|
5245
|
+
if (sub === false) return 'function(){return false}'
|
|
5246
|
+
const bl = []
|
|
5247
|
+
nestedGenCode(sub, '_bv', bl, ctx)
|
|
5248
|
+
return bl.length === 0 ? 'function(){return true}' : `function(_bv){${bl.join(';')};return true}`
|
|
5249
|
+
})
|
|
5250
|
+
// Only while the verdicts are small: they are a second copy of the
|
|
5251
|
+
// branches, with every reference expanded, and on large branches that
|
|
5252
|
+
// copy cost more to compile than counting saves (apollo-router's error
|
|
5253
|
+
// function grew from 1.5 to 2.5 MB of source).
|
|
5254
|
+
if (!vfs.some((b) => b.includes(SP_PLACEHOLDER)) && vfs.reduce((n, b) => n + b.length, 0) <= BRANCH_VERDICT_MAX) {
|
|
5255
|
+
// One constant per branch, called directly: through an array the one
|
|
5256
|
+
// call site sees every branch and none of them is inlined.
|
|
5257
|
+
vfArr = `_bvf${fi}`
|
|
5258
|
+
ctx.helperCode.push(vfs.map((b, i) => `const ${vfArr}_${i}=${b}`).join(';'))
|
|
5259
|
+
} else {
|
|
5260
|
+
restoreCtx(ctx, saved)
|
|
5261
|
+
}
|
|
5262
|
+
} catch (e) {
|
|
5263
|
+
if (e !== DECLINE) throw e
|
|
5264
|
+
restoreCtx(ctx, saved)
|
|
5265
|
+
}
|
|
5266
|
+
}
|
|
5267
|
+
if (vfArr !== null) {
|
|
5268
|
+
const L = branches.length
|
|
5269
|
+
const top = ctx.inCombined && !ctx.inBranchFn
|
|
5270
|
+
// An accepted document is still answered by the verdict first (see the
|
|
5271
|
+
// result shape in compileToJSCombined): that is the fastest path to it,
|
|
5272
|
+
// and dropping it measured 20 ns slower per accepted oneOf.
|
|
5273
|
+
if (top) ctx.unguardedCollapse = true
|
|
5274
|
+
const tests = branches.map((_, i) => `${vfArr}_${i}(${v})`)
|
|
5275
|
+
if (keyword === 'anyOf') {
|
|
5276
|
+
lines.push(`{const _pp${fi}=${pp};if(!(${tests.join('||')})){if(_all===0){_e.push(null);return{valid:false,errors:_e}}const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}}}`)
|
|
5277
|
+
} else {
|
|
5278
|
+
const mk = L <= 30
|
|
5279
|
+
const collect = mk
|
|
5280
|
+
? `const _pi${fi}=[];for(let _k=0;_k<${L};_k++)if((_m${fi}>>_k)&1)_pi${fi}.push(_k);`
|
|
5281
|
+
: ''
|
|
5282
|
+
const multi = `__ataMulti(_pi${fi},${L},_pp${fi},'${branchSp}',${ord})`
|
|
5283
|
+
const pushMulti = top ? `(_e||(_e=[])).push(${multi})` : `_e.push(${multi});if(!_all)return{valid:false,errors:_e}`
|
|
5284
|
+
const count = tests.map((t, i) => `if(${t}){_n${fi}++;${mk ? `_m${fi}|=${1 << i}` : `_pi${fi}.push(${i})`}}`).join('')
|
|
5285
|
+
lines.push(`{const _pp${fi}=${pp};let _n${fi}=0${mk ? `,_m${fi}=0` : `,_pi${fi}=[]`};${count}if(_n${fi}!==1){if(_all===0){_e.push(null);return{valid:false,errors:_e}}if(_n${fi}>1){${collect}${pushMulti}}else{const ${resArr}=[];for(let _bi=0;_bi<${L};_bi++)${resArr}.push(${call});${report}}}}`)
|
|
5286
|
+
}
|
|
5287
|
+
return
|
|
5288
|
+
}
|
|
5289
|
+
if (guard === '' && ctx.inCombined && !ctx.inBranchFn) ctx.unguardedCollapse = true
|
|
5007
5290
|
if (keyword === 'anyOf') {
|
|
5008
5291
|
// anyOf reports nothing once a branch passes, so the branches are first
|
|
5009
5292
|
// only asked whether they pass. They are called with `_all` set to 0, a
|
|
@@ -5017,12 +5300,12 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
|
|
|
5017
5300
|
// no one read. Only when no branch passes do they run again with the
|
|
5018
5301
|
// caller's `_all`, and the collapse sees them all, as before.
|
|
5019
5302
|
const probe = `${fnArr}[_bi](${v},_pp${fi},0${usesSp ? ',_sp' : ''})`
|
|
5020
|
-
lines.push(
|
|
5303
|
+
lines.push(`${guard}{const _pp${fi}=${pp};let _bp${fi}=false;for(let _bi=0;_bi<${fnArr}.length;_bi++){if(${probe}.valid){_bp${fi}=true;break}}if(!_bp${fi}){if(_all===0){_e.push(null);return{valid:false,errors:_e}}const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++){const _br=${call};${resArr}.push(_br.valid?{valid:false,errors:_br.errors}:_br)}${report}}}`)
|
|
5021
5304
|
} else {
|
|
5022
5305
|
// Asked only whether it passes (`_all` 0, see anyOf above), oneOf counts
|
|
5023
5306
|
// the passing branches and answers; the branch errors of a probe are not
|
|
5024
5307
|
// errors anyone reads and are not collapsed.
|
|
5025
|
-
lines.push(
|
|
5308
|
+
lines.push(`${guard}{const _pp${fi}=${pp};const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++)${resArr}.push(${call});if(_all===0){let _n=0;for(const _r of ${resArr})if(_r.valid)_n++;if(_n!==1){_e.push(null);return{valid:false,errors:_e}}}else{${report}}}`)
|
|
5026
5309
|
}
|
|
5027
5310
|
}
|
|
5028
5311
|
|
|
@@ -5259,7 +5542,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5259
5542
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
5260
5543
|
ctx.usesSafeRe = true
|
|
5261
5544
|
} else {
|
|
5262
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
5545
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
|
|
5263
5546
|
}
|
|
5264
5547
|
}
|
|
5265
5548
|
const ri = ctx.regExpMap.get(pattern);
|
|
@@ -5334,7 +5617,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5334
5617
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
5335
5618
|
ctx.usesSafeRe = true
|
|
5336
5619
|
} else {
|
|
5337
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
5620
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
|
|
5338
5621
|
}
|
|
5339
5622
|
}
|
|
5340
5623
|
patChecks.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ci}[_i])`)
|
|
@@ -5365,6 +5648,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5365
5648
|
// whose evaluated set depends on its own branches would report the wrong
|
|
5366
5649
|
// errors, and the collapse would pick the wrong closest branch; declined.
|
|
5367
5650
|
if (ctx.inCombined && schema.unevaluatedProperties !== undefined && schema.unevaluatedProperties !== true && !unevalLocalOk(schema, 'unevaluatedProperties')) throw DECLINE
|
|
5651
|
+
if (ctx.inCombined && schema.unevaluatedItems !== undefined && schema.unevaluatedItems !== true && !unevalLocalOk(schema, 'unevaluatedItems')) throw DECLINE
|
|
5368
5652
|
if (schema.unevaluatedProperties === false && schema.additionalProperties === undefined) genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isObj)
|
|
5369
5653
|
|
|
5370
5654
|
// unevaluatedItems: false under the same rule. The interpreted engine
|
|
@@ -5497,7 +5781,7 @@ function genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isO
|
|
|
5497
5781
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
5498
5782
|
ctx.usesSafeRe = true
|
|
5499
5783
|
} else {
|
|
5500
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
5784
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
|
|
5501
5785
|
}
|
|
5502
5786
|
}
|
|
5503
5787
|
patChecksU.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ui}[_i])`)
|
|
@@ -5529,7 +5813,7 @@ function genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5529
5813
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
5530
5814
|
ctx.usesSafeRe = true
|
|
5531
5815
|
} else {
|
|
5532
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`);
|
|
5816
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`);
|
|
5533
5817
|
}
|
|
5534
5818
|
}
|
|
5535
5819
|
const ri = ctx.regExpMap.get(pattern);
|
|
@@ -5559,7 +5843,7 @@ function genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5559
5843
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
5560
5844
|
ctx.usesSafeRe = true
|
|
5561
5845
|
} else {
|
|
5562
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`)
|
|
5846
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`)
|
|
5563
5847
|
}
|
|
5564
5848
|
}
|
|
5565
5849
|
patTests.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ki})`)
|
|
@@ -5596,6 +5880,13 @@ function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
|
5596
5880
|
const pn = schema.propertyNames
|
|
5597
5881
|
const ki = ctx.varCounter++
|
|
5598
5882
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
|
|
5883
|
+
// The subschema checks the key as a value, and its errors are reported at
|
|
5884
|
+
// the object, as the interpreter reports them.
|
|
5885
|
+
if (!isSimplePN(pn)) {
|
|
5886
|
+
genCodeE(pn, `_k${ki}`, pathExpr, lines, ctx, schemaPrefix + '/propertyNames')
|
|
5887
|
+
lines.push(`}}`)
|
|
5888
|
+
return
|
|
5889
|
+
}
|
|
5599
5890
|
if (pn.minLength !== undefined) {
|
|
5600
5891
|
lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
5601
5892
|
}
|
|
@@ -5611,7 +5902,7 @@ function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
|
5611
5902
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
5612
5903
|
ctx.usesSafeRe = true
|
|
5613
5904
|
} else {
|
|
5614
|
-
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern})`);
|
|
5905
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${pattern}${reFlagArg(pattern)})`);
|
|
5615
5906
|
}
|
|
5616
5907
|
}
|
|
5617
5908
|
const ri = ctx.regExpMap.get(pattern);
|
|
@@ -5733,8 +6024,11 @@ function errPushC(ctx, keyword, sp, pathExpr, paramsCode, msgCode, valueExpr, le
|
|
|
5733
6024
|
const { errorSite } = require('./enrich-site')
|
|
5734
6025
|
const siteVar = `_S${ctx.varCounter++}`
|
|
5735
6026
|
ctx.closureVars.push(siteVar)
|
|
5736
|
-
|
|
5737
|
-
|
|
6027
|
+
// Inside a shared definition (emitSharedDefC) the site holds the path past
|
|
6028
|
+
// the definition and the reference's own comes in as `_sp`.
|
|
6029
|
+
const rel = sp.startsWith(SP_PLACEHOLDER)
|
|
6030
|
+
ctx.closureVals.push(errorSite(keyword, unescapeSp(rel ? sp.slice(SP_PLACEHOLDER.length) : sp), paramsVal, fmt))
|
|
6031
|
+
return `(_e||(_e=[])).push(_mk(${siteVar},${pathExpr || '""'},${paramsShared || paramsCode},${msgCode},${valueExpr},d${rel ? ',_sp' : ''}))`
|
|
5738
6032
|
}
|
|
5739
6033
|
|
|
5740
6034
|
function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts) {
|
|
@@ -5743,15 +6037,12 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5743
6037
|
// Same provably-local rule as the error generator above.
|
|
5744
6038
|
if (typeof schema === 'object' && schema !== null) {
|
|
5745
6039
|
const s = JSON.stringify(schema)
|
|
5746
|
-
// unevaluatedProperties that
|
|
5747
|
-
//
|
|
5748
|
-
|
|
5749
|
-
//
|
|
5750
|
-
|
|
5751
|
-
//
|
|
5752
|
-
// errors generators; this generator has no preamble for them, so it
|
|
5753
|
-
// declines and the router uses the errors generator.
|
|
5754
|
-
if (cyclicDefNames(schema.$defs || schema.definitions).size) return null
|
|
6040
|
+
// unevaluatedProperties and unevaluatedItems that are not provably local
|
|
6041
|
+
// are worked out at run time (genUnevaluatedDynamicC,
|
|
6042
|
+
// genUnevaluatedItemsDynamicC).
|
|
6043
|
+
// A schema that references itself, through `$ref: "#"` or a definition on
|
|
6044
|
+
// a cycle, has those references written as calls under the cycle guard
|
|
6045
|
+
// (emitDefFnC).
|
|
5755
6046
|
// Bail on oneOf/anyOf — branch-collapse aggregation lives in genCodeE only.
|
|
5756
6047
|
// Letting genCodeC emit the legacy "must match exactly one" placeholder
|
|
5757
6048
|
// would short-circuit the ATA4001/ATA4002/ATA4003 contract. The boolean
|
|
@@ -5775,7 +6066,6 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5775
6066
|
if (!sharedCodegenGate(schema, schemaMap)) return null
|
|
5776
6067
|
if (schema.patternProperties) {
|
|
5777
6068
|
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
5778
|
-
if (/\\[pP]\{/.test(pat)) return null
|
|
5779
6069
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
|
|
5780
6070
|
}
|
|
5781
6071
|
}
|
|
@@ -5784,12 +6074,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5784
6074
|
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
|
|
5785
6075
|
}
|
|
5786
6076
|
}
|
|
5787
|
-
if (schema.propertyNames)
|
|
5788
|
-
const pn = schema.propertyNames
|
|
5789
|
-
const supported = ['maxLength', 'minLength', 'pattern', 'const', 'enum']
|
|
5790
|
-
const keys = Object.keys(pn).filter(k => k !== '$schema')
|
|
5791
|
-
if (keys.some(k => !supported.includes(k))) return null
|
|
5792
|
-
}
|
|
6077
|
+
if (schema.propertyNames && typeof schema.propertyNames === 'object' && !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return null
|
|
5793
6078
|
|
|
5794
6079
|
// Build anchors map for $ref/#anchor and $dynamicRef resolution
|
|
5795
6080
|
const cRootDefs = schema.$defs || schema.definitions || null
|
|
@@ -5825,7 +6110,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5825
6110
|
if (needsBaseTracking(schema, schemaMap, new Set())) return null
|
|
5826
6111
|
|
|
5827
6112
|
// runtimeShape: errors without the ordinal, see compileToJSCodegenWithErrors.
|
|
5828
|
-
const ctx = { noOrdinal: !!(opts && opts.runtimeShape), rich: !!(opts && opts.rich), inCombined: true, varCounter: 0, helperCode: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen],
|
|
6113
|
+
const ctx = { noOrdinal: !!(opts && opts.runtimeShape), rich: !!(opts && opts.rich), inCombined: true, hoisted: [], rootC: schema, varCounter: 0, helperCode: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen],
|
|
5829
6114
|
rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: inputSchema, userFormats: userFormats || null }
|
|
5830
6115
|
const lines = []
|
|
5831
6116
|
try {
|
|
@@ -5851,10 +6136,15 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5851
6136
|
const closureParams = closureNames.join(',')
|
|
5852
6137
|
// A definition the error generator wrote as a named function inside a
|
|
5853
6138
|
// branch (one on a cycle, or referenced from several places) reads the
|
|
5854
|
-
// cycle-guard state
|
|
5855
|
-
// the
|
|
5856
|
-
//
|
|
5857
|
-
|
|
6139
|
+
// cycle-guard state the error function declares. Here that state is
|
|
6140
|
+
// declared too, and the checks run as the error function runs them: in a
|
|
6141
|
+
// body of their own, again in the guarded mode when data turns out to
|
|
6142
|
+
// point back at itself. This used to decline, which left every schema
|
|
6143
|
+
// whose oneOf or anyOf branches reach a shared definition without one
|
|
6144
|
+
// pass.
|
|
6145
|
+
const guardList = [...(ctx.defFns ? ctx.defFns.values() : []), ...(ctx.cycFnsC || [])]
|
|
6146
|
+
const guardDefs = guardList.length ? guardList : null
|
|
6147
|
+
if (guardDefs) ctx.helperCode.unshift(emitGuardState())
|
|
5858
6148
|
// Collapsed oneOf/anyOf: the helper the error generator embeds, `_all` for
|
|
5859
6149
|
// the branch functions (this function collects everything), and the
|
|
5860
6150
|
// runtime shape the error generator gives its literals, without code and
|
|
@@ -5865,12 +6155,23 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5865
6155
|
for (let i = 0; i < ctx.helperCode.length; i++) ctx.helperCode[i] = shape(ctx.helperCode[i])
|
|
5866
6156
|
for (let i = 0; i < lines.length; i++) lines[i] = shape(lines[i])
|
|
5867
6157
|
}
|
|
5868
|
-
const helpers = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
|
|
5869
6158
|
// Lazy error array: no allocation for valid data (the common case)
|
|
5870
6159
|
// resultShape: validate() itself for a validator whose errors are being read
|
|
5871
6160
|
// (see validator-core): the result objects built here, so one pass decides,
|
|
5872
6161
|
// collects and answers, with nothing between the caller and the checks.
|
|
5873
6162
|
const rs = opts && opts.resultShape
|
|
6163
|
+
// Counts shared between a node's unevaluatedProperties and its anyOf and
|
|
6164
|
+
// oneOf (genUnevaluatedDynamicC), visible to every line of the function.
|
|
6165
|
+
let hoistDecl = ctx.hoisted.length ? `let ${ctx.hoisted.map((n) => n + '=null').join(',')};` : ''
|
|
6166
|
+
let checks = lines.join('\n ')
|
|
6167
|
+
let allDecl = ctx.usesBranchCollapse ? 'const _all=true;' : ''
|
|
6168
|
+
if (guardDefs) {
|
|
6169
|
+
const clears = ['_stk.clear()'].concat(guardDefs.map((n) => `${n}_s.clear()`)).join(';')
|
|
6170
|
+
ctx.helperCode.push(`function _bodyC(d){${hoistDecl}${allDecl}let _e;\n ${checks}\n return _e}`)
|
|
6171
|
+
hoistDecl = ''
|
|
6172
|
+
allDecl = ''
|
|
6173
|
+
checks = `_sd=0;try{_e=_bodyC(d)}catch(_err){if(_err!==_CYC)throw _err;_sg=true;${clears};try{_e=_bodyC(d)}finally{_sg=false}}`
|
|
6174
|
+
}
|
|
5874
6175
|
// In the result shape this function is validate() itself, so a document
|
|
5875
6176
|
// that makes it throw is answered by the caller's previous path instead
|
|
5876
6177
|
// (`_FB`). A thrown error reaching the caller is the one thing this form
|
|
@@ -5879,12 +6180,16 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
|
|
|
5879
6180
|
// With collapsed branches, collecting runs every branch of every oneOf
|
|
5880
6181
|
// and anyOf in full, which an accepted document does not need: the
|
|
5881
6182
|
// verdict answers it first.
|
|
5882
|
-
|
|
6183
|
+
// Not when every collapse is guarded by a count (emitBranchCollapse): an
|
|
6184
|
+
// accepted document then runs no branch twice, and a rejected one would
|
|
6185
|
+
// pay for the verdict for nothing.
|
|
6186
|
+
? `try{${hoistDecl}${ctx.usesBranchCollapse && ctx.unguardedCollapse ? 'if(_VF!==null&&_VF(d))return{valid:true,data:d,errors:_EE};' : ''}${allDecl}let _e;\n ` + checks +
|
|
5883
6187
|
// A collapsed branch error is presented even alone (its ordinal and
|
|
5884
6188
|
// raw branch errors stay inside); otherwise only a list of two or more
|
|
5885
6189
|
// needs ordering.
|
|
5886
6190
|
`\n return _e?new _ER(${ctx.usesBranchCollapse ? '_SRT(_e)' : '_e.length>1?_SRT(_e):_e'}):{valid:true,data:d,errors:_EE}}catch(_x){return _FB(d)}`
|
|
5887
|
-
:
|
|
6191
|
+
: hoistDecl + allDecl + `let _e;\n ` + checks + `\n return _e?{valid:false,errors:_e}:R`
|
|
6192
|
+
const helpers = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
|
|
5888
6193
|
|
|
5889
6194
|
try {
|
|
5890
6195
|
if (typeof process !== 'undefined' && process.env && process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + helpers + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
|
|
@@ -5921,9 +6226,12 @@ function typeMismatchRest(schema, types) {
|
|
|
5921
6226
|
for (const k of Object.keys(schema)) {
|
|
5922
6227
|
if (drop.has(k)) continue
|
|
5923
6228
|
if (rest === null) rest = {}
|
|
5924
|
-
rest
|
|
6229
|
+
setOwn(rest, k, schema[k])
|
|
5925
6230
|
}
|
|
5926
6231
|
if (rest === null) return null
|
|
6232
|
+
// A node whose reference was already written (genCodeCNode) keeps that
|
|
6233
|
+
// mark, or the reference is written again here and its errors twice.
|
|
6234
|
+
if (schema[REF_DONE]) Object.defineProperty(rest, REF_DONE, { value: true })
|
|
5927
6235
|
for (const k of Object.keys(rest)) if (!NOT_VALIDATING.has(k) && !k.startsWith('x-')) return rest
|
|
5928
6236
|
return null
|
|
5929
6237
|
}
|
|
@@ -5931,6 +6239,68 @@ function typeMismatchRest(schema, types) {
|
|
|
5931
6239
|
function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5932
6240
|
return withPlain(schema, v, lines, ctx, () => genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix))
|
|
5933
6241
|
}
|
|
6242
|
+
// A definition referenced from several places, written once as a function
|
|
6243
|
+
// and called at each reference, instead of its whole code at every one: the
|
|
6244
|
+
// combined function of SchemaStore's cloud-run (48 KB of schema) came to
|
|
6245
|
+
// 1.1 MB of source and sarif's (79 KB) to 2.8 MB, and compiling those was most
|
|
6246
|
+
// of the 50 to 300 ms a first rejection with its errors read took. The body is
|
|
6247
|
+
// generated against a placeholder schema path that becomes the `_sp` argument,
|
|
6248
|
+
// as the error generator does (genCodeENode), so an error names the path it
|
|
6249
|
+
// was reached by. It collects into its own list, returned to the caller, and
|
|
6250
|
+
// declares its own branch counts (genUnevaluatedDynamicC). The document root
|
|
6251
|
+
// comes in as `d` for the rich errors that read it. Not for a definition on a
|
|
6252
|
+
// reference cycle, which keeps its existing path. False when not shared.
|
|
6253
|
+
function emitSharedDefC(defName, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
6254
|
+
if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
|
|
6255
|
+
if (!ctx.sharedDefs) ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs)
|
|
6256
|
+
const cyclic = ctx.cyclicDefs.has(defName)
|
|
6257
|
+
if (!cyclic && !ctx.sharedDefs.has(defName)) return false
|
|
6258
|
+
emitDefFnC(defName, ctx.rootDefs[defName], cyclic, v, pathExpr, lines, ctx, schemaPrefix)
|
|
6259
|
+
return true
|
|
6260
|
+
}
|
|
6261
|
+
|
|
6262
|
+
// One schema as a function of its own in the combined function (see
|
|
6263
|
+
// emitSharedDefC). The name is registered before the body is written, so a
|
|
6264
|
+
// reference back to it inside becomes a call. One that can re-enter itself,
|
|
6265
|
+
// a definition on a cycle or the root through `$ref: "#"`, goes through the
|
|
6266
|
+
// cycle guard the error generator's definitions use: depth counted, and in
|
|
6267
|
+
// the guarded pass a value already being checked is not checked again.
|
|
6268
|
+
function emitDefFnC(key, target, cyclic, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
6269
|
+
if (!ctx.defFnsC) ctx.defFnsC = new Map()
|
|
6270
|
+
let fnName = ctx.defFnsC.get(key)
|
|
6271
|
+
if (!fnName) {
|
|
6272
|
+
fnName = '_defC' + ctx.defFnsC.size + '_' + (key === '#' ? 'root' : key.replace(/[^A-Za-z0-9_]/g, '_'))
|
|
6273
|
+
ctx.defFnsC.set(key, fnName)
|
|
6274
|
+
const outerHoisted = ctx.hoisted
|
|
6275
|
+
ctx.hoisted = []
|
|
6276
|
+
const bodyLines = []
|
|
6277
|
+
let hoisted
|
|
6278
|
+
try {
|
|
6279
|
+
genCodeC(target, '_dv', '_p', bodyLines, ctx, SP_PLACEHOLDER)
|
|
6280
|
+
} finally {
|
|
6281
|
+
hoisted = ctx.hoisted
|
|
6282
|
+
ctx.hoisted = outerHoisted
|
|
6283
|
+
}
|
|
6284
|
+
let body = bodyLines.join('\n ')
|
|
6285
|
+
body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
|
|
6286
|
+
if (body.includes(SP_PLACEHOLDER)) throw DECLINE
|
|
6287
|
+
const decl = hoisted.length ? `let ${hoisted.map((n) => n + '=null').join(',')};` : ''
|
|
6288
|
+
const fn = `function ${cyclic ? fnName + '_b' : fnName}(_dv,_p,_sp,d){const _all=true;${decl}let _e;\n ${body}\n return _e}`
|
|
6289
|
+
if (cyclic) {
|
|
6290
|
+
if (!ctx.cycFnsC) ctx.cycFnsC = []
|
|
6291
|
+
ctx.cycFnsC.push(fnName)
|
|
6292
|
+
ctx.helperCode.push(
|
|
6293
|
+
`const ${fnName}_s=new Set()\n function ${fnName}(_dv,_p,_sp,d){\n ` +
|
|
6294
|
+
`if(_sg){if(typeof _dv!=='object'||_dv===null)return ${fnName}_b(_dv,_p,_sp,d);if(${fnName}_s.has(_dv))return;${fnName}_s.add(_dv);try{return ${fnName}_b(_dv,_p,_sp,d)}finally{${fnName}_s.delete(_dv)}}\n ` +
|
|
6295
|
+
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(_dv,_p,_sp,d)\n _sd--\n return _r\n }\n ` + fn)
|
|
6296
|
+
} else {
|
|
6297
|
+
ctx.helperCode.push(fn)
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
lines.push(`{const _r=${fnName}(${v},${pathExpr || '""'},'${schemaPrefix}',d);if(_r!==undefined){if(_e===undefined)_e=_r;else for(let _i=0;_i<_r.length;_i++)_e.push(_r[_i])}}`)
|
|
6301
|
+
}
|
|
6302
|
+
|
|
6303
|
+
const REF_DONE = Symbol('ata.refDone')
|
|
5934
6304
|
function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
5935
6305
|
if (!schemaPrefix) schemaPrefix = '#'
|
|
5936
6306
|
// Per-node, for the reason spelled out in genCode.
|
|
@@ -5945,18 +6315,34 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5945
6315
|
if (typeof schema !== 'object' || schema === null) return
|
|
5946
6316
|
|
|
5947
6317
|
// $ref — resolve local, anchor, and cross-schema refs
|
|
5948
|
-
if (schema.$ref) {
|
|
5949
|
-
//
|
|
5950
|
-
//
|
|
5951
|
-
//
|
|
5952
|
-
//
|
|
5953
|
-
//
|
|
5954
|
-
|
|
5955
|
-
//
|
|
5956
|
-
|
|
6318
|
+
if (schema.$ref && !schema[REF_DONE]) {
|
|
6319
|
+
// A keyword beside $ref that validates applies too (2020-12; draft 7
|
|
6320
|
+
// drops such siblings when the schema is read). The reference is written
|
|
6321
|
+
// on its own, then the node again with the reference marked done, on a
|
|
6322
|
+
// fresh copy so the mark cannot reach the same schema object met through
|
|
6323
|
+
// another path; the copy keeps `$ref` for unevaluatedProperties and
|
|
6324
|
+
// unevaluatedItems, which read its annotations. Dropping the siblings
|
|
6325
|
+
// once accepted what they reject, which is why this declined. Not in a
|
|
6326
|
+
// branch function, which the error generator writes.
|
|
6327
|
+
if (Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))) {
|
|
6328
|
+
if (!ctx.inCombined || ctx.inBranchFn) throw DECLINE
|
|
6329
|
+
genCodeCNode({ $ref: schema.$ref }, v, pathExpr, lines, ctx, schemaPrefix)
|
|
6330
|
+
const rest = { ...schema }
|
|
6331
|
+
Object.defineProperty(rest, REF_DONE, { value: true })
|
|
6332
|
+
genCodeCNode(rest, v, pathExpr, lines, ctx, schemaPrefix)
|
|
6333
|
+
return
|
|
6334
|
+
}
|
|
6335
|
+
// A reference back into what is being expanded is a call to that
|
|
6336
|
+
// schema's function (emitDefFnC), never nothing: an expansion that stops
|
|
6337
|
+
// and emits no lines reads as valid.
|
|
6338
|
+
if (schema.$ref === '#') {
|
|
6339
|
+
if (ctx.inCombined && !ctx.inBranchFn && ctx.rootC) { emitDefFnC('#', ctx.rootC, true, v, pathExpr, lines, ctx, schemaPrefix); return }
|
|
6340
|
+
throw DECLINE
|
|
6341
|
+
}
|
|
5957
6342
|
const m = schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
|
|
5958
6343
|
if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
|
|
5959
|
-
if (ctx.
|
|
6344
|
+
if (ctx.inCombined && !ctx.inBranchFn && emitSharedDefC(m[1], v, pathExpr, lines, ctx, schemaPrefix)) return
|
|
6345
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5960
6346
|
ctx.refStack.add(schema.$ref)
|
|
5961
6347
|
genCodeC(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx, schemaPrefix)
|
|
5962
6348
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -5967,7 +6353,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5967
6353
|
const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
|
|
5968
6354
|
const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
|
|
5969
6355
|
if (anchorTarget) {
|
|
5970
|
-
if (ctx.refStack.has(schema.$ref))
|
|
6356
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5971
6357
|
ctx.refStack.add(schema.$ref)
|
|
5972
6358
|
genCodeC(anchorTarget, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5973
6359
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -5975,7 +6361,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5975
6361
|
}
|
|
5976
6362
|
}
|
|
5977
6363
|
if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
|
|
5978
|
-
if (ctx.refStack.has(schema.$ref))
|
|
6364
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5979
6365
|
ctx.refStack.add(schema.$ref)
|
|
5980
6366
|
genCodeC(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
|
|
5981
6367
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -5985,7 +6371,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5985
6371
|
if (ctx.schemaMap && schema.$ref.includes('#') && !schema.$ref.startsWith('#')) {
|
|
5986
6372
|
const r = resolveCrossSchemaRef(schema.$ref, ctx.schemaMap)
|
|
5987
6373
|
if (r) {
|
|
5988
|
-
if (ctx.refStack.has(schema.$ref))
|
|
6374
|
+
if (ctx.refStack.has(schema.$ref)) throw DECLINE
|
|
5989
6375
|
ctx.refStack.add(schema.$ref)
|
|
5990
6376
|
genCodeC(r.schema, v, pathExpr, lines, ctx, schemaPrefix)
|
|
5991
6377
|
ctx.refStack.delete(schema.$ref)
|
|
@@ -6201,7 +6587,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6201
6587
|
const ri = ctx.varCounter++
|
|
6202
6588
|
const reVar = `_re${ri}`
|
|
6203
6589
|
ctx.closureVars.push(reVar)
|
|
6204
|
-
ctx.closureVals.push(useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern))
|
|
6590
|
+
ctx.closureVals.push(useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern, reFlags(schema.pattern)))
|
|
6205
6591
|
const c = isStr ? `!${reVar}.test(${v})` : `typeof ${v}==='string'&&!${reVar}.test(${v})`
|
|
6206
6592
|
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
|
|
6207
6593
|
}
|
|
@@ -6297,7 +6683,10 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6297
6683
|
// unevaluatedItems: false under the same rule: one error for the array,
|
|
6298
6684
|
// the first uncovered position as the limit. Adjacent schema-form `items`
|
|
6299
6685
|
// evaluates everything; the draft-07 array form acts as the prefix.
|
|
6300
|
-
if (schema.unevaluatedItems
|
|
6686
|
+
if (schema.unevaluatedItems !== undefined && schema.unevaluatedItems !== true) {
|
|
6687
|
+
if (unevalLocalOk(schema, 'unevaluatedItems')) genUnevaluatedItemsFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
6688
|
+
else genUnevaluatedItemsDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
6689
|
+
}
|
|
6301
6690
|
|
|
6302
6691
|
// dependentRequired
|
|
6303
6692
|
if (schema.dependentRequired) {
|
|
@@ -6447,17 +6836,17 @@ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj) {
|
|
|
6447
6836
|
if (schema.additionalProperties !== undefined) return
|
|
6448
6837
|
const ui = ctx.varCounter++
|
|
6449
6838
|
const S = `_ev${ui}`
|
|
6450
|
-
const body = [`const ${S}=
|
|
6839
|
+
const body = [`const ${S}=[]`]
|
|
6451
6840
|
addLocalAnnotations(schema, v, S, body, ctx)
|
|
6452
|
-
|
|
6841
|
+
addTopApplicatorAnnotations(schema, v, S, body, ctx)
|
|
6453
6842
|
const kVar = `_uk${ui}`
|
|
6454
6843
|
const up = schema.unevaluatedProperties
|
|
6455
6844
|
if (up === false) {
|
|
6456
|
-
body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.
|
|
6845
|
+
body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar}))return false`)
|
|
6457
6846
|
} else if (typeof up === 'object' && up !== null) {
|
|
6458
6847
|
const sub = []
|
|
6459
6848
|
genCode(up, `${v}[${kVar}]`, sub, ctx)
|
|
6460
|
-
if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.
|
|
6849
|
+
if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${sub.join('\n ')}\n }`)
|
|
6461
6850
|
} else {
|
|
6462
6851
|
throw DECLINE
|
|
6463
6852
|
}
|
|
@@ -6470,36 +6859,56 @@ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj) {
|
|
|
6470
6859
|
// count whether or not their values pass; an allOf, anyOf or dependentSchemas
|
|
6471
6860
|
// branch, an if, then or else, or a $ref target counts only when it passes,
|
|
6472
6861
|
// a oneOf only when exactly one branch passes, and `not` never. Each branch is
|
|
6473
|
-
// asked through an annotation function (emitAnnot)
|
|
6474
|
-
// only then adds what it evaluated, so nothing has to be rolled back.
|
|
6862
|
+
// asked through an annotation function (emitAnnot).
|
|
6475
6863
|
function genUnevaluatedDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
|
|
6476
6864
|
// additionalProperties evaluates every key the others do not, pass or fail.
|
|
6477
6865
|
if (schema.additionalProperties !== undefined) return
|
|
6478
6866
|
const ui = ctx.varCounter++
|
|
6479
6867
|
const S = `_ev${ui}`
|
|
6480
|
-
const body = [`const ${S}=
|
|
6868
|
+
const body = [`const ${S}=[]`]
|
|
6481
6869
|
addLocalAnnotations(schema, v, S, body, ctx)
|
|
6482
|
-
|
|
6870
|
+
// The anyOf and oneOf counts are handed to the keywords' own error code
|
|
6871
|
+
// (emitBranchCollapse), with the object they were counted for: they are
|
|
6872
|
+
// declared once at the top of the combined function, where every later
|
|
6873
|
+
// line sees them, and read only when that object is the value at hand, so
|
|
6874
|
+
// a count left from another value, another loop iteration or another place
|
|
6875
|
+
// is never taken for this one. Only in the combined function proper.
|
|
6876
|
+
const share = ctx.inCombined && !ctx.inBranchFn && ctx.hoisted !== undefined &&
|
|
6877
|
+
(Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf))
|
|
6878
|
+
const counts = share ? { anyOf: `_ac${ui}`, oneOf: `_oc${ui}`, oneOfMask: `_om${ui}`, of: `_ao${ui}` } : null
|
|
6879
|
+
if (share) {
|
|
6880
|
+
ctx.hoisted.push(counts.anyOf, counts.oneOf, counts.oneOfMask, counts.of)
|
|
6881
|
+
body.push(`${counts.of}=${v};${counts.oneOfMask}=0`)
|
|
6882
|
+
if (!ctx.branchCounts) ctx.branchCounts = new Map()
|
|
6883
|
+
if (Array.isArray(schema.anyOf)) ctx.branchCounts.set(schema.anyOf, { of: counts.of, count: counts.anyOf, mask: null })
|
|
6884
|
+
if (Array.isArray(schema.oneOf)) ctx.branchCounts.set(schema.oneOf, { of: counts.of, count: counts.oneOf, mask: schema.oneOf.length <= 30 ? counts.oneOfMask : null })
|
|
6885
|
+
}
|
|
6886
|
+
addTopApplicatorAnnotations(schema, v, S, body, ctx, counts)
|
|
6483
6887
|
const kVar = `_uk${ui}`
|
|
6484
6888
|
const up = schema.unevaluatedProperties
|
|
6485
6889
|
if (up === false) {
|
|
6486
|
-
body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.
|
|
6890
|
+
body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${fail('unevaluatedProperties', 'unevaluatedProperties', `{unevaluatedProperty:${kVar}}`, "'must NOT have unevaluated properties'")}}`)
|
|
6487
6891
|
} else if (typeof up === 'object' && up !== null) {
|
|
6488
6892
|
const pe = emitPtrEsc(ctx)
|
|
6489
6893
|
const p = pathExpr ? `${pathExpr}+'/'+${pe}(${kVar})` : `'/'+${pe}(${kVar})`
|
|
6490
6894
|
const sub = []
|
|
6491
6895
|
genCodeC(up, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/unevaluatedProperties')
|
|
6492
|
-
if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.
|
|
6896
|
+
if (sub.length) body.push(`for(const ${kVar} of Object.keys(${v}))if(!${S}.includes(${kVar})){${sub.join('\n ')}\n }`)
|
|
6493
6897
|
} else {
|
|
6494
6898
|
throw DECLINE
|
|
6495
6899
|
}
|
|
6496
6900
|
lines.push(isObj ? `{${body.join(';')}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body.join(';')}}`)
|
|
6497
6901
|
}
|
|
6498
6902
|
|
|
6499
|
-
// The names a node's own keywords evaluate,
|
|
6500
|
-
// properties, keys matching a pattern
|
|
6903
|
+
// The names a node's own keywords evaluate, pushed onto the array `S`:
|
|
6904
|
+
// present declared properties, keys matching a pattern, and every key when
|
|
6905
|
+
// additionalProperties is there.
|
|
6501
6906
|
function addLocalAnnotations(node, v, S, out, ctx) {
|
|
6502
|
-
|
|
6907
|
+
if (node.additionalProperties !== undefined) {
|
|
6908
|
+
out.push(`for(const _ka in ${v})${S}.push(_ka)`)
|
|
6909
|
+
return
|
|
6910
|
+
}
|
|
6911
|
+
for (const k of Object.keys(node.properties || {})) out.push(`if(Object.prototype.hasOwnProperty.call(${v},${JSON.stringify(k)}))${S}.push(${JSON.stringify(k)})`)
|
|
6503
6912
|
const pats = Object.keys(node.patternProperties || {})
|
|
6504
6913
|
if (pats.length) {
|
|
6505
6914
|
const tests = []
|
|
@@ -6512,24 +6921,31 @@ function addLocalAnnotations(node, v, S, out, ctx) {
|
|
|
6512
6921
|
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
6513
6922
|
tests.push(`_re${ri}.test(${kv})`)
|
|
6514
6923
|
}
|
|
6515
|
-
out.push(`for(const ${kv} in ${v})if(${tests.join('||')})${S}.
|
|
6924
|
+
out.push(`for(const ${kv} in ${v})if(${tests.join('||')})${S}.push(${kv})`)
|
|
6516
6925
|
}
|
|
6517
6926
|
}
|
|
6518
6927
|
|
|
6519
|
-
// What
|
|
6520
|
-
//
|
|
6521
|
-
//
|
|
6522
|
-
|
|
6928
|
+
// What the applicators of the node that owns the unevaluatedProperties add.
|
|
6929
|
+
// Their validity is checked by the code around this; here a branch only
|
|
6930
|
+
// contributes, through its annotation function, when it passes, and a oneOf
|
|
6931
|
+
// only with exactly one passing branch. An annotation function that fails
|
|
6932
|
+
// leaves `S` as it found it.
|
|
6933
|
+
function addTopApplicatorAnnotations(node, v, S, out, ctx, counts) {
|
|
6934
|
+
if (node.$dynamicRef !== undefined || node.$recursiveRef !== undefined || node.dependencies !== undefined) throw DECLINE
|
|
6523
6935
|
for (const b of node.allOf || []) out.push(`${emitAnnot(b, ctx)}(${v},${S})`)
|
|
6524
|
-
|
|
6936
|
+
// With `counts`, the passing branches of anyOf and oneOf are counted into
|
|
6937
|
+
// variables the caller declared, so the keyword's own error code further
|
|
6938
|
+
// down can skip its branches when the count already says it holds (see
|
|
6939
|
+
// emitBranchCollapse).
|
|
6940
|
+
if (Array.isArray(node.anyOf)) {
|
|
6941
|
+
if (counts) out.push(`${counts.anyOf}=0;${node.anyOf.map((b) => `if(${emitAnnot(b, ctx)}(${v},${S}))${counts.anyOf}++;`).join('')}`)
|
|
6942
|
+
else for (const b of node.anyOf) out.push(`${emitAnnot(b, ctx)}(${v},${S})`)
|
|
6943
|
+
}
|
|
6525
6944
|
if (Array.isArray(node.oneOf)) {
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
} else {
|
|
6531
|
-
for (const b of node.oneOf) out.push(`${emitAnnot(b, ctx)}(${v},${S})`)
|
|
6532
|
-
}
|
|
6945
|
+
const t = counts ? counts.oneOf : `_oc${ctx.varCounter++}`
|
|
6946
|
+
// Which branches passed, as bits, for a collapse that has to name them.
|
|
6947
|
+
const mask = counts && node.oneOf.length <= 30 ? counts.oneOfMask : null
|
|
6948
|
+
out.push(`{const ${t}m=${S}.length;${counts ? '' : 'let '}${t}=0;${node.oneOf.map((b, i) => `if(${emitAnnot(b, ctx)}(${v},${S})){${t}++;${mask ? `${mask}|=${1 << i};` : ''}}`).join('')}if(${t}!==1)_rb(${S},${t}m)}`)
|
|
6533
6949
|
}
|
|
6534
6950
|
if (node.if !== undefined) {
|
|
6535
6951
|
const thenCall = node.then !== undefined ? `${emitAnnot(node.then, ctx)}(${v},${S})` : ''
|
|
@@ -6540,7 +6956,6 @@ function addApplicatorAnnotations(node, v, S, out, ctx, top) {
|
|
|
6540
6956
|
for (const [k, ds] of Object.entries(node.dependentSchemas)) out.push(`if(Object.prototype.hasOwnProperty.call(${v},${JSON.stringify(k)}))${emitAnnot(ds, ctx)}(${v},${S})`)
|
|
6541
6957
|
}
|
|
6542
6958
|
if (typeof node.$ref === 'string') out.push(`${emitAnnot(refTargetFor(node.$ref, ctx), ctx)}(${v},${S})`)
|
|
6543
|
-
if (node.$dynamicRef !== undefined || node.$recursiveRef !== undefined || node.dependencies !== undefined) throw DECLINE
|
|
6544
6959
|
}
|
|
6545
6960
|
|
|
6546
6961
|
function refTargetFor(ref, ctx) {
|
|
@@ -6550,44 +6965,236 @@ function refTargetFor(ref, ctx) {
|
|
|
6550
6965
|
return t
|
|
6551
6966
|
}
|
|
6552
6967
|
|
|
6553
|
-
//
|
|
6554
|
-
//
|
|
6968
|
+
// The keywords an annotation function evaluates itself, through its children;
|
|
6969
|
+
// the rest of a subschema is checked by a plain verdict.
|
|
6970
|
+
const ANNOT_APPLICATORS = new Set(['allOf', 'anyOf', 'oneOf', 'if', 'then', 'else', 'dependentSchemas', '$ref', 'not', 'unevaluatedProperties'])
|
|
6971
|
+
|
|
6972
|
+
// A function `(value, S) => valid` for one subschema: whether it passes, and
|
|
6973
|
+
// when it does, what it evaluated pushed onto S; when it does not, S is left
|
|
6974
|
+
// as it was. Validity is worked out bottom up: the subschema's own keywords
|
|
6975
|
+
// by a verdict of the subschema without its applicators, each applicator from
|
|
6976
|
+
// its branches' annotation functions. Asking each branch for a full verdict
|
|
6977
|
+
// and then again for its annotations repeated every nested branch once per
|
|
6978
|
+
// level, which made nested references the slowest shape there was.
|
|
6555
6979
|
function emitAnnot(sub, ctx) {
|
|
6556
6980
|
if (sub === true) return '_anT'
|
|
6557
6981
|
if (sub === false) return '_anF'
|
|
6558
6982
|
if (sub === null || typeof sub !== 'object') throw DECLINE
|
|
6559
6983
|
if (!ctx.annFns) {
|
|
6560
6984
|
ctx.annFns = new Map()
|
|
6561
|
-
;(ctx.preamble || ctx.helperCode).push('function _anT(){return true}function _anF(){return false}')
|
|
6985
|
+
;(ctx.preamble || ctx.helperCode).push('function _anT(){return true}function _anF(){return false}function _rb(S,m){if(S.length!==m)S.length=m}')
|
|
6562
6986
|
}
|
|
6563
6987
|
const hit = ctx.annFns.get(sub)
|
|
6564
6988
|
if (hit !== undefined) {
|
|
6565
6989
|
if (hit === null) throw DECLINE // a reference cycle: not modelled here
|
|
6566
6990
|
return hit
|
|
6567
6991
|
}
|
|
6992
|
+
if (sub.$dynamicRef !== undefined || sub.$recursiveRef !== undefined || sub.dependencies !== undefined) throw DECLINE
|
|
6993
|
+
// unevaluatedItems reads what the applicators evaluate too; the local
|
|
6994
|
+
// verdict would see it without them.
|
|
6995
|
+
if (sub.unevaluatedItems !== undefined) throw DECLINE
|
|
6568
6996
|
ctx.annFns.set(sub, null)
|
|
6569
6997
|
const name = `_an${ctx.varCounter++}`
|
|
6998
|
+
const local = {}
|
|
6999
|
+
for (const k of Object.keys(sub)) if (!ANNOT_APPLICATORS.has(k)) setOwn(local, k, sub[k])
|
|
6570
7000
|
const vl = []
|
|
6571
|
-
nestedGenCode(
|
|
6572
|
-
const
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
body.push(`
|
|
6579
|
-
}
|
|
6580
|
-
|
|
6581
|
-
|
|
7001
|
+
nestedGenCode(local, '_av', vl, ctx)
|
|
7002
|
+
const decls = [`function ${name}_v(_av){${vl.join('\n ')}\n return true}`]
|
|
7003
|
+
const body = [`if(!${name}_v(d))return false`]
|
|
7004
|
+
if (sub.not !== undefined) {
|
|
7005
|
+
const nl = []
|
|
7006
|
+
nestedGenCode(sub.not, '_nv', nl, ctx)
|
|
7007
|
+
decls.push(`function ${name}_n(_nv){${nl.join('\n ')}\n return true}`)
|
|
7008
|
+
body.push(`if(${name}_n(d))return false`)
|
|
7009
|
+
}
|
|
7010
|
+
body.push(`const _m=S.length;const _o=typeof d==='object'&&d!==null&&!Array.isArray(d)`)
|
|
7011
|
+
const locals = []
|
|
7012
|
+
addLocalAnnotations(sub, 'd', 'S', locals, ctx)
|
|
7013
|
+
if (locals.length) body.push(`if(_o){${locals.join(';')}}`)
|
|
7014
|
+
const failOut = '{_rb(S,_m);return false}'
|
|
7015
|
+
for (const b of sub.allOf || []) body.push(`if(!${emitAnnot(b, ctx)}(d,S))${failOut}`)
|
|
7016
|
+
if (Array.isArray(sub.anyOf)) body.push(`{let _a=false;${sub.anyOf.map((b) => `if(${emitAnnot(b, ctx)}(d,S))_a=true;`).join('')}if(!_a)${failOut}}`)
|
|
7017
|
+
if (Array.isArray(sub.oneOf)) body.push(`{let _c=0;${sub.oneOf.map((b) => `if(${emitAnnot(b, ctx)}(d,S))_c++;`).join('')}if(_c!==1)${failOut}}`)
|
|
7018
|
+
if (sub.if !== undefined) {
|
|
7019
|
+
const thenCheck = sub.then !== undefined ? `if(!${emitAnnot(sub.then, ctx)}(d,S))${failOut}` : ''
|
|
7020
|
+
const elseCheck = sub.else !== undefined ? `if(!${emitAnnot(sub.else, ctx)}(d,S))${failOut}` : ''
|
|
7021
|
+
body.push(`if(${emitAnnot(sub.if, ctx)}(d,S)){${thenCheck}}else{${elseCheck}}`)
|
|
7022
|
+
}
|
|
7023
|
+
if (sub.dependentSchemas && typeof sub.dependentSchemas === 'object') {
|
|
7024
|
+
for (const [k, ds] of Object.entries(sub.dependentSchemas)) body.push(`if(_o&&Object.prototype.hasOwnProperty.call(d,${JSON.stringify(k)})&&!${emitAnnot(ds, ctx)}(d,S))${failOut}`)
|
|
7025
|
+
}
|
|
7026
|
+
if (typeof sub.$ref === 'string') body.push(`if(!${emitAnnot(refTargetFor(sub.$ref, ctx), ctx)}(d,S))${failOut}`)
|
|
7027
|
+
// An unevaluatedProperties of its own: the keys this subschema evaluated
|
|
7028
|
+
// are the ones pushed since _m; what is left must pass it, and then every
|
|
7029
|
+
// key is evaluated.
|
|
7030
|
+
const up = sub.unevaluatedProperties
|
|
7031
|
+
if (up !== undefined && up !== true && sub.additionalProperties === undefined) {
|
|
7032
|
+
if (up === false) {
|
|
7033
|
+
body.push(`if(_o){for(const _k of Object.keys(d))if(S.indexOf(_k,_m)<0)${failOut}}`)
|
|
7034
|
+
} else if (typeof up === 'object' && up !== null) {
|
|
7035
|
+
const ul = []
|
|
7036
|
+
nestedGenCode(up, '_uv', ul, ctx)
|
|
7037
|
+
decls.push(`function ${name}_u(_uv){${ul.join('\n ')}\n return true}`)
|
|
7038
|
+
body.push(`if(_o){for(const _k of Object.keys(d))if(S.indexOf(_k,_m)<0&&!${name}_u(d[_k]))${failOut}}`)
|
|
7039
|
+
} else {
|
|
7040
|
+
throw DECLINE
|
|
7041
|
+
}
|
|
6582
7042
|
}
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
;(ctx.preamble || ctx.helperCode).push(`function ${name}_v(_av){${vl.join('\n ')}\n return true}function ${name}(d,S){if(!${name}_v(d))return false;if(typeof d!=='object'||d===null||Array.isArray(d))return true;${body.join(';')};return true}`)
|
|
7043
|
+
if (up !== undefined && sub.additionalProperties === undefined) body.push(`if(_o)for(const _ka in d)S.push(_ka)`)
|
|
7044
|
+
decls.push(`function ${name}(d,S){${body.join(';')};return true}`)
|
|
7045
|
+
;(ctx.preamble || ctx.helperCode).push(decls.join(''))
|
|
6587
7046
|
ctx.annFns.set(sub, name)
|
|
6588
7047
|
return name
|
|
6589
7048
|
}
|
|
6590
7049
|
|
|
7050
|
+
// unevaluatedItems worked out at run time, for the combined function. The
|
|
7051
|
+
// evaluated positions of an array are kept as the interpreter keeps them
|
|
7052
|
+
// (lib/plan-compiler.js): `n`, every index below it evaluated, and `x`, the
|
|
7053
|
+
// indexes past it that `contains` matched. This node's own prefixItems,
|
|
7054
|
+
// items and contains mark them whatever their own verdict, and each in-place
|
|
7055
|
+
// subschema marks them only when it passes (emitAnnotI); what is left must
|
|
7056
|
+
// pass unevaluatedItems. `false` reports once, at the first position left,
|
|
7057
|
+
// as the interpreter does.
|
|
7058
|
+
function genUnevaluatedItemsDynamicC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
7059
|
+
if (schema.$dynamicRef !== undefined || schema.$recursiveRef !== undefined) throw DECLINE
|
|
7060
|
+
if (Array.isArray(schema.items) || schema.additionalItems !== undefined) throw DECLINE
|
|
7061
|
+
annItemHelpers(ctx)
|
|
7062
|
+
const ui = ctx.varCounter++
|
|
7063
|
+
const S = `_es${ui}`
|
|
7064
|
+
const body = [`const ${S}={n:0,x:null}`]
|
|
7065
|
+
addLocalItemAnnotations(schema, v, S, body, ctx)
|
|
7066
|
+
for (const b of schema.allOf || []) body.push(`${emitAnnotI(b, ctx)}(${v},${S})`)
|
|
7067
|
+
if (Array.isArray(schema.anyOf)) for (const b of schema.anyOf) body.push(`${emitAnnotI(b, ctx)}(${v},${S})`)
|
|
7068
|
+
if (Array.isArray(schema.oneOf)) {
|
|
7069
|
+
body.push(`{const _n0=${S}.n,_x0=${S}.x===null?0:${S}.x.length;let _c=0;${schema.oneOf.map((b) => `if(${emitAnnotI(b, ctx)}(${v},${S}))_c++;`).join('')}if(_c!==1)_rbI(${S},_n0,_x0)}`)
|
|
7070
|
+
}
|
|
7071
|
+
if (schema.if !== undefined) {
|
|
7072
|
+
const thenCall = schema.then !== undefined ? `${emitAnnotI(schema.then, ctx)}(${v},${S})` : ''
|
|
7073
|
+
const elseCall = schema.else !== undefined ? `${emitAnnotI(schema.else, ctx)}(${v},${S})` : ''
|
|
7074
|
+
body.push(`if(${emitAnnotI(schema.if, ctx)}(${v},${S})){${thenCall}}else{${elseCall}}`)
|
|
7075
|
+
}
|
|
7076
|
+
if (typeof schema.$ref === 'string') body.push(`${emitAnnotI(refTargetFor(schema.$ref, ctx), ctx)}(${v},${S})`)
|
|
7077
|
+
const iv = `_ix${ui}`
|
|
7078
|
+
const ui2 = schema.unevaluatedItems
|
|
7079
|
+
if (ui2 === false) {
|
|
7080
|
+
const failI = fail('unevaluatedItems', 'unevaluatedItems', `{limit:${iv}}`, `'must NOT have more than '+${iv}+' items'`)
|
|
7081
|
+
body.push(`for(let ${iv}=0;${iv}<${v}.length;${iv}++){if(_hI(${S},${iv}))continue;${failI};break}`)
|
|
7082
|
+
} else if (typeof ui2 === 'object' && ui2 !== null) {
|
|
7083
|
+
const pe = pathExpr ? `${pathExpr}+'/'+${iv}` : `'/'+${iv}`
|
|
7084
|
+
const sub = []
|
|
7085
|
+
genCodeC(ui2, `${v}[${iv}]`, pe, sub, ctx, schemaPrefix + '/unevaluatedItems')
|
|
7086
|
+
if (sub.length) body.push(`for(let ${iv}=0;${iv}<${v}.length;${iv}++){if(_hI(${S},${iv}))continue;${sub.join('\n ')}\n }`)
|
|
7087
|
+
} else {
|
|
7088
|
+
throw DECLINE
|
|
7089
|
+
}
|
|
7090
|
+
lines.push(`if(Array.isArray(${v})){${body.join(';')}}`)
|
|
7091
|
+
}
|
|
7092
|
+
|
|
7093
|
+
function annItemHelpers(ctx) {
|
|
7094
|
+
if (ctx.annItemHelpers) return
|
|
7095
|
+
ctx.annItemHelpers = true
|
|
7096
|
+
;(ctx.preamble || ctx.helperCode).push(
|
|
7097
|
+
'function _aI(S,i){if(i<S.n)return;if(i===S.n){S.n=i+1;return}(S.x||(S.x=[])).push(i)}' +
|
|
7098
|
+
'function _mrI(S,f,t){if(f>=t)return;if(f<=S.n){if(t>S.n)S.n=t;return}const x=S.x||(S.x=[]);for(let i=f;i<t;i++)x.push(i)}' +
|
|
7099
|
+
'function _hI(S,i){if(i<S.n)return true;const x=S.x;if(x===null)return false;for(let j=0;j<x.length;j++)if(x[j]===i)return true;return false}' +
|
|
7100
|
+
'function _rbI(S,n,l){S.n=n;if(S.x!==null)S.x.length=l}' +
|
|
7101
|
+
'function _anIT(){return true}function _anIF(){return false}')
|
|
7102
|
+
}
|
|
7103
|
+
|
|
7104
|
+
// The positions a node's own keywords evaluate, marked on `S`, for an array.
|
|
7105
|
+
function addLocalItemAnnotations(node, v, S, out, ctx) {
|
|
7106
|
+
if (Array.isArray(node.items) || node.additionalItems !== undefined) throw DECLINE
|
|
7107
|
+
const prefix = Array.isArray(node.prefixItems) ? node.prefixItems.length : 0
|
|
7108
|
+
if (prefix) out.push(`_mrI(${S},0,Math.min(${v}.length,${prefix}))`)
|
|
7109
|
+
if (node.items !== undefined) out.push(`_mrI(${S},${prefix},${v}.length)`)
|
|
7110
|
+
if (node.contains !== undefined) {
|
|
7111
|
+
const cl = []
|
|
7112
|
+
nestedGenCode(node.contains, '_cv', cl, ctx)
|
|
7113
|
+
const ci = ctx.varCounter++
|
|
7114
|
+
;(ctx.preamble || ctx.helperCode).push(`function _cI${ci}(_cv){${cl.join('\n ')}\n return true}`)
|
|
7115
|
+
out.push(`for(let _i=0;_i<${v}.length;_i++)if(_cI${ci}(${v}[_i]))_aI(${S},_i)`)
|
|
7116
|
+
}
|
|
7117
|
+
}
|
|
7118
|
+
|
|
7119
|
+
// The keywords an item annotation function evaluates through its children;
|
|
7120
|
+
// the rest of a subschema is checked by a plain verdict.
|
|
7121
|
+
const ANNOT_ITEM_APPLICATORS = new Set(['allOf', 'anyOf', 'oneOf', 'if', 'then', 'else', '$ref', 'not', 'unevaluatedItems'])
|
|
7122
|
+
|
|
7123
|
+
// A function `(value, S) => valid` for one subschema, marking on S the array
|
|
7124
|
+
// positions it evaluated when it passes, and leaving S as it was when it does
|
|
7125
|
+
// not. A subschema with an unevaluatedItems of its own evaluates every
|
|
7126
|
+
// position once that passes, so it works on a record of its own and then
|
|
7127
|
+
// marks them all.
|
|
7128
|
+
function emitAnnotI(sub, ctx) {
|
|
7129
|
+
if (sub === true) return '_anIT'
|
|
7130
|
+
if (sub === false) return '_anIF'
|
|
7131
|
+
if (sub === null || typeof sub !== 'object') throw DECLINE
|
|
7132
|
+
if (!ctx.annFnsI) ctx.annFnsI = new Map()
|
|
7133
|
+
const hit = ctx.annFnsI.get(sub)
|
|
7134
|
+
if (hit !== undefined) {
|
|
7135
|
+
if (hit === null) throw DECLINE
|
|
7136
|
+
return hit
|
|
7137
|
+
}
|
|
7138
|
+
if (sub.$dynamicRef !== undefined || sub.$recursiveRef !== undefined) throw DECLINE
|
|
7139
|
+
if (Array.isArray(sub.items) || sub.additionalItems !== undefined) throw DECLINE
|
|
7140
|
+
// unevaluatedProperties reads the applicators too, which the local verdict
|
|
7141
|
+
// below leaves out.
|
|
7142
|
+
if (sub.unevaluatedProperties !== undefined && sub.unevaluatedProperties !== true) throw DECLINE
|
|
7143
|
+
ctx.annFnsI.set(sub, null)
|
|
7144
|
+
annItemHelpers(ctx)
|
|
7145
|
+
const name = `_ai${ctx.varCounter++}`
|
|
7146
|
+
const local = {}
|
|
7147
|
+
for (const k of Object.keys(sub)) if (!ANNOT_ITEM_APPLICATORS.has(k)) setOwn(local, k, sub[k])
|
|
7148
|
+
const vl = []
|
|
7149
|
+
nestedGenCode(local, '_av', vl, ctx)
|
|
7150
|
+
const decls = [`function ${name}_v(_av){${vl.join('\n ')}\n return true}`]
|
|
7151
|
+
const body = [`if(!${name}_v(d))return false`]
|
|
7152
|
+
if (sub.not !== undefined) {
|
|
7153
|
+
const nl = []
|
|
7154
|
+
nestedGenCode(sub.not, '_nv', nl, ctx)
|
|
7155
|
+
decls.push(`function ${name}_n(_nv){${nl.join('\n ')}\n return true}`)
|
|
7156
|
+
body.push(`if(${name}_n(d))return false`)
|
|
7157
|
+
}
|
|
7158
|
+
const own = sub.unevaluatedItems !== undefined && sub.unevaluatedItems !== true
|
|
7159
|
+
const R = own ? '_R' : 'S'
|
|
7160
|
+
body.push(own ? `const _R={n:0,x:null}` : `const _n0=S.n,_x0=S.x===null?0:S.x.length`)
|
|
7161
|
+
const failOut = own ? 'return false' : '{_rbI(S,_n0,_x0);return false}'
|
|
7162
|
+
const locals = []
|
|
7163
|
+
addLocalItemAnnotations(sub, 'd', R, locals, ctx)
|
|
7164
|
+
if (locals.length) body.push(`if(Array.isArray(d)){${locals.join(';')}}`)
|
|
7165
|
+
for (const b of sub.allOf || []) body.push(`if(!${emitAnnotI(b, ctx)}(d,${R}))${failOut}`)
|
|
7166
|
+
if (Array.isArray(sub.anyOf)) body.push(`{let _a=false;${sub.anyOf.map((b) => `if(${emitAnnotI(b, ctx)}(d,${R}))_a=true;`).join('')}if(!_a)${failOut}}`)
|
|
7167
|
+
if (Array.isArray(sub.oneOf)) body.push(`{let _c=0;${sub.oneOf.map((b) => `if(${emitAnnotI(b, ctx)}(d,${R}))_c++;`).join('')}if(_c!==1)${failOut}}`)
|
|
7168
|
+
if (sub.if !== undefined) {
|
|
7169
|
+
const thenCheck = sub.then !== undefined ? `if(!${emitAnnotI(sub.then, ctx)}(d,${R}))${failOut}` : ''
|
|
7170
|
+
const elseCheck = sub.else !== undefined ? `if(!${emitAnnotI(sub.else, ctx)}(d,${R}))${failOut}` : ''
|
|
7171
|
+
body.push(`if(${emitAnnotI(sub.if, ctx)}(d,${R})){${thenCheck}}else{${elseCheck}}`)
|
|
7172
|
+
}
|
|
7173
|
+
if (typeof sub.$ref === 'string') body.push(`if(!${emitAnnotI(refTargetFor(sub.$ref, ctx), ctx)}(d,${R}))${failOut}`)
|
|
7174
|
+
if (own) {
|
|
7175
|
+
const u = sub.unevaluatedItems
|
|
7176
|
+
if (u === false) {
|
|
7177
|
+
body.push(`if(Array.isArray(d)){for(let _i=0;_i<d.length;_i++)if(!_hI(_R,_i))return false}`)
|
|
7178
|
+
} else if (typeof u === 'object' && u !== null) {
|
|
7179
|
+
const ul = []
|
|
7180
|
+
nestedGenCode(u, '_uv', ul, ctx)
|
|
7181
|
+
decls.push(`function ${name}_u(_uv){${ul.join('\n ')}\n return true}`)
|
|
7182
|
+
body.push(`if(Array.isArray(d)){for(let _i=0;_i<d.length;_i++)if(!_hI(_R,_i)&&!${name}_u(d[_i]))return false}`)
|
|
7183
|
+
} else {
|
|
7184
|
+
throw DECLINE
|
|
7185
|
+
}
|
|
7186
|
+
body.push(`if(Array.isArray(d))_mrI(S,0,d.length)`)
|
|
7187
|
+
}
|
|
7188
|
+
// `unevaluatedItems: true` evaluates every position once the subschema
|
|
7189
|
+
// has passed, with nothing to check; marked last, so a failure above has
|
|
7190
|
+
// nothing of it to undo.
|
|
7191
|
+
if (sub.unevaluatedItems === true) body.push(`if(Array.isArray(d))_mrI(S,0,d.length)`)
|
|
7192
|
+
decls.push(`function ${name}(d,S){${body.join(';')};return true}`)
|
|
7193
|
+
;(ctx.preamble || ctx.helperCode).push(decls.join(''))
|
|
7194
|
+
ctx.annFnsI.set(sub, name)
|
|
7195
|
+
return name
|
|
7196
|
+
}
|
|
7197
|
+
|
|
6591
7198
|
function genUnevaluatedFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
|
|
6592
7199
|
const propKeysU = Object.keys(schema.properties || {})
|
|
6593
7200
|
const ui = ctx.varCounter++
|
|
@@ -6653,7 +7260,7 @@ function genAdditionalSchemaC(schema, v, pathExpr, lines, ctx, schemaPrefix, isO
|
|
|
6653
7260
|
function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
|
|
6654
7261
|
let handledPropertyNames = false
|
|
6655
7262
|
const ppEntries = Object.entries(schema.patternProperties)
|
|
6656
|
-
const pn = schema.propertyNames
|
|
7263
|
+
const pn = isSimplePN(schema.propertyNames) ? schema.propertyNames : null
|
|
6657
7264
|
const pi = ctx.varCounter++
|
|
6658
7265
|
|
|
6659
7266
|
// Build pattern matchers: prefer charCodeAt for simple prefixes
|
|
@@ -6774,7 +7381,16 @@ function genPropertyNamesFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
6774
7381
|
function genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
6775
7382
|
const pn = schema.propertyNames
|
|
6776
7383
|
const ki = ctx.varCounter++
|
|
7384
|
+
// In the rich function an error carries the value it was raised on, and
|
|
7385
|
+
// here that would be the key where the interpreter's error points at the
|
|
7386
|
+
// object, so that one is left to the plain function and enrichment.
|
|
7387
|
+
if (!isSimplePN(pn) && ctx.rich) throw DECLINE
|
|
6777
7388
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
|
|
7389
|
+
if (!isSimplePN(pn)) {
|
|
7390
|
+
genCodeC(pn, `_k${ki}`, pathExpr, lines, ctx, schemaPrefix + '/propertyNames')
|
|
7391
|
+
lines.push(`}}`)
|
|
7392
|
+
return
|
|
7393
|
+
}
|
|
6778
7394
|
if (pn.minLength !== undefined) {
|
|
6779
7395
|
lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
6780
7396
|
}
|
|
@@ -6992,6 +7608,6 @@ function _collectEval(schema, result, defs, schemaMap, refStack, isRoot) {
|
|
|
6992
7608
|
// not → contributes nothing (spec: annotations from not are discarded)
|
|
6993
7609
|
}
|
|
6994
7610
|
|
|
6995
|
-
module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, AJV_MESSAGES, _setCodeBudget }
|
|
7611
|
+
module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, expandedChars, AJV_MESSAGES, _setCodeBudget }
|
|
6996
7612
|
// For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
|
|
6997
7613
|
Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })
|