ata-validator 1.62.1 → 1.63.1
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 +63 -8
- package/build.mjs +1 -0
- package/index.browser.mjs +23 -4
- package/index.bundle.mjs +14 -4
- package/index.edge.mjs +14 -4
- package/index.js +7 -2
- package/lib/aot-build.js +18 -0
- package/lib/codegen-engine.js +72 -3
- package/lib/compiled-common.js +2 -1
- package/lib/email-literal.js +4 -1
- package/lib/formats-source.js +39 -40
- package/lib/interpreter-core.js +148 -70
- package/lib/ir/emit-deps.mjs +131 -0
- package/lib/ir/emit.mjs +707 -0
- package/lib/ir/front.mjs +865 -0
- package/lib/ir/index.mjs +10 -0
- package/lib/ir/refs.mjs +108 -0
- package/lib/ir/run.mjs +520 -0
- package/lib/ir/table.mjs +233 -0
- package/lib/js-compiler-errors.js +9 -4
- package/lib/kernels/collapse.mjs +114 -0
- package/lib/kernels/custom.mjs +70 -0
- package/lib/kernels/fill.mjs +6 -0
- package/lib/kernels/formats-small.mjs +40 -0
- package/lib/kernels/formats.mjs +29 -0
- package/lib/kernels/index.mjs +11 -0
- package/lib/kernels/kernels.mjs +285 -0
- package/lib/kernels/leaves.mjs +28 -0
- package/lib/kernels/order.mjs +25 -0
- package/lib/kernels/plan-rich.mjs +17 -0
- package/lib/kernels/plan.d.ts +9 -0
- package/lib/kernels/plan.mjs +128 -0
- package/lib/kernels/preprocess.mjs +223 -0
- package/lib/kernels/rank.mjs +97 -0
- package/lib/kernels/rt.mjs +249 -0
- package/lib/kernels/table.mjs +182 -0
- package/lib/kernels/unique.mjs +157 -0
- package/lib/native-positions.js +1 -1
- package/lib/openapi.d.ts +2 -0
- package/lib/openapi.js +128 -0
- package/lib/plan-compiler.js +113 -14
- package/lib/public-api.js +7 -2
- package/lib/rejections.js +3 -1
- package/lib/schema-helpers.js +12 -0
- package/lib/schema-helpers.mjs +20 -0
- package/lib/schema-order.js +4 -2
- package/lib/shape-classifier.js +13 -1
- package/lib/standard-schema.js +1 -1
- package/lib/tier0.js +38 -8
- package/lib/validator-core.js +59 -36
- package/lib/version.js +1 -1
- package/lite.js +7 -2
- package/package.json +26 -8
package/lib/interpreter-core.js
CHANGED
|
@@ -89,8 +89,6 @@ function indexSchemas(rootSchema, schemaMap, flat) {
|
|
|
89
89
|
state.rootBase = rootBase;
|
|
90
90
|
if (flat === true && !schemaMap && typeof rootSchema === 'object' && rootSchema !== null) {
|
|
91
91
|
state.resources.set(rootBase, rootSchema);
|
|
92
|
-
state.anchors.set(rootBase, new Map());
|
|
93
|
-
state.dynamicAnchors.set(rootBase, new Map());
|
|
94
92
|
state.nodeBase.set(rootSchema, rootBase);
|
|
95
93
|
return state;
|
|
96
94
|
}
|
|
@@ -107,11 +105,15 @@ function indexSchemas(rootSchema, schemaMap, flat) {
|
|
|
107
105
|
return state;
|
|
108
106
|
}
|
|
109
107
|
|
|
108
|
+
function anchorMap(maps, baseUri) {
|
|
109
|
+
let m = maps.get(baseUri);
|
|
110
|
+
if (m === undefined) maps.set(baseUri, (m = new Map()));
|
|
111
|
+
return m;
|
|
112
|
+
}
|
|
113
|
+
|
|
110
114
|
function indexResource(node, baseUri, state) {
|
|
111
115
|
if (typeof node !== 'object' || node === null) return;
|
|
112
116
|
if (!state.resources.has(baseUri)) state.resources.set(baseUri, node);
|
|
113
|
-
if (!state.anchors.has(baseUri)) state.anchors.set(baseUri, new Map());
|
|
114
|
-
if (!state.dynamicAnchors.has(baseUri)) state.dynamicAnchors.set(baseUri, new Map());
|
|
115
117
|
walkSchema(node, baseUri, state, true);
|
|
116
118
|
}
|
|
117
119
|
|
|
@@ -128,11 +130,14 @@ function walkSchema(node, baseUri, state, isResourceRoot) {
|
|
|
128
130
|
return;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
|
|
133
|
+
// A resource's anchor maps are made at its first anchor: most schemas have
|
|
134
|
+
// none, and two empty maps per resource were a sixth of a small schema's
|
|
135
|
+
// interpreter. Every reader takes a missing map as no anchor.
|
|
136
|
+
if (typeof node.$anchor === 'string') anchorMap(state.anchors, baseUri).set(node.$anchor, node);
|
|
132
137
|
if (typeof node.$dynamicAnchor === 'string') {
|
|
133
|
-
state.dynamicAnchors
|
|
138
|
+
anchorMap(state.dynamicAnchors, baseUri).set(node.$dynamicAnchor, node);
|
|
134
139
|
// A $dynamicAnchor is also addressable as a plain anchor.
|
|
135
|
-
state.anchors
|
|
140
|
+
anchorMap(state.anchors, baseUri).set(node.$dynamicAnchor, node);
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
for (const kw of SCHEMA_KEYWORDS.single) {
|
|
@@ -242,6 +247,7 @@ function enumMember(P, data) {
|
|
|
242
247
|
// JSON equality: the same function the code generator emits as `_deq`, so the
|
|
243
248
|
// engines agree on what equal means (an object's own keys, in any order).
|
|
244
249
|
const deepEqual = _deq;
|
|
250
|
+
const propEnum = Object.prototype.propertyIsEnumerable;
|
|
245
251
|
|
|
246
252
|
function codePointLength(s) {
|
|
247
253
|
let n = 0;
|
|
@@ -364,6 +370,60 @@ function isObjectMap(v) {
|
|
|
364
370
|
// per section so eval() can skip whole blocks without touching the schema
|
|
365
371
|
// object. Plans have a fixed shape, which keeps eval() monomorphic where the
|
|
366
372
|
// raw schema objects are not.
|
|
373
|
+
// The keyword values of a schema node, from one pass over its enumerable
|
|
374
|
+
// names (its own, as JSON gives them), in a record of one shape for link().
|
|
375
|
+
const KEYWORD_RECORD = () => ({ '$ref': undefined, '$dynamicRef': undefined, type: undefined, enum: undefined, const: undefined, minimum: undefined, maximum: undefined, exclusiveMinimum: undefined, exclusiveMaximum: undefined, multipleOf: undefined, minLength: undefined, maxLength: undefined, pattern: undefined, format: undefined, minItems: undefined, maxItems: undefined, uniqueItems: undefined, prefixItems: undefined, items: undefined, contains: undefined, minContains: undefined, maxContains: undefined, required: undefined, minProperties: undefined, maxProperties: undefined, dependentRequired: undefined, propertyNames: undefined, properties: undefined, patternProperties: undefined, additionalProperties: undefined, dependentSchemas: undefined, propertyDependencies: undefined, allOf: undefined, anyOf: undefined, oneOf: undefined, not: undefined, if: undefined, then: undefined, else: undefined, unevaluatedProperties: undefined, unevaluatedItems: undefined });
|
|
376
|
+
function readKeywords (schema) {
|
|
377
|
+
const S = KEYWORD_RECORD();
|
|
378
|
+
for (const k in schema) {
|
|
379
|
+
const v = schema[k];
|
|
380
|
+
switch (k) {
|
|
381
|
+
case '$ref': S['$ref'] = v; break;
|
|
382
|
+
case '$dynamicRef': S['$dynamicRef'] = v; break;
|
|
383
|
+
case 'type': S.type = v; break;
|
|
384
|
+
case 'enum': S.enum = v; break;
|
|
385
|
+
case 'const': S.const = v; break;
|
|
386
|
+
case 'minimum': S.minimum = v; break;
|
|
387
|
+
case 'maximum': S.maximum = v; break;
|
|
388
|
+
case 'exclusiveMinimum': S.exclusiveMinimum = v; break;
|
|
389
|
+
case 'exclusiveMaximum': S.exclusiveMaximum = v; break;
|
|
390
|
+
case 'multipleOf': S.multipleOf = v; break;
|
|
391
|
+
case 'minLength': S.minLength = v; break;
|
|
392
|
+
case 'maxLength': S.maxLength = v; break;
|
|
393
|
+
case 'pattern': S.pattern = v; break;
|
|
394
|
+
case 'format': S.format = v; break;
|
|
395
|
+
case 'minItems': S.minItems = v; break;
|
|
396
|
+
case 'maxItems': S.maxItems = v; break;
|
|
397
|
+
case 'uniqueItems': S.uniqueItems = v; break;
|
|
398
|
+
case 'prefixItems': S.prefixItems = v; break;
|
|
399
|
+
case 'items': S.items = v; break;
|
|
400
|
+
case 'contains': S.contains = v; break;
|
|
401
|
+
case 'minContains': S.minContains = v; break;
|
|
402
|
+
case 'maxContains': S.maxContains = v; break;
|
|
403
|
+
case 'required': S.required = v; break;
|
|
404
|
+
case 'minProperties': S.minProperties = v; break;
|
|
405
|
+
case 'maxProperties': S.maxProperties = v; break;
|
|
406
|
+
case 'dependentRequired': S.dependentRequired = v; break;
|
|
407
|
+
case 'propertyNames': S.propertyNames = v; break;
|
|
408
|
+
case 'properties': S.properties = v; break;
|
|
409
|
+
case 'patternProperties': S.patternProperties = v; break;
|
|
410
|
+
case 'additionalProperties': S.additionalProperties = v; break;
|
|
411
|
+
case 'dependentSchemas': S.dependentSchemas = v; break;
|
|
412
|
+
case 'propertyDependencies': S.propertyDependencies = v; break;
|
|
413
|
+
case 'allOf': S.allOf = v; break;
|
|
414
|
+
case 'anyOf': S.anyOf = v; break;
|
|
415
|
+
case 'oneOf': S.oneOf = v; break;
|
|
416
|
+
case 'not': S.not = v; break;
|
|
417
|
+
case 'if': S.if = v; break;
|
|
418
|
+
case 'then': S.then = v; break;
|
|
419
|
+
case 'else': S.else = v; break;
|
|
420
|
+
case 'unevaluatedProperties': S.unevaluatedProperties = v; break;
|
|
421
|
+
case 'unevaluatedItems': S.unevaluatedItems = v; break;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return S;
|
|
425
|
+
}
|
|
426
|
+
|
|
367
427
|
class Plan {
|
|
368
428
|
// The plan is registered in the cache before `link` runs, so a schema
|
|
369
429
|
// object that contains itself structurally resolves to the same plan
|
|
@@ -374,6 +434,13 @@ class Plan {
|
|
|
374
434
|
|
|
375
435
|
link(interp) {
|
|
376
436
|
const schema = this.schema;
|
|
437
|
+
// The keywords read once, by one pass over the schema's own names, into
|
|
438
|
+
// a record of one shape: read by name on the schema, each absent keyword
|
|
439
|
+
// was a lookup that missed on an object of yet another shape, and with
|
|
440
|
+
// many schemas in a process the 41 reads cost 3.7 us a node against 8 ns
|
|
441
|
+
// for the pass (measured on 64 shapes). Reads on the record hit one
|
|
442
|
+
// shape every time.
|
|
443
|
+
const S = readKeywords(schema);
|
|
377
444
|
// Children are linked as plans (or booleans) up front, so a visit never
|
|
378
445
|
// goes back through the plan cache. Reference targets are resolved at
|
|
379
446
|
// evaluation time and cached per base URI.
|
|
@@ -382,21 +449,21 @@ class Plan {
|
|
|
382
449
|
this.refBase0 = undefined;
|
|
383
450
|
this.refRes0 = null;
|
|
384
451
|
this.dynCache = null;
|
|
385
|
-
this.ref = typeof
|
|
386
|
-
this.dynamicRef = typeof
|
|
452
|
+
this.ref = typeof S.$ref === 'string' ? S.$ref : null;
|
|
453
|
+
this.dynamicRef = typeof S.$dynamicRef === 'string' ? S.$dynamicRef : null;
|
|
387
454
|
this.tracked = this.ref !== null || this.dynamicRef !== null;
|
|
388
455
|
|
|
389
456
|
this.typeMask = 0;
|
|
390
457
|
this.typeNames = null;
|
|
391
|
-
if (
|
|
392
|
-
const types = Array.isArray(
|
|
458
|
+
if (S.type !== undefined) {
|
|
459
|
+
const types = Array.isArray(S.type) ? S.type : [S.type];
|
|
393
460
|
let mask = 0;
|
|
394
461
|
for (const t of types) mask |= typeBit(t);
|
|
395
462
|
this.typeMask = mask;
|
|
396
463
|
this.typeNames = types;
|
|
397
464
|
}
|
|
398
|
-
this.hasType =
|
|
399
|
-
this.enum =
|
|
465
|
+
this.hasType = S.type !== undefined;
|
|
466
|
+
this.enum = S.enum !== undefined ? S.enum : null;
|
|
400
467
|
// Membership for enumMember(): the primitive values in a Set, so a long
|
|
401
468
|
// enum answers in one lookup, and the object and array values, which only
|
|
402
469
|
// deepEqual can compare, in a list. Scanning every value with deepEqual
|
|
@@ -412,28 +479,28 @@ class Plan {
|
|
|
412
479
|
else if (!(typeof v === 'number' && v !== v)) this.enumPrims.add(v);
|
|
413
480
|
}
|
|
414
481
|
}
|
|
415
|
-
this.hasConst =
|
|
416
|
-
this.const =
|
|
417
|
-
|
|
418
|
-
this.minimum = typeof
|
|
419
|
-
this.maximum = typeof
|
|
420
|
-
this.exclusiveMinimum = typeof
|
|
421
|
-
this.exclusiveMaximum = typeof
|
|
422
|
-
this.multipleOf = typeof
|
|
482
|
+
this.hasConst = S.const !== undefined;
|
|
483
|
+
this.const = S.const;
|
|
484
|
+
|
|
485
|
+
this.minimum = typeof S.minimum === 'number' ? S.minimum : undefined;
|
|
486
|
+
this.maximum = typeof S.maximum === 'number' ? S.maximum : undefined;
|
|
487
|
+
this.exclusiveMinimum = typeof S.exclusiveMinimum === 'number' ? S.exclusiveMinimum : undefined;
|
|
488
|
+
this.exclusiveMaximum = typeof S.exclusiveMaximum === 'number' ? S.exclusiveMaximum : undefined;
|
|
489
|
+
this.multipleOf = typeof S.multipleOf === 'number' ? S.multipleOf : undefined;
|
|
423
490
|
this.hasNumber = this.minimum !== undefined || this.maximum !== undefined || this.exclusiveMinimum !== undefined || this.exclusiveMaximum !== undefined || this.multipleOf !== undefined;
|
|
424
491
|
|
|
425
|
-
this.minLength =
|
|
426
|
-
this.maxLength =
|
|
427
|
-
this.pattern =
|
|
428
|
-
this.patternSource =
|
|
429
|
-
this.format =
|
|
492
|
+
this.minLength = S.minLength;
|
|
493
|
+
this.maxLength = S.maxLength;
|
|
494
|
+
this.pattern = S.pattern !== undefined ? interp.pattern(S.pattern) : null;
|
|
495
|
+
this.patternSource = S.pattern;
|
|
496
|
+
this.format = S.format;
|
|
430
497
|
this.formatFn = null;
|
|
431
|
-
if (
|
|
498
|
+
if (S.format !== undefined) {
|
|
432
499
|
// Own properties only, as the generated code reads them: an inherited
|
|
433
500
|
// Object member ('hasOwnProperty', 'constructor') is not a check.
|
|
434
501
|
const own = Object.prototype.hasOwnProperty;
|
|
435
502
|
const uf = interp.userFormats;
|
|
436
|
-
const f =
|
|
503
|
+
const f = S.format;
|
|
437
504
|
const table = require('./formats').formatChecks();
|
|
438
505
|
const fc = (uf && typeof f === 'string' && own.call(uf, f) && typeof uf[f] === 'function') ? uf[f]
|
|
439
506
|
: (typeof f === 'string' && own.call(table, f) && typeof table[f] === 'function' ? table[f] : null);
|
|
@@ -443,31 +510,31 @@ class Plan {
|
|
|
443
510
|
// A format compat registered for numbers (`type: 'number'`), checked on
|
|
444
511
|
// numbers as the reference checks it.
|
|
445
512
|
const nf = interp.numberFormats;
|
|
446
|
-
this.numFormatFn =
|
|
513
|
+
this.numFormatFn = S.format !== undefined && nf !== null && typeof nf[S.format] === 'function' ? nf[S.format] : null;
|
|
447
514
|
if (this.numFormatFn !== null) this.hasNumber = true;
|
|
448
515
|
|
|
449
|
-
this.minItems =
|
|
450
|
-
this.maxItems =
|
|
451
|
-
this.uniqueItems =
|
|
452
|
-
this.prefixItems = Array.isArray(
|
|
453
|
-
this.items =
|
|
454
|
-
this.contains =
|
|
455
|
-
this.minContains =
|
|
456
|
-
this.maxContains =
|
|
516
|
+
this.minItems = S.minItems;
|
|
517
|
+
this.maxItems = S.maxItems;
|
|
518
|
+
this.uniqueItems = S.uniqueItems === true;
|
|
519
|
+
this.prefixItems = Array.isArray(S.prefixItems) ? S.prefixItems.map(child) : null;
|
|
520
|
+
this.items = S.items !== undefined ? child(S.items) : undefined;
|
|
521
|
+
this.contains = S.contains !== undefined ? child(S.contains) : undefined;
|
|
522
|
+
this.minContains = S.minContains;
|
|
523
|
+
this.maxContains = S.maxContains;
|
|
457
524
|
this.hasArray = this.minItems !== undefined || this.maxItems !== undefined || this.uniqueItems || this.prefixItems !== null || this.items !== undefined || this.contains !== undefined;
|
|
458
525
|
|
|
459
|
-
this.required = Array.isArray(
|
|
460
|
-
this.minProperties =
|
|
461
|
-
this.maxProperties =
|
|
462
|
-
this.dependentRequired = isObjectMap(
|
|
463
|
-
this.propertyNames =
|
|
526
|
+
this.required = Array.isArray(S.required) ? S.required : null;
|
|
527
|
+
this.minProperties = S.minProperties;
|
|
528
|
+
this.maxProperties = S.maxProperties;
|
|
529
|
+
this.dependentRequired = isObjectMap(S.dependentRequired) ? Object.entries(S.dependentRequired) : null;
|
|
530
|
+
this.propertyNames = S.propertyNames !== undefined ? child(S.propertyNames) : undefined;
|
|
464
531
|
this.properties = null;
|
|
465
|
-
if (isObjectMap(
|
|
532
|
+
if (isObjectMap(S.properties)) {
|
|
466
533
|
this.properties = new Map();
|
|
467
|
-
for (const key of Object.keys(
|
|
534
|
+
for (const key of Object.keys(S.properties)) {
|
|
468
535
|
const ek = escapePointer(key);
|
|
469
536
|
// Escaped once here; the walk only concatenates.
|
|
470
|
-
this.properties.set(key, { node: child(
|
|
537
|
+
this.properties.set(key, { node: child(S.properties[key]), seg: '/' + ek, schemaSeg: '/properties/' + ek });
|
|
471
538
|
}
|
|
472
539
|
}
|
|
473
540
|
// A required key holding undefined is missing. Where the key is also a
|
|
@@ -486,26 +553,26 @@ class Plan {
|
|
|
486
553
|
}
|
|
487
554
|
}
|
|
488
555
|
this.patternProperties = null;
|
|
489
|
-
if (isObjectMap(
|
|
490
|
-
this.patternProperties = Object.keys(
|
|
556
|
+
if (isObjectMap(S.patternProperties)) {
|
|
557
|
+
this.patternProperties = Object.keys(S.patternProperties).map((src) => ({ src, re: interp.pattern(src), node: child(S.patternProperties[src]) }));
|
|
491
558
|
}
|
|
492
|
-
this.additionalProperties =
|
|
493
|
-
this.dependentSchemas = isObjectMap(
|
|
559
|
+
this.additionalProperties = S.additionalProperties !== undefined ? child(S.additionalProperties) : undefined;
|
|
560
|
+
this.dependentSchemas = isObjectMap(S.dependentSchemas) ? Object.entries(S.dependentSchemas).map(([k, v]) => [k, child(v)]) : null;
|
|
494
561
|
this.propertyDependencies = null;
|
|
495
|
-
if (isObjectMap(
|
|
496
|
-
this.propertyDependencies = Object.entries(
|
|
562
|
+
if (isObjectMap(S.propertyDependencies)) {
|
|
563
|
+
this.propertyDependencies = Object.entries(S.propertyDependencies)
|
|
497
564
|
.filter(([, choices]) => isObjectMap(choices))
|
|
498
565
|
.map(([k, choices]) => [k, new Map(Object.keys(choices).map((v) => [v, child(choices[v])]))]);
|
|
499
566
|
}
|
|
500
567
|
this.hasObject = this.required !== null || this.minProperties !== undefined || this.maxProperties !== undefined || this.dependentRequired !== null || this.propertyNames !== undefined || this.properties !== null || this.patternProperties !== null || this.additionalProperties !== undefined || this.dependentSchemas !== null || this.propertyDependencies !== null;
|
|
501
568
|
|
|
502
|
-
this.allOf = Array.isArray(
|
|
503
|
-
this.anyOf = Array.isArray(
|
|
504
|
-
this.oneOf = Array.isArray(
|
|
505
|
-
this.not =
|
|
506
|
-
this.if =
|
|
507
|
-
this.then =
|
|
508
|
-
this.else =
|
|
569
|
+
this.allOf = Array.isArray(S.allOf) ? S.allOf.map(child) : null;
|
|
570
|
+
this.anyOf = Array.isArray(S.anyOf) ? S.anyOf.map(child) : null;
|
|
571
|
+
this.oneOf = Array.isArray(S.oneOf) ? S.oneOf.map(child) : null;
|
|
572
|
+
this.not = S.not !== undefined ? child(S.not) : undefined;
|
|
573
|
+
this.if = S.if !== undefined ? child(S.if) : undefined;
|
|
574
|
+
this.then = S.then !== undefined ? child(S.then) : undefined;
|
|
575
|
+
this.else = S.else !== undefined ? child(S.else) : undefined;
|
|
509
576
|
// Macro keywords: the schema the macro returns is applied in place, the
|
|
510
577
|
// way the reference validator does it, so its errors carry the keyword's
|
|
511
578
|
// path and the keyword adds an error of its own when that schema fails.
|
|
@@ -522,13 +589,13 @@ class Plan {
|
|
|
522
589
|
}
|
|
523
590
|
this.hasApplicators = this.allOf !== null || this.anyOf !== null || this.oneOf !== null || this.not !== undefined || this.if !== undefined || this.macros !== null;
|
|
524
591
|
|
|
525
|
-
this.unevaluatedProperties =
|
|
526
|
-
this.unevaluatedItems =
|
|
592
|
+
this.unevaluatedProperties = S.unevaluatedProperties !== undefined ? child(S.unevaluatedProperties) : undefined;
|
|
593
|
+
this.unevaluatedItems = S.unevaluatedItems !== undefined ? child(S.unevaluatedItems) : undefined;
|
|
527
594
|
// A `false` here rejects the member outright, and the error belongs to this
|
|
528
595
|
// keyword on this object, not to a boolean schema that happens to sit
|
|
529
596
|
// under it. Recorded at compile time so the check below is one test.
|
|
530
|
-
this.unevaluatedPropertiesFalse =
|
|
531
|
-
this.unevaluatedItemsFalse =
|
|
597
|
+
this.unevaluatedPropertiesFalse = S.unevaluatedProperties === false;
|
|
598
|
+
this.unevaluatedItemsFalse = S.unevaluatedItems === false;
|
|
532
599
|
this.hasUnevaluated = this.unevaluatedProperties !== undefined || this.unevaluatedItems !== undefined;
|
|
533
600
|
|
|
534
601
|
// Custom keywords present on this node, in schema order. Each op holds
|
|
@@ -847,7 +914,8 @@ class Interpreter {
|
|
|
847
914
|
// 2020-12 requires bookending for `$dynamicRef`; v1 removes the
|
|
848
915
|
// requirement. See the `$dynamicRef` branch in eval().
|
|
849
916
|
this.bookending = !opts.v1;
|
|
850
|
-
|
|
917
|
+
// Made at the first pattern.
|
|
918
|
+
this.patternCache = null;
|
|
851
919
|
// Plans are built on first visit; a schema node is never re-read after.
|
|
852
920
|
this.plans = new Map();
|
|
853
921
|
this.rootNode = this.node(rootSchema);
|
|
@@ -919,6 +987,7 @@ class Interpreter {
|
|
|
919
987
|
}
|
|
920
988
|
|
|
921
989
|
pattern(src) {
|
|
990
|
+
if (this.patternCache === null) this.patternCache = new Map();
|
|
922
991
|
let re = this.patternCache.get(src);
|
|
923
992
|
if (!re) {
|
|
924
993
|
if (/\\[pP]\{/.test(src)) {
|
|
@@ -1244,9 +1313,15 @@ class Interpreter {
|
|
|
1244
1313
|
}
|
|
1245
1314
|
}
|
|
1246
1315
|
|
|
1247
|
-
// objects
|
|
1316
|
+
// objects. The object's own names, enumerable or not: a declared
|
|
1317
|
+
// property is read by name, as the generated code reads it, so one
|
|
1318
|
+
// hidden under its name is checked; patternProperties, propertyNames
|
|
1319
|
+
// and additionalProperties see the names for-in sees, as there too, and
|
|
1320
|
+
// minProperties and maxProperties count those.
|
|
1248
1321
|
if (P.hasObject && bits === T_OBJECT) {
|
|
1249
|
-
const keys = Object.
|
|
1322
|
+
const keys = Object.getOwnPropertyNames(data);
|
|
1323
|
+
let hidden = 0;
|
|
1324
|
+
if (P.minProperties !== undefined || P.maxProperties !== undefined) for (let k = 0; k < keys.length; k++) if (!propEnum.call(data, keys[k])) hidden++;
|
|
1250
1325
|
if (P.required !== null) {
|
|
1251
1326
|
const req = P.required;
|
|
1252
1327
|
for (let i = 0; i < req.length; i++) {
|
|
@@ -1254,8 +1329,8 @@ class Interpreter {
|
|
|
1254
1329
|
if (!Object.hasOwn(data, key) || (P.requiredUndef !== null && P.requiredUndef[i] && data[key] === undefined)) { errors === NOERRORS || errors.push(err('required', 'required', instancePath, schemaPath + '/required', { missingProperty: key }, `must have required property '${key}'`)); valid = false; }
|
|
1255
1330
|
}
|
|
1256
1331
|
}
|
|
1257
|
-
if (P.minProperties !== undefined && keys.length < P.minProperties) { errors === NOERRORS || errors.push(err('minProperties', 'minProperties', instancePath, schemaPath + '/minProperties', { limit: P.minProperties }, `must NOT have fewer than ${P.minProperties} properties`)); valid = false; }
|
|
1258
|
-
if (P.maxProperties !== undefined && keys.length > P.maxProperties) { errors === NOERRORS || errors.push(err('maxProperties', 'maxProperties', instancePath, schemaPath + '/maxProperties', { limit: P.maxProperties }, `must NOT have more than ${P.maxProperties} properties`)); valid = false; }
|
|
1332
|
+
if (P.minProperties !== undefined && keys.length - hidden < P.minProperties) { errors === NOERRORS || errors.push(err('minProperties', 'minProperties', instancePath, schemaPath + '/minProperties', { limit: P.minProperties }, `must NOT have fewer than ${P.minProperties} properties`)); valid = false; }
|
|
1333
|
+
if (P.maxProperties !== undefined && keys.length - hidden > P.maxProperties) { errors === NOERRORS || errors.push(err('maxProperties', 'maxProperties', instancePath, schemaPath + '/maxProperties', { limit: P.maxProperties }, `must NOT have more than ${P.maxProperties} properties`)); valid = false; }
|
|
1259
1334
|
if (P.dependentRequired !== null) {
|
|
1260
1335
|
for (const [key, deps] of P.dependentRequired) {
|
|
1261
1336
|
if (Object.hasOwn(data, key)) {
|
|
@@ -1267,6 +1342,7 @@ class Interpreter {
|
|
|
1267
1342
|
}
|
|
1268
1343
|
if (P.propertyNames !== undefined) {
|
|
1269
1344
|
for (const key of keys) {
|
|
1345
|
+
if (!propEnum.call(data, key)) continue;
|
|
1270
1346
|
if (!this.eval(P.propertyNames, key, base, dynScope, errors, instancePath, E ? schemaPath + '/propertyNames' : '', stack, DISCARD)) valid = false;
|
|
1271
1347
|
}
|
|
1272
1348
|
}
|
|
@@ -1276,6 +1352,7 @@ class Interpreter {
|
|
|
1276
1352
|
const key = keys[k];
|
|
1277
1353
|
let evaluated = false;
|
|
1278
1354
|
const prop = props !== null ? props.get(key) : undefined;
|
|
1355
|
+
const seen = prop !== undefined && patterns === null && P.additionalProperties === undefined || propEnum.call(data, key);
|
|
1279
1356
|
if (prop !== undefined) {
|
|
1280
1357
|
// A declared property whose value is undefined is not checked, as the
|
|
1281
1358
|
// generated code does not check it: no JSON document carries one, and
|
|
@@ -1287,7 +1364,7 @@ class Interpreter {
|
|
|
1287
1364
|
} else if (P.requiredInProps !== null && P.requiredInProps.has(key)) { errors === NOERRORS || errors.push(err('required', 'required', instancePath, schemaPath + '/required', { missingProperty: key }, `must have required property '${key}'`)); valid = false; }
|
|
1288
1365
|
evaluated = true;
|
|
1289
1366
|
}
|
|
1290
|
-
if (patterns !== null) {
|
|
1367
|
+
if (patterns !== null && seen) {
|
|
1291
1368
|
for (let pi = 0; pi < patterns.length; pi++) {
|
|
1292
1369
|
const pp = patterns[pi];
|
|
1293
1370
|
if (pp.re.test(key)) {
|
|
@@ -1296,7 +1373,7 @@ class Interpreter {
|
|
|
1296
1373
|
}
|
|
1297
1374
|
}
|
|
1298
1375
|
}
|
|
1299
|
-
if (!evaluated && P.additionalProperties !== undefined) {
|
|
1376
|
+
if (!evaluated && seen && P.additionalProperties !== undefined) {
|
|
1300
1377
|
// `additionalProperties: false` keeps its own keyword and names the
|
|
1301
1378
|
// property it turned away. Running it as a plain false subschema
|
|
1302
1379
|
// reported `not` at the key's path, which drops both.
|
|
@@ -1549,7 +1626,8 @@ function planCompiler() {
|
|
|
1549
1626
|
let installCompat = null;
|
|
1550
1627
|
let compatInstalled = null;
|
|
1551
1628
|
function setCompat(install) {
|
|
1552
|
-
|
|
1629
|
+
// esm.sh hands over a module of one function as { default: fn }.
|
|
1630
|
+
installCompat = typeof install === 'function' ? install : install && install.default;
|
|
1553
1631
|
compatInstalled = null;
|
|
1554
1632
|
}
|
|
1555
1633
|
function compatExt() {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Generated by scripts/gen-ir-emit-deps.js from lib/js-compiler.js and lib/suggestions.js. Do not edit.
|
|
2
|
+
const FIXED_LITERAL = /[A-Za-z0-9 _\-:@#%&=,;'"<>!~`\/]/
|
|
3
|
+
function compilePatternInline(pattern, varName) {
|
|
4
|
+
let m = pattern.match(/^\^(\[[\w\-]+\])\{(\d+)\}\$$/)
|
|
5
|
+
if (m) {
|
|
6
|
+
const len = parseInt(m[2])
|
|
7
|
+
if (len <= 16) {
|
|
8
|
+
const checks = []
|
|
9
|
+
for (let i = 0; i < len; i++) {
|
|
10
|
+
const ck = charClassToCheck(m[1], `${varName}.charCodeAt(${i})`)
|
|
11
|
+
if (!ck) return null
|
|
12
|
+
checks.push(ck)
|
|
13
|
+
}
|
|
14
|
+
return `${varName}.length===${len}&&${checks.join('&&')}`
|
|
15
|
+
}
|
|
16
|
+
const rangeCheck = charClassToCheck(m[1], `${varName}.charCodeAt(_pi)`)
|
|
17
|
+
if (!rangeCheck) return null
|
|
18
|
+
return `${varName}.length===${len}&&(()=>{for(let _pi=0;_pi<${len};_pi++){if(!(${rangeCheck}))return false}return true})()`
|
|
19
|
+
}
|
|
20
|
+
m = pattern.match(/^\^(\[[\w\-]+\])\+\$$/)
|
|
21
|
+
if (m) {
|
|
22
|
+
const rangeCheck = charClassToCheck(m[1], `${varName}.charCodeAt(_pi)`)
|
|
23
|
+
if (!rangeCheck) return null
|
|
24
|
+
return `${varName}.length>0&&(()=>{for(let _pi=0;_pi<${varName}.length;_pi++){if(!(${rangeCheck}))return false}return true})()`
|
|
25
|
+
}
|
|
26
|
+
m = pattern.match(/^\^(\[[\w\-]+\])\{(\d+),(\d+)\}\$$/)
|
|
27
|
+
if (m) {
|
|
28
|
+
const rangeCheck = charClassToCheck(m[1], `${varName}.charCodeAt(_pi)`)
|
|
29
|
+
if (!rangeCheck) return null
|
|
30
|
+
const min = parseInt(m[2]), max = parseInt(m[3])
|
|
31
|
+
return `${varName}.length>=${min}&&${varName}.length<=${max}&&(()=>{for(let _pi=0;_pi<${varName}.length;_pi++){if(!(${rangeCheck}))return false}return true})()`
|
|
32
|
+
}
|
|
33
|
+
return fixedSequenceInline(pattern, varName)
|
|
34
|
+
}
|
|
35
|
+
function fixedSequenceInline(pattern, varName) {
|
|
36
|
+
if (pattern.length < 3 || pattern[0] !== '^' || pattern[pattern.length - 1] !== '$' || pattern[pattern.length - 2] === '\\') return null
|
|
37
|
+
const end = pattern.length - 1
|
|
38
|
+
const pieces = []
|
|
39
|
+
let i = 1
|
|
40
|
+
while (i < end) {
|
|
41
|
+
let piece
|
|
42
|
+
const c = pattern[i]
|
|
43
|
+
if (c === '[') {
|
|
44
|
+
const j = pattern.indexOf(']', i)
|
|
45
|
+
if (j < 0 || j >= end) return null
|
|
46
|
+
const cls = pattern.slice(i, j + 1)
|
|
47
|
+
if (!/^\[[\w-]+\]$/.test(cls)) return null
|
|
48
|
+
piece = { cls }
|
|
49
|
+
i = j + 1
|
|
50
|
+
} else if (c === '\\') {
|
|
51
|
+
const e = pattern[i + 1]
|
|
52
|
+
if (e === 'd') piece = { cls: '[0-9]' }
|
|
53
|
+
else if (e !== undefined && './()[]{}+*?^$|\\'.includes(e)) piece = { lit: e }
|
|
54
|
+
else return null
|
|
55
|
+
i += 2
|
|
56
|
+
} else if (FIXED_LITERAL.test(c)) {
|
|
57
|
+
piece = { lit: c }
|
|
58
|
+
i += 1
|
|
59
|
+
} else {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
let count = 1
|
|
63
|
+
if (pattern[i] === '{') {
|
|
64
|
+
const q = /^\{(\d+)\}/.exec(pattern.slice(i))
|
|
65
|
+
if (!q) return null
|
|
66
|
+
count = Number(q[1])
|
|
67
|
+
if (!Number.isSafeInteger(count) || count > 32 - pieces.length) return null
|
|
68
|
+
i += q[0].length
|
|
69
|
+
} else if (i < end && '*+?'.includes(pattern[i])) {
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
for (let k = 0; k < count; k++) pieces.push(piece)
|
|
73
|
+
if (pieces.length > 32) return null
|
|
74
|
+
}
|
|
75
|
+
if (pieces.length === 0) return null
|
|
76
|
+
const checks = []
|
|
77
|
+
for (let k = 0; k < pieces.length; k++) {
|
|
78
|
+
const at = `${varName}.charCodeAt(${k})`
|
|
79
|
+
if (pieces[k].lit !== undefined) checks.push(`${at}===${pieces[k].lit.charCodeAt(0)}`)
|
|
80
|
+
else {
|
|
81
|
+
const ck = charClassToCheck(pieces[k].cls, at)
|
|
82
|
+
if (!ck) return null
|
|
83
|
+
checks.push(`(${ck})`)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return `${varName}.length===${pieces.length}&&${checks.join('&&')}`
|
|
87
|
+
}
|
|
88
|
+
function charClassToCheck(charClass, codeExpr) {
|
|
89
|
+
const inner = charClass.slice(1, -1)
|
|
90
|
+
const ranges = []
|
|
91
|
+
let i = 0
|
|
92
|
+
while (i < inner.length) {
|
|
93
|
+
if (i + 2 < inner.length && inner[i + 1] === '-') {
|
|
94
|
+
ranges.push([inner.charCodeAt(i), inner.charCodeAt(i + 2)])
|
|
95
|
+
i += 3
|
|
96
|
+
} else {
|
|
97
|
+
ranges.push([inner.charCodeAt(i), inner.charCodeAt(i)])
|
|
98
|
+
i++
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (ranges.length === 0) return null
|
|
102
|
+
const checks = ranges.map(([lo, hi]) =>
|
|
103
|
+
lo === hi ? `${codeExpr}===${lo}` : `(${codeExpr}>=${lo}&&${codeExpr}<=${hi})`
|
|
104
|
+
)
|
|
105
|
+
return `(${checks.join('||')})`
|
|
106
|
+
}
|
|
107
|
+
const FORMAT_HINTS = {
|
|
108
|
+
email: (val) => {
|
|
109
|
+
if (typeof val !== 'string') return null;
|
|
110
|
+
if (!val.includes('@')) return "missing '@' and domain part";
|
|
111
|
+
if (val.split('@').length > 2) return "multiple '@' characters";
|
|
112
|
+
const [, dom] = val.split('@');
|
|
113
|
+
if (!dom || !dom.includes('.')) return 'domain part missing dot';
|
|
114
|
+
return null;
|
|
115
|
+
},
|
|
116
|
+
date: (val) => {
|
|
117
|
+
if (typeof val !== 'string') return null;
|
|
118
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(val);
|
|
119
|
+
if (!m) return 'expected YYYY-MM-DD layout';
|
|
120
|
+
const mo = +m[2]; if (mo < 1 || mo > 12) return 'month must be 01-12';
|
|
121
|
+
const d = +m[3]; if (d < 1 || d > 31) return 'day must be 01-31';
|
|
122
|
+
return null;
|
|
123
|
+
},
|
|
124
|
+
uuid: (val) => {
|
|
125
|
+
if (typeof val !== 'string') return null;
|
|
126
|
+
if (!/^[0-9a-fA-F-]+$/.test(val)) return 'expected hex digits and dashes';
|
|
127
|
+
return 'expected 8-4-4-4-12 hex layout';
|
|
128
|
+
},
|
|
129
|
+
ipv4: (val) => typeof val === 'string' ? 'expected four 0-255 octets separated by dots' : null,
|
|
130
|
+
};
|
|
131
|
+
export { compilePatternInline, FORMAT_HINTS };
|