ata-validator 1.45.0 → 1.46.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 +9 -9
- package/bin/ata.js +16 -0
- package/index.js +73 -12
- package/lib/branch-collapse.js +1 -1
- package/lib/combined-runtime.js +149 -0
- package/lib/interpreter.js +1 -1
- package/lib/js-compiler.js +1096 -78
- package/lib/migrate.js +253 -0
- package/lib/regex-linear.js +35 -26
- package/lib/validator-core.js +26 -4
- package/lib/version.js +1 -1
- package/package.json +8 -8
package/lib/migrate.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// `ata migrate`: what switching a project from ajv to ata-validator/compat
|
|
4
|
+
// would change, and what it cannot translate. The report comes first and
|
|
5
|
+
// touches nothing; `--write` applies only the two mechanical rewrites (the
|
|
6
|
+
// import specifier, and the `addFormats()` call compat refuses on purpose),
|
|
7
|
+
// so a site the report flags is never changed behind the reader's back.
|
|
8
|
+
//
|
|
9
|
+
// Sources are read through a small masker that blanks comments and the
|
|
10
|
+
// insides of string and template literals, so a specifier is matched only
|
|
11
|
+
// where it is code: `require('ajv')` in a comment or in a longer string is
|
|
12
|
+
// not an import. Regular expression literals are not recognised; a pattern
|
|
13
|
+
// such as /from 'ajv'/ would be read as code, which only ever adds a line to
|
|
14
|
+
// the report, never a rewrite the report did not show.
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
|
|
19
|
+
const SOURCE_EXT = new Set(['.js', '.mjs', '.cjs', '.jsx', '.ts', '.mts', '.cts', '.tsx']);
|
|
20
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.nuxt', 'out', '.turbo', '.cache']);
|
|
21
|
+
|
|
22
|
+
// Module specifiers and what each means for the switch.
|
|
23
|
+
const SPECIFIERS = {
|
|
24
|
+
ajv: { kind: 'rewrite', to: 'ata-validator/compat' },
|
|
25
|
+
'ajv/dist/ajv': { kind: 'rewrite', to: 'ata-validator/compat' },
|
|
26
|
+
'ajv/dist/2020': { kind: 'rewrite-note', to: 'ata-validator/compat', note: 'compat reads a schema with no $schema as draft-07, where this import read it as Draft 2020-12: give each such schema a $schema, or keep this import until it has one' },
|
|
27
|
+
'ajv/dist/2019': { kind: 'rewrite-note', to: 'ata-validator/compat', note: 'compat reads a schema with no $schema as draft-07, where this import read it as Draft 2019-09, which ata does not implement: give each such schema a Draft 2020-12 or draft-07 $schema first' },
|
|
28
|
+
'ajv/dist/jtd': { kind: 'refuse', note: 'JSON Type Definition is not JSON Schema; ata has no equivalent' },
|
|
29
|
+
'ajv-formats': { kind: 'delete-import', note: 'the formats this plugin adds are built into ata; the import and the addFormats() call go' },
|
|
30
|
+
'ajv-errors': { kind: 'refuse', note: 'ata reads the errorMessage keyword natively (docs/migration-from-ajv.md, "Error UX"); the plugin call must be removed by hand and the keyword stays as it is' },
|
|
31
|
+
'ajv-keywords': { kind: 'refuse', note: 'not supported; each keyword used needs a definition through addKeyword with validate, compile or macro' },
|
|
32
|
+
'ajv-i18n': { kind: 'refuse', note: 'not supported; messages are English' },
|
|
33
|
+
'ajv-merge-patch': { kind: 'refuse', note: '$merge and $patch are not supported' },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Masks comments and string contents with spaces, keeping every index and
|
|
37
|
+
// line break where it was, so matches in the mask map back to the source.
|
|
38
|
+
function mask (src) {
|
|
39
|
+
const out = src.split('');
|
|
40
|
+
const n = src.length;
|
|
41
|
+
let i = 0;
|
|
42
|
+
const blank = (from, to) => { for (let k = from; k < to; k++) if (out[k] !== '\n') out[k] = ' '; };
|
|
43
|
+
while (i < n) {
|
|
44
|
+
const c = src[i];
|
|
45
|
+
const d = src[i + 1];
|
|
46
|
+
if (c === '/' && d === '/') {
|
|
47
|
+
let j = i;
|
|
48
|
+
while (j < n && src[j] !== '\n') j++;
|
|
49
|
+
blank(i, j); i = j; continue;
|
|
50
|
+
}
|
|
51
|
+
if (c === '/' && d === '*') {
|
|
52
|
+
let j = src.indexOf('*/', i + 2);
|
|
53
|
+
if (j < 0) j = n; else j += 2;
|
|
54
|
+
blank(i, j); i = j; continue;
|
|
55
|
+
}
|
|
56
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
57
|
+
let j = i + 1;
|
|
58
|
+
while (j < n) {
|
|
59
|
+
if (src[j] === '\\') { j += 2; continue; }
|
|
60
|
+
if (src[j] === c) break;
|
|
61
|
+
if (c !== '`' && src[j] === '\n') break;
|
|
62
|
+
j++;
|
|
63
|
+
}
|
|
64
|
+
blank(i + 1, j); i = j + 1; continue;
|
|
65
|
+
}
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
return out.join('');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function lineOf (src, index) {
|
|
72
|
+
let line = 1;
|
|
73
|
+
for (let i = 0; i < index; i++) if (src.charCodeAt(i) === 10) line++;
|
|
74
|
+
return line;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Every module specifier in `src`: the string following `from`, inside
|
|
78
|
+
// `require(` or `import(`, and in a bare `import '...'`. Returns the span of
|
|
79
|
+
// the string contents, so a rewrite replaces only the specifier.
|
|
80
|
+
function specifiers (src) {
|
|
81
|
+
const m = mask(src);
|
|
82
|
+
const found = [];
|
|
83
|
+
const re = /(\bfrom\s*|\brequire\s*\(\s*|\bimport\s*\(\s*|\bimport\s+)(['"])/g;
|
|
84
|
+
let x;
|
|
85
|
+
while ((x = re.exec(m)) !== null) {
|
|
86
|
+
const quote = x[2];
|
|
87
|
+
const start = x.index + x[0].length;
|
|
88
|
+
const end = src.indexOf(quote, start);
|
|
89
|
+
if (end < 0) continue;
|
|
90
|
+
found.push({ start, end, value: src.slice(start, end), line: lineOf(src, start) });
|
|
91
|
+
}
|
|
92
|
+
return found;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// `addFormats(x)` call statements, with the span to delete when the call is a
|
|
96
|
+
// statement on its own.
|
|
97
|
+
function addFormatsCalls (src) {
|
|
98
|
+
const m = mask(src);
|
|
99
|
+
const calls = [];
|
|
100
|
+
const re = /\baddFormats\s*\(/g;
|
|
101
|
+
let x;
|
|
102
|
+
while ((x = re.exec(m)) !== null) {
|
|
103
|
+
let depth = 1;
|
|
104
|
+
let j = x.index + x[0].length;
|
|
105
|
+
while (j < m.length && depth > 0) { if (m[j] === '(') depth++; else if (m[j] === ')') depth--; j++; }
|
|
106
|
+
let end = j;
|
|
107
|
+
if (m[end] === ';') end++;
|
|
108
|
+
// A statement of its own: only whitespace between the previous line
|
|
109
|
+
// break and the call, and between the call and the next one.
|
|
110
|
+
let ls = x.index;
|
|
111
|
+
while (ls > 0 && m[ls - 1] !== '\n') ls--;
|
|
112
|
+
let le = end;
|
|
113
|
+
while (le < m.length && m[le] !== '\n') le++;
|
|
114
|
+
const alone = /^\s*$/.test(m.slice(ls, x.index)) && /^\s*$/.test(m.slice(end, le));
|
|
115
|
+
calls.push({ start: x.index, end, lineStart: ls, lineEnd: le < m.length ? le + 1 : le, alone, line: lineOf(src, x.index) });
|
|
116
|
+
}
|
|
117
|
+
return calls;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// `$data` as an object key, bare or quoted. Read from the source rather than
|
|
121
|
+
// the mask, since a quoted key is a string the mask blanks.
|
|
122
|
+
function usesDollarData (src) {
|
|
123
|
+
const re = /(['"])\$data\1\s*:|\$data\s*:/g;
|
|
124
|
+
const lines = [];
|
|
125
|
+
let x;
|
|
126
|
+
while ((x = re.exec(src)) !== null) lines.push(lineOf(src, x.index));
|
|
127
|
+
return [...new Set(lines)];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function walk (dir, files) {
|
|
131
|
+
let entries;
|
|
132
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return files; }
|
|
133
|
+
for (const e of entries) {
|
|
134
|
+
if (e.isDirectory()) { if (!SKIP_DIRS.has(e.name)) walk(path.join(dir, e.name), files); continue; }
|
|
135
|
+
if (e.isFile() && SOURCE_EXT.has(path.extname(e.name))) files.push(path.join(dir, e.name));
|
|
136
|
+
}
|
|
137
|
+
return files;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// One file. Returns null when the file mentions nothing of ajv. A file that
|
|
141
|
+
// already imports ata-validator/compat is read too: what compat refuses
|
|
142
|
+
// ($data, a code keyword, addFormats) is still a finding there, so a second
|
|
143
|
+
// run after --write, or a CI check, keeps reporting it until it is fixed.
|
|
144
|
+
const COMPAT = 'ata-validator/compat';
|
|
145
|
+
function inspectFile (file, src) {
|
|
146
|
+
const specs = specifiers(src).filter((s) => s.value === 'ajv' || s.value.startsWith('ajv/') || s.value.startsWith('ajv-') || s.value === COMPAT);
|
|
147
|
+
if (specs.length === 0) return null;
|
|
148
|
+
const findings = [];
|
|
149
|
+
for (const s of specs) {
|
|
150
|
+
if (s.value === COMPAT) continue;
|
|
151
|
+
const rule = SPECIFIERS[s.value];
|
|
152
|
+
if (!rule) {
|
|
153
|
+
findings.push({ line: s.line, kind: 'flag', text: `import of "${s.value}": no translation known; check docs/migration-from-ajv.md` });
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (rule.kind === 'rewrite') findings.push({ line: s.line, kind: 'rewrite', text: `"${s.value}" becomes "${rule.to}"`, span: s, to: rule.to });
|
|
157
|
+
else if (rule.kind === 'rewrite-note') findings.push({ line: s.line, kind: 'rewrite', text: `"${s.value}" becomes "${rule.to}"; ${rule.note}`, span: s, to: rule.to, note: true });
|
|
158
|
+
else if (rule.kind === 'delete-import') findings.push({ line: s.line, kind: 'delete', text: `import of "${s.value}" goes: ${rule.note}`, span: s, deleteImport: true });
|
|
159
|
+
else findings.push({ line: s.line, kind: 'flag', text: `import of "${s.value}": ${rule.note}` });
|
|
160
|
+
}
|
|
161
|
+
const usesAjv = specs.some((s) => s.value === 'ajv' || s.value.startsWith('ajv/') || s.value === COMPAT);
|
|
162
|
+
if (usesAjv) {
|
|
163
|
+
for (const c of addFormatsCalls(src)) {
|
|
164
|
+
if (c.alone) findings.push({ line: c.line, kind: 'delete', text: 'addFormats() call goes: these formats are built in, and compat refuses the call', call: c });
|
|
165
|
+
else findings.push({ line: c.line, kind: 'flag', text: 'addFormats() is part of a larger statement; remove it by hand' });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (usesAjv) {
|
|
169
|
+
for (const line of usesDollarData(src)) findings.push({ line, kind: 'flag', text: '$data reference: compat refuses $data; rewrite the constraint without it' });
|
|
170
|
+
const m = mask(src);
|
|
171
|
+
const kw = /\.addKeyword\s*\(/g;
|
|
172
|
+
let x;
|
|
173
|
+
while ((x = kw.exec(m)) !== null) {
|
|
174
|
+
// The definition object follows; a `code:` key in it is the form compat
|
|
175
|
+
// cannot take. Looked for up to the matching parenthesis.
|
|
176
|
+
let depth = 1; let j = x.index + x[0].length;
|
|
177
|
+
while (j < m.length && depth > 0) { if (m[j] === '(') depth++; else if (m[j] === ')') depth--; j++; }
|
|
178
|
+
if (/\bcode\s*[:(]/.test(m.slice(x.index, j))) findings.push({ line: lineOf(src, x.index), kind: 'flag', text: 'addKeyword with a code-generating definition: compat takes validate, compile or macro only' });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (findings.length === 0) return null;
|
|
182
|
+
findings.sort((a, b) => a.line - b.line);
|
|
183
|
+
return findings;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// The source with the mechanical rewrites applied: specifiers replaced,
|
|
187
|
+
// standalone addFormats() statements removed, and the ajv-formats import
|
|
188
|
+
// line removed when its binding has no other use.
|
|
189
|
+
function rewriteFile (src, findings) {
|
|
190
|
+
const edits = [];
|
|
191
|
+
for (const f of findings) {
|
|
192
|
+
if (f.kind === 'rewrite') edits.push({ start: f.span.start, end: f.span.end, text: f.to });
|
|
193
|
+
else if (f.kind === 'delete' && f.call) edits.push({ start: f.call.lineStart, end: f.call.lineEnd, text: '' });
|
|
194
|
+
else if (f.kind === 'delete' && f.deleteImport) {
|
|
195
|
+
let ls = f.span.start; while (ls > 0 && src[ls - 1] !== '\n') ls--;
|
|
196
|
+
let le = f.span.end; while (le < src.length && src[le] !== '\n') le++;
|
|
197
|
+
if (le < src.length) le++;
|
|
198
|
+
const stmt = src.slice(ls, le);
|
|
199
|
+
// Only a whole import or require statement on its own line.
|
|
200
|
+
if (/^\s*(import\b[^;\n]*from\s*['"]ajv-formats['"]\s*;?\s*\n?|(const|let|var)\s+\w+\s*=\s*require\s*\(\s*['"]ajv-formats['"]\s*\)\s*;?\s*\n?)$/.test(stmt)) edits.push({ start: ls, end: le, text: '' });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
edits.sort((a, b) => b.start - a.start);
|
|
204
|
+
let out = src;
|
|
205
|
+
for (const e of edits) out = out.slice(0, e.start) + e.text + out.slice(e.end);
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function migrate (dir, opts = {}) {
|
|
210
|
+
const root = path.resolve(dir || '.');
|
|
211
|
+
const files = walk(root, []);
|
|
212
|
+
const report = [];
|
|
213
|
+
let flagged = 0;
|
|
214
|
+
let written = 0;
|
|
215
|
+
for (const file of files) {
|
|
216
|
+
let src;
|
|
217
|
+
try { src = fs.readFileSync(file, 'utf8'); } catch { continue; }
|
|
218
|
+
const findings = inspectFile(file, src);
|
|
219
|
+
if (findings === null) continue;
|
|
220
|
+
// Forward slashes on every platform: the path is for people to read and
|
|
221
|
+
// paste, and Windows gave `src\\plain.js` where the report and its test
|
|
222
|
+
// name `src/plain.js`.
|
|
223
|
+
report.push({ file: path.relative(root, file).split(path.sep).join('/'), findings });
|
|
224
|
+
flagged += findings.filter((f) => f.kind === 'flag').length;
|
|
225
|
+
if (opts.write && findings.some((f) => f.kind !== 'flag')) {
|
|
226
|
+
const out = rewriteFile(src, findings);
|
|
227
|
+
if (out !== src) { fs.writeFileSync(file, out); written++; }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { root, scanned: files.length, report, flagged, written };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function formatReport (result, opts = {}) {
|
|
234
|
+
const lines = [];
|
|
235
|
+
if (result.report.length === 0) {
|
|
236
|
+
lines.push(`no use of ajv found in ${result.scanned} source files under ${result.root}`);
|
|
237
|
+
return lines.join('\n');
|
|
238
|
+
}
|
|
239
|
+
const label = { rewrite: 'rewrite', delete: 'delete ', flag: 'flag ' };
|
|
240
|
+
for (const r of result.report) {
|
|
241
|
+
lines.push(r.file);
|
|
242
|
+
for (const f of r.findings) lines.push(` ${label[f.kind]} line ${String(f.line).padStart(4)} ${f.text}`);
|
|
243
|
+
}
|
|
244
|
+
const rewrites = result.report.reduce((n, r) => n + r.findings.filter((f) => f.kind !== 'flag').length, 0);
|
|
245
|
+
lines.push('');
|
|
246
|
+
lines.push(`${result.report.length} of ${result.scanned} source files use ajv: ${rewrites} mechanical change${rewrites === 1 ? '' : 's'}, ${result.flagged} site${result.flagged === 1 ? '' : 's'} to look at by hand`);
|
|
247
|
+
if (opts.write) lines.push(`${result.written} file${result.written === 1 ? '' : 's'} written; flagged sites were left as they are`);
|
|
248
|
+
else if (rewrites > 0) lines.push('nothing was changed; run again with --write to apply the mechanical changes');
|
|
249
|
+
if (result.flagged > 0) lines.push('each flagged site is explained in docs/migration-from-ajv.md');
|
|
250
|
+
return lines.join('\n');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = { migrate, formatReport, inspectFile, rewriteFile, mask, specifiers, SPECIFIERS };
|
package/lib/regex-linear.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
const { parse } = require('./safe-regex')
|
|
4
4
|
|
|
5
5
|
// True when the platform RegExp, a backtracking engine, also runs `src` in
|
|
6
|
-
// linear time, so it can take the pattern: every quantifier on one
|
|
7
|
-
// class or `.`; at most one
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// linear time, so it can take the pattern: every unbounded quantifier on one
|
|
7
|
+
// character, class or `.`; at most one of them, and then anchored with `^`;
|
|
8
|
+
// a finite repeat may cover a group that holds no unbounded quantifier; few
|
|
9
|
+
// bounded choices in all; only escapes both engines read alike; nothing
|
|
10
|
+
// outside the BMP, so both see the same UTF-16 units.
|
|
11
|
+
// tests/test_regex_native_linear.js holds it.
|
|
10
12
|
const NATIVE_ESCAPES = new Set(['d', 'D', 'w', 'W', 's', 'S', 'n', 'r', 't', 'f', 'v', '.', '\\', '/', '^', '$', '|', '?', '*', '+', '(', ')', '[', ']', '{', '}', '-', 'x', 'u'])
|
|
11
13
|
function nativeIsLinear (src) {
|
|
12
14
|
if (typeof src !== 'string' || src.length > 512) return false
|
|
@@ -23,33 +25,40 @@ function nativeIsLinear (src) {
|
|
|
23
25
|
}
|
|
24
26
|
let ast
|
|
25
27
|
try { ast = parse(src) } catch { return false }
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// Per subtree: how many unbounded quantifiers it holds (`u`) and how many
|
|
29
|
+
// bounded choices a backtracking engine could try (`b`), or null where the
|
|
30
|
+
// shape is not provably linear. A finite repeat of a group is linear when
|
|
31
|
+
// the group holds no unbounded quantifier: `(?:[A-F0-9]{2}:){31}` has
|
|
32
|
+
// nothing to retry, and its choices multiply through the nesting.
|
|
29
33
|
const atom = (n) => n.t === 'char' || n.t === 'class' || n.t === 'any'
|
|
30
|
-
const
|
|
31
|
-
if (!ok) return
|
|
34
|
+
const info = (n) => {
|
|
32
35
|
switch (n.t) {
|
|
33
36
|
case 'star': case 'plus':
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
case 'group':
|
|
47
|
-
case 'concat':
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
return atom(n.child) ? { u: 1, b: 0 } : null
|
|
38
|
+
case 'quest': {
|
|
39
|
+
if (atom(n.child)) return { u: 0, b: 1 }
|
|
40
|
+
const c = info(n.child)
|
|
41
|
+
return c === null || c.u > 0 ? null : { u: 0, b: 1 + c.b }
|
|
42
|
+
}
|
|
43
|
+
case 'repeat': {
|
|
44
|
+
if (atom(n.child)) return n.max === Infinity ? { u: 1, b: 0 } : { u: 0, b: n.max - n.min }
|
|
45
|
+
if (n.max === Infinity) return null
|
|
46
|
+
const c = info(n.child)
|
|
47
|
+
return c === null || c.u > 0 ? null : { u: 0, b: (n.max - n.min) + n.max * c.b }
|
|
48
|
+
}
|
|
49
|
+
case 'group': return info(n.child)
|
|
50
|
+
case 'concat': case 'alt': {
|
|
51
|
+
let u = 0, b = 0
|
|
52
|
+
for (const part of (n.t === 'concat' ? n.parts : n.opts)) { const c = info(part); if (c === null) return null; u += c.u; b += c.b }
|
|
53
|
+
return { u, b }
|
|
54
|
+
}
|
|
55
|
+
default: return { u: 0, b: 0 }
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
|
-
|
|
58
|
+
const r = info(ast)
|
|
59
|
+
const ok = r !== null
|
|
60
|
+
const unbounded = ok ? r.u : 0
|
|
61
|
+
const bounded = ok ? r.b : 0
|
|
53
62
|
if (!ok || unbounded > 1 || bounded > 64) return false
|
|
54
63
|
if (unbounded === 1) {
|
|
55
64
|
// Anchored at the start on every top-level branch.
|
package/lib/validator-core.js
CHANGED
|
@@ -755,6 +755,17 @@ class Validator {
|
|
|
755
755
|
// richErrors: default true. Only the literal `false` opts back into the
|
|
756
756
|
// v0.14 error shape (no code/expected/received/docUrl, no aliases).
|
|
757
757
|
this._richErrors = options && options.richErrors === false ? false : true;
|
|
758
|
+
// Declared here, set later (the one-pass switch): a property added to an
|
|
759
|
+
// instance after construction moves every instance that gets it to a
|
|
760
|
+
// new hidden class, which cost the suite benchmark nine percent.
|
|
761
|
+
this._onePass = undefined;
|
|
762
|
+
this._tierOnePass = undefined;
|
|
763
|
+
this._skipNativeFast = false;
|
|
764
|
+
this._scanner = undefined;
|
|
765
|
+
this._lastParsed = undefined;
|
|
766
|
+
this._twin = undefined;
|
|
767
|
+
this._coldCalls = undefined;
|
|
768
|
+
this._posCache = undefined;
|
|
758
769
|
|
|
759
770
|
// Optional schema source descriptor. When supplied, the renderer pipeline
|
|
760
771
|
// can attach a `schemaSource` frame to enriched errors.
|
|
@@ -1362,9 +1373,13 @@ class Validator {
|
|
|
1362
1373
|
let _errorsNow = _buildErrors;
|
|
1363
1374
|
let _onePass = null;
|
|
1364
1375
|
let _richLater = (_plain !== null && _oneShotRich !== null && _richBuilder !== null && !_extra) ? 1 : 0;
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1376
|
+
// The switch the third read makes, also reachable from validateJSON
|
|
1377
|
+
// (index.js) for a validator whose rejections only ever arrive as
|
|
1378
|
+
// text: those reads never pass through `_errorsOf`.
|
|
1379
|
+
const _switchOnePass = () => {
|
|
1380
|
+
if (_richLater === 0) return _onePass;
|
|
1381
|
+
_richLater = 0;
|
|
1382
|
+
{
|
|
1368
1383
|
const combRich = _oneShotRich();
|
|
1369
1384
|
if (combRich) {
|
|
1370
1385
|
const clean = !this._richErrors && _isCleanShape !== null && _isCleanShape(combRich);
|
|
@@ -1384,9 +1399,16 @@ class Validator {
|
|
|
1384
1399
|
// the verdict, so one pass measured no faster on rejections and
|
|
1385
1400
|
// slower on acceptances.
|
|
1386
1401
|
const onePass = !this._richErrors && _onePassOf !== null && _isCombined !== null && _isCombined(combRich) ? _onePassOf(EMPTY_ERRORS, _plainValidate) : null;
|
|
1387
|
-
|
|
1402
|
+
// Also where validateJSON can reach it (index.js): a rejected
|
|
1403
|
+
// text is parsed and handed to this function directly.
|
|
1404
|
+
if (onePass) { _onePass = onePass; self.validate = onePass; self._onePass = onePass; }
|
|
1388
1405
|
}
|
|
1389
1406
|
}
|
|
1407
|
+
return _onePass;
|
|
1408
|
+
};
|
|
1409
|
+
if (_richLater !== 0) this._tierOnePass = _switchOnePass;
|
|
1410
|
+
const _errorsOf = (data) => {
|
|
1411
|
+
if (_richLater !== 0 && ++_richLater > 2) _switchOnePass();
|
|
1390
1412
|
return _errorsNow(data);
|
|
1391
1413
|
};
|
|
1392
1414
|
_errorsOf.final = false;
|
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.46.1",
|
|
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
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -143,13 +143,13 @@
|
|
|
143
143
|
"LICENSE"
|
|
144
144
|
],
|
|
145
145
|
"optionalDependencies": {
|
|
146
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
147
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
148
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
149
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
150
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
151
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
152
|
-
"@ata-validator/native-win32-x64": "1.
|
|
146
|
+
"@ata-validator/native-darwin-arm64": "1.46.1",
|
|
147
|
+
"@ata-validator/native-darwin-x64": "1.46.1",
|
|
148
|
+
"@ata-validator/native-linux-arm64-gnu": "1.46.1",
|
|
149
|
+
"@ata-validator/native-linux-arm64-musl": "1.46.1",
|
|
150
|
+
"@ata-validator/native-linux-x64-gnu": "1.46.1",
|
|
151
|
+
"@ata-validator/native-linux-x64-musl": "1.46.1",
|
|
152
|
+
"@ata-validator/native-win32-x64": "1.46.1"
|
|
153
153
|
},
|
|
154
154
|
"peerDependencies": {
|
|
155
155
|
"yaml": "^2.0.0"
|