ata-validator 1.43.0 → 1.45.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.
@@ -1,7 +1,14 @@
1
1
  'use strict';
2
2
 
3
- // Bounded Levenshtein. Returns Infinity if distance > maxDistance.
4
- // Single-row DP. O(n*m) worst case but typical strings are <30 chars.
3
+ // Bounded Levenshtein. Returns Infinity if distance > maxDistance (or, past
4
+ // the bound, possibly some number greater than it; callers compare).
5
+ //
6
+ // Only the cells within the bound of the diagonal are computed: a path
7
+ // through any other cell already costs more than the bound. A typo hint asks
8
+ // with a bound of one or two and compares the missing name with every key
9
+ // present, so a row is five cells instead of the whole key. Without a bound
10
+ // the band is the whole row.
11
+ //
5
12
  // Two rows of scratch, reused across calls. The function is not recursive
6
13
  // and the library is single-threaded, so nothing else is using them; growing
7
14
  // them once beats allocating two arrays on every comparison, which the
@@ -10,42 +17,47 @@ let scratchA = new Int32Array(64);
10
17
  let scratchB = new Int32Array(64);
11
18
 
12
19
  function levenshtein (a, b, maxDistance) {
13
- const max = maxDistance == null ? Infinity : maxDistance;
14
20
  if (a === b) return 0;
15
- if (Math.abs(a.length - b.length) > max) return Infinity;
16
- if (a.length === 0) return b.length;
17
- if (b.length === 0) return a.length;
18
- if (scratchA.length < b.length + 1) {
19
- scratchA = new Int32Array(b.length + 1);
20
- scratchB = new Int32Array(b.length + 1);
21
+ const n = a.length;
22
+ const m = b.length;
23
+ const max = maxDistance == null || maxDistance > n + m ? n + m : maxDistance;
24
+ if (Math.abs(n - m) > max) return Infinity;
25
+ if (n === 0) return m;
26
+ if (m === 0) return n;
27
+ const big = max + 1;
28
+ if (scratchA.length < m + 2) {
29
+ scratchA = new Int32Array(m + 2);
30
+ scratchB = new Int32Array(m + 2);
21
31
  }
22
32
  let prev = scratchA;
23
33
  let curr = scratchB;
24
- const bn = b.length;
25
- for (let j = 0; j <= bn; j++) prev[j] = j;
26
- for (let i = 1; i <= a.length; i++) {
27
- curr[0] = i;
28
- let rowMin = i;
34
+ for (let j = 0; j <= m; j++) prev[j] = j <= max ? j : big;
35
+ for (let i = 1; i <= n; i++) {
36
+ const lo = i - max > 1 ? i - max : 1;
37
+ const hi = i + max < m ? i + max : m;
38
+ curr[lo - 1] = lo === 1 ? i : big;
39
+ let rowMin = curr[lo - 1];
29
40
  // charCodeAt rather than indexing: reading a character by index allocates
30
41
  // a one-character string, and this is the innermost loop.
31
42
  const ca = a.charCodeAt(i - 1);
32
- for (let j = 1; j <= bn; j++) {
33
- const cost = ca === b.charCodeAt(j - 1) ? 0 : 1;
34
- let m = prev[j - 1] + cost;
43
+ for (let j = lo; j <= hi; j++) {
44
+ let v = prev[j - 1] + (ca === b.charCodeAt(j - 1) ? 0 : 1);
35
45
  const del = curr[j - 1] + 1;
36
- if (del < m) m = del;
46
+ if (del < v) v = del;
37
47
  const ins = prev[j] + 1;
38
- if (ins < m) m = ins;
39
- curr[j] = m;
40
- if (m < rowMin) rowMin = m;
48
+ if (ins < v) v = ins;
49
+ if (v > big) v = big;
50
+ curr[j] = v;
51
+ if (v < rowMin) rowMin = v;
41
52
  }
53
+ if (hi < m) curr[hi + 1] = big;
42
54
  if (rowMin > max) return Infinity;
43
55
  // A destructured swap builds an array per row; a temporary does not.
44
56
  const t = prev;
45
57
  prev = curr;
46
58
  curr = t;
47
59
  }
48
- return prev[bn];
60
+ return prev[m] > max ? Infinity : prev[m];
49
61
  }
50
62
 
51
63
  module.exports = { levenshtein };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ // The entries buildTargetedPositionMap gives, found by the native addon where
4
+ // it can: simdjson indexes the document's structure once and walks that
5
+ // index to each pointer, where the script walk reads every character. On a
6
+ // 5 MB log with the error near its end that is 3.1 ms against 8.3. Only for
7
+ // a large ASCII string, where the addon's UTF-8 byte offsets are the string's
8
+ // indexes, and only when every wanted pointer is found; otherwise null, and
9
+ // the caller builds the map in script as before.
10
+ const NATIVE_MIN = 32 * 1024;
11
+ let _native;
12
+ function nativeTargeted (text, wanted) {
13
+ if (typeof text !== 'string' || text.length < NATIVE_MIN) return null;
14
+ if (_native === undefined) {
15
+ try { _native = require('./native-load')(); } catch { _native = null; }
16
+ if (_native && typeof _native.locatePointers !== 'function') _native = null;
17
+ }
18
+ if (_native === null || typeof Buffer === 'undefined' || Buffer.byteLength(text, 'utf8') !== text.length) return null;
19
+ const ptrs = Array.from(wanted);
20
+ let found;
21
+ try { found = _native.locatePointers(text, ptrs); } catch { return null; }
22
+ const offs = [];
23
+ for (let k = 0; k < ptrs.length; k++) {
24
+ if (!(found[2 * k] >= 0)) return null;
25
+ offs.push(found[2 * k]);
26
+ }
27
+ // Line starts for the offsets that are asked about, in one forward pass.
28
+ const needed = [];
29
+ for (let k = 0; k < ptrs.length; k++) {
30
+ needed.push(offs[k]);
31
+ const ks = keyStartBefore(text, offs[k]);
32
+ if (ks >= 0) needed.push(ks);
33
+ }
34
+ const lineOf = lineFinder(text, needed);
35
+ const map = Object.create(null);
36
+ for (let k = 0; k < ptrs.length; k++) {
37
+ const off = offs[k];
38
+ const at = lineOf(off);
39
+ const entry = { byteOffset: off, length: found[2 * k + 1], line: at.line, col: off - at.start + 1, text: at.text };
40
+ const ks = keyStartBefore(text, off);
41
+ if (ks >= 0 && ptrs[k] !== '') {
42
+ const kat = lineOf(ks);
43
+ entry.keyOffset = ks;
44
+ entry.keyLength = keyEndAfter(text, ks) - ks + 1;
45
+ entry.keyLine = kat.line;
46
+ entry.keyCol = ks - kat.start + 1;
47
+ }
48
+ map[ptrs[k]] = entry;
49
+ }
50
+ return map;
51
+ }
52
+
53
+ // The opening quote of the member name before a value at `off`, or -1 where
54
+ // the value is an array element or the root.
55
+ function keyStartBefore (text, off) {
56
+ let j = off - 1;
57
+ while (j >= 0) { const c = text.charCodeAt(j); if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d) j--; else break; }
58
+ if (j < 0 || text.charCodeAt(j) !== 0x3a) return -1;
59
+ j--;
60
+ while (j >= 0) { const c = text.charCodeAt(j); if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d) j--; else break; }
61
+ if (j < 0 || text.charCodeAt(j) !== 0x22) return -1;
62
+ // Back to the quote that opens the name: one not preceded by an odd run of
63
+ // backslashes.
64
+ for (let q = j - 1; q >= 0; q--) {
65
+ if (text.charCodeAt(q) !== 0x22) continue;
66
+ let b = q - 1;
67
+ while (b >= 0 && text.charCodeAt(b) === 0x5c) b--;
68
+ if (((q - 1 - b) & 1) === 0) return q;
69
+ }
70
+ return -1;
71
+ }
72
+ function keyEndAfter (text, start) {
73
+ let q = start + 1;
74
+ for (;;) {
75
+ const c = text.charCodeAt(q);
76
+ if (c === 0x5c) { q += 2; continue; }
77
+ if (c === 0x22 || q >= text.length) return q;
78
+ q++;
79
+ }
80
+ }
81
+ // Line number, line start and line text for any of `offsets`, from one pass.
82
+ function lineFinder (text, offsets) {
83
+ const sorted = Array.from(new Set(offsets)).sort((a, b) => a - b);
84
+ const info = new Map();
85
+ let line = 1, start = 0, s = 0;
86
+ while (s < sorted.length) {
87
+ const nl = text.indexOf('\n', start);
88
+ const end = nl === -1 ? text.length : nl;
89
+ while (s < sorted.length && sorted[s] <= end) {
90
+ info.set(sorted[s], { line, start, text: text.slice(start, end) });
91
+ s++;
92
+ }
93
+ if (nl === -1) break;
94
+ line++;
95
+ start = nl + 1;
96
+ }
97
+ return (off) => info.get(off);
98
+ }
99
+
100
+ module.exports = { nativeTargeted };
@@ -52,12 +52,16 @@ const branchTitle = (n) => (n && n.schema && typeof n.schema.title === 'string'
52
52
 
53
53
  function install(deps) {
54
54
  const { Plan, NOERRORS, err, evalLeaf, evalLeafV, runCustomV, dataBits, escapePointer,
55
- deepEqual, multipleOfOk, cpAtLeast, cpAtMost,
55
+ deepEqual, enumMember, allDistinct, multipleOfOk, cpAtLeast, cpAtMost,
56
56
  T_STRING, T_ARRAY, T_OBJECT, resolveRef, splitFragment, resolveUri } = deps;
57
57
 
58
58
  const TRUE_FN = () => true;
59
- const TRUE_PAIR = { v: TRUE_FN, c: TRUE_FN };
59
+ // `leaf`: the node compiled no subschema of its own (see compileNode). A
60
+ // collecting caller asks a leaf for its verdict first and builds the paths
61
+ // for its errors only when it fails.
62
+ const TRUE_PAIR = { v: TRUE_FN, c: TRUE_FN, leaf: true };
60
63
  const FALSE_PAIR = {
64
+ leaf: true,
61
65
  v: () => false,
62
66
  c: (data, errors, instancePath, schemaPath) => {
63
67
  if (errors !== NOERRORS) errors.push(err('false schema', 'not', instancePath, schemaPath, {}, 'boolean schema is false'));
@@ -164,12 +168,8 @@ function install(deps) {
164
168
  checks.push((data, bits) => (bits & mask) !== 0);
165
169
  }
166
170
  if (P.enum !== null) {
167
- const vals = P.enum;
168
- if (vals.length === 0) checks.push(() => false);
169
- else checks.push((data) => {
170
- for (let i = 0; i < vals.length; i++) if (deepEqual(vals[i], data)) return true;
171
- return false;
172
- });
171
+ if (P.enum.length === 0) checks.push(() => false);
172
+ else checks.push((data) => enumMember(P, data));
173
173
  }
174
174
  if (P.hasConst) {
175
175
  const c = P.const;
@@ -205,14 +205,7 @@ function install(deps) {
205
205
  const arrs = [];
206
206
  if (P.minItems !== undefined) { const m = P.minItems; arrs.push((d) => d.length >= m); }
207
207
  if (P.maxItems !== undefined) { const m = P.maxItems; arrs.push((d) => d.length <= m); }
208
- if (P.uniqueItems) {
209
- arrs.push((d) => {
210
- for (let i = 0; i < d.length; i++) {
211
- for (let j = i + 1; j < d.length; j++) if (deepEqual(d[i], d[j])) return false;
212
- }
213
- return true;
214
- });
215
- }
208
+ if (P.uniqueItems) arrs.push(allDistinct);
216
209
  const run = combineValue(arrs);
217
210
  if (run !== null) { needsBits = true; checks.push((data, bits) => bits !== T_ARRAY || run(data)); }
218
211
  }
@@ -300,6 +293,7 @@ function install(deps) {
300
293
  const box = { v: null, c: null };
301
294
  const pair = {
302
295
  open: true,
296
+ leaf: false,
303
297
  v: (d, st, rec) => box.v(d, st, rec),
304
298
  c: (d, e, ip, sp, st, rec) => box.c(d, e, ip, sp, st, rec),
305
299
  };
@@ -308,8 +302,11 @@ function install(deps) {
308
302
  // In-place children collect when this node does; child applicators
309
303
  // never do. `rec` is threaded as the trailing argument of every step
310
304
  // and is undefined on non-collecting nodes, where nothing reads it.
311
- const inPlace = (n, b) => compileNode(ctx, n, b, chain, collect);
312
- const child = (n) => compileNode(ctx, n, base, chain, false);
305
+ // Whether this node compiles any subschema: a node that does not is a
306
+ // leaf, whose verdict is a few comparisons.
307
+ let nested = false;
308
+ const inPlace = (n, b) => { nested = true; return compileNode(ctx, n, b, chain, collect); };
309
+ const child = (n) => { nested = true; return compileNode(ctx, n, base, chain, false); };
313
310
 
314
311
  const steps = []; // collect variant: (data, errors, ip, sp, stack, rec)
315
312
  const vsteps = []; // verdict variant: (data, stack, rec)
@@ -399,7 +396,7 @@ function install(deps) {
399
396
  let ok = true;
400
397
  const n = Math.min(fns.length, data.length);
401
398
  for (let i = 0; i < n; i++) {
402
- if (!fns[i].c(data[i], errors, instancePath + '/' + i, schemaPath + '/prefixItems/' + i, stack)) {
399
+ if (!((fns[i].leaf && fns[i].v(data[i], stack)) || fns[i].c(data[i], errors, instancePath + '/' + i, schemaPath + '/prefixItems/' + i, stack))) {
403
400
  ok = false;
404
401
  if (errors === NOERRORS) return false;
405
402
  }
@@ -422,7 +419,7 @@ function install(deps) {
422
419
  if (dataBits(data) !== T_ARRAY) return true;
423
420
  let ok = true;
424
421
  for (let i = start; i < data.length; i++) {
425
- if (!fn.c(data[i], errors, instancePath + '/' + i, schemaPath + '/items', stack)) {
422
+ if (!((fn.leaf && fn.v(data[i], stack)) || fn.c(data[i], errors, instancePath + '/' + i, schemaPath + '/items', stack))) {
426
423
  ok = false;
427
424
  if (errors === NOERRORS) return false;
428
425
  }
@@ -536,7 +533,7 @@ function install(deps) {
536
533
  let evaluated = false;
537
534
  const prop = lookup !== null ? lookup(key) : undefined;
538
535
  if (prop !== undefined) {
539
- if (!prop.fn.c(data[key], errors, instancePath + prop.seg, schemaPath + prop.schemaSeg, stack)) {
536
+ if (!((prop.fn.leaf && prop.fn.v(data[key], stack)) || prop.fn.c(data[key], errors, instancePath + prop.seg, schemaPath + prop.schemaSeg, stack))) {
540
537
  ok = false;
541
538
  if (errors === NOERRORS) return false;
542
539
  }
@@ -546,7 +543,7 @@ function install(deps) {
546
543
  for (let pi = 0; pi < patterns.length; pi++) {
547
544
  const pp = patterns[pi];
548
545
  if (pp.re.test(key)) {
549
- if (!pp.fn.c(data[key], errors, instancePath + '/' + escapePointer(key), schemaPath + '/patternProperties/' + escapePointer(pp.src), stack)) {
546
+ if (!((pp.fn.leaf && pp.fn.v(data[key], stack)) || pp.fn.c(data[key], errors, instancePath + '/' + escapePointer(key), schemaPath + '/patternProperties/' + escapePointer(pp.src), stack))) {
550
547
  ok = false;
551
548
  if (errors === NOERRORS) return false;
552
549
  }
@@ -559,7 +556,7 @@ function install(deps) {
559
556
  ok = false;
560
557
  if (errors === NOERRORS) return false;
561
558
  errors.push(err('additionalProperties', 'additionalProperties', instancePath, schemaPath + '/additionalProperties', { additionalProperty: key }, 'must NOT have additional properties'));
562
- } else if (!apFn.c(data[key], errors, instancePath + '/' + escapePointer(key), schemaPath + '/additionalProperties', stack)) {
559
+ } else if (!((apFn.leaf && apFn.v(data[key], stack)) || apFn.c(data[key], errors, instancePath + '/' + escapePointer(key), schemaPath + '/additionalProperties', stack))) {
563
560
  ok = false;
564
561
  if (errors === NOERRORS) return false;
565
562
  }
@@ -927,7 +924,7 @@ function install(deps) {
927
924
  for (let k = 0; k < keys.length; k++) {
928
925
  const key = keys[k];
929
926
  if (hasProp(rec, key)) continue;
930
- if (!fn.c(data[key], errors, instancePath + '/' + escapePointer(key), schemaPath + '/unevaluatedProperties', stack)) {
927
+ if (!((fn.leaf && fn.v(data[key], stack)) || fn.c(data[key], errors, instancePath + '/' + escapePointer(key), schemaPath + '/unevaluatedProperties', stack))) {
931
928
  ok = false;
932
929
  if (errors === NOERRORS) return false;
933
930
  }
@@ -983,7 +980,7 @@ function install(deps) {
983
980
  let ok = true;
984
981
  for (let i = 0; i < data.length; i++) {
985
982
  if (hasItem(rec, i)) continue;
986
- if (!fn.c(data[i], errors, instancePath + '/' + i, schemaPath + '/unevaluatedItems', stack)) {
983
+ if (!((fn.leaf && fn.v(data[i], stack)) || fn.c(data[i], errors, instancePath + '/' + i, schemaPath + '/unevaluatedItems', stack))) {
987
984
  ok = false;
988
985
  if (errors === NOERRORS) return false;
989
986
  }
@@ -1068,6 +1065,7 @@ function install(deps) {
1068
1065
  box.v = vfn;
1069
1066
  pair.c = cfn;
1070
1067
  pair.v = vfn;
1068
+ pair.leaf = !nested;
1071
1069
  pair.open = false;
1072
1070
  return pair;
1073
1071
  }
package/lib/rejections.js CHANGED
@@ -133,13 +133,18 @@ Object.defineProperty(RichRejection.prototype, 'errors', {
133
133
  // touches none of it. The JSON text is held here rather than read back off the
134
134
  // validator, so the result does not depend on what the instance does next.
135
135
  class LazyJsonRejection {
136
- constructor(result, jsonStr, self, enrich) {
136
+ // `parsed`, when given, is the document the inner path parsed from
137
+ // `jsonStr` and validated, after defaults, coercion and removal: reading the
138
+ // errors uses it instead of parsing the text again, which on a 5 MB
139
+ // document was a third of what reading them cost.
140
+ constructor(result, jsonStr, self, enrich, parsed) {
137
141
  this.valid = false;
138
142
  this._result = result;
139
143
  this._jsonStr = jsonStr;
140
144
  this._self = self;
141
145
  this._enrich = enrich;
142
146
  this._cached = null;
147
+ this._parsed = parsed;
143
148
  }
144
149
  toJSON() {
145
150
  return { valid: false, errors: this.errors };
@@ -159,12 +164,14 @@ Object.defineProperty(LazyJsonRejection.prototype, 'errors', {
159
164
 
160
165
  // The enrich pass plucks `received` from the value tree and the suggestion
161
166
  // engine (required-typo, format hints, coercion nudges) walks it too.
162
- let parsedData;
163
- try { parsedData = JSON.parse(jsonStr); } catch { parsedData = undefined; }
164
- // What was checked is the document after defaults, coercion and removal,
165
- // so `received` has to come from that, as it does for validate(). A
166
- // default that failed its own schema has no value in the text at all.
167
- if (parsedData !== undefined && self._preprocess) self._preprocess(parsedData);
167
+ let parsedData = this._parsed;
168
+ if (parsedData === undefined) {
169
+ try { parsedData = JSON.parse(jsonStr); } catch { parsedData = undefined; }
170
+ // What was checked is the document after defaults, coercion and removal,
171
+ // so `received` has to come from that, as it does for validate(). A
172
+ // default that failed its own schema has no value in the text at all.
173
+ if (parsedData !== undefined && self._preprocess) self._preprocess(parsedData);
174
+ }
168
175
 
169
176
  // Errors the inner path already enriched carry a `received` key: enrich()
170
177
  // always sets one, even when there is no value to show. `code` is not a
@@ -29,22 +29,35 @@ const FORMAT_HINTS = {
29
29
  ipv4: (val) => typeof val === 'string' ? 'expected four 0-255 octets separated by dots' : null,
30
30
  };
31
31
 
32
- function suggestEnumTypo (received, enumValues) {
33
- if (typeof received !== 'string') return null;
34
- if (!Array.isArray(enumValues) || enumValues.length === 0 || enumValues.length > 30) return null;
32
+ // Edits that still read as a typo of a name this long. With a flat two, a
33
+ // missing `name` was "corrected" to `age`, and `c` to `a`.
34
+ function typoBudget (name) {
35
+ const n = name.length;
36
+ return n < 3 ? 0 : n < 5 ? 1 : 2;
37
+ }
38
+
39
+ // The one string candidate nearest `target` within the budget, or null when
40
+ // none is near enough or two are equally near.
41
+ function nearest (target, candidates) {
42
+ const budget = typoBudget(target);
35
43
  let best = null;
36
- let bestDist = Infinity;
44
+ let bestDist = budget + 1;
37
45
  let tied = false;
38
- for (const v of enumValues) {
39
- if (typeof v !== 'string') continue;
40
- const d = levenshtein(received, v, 2);
41
- if (d < bestDist) { best = v; bestDist = d; tied = false; }
46
+ for (const c of candidates) {
47
+ if (typeof c !== 'string') continue;
48
+ const d = levenshtein(target, c, budget);
49
+ if (d === 0 || d > budget) continue;
50
+ if (d < bestDist) { best = c; bestDist = d; tied = false; }
42
51
  else if (d === bestDist) tied = true;
43
52
  }
44
- if (best && bestDist <= 2 && !tied) {
45
- return { text: `did you mean \`${best}\`?`, kind: 'typo' };
46
- }
47
- return null;
53
+ return tied ? null : best;
54
+ }
55
+
56
+ function suggestEnumTypo (received, enumValues) {
57
+ if (typeof received !== 'string') return null;
58
+ if (!Array.isArray(enumValues) || enumValues.length === 0 || enumValues.length > 30) return null;
59
+ const best = nearest(received, enumValues);
60
+ return best === null ? null : { text: `did you mean \`${best}\`?`, kind: 'typo' };
48
61
  }
49
62
 
50
63
  // The widest container a typo hint is offered on. `suggestEnumTypo` caps its
@@ -56,14 +69,8 @@ const MAX_TYPO_CANDIDATES = 64;
56
69
  function suggestRequiredTypo (missing, presentKeys) {
57
70
  if (!missing || !Array.isArray(presentKeys)) return null;
58
71
  if (presentKeys.length > MAX_TYPO_CANDIDATES) return null;
59
- for (const k of presentKeys) {
60
- if (typeof k !== 'string') continue;
61
- const d = levenshtein(missing, k, 2);
62
- if (d <= 2 && d > 0) {
63
- return { text: `did you mean \`${missing}\` instead of \`${k}\`?`, kind: 'similar-key' };
64
- }
65
- }
66
- return null;
72
+ const best = nearest(missing, presentKeys);
73
+ return best === null ? null : { text: `did you mean \`${missing}\` instead of \`${best}\`?`, kind: 'similar-key' };
67
74
  }
68
75
 
69
76
  function suggestFormat (format, raw) {
@@ -0,0 +1,139 @@
1
+ 'use strict';
2
+
3
+ // uniqueItems for every engine. These are real functions: the interpreted
4
+ // engine calls them, and the code generator emits their source text
5
+ // (`String(fn)`) into the function it builds, so both run the same code.
6
+ // They read only each other, `_deq` and `_uqpI`, which the generator emits
7
+ // beside them.
8
+ //
9
+ // Short arrays compare pairwise and allocate nothing. Longer arrays of
10
+ // primitives key a Set or Map by the value itself. Longer arrays that hold an
11
+ // object key a Map by a numeric structural hash, and only items whose hashes
12
+ // collide are compared with `_deq`. Equal values always hash alike: numbers
13
+ // by value (so 1 and 1.0, 0 and -0 agree), strings by their characters,
14
+ // arrays by position, and objects by the sum of their own key-value hashes,
15
+ // whatever the key order. The hash replaced a sorted-key canonical string
16
+ // built for every item on every call, which was over a quarter of the time a
17
+ // generated verdict spent on SchemaStore's sample documents.
18
+
19
+ // JSON equality, the same function the generator hoists as DEQ_HELPER.
20
+ function _deq(a, b) {
21
+ if (a === b) return true;
22
+ if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;
23
+ var aa = Array.isArray(a);
24
+ if (aa !== Array.isArray(b)) return false;
25
+ var i;
26
+ if (aa) {
27
+ if (a.length !== b.length) return false;
28
+ for (i = 0; i < a.length; i++) if (!_deq(a[i], b[i])) return false;
29
+ return true;
30
+ }
31
+ var ka = Object.keys(a);
32
+ if (ka.length !== Object.keys(b).length) return false;
33
+ for (i = 0; i < ka.length; i++) {
34
+ var k = ka[i];
35
+ if (!Object.prototype.hasOwnProperty.call(b, k) || !_deq(a[k], b[k])) return false;
36
+ }
37
+ return true;
38
+ }
39
+
40
+ function _uqh(x) {
41
+ if (x === null) return 1;
42
+ var t = typeof x, h, i;
43
+ if (t === 'boolean') return x ? 2 : 3;
44
+ if (t === 'number') {
45
+ if (x === 0) return 4;
46
+ h = x | 0;
47
+ return Math.imul(h ^ (((x - h) * 1e9) | 0), 0x9e3779b1) ^ 5;
48
+ }
49
+ if (t === 'string') {
50
+ h = 0x811c9dc5 ^ x.length;
51
+ for (i = 0; i < x.length; i++) h = Math.imul(h ^ x.charCodeAt(i), 16777619);
52
+ return h;
53
+ }
54
+ if (t !== 'object') return 6;
55
+ if (Array.isArray(x)) {
56
+ h = 0x2545f491 ^ x.length;
57
+ for (i = 0; i < x.length; i++) h = Math.imul(h ^ _uqh(x[i]), 16777619);
58
+ return h;
59
+ }
60
+ var ks = Object.keys(x);
61
+ h = 0x6c8e9cf5 ^ ks.length;
62
+ for (i = 0; i < ks.length; i++) h = (h + Math.imul(_uqh(ks[i]) ^ Math.imul(_uqh(x[ks[i]]), 0x5bd1e995), 0x27d4eb2d)) | 0;
63
+ return h;
64
+ }
65
+
66
+ // Whether every item is distinct.
67
+ function _uq(a) {
68
+ var n = a.length, i, k;
69
+ if (n < 2) return true;
70
+ if (n <= 12) {
71
+ for (i = 1; i < n; i++) for (k = 0; k < i; k++) if (_deq(a[i], a[k])) return false;
72
+ return true;
73
+ }
74
+ var prim = true;
75
+ for (i = 0; i < n; i++) { var x = a[i]; if (x !== null && typeof x === 'object') { prim = false; break; } }
76
+ if (prim) {
77
+ var s = new Set();
78
+ for (i = 0; i < n; i++) { if (s.has(a[i])) return false; s.add(a[i]); }
79
+ return true;
80
+ }
81
+ var m = new Map();
82
+ for (i = 0; i < n; i++) {
83
+ var hh = _uqh(a[i]), b = m.get(hh);
84
+ if (b === undefined) m.set(hh, i);
85
+ else if (typeof b === 'number') { if (_deq(a[b], a[i])) return false; m.set(hh, [b, i]); }
86
+ else { for (k = 0; k < b.length; k++) if (_deq(a[b[k]], a[i])) return false; b.push(i); }
87
+ }
88
+ return true;
89
+ }
90
+
91
+ // The duplicate pair an error reports: the first item that has a later
92
+ // equal, and its first later equal. Returns that later index and leaves the
93
+ // first in _uqpI, or returns -1 when every item is distinct.
94
+ var _uqpI = 0;
95
+ function _uqp(a) {
96
+ var n = a.length, i, k;
97
+ if (n < 2) return -1;
98
+ if (n <= 12) {
99
+ for (i = 0; i < n - 1; i++) for (k = i + 1; k < n; k++) if (_deq(a[i], a[k])) { _uqpI = i; return k; }
100
+ return -1;
101
+ }
102
+ var prim = true;
103
+ for (i = 0; i < n; i++) { var x = a[i]; if (x !== null && typeof x === 'object') { prim = false; break; } }
104
+ var bi = -1, bj = -1, m = new Map();
105
+ if (prim) {
106
+ for (i = 0; i < n; i++) {
107
+ var p = m.get(a[i]);
108
+ if (p === undefined) m.set(a[i], i);
109
+ else if (p >= 0) { if (bi < 0 || p < bi) { bi = p; bj = i; } m.set(a[i], -1); }
110
+ }
111
+ } else {
112
+ // Each bucket holds the first occurrence of every distinct value that
113
+ // hashed there, in index order; a value is paired with its first later
114
+ // equal only once.
115
+ var paired = null;
116
+ for (i = 0; i < n; i++) {
117
+ var hh = _uqh(a[i]), b = m.get(hh), r = -1;
118
+ if (b === undefined) { m.set(hh, i); continue; }
119
+ if (typeof b === 'number') { if (_deq(a[b], a[i])) r = b; else m.set(hh, [b, i]); }
120
+ else { for (k = 0; k < b.length; k++) if (_deq(a[b[k]], a[i])) { r = b[k]; break; } if (r < 0) b.push(i); }
121
+ if (r >= 0) {
122
+ if (paired === null) paired = new Set();
123
+ if (!paired.has(r)) { paired.add(r); if (bi < 0 || r < bi) { bi = r; bj = i; } }
124
+ }
125
+ }
126
+ }
127
+ if (bi < 0) return -1;
128
+ _uqpI = bi;
129
+ return bj;
130
+ }
131
+
132
+ // For the interpreted engine.
133
+ function allDistinct(a) { return _uq(a); }
134
+ function duplicatePair(a) {
135
+ var j = _uqp(a);
136
+ return j < 0 ? null : [_uqpI, j];
137
+ }
138
+
139
+ module.exports = { allDistinct, duplicatePair, _deq, _uqh, _uq, _uqp };