ata-validator 1.22.1 → 1.24.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/CHANGELOG.md +30 -0
- package/bin/ata.js +27 -1
- package/compat.d.ts +5 -0
- package/compat.js +11 -2
- package/index.js +256 -70
- package/lib/aot-build.js +24 -2
- package/lib/aot-impl.js +14 -1
- package/lib/compat-errors.js +49 -22
- package/lib/js-compiler.js +15 -4
- package/lib/scan-compiler.js +1007 -0
- package/lib/scan-runtime.js +158 -0
- package/lib/strict-check.js +195 -0
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/lib/aot-impl.js
CHANGED
|
@@ -269,6 +269,16 @@ function toStandaloneModule(validator, opts) {
|
|
|
269
269
|
const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : '';
|
|
270
270
|
if (errSrc) {
|
|
271
271
|
errCore = `const errFn = function(d, _all) {\n ${errSrc}\n};\n`;
|
|
272
|
+
} else if (opts && typeof opts.onWarning === 'function') {
|
|
273
|
+
// The caller asked for error detail and is not getting it: the error
|
|
274
|
+
// generator declined this schema (unevaluated* and a few other shapes),
|
|
275
|
+
// and a standalone module has no interpreted engine to fall back to the
|
|
276
|
+
// way the runtime validator does. The module still ships, with the
|
|
277
|
+
// verdict exact, but every failure reports the single ATA9000 stub.
|
|
278
|
+
// Silence here cost a user a debugging session; hence the channel.
|
|
279
|
+
opts.onWarning(
|
|
280
|
+
'error detail could not be generated for this schema; the module reports failures as the single ATA9000 abort-early error. The verdict is unaffected. For detailed errors, validate failing documents with the runtime Validator.'
|
|
281
|
+
);
|
|
272
282
|
}
|
|
273
283
|
}
|
|
274
284
|
|
|
@@ -379,9 +389,12 @@ function toStandaloneModule(validator, opts) {
|
|
|
379
389
|
? `export { ${names}${parseAlias} };\nexport default { ${names}${parseProp} };\n`
|
|
380
390
|
: `module.exports = { ${names}${parseProp} };\nmodule.exports.default = module.exports;\n`;
|
|
381
391
|
|
|
392
|
+
const degradedNote = (!abortEarly && !errCore)
|
|
393
|
+
? '// NOTE: error detail was requested but could not be generated for this\n// schema; failures report the single ATA9000 abort-early error. The verdict\n// is exact. Validate failing documents with the runtime Validator for detail.\n'
|
|
394
|
+
: '';
|
|
382
395
|
return `// Auto-generated by ata-validator — do not edit.
|
|
383
396
|
// Schema is embedded; runtime has zero dependency on ata-validator.
|
|
384
|
-
'use strict';
|
|
397
|
+
${degradedNote}'use strict';
|
|
385
398
|
${_CP_LEN_SOURCE}
|
|
386
399
|
${safeRePrelude(jsFn, jsErrFn)}${schemaSourceConst}const VALID = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
387
400
|
const ABORT = Object.freeze({
|
package/lib/compat-errors.js
CHANGED
|
@@ -49,7 +49,14 @@ function pointerOf(schemaPath) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function segments(pointer) {
|
|
52
|
-
|
|
52
|
+
// split-then-slice built two arrays per call, and this is called for every
|
|
53
|
+
// error's pointer and every pointer walk. Dropping everything before the
|
|
54
|
+
// first '/' first builds one. Same answer for every input shape, including
|
|
55
|
+
// a pointer with no slash at all, which the old form turned into [].
|
|
56
|
+
if (pointer === '') return [];
|
|
57
|
+
const i = pointer.indexOf('/');
|
|
58
|
+
if (i < 0) return [];
|
|
59
|
+
return pointer.slice(i + 1).split('/');
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
// Walks a pointer's segments and reports, per position, whether that segment
|
|
@@ -67,24 +74,41 @@ function classify(segs) {
|
|
|
67
74
|
return kinds;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
77
|
+
// One derived key per error: its pointer relative to the hash, the split
|
|
78
|
+
// segments, and each segment's kind. Everything below used to re-derive these
|
|
79
|
+
// wherever it needed them, which for one shaped rejection meant splitting the
|
|
80
|
+
// same pointer three times per error.
|
|
81
|
+
function keyFor(e) {
|
|
82
|
+
const rel = pointerOf(e && e.schemaPath ? e.schemaPath : '#');
|
|
83
|
+
const segs = segments(rel);
|
|
84
|
+
return { rel, segs, kinds: classify(segs) };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function sortByKeys(errors, keys) {
|
|
88
|
+
if (errors.length < 2) return errors.slice();
|
|
89
|
+
const keyed = errors.map((e) => ({ e, k: keys.get(e) }));
|
|
90
|
+
keyed.sort((A, B) => {
|
|
91
|
+
const pa = A.k.segs, pb = B.k.segs, ka = A.k.kinds;
|
|
92
|
+
const n = Math.min(pa.length, pb.length);
|
|
93
|
+
for (let i = 0; i < n; i++) {
|
|
94
|
+
if (pa[i] === pb[i]) continue;
|
|
95
|
+
if (ka[i] !== 'kw') return 0;
|
|
96
|
+
const ra = REFERENCE_RANK.get(pa[i]);
|
|
97
|
+
const rb = REFERENCE_RANK.get(pb[i]);
|
|
98
|
+
if (ra === undefined || rb === undefined) return 0;
|
|
99
|
+
return ra - rb;
|
|
100
|
+
}
|
|
101
|
+
return 0;
|
|
102
|
+
});
|
|
103
|
+
for (let i = 0; i < keyed.length; i++) keyed[i] = keyed[i].e;
|
|
104
|
+
return keyed;
|
|
84
105
|
}
|
|
85
106
|
|
|
86
107
|
function sortLikeReference(errors) {
|
|
87
|
-
|
|
108
|
+
if (errors.length < 2) return errors;
|
|
109
|
+
const keys = new Map();
|
|
110
|
+
for (const e of errors) keys.set(e, keyFor(e));
|
|
111
|
+
return sortByKeys(errors, keys);
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
function walkPointer(node, pointer) {
|
|
@@ -151,14 +175,16 @@ function createShaper({ Validator, rootId, rootDoc, schemas, options, allErrors,
|
|
|
151
175
|
// `basePointer` is where `errors` were produced relative to the root
|
|
152
176
|
// document; a branch validator reports paths relative to its branch.
|
|
153
177
|
function shape(errors, data, basePointer) {
|
|
154
|
-
const
|
|
178
|
+
const keys = new Map();
|
|
179
|
+
for (const e of errors) keys.set(e, keyFor(e));
|
|
180
|
+
const sorted = sortByKeys(errors, keys);
|
|
155
181
|
const out = [];
|
|
156
182
|
const consumed = new Set();
|
|
157
183
|
let thenGroup = null; // { prefix, which } for the pending `if` wrapper
|
|
158
184
|
|
|
159
185
|
const flushIf = (e) => {
|
|
160
186
|
if (thenGroup === null) return;
|
|
161
|
-
const segs =
|
|
187
|
+
const segs = e ? keys.get(e).segs : [];
|
|
162
188
|
const still = e && segs.length > thenGroup.depth && segs[thenGroup.depth] === thenGroup.which &&
|
|
163
189
|
segs.slice(0, thenGroup.depth).join('/') === thenGroup.prefixSegs;
|
|
164
190
|
if (still) return;
|
|
@@ -170,10 +196,11 @@ function createShaper({ Validator, rootId, rootDoc, schemas, options, allErrors,
|
|
|
170
196
|
for (let idx = 0; idx < sorted.length; idx++) {
|
|
171
197
|
const e = sorted[idx];
|
|
172
198
|
if (consumed.has(e)) continue;
|
|
173
|
-
const
|
|
199
|
+
const K = keys.get(e);
|
|
200
|
+
const rel = K.rel;
|
|
174
201
|
const abs = basePointer + rel;
|
|
175
|
-
const segs =
|
|
176
|
-
const kinds =
|
|
202
|
+
const segs = K.segs;
|
|
203
|
+
const kinds = K.kinds;
|
|
177
204
|
const schemaPrefix = (e.schemaPath || '#').slice(0, (e.schemaPath || '#').indexOf('#') + 1);
|
|
178
205
|
|
|
179
206
|
if (allErrors) flushIf(e);
|
|
@@ -234,7 +261,7 @@ function createShaper({ Validator, rootId, rootDoc, schemas, options, allErrors,
|
|
|
234
261
|
// instance is replaced by the regenerated sequence.
|
|
235
262
|
for (let j = idx; j < sorted.length; j++) {
|
|
236
263
|
const o = sorted[j];
|
|
237
|
-
if ((o.instancePath || '') === (e.instancePath || '') &&
|
|
264
|
+
if ((o.instancePath || '') === (e.instancePath || '') && keys.get(o).rel.startsWith(pnPointer + '/')) consumed.add(o);
|
|
238
265
|
}
|
|
239
266
|
const pnSchemaPath = schemaPrefix + pnPointer;
|
|
240
267
|
for (const name of Object.keys(object)) {
|
package/lib/js-compiler.js
CHANGED
|
@@ -2612,8 +2612,14 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
2612
2612
|
}
|
|
2613
2613
|
// else: already emitted early (before properties)
|
|
2614
2614
|
} else if (propCount > 0) {
|
|
2615
|
-
// TRICK 3: charCodeAt switch tree
|
|
2616
|
-
|
|
2615
|
+
// TRICK 3: charCodeAt switch tree. Above the lookup threshold it is
|
|
2616
|
+
// the additionalProperties quadratic wearing a disguise: keys that
|
|
2617
|
+
// share a first character, ENV_0 through ENV_999 say, all land in
|
|
2618
|
+
// one case whose body is a chain of every name, per key of the
|
|
2619
|
+
// document. The hoisted name lookup is the same cure it was there.
|
|
2620
|
+
inner = propCount >= AP_LOOKUP_MIN
|
|
2621
|
+
? apMembershipCheck(ctx, knownKeys, v)
|
|
2622
|
+
: genCharCodeSwitch(knownKeys, v)
|
|
2617
2623
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
2618
2624
|
} else {
|
|
2619
2625
|
inner = `for(var _k in ${v})return false`
|
|
@@ -2627,8 +2633,13 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
2627
2633
|
genCode(schema.unevaluatedProperties, `${v}[${ukVar}]`, subLines, ctx)
|
|
2628
2634
|
if (subLines.length > 0) {
|
|
2629
2635
|
const check = subLines.join(';')
|
|
2630
|
-
|
|
2631
|
-
|
|
2636
|
+
let skipKnown = ''
|
|
2637
|
+
if (knownKeys.length >= AP_LOOKUP_MIN) {
|
|
2638
|
+
const id = emitNameLookup(ctx, knownKeys)
|
|
2639
|
+
skipKnown = id !== null ? `if(${id}[${ukVar}]!==undefined)continue;` : `if(${knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')})continue;`
|
|
2640
|
+
} else if (knownKeys.length > 0) {
|
|
2641
|
+
skipKnown = `if(${knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')})continue;`
|
|
2642
|
+
}
|
|
2632
2643
|
const inner = `for(var ${ukVar} in ${v}){${skipKnown}${check}}`
|
|
2633
2644
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
2634
2645
|
}
|