ata-validator 1.51.0 → 1.53.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 +26 -20
- package/compat.js +314 -29
- package/index.bundle.mjs +15 -0
- package/lib/aot-impl.js +2 -2
- package/lib/combined-runtime.js +4 -1
- package/lib/compat-errors.js +131 -17
- package/lib/draft7.js +65 -35
- package/lib/error-runtime.js +5 -1
- package/lib/interpreter-core.js +297 -14
- package/lib/js-compiler-errors.js +15 -15
- package/lib/js-compiler.js +196 -45
- package/lib/keywords.js +24 -1
- package/lib/levenshtein.js +21 -5
- package/lib/plan-compiler.js +8 -3
- package/lib/safe-regex.js +33 -14
- package/lib/schema-scan.js +29 -1
- package/lib/validator-core.js +99 -13
- package/lib/version.js +1 -1
- package/package.json +10 -8
package/lib/js-compiler.js
CHANGED
|
@@ -36,7 +36,7 @@ function uqSources () {
|
|
|
36
36
|
// that would have written them.
|
|
37
37
|
const SHARED_RT = {
|
|
38
38
|
_deqHoisted: ['_deq'], _peHoisted: ['_pe'],
|
|
39
|
-
_own_PN: ['_PN', '_OP', '_gp'], _own_hop: ['_hop'], _own_h: ['_h'], _own_ok: ['_ok'], _own_hall: ['_hall'],
|
|
39
|
+
_own_PN: ['_PN', '_OP', '_gp'], _own_hop: ['_hop'], _own_h: ['_h'], _own_ok: ['_ok'], _own_hall: ['_hall'], _own_nk: ['_nk'],
|
|
40
40
|
}
|
|
41
41
|
let _rt = null
|
|
42
42
|
function sharedRt () { return _rt || (_rt = require('./combined-runtime')) }
|
|
@@ -96,8 +96,8 @@ function apMembershipCheck (ctx, names, v) {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
// With nothing declared every key is additional; `if()` would not parse.
|
|
99
|
-
if (names.length === 0) return `for(var _k in ${v})return false`
|
|
100
|
-
return `for(var _k in ${v})if(${names.map((k) => `_k!==${JSON.stringify(k)}`).join('&&')})return false`
|
|
99
|
+
if (names.length === 0) return `for(var _k in ${v})if(${ownFail(ctx, v)})return false`
|
|
100
|
+
return `for(var _k in ${v})if(${names.map((k) => `_k!==${JSON.stringify(k)}`).join('&&')}&&${ownFail(ctx, v)})return false`
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
function emitDeq(ctx) {
|
|
@@ -376,7 +376,9 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
376
376
|
if (!propCheck) return null // bail if sub-schema too complex
|
|
377
377
|
checks.push((d) => {
|
|
378
378
|
if (typeof d !== 'object' || d === null || !hasOwnKey(d, key)) return true
|
|
379
|
-
|
|
379
|
+
// An undefined value is not checked, as the generated code does not.
|
|
380
|
+
const v = d[key]
|
|
381
|
+
return v === undefined || propCheck(v)
|
|
380
382
|
})
|
|
381
383
|
}
|
|
382
384
|
}
|
|
@@ -907,8 +909,9 @@ const OWN_HELPER_CODE = {
|
|
|
907
909
|
_h: 'function _h(o,k){return _hop.call(o,k)}',
|
|
908
910
|
_ok: 'function _ok(o,k){return (_PN?o.__proto__:_gp(o))===_OP||_hop.call(o,k)}',
|
|
909
911
|
_hall: 'function _hall(o,ks){for(let i=0;i<ks.length;i++)if(!_hop.call(o,ks[i]))return false;return true}',
|
|
912
|
+
_nk: 'function _nk(o,n){return (_PN?o.__proto__:_gp(o))===_OP||Object.keys(o).length!==n}',
|
|
910
913
|
}
|
|
911
|
-
const OWN_HELPER_DEPS = { _h: ['_hop'], _ok: ['_PN', '_hop'], _hall: ['_hop'] }
|
|
914
|
+
const OWN_HELPER_DEPS = { _h: ['_hop'], _ok: ['_PN', '_hop'], _hall: ['_hop'], _nk: ['_PN'] }
|
|
912
915
|
function useOwnHelper (ctx, name) {
|
|
913
916
|
for (const dep of OWN_HELPER_DEPS[name] || []) useOwnHelper(ctx, dep)
|
|
914
917
|
hoistOnce(ctx, '_own' + name, OWN_HELPER_CODE[name])
|
|
@@ -1208,6 +1211,17 @@ function propFnCandidate (prop, ctx) {
|
|
|
1208
1211
|
// inline after all": an empty body (the inline path then asserts it the
|
|
1209
1212
|
// usual way) or one that calls the recursive entry, which is out of scope
|
|
1210
1213
|
// in the preamble.
|
|
1214
|
+
// Generates a body that will become a function of its own in the preamble.
|
|
1215
|
+
// The ordinary-object flags withPlain declared belong to the function being
|
|
1216
|
+
// written now and are out of scope there; a hoisted body takes the same
|
|
1217
|
+
// parameter names (`d`, `_av`, `_ov`) and would otherwise read a flag the
|
|
1218
|
+
// enclosing node declared for a value of that name.
|
|
1219
|
+
function genIsolated (ctx, run) {
|
|
1220
|
+
const outerPlain = ctx.plainOf
|
|
1221
|
+
ctx.plainOf = undefined
|
|
1222
|
+
try { return run() } finally { ctx.plainOf = outerPlain }
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1211
1225
|
function emitPropFn (prop, ctx) {
|
|
1212
1226
|
const bodyLines = []
|
|
1213
1227
|
const outerDeferred = ctx.deferredChecks
|
|
@@ -1216,7 +1230,7 @@ function emitPropFn (prop, ctx) {
|
|
|
1216
1230
|
ctx.plainReuse = undefined
|
|
1217
1231
|
const outerBytes = ctx.fnBytes
|
|
1218
1232
|
ctx.fnBytes = 0
|
|
1219
|
-
try { genCode(prop, 'd', bodyLines, ctx) } finally { ctx.plainReuse = outerReuse; ctx.fnBytes = outerBytes }
|
|
1233
|
+
try { genIsolated(ctx, () => genCode(prop, 'd', bodyLines, ctx)) } finally { ctx.plainReuse = outerReuse; ctx.fnBytes = outerBytes }
|
|
1220
1234
|
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
1221
1235
|
ctx.deferredChecks = outerDeferred
|
|
1222
1236
|
if (bodyLines.length === 0 || bodyLines.some((l) => /\b_validate\b/.test(l))) return null
|
|
@@ -1341,6 +1355,54 @@ function sharedDefNames (root, defs) {
|
|
|
1341
1355
|
// known trade: on that shape the shared function costs 27 ns a call.
|
|
1342
1356
|
const SHARED_DEF_MIN_CHARS = 120
|
|
1343
1357
|
|
|
1358
|
+
// A small definition applied at many places is called with objects of many
|
|
1359
|
+
// shapes, one or two per place, and V8 gives a function's property reads up
|
|
1360
|
+
// to four shapes before it falls back to a generic lookup several times
|
|
1361
|
+
// slower: SchemaStore's detekt sends 72 shapes through one four-property rule
|
|
1362
|
+
// definition. Such a definition is emitted as several functions with the same
|
|
1363
|
+
// body, each serving at most SITES_PER_COPY references, so every copy stays
|
|
1364
|
+
// within four shapes. Only small bodies are copied, so the source grows by a
|
|
1365
|
+
// bounded amount; a large or rarely referenced definition keeps one function.
|
|
1366
|
+
const SITES_PER_COPY = 4
|
|
1367
|
+
const COPY_MIN_SITES = 16
|
|
1368
|
+
const COPY_MAX_BODY = 1500
|
|
1369
|
+
function sharedDefCopy (ctx, defName, fnName) {
|
|
1370
|
+
const body = ctx.sharedDefBody && ctx.sharedDefBody.get(fnName)
|
|
1371
|
+
if (body === undefined || body.length > COPY_MAX_BODY) return fnName
|
|
1372
|
+
// Only a definition that reads properties gains: a string, a number or a
|
|
1373
|
+
// wrapper around another reference has no shapes to keep apart, and more
|
|
1374
|
+
// copies only mean more functions to warm up.
|
|
1375
|
+
const def = ctx.rootDefs && ctx.rootDefs[defName]
|
|
1376
|
+
if (def === null || typeof def !== 'object' || typeof def.properties !== 'object' || def.properties === null) return fnName
|
|
1377
|
+
if (!ctx.sharedDefSites) ctx.sharedDefSites = new Map()
|
|
1378
|
+
if (ctx.sharedDefSiteCounts === undefined) ctx.sharedDefSiteCounts = refCounts(ctx.rootSchema)
|
|
1379
|
+
if ((ctx.sharedDefSiteCounts.get(defName) || 0) < COPY_MIN_SITES) return fnName
|
|
1380
|
+
const site = ctx.sharedDefSites.get(fnName) || 0
|
|
1381
|
+
ctx.sharedDefSites.set(fnName, site + 1)
|
|
1382
|
+
const copy = Math.floor(site / SITES_PER_COPY)
|
|
1383
|
+
if (copy === 0) return fnName
|
|
1384
|
+
const name = `${fnName}_c${copy}`
|
|
1385
|
+
if (site % SITES_PER_COPY === 0) hoist(ctx, `function ${name}(d){${body}\n return true}`)
|
|
1386
|
+
return name
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// How many `$ref`s name each local definition, by name.
|
|
1390
|
+
function refCounts (root) {
|
|
1391
|
+
const counts = new Map()
|
|
1392
|
+
const seen = new WeakSet()
|
|
1393
|
+
const walk = (node) => {
|
|
1394
|
+
if (node === null || typeof node !== 'object' || seen.has(node)) return
|
|
1395
|
+
seen.add(node)
|
|
1396
|
+
if (typeof node.$ref === 'string') {
|
|
1397
|
+
const m = /^#\/(?:\$defs|definitions)\/([^/]+)$/.exec(node.$ref)
|
|
1398
|
+
if (m) counts.set(m[1], (counts.get(m[1]) || 0) + 1)
|
|
1399
|
+
}
|
|
1400
|
+
for (const v of Object.values(node)) walk(v)
|
|
1401
|
+
}
|
|
1402
|
+
walk(root)
|
|
1403
|
+
return counts
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1344
1406
|
function cyclicDefNames (defs) {
|
|
1345
1407
|
const out = new Set()
|
|
1346
1408
|
if (!defs || typeof defs !== 'object') return out
|
|
@@ -3419,7 +3481,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3419
3481
|
if (allRequired && propCount > 0) {
|
|
3420
3482
|
// Adaptive: for-in for <=15 keys (V8 fast path), Object.keys for >15
|
|
3421
3483
|
if (propCount <= 15) {
|
|
3422
|
-
lines.push(`var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount})return false`)
|
|
3484
|
+
lines.push(`var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount}&&${ownCountOff(ctx, v, propCount, plainFlag(ctx, v))})return false`)
|
|
3423
3485
|
} else {
|
|
3424
3486
|
lines.push(`if(Object.keys(${v}).length!==${propCount})return false`)
|
|
3425
3487
|
}
|
|
@@ -3534,7 +3596,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3534
3596
|
if (schema.additionalProperties === false && schema.properties && !schema.patternProperties && ctx.removeNodes && ctx.removeNodes.has(schema)) {
|
|
3535
3597
|
const kv = `_rk${ctx.varCounter++}`
|
|
3536
3598
|
const declared = Object.keys(schema.properties)
|
|
3537
|
-
let loop =
|
|
3599
|
+
let loop = `${ownHead(ctx, v, kv, 'var')}if(${declared.map((k) => `${kv}!==${JSON.stringify(k)}`).join('&&')})delete ${v}[${kv}]`
|
|
3538
3600
|
// When every declared property is required, the verdict has already
|
|
3539
3601
|
// established they are all there, so a key count equal to the declared
|
|
3540
3602
|
// count means nothing is extra and the deleting walk, which compares every
|
|
@@ -3542,7 +3604,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3542
3604
|
const req = new Set(schema.required || [])
|
|
3543
3605
|
if (declared.length <= 15 && declared.every((k) => req.has(k))) {
|
|
3544
3606
|
const n = `_rn${ctx.varCounter++}`
|
|
3545
|
-
loop = `{var ${n}=0
|
|
3607
|
+
loop = `{var ${n}=0;${ownHead(ctx, v, kv, 'var')}${n}++;if(${n}!==${declared.length}){${loop}}}`
|
|
3546
3608
|
}
|
|
3547
3609
|
_deferOrInline(ctx, lines, v, isObj ? loop : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${loop}}`)
|
|
3548
3610
|
} else if (schema.additionalProperties === false && !schema.patternProperties) {
|
|
@@ -3559,7 +3621,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3559
3621
|
const allRequired = !!schema.required && declaredKeys.every((k) => req.has(k))
|
|
3560
3622
|
const inner = allRequired
|
|
3561
3623
|
? (propCount <= 15
|
|
3562
|
-
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount})return false`
|
|
3624
|
+
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount}&&${ownCountOff(ctx, v, propCount, plainFlag(ctx, v))})return false`
|
|
3563
3625
|
: `if(Object.keys(${v}).length!==${propCount})return false`)
|
|
3564
3626
|
: apMembershipCheck(ctx, Object.keys(schema.properties || {}), v)
|
|
3565
3627
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
@@ -3590,8 +3652,8 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3590
3652
|
// propertyNames: false rejects any key at all; true constrains nothing.
|
|
3591
3653
|
if (schema.propertyNames === false) {
|
|
3592
3654
|
lines.push(isObj
|
|
3593
|
-
?
|
|
3594
|
-
: `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){
|
|
3655
|
+
? `${ownHead(ctx, v, `_k`, 'const')}return false`
|
|
3656
|
+
: `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${ownHead(ctx, v, `_k`, 'const')}return false}`)
|
|
3595
3657
|
}
|
|
3596
3658
|
|
|
3597
3659
|
// propertyNames — only emit if not already merged into patternProperties loop
|
|
@@ -3779,8 +3841,8 @@ function genAdditionalSchemaNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
3779
3841
|
if (subLines.length > 0) {
|
|
3780
3842
|
const body = subLines.join(';')
|
|
3781
3843
|
const loop = skipCheck
|
|
3782
|
-
?
|
|
3783
|
-
:
|
|
3844
|
+
? `${ownHead(ctx, v, kVar, 'var')}{if(${skipCheck})continue;const ${avVar}=${v}[${kVar}];${body}}`
|
|
3845
|
+
: `${ownHead(ctx, v, kVar, 'var')}{const ${avVar}=${v}[${kVar}];${body}}`
|
|
3784
3846
|
_deferOrInline(ctx, lines, v, isObj ? loop : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${loop}}`)
|
|
3785
3847
|
}
|
|
3786
3848
|
}
|
|
@@ -3804,10 +3866,10 @@ function genPropertyNamesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
3804
3866
|
const sub = []
|
|
3805
3867
|
genCode(pn, '_pv', sub, ctx)
|
|
3806
3868
|
if (sub.length === 0) return
|
|
3807
|
-
lines.push(`${guard}{const _pnf${ki}=function(_pv){${sub.join(';')};return true}
|
|
3869
|
+
lines.push(`${guard}{const _pnf${ki}=function(_pv){${sub.join(';')};return true};${ownHead(ctx, v, `_k${ki}`, 'const')}{if(!_pnf${ki}(_k${ki}))return false}}`)
|
|
3808
3870
|
return
|
|
3809
3871
|
}
|
|
3810
|
-
lines.push(`${guard}{
|
|
3872
|
+
lines.push(`${guard}{${ownHead(ctx, v, `_k${ki}`, 'const')}{`)
|
|
3811
3873
|
if (pn.minLength !== undefined) lines.push(`if(_k${ki}.length<${pn.minLength})return false`)
|
|
3812
3874
|
if (pn.maxLength !== undefined) lines.push(`if(_k${ki}.length>${pn.maxLength})return false`)
|
|
3813
3875
|
if (pn.pattern) {
|
|
@@ -3878,7 +3940,8 @@ function genAnyOfNode(schema, v, lines, ctx, knownType) {
|
|
|
3878
3940
|
continue
|
|
3879
3941
|
}
|
|
3880
3942
|
const subLines = []
|
|
3881
|
-
genCode(sub, '_av', subLines, ctx)
|
|
3943
|
+
if (canHoist) genIsolated(ctx, () => genCode(sub, '_av', subLines, ctx))
|
|
3944
|
+
else genCode(sub, '_av', subLines, ctx)
|
|
3882
3945
|
const body = subLines.length === 0 ? 'return true' : `${subLines.join(';')};return true`
|
|
3883
3946
|
if (/\b_validate\b/.test(body)) canHoist = false
|
|
3884
3947
|
branchBodies.push(body)
|
|
@@ -3916,6 +3979,35 @@ function branchKey (sub, ctx) {
|
|
|
3916
3979
|
return sub
|
|
3917
3980
|
}
|
|
3918
3981
|
|
|
3982
|
+
// A oneOf whose branches each allow only their own properties, the way
|
|
3983
|
+
// Traefik's middlewares and many tagged unions are written: every branch is
|
|
3984
|
+
// `{ properties: {...}, additionalProperties: false }` (an object `type` and
|
|
3985
|
+
// annotations aside), and no property belongs to two branches. Then an
|
|
3986
|
+
// object passes a branch only if every key it has is that branch's, so its
|
|
3987
|
+
// first key names the one branch that can pass, and an object with no keys
|
|
3988
|
+
// passes all of them. Returns the branch index per property name, or null.
|
|
3989
|
+
const KEYED_BRANCH_KEYS = new Set(['properties', 'additionalProperties', 'type', 'title', 'description', '$comment'])
|
|
3990
|
+
function keyedBranches (branches) {
|
|
3991
|
+
if (branches.length < 4) return null
|
|
3992
|
+
const owner = new Map()
|
|
3993
|
+
for (let i = 0; i < branches.length; i++) {
|
|
3994
|
+
const b = branches[i]
|
|
3995
|
+
if (typeof b !== 'object' || b === null || Array.isArray(b)) return null
|
|
3996
|
+
for (const k of Object.keys(b)) if (!KEYED_BRANCH_KEYS.has(k)) return null
|
|
3997
|
+
if (b.additionalProperties !== false) return null
|
|
3998
|
+
if (b.type !== undefined && b.type !== 'object') return null
|
|
3999
|
+
const props = b.properties
|
|
4000
|
+
if (typeof props !== 'object' || props === null || Array.isArray(props)) return null
|
|
4001
|
+
const names = Object.keys(props)
|
|
4002
|
+
if (names.length === 0) return null
|
|
4003
|
+
for (const n of names) {
|
|
4004
|
+
if (owner.has(n)) return null
|
|
4005
|
+
owner.set(n, i)
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
return owner
|
|
4009
|
+
}
|
|
4010
|
+
|
|
3919
4011
|
function genOneOfNode(schema, v, lines, ctx, knownType) {
|
|
3920
4012
|
const fi = ctx.varCounter++
|
|
3921
4013
|
const branchBodies = []
|
|
@@ -3939,7 +4031,8 @@ function genOneOfNode(schema, v, lines, ctx, knownType) {
|
|
|
3939
4031
|
continue
|
|
3940
4032
|
}
|
|
3941
4033
|
const subLines = []
|
|
3942
|
-
genCode(sub, '_ov', subLines, ctx)
|
|
4034
|
+
if (canHoist) genIsolated(ctx, () => genCode(sub, '_ov', subLines, ctx))
|
|
4035
|
+
else genCode(sub, '_ov', subLines, ctx)
|
|
3943
4036
|
const body = subLines.length === 0 ? 'return true' : `${subLines.join(';')};return true`
|
|
3944
4037
|
// _validate is the recursive entry — hoisting branches above it breaks scope.
|
|
3945
4038
|
if (/\b_validate\b/.test(body)) canHoist = false
|
|
@@ -3955,6 +4048,17 @@ function genOneOfNode(schema, v, lines, ctx, knownType) {
|
|
|
3955
4048
|
return name
|
|
3956
4049
|
})
|
|
3957
4050
|
const calls = names.map(n => `if(${n}(${v})){_oc${fi}++;if(_oc${fi}>1)return false}`).join(';')
|
|
4051
|
+
const owner = keyedBranches(schema.oneOf)
|
|
4052
|
+
if (owner !== null) {
|
|
4053
|
+
// An object goes to the branch its first key names; anything else, and
|
|
4054
|
+
// an object with no keys, takes the loop over every branch.
|
|
4055
|
+
const entries = [...owner].map(([k, i]) => `[${JSON.stringify(k)},${names[i]}]`).join(',')
|
|
4056
|
+
hoist(ctx, `const _ofm${fi}=new Map([${entries}])`)
|
|
4057
|
+
lines.push(`{let _ofk${fi};if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${ownHead(ctx, v, '_q', 'const')}{_ofk${fi}=_q;break}}` +
|
|
4058
|
+
`if(_ofk${fi}!==undefined){const _ob${fi}=_ofm${fi}.get(_ofk${fi});if(_ob${fi}===undefined||!_ob${fi}(${v}))return false}` +
|
|
4059
|
+
`else{let _oc${fi}=0;${calls};if(_oc${fi}!==1)return false}}`)
|
|
4060
|
+
return
|
|
4061
|
+
}
|
|
3958
4062
|
lines.push(`{let _oc${fi}=0;${calls};if(_oc${fi}!==1)return false}`)
|
|
3959
4063
|
} else {
|
|
3960
4064
|
const fns = branchBodies.map(body => `function(_ov){${body}}`)
|
|
@@ -4032,7 +4136,7 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
4032
4136
|
ctx.deferredChecks = null
|
|
4033
4137
|
const outerReuse = ctx.plainReuse
|
|
4034
4138
|
ctx.plainReuse = undefined
|
|
4035
|
-
try { genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx) } finally { ctx.plainReuse = outerReuse }
|
|
4139
|
+
try { genIsolated(ctx, () => genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)) } finally { ctx.plainReuse = outerReuse }
|
|
4036
4140
|
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
4037
4141
|
ctx.deferredChecks = outerDeferred
|
|
4038
4142
|
// The def is hoisted above the entry function, where the recursive
|
|
@@ -4070,7 +4174,7 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
4070
4174
|
const outerReuse = ctx.plainReuse
|
|
4071
4175
|
ctx.plainReuse = undefined
|
|
4072
4176
|
try {
|
|
4073
|
-
genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
|
|
4177
|
+
genIsolated(ctx, () => genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx))
|
|
4074
4178
|
} finally {
|
|
4075
4179
|
ctx.refStack.delete(schema.$ref)
|
|
4076
4180
|
ctx.plainReuse = outerReuse
|
|
@@ -4078,9 +4182,12 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
4078
4182
|
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
4079
4183
|
ctx.deferredChecks = outerDeferred
|
|
4080
4184
|
if (bodyLines.some((l) => /\b_validate\b/.test(l))) throw DECLINE
|
|
4081
|
-
|
|
4185
|
+
const body = bodyLines.join('\n ')
|
|
4186
|
+
hoist(ctx, `function ${fnName}(d){${body}\n return true}`)
|
|
4187
|
+
if (!ctx.sharedDefBody) ctx.sharedDefBody = new Map()
|
|
4188
|
+
ctx.sharedDefBody.set(fnName, body)
|
|
4082
4189
|
}
|
|
4083
|
-
lines.push(`if(!${fnName}(${v}))return false`)
|
|
4190
|
+
lines.push(`if(!${sharedDefCopy(ctx, defName, fnName)}(${v}))return false`)
|
|
4084
4191
|
if (!hasSiblings) return true
|
|
4085
4192
|
} else if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return true }
|
|
4086
4193
|
else {
|
|
@@ -4177,7 +4284,7 @@ function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
4177
4284
|
genCode(apSchema, `_apv${pi}`, apLines, ctx)
|
|
4178
4285
|
apCheck = apLines.join(';')
|
|
4179
4286
|
}
|
|
4180
|
-
lines.push(`${guard}{
|
|
4287
|
+
lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{`)
|
|
4181
4288
|
// propertyNames checks (merged into same loop)
|
|
4182
4289
|
if (pn) {
|
|
4183
4290
|
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength})return false`)
|
|
@@ -4228,7 +4335,7 @@ function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
4228
4335
|
} else {
|
|
4229
4336
|
// No additionalProperties: validate matching keys + propertyNames
|
|
4230
4337
|
handledPropertyNames = !!pn
|
|
4231
|
-
lines.push(`${guard}{
|
|
4338
|
+
lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{`)
|
|
4232
4339
|
// propertyNames checks (merged)
|
|
4233
4340
|
if (pn) {
|
|
4234
4341
|
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength})return false`)
|
|
@@ -4307,7 +4414,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4307
4414
|
if (!earlyKeyCount) {
|
|
4308
4415
|
// Adaptive: for-in for <=15 keys, Object.keys for >15
|
|
4309
4416
|
inner = propCount <= 15
|
|
4310
|
-
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount})return false`
|
|
4417
|
+
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount}&&${ownCountOff(ctx, v, propCount, plainFlag(ctx, v))})return false`
|
|
4311
4418
|
: `if(Object.keys(${v}).length!==${propCount})return false`
|
|
4312
4419
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
4313
4420
|
}
|
|
@@ -4320,10 +4427,10 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4320
4427
|
// document. The hoisted name lookup is the same cure it was there.
|
|
4321
4428
|
inner = propCount >= AP_LOOKUP_MIN
|
|
4322
4429
|
? apMembershipCheck(ctx, knownKeys, v)
|
|
4323
|
-
: genCharCodeSwitch(knownKeys, v)
|
|
4430
|
+
: genCharCodeSwitch(knownKeys, v, ctx)
|
|
4324
4431
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
4325
4432
|
} else {
|
|
4326
|
-
inner = `for(var _k in ${v})return false`
|
|
4433
|
+
inner = `for(var _k in ${v})if(${ownFail(ctx, v)})return false`
|
|
4327
4434
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
4328
4435
|
}
|
|
4329
4436
|
} else if (typeof schema.unevaluatedProperties === 'object') {
|
|
@@ -4341,7 +4448,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4341
4448
|
} else if (knownKeys.length > 0) {
|
|
4342
4449
|
skipKnown = `if(${knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')})continue;`
|
|
4343
4450
|
}
|
|
4344
|
-
const inner =
|
|
4451
|
+
const inner = `${ownHead(ctx, v, ukVar, 'var')}{${skipKnown}${check}}`
|
|
4345
4452
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
4346
4453
|
}
|
|
4347
4454
|
}
|
|
@@ -4386,8 +4493,8 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4386
4493
|
const uniqueThen = [...new Set([...baseProps, ...ifProps, ...(thenEval.props || [])])]
|
|
4387
4494
|
const uniqueElse = [...new Set([...baseProps, ...(elseEval.props || [])])]
|
|
4388
4495
|
|
|
4389
|
-
const thenCheck = genCharCodeSwitch(uniqueThen, v)
|
|
4390
|
-
const elseCheck = genCharCodeSwitch(uniqueElse, v)
|
|
4496
|
+
const thenCheck = genCharCodeSwitch(uniqueThen, v, ctx)
|
|
4497
|
+
const elseCheck = genCharCodeSwitch(uniqueElse, v, ctx)
|
|
4391
4498
|
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
4392
4499
|
lines.push(`${guard}{const _uif${ufi}=${ifFn2};if(_uif${ufi}(${v})){${thenCheck}}else{${elseCheck}}}`)
|
|
4393
4500
|
} else if (branchKeyword) {
|
|
@@ -4458,8 +4565,8 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4458
4565
|
}
|
|
4459
4566
|
const dynamicCheck = `switch(_k.charCodeAt(0)){${switchCases}default:break}`
|
|
4460
4567
|
const inner = staticCheck
|
|
4461
|
-
? `for(var _k in ${v}){if(${staticCheck})continue;${dynamicCheck}return false}`
|
|
4462
|
-
: `for(var _k in ${v}){${dynamicCheck}return false}`
|
|
4568
|
+
? `for(var _k in ${v}){if(${staticCheck})continue;${dynamicCheck}if(${ownFail(ctx, v)})return false}`
|
|
4569
|
+
: `for(var _k in ${v}){${dynamicCheck}if(${ownFail(ctx, v)})return false}`
|
|
4463
4570
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4464
4571
|
} else {
|
|
4465
4572
|
// Fallback: plain object tracking
|
|
@@ -4484,7 +4591,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4484
4591
|
// Single pass: validate anyOf (at least one) + track all matching
|
|
4485
4592
|
lines.push(`let _am${bfi}=false;for(let _bi=0;_bi<_bf${bfi}.length;_bi++){if(_bf${bfi}[_bi](${v})){_am${bfi}=true;for(const _p of _bk${bfi}[_bi])${evVar}[_p]=1}}if(!_am${bfi})return false`)
|
|
4486
4593
|
}
|
|
4487
|
-
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
4594
|
+
const inner = `for(var _k in ${v}){if(!${evVar}[_k]&&${ownFail(ctx, v)})return false}`
|
|
4488
4595
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4489
4596
|
}
|
|
4490
4597
|
} else if (schema.dependentSchemas) {
|
|
@@ -4499,7 +4606,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4499
4606
|
lines.push(`if(${ownKeyExpr(ctx, v, trigger)}){${depResult.props.map(k => `${evVar}[${JSON.stringify(k)}]=1`).join(';')}}`)
|
|
4500
4607
|
}
|
|
4501
4608
|
}
|
|
4502
|
-
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
4609
|
+
const inner = `for(var _k in ${v}){if(!${evVar}[_k]&&${ownFail(ctx, v)})return false}`
|
|
4503
4610
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4504
4611
|
} else {
|
|
4505
4612
|
// General fallback: collect all patternProperties from root + allOf sub-schemas + if
|
|
@@ -4559,10 +4666,10 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4559
4666
|
}
|
|
4560
4667
|
const rootPatCheck = rootReVars.map(rv => `if(${rv}.test(_k))continue;`).join('')
|
|
4561
4668
|
const ifPatCheck = ifReVars.map(rv => `if(${rv}.test(_k))continue;`).join('')
|
|
4562
|
-
const inner = `const _uif${ufi}=${ifFn};if(_uif${ufi}(${v})){for(var _k in ${v}){if(${evVar}[_k])continue;${rootPatCheck}${ifPatCheck}return false}}else{for(var _k in ${v}){if(${evVar}[_k])continue;${rootPatCheck}return false}}`
|
|
4669
|
+
const inner = `const _uif${ufi}=${ifFn};if(_uif${ufi}(${v})){for(var _k in ${v}){if(${evVar}[_k])continue;${rootPatCheck}${ifPatCheck}if(${ownFail(ctx, v)})return false}}else{for(var _k in ${v}){if(${evVar}[_k])continue;${rootPatCheck}if(${ownFail(ctx, v)})return false}}`
|
|
4563
4670
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4564
4671
|
} else {
|
|
4565
|
-
const inner = `for(var _k in ${v}){if(${evVar}[_k])continue;${reVars.map(rv => `if(${rv}.test(_k)){${evVar}[_k]=1;continue}`).join('')}return false}`
|
|
4672
|
+
const inner = `for(var _k in ${v}){if(${evVar}[_k])continue;${reVars.map(rv => `if(${rv}.test(_k)){${evVar}[_k]=1;continue}`).join('')}if(${ownFail(ctx, v)})return false}`
|
|
4566
4673
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4567
4674
|
}
|
|
4568
4675
|
} else {
|
|
@@ -4573,7 +4680,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4573
4680
|
const evVar = `_ev${ei}`
|
|
4574
4681
|
lines.push(`{const ${evVar}={}`)
|
|
4575
4682
|
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
4576
|
-
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
4683
|
+
const inner = `for(var _k in ${v}){if(!${evVar}[_k]&&${ownFail(ctx, v)})return false}`
|
|
4577
4684
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4578
4685
|
}
|
|
4579
4686
|
}
|
|
@@ -4613,7 +4720,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4613
4720
|
genCode(schema.unevaluatedProperties, `${v}[${ukVar}]`, subLines2, ctx)
|
|
4614
4721
|
if (subLines2.length > 0) {
|
|
4615
4722
|
const check = subLines2.join(';')
|
|
4616
|
-
const inner =
|
|
4723
|
+
const inner = `${ownHead(ctx, v, ukVar, 'var')}{if(${evVar}[${ukVar}])continue;${check}}`
|
|
4617
4724
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4618
4725
|
} else {
|
|
4619
4726
|
lines.push('}')
|
|
@@ -5051,13 +5158,57 @@ function fastPrefixCheck(pattern, keyVar) {
|
|
|
5051
5158
|
return `${keyVar}.startsWith(${JSON.stringify(prefix)})`
|
|
5052
5159
|
}
|
|
5053
5160
|
|
|
5161
|
+
// The head of a loop over the own enumerable keys of `v`, written as
|
|
5162
|
+
// `for...in`, the fast way to walk an object, which also visits enumerable
|
|
5163
|
+
// keys its prototype carries. An ordinary object's prototype is
|
|
5164
|
+
// Object.prototype, and its keys are taken as they come; any other object, a
|
|
5165
|
+
// class instance or one made with Object.create, has each key checked as its
|
|
5166
|
+
// own, as the interpreter reads them. One statement, so the body that follows
|
|
5167
|
+
// is written as it was: `for(var _k in v)if(...)continue;else BODY`.
|
|
5168
|
+
// For a loop whose only work is to reject on a key it does not expect: the
|
|
5169
|
+
// key is the object's own, or the object is ordinary, whose prototype's keys
|
|
5170
|
+
// are taken as they come. Read only on the way to `return false`, so an
|
|
5171
|
+
// object that has only expected keys pays nothing.
|
|
5172
|
+
// `_ok` reads the prototype rather than the node's flag: this check can land
|
|
5173
|
+
// in a hoisted branch function the flag is not declared in.
|
|
5174
|
+
function ownFail (ctx, v, k = '_k') {
|
|
5175
|
+
useOwnHelper(ctx, '_ok')
|
|
5176
|
+
return `_ok(${v},${k})`
|
|
5177
|
+
}
|
|
5178
|
+
// For a key count that came out wrong: whether it is wrong counting own keys
|
|
5179
|
+
// only. An ordinary object's count stands; any other object is counted again.
|
|
5180
|
+
// A oneOf of closed objects fails its other branches here on every value, so
|
|
5181
|
+
// this is not only a cold path.
|
|
5182
|
+
function ownCountOff (ctx, v, n, pk) {
|
|
5183
|
+
// A call to a shared helper rather than the test written in place: a oneOf
|
|
5184
|
+
// branch this small is inlined into its caller by V8, and the longer
|
|
5185
|
+
// bytecode cost that, 8% on a document of patch operations. The node's
|
|
5186
|
+
// ordinary-object flag is not used: this can land in a hoisted branch
|
|
5187
|
+
// function it is not declared in.
|
|
5188
|
+
useOwnHelper(ctx, '_nk')
|
|
5189
|
+
// `pk`: the node's own ordinary-object flag, where the caller holds it in
|
|
5190
|
+
// the same function as this check. An ordinary object needs no recount.
|
|
5191
|
+
return pk ? `(${pk}||_nk(${v},${n}))` : `_nk(${v},${n})`
|
|
5192
|
+
}
|
|
5193
|
+
|
|
5194
|
+
function ownHead (ctx, v, k = '_k', kind = 'var') {
|
|
5195
|
+
useOwnHelper(ctx, '_hop')
|
|
5196
|
+
// Read here rather than taken from the node's flag: generated bodies that
|
|
5197
|
+
// become functions of their own (branches, annotations, definitions) take
|
|
5198
|
+
// the same parameter names and do not see the flag. Written in place, not
|
|
5199
|
+
// through a shared helper, which would see every object shape of the
|
|
5200
|
+
// document and read the prototype megamorphically: 7% on validate().
|
|
5201
|
+
const i = ctx.varCounter++
|
|
5202
|
+
return `var _pl${i}=${v}!==null&&typeof ${v}==='object'&&${protoIs(ctx, v)};for(${kind} ${k} in ${v})if(!_pl${i}&&!_hop.call(${v},${k}))continue;else `
|
|
5203
|
+
}
|
|
5204
|
+
|
|
5054
5205
|
// Generate a charCodeAt(0)-based switch tree for fast key validation.
|
|
5055
5206
|
// V8 compiles switch to jump tables — O(1) dispatch vs O(n) chain.
|
|
5056
|
-
function genCharCodeSwitch(keys, v) {
|
|
5057
|
-
if (keys.length === 0) return `for(var _k in ${v})return false`
|
|
5207
|
+
function genCharCodeSwitch(keys, v, ctx) {
|
|
5208
|
+
if (keys.length === 0) return `for(var _k in ${v})if(${ownFail(ctx, v)})return false`
|
|
5058
5209
|
if (keys.length <= 3) {
|
|
5059
5210
|
// Small set: simple chain is faster than switch overhead
|
|
5060
|
-
return `for(var _k in ${v})if(${keys.map(k => `_k!==${JSON.stringify(k)}`).join('&&')})return false`
|
|
5211
|
+
return `for(var _k in ${v})if(${keys.map(k => `_k!==${JSON.stringify(k)}`).join('&&')}&&${ownFail(ctx, v)})return false`
|
|
5061
5212
|
}
|
|
5062
5213
|
|
|
5063
5214
|
// Group keys by first charCode
|
|
@@ -5074,7 +5225,7 @@ function genCharCodeSwitch(keys, v) {
|
|
|
5074
5225
|
cases += `case ${cc}:if(${cond})continue;break;`
|
|
5075
5226
|
}
|
|
5076
5227
|
|
|
5077
|
-
return `for(var _k in ${v}){switch(_k.charCodeAt(0)){${cases}default:break}return false}`
|
|
5228
|
+
return `for(var _k in ${v}){switch(_k.charCodeAt(0)){${cases}default:break}if(${ownFail(ctx, v)})return false}`
|
|
5078
5229
|
}
|
|
5079
5230
|
|
|
5080
5231
|
// The verdict form of genUnevaluatedDynamicC: the same set, built the same
|
|
@@ -5120,7 +5271,7 @@ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj, nonObjApplicators
|
|
|
5120
5271
|
// additionalProperties is there.
|
|
5121
5272
|
function addLocalAnnotations(node, v, S, out, ctx) {
|
|
5122
5273
|
if (node.additionalProperties !== undefined) {
|
|
5123
|
-
out.push(
|
|
5274
|
+
out.push(`${ownHead(ctx, v, `_ka`, 'const')}${S}.push(_ka)`)
|
|
5124
5275
|
return
|
|
5125
5276
|
}
|
|
5126
5277
|
for (const k of Object.keys(node.properties || {})) out.push(`if(Object.prototype.hasOwnProperty.call(${v},${JSON.stringify(k)}))${S}.push(${JSON.stringify(k)})`)
|
|
@@ -5136,7 +5287,7 @@ function addLocalAnnotations(node, v, S, out, ctx) {
|
|
|
5136
5287
|
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
5137
5288
|
tests.push(`_re${ri}.test(${kv})`)
|
|
5138
5289
|
}
|
|
5139
|
-
out.push(
|
|
5290
|
+
out.push(`${ownHead(ctx, v, kv, 'const')}if(${tests.join('||')})${S}.push(${kv})`)
|
|
5140
5291
|
}
|
|
5141
5292
|
}
|
|
5142
5293
|
|
|
@@ -5419,4 +5570,4 @@ Object.defineProperty(module.exports, '_expandDynamicScopes', { value: expandDyn
|
|
|
5419
5570
|
// For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
|
|
5420
5571
|
Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })
|
|
5421
5572
|
|
|
5422
|
-
module.exports._internal = { hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, enumOperand, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun }
|
|
5573
|
+
module.exports._internal = { ownHead, hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, enumOperand, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun }
|
package/lib/keywords.js
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
// { macro(value, parent) -> schema } the returned schema is applied in
|
|
11
11
|
// place, and the keyword reports an
|
|
12
12
|
// error of its own when it fails
|
|
13
|
+
// with optional `dataContext: true`: the per-value function is also given
|
|
14
|
+
// `{ instancePath, parentData, parentDataProperty, rootData }`, as the default
|
|
15
|
+
// validator's keywords are, so it can read or change the value where it sits
|
|
16
|
+
// (validate(value, data, parent, ctx), or the compiled fn(data, ctx)). Such a
|
|
17
|
+
// schema runs on the interpreted engine's collecting walk, which knows where
|
|
18
|
+
// every value is.
|
|
13
19
|
// with optional `type`: a JSON Schema type name or a list of them. A typed
|
|
14
20
|
// keyword is skipped for data of any other type, which is what makes
|
|
15
21
|
// `{ type: 'number', ... }` safe next to `type: ['number', 'string']`.
|
|
@@ -72,6 +78,12 @@ function normalizeKeywords(defs) {
|
|
|
72
78
|
validate: hasValidate ? def.validate : null,
|
|
73
79
|
compile: hasCompile ? def.compile : null,
|
|
74
80
|
macro: hasMacro ? def.macro : null,
|
|
81
|
+
dataContext: def.dataContext === true,
|
|
82
|
+
// Order among the custom keywords of one node, as the default
|
|
83
|
+
// validator's keywords declare it: `before` names a keyword this one
|
|
84
|
+
// runs ahead of, `post` runs it after the others.
|
|
85
|
+
before: typeof def.before === 'string' ? def.before : null,
|
|
86
|
+
post: def.post === true,
|
|
75
87
|
};
|
|
76
88
|
}
|
|
77
89
|
return out;
|
|
@@ -110,4 +122,15 @@ const NAME_MAPS = new Set([
|
|
|
110
122
|
'dependentSchemas', 'dependentRequired', 'dependencies', 'propertyDependencies',
|
|
111
123
|
]);
|
|
112
124
|
|
|
113
|
-
|
|
125
|
+
// Does the schema use a registered keyword that asks for its data's context?
|
|
126
|
+
function schemaUsesDataContext(schema, keywords) {
|
|
127
|
+
if (!keywords) return false;
|
|
128
|
+
let any = false;
|
|
129
|
+
for (const name of Object.keys(keywords)) if (keywords[name].dataContext) { any = true; break; }
|
|
130
|
+
if (!any) return false;
|
|
131
|
+
const only = Object.create(null);
|
|
132
|
+
for (const name of Object.keys(keywords)) if (keywords[name].dataContext) only[name] = keywords[name];
|
|
133
|
+
return schemaUsesKeywords(schema, only);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { normalizeKeywords, schemaUsesKeywords, schemaUsesDataContext };
|
package/lib/levenshtein.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Bounded
|
|
3
|
+
// Bounded edit distance. Returns Infinity if distance > maxDistance (or, past
|
|
4
4
|
// the bound, possibly some number greater than it; callers compare).
|
|
5
5
|
//
|
|
6
|
+
// Two adjacent characters swapped count as one edit, not two (the optimal
|
|
7
|
+
// string alignment distance): `nmae` for `name` is the commonest typo there
|
|
8
|
+
// is, and at two edits a four-letter name's budget of one turned it away.
|
|
9
|
+
//
|
|
6
10
|
// Only the cells within the bound of the diagonal are computed: a path
|
|
7
11
|
// through any other cell already costs more than the bound. A typo hint asks
|
|
8
12
|
// with a bound of one or two and compares the missing name with every key
|
|
9
13
|
// present, so a row is five cells instead of the whole key. Without a bound
|
|
10
14
|
// the band is the whole row.
|
|
11
15
|
//
|
|
12
|
-
//
|
|
16
|
+
// Three rows of scratch, reused across calls. The function is not recursive
|
|
13
17
|
// and the library is single-threaded, so nothing else is using them; growing
|
|
14
18
|
// them once beats allocating two arrays on every comparison, which the
|
|
15
19
|
// reject-path profile showed as most of this function's cost.
|
|
16
20
|
let scratchA = new Int32Array(64);
|
|
17
21
|
let scratchB = new Int32Array(64);
|
|
22
|
+
let scratchC = new Int32Array(64);
|
|
18
23
|
|
|
19
24
|
function levenshtein (a, b, maxDistance) {
|
|
20
25
|
if (a === b) return 0;
|
|
@@ -28,7 +33,11 @@ function levenshtein (a, b, maxDistance) {
|
|
|
28
33
|
if (scratchA.length < m + 2) {
|
|
29
34
|
scratchA = new Int32Array(m + 2);
|
|
30
35
|
scratchB = new Int32Array(m + 2);
|
|
36
|
+
scratchC = new Int32Array(m + 2);
|
|
31
37
|
}
|
|
38
|
+
// The row before the previous one, read for a swap: within the band of
|
|
39
|
+
// row i the cell two back on the diagonal is inside the band of row i - 2.
|
|
40
|
+
let prev2 = scratchC;
|
|
32
41
|
let prev = scratchA;
|
|
33
42
|
let curr = scratchB;
|
|
34
43
|
for (let j = 0; j <= m; j++) prev[j] = j <= max ? j : big;
|
|
@@ -40,20 +49,27 @@ function levenshtein (a, b, maxDistance) {
|
|
|
40
49
|
// charCodeAt rather than indexing: reading a character by index allocates
|
|
41
50
|
// a one-character string, and this is the innermost loop.
|
|
42
51
|
const ca = a.charCodeAt(i - 1);
|
|
52
|
+
const pa = i > 1 ? a.charCodeAt(i - 2) : -1;
|
|
43
53
|
for (let j = lo; j <= hi; j++) {
|
|
44
|
-
|
|
54
|
+
const cb = b.charCodeAt(j - 1);
|
|
55
|
+
let v = prev[j - 1] + (ca === cb ? 0 : 1);
|
|
45
56
|
const del = curr[j - 1] + 1;
|
|
46
57
|
if (del < v) v = del;
|
|
47
58
|
const ins = prev[j] + 1;
|
|
48
59
|
if (ins < v) v = ins;
|
|
60
|
+
if (j > 1 && ca !== cb && ca === b.charCodeAt(j - 2) && pa === cb) {
|
|
61
|
+
const swap = prev2[j - 2] + 1;
|
|
62
|
+
if (swap < v) v = swap;
|
|
63
|
+
}
|
|
49
64
|
if (v > big) v = big;
|
|
50
65
|
curr[j] = v;
|
|
51
66
|
if (v < rowMin) rowMin = v;
|
|
52
67
|
}
|
|
53
68
|
if (hi < m) curr[hi + 1] = big;
|
|
54
69
|
if (rowMin > max) return Infinity;
|
|
55
|
-
// A destructured swap builds an array per row;
|
|
56
|
-
const t =
|
|
70
|
+
// A destructured swap builds an array per row; temporaries do not.
|
|
71
|
+
const t = prev2;
|
|
72
|
+
prev2 = prev;
|
|
57
73
|
prev = curr;
|
|
58
74
|
curr = t;
|
|
59
75
|
}
|