ata-validator 1.51.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/index.bundle.mjs +15 -0
- package/lib/aot-impl.js +2 -2
- 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/version.js +1 -1
- package/package.json +10 -8
package/README.md
CHANGED
|
@@ -138,18 +138,19 @@ errors (7.6M with `richErrors: false`).
|
|
|
138
138
|
|
|
139
139
|
| Dimension | Schema | ata-AOT | runtime validator | Difference |
|
|
140
140
|
|---|---|---|---|---|
|
|
141
|
-
| Bundle (gzipped) | simple | 1.
|
|
142
|
-
| Bundle (gzipped) | complex | 3.0 KB | 52.7 KB | 17.
|
|
143
|
-
| Bundle (gzipped) | nested | 4.5 KB | 52.7 KB | 11.
|
|
141
|
+
| Bundle (gzipped) | simple | 1.3 KB | 52.7 KB | 39.0x smaller |
|
|
142
|
+
| Bundle (gzipped) | complex | 3.0 KB | 52.7 KB | 17.7x smaller |
|
|
143
|
+
| Bundle (gzipped) | nested | 4.5 KB | 52.7 KB | 11.8x smaller |
|
|
144
144
|
| Cold start | simple | 20 ms | 37 ms | 1.9x faster |
|
|
145
|
-
| Throughput (1M ops) | simple |
|
|
146
|
-
| Compile time | simple |
|
|
145
|
+
| Throughput (1M ops) | simple | 315 Mops/s | 99 Mops/s | 3.2x faster |
|
|
146
|
+
| Compile time | simple | 24 µs | 1.57 ms | 65x faster |
|
|
147
147
|
|
|
148
148
|
The runtime column is the default validator most frameworks ship. Reproduce on your machine
|
|
149
|
-
with `npm run bench:aot-vs-ajv`. Numbers from one run on Apple M4 Pro, Node 25.2.1, 2026-10-
|
|
150
|
-
on ata-validator 1.
|
|
151
|
-
|
|
152
|
-
and
|
|
149
|
+
with `npm run bench:aot-vs-ajv`. Numbers from one run on Apple M4 Pro, Node 25.2.1, 2026-10-11,
|
|
150
|
+
on ata-validator 1.51.0. Across five runs throughput moved between 263 and 325 Mops/s against
|
|
151
|
+
97 to 110, cold start between 20 and 22 ms against 37 to 40, and the compile ratio between 61x
|
|
152
|
+
and 65x. The compile row is the time to write a module, a build-time cost; it was 18 µs on
|
|
153
|
+
1.46.0, and modules have carried the one-pass error function since 1.47.0. The throughput row times a single one-million-call
|
|
153
154
|
loop of a few milliseconds, so it moves the most from run to run; treat the last three rows as
|
|
154
155
|
an order of magnitude rather than a constant.
|
|
155
156
|
|
|
@@ -279,13 +280,13 @@ is the most common way to get a misleading number out of this library.
|
|
|
279
280
|
|
|
280
281
|
| | compiled with `ata build` | runtime `new Validator(schema)` |
|
|
281
282
|
|---|---|---|
|
|
282
|
-
| In a bundle, gzipped | **2.1 KB** |
|
|
283
|
+
| In a bundle, gzipped | **2.1 KB** | 123.9 KB |
|
|
283
284
|
| Time to a served request | **3.5 ms** | 7.6 ms |
|
|
284
285
|
| Schema known when | build time | any time |
|
|
285
286
|
|
|
286
287
|
The bundle row is the ten-field user schema in
|
|
287
288
|
`tests/fixtures/error-dx/user.schema.json`, every export of the compiled module against
|
|
288
|
-
`new Validator(schema)`, built with `bun build --minify --target=browser` on ata 1.
|
|
289
|
+
`new Validator(schema)`, built with `bun build --minify --target=browser` on ata 1.51.0.
|
|
289
290
|
The startup row is a Hono route on Bun 1.4, the median of three rounds of best-of-seven, from
|
|
290
291
|
`benchmark/bundle`, against 3.5 ms for the same app doing no validation at all, so the
|
|
291
292
|
compiled path costs nothing measurable to start. The runtime figure is what it is because
|
|
@@ -293,7 +294,7 @@ a schema that arrives at run time can use any keyword, so the whole engine has t
|
|
|
293
294
|
there. The compiled module imports nothing and contains only the checks your schema asks
|
|
294
295
|
for.
|
|
295
296
|
|
|
296
|
-
**On a server, use whichever fits your schemas.**
|
|
297
|
+
**On a server, use whichever fits your schemas.** 124 KB of JavaScript on a Node or Bun
|
|
297
298
|
process is not a cost anyone notices, and the runtime API is the simpler thing to reach
|
|
298
299
|
for. Speed is the same either way once warm.
|
|
299
300
|
|
|
@@ -535,7 +536,7 @@ const v = new Validator(schema, {
|
|
|
535
536
|
|
|
536
537
|
### Build-time compile (`ata compile`)
|
|
537
538
|
|
|
538
|
-
The `ata` CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on `ata-validator`, so only the generated validator ships to the browser. For the ten-field user schema in `tests/fixtures/error-dx/user.schema.json` the module is 2.1 KB gzipped, full error detail included, against
|
|
539
|
+
The `ata` CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on `ata-validator`, so only the generated validator ships to the browser. For the ten-field user schema in `tests/fixtures/error-dx/user.schema.json` the module is 2.1 KB gzipped, full error detail included, against 123.9 KB for the runtime bundled for the browser (ata 1.51.0).
|
|
539
540
|
|
|
540
541
|
```bash
|
|
541
542
|
npx ata compile schemas/user.json -o src/generated/user.validator.mjs
|
|
@@ -581,20 +582,25 @@ error collector out of the modules: each one carries its verdict and its schema,
|
|
|
581
582
|
its errors from one `ata-errors.mjs` the build writes beside them. The build does this only
|
|
582
583
|
where it makes the output smaller, so a single small schema keeps its self-contained module.
|
|
583
584
|
The errors are the ones `new Validator(schema, { richErrors: false })` reports, in the same
|
|
584
|
-
order.
|
|
585
|
-
to
|
|
585
|
+
order. Forty SchemaStore schemas imported into one app, minified with esbuild, go from 513.8 KB
|
|
586
|
+
to 213.3 KB gzipped, and from 60.4 ms to 28.4 ms to import with code generation blocked. A
|
|
586
587
|
rejected document costs more this way, since its errors come from an interpreted walk:
|
|
587
588
|
`--errors inline` keeps every module self-contained. `--mode schema` writes each module as
|
|
588
589
|
its schema and takes the verdict from the runtime too: forty SchemaStore schemas in one app
|
|
589
|
-
come to
|
|
590
|
-
23.
|
|
590
|
+
come to 102.4 KB gzipped against 213.3 KB as compact modules, and load and check each once in
|
|
591
|
+
23.9 ms against 34.5, while a check once warm costs about five times what generated code costs.
|
|
592
|
+
Schemas that reference each other by `$id` are built against each other, and in schema mode
|
|
593
|
+
they are written once to a shared registry that keeps only the definitions the modules reach;
|
|
594
|
+
`--base-uri <uri>` names files that declare no `$id`. Eight Kubernetes kinds, whose schemas
|
|
595
|
+
share one 1.1 MB definitions file, come to 32.6 KB gzipped as schema modules against 68.6 KB
|
|
596
|
+
self-contained, on ata 1.51.0.
|
|
591
597
|
|
|
592
598
|
Bundle sizes for the 10-field user schema in `tests/fixtures/error-dx/user.schema.json`,
|
|
593
|
-
minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.
|
|
599
|
+
minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.51.0:
|
|
594
600
|
|
|
595
601
|
| What the app imports | Size | Notes |
|
|
596
602
|
|---|---|---|
|
|
597
|
-
| `Validator` from `ata-validator` |
|
|
603
|
+
| `Validator` from `ata-validator` | 123.9 KB | The compiler ships with it, because a runtime schema can use any keyword |
|
|
598
604
|
| `isValid` from the compiled module | **1.3 KB** | Nothing else is reachable, so the error collector is dropped |
|
|
599
605
|
| `validate` from the compiled module | **1.9 KB** | Adds the detailed error collector |
|
|
600
606
|
|
|
@@ -632,7 +638,7 @@ through a `setFormats()` export the module carries. `docs/API.md` has the
|
|
|
632
638
|
details.
|
|
633
639
|
|
|
634
640
|
**Fastify startup, 10 route schemas, from a cold process to the first validated request:
|
|
635
|
-
ajv 19.
|
|
641
|
+
ajv 19.0 ms, ata 2.81 ms, no build step required.** ata registers in 0.20 ms of that and
|
|
636
642
|
compiles on the first request, so counting only registration would overstate the gap.
|
|
637
643
|
Reproduce with `node benchmark/bench_fastify_boot.mjs`.
|
|
638
644
|
|
package/index.bundle.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// ESM entry for bundlers that target a server (Bun, esbuild or Rollup for
|
|
2
|
+
// Node) and for Node before 22.10, which does not match `module-sync`: the
|
|
3
|
+
// validator core with the code generator registered, as index.js, but with
|
|
4
|
+
// each tool imported on its own (lib/tools.mjs), so the tools an application
|
|
5
|
+
// does not import stay out of its bundle. Through index.js a bundler sees one
|
|
6
|
+
// CommonJS module and keeps all of them.
|
|
7
|
+
import core from './lib/validator-core.js';
|
|
8
|
+
import engine from './lib/codegen-engine.js';
|
|
9
|
+
import { toTypeScript, renderPretty, renderCompact, toOutput, toRetryMessage, describeSchema, renderJSON } from './lib/tools.mjs';
|
|
10
|
+
|
|
11
|
+
core._registerCodegen(engine);
|
|
12
|
+
|
|
13
|
+
export const { Validator, compile, validate, validateAsync, parseAsync, version, createPaddedBuffer, SIMDJSON_PADDING, parseJSON, defineSchema } = core;
|
|
14
|
+
export { toTypeScript, renderPretty, renderCompact, toOutput, toRetryMessage, describeSchema, renderJSON };
|
|
15
|
+
export default { Validator, compile, validate, validateAsync, parseAsync, version, createPaddedBuffer, SIMDJSON_PADDING, parseJSON, toTypeScript, defineSchema, renderPretty, renderCompact, toOutput, toRetryMessage, describeSchema, renderJSON };
|
package/lib/aot-impl.js
CHANGED
|
@@ -251,7 +251,7 @@ function onePassCore(validator, opts) {
|
|
|
251
251
|
const decls = [];
|
|
252
252
|
const args = [];
|
|
253
253
|
const own = new Set();
|
|
254
|
-
const addOwn = (n) => { if (own.has(n)) return; own.add(n); if (n === '_h' || n === '_hall') addOwn('_hop'); if (n === '_ok') { addOwn('_PN'); addOwn('_hop'); } decls.push(OWN_HELPER_CODE[n]); };
|
|
254
|
+
const addOwn = (n) => { if (own.has(n)) return; own.add(n); if (n === '_h' || n === '_hall') addOwn('_hop'); if (n === '_ok') { addOwn('_PN'); addOwn('_hop'); } if (n === '_nk') addOwn('_PN'); decls.push(OWN_HELPER_CODE[n]); };
|
|
255
255
|
let usesSafeRe = emit.usesSafeRe, usesCollapse = false;
|
|
256
256
|
for (let i = 0; i < emit.names.length; i++) {
|
|
257
257
|
const name = emit.names[i], val = emit.values[i];
|
|
@@ -272,7 +272,7 @@ function onePassCore(validator, opts) {
|
|
|
272
272
|
if (name === '_VF') { args.push('null'); continue; }
|
|
273
273
|
if (name === '_PN') { addOwn('_PN'); args.push('_PN'); continue; }
|
|
274
274
|
if (name === '_OP' || name === '_gp') { addOwn('_PN'); args.push(name); continue; }
|
|
275
|
-
if (name === '_hop' || name === '_h' || name === '_ok' || name === '_hall') { addOwn(name); args.push(name); continue; }
|
|
275
|
+
if (name === '_hop' || name === '_h' || name === '_ok' || name === '_hall' || name === '_nk') { addOwn(name); args.push(name); continue; }
|
|
276
276
|
if (name === '_pe') { decls.push(PTR_ESC_HELPER); args.push('_pe'); continue; }
|
|
277
277
|
if (name === '_deq') { decls.push(DEQ_HELPER); args.push('_deq'); continue; }
|
|
278
278
|
if (name === '_anT') { decls.push('function _anT(){return true}'); args.push('_anT'); continue; }
|
package/lib/combined-runtime.js
CHANGED
|
@@ -121,6 +121,9 @@ const _hop = Object.prototype.hasOwnProperty
|
|
|
121
121
|
function _h (o, k) { return _hop.call(o, k) }
|
|
122
122
|
function _ok (o, k) { return (_PN ? o.__proto__ : _gp(o)) === _OP || _hop.call(o, k) } // eslint-disable-line no-proto
|
|
123
123
|
function _hall (o, ks) { for (let i = 0; i < ks.length; i++) if (!_hop.call(o, ks[i])) return false; return true }
|
|
124
|
+
// A key count that came out wrong, counted again over own keys unless the
|
|
125
|
+
// object is ordinary.
|
|
126
|
+
function _nk (o, n) { return (_PN ? o.__proto__ : _gp(o)) === _OP || Object.keys(o).length !== n } // eslint-disable-line no-proto
|
|
124
127
|
|
|
125
128
|
// RFC 6901 escaping for a key only known at run time (the static ones are
|
|
126
129
|
// escaped when the code is written).
|
|
@@ -140,7 +143,7 @@ function _rb (S, m) { if (S.length !== m) S.length = m }
|
|
|
140
143
|
|
|
141
144
|
module.exports = {
|
|
142
145
|
apState, ap, sr, srX, multiBranch,
|
|
143
|
-
_PN, _OP, _gp, _hop, _h, _ok, _hall, _pe, _anT, _anF, _rb,
|
|
146
|
+
_PN, _OP, _gp, _hop, _h, _ok, _hall, _nk, _pe, _anT, _anF, _rb,
|
|
144
147
|
}
|
|
145
148
|
// Loaded when a program first asks for them: a schema without const, enum,
|
|
146
149
|
// uniqueItems, oneOf or anyOf never needs these modules on its cold path.
|
package/lib/error-runtime.js
CHANGED
|
@@ -94,7 +94,11 @@ function errorRuntimeSource(format, version, features, schemaMode) {
|
|
|
94
94
|
const src = OPTIONAL[name] !== undefined && !want(name)
|
|
95
95
|
? OPTIONAL[name].stub
|
|
96
96
|
: dropCommentLines(fs.readFileSync(path.join(__dirname, name + '.js'), 'utf8'), name);
|
|
97
|
-
|
|
97
|
+
// The files' own require('./x') calls go to the table under another name:
|
|
98
|
+
// a tool that reads the runtime without scope (knip, a lint rule) took a
|
|
99
|
+
// local `require` for an import of a file that is not there.
|
|
100
|
+
const own = src.replace(/\brequire\((['"])\.\//g, '__ataReq($1./');
|
|
101
|
+
defs.push(`${JSON.stringify(name)}: function (module, exports, __ataReq) {\n${own}\n}`);
|
|
98
102
|
}
|
|
99
103
|
const { byKw, byFmt } = codeTable();
|
|
100
104
|
const body = `// Auto-generated by ata-validator ${version}, do not edit.
|
|
@@ -9,7 +9,7 @@ const { compileFunction } = require('./compile-fn')
|
|
|
9
9
|
// the first call that compiles errors. Everything here was written as part
|
|
10
10
|
// of js-compiler.js and reads its helpers from that module's _internal.
|
|
11
11
|
const core = require('./js-compiler')
|
|
12
|
-
const { 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 } = core._internal
|
|
12
|
+
const { 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 } = core._internal
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
function emitPtrEsc(ctx) {
|
|
@@ -1313,7 +1313,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
1313
1313
|
// The name set is built once per call of the error function, not at every
|
|
1314
1314
|
// object it checks: on a list of 1723 tokens it was 1723 Sets.
|
|
1315
1315
|
ctx.helperCode.push(`const _a${ci}=new Set([${allowed}])`)
|
|
1316
|
-
const inner = `let _x${ci}=0
|
|
1316
|
+
const inner = `let _x${ci}=0;${ownHead(ctx, v, `_f${ci}`, 'const')}{if(${gateTest}){_x${ci}=1;break}}if(_x${ci}){${detail}}`
|
|
1317
1317
|
lines.push(isObj ? `{${inner}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
1318
1318
|
}
|
|
1319
1319
|
|
|
@@ -1504,7 +1504,7 @@ function genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
1504
1504
|
}
|
|
1505
1505
|
const ri = ctx.regExpMap.get(pattern);
|
|
1506
1506
|
const ki = ctx.varCounter++
|
|
1507
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){
|
|
1507
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${ownHead(ctx, v, `_k${ki}`, 'const')}{if(_re${ri}.test(_k${ki})){`)
|
|
1508
1508
|
const _peFn = emitPtrEsc(ctx)
|
|
1509
1509
|
const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
|
|
1510
1510
|
// The real pointer, so source maps can locate the failing keyword.
|
|
@@ -1538,7 +1538,7 @@ function genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
1538
1538
|
if (known.length) conds.push(`!_ak${ki}.has(_k${ki})`)
|
|
1539
1539
|
if (patTests.length) conds.push(`!(${patTests.join('||')})`)
|
|
1540
1540
|
const keep = conds.length ? `if(${conds.join('&&')}){` : `{`
|
|
1541
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${guard}
|
|
1541
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${guard}${ownHead(ctx, v, `_k${ki}`, 'const')}{${keep}`)
|
|
1542
1542
|
const _peFn = emitPtrEsc(ctx)
|
|
1543
1543
|
const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
|
|
1544
1544
|
genCodeE(schema.additionalProperties, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/additionalProperties')
|
|
@@ -1559,13 +1559,13 @@ function genPropertyNamesFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
1559
1559
|
// the object that carries the name, not to `/<key>`, which is where the
|
|
1560
1560
|
// value lives. Same placement as the object-valued propertyNames branch.
|
|
1561
1561
|
const p = pathExpr || '""'
|
|
1562
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){
|
|
1562
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${ownHead(ctx, v, `_k${ki}`, 'const')}{_e.push({keyword:'not',instancePath:${p},schemaPath:'${schemaPrefix}/propertyNames'${ordinalField(ctx, `${schemaPrefix}/propertyNames`)},params:{},message:'boolean schema is false'});if(!_all)return{valid:false,errors:_e}}}`)
|
|
1563
1563
|
}
|
|
1564
1564
|
|
|
1565
1565
|
function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
1566
1566
|
const pn = schema.propertyNames
|
|
1567
1567
|
const ki = ctx.varCounter++
|
|
1568
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){
|
|
1568
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${ownHead(ctx, v, `_k${ki}`, 'const')}{`)
|
|
1569
1569
|
// The subschema checks the key as a value, and its errors are reported at
|
|
1570
1570
|
// the object, as the interpreter reports them.
|
|
1571
1571
|
if (!isSimplePN(pn)) {
|
|
@@ -2523,14 +2523,14 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
2523
2523
|
// leave `if()`, so the test is `true`.
|
|
2524
2524
|
const gate = propKeys.map(k => `_f${ci}!==${JSON.stringify(k)}`).join('&&') || 'true'
|
|
2525
2525
|
const checks = propKeys.map(k => `_k${ci}[_i]!==${JSON.stringify(k)}`).join('&&') || 'true'
|
|
2526
|
-
body = `let _x${ci}=0
|
|
2526
|
+
body = `let _x${ci}=0;${ownHead(ctx, v, `_f${ci}`, 'const')}{if(${gate}){_x${ci}=1;break}}if(_x${ci}){${detail(checks)}}`
|
|
2527
2527
|
} else {
|
|
2528
2528
|
// The name set lives in the closure, built once with the function: built
|
|
2529
2529
|
// in the body it was a 33-name Set on every call of this function, which
|
|
2530
2530
|
// is where a rejected npm-badges document spent most of its 701 ns.
|
|
2531
2531
|
ctx.closureVars.push(`_a${ci}`)
|
|
2532
2532
|
ctx.closureVals.push(new Set(propKeys))
|
|
2533
|
-
body = `let _x${ci}=0
|
|
2533
|
+
body = `let _x${ci}=0;${ownHead(ctx, v, `_f${ci}`, 'const')}{if(!_a${ci}.has(_f${ci})){_x${ci}=1;break}}if(_x${ci}){${detail(`!_a${ci}.has(_k${ci}[_i])`)}}`
|
|
2534
2534
|
}
|
|
2535
2535
|
// When every declared property is required, a clean object has exactly the
|
|
2536
2536
|
// declared keys: if the enumerated count equals the declared count and
|
|
@@ -2545,7 +2545,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
2545
2545
|
const req = new Set(Array.isArray(schema.required) ? schema.required : [])
|
|
2546
2546
|
if (propKeys.length > 0 && propKeys.every((k) => req.has(k) && !PROTO_NAMES.has(k))) {
|
|
2547
2547
|
const present = propKeys.map((k) => `${JSON.stringify(k)} in ${v}`).join('&&')
|
|
2548
|
-
body = `let _n${ci}=0
|
|
2548
|
+
body = `let _n${ci}=0;${ownHead(ctx, v, `_f${ci}`, 'const')}_n${ci}++;if(_n${ci}!==${propKeys.length}||!(${present})){${body}}`
|
|
2549
2549
|
}
|
|
2550
2550
|
lines.push(isObj
|
|
2551
2551
|
? `{${body}}`
|
|
@@ -2945,7 +2945,7 @@ function inlineUnevalBlock (schema, v, pathExpr, ctx, schemaPrefix, fail, counts
|
|
|
2945
2945
|
} else {
|
|
2946
2946
|
throw DECLINE
|
|
2947
2947
|
}
|
|
2948
|
-
if (body) out.push(
|
|
2948
|
+
if (body) out.push(`${ownHead(ctx, v, kVar, 'const')}{if(!${ownKeyExpr(ctx, v, kVar).split(JSON.stringify(kVar)).join(kVar)})continue;let _ub${id}=0;switch(${kVar}){${cases}}if(_ub${id}!==0&&(${m}&_ub${id})!==0)continue;${allBit}${patBits}${body}}`)
|
|
2949
2949
|
const text = out.join(';')
|
|
2950
2950
|
ctx.plainReuse = prevReuse
|
|
2951
2951
|
if (prevPk === undefined) ctx.plainOf.delete(v)
|
|
@@ -3357,7 +3357,7 @@ function genAdditionalSchemaC(schema, v, pathExpr, lines, ctx, schemaPrefix, isO
|
|
|
3357
3357
|
const sub = []
|
|
3358
3358
|
genCodeC(schema.additionalProperties, `${v}[${kVar}]`, p, sub, ctx, schemaPrefix + '/additionalProperties')
|
|
3359
3359
|
if (sub.length === 0) return
|
|
3360
|
-
lines.push(`${guard}{
|
|
3360
|
+
lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{${keep}{${sub.join('\n ')}\n }}}`)
|
|
3361
3361
|
}
|
|
3362
3362
|
|
|
3363
3363
|
function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
|
|
@@ -3397,7 +3397,7 @@ function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fa
|
|
|
3397
3397
|
ctx.closureVals.push(new Set(propKeys))
|
|
3398
3398
|
}
|
|
3399
3399
|
|
|
3400
|
-
lines.push(`${guard}{
|
|
3400
|
+
lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{`)
|
|
3401
3401
|
// propertyNames checks (merged)
|
|
3402
3402
|
if (pn) {
|
|
3403
3403
|
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
@@ -3434,7 +3434,7 @@ function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fa
|
|
|
3434
3434
|
lines.push(`}}`)
|
|
3435
3435
|
} else {
|
|
3436
3436
|
handledPropertyNames = !!pn
|
|
3437
|
-
lines.push(`${guard}{
|
|
3437
|
+
lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{`)
|
|
3438
3438
|
if (pn) {
|
|
3439
3439
|
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
3440
3440
|
if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
|
|
@@ -3478,7 +3478,7 @@ function genDependentSchemasC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3478
3478
|
function genPropertyNamesFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
3479
3479
|
const ki = ctx.varCounter++
|
|
3480
3480
|
const p = pathExpr || '""'
|
|
3481
|
-
lines.push(`;if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){
|
|
3481
|
+
lines.push(`;if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${ownHead(ctx, v, `_k${ki}`, 'const')}{${errPushC(ctx, 'not', `${schemaPrefix}/propertyNames`, pathExpr, '{}', "'boolean schema is false'", v, `{keyword:'not',instancePath:${p},schemaPath:'${schemaPrefix}/propertyNames'${ordinalField(ctx, `${schemaPrefix}/propertyNames`)},params:{},message:'boolean schema is false'}`)}}}`)
|
|
3482
3482
|
}
|
|
3483
3483
|
|
|
3484
3484
|
function genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
@@ -3488,7 +3488,7 @@ function genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
|
3488
3488
|
// here that would be the key where the interpreter's error points at the
|
|
3489
3489
|
// object, so that one is left to the plain function and enrichment.
|
|
3490
3490
|
if (!isSimplePN(pn) && ctx.rich) throw DECLINE
|
|
3491
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){
|
|
3491
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${ownHead(ctx, v, `_k${ki}`, 'const')}{`)
|
|
3492
3492
|
if (!isSimplePN(pn)) {
|
|
3493
3493
|
genCodeC(pn, `_k${ki}`, pathExpr, lines, ctx, schemaPrefix + '/propertyNames')
|
|
3494
3494
|
lines.push(`}}`)
|
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) {
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.52.0",
|
|
4
4
|
"description": "JSON Schema validator that compiles to plain JavaScript, still runs where code generation is blocked, and can be compiled away at build time. Passes the full official test suite.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"require": "./index.js"
|
|
25
25
|
},
|
|
26
26
|
"import": {
|
|
27
|
+
"module": "./index.bundle.mjs",
|
|
27
28
|
"module-sync": "./index.node.mjs",
|
|
28
29
|
"default": "./index.mjs"
|
|
29
30
|
},
|
|
@@ -137,6 +138,7 @@
|
|
|
137
138
|
"index.js",
|
|
138
139
|
"index.mjs",
|
|
139
140
|
"index.node.mjs",
|
|
141
|
+
"index.bundle.mjs",
|
|
140
142
|
"index.browser.mjs",
|
|
141
143
|
"index.edge.js",
|
|
142
144
|
"index.edge.mjs",
|
|
@@ -172,13 +174,13 @@
|
|
|
172
174
|
"LICENSE"
|
|
173
175
|
],
|
|
174
176
|
"optionalDependencies": {
|
|
175
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
176
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
177
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
178
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
179
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
180
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
181
|
-
"@ata-validator/native-win32-x64": "1.
|
|
177
|
+
"@ata-validator/native-darwin-arm64": "1.52.0",
|
|
178
|
+
"@ata-validator/native-darwin-x64": "1.52.0",
|
|
179
|
+
"@ata-validator/native-linux-arm64-gnu": "1.52.0",
|
|
180
|
+
"@ata-validator/native-linux-arm64-musl": "1.52.0",
|
|
181
|
+
"@ata-validator/native-linux-x64-gnu": "1.52.0",
|
|
182
|
+
"@ata-validator/native-linux-x64-musl": "1.52.0",
|
|
183
|
+
"@ata-validator/native-win32-x64": "1.52.0"
|
|
182
184
|
},
|
|
183
185
|
"peerDependencies": {
|
|
184
186
|
"yaml": "^2.0.0"
|