ata-validator 1.50.0 → 1.52.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/aot.d.ts +11 -1
- package/aot.mjs +1 -1
- package/bin/ata.js +21 -0
- package/index.bundle.mjs +15 -0
- package/lib/aot-build.js +183 -9
- package/lib/aot-impl.js +49 -16
- package/lib/combined-runtime.js +4 -1
- package/lib/error-runtime.js +5 -1
- package/lib/js-compiler-errors.js +15 -15
- package/lib/js-compiler.js +193 -44
- package/lib/safe-regex.js +33 -14
- package/lib/schema-order.js +64 -2
- package/lib/schema-runtime-entry.js +25 -1
- package/lib/validator-core.js +3 -0
- 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) {
|
|
@@ -907,8 +907,9 @@ const OWN_HELPER_CODE = {
|
|
|
907
907
|
_h: 'function _h(o,k){return _hop.call(o,k)}',
|
|
908
908
|
_ok: 'function _ok(o,k){return (_PN?o.__proto__:_gp(o))===_OP||_hop.call(o,k)}',
|
|
909
909
|
_hall: 'function _hall(o,ks){for(let i=0;i<ks.length;i++)if(!_hop.call(o,ks[i]))return false;return true}',
|
|
910
|
+
_nk: 'function _nk(o,n){return (_PN?o.__proto__:_gp(o))===_OP||Object.keys(o).length!==n}',
|
|
910
911
|
}
|
|
911
|
-
const OWN_HELPER_DEPS = { _h: ['_hop'], _ok: ['_PN', '_hop'], _hall: ['_hop'] }
|
|
912
|
+
const OWN_HELPER_DEPS = { _h: ['_hop'], _ok: ['_PN', '_hop'], _hall: ['_hop'], _nk: ['_PN'] }
|
|
912
913
|
function useOwnHelper (ctx, name) {
|
|
913
914
|
for (const dep of OWN_HELPER_DEPS[name] || []) useOwnHelper(ctx, dep)
|
|
914
915
|
hoistOnce(ctx, '_own' + name, OWN_HELPER_CODE[name])
|
|
@@ -1208,6 +1209,17 @@ function propFnCandidate (prop, ctx) {
|
|
|
1208
1209
|
// inline after all": an empty body (the inline path then asserts it the
|
|
1209
1210
|
// usual way) or one that calls the recursive entry, which is out of scope
|
|
1210
1211
|
// in the preamble.
|
|
1212
|
+
// Generates a body that will become a function of its own in the preamble.
|
|
1213
|
+
// The ordinary-object flags withPlain declared belong to the function being
|
|
1214
|
+
// written now and are out of scope there; a hoisted body takes the same
|
|
1215
|
+
// parameter names (`d`, `_av`, `_ov`) and would otherwise read a flag the
|
|
1216
|
+
// enclosing node declared for a value of that name.
|
|
1217
|
+
function genIsolated (ctx, run) {
|
|
1218
|
+
const outerPlain = ctx.plainOf
|
|
1219
|
+
ctx.plainOf = undefined
|
|
1220
|
+
try { return run() } finally { ctx.plainOf = outerPlain }
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1211
1223
|
function emitPropFn (prop, ctx) {
|
|
1212
1224
|
const bodyLines = []
|
|
1213
1225
|
const outerDeferred = ctx.deferredChecks
|
|
@@ -1216,7 +1228,7 @@ function emitPropFn (prop, ctx) {
|
|
|
1216
1228
|
ctx.plainReuse = undefined
|
|
1217
1229
|
const outerBytes = ctx.fnBytes
|
|
1218
1230
|
ctx.fnBytes = 0
|
|
1219
|
-
try { genCode(prop, 'd', bodyLines, ctx) } finally { ctx.plainReuse = outerReuse; ctx.fnBytes = outerBytes }
|
|
1231
|
+
try { genIsolated(ctx, () => genCode(prop, 'd', bodyLines, ctx)) } finally { ctx.plainReuse = outerReuse; ctx.fnBytes = outerBytes }
|
|
1220
1232
|
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
1221
1233
|
ctx.deferredChecks = outerDeferred
|
|
1222
1234
|
if (bodyLines.length === 0 || bodyLines.some((l) => /\b_validate\b/.test(l))) return null
|
|
@@ -1341,6 +1353,54 @@ function sharedDefNames (root, defs) {
|
|
|
1341
1353
|
// known trade: on that shape the shared function costs 27 ns a call.
|
|
1342
1354
|
const SHARED_DEF_MIN_CHARS = 120
|
|
1343
1355
|
|
|
1356
|
+
// A small definition applied at many places is called with objects of many
|
|
1357
|
+
// shapes, one or two per place, and V8 gives a function's property reads up
|
|
1358
|
+
// to four shapes before it falls back to a generic lookup several times
|
|
1359
|
+
// slower: SchemaStore's detekt sends 72 shapes through one four-property rule
|
|
1360
|
+
// definition. Such a definition is emitted as several functions with the same
|
|
1361
|
+
// body, each serving at most SITES_PER_COPY references, so every copy stays
|
|
1362
|
+
// within four shapes. Only small bodies are copied, so the source grows by a
|
|
1363
|
+
// bounded amount; a large or rarely referenced definition keeps one function.
|
|
1364
|
+
const SITES_PER_COPY = 4
|
|
1365
|
+
const COPY_MIN_SITES = 16
|
|
1366
|
+
const COPY_MAX_BODY = 1500
|
|
1367
|
+
function sharedDefCopy (ctx, defName, fnName) {
|
|
1368
|
+
const body = ctx.sharedDefBody && ctx.sharedDefBody.get(fnName)
|
|
1369
|
+
if (body === undefined || body.length > COPY_MAX_BODY) return fnName
|
|
1370
|
+
// Only a definition that reads properties gains: a string, a number or a
|
|
1371
|
+
// wrapper around another reference has no shapes to keep apart, and more
|
|
1372
|
+
// copies only mean more functions to warm up.
|
|
1373
|
+
const def = ctx.rootDefs && ctx.rootDefs[defName]
|
|
1374
|
+
if (def === null || typeof def !== 'object' || typeof def.properties !== 'object' || def.properties === null) return fnName
|
|
1375
|
+
if (!ctx.sharedDefSites) ctx.sharedDefSites = new Map()
|
|
1376
|
+
if (ctx.sharedDefSiteCounts === undefined) ctx.sharedDefSiteCounts = refCounts(ctx.rootSchema)
|
|
1377
|
+
if ((ctx.sharedDefSiteCounts.get(defName) || 0) < COPY_MIN_SITES) return fnName
|
|
1378
|
+
const site = ctx.sharedDefSites.get(fnName) || 0
|
|
1379
|
+
ctx.sharedDefSites.set(fnName, site + 1)
|
|
1380
|
+
const copy = Math.floor(site / SITES_PER_COPY)
|
|
1381
|
+
if (copy === 0) return fnName
|
|
1382
|
+
const name = `${fnName}_c${copy}`
|
|
1383
|
+
if (site % SITES_PER_COPY === 0) hoist(ctx, `function ${name}(d){${body}\n return true}`)
|
|
1384
|
+
return name
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// How many `$ref`s name each local definition, by name.
|
|
1388
|
+
function refCounts (root) {
|
|
1389
|
+
const counts = new Map()
|
|
1390
|
+
const seen = new WeakSet()
|
|
1391
|
+
const walk = (node) => {
|
|
1392
|
+
if (node === null || typeof node !== 'object' || seen.has(node)) return
|
|
1393
|
+
seen.add(node)
|
|
1394
|
+
if (typeof node.$ref === 'string') {
|
|
1395
|
+
const m = /^#\/(?:\$defs|definitions)\/([^/]+)$/.exec(node.$ref)
|
|
1396
|
+
if (m) counts.set(m[1], (counts.get(m[1]) || 0) + 1)
|
|
1397
|
+
}
|
|
1398
|
+
for (const v of Object.values(node)) walk(v)
|
|
1399
|
+
}
|
|
1400
|
+
walk(root)
|
|
1401
|
+
return counts
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1344
1404
|
function cyclicDefNames (defs) {
|
|
1345
1405
|
const out = new Set()
|
|
1346
1406
|
if (!defs || typeof defs !== 'object') return out
|
|
@@ -3419,7 +3479,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3419
3479
|
if (allRequired && propCount > 0) {
|
|
3420
3480
|
// Adaptive: for-in for <=15 keys (V8 fast path), Object.keys for >15
|
|
3421
3481
|
if (propCount <= 15) {
|
|
3422
|
-
lines.push(`var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount})return false`)
|
|
3482
|
+
lines.push(`var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount}&&${ownCountOff(ctx, v, propCount, plainFlag(ctx, v))})return false`)
|
|
3423
3483
|
} else {
|
|
3424
3484
|
lines.push(`if(Object.keys(${v}).length!==${propCount})return false`)
|
|
3425
3485
|
}
|
|
@@ -3534,7 +3594,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3534
3594
|
if (schema.additionalProperties === false && schema.properties && !schema.patternProperties && ctx.removeNodes && ctx.removeNodes.has(schema)) {
|
|
3535
3595
|
const kv = `_rk${ctx.varCounter++}`
|
|
3536
3596
|
const declared = Object.keys(schema.properties)
|
|
3537
|
-
let loop =
|
|
3597
|
+
let loop = `${ownHead(ctx, v, kv, 'var')}if(${declared.map((k) => `${kv}!==${JSON.stringify(k)}`).join('&&')})delete ${v}[${kv}]`
|
|
3538
3598
|
// When every declared property is required, the verdict has already
|
|
3539
3599
|
// established they are all there, so a key count equal to the declared
|
|
3540
3600
|
// count means nothing is extra and the deleting walk, which compares every
|
|
@@ -3542,7 +3602,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3542
3602
|
const req = new Set(schema.required || [])
|
|
3543
3603
|
if (declared.length <= 15 && declared.every((k) => req.has(k))) {
|
|
3544
3604
|
const n = `_rn${ctx.varCounter++}`
|
|
3545
|
-
loop = `{var ${n}=0
|
|
3605
|
+
loop = `{var ${n}=0;${ownHead(ctx, v, kv, 'var')}${n}++;if(${n}!==${declared.length}){${loop}}}`
|
|
3546
3606
|
}
|
|
3547
3607
|
_deferOrInline(ctx, lines, v, isObj ? loop : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${loop}}`)
|
|
3548
3608
|
} else if (schema.additionalProperties === false && !schema.patternProperties) {
|
|
@@ -3559,7 +3619,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3559
3619
|
const allRequired = !!schema.required && declaredKeys.every((k) => req.has(k))
|
|
3560
3620
|
const inner = allRequired
|
|
3561
3621
|
? (propCount <= 15
|
|
3562
|
-
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount})return false`
|
|
3622
|
+
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount}&&${ownCountOff(ctx, v, propCount, plainFlag(ctx, v))})return false`
|
|
3563
3623
|
: `if(Object.keys(${v}).length!==${propCount})return false`)
|
|
3564
3624
|
: apMembershipCheck(ctx, Object.keys(schema.properties || {}), v)
|
|
3565
3625
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
@@ -3590,8 +3650,8 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3590
3650
|
// propertyNames: false rejects any key at all; true constrains nothing.
|
|
3591
3651
|
if (schema.propertyNames === false) {
|
|
3592
3652
|
lines.push(isObj
|
|
3593
|
-
?
|
|
3594
|
-
: `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){
|
|
3653
|
+
? `${ownHead(ctx, v, `_k`, 'const')}return false`
|
|
3654
|
+
: `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${ownHead(ctx, v, `_k`, 'const')}return false}`)
|
|
3595
3655
|
}
|
|
3596
3656
|
|
|
3597
3657
|
// propertyNames — only emit if not already merged into patternProperties loop
|
|
@@ -3779,8 +3839,8 @@ function genAdditionalSchemaNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
3779
3839
|
if (subLines.length > 0) {
|
|
3780
3840
|
const body = subLines.join(';')
|
|
3781
3841
|
const loop = skipCheck
|
|
3782
|
-
?
|
|
3783
|
-
:
|
|
3842
|
+
? `${ownHead(ctx, v, kVar, 'var')}{if(${skipCheck})continue;const ${avVar}=${v}[${kVar}];${body}}`
|
|
3843
|
+
: `${ownHead(ctx, v, kVar, 'var')}{const ${avVar}=${v}[${kVar}];${body}}`
|
|
3784
3844
|
_deferOrInline(ctx, lines, v, isObj ? loop : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${loop}}`)
|
|
3785
3845
|
}
|
|
3786
3846
|
}
|
|
@@ -3804,10 +3864,10 @@ function genPropertyNamesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
3804
3864
|
const sub = []
|
|
3805
3865
|
genCode(pn, '_pv', sub, ctx)
|
|
3806
3866
|
if (sub.length === 0) return
|
|
3807
|
-
lines.push(`${guard}{const _pnf${ki}=function(_pv){${sub.join(';')};return true}
|
|
3867
|
+
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
3868
|
return
|
|
3809
3869
|
}
|
|
3810
|
-
lines.push(`${guard}{
|
|
3870
|
+
lines.push(`${guard}{${ownHead(ctx, v, `_k${ki}`, 'const')}{`)
|
|
3811
3871
|
if (pn.minLength !== undefined) lines.push(`if(_k${ki}.length<${pn.minLength})return false`)
|
|
3812
3872
|
if (pn.maxLength !== undefined) lines.push(`if(_k${ki}.length>${pn.maxLength})return false`)
|
|
3813
3873
|
if (pn.pattern) {
|
|
@@ -3878,7 +3938,8 @@ function genAnyOfNode(schema, v, lines, ctx, knownType) {
|
|
|
3878
3938
|
continue
|
|
3879
3939
|
}
|
|
3880
3940
|
const subLines = []
|
|
3881
|
-
genCode(sub, '_av', subLines, ctx)
|
|
3941
|
+
if (canHoist) genIsolated(ctx, () => genCode(sub, '_av', subLines, ctx))
|
|
3942
|
+
else genCode(sub, '_av', subLines, ctx)
|
|
3882
3943
|
const body = subLines.length === 0 ? 'return true' : `${subLines.join(';')};return true`
|
|
3883
3944
|
if (/\b_validate\b/.test(body)) canHoist = false
|
|
3884
3945
|
branchBodies.push(body)
|
|
@@ -3916,6 +3977,35 @@ function branchKey (sub, ctx) {
|
|
|
3916
3977
|
return sub
|
|
3917
3978
|
}
|
|
3918
3979
|
|
|
3980
|
+
// A oneOf whose branches each allow only their own properties, the way
|
|
3981
|
+
// Traefik's middlewares and many tagged unions are written: every branch is
|
|
3982
|
+
// `{ properties: {...}, additionalProperties: false }` (an object `type` and
|
|
3983
|
+
// annotations aside), and no property belongs to two branches. Then an
|
|
3984
|
+
// object passes a branch only if every key it has is that branch's, so its
|
|
3985
|
+
// first key names the one branch that can pass, and an object with no keys
|
|
3986
|
+
// passes all of them. Returns the branch index per property name, or null.
|
|
3987
|
+
const KEYED_BRANCH_KEYS = new Set(['properties', 'additionalProperties', 'type', 'title', 'description', '$comment'])
|
|
3988
|
+
function keyedBranches (branches) {
|
|
3989
|
+
if (branches.length < 4) return null
|
|
3990
|
+
const owner = new Map()
|
|
3991
|
+
for (let i = 0; i < branches.length; i++) {
|
|
3992
|
+
const b = branches[i]
|
|
3993
|
+
if (typeof b !== 'object' || b === null || Array.isArray(b)) return null
|
|
3994
|
+
for (const k of Object.keys(b)) if (!KEYED_BRANCH_KEYS.has(k)) return null
|
|
3995
|
+
if (b.additionalProperties !== false) return null
|
|
3996
|
+
if (b.type !== undefined && b.type !== 'object') return null
|
|
3997
|
+
const props = b.properties
|
|
3998
|
+
if (typeof props !== 'object' || props === null || Array.isArray(props)) return null
|
|
3999
|
+
const names = Object.keys(props)
|
|
4000
|
+
if (names.length === 0) return null
|
|
4001
|
+
for (const n of names) {
|
|
4002
|
+
if (owner.has(n)) return null
|
|
4003
|
+
owner.set(n, i)
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
return owner
|
|
4007
|
+
}
|
|
4008
|
+
|
|
3919
4009
|
function genOneOfNode(schema, v, lines, ctx, knownType) {
|
|
3920
4010
|
const fi = ctx.varCounter++
|
|
3921
4011
|
const branchBodies = []
|
|
@@ -3939,7 +4029,8 @@ function genOneOfNode(schema, v, lines, ctx, knownType) {
|
|
|
3939
4029
|
continue
|
|
3940
4030
|
}
|
|
3941
4031
|
const subLines = []
|
|
3942
|
-
genCode(sub, '_ov', subLines, ctx)
|
|
4032
|
+
if (canHoist) genIsolated(ctx, () => genCode(sub, '_ov', subLines, ctx))
|
|
4033
|
+
else genCode(sub, '_ov', subLines, ctx)
|
|
3943
4034
|
const body = subLines.length === 0 ? 'return true' : `${subLines.join(';')};return true`
|
|
3944
4035
|
// _validate is the recursive entry — hoisting branches above it breaks scope.
|
|
3945
4036
|
if (/\b_validate\b/.test(body)) canHoist = false
|
|
@@ -3955,6 +4046,17 @@ function genOneOfNode(schema, v, lines, ctx, knownType) {
|
|
|
3955
4046
|
return name
|
|
3956
4047
|
})
|
|
3957
4048
|
const calls = names.map(n => `if(${n}(${v})){_oc${fi}++;if(_oc${fi}>1)return false}`).join(';')
|
|
4049
|
+
const owner = keyedBranches(schema.oneOf)
|
|
4050
|
+
if (owner !== null) {
|
|
4051
|
+
// An object goes to the branch its first key names; anything else, and
|
|
4052
|
+
// an object with no keys, takes the loop over every branch.
|
|
4053
|
+
const entries = [...owner].map(([k, i]) => `[${JSON.stringify(k)},${names[i]}]`).join(',')
|
|
4054
|
+
hoist(ctx, `const _ofm${fi}=new Map([${entries}])`)
|
|
4055
|
+
lines.push(`{let _ofk${fi};if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${ownHead(ctx, v, '_q', 'const')}{_ofk${fi}=_q;break}}` +
|
|
4056
|
+
`if(_ofk${fi}!==undefined){const _ob${fi}=_ofm${fi}.get(_ofk${fi});if(_ob${fi}===undefined||!_ob${fi}(${v}))return false}` +
|
|
4057
|
+
`else{let _oc${fi}=0;${calls};if(_oc${fi}!==1)return false}}`)
|
|
4058
|
+
return
|
|
4059
|
+
}
|
|
3958
4060
|
lines.push(`{let _oc${fi}=0;${calls};if(_oc${fi}!==1)return false}`)
|
|
3959
4061
|
} else {
|
|
3960
4062
|
const fns = branchBodies.map(body => `function(_ov){${body}}`)
|
|
@@ -4032,7 +4134,7 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
4032
4134
|
ctx.deferredChecks = null
|
|
4033
4135
|
const outerReuse = ctx.plainReuse
|
|
4034
4136
|
ctx.plainReuse = undefined
|
|
4035
|
-
try { genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx) } finally { ctx.plainReuse = outerReuse }
|
|
4137
|
+
try { genIsolated(ctx, () => genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)) } finally { ctx.plainReuse = outerReuse }
|
|
4036
4138
|
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
4037
4139
|
ctx.deferredChecks = outerDeferred
|
|
4038
4140
|
// The def is hoisted above the entry function, where the recursive
|
|
@@ -4070,7 +4172,7 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
4070
4172
|
const outerReuse = ctx.plainReuse
|
|
4071
4173
|
ctx.plainReuse = undefined
|
|
4072
4174
|
try {
|
|
4073
|
-
genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
|
|
4175
|
+
genIsolated(ctx, () => genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx))
|
|
4074
4176
|
} finally {
|
|
4075
4177
|
ctx.refStack.delete(schema.$ref)
|
|
4076
4178
|
ctx.plainReuse = outerReuse
|
|
@@ -4078,9 +4180,12 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
4078
4180
|
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
4079
4181
|
ctx.deferredChecks = outerDeferred
|
|
4080
4182
|
if (bodyLines.some((l) => /\b_validate\b/.test(l))) throw DECLINE
|
|
4081
|
-
|
|
4183
|
+
const body = bodyLines.join('\n ')
|
|
4184
|
+
hoist(ctx, `function ${fnName}(d){${body}\n return true}`)
|
|
4185
|
+
if (!ctx.sharedDefBody) ctx.sharedDefBody = new Map()
|
|
4186
|
+
ctx.sharedDefBody.set(fnName, body)
|
|
4082
4187
|
}
|
|
4083
|
-
lines.push(`if(!${fnName}(${v}))return false`)
|
|
4188
|
+
lines.push(`if(!${sharedDefCopy(ctx, defName, fnName)}(${v}))return false`)
|
|
4084
4189
|
if (!hasSiblings) return true
|
|
4085
4190
|
} else if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return true }
|
|
4086
4191
|
else {
|
|
@@ -4177,7 +4282,7 @@ function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
4177
4282
|
genCode(apSchema, `_apv${pi}`, apLines, ctx)
|
|
4178
4283
|
apCheck = apLines.join(';')
|
|
4179
4284
|
}
|
|
4180
|
-
lines.push(`${guard}{
|
|
4285
|
+
lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{`)
|
|
4181
4286
|
// propertyNames checks (merged into same loop)
|
|
4182
4287
|
if (pn) {
|
|
4183
4288
|
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength})return false`)
|
|
@@ -4228,7 +4333,7 @@ function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
4228
4333
|
} else {
|
|
4229
4334
|
// No additionalProperties: validate matching keys + propertyNames
|
|
4230
4335
|
handledPropertyNames = !!pn
|
|
4231
|
-
lines.push(`${guard}{
|
|
4336
|
+
lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{`)
|
|
4232
4337
|
// propertyNames checks (merged)
|
|
4233
4338
|
if (pn) {
|
|
4234
4339
|
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength})return false`)
|
|
@@ -4307,7 +4412,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4307
4412
|
if (!earlyKeyCount) {
|
|
4308
4413
|
// Adaptive: for-in for <=15 keys, Object.keys for >15
|
|
4309
4414
|
inner = propCount <= 15
|
|
4310
|
-
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount})return false`
|
|
4415
|
+
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount}&&${ownCountOff(ctx, v, propCount, plainFlag(ctx, v))})return false`
|
|
4311
4416
|
: `if(Object.keys(${v}).length!==${propCount})return false`
|
|
4312
4417
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
4313
4418
|
}
|
|
@@ -4320,10 +4425,10 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4320
4425
|
// document. The hoisted name lookup is the same cure it was there.
|
|
4321
4426
|
inner = propCount >= AP_LOOKUP_MIN
|
|
4322
4427
|
? apMembershipCheck(ctx, knownKeys, v)
|
|
4323
|
-
: genCharCodeSwitch(knownKeys, v)
|
|
4428
|
+
: genCharCodeSwitch(knownKeys, v, ctx)
|
|
4324
4429
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
4325
4430
|
} else {
|
|
4326
|
-
inner = `for(var _k in ${v})return false`
|
|
4431
|
+
inner = `for(var _k in ${v})if(${ownFail(ctx, v)})return false`
|
|
4327
4432
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
4328
4433
|
}
|
|
4329
4434
|
} else if (typeof schema.unevaluatedProperties === 'object') {
|
|
@@ -4341,7 +4446,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4341
4446
|
} else if (knownKeys.length > 0) {
|
|
4342
4447
|
skipKnown = `if(${knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')})continue;`
|
|
4343
4448
|
}
|
|
4344
|
-
const inner =
|
|
4449
|
+
const inner = `${ownHead(ctx, v, ukVar, 'var')}{${skipKnown}${check}}`
|
|
4345
4450
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
4346
4451
|
}
|
|
4347
4452
|
}
|
|
@@ -4386,8 +4491,8 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4386
4491
|
const uniqueThen = [...new Set([...baseProps, ...ifProps, ...(thenEval.props || [])])]
|
|
4387
4492
|
const uniqueElse = [...new Set([...baseProps, ...(elseEval.props || [])])]
|
|
4388
4493
|
|
|
4389
|
-
const thenCheck = genCharCodeSwitch(uniqueThen, v)
|
|
4390
|
-
const elseCheck = genCharCodeSwitch(uniqueElse, v)
|
|
4494
|
+
const thenCheck = genCharCodeSwitch(uniqueThen, v, ctx)
|
|
4495
|
+
const elseCheck = genCharCodeSwitch(uniqueElse, v, ctx)
|
|
4391
4496
|
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
4392
4497
|
lines.push(`${guard}{const _uif${ufi}=${ifFn2};if(_uif${ufi}(${v})){${thenCheck}}else{${elseCheck}}}`)
|
|
4393
4498
|
} else if (branchKeyword) {
|
|
@@ -4458,8 +4563,8 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4458
4563
|
}
|
|
4459
4564
|
const dynamicCheck = `switch(_k.charCodeAt(0)){${switchCases}default:break}`
|
|
4460
4565
|
const inner = staticCheck
|
|
4461
|
-
? `for(var _k in ${v}){if(${staticCheck})continue;${dynamicCheck}return false}`
|
|
4462
|
-
: `for(var _k in ${v}){${dynamicCheck}return false}`
|
|
4566
|
+
? `for(var _k in ${v}){if(${staticCheck})continue;${dynamicCheck}if(${ownFail(ctx, v)})return false}`
|
|
4567
|
+
: `for(var _k in ${v}){${dynamicCheck}if(${ownFail(ctx, v)})return false}`
|
|
4463
4568
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4464
4569
|
} else {
|
|
4465
4570
|
// Fallback: plain object tracking
|
|
@@ -4484,7 +4589,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4484
4589
|
// Single pass: validate anyOf (at least one) + track all matching
|
|
4485
4590
|
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
4591
|
}
|
|
4487
|
-
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
4592
|
+
const inner = `for(var _k in ${v}){if(!${evVar}[_k]&&${ownFail(ctx, v)})return false}`
|
|
4488
4593
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4489
4594
|
}
|
|
4490
4595
|
} else if (schema.dependentSchemas) {
|
|
@@ -4499,7 +4604,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4499
4604
|
lines.push(`if(${ownKeyExpr(ctx, v, trigger)}){${depResult.props.map(k => `${evVar}[${JSON.stringify(k)}]=1`).join(';')}}`)
|
|
4500
4605
|
}
|
|
4501
4606
|
}
|
|
4502
|
-
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
4607
|
+
const inner = `for(var _k in ${v}){if(!${evVar}[_k]&&${ownFail(ctx, v)})return false}`
|
|
4503
4608
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4504
4609
|
} else {
|
|
4505
4610
|
// General fallback: collect all patternProperties from root + allOf sub-schemas + if
|
|
@@ -4559,10 +4664,10 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4559
4664
|
}
|
|
4560
4665
|
const rootPatCheck = rootReVars.map(rv => `if(${rv}.test(_k))continue;`).join('')
|
|
4561
4666
|
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}}`
|
|
4667
|
+
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
4668
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4564
4669
|
} 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}`
|
|
4670
|
+
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
4671
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4567
4672
|
}
|
|
4568
4673
|
} else {
|
|
@@ -4573,7 +4678,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4573
4678
|
const evVar = `_ev${ei}`
|
|
4574
4679
|
lines.push(`{const ${evVar}={}`)
|
|
4575
4680
|
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}`
|
|
4681
|
+
const inner = `for(var _k in ${v}){if(!${evVar}[_k]&&${ownFail(ctx, v)})return false}`
|
|
4577
4682
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4578
4683
|
}
|
|
4579
4684
|
}
|
|
@@ -4613,7 +4718,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
4613
4718
|
genCode(schema.unevaluatedProperties, `${v}[${ukVar}]`, subLines2, ctx)
|
|
4614
4719
|
if (subLines2.length > 0) {
|
|
4615
4720
|
const check = subLines2.join(';')
|
|
4616
|
-
const inner =
|
|
4721
|
+
const inner = `${ownHead(ctx, v, ukVar, 'var')}{if(${evVar}[${ukVar}])continue;${check}}`
|
|
4617
4722
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
4618
4723
|
} else {
|
|
4619
4724
|
lines.push('}')
|
|
@@ -5051,13 +5156,57 @@ function fastPrefixCheck(pattern, keyVar) {
|
|
|
5051
5156
|
return `${keyVar}.startsWith(${JSON.stringify(prefix)})`
|
|
5052
5157
|
}
|
|
5053
5158
|
|
|
5159
|
+
// The head of a loop over the own enumerable keys of `v`, written as
|
|
5160
|
+
// `for...in`, the fast way to walk an object, which also visits enumerable
|
|
5161
|
+
// keys its prototype carries. An ordinary object's prototype is
|
|
5162
|
+
// Object.prototype, and its keys are taken as they come; any other object, a
|
|
5163
|
+
// class instance or one made with Object.create, has each key checked as its
|
|
5164
|
+
// own, as the interpreter reads them. One statement, so the body that follows
|
|
5165
|
+
// is written as it was: `for(var _k in v)if(...)continue;else BODY`.
|
|
5166
|
+
// For a loop whose only work is to reject on a key it does not expect: the
|
|
5167
|
+
// key is the object's own, or the object is ordinary, whose prototype's keys
|
|
5168
|
+
// are taken as they come. Read only on the way to `return false`, so an
|
|
5169
|
+
// object that has only expected keys pays nothing.
|
|
5170
|
+
// `_ok` reads the prototype rather than the node's flag: this check can land
|
|
5171
|
+
// in a hoisted branch function the flag is not declared in.
|
|
5172
|
+
function ownFail (ctx, v, k = '_k') {
|
|
5173
|
+
useOwnHelper(ctx, '_ok')
|
|
5174
|
+
return `_ok(${v},${k})`
|
|
5175
|
+
}
|
|
5176
|
+
// For a key count that came out wrong: whether it is wrong counting own keys
|
|
5177
|
+
// only. An ordinary object's count stands; any other object is counted again.
|
|
5178
|
+
// A oneOf of closed objects fails its other branches here on every value, so
|
|
5179
|
+
// this is not only a cold path.
|
|
5180
|
+
function ownCountOff (ctx, v, n, pk) {
|
|
5181
|
+
// A call to a shared helper rather than the test written in place: a oneOf
|
|
5182
|
+
// branch this small is inlined into its caller by V8, and the longer
|
|
5183
|
+
// bytecode cost that, 8% on a document of patch operations. The node's
|
|
5184
|
+
// ordinary-object flag is not used: this can land in a hoisted branch
|
|
5185
|
+
// function it is not declared in.
|
|
5186
|
+
useOwnHelper(ctx, '_nk')
|
|
5187
|
+
// `pk`: the node's own ordinary-object flag, where the caller holds it in
|
|
5188
|
+
// the same function as this check. An ordinary object needs no recount.
|
|
5189
|
+
return pk ? `(${pk}||_nk(${v},${n}))` : `_nk(${v},${n})`
|
|
5190
|
+
}
|
|
5191
|
+
|
|
5192
|
+
function ownHead (ctx, v, k = '_k', kind = 'var') {
|
|
5193
|
+
useOwnHelper(ctx, '_hop')
|
|
5194
|
+
// Read here rather than taken from the node's flag: generated bodies that
|
|
5195
|
+
// become functions of their own (branches, annotations, definitions) take
|
|
5196
|
+
// the same parameter names and do not see the flag. Written in place, not
|
|
5197
|
+
// through a shared helper, which would see every object shape of the
|
|
5198
|
+
// document and read the prototype megamorphically: 7% on validate().
|
|
5199
|
+
const i = ctx.varCounter++
|
|
5200
|
+
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 `
|
|
5201
|
+
}
|
|
5202
|
+
|
|
5054
5203
|
// Generate a charCodeAt(0)-based switch tree for fast key validation.
|
|
5055
5204
|
// 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`
|
|
5205
|
+
function genCharCodeSwitch(keys, v, ctx) {
|
|
5206
|
+
if (keys.length === 0) return `for(var _k in ${v})if(${ownFail(ctx, v)})return false`
|
|
5058
5207
|
if (keys.length <= 3) {
|
|
5059
5208
|
// 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`
|
|
5209
|
+
return `for(var _k in ${v})if(${keys.map(k => `_k!==${JSON.stringify(k)}`).join('&&')}&&${ownFail(ctx, v)})return false`
|
|
5061
5210
|
}
|
|
5062
5211
|
|
|
5063
5212
|
// Group keys by first charCode
|
|
@@ -5074,7 +5223,7 @@ function genCharCodeSwitch(keys, v) {
|
|
|
5074
5223
|
cases += `case ${cc}:if(${cond})continue;break;`
|
|
5075
5224
|
}
|
|
5076
5225
|
|
|
5077
|
-
return `for(var _k in ${v}){switch(_k.charCodeAt(0)){${cases}default:break}return false}`
|
|
5226
|
+
return `for(var _k in ${v}){switch(_k.charCodeAt(0)){${cases}default:break}if(${ownFail(ctx, v)})return false}`
|
|
5078
5227
|
}
|
|
5079
5228
|
|
|
5080
5229
|
// The verdict form of genUnevaluatedDynamicC: the same set, built the same
|
|
@@ -5120,7 +5269,7 @@ function genUnevalDynamicVerdict(schema, v, lines, ctx, isObj, nonObjApplicators
|
|
|
5120
5269
|
// additionalProperties is there.
|
|
5121
5270
|
function addLocalAnnotations(node, v, S, out, ctx) {
|
|
5122
5271
|
if (node.additionalProperties !== undefined) {
|
|
5123
|
-
out.push(
|
|
5272
|
+
out.push(`${ownHead(ctx, v, `_ka`, 'const')}${S}.push(_ka)`)
|
|
5124
5273
|
return
|
|
5125
5274
|
}
|
|
5126
5275
|
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 +5285,7 @@ function addLocalAnnotations(node, v, S, out, ctx) {
|
|
|
5136
5285
|
ctx.closureVals.push(safeReClosure(ctx, pat))
|
|
5137
5286
|
tests.push(`_re${ri}.test(${kv})`)
|
|
5138
5287
|
}
|
|
5139
|
-
out.push(
|
|
5288
|
+
out.push(`${ownHead(ctx, v, kv, 'const')}if(${tests.join('||')})${S}.push(${kv})`)
|
|
5140
5289
|
}
|
|
5141
5290
|
}
|
|
5142
5291
|
|
|
@@ -5419,4 +5568,4 @@ Object.defineProperty(module.exports, '_expandDynamicScopes', { value: expandDyn
|
|
|
5419
5568
|
// For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
|
|
5420
5569
|
Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })
|
|
5421
5570
|
|
|
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 }
|
|
5571
|
+
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/safe-regex.js
CHANGED
|
@@ -381,6 +381,24 @@ function __ataSafeRegex () {
|
|
|
381
381
|
const states = []
|
|
382
382
|
const stateIds = new Map()
|
|
383
383
|
let overflow = false
|
|
384
|
+
// Transitions in one flat table per end flag, 128 slots per state (ASCII),
|
|
385
|
+
// -2 for not computed yet, -1 for overflow; `flags` holds isMatch (1) and
|
|
386
|
+
// dead (2) per state. The loop below then reads two typed arrays per
|
|
387
|
+
// character instead of a state object and the tables hanging off it. The
|
|
388
|
+
// tables grow with the states, so a pattern with few states stays small.
|
|
389
|
+
// State ids stay below MAX_STATES, so 16 bits hold them and the two
|
|
390
|
+
// sentinels: half the memory of the per-state tables this replaced.
|
|
391
|
+
let cap = 0
|
|
392
|
+
let trans = new Int16Array(0)
|
|
393
|
+
let transEnd = new Int16Array(0)
|
|
394
|
+
let flags = new Uint8Array(0)
|
|
395
|
+
function grow () {
|
|
396
|
+
const next = cap === 0 ? 2 : cap * 2
|
|
397
|
+
const t = new Int16Array(next * 128).fill(-2); t.set(trans); trans = t
|
|
398
|
+
const te = new Int16Array(next * 128).fill(-2); te.set(transEnd); transEnd = te
|
|
399
|
+
const f = new Uint8Array(next); f.set(flags); flags = f
|
|
400
|
+
cap = next
|
|
401
|
+
}
|
|
384
402
|
|
|
385
403
|
function closure (list, count, pc, atStart, atEnd) {
|
|
386
404
|
// Same walk as addThread, with the position replaced by the two flags.
|
|
@@ -411,7 +429,9 @@ function __ataSafeRegex () {
|
|
|
411
429
|
let isMatch = false
|
|
412
430
|
for (let k = 0; k < pcs.length; k++) if (ops[pcs[k]] === OP_MATCH) { isMatch = true; break }
|
|
413
431
|
// `dead`: an anchored match with no thread left cannot recover.
|
|
414
|
-
states.push({ pcs: Int32Array.from(pcs)
|
|
432
|
+
states.push({ pcs: Int32Array.from(pcs) })
|
|
433
|
+
if (id >= cap) grow()
|
|
434
|
+
flags[id] = (isMatch ? 1 : 0) | (anchored && pcs.length === 0 ? 2 : 0)
|
|
415
435
|
stateIds.set(key, id)
|
|
416
436
|
return id
|
|
417
437
|
}
|
|
@@ -452,37 +472,36 @@ function __ataSafeRegex () {
|
|
|
452
472
|
id = startNonEmpty
|
|
453
473
|
}
|
|
454
474
|
if (id < 0) return testNFA(s)
|
|
455
|
-
|
|
456
|
-
if (len === 0) return state.isMatch
|
|
475
|
+
if (len === 0) return (flags[id] & 1) === 1
|
|
457
476
|
// The last character is stepped with the end-of-input flag set and is
|
|
458
477
|
// handled after the loop, so the loop body selects no table and tests
|
|
459
478
|
// no position.
|
|
460
479
|
const last = len - 1
|
|
461
480
|
for (let pos = 0; pos < last; pos++) {
|
|
462
|
-
if (
|
|
481
|
+
if ((flags[id] & 1) === 1) return true
|
|
463
482
|
const c = s.charCodeAt(pos)
|
|
464
483
|
let nid
|
|
465
484
|
if (c < 128) {
|
|
466
|
-
nid =
|
|
467
|
-
if (nid === -2) { nid = step(
|
|
485
|
+
nid = trans[(id << 7) | c]
|
|
486
|
+
if (nid === -2) { nid = step(states[id], c, false); if (nid >= 0 || overflow) trans[(id << 7) | c] = nid }
|
|
468
487
|
} else {
|
|
469
|
-
nid = step(
|
|
488
|
+
nid = step(states[id], c, false)
|
|
470
489
|
}
|
|
471
490
|
if (nid < 0) return testNFA(s)
|
|
472
|
-
|
|
473
|
-
if (
|
|
491
|
+
id = nid
|
|
492
|
+
if ((flags[id] & 2) === 2) return false
|
|
474
493
|
}
|
|
475
|
-
if (
|
|
494
|
+
if ((flags[id] & 1) === 1) return true
|
|
476
495
|
const c = s.charCodeAt(last)
|
|
477
496
|
let nid
|
|
478
497
|
if (c < 128) {
|
|
479
|
-
nid =
|
|
480
|
-
if (nid === -2) { nid = step(
|
|
498
|
+
nid = transEnd[(id << 7) | c]
|
|
499
|
+
if (nid === -2) { nid = step(states[id], c, true); if (nid >= 0 || overflow) transEnd[(id << 7) | c] = nid }
|
|
481
500
|
} else {
|
|
482
|
-
nid = step(
|
|
501
|
+
nid = step(states[id], c, true)
|
|
483
502
|
}
|
|
484
503
|
if (nid < 0) return testNFA(s)
|
|
485
|
-
return
|
|
504
|
+
return (flags[nid] & 1) === 1
|
|
486
505
|
}
|
|
487
506
|
|
|
488
507
|
return function test (s) {
|