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/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({
@@ -49,7 +49,14 @@ function pointerOf(schemaPath) {
49
49
  }
50
50
 
51
51
  function segments(pointer) {
52
- return pointer === '' ? [] : pointer.split('/').slice(1);
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
- function compareReferenceOrder(a, b) {
71
- const pa = segments(pointerOf(a.schemaPath || ''));
72
- const pb = segments(pointerOf(b.schemaPath || ''));
73
- const ka = classify(pa);
74
- const n = Math.min(pa.length, pb.length);
75
- for (let i = 0; i < n; i++) {
76
- if (pa[i] === pb[i]) continue;
77
- if (ka[i] !== 'kw') return 0;
78
- const ra = REFERENCE_RANK.get(pa[i]);
79
- const rb = REFERENCE_RANK.get(pb[i]);
80
- if (ra === undefined || rb === undefined) return 0;
81
- return ra - rb;
82
- }
83
- return 0;
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
- return errors.length < 2 ? errors : errors.slice().sort(compareReferenceOrder);
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 sorted = sortLikeReference(errors);
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 = segments(pointerOf(e ? e.schemaPath : ''));
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 rel = pointerOf(e.schemaPath || '#');
199
+ const K = keys.get(e);
200
+ const rel = K.rel;
174
201
  const abs = basePointer + rel;
175
- const segs = segments(rel);
176
- const kinds = classify(segs);
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 || '') && pointerOf(o.schemaPath || '#').startsWith(pnPointer + '/')) consumed.add(o);
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)) {
@@ -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
- inner = genCharCodeSwitch(knownKeys, v)
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
- const keyChecks = knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')
2631
- const skipKnown = knownKeys.length > 0 ? `if(${keyChecks})continue;` : ''
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
  }