ata-validator 1.37.0 → 1.38.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/buffer-gate.browser.js +6 -1
- package/lib/buffer-gate.js +25 -0
- package/lib/js-compiler.js +61 -23
- package/lib/regex-linear.js +65 -0
- package/lib/safe-regex-source.js +1 -1
- package/lib/safe-regex.js +12 -4
- package/lib/validator-core.js +7 -1
- package/lib/version.js +1 -1
- package/package.json +10 -10
|
@@ -9,5 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
11
|
bufferNeedsSlowPath: () => false,
|
|
12
|
-
|
|
12
|
+
// A browser build has no addon and no Buffer, and the core throws before
|
|
13
|
+
// it gets here. A polyfilled Buffer would reach this: fail loudly rather
|
|
14
|
+
// than return with the methods still uninstalled.
|
|
15
|
+
installSlowBufferApis() {
|
|
16
|
+
throw new Error('The buffer APIs are not available in the browser build. Use validate(), isValidObject() or validateJSON().');
|
|
17
|
+
},
|
|
13
18
|
};
|
package/lib/buffer-gate.js
CHANGED
|
@@ -37,6 +37,30 @@ const SUBSCHEMA_NODES = [
|
|
|
37
37
|
'allOf', 'anyOf', 'oneOf', 'prefixItems',
|
|
38
38
|
];
|
|
39
39
|
|
|
40
|
+
// Where RE2 and ECMA-262 give different answers for the same pattern. RE2's
|
|
41
|
+
// `\s` is the ASCII whitespace alone, so it misses U+00A0, U+2028, U+3000 and
|
|
42
|
+
// U+FEFF, which ECMA-262 counts, and its `.` matches a carriage return and the
|
|
43
|
+
// line and paragraph separators, which ECMA-262's does not. `^\S+$` accepted
|
|
44
|
+
// "a\u2028b" on the native text path while validate() rejected it. Any `\s`
|
|
45
|
+
// or `\S`, and any `.` outside a class that is not escaped, sends the schema to
|
|
46
|
+
// the JS path.
|
|
47
|
+
function re2Diverges(pattern) {
|
|
48
|
+
let inClass = false;
|
|
49
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
50
|
+
const c = pattern[i];
|
|
51
|
+
if (c === '\\') {
|
|
52
|
+
const e = pattern[i + 1];
|
|
53
|
+
if (e === 's' || e === 'S') return true;
|
|
54
|
+
i++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (inClass) { if (c === ']') inClass = false; continue; }
|
|
58
|
+
if (c === '[') { inClass = true; continue; }
|
|
59
|
+
if (c === '.') return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
40
64
|
function walk(schema, depth) {
|
|
41
65
|
if (schema === true || schema === false) {
|
|
42
66
|
// A boolean root is answered wrong by the walker; nested booleans are
|
|
@@ -62,6 +86,7 @@ function walk(schema, depth) {
|
|
|
62
86
|
// Unicode property escapes: RE2 cannot parse them and the walker then
|
|
63
87
|
// skips the pattern instead of failing.
|
|
64
88
|
if (key === 'pattern' && typeof v === 'string' && /\\[pP]\{/.test(v)) return true;
|
|
89
|
+
if (key === 'pattern' && typeof v === 'string' && re2Diverges(v)) return true;
|
|
65
90
|
// Tuple forms: prefixItems and the draft-07 array form of items are
|
|
66
91
|
// checked against the wrong positions by the walker.
|
|
67
92
|
if (key === 'prefixItems') return true;
|
package/lib/js-compiler.js
CHANGED
|
@@ -135,12 +135,26 @@ function enumCondition(ctx, vals, v) {
|
|
|
135
135
|
const { codeFor } = require('./error-codes')
|
|
136
136
|
const { ordinalFor } = require('./schema-order')
|
|
137
137
|
const { compileSafe, patternIsSafe } = require('./safe-regex')
|
|
138
|
+
// Whether a pattern goes to the linear-time engine. A pattern the platform
|
|
139
|
+
// RegExp also runs in linear time (nativeIsLinear) takes the RegExp: it answers
|
|
140
|
+
// the same, runs faster, and a compiled module that needs no other pattern
|
|
141
|
+
// does not carry the engine. Patterns the engine cannot represent keep the
|
|
142
|
+
// RegExp, as before.
|
|
143
|
+
// Loaded on the first pattern, so a schema without one does not pay for it.
|
|
144
|
+
let _nativeIsLinear = null
|
|
145
|
+
// The linearity check only parses; it runs first so a pattern bound for the
|
|
146
|
+
// RegExp is never compiled for the engine just to be told it could have been.
|
|
147
|
+
const useSafeEngine = (src) => {
|
|
148
|
+
if (_nativeIsLinear === null) _nativeIsLinear = require('./regex-linear').nativeIsLinear
|
|
149
|
+
if (_nativeIsLinear(src)) return false
|
|
150
|
+
return patternIsSafe(src)
|
|
151
|
+
}
|
|
138
152
|
|
|
139
153
|
// Closure value for a patternProperties/propertyNames regex: the linear-time
|
|
140
154
|
// safe matcher when the pattern is in the supported subset (and flag the ctx so
|
|
141
155
|
// standalone embeds the engine), else a plain RegExp. Both expose `.test`.
|
|
142
156
|
function safeReClosure (ctx, src) {
|
|
143
|
-
if (
|
|
157
|
+
if (useSafeEngine(src)) { ctx.usesSafeRe = true; return compileSafe(src) }
|
|
144
158
|
return new RegExp(src)
|
|
145
159
|
}
|
|
146
160
|
|
|
@@ -523,7 +537,7 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
523
537
|
}
|
|
524
538
|
if (schema.pattern) {
|
|
525
539
|
try {
|
|
526
|
-
const re =
|
|
540
|
+
const re = useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern)
|
|
527
541
|
checks.push((d) => typeof d !== 'string' || re.test(d))
|
|
528
542
|
} catch {
|
|
529
543
|
return null
|
|
@@ -1188,6 +1202,31 @@ function emitGuardedRun (attempt, reset, defSets) {
|
|
|
1188
1202
|
)
|
|
1189
1203
|
}
|
|
1190
1204
|
|
|
1205
|
+
// Definitions referenced from more than one place whose body is large enough
|
|
1206
|
+
// that repeating its error code at every reference costs more than a call.
|
|
1207
|
+
function sharedDefNames (root, defs) {
|
|
1208
|
+
const out = new Set()
|
|
1209
|
+
if (!defs || typeof defs !== 'object') return out
|
|
1210
|
+
const counts = new Map()
|
|
1211
|
+
const seen = new WeakSet()
|
|
1212
|
+
const walk = (node) => {
|
|
1213
|
+
if (node === null || typeof node !== 'object' || seen.has(node)) return
|
|
1214
|
+
seen.add(node)
|
|
1215
|
+
if (typeof node.$ref === 'string') {
|
|
1216
|
+
const m = /^#\/(?:\$defs|definitions)\/([^/]+)$/.exec(node.$ref)
|
|
1217
|
+
if (m) counts.set(m[1], (counts.get(m[1]) || 0) + 1)
|
|
1218
|
+
}
|
|
1219
|
+
for (const v of Object.values(node)) walk(v)
|
|
1220
|
+
}
|
|
1221
|
+
walk(root)
|
|
1222
|
+
for (const [name, n] of counts) {
|
|
1223
|
+
const def = defs[name]
|
|
1224
|
+
if (n >= 2 && def && typeof def === 'object' && JSON.stringify(def).length >= SHARED_DEF_MIN_CHARS) out.add(name)
|
|
1225
|
+
}
|
|
1226
|
+
return out
|
|
1227
|
+
}
|
|
1228
|
+
const SHARED_DEF_MIN_CHARS = 120
|
|
1229
|
+
|
|
1191
1230
|
function cyclicDefNames (defs) {
|
|
1192
1231
|
const out = new Set()
|
|
1193
1232
|
if (!defs || typeof defs !== 'object') return out
|
|
@@ -1725,16 +1764,10 @@ function sharedCodegenGate(schema, schemaMap) {
|
|
|
1725
1764
|
|
|
1726
1765
|
// --- Codegen mode: generates a single Function (NOT CSP-safe) ---
|
|
1727
1766
|
// This matches ajv's approach: one monolithic function, V8 JIT fully inlines it
|
|
1728
|
-
// `propertyNames` as the generators read it
|
|
1729
|
-
//
|
|
1730
|
-
//
|
|
1731
|
-
//
|
|
1732
|
-
// a copy of the nodes that change: a local reference, through aliases, is
|
|
1733
|
-
// replaced by what it names, `type: 'string'` is dropped since every property
|
|
1734
|
-
// name is a string, and annotations go. Where what is left is still not all
|
|
1735
|
-
// supported keys, the node is left as written and the gate declines as before.
|
|
1736
|
-
// Errors keep the path the interpreted engine reports, which runs through the
|
|
1737
|
-
// reference without naming it.
|
|
1767
|
+
// `propertyNames` as the generators read it (maxLength, minLength, pattern,
|
|
1768
|
+
// const, enum): a local $ref, through aliases, becomes what it names, and
|
|
1769
|
+
// `type: 'string'` and annotations go, on a copy. Anything else is left as
|
|
1770
|
+
// written and the gate declines. Errors keep the interpreted engine's path.
|
|
1738
1771
|
const PN_SUPPORTED = new Set(['maxLength', 'minLength', 'pattern', 'const', 'enum'])
|
|
1739
1772
|
const PN_ANNOTATIONS = new Set(['$schema', '$comment', 'title', 'description', 'examples', 'default', 'deprecated', 'readOnly', 'writeOnly'])
|
|
1740
1773
|
function simplePropertyNames(pn, defs) {
|
|
@@ -2476,7 +2509,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2476
2509
|
if (!ctx.regExpMap.has(pattern)) {
|
|
2477
2510
|
const ri = ctx.varCounter++
|
|
2478
2511
|
ctx.regExpMap.set(pattern, ri)
|
|
2479
|
-
if (
|
|
2512
|
+
if (useSafeEngine(schema.pattern)) {
|
|
2480
2513
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
2481
2514
|
ctx.usesSafeRe = true
|
|
2482
2515
|
} else {
|
|
@@ -4277,9 +4310,14 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4277
4310
|
if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
|
|
4278
4311
|
const defName = m[1]
|
|
4279
4312
|
if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
|
|
4280
|
-
if (ctx.
|
|
4281
|
-
|
|
4282
|
-
//
|
|
4313
|
+
if (!ctx.sharedDefs) ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs)
|
|
4314
|
+
if (ctx.cyclicDefs.has(defName) || ctx.sharedDefs.has(defName)) {
|
|
4315
|
+
// Named helper for a def on a cycle, and for one referenced from
|
|
4316
|
+
// several places, which inlined would repeat its whole error code at
|
|
4317
|
+
// each: Uniswap's token-list schema has 16 references to 10
|
|
4318
|
+
// definitions. The path comes in as an argument because every call
|
|
4319
|
+
// site has its own. Only the error path runs these; the verdict
|
|
4320
|
+
// function, which answers valid documents, is unchanged.
|
|
4283
4321
|
if (!ctx.defFns) ctx.defFns = new Map()
|
|
4284
4322
|
let fnName = ctx.defFns.get(defName)
|
|
4285
4323
|
if (!fnName) {
|
|
@@ -4459,7 +4497,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4459
4497
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4460
4498
|
const ri = ctx.varCounter++
|
|
4461
4499
|
ctx.regExpMap.set(pattern, ri)
|
|
4462
|
-
if (
|
|
4500
|
+
if (useSafeEngine(schema.pattern)) {
|
|
4463
4501
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4464
4502
|
ctx.usesSafeRe = true
|
|
4465
4503
|
} else {
|
|
@@ -4534,7 +4572,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4534
4572
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4535
4573
|
const ri = ctx.varCounter++
|
|
4536
4574
|
ctx.regExpMap.set(pattern, ri)
|
|
4537
|
-
if (
|
|
4575
|
+
if (useSafeEngine(pat)) {
|
|
4538
4576
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4539
4577
|
ctx.usesSafeRe = true
|
|
4540
4578
|
} else {
|
|
@@ -4697,7 +4735,7 @@ function genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isO
|
|
|
4697
4735
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4698
4736
|
const ri = ctx.varCounter++
|
|
4699
4737
|
ctx.regExpMap.set(pattern, ri)
|
|
4700
|
-
if (
|
|
4738
|
+
if (useSafeEngine(pat)) {
|
|
4701
4739
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4702
4740
|
ctx.usesSafeRe = true
|
|
4703
4741
|
} else {
|
|
@@ -4728,7 +4766,7 @@ function genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4728
4766
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4729
4767
|
const ri = ctx.varCounter++
|
|
4730
4768
|
ctx.regExpMap.set(pattern, ri)
|
|
4731
|
-
if (
|
|
4769
|
+
if (useSafeEngine(pat)) {
|
|
4732
4770
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
4733
4771
|
ctx.usesSafeRe = true
|
|
4734
4772
|
} else {
|
|
@@ -4757,7 +4795,7 @@ function genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4757
4795
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4758
4796
|
const ri = ctx.varCounter++
|
|
4759
4797
|
ctx.regExpMap.set(pattern, ri)
|
|
4760
|
-
if (
|
|
4798
|
+
if (useSafeEngine(pat)) {
|
|
4761
4799
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4762
4800
|
ctx.usesSafeRe = true
|
|
4763
4801
|
} else {
|
|
@@ -4809,7 +4847,7 @@ function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
|
4809
4847
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4810
4848
|
const ri = ctx.varCounter++
|
|
4811
4849
|
ctx.regExpMap.set(pattern, ri)
|
|
4812
|
-
if (
|
|
4850
|
+
if (useSafeEngine(pn.pattern)) {
|
|
4813
4851
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
4814
4852
|
ctx.usesSafeRe = true
|
|
4815
4853
|
} else {
|
|
@@ -5248,7 +5286,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5248
5286
|
const ri = ctx.varCounter++
|
|
5249
5287
|
const reVar = `_re${ri}`
|
|
5250
5288
|
ctx.closureVars.push(reVar)
|
|
5251
|
-
ctx.closureVals.push(
|
|
5289
|
+
ctx.closureVals.push(useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern))
|
|
5252
5290
|
const c = isStr ? `!${reVar}.test(${v})` : `typeof ${v}==='string'&&!${reVar}.test(${v})`
|
|
5253
5291
|
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
|
|
5254
5292
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { parse } = require('./safe-regex')
|
|
4
|
+
|
|
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 character,
|
|
7
|
+
// class or `.`; at most one unbounded, and then anchored with `^`; few bounded
|
|
8
|
+
// choices; only escapes both engines read alike; nothing outside the BMP, so
|
|
9
|
+
// both see the same UTF-16 units. tests/test_regex_native_linear.js holds it.
|
|
10
|
+
const NATIVE_ESCAPES = new Set(['d', 'D', 'w', 'W', 's', 'S', 'n', 'r', 't', 'f', 'v', '.', '\\', '/', '^', '$', '|', '?', '*', '+', '(', ')', '[', ']', '{', '}', '-', 'x', 'u'])
|
|
11
|
+
function nativeIsLinear (src) {
|
|
12
|
+
if (typeof src !== 'string' || src.length > 512) return false
|
|
13
|
+
for (let i = 0; i < src.length; i++) {
|
|
14
|
+
const c = src.charCodeAt(i)
|
|
15
|
+
if (c >= 0xd800 && c <= 0xdfff) return false
|
|
16
|
+
if (src[i] === '\\') {
|
|
17
|
+
const e = src[i + 1]
|
|
18
|
+
if (!NATIVE_ESCAPES.has(e)) return false
|
|
19
|
+
if (e === 'x' && !/^[0-9a-fA-F]{2}$/.test(src.slice(i + 2, i + 4))) return false
|
|
20
|
+
if (e === 'u' && !/^[0-9a-fA-F]{4}$/.test(src.slice(i + 2, i + 6))) return false
|
|
21
|
+
i++
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
let ast
|
|
25
|
+
try { ast = parse(src) } catch { return false }
|
|
26
|
+
let unbounded = 0
|
|
27
|
+
let bounded = 0
|
|
28
|
+
let ok = true
|
|
29
|
+
const atom = (n) => n.t === 'char' || n.t === 'class' || n.t === 'any'
|
|
30
|
+
const walk = (n) => {
|
|
31
|
+
if (!ok) return
|
|
32
|
+
switch (n.t) {
|
|
33
|
+
case 'star': case 'plus':
|
|
34
|
+
if (!atom(n.child)) ok = false
|
|
35
|
+
unbounded++
|
|
36
|
+
break
|
|
37
|
+
case 'quest':
|
|
38
|
+
if (!atom(n.child)) ok = false
|
|
39
|
+
bounded += 1
|
|
40
|
+
break
|
|
41
|
+
case 'repeat':
|
|
42
|
+
if (!atom(n.child)) ok = false
|
|
43
|
+
if (n.max === Infinity) unbounded++
|
|
44
|
+
else bounded += n.max - n.min
|
|
45
|
+
break
|
|
46
|
+
case 'group': walk(n.child); break
|
|
47
|
+
case 'concat': n.parts.forEach(walk); break
|
|
48
|
+
case 'alt': n.opts.forEach(walk); break
|
|
49
|
+
default: break
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
walk(ast)
|
|
53
|
+
if (!ok || unbounded > 1 || bounded > 64) return false
|
|
54
|
+
if (unbounded === 1) {
|
|
55
|
+
// Anchored at the start on every top-level branch.
|
|
56
|
+
let top = ast
|
|
57
|
+
while (top.t === 'group') top = top.child
|
|
58
|
+
const branches = top.t === 'alt' ? top.opts : [top]
|
|
59
|
+
const first = (n) => { while (n && n.t === 'group') n = n.child; return n && n.t === 'concat' ? first(n.parts[0]) : n }
|
|
60
|
+
if (!branches.every((b) => { const f = first(b); return f && f.t === 'bol' })) return false
|
|
61
|
+
}
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { nativeIsLinear }
|
package/lib/safe-regex-source.js
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
// into standalone output without a runtime `fs` read. Kept in sync by
|
|
6
6
|
// `tests/test_safe_regex_source_sync.js`.
|
|
7
7
|
|
|
8
|
-
module.exports = "'use strict'\n\n// Linear-time regex engine for JSON Schema `pattern`, used in place of JS RegExp\n// so an adversarial input cannot trigger catastrophic backtracking (ReDoS).\n//\n// It is a Pike VM: the pattern compiles to a small instruction program, and the\n// VM simulates all NFA threads in lockstep over the input, deduping by program\n// counter. Runtime is O(input * program), with no backtracking.\n//\n// Supported (the RE2 subset, which is what ata's native path also accepts):\n// literals, ., character classes, \\d \\w \\s \\D \\W \\S, anchors ^ $, quantifiers\n// * + ? {n} {n,} {n,m} (greedy or lazy, same language for a boolean test),\n// groups ( ) (?: ), alternation |. Backreferences and lookaround are not\n// supported by linear engines; compileSafe throws on them so the caller can\n// decide (ata's codegen rejects such schemas rather than risk a hang).\n\nconst WS = [[9, 13], [32, 32], [160, 160]]\nconst DIGIT = [[48, 57]]\nconst WORD = [[48, 57], [65, 90], [97, 122], [95, 95]]\n\nfunction parse (src) {\n let i = 0\n const len = src.length\n const peek = () => src[i]\n const eof = () => i >= len\n\n function parseAlt () {\n const opts = [parseConcat()]\n while (!eof() && peek() === '|') { i++; opts.push(parseConcat()) }\n return opts.length === 1 ? opts[0] : { t: 'alt', opts }\n }\n\n function parseConcat () {\n const parts = []\n while (!eof() && peek() !== '|' && peek() !== ')') parts.push(parseRepeat())\n if (parts.length === 0) return { t: 'empty' }\n return parts.length === 1 ? parts[0] : { t: 'concat', parts }\n }\n\n function parseRepeat () {\n let node = parseAtom()\n while (!eof()) {\n const ch = peek()\n if (ch === '*') { i++; node = { t: 'star', child: node } }\n else if (ch === '+') { i++; node = { t: 'plus', child: node } }\n else if (ch === '?') { i++; node = { t: 'quest', child: node } }\n else if (ch === '{') {\n const saved = i\n const q = tryQuantifier()\n if (!q) { i = saved; break }\n node = { t: 'repeat', child: node, min: q.min, max: q.max }\n } else break\n // a trailing ? makes the quantifier lazy; same language for a boolean test\n if (!eof() && peek() === '?') i++\n }\n return node\n }\n\n function tryQuantifier () {\n // assumes current char is '{'\n i++\n let min = ''\n while (!eof() && /[0-9]/.test(peek())) { min += peek(); i++ }\n if (min === '') return null\n let max\n if (peek() === '}') { i++; return { min: +min, max: +min } }\n if (peek() === ',') {\n i++\n let m = ''\n while (!eof() && /[0-9]/.test(peek())) { m += peek(); i++ }\n if (peek() !== '}') return null\n i++\n max = m === '' ? Infinity : +m\n return { min: +min, max }\n }\n return null\n }\n\n function parseAtom () {\n const ch = peek()\n if (ch === '(') {\n i++\n if (src[i] === '?') {\n if (src[i + 1] === ':') { i += 2 }\n else throw new Error('unsupported group (lookaround/named) in pattern')\n }\n const child = parseAlt()\n if (peek() !== ')') throw new Error('unbalanced ( in pattern')\n i++\n return { t: 'group', child }\n }\n if (ch === '[') return parseClass()\n if (ch === '.') { i++; return { t: 'any' } }\n if (ch === '^') { i++; return { t: 'bol' } }\n if (ch === '$') { i++; return { t: 'eol' } }\n if (ch === '\\\\') return parseEscape(false)\n if (ch === ')' || ch === '|') return { t: 'empty' }\n i++\n return { t: 'char', c: ch.charCodeAt(0) }\n }\n\n function parseClass () {\n i++ // [\n let neg = false\n if (peek() === '^') { neg = true; i++ }\n const ranges = []\n while (!eof() && peek() !== ']') {\n let lo\n if (peek() === '\\\\') {\n const esc = parseEscape(true)\n if (esc.t === 'classpart') { for (const r of esc.ranges) ranges.push(r); continue }\n lo = esc.c\n } else { lo = peek().charCodeAt(0); i++ }\n if (peek() === '-' && src[i + 1] !== ']' && i + 1 < len) {\n i++ // -\n let hi\n if (peek() === '\\\\') { const e = parseEscape(true); hi = e.c } else { hi = peek().charCodeAt(0); i++ }\n ranges.push([lo, hi])\n } else {\n ranges.push([lo, lo])\n }\n }\n if (peek() !== ']') throw new Error('unbalanced [ in pattern')\n i++\n return { t: 'class', neg, ranges }\n }\n\n function parseEscape (inClass) {\n i++ // backslash\n if (eof()) throw new Error('trailing backslash in pattern')\n const ch = peek(); i++\n switch (ch) {\n case 'd': return inClass ? { t: 'classpart', ranges: DIGIT } : { t: 'class', neg: false, ranges: DIGIT }\n case 'w': return inClass ? { t: 'classpart', ranges: WORD } : { t: 'class', neg: false, ranges: WORD }\n case 's': return inClass ? { t: 'classpart', ranges: WS } : { t: 'class', neg: false, ranges: WS }\n case 'D': if (inClass) throw new Error('\\\\D inside a class is not supported'); return { t: 'class', neg: true, ranges: DIGIT }\n case 'W': if (inClass) throw new Error('\\\\W inside a class is not supported'); return { t: 'class', neg: true, ranges: WORD }\n case 'S': if (inClass) throw new Error('\\\\S inside a class is not supported'); return { t: 'class', neg: true, ranges: WS }\n case 'n': return { t: 'char', c: 10 }\n case 'r': return { t: 'char', c: 13 }\n case 't': return { t: 'char', c: 9 }\n case 'f': return { t: 'char', c: 12 }\n case 'v': return { t: 'char', c: 11 }\n case '0': return { t: 'char', c: 0 }\n case 'x': { const h = src.slice(i, i + 2); i += 2; return { t: 'char', c: parseInt(h, 16) } }\n case 'u': { const h = src.slice(i, i + 4); i += 4; return { t: 'char', c: parseInt(h, 16) } }\n case 'b': if (inClass) return { t: 'char', c: 8 }; throw new Error('\\\\b word boundary is not supported')\n default:\n if (/[1-9]/.test(ch)) throw new Error('backreferences are not supported in pattern')\n return { t: 'char', c: ch.charCodeAt(0) }\n }\n }\n\n const ast = parseAlt()\n if (!eof()) throw new Error('unexpected \"' + peek() + '\" in pattern')\n return ast\n}\n\nfunction compileProg (ast) {\n const prog = []\n const emit = (op, extra) => { const idx = prog.length; prog.push(Object.assign({ op }, extra)); return idx }\n\n function rec (n) {\n switch (n.t) {\n case 'empty': break\n case 'char': emit('char', { c: n.c }); break\n case 'any': emit('any'); break\n case 'class': emit('class', { neg: n.neg, ranges: n.ranges }); break\n case 'bol': emit('bol'); break\n case 'eol': emit('eol'); break\n case 'group': rec(n.child); break\n case 'concat': for (const p of n.parts) rec(p); break\n case 'alt': {\n const jmps = []\n for (let k = 0; k < n.opts.length; k++) {\n if (k < n.opts.length - 1) {\n const sp = emit('split', { x: 0, y: 0 })\n prog[sp].x = prog.length\n rec(n.opts[k])\n jmps.push(emit('jmp', { x: 0 }))\n prog[sp].y = prog.length\n } else {\n rec(n.opts[k])\n }\n }\n for (const j of jmps) prog[j].x = prog.length\n break\n }\n case 'star': {\n const sp = emit('split', { x: 0, y: 0 })\n prog[sp].x = prog.length\n rec(n.child)\n emit('jmp', { x: sp })\n prog[sp].y = prog.length\n break\n }\n case 'plus': {\n const start = prog.length\n rec(n.child)\n const sp = emit('split', { x: start, y: 0 })\n prog[sp].y = prog.length\n break\n }\n case 'quest': {\n const sp = emit('split', { x: 0, y: 0 })\n prog[sp].x = prog.length\n rec(n.child)\n prog[sp].y = prog.length\n break\n }\n case 'repeat': {\n for (let k = 0; k < n.min; k++) rec(n.child)\n if (n.max === Infinity) {\n if (n.min === 0) rec({ t: 'star', child: n.child })\n else rec({ t: 'star', child: n.child })\n } else {\n for (let k = 0; k < n.max - n.min; k++) rec({ t: 'quest', child: n.child })\n }\n break\n }\n }\n }\n\n rec(ast)\n emit('match')\n return prog\n}\n\n// Numeric opcodes for the runner. The program is compiled once into flat\n// typed arrays so the inner loop does no property lookups or string compares.\nconst OP_CHAR = 0\nconst OP_ANY = 1\nconst OP_CLASS = 2\nconst OP_SPLIT = 3\nconst OP_JMP = 4\nconst OP_BOL = 5\nconst OP_EOL = 6\nconst OP_MATCH = 7\n\nfunction classMatcher (instr) {\n // ASCII is answered from a bitmap; anything above 0x7f walks the ranges.\n const bits = new Uint8Array(128)\n const r = instr.ranges\n for (let k = 0; k < r.length; k++) {\n const hi = Math.min(r[k][1], 127)\n for (let c = r[k][0]; c <= hi; c++) bits[c] = 1\n }\n return { bits, ranges: r, neg: instr.neg }\n}\n\nfunction matchClass (cls, c) {\n let inside\n if (c < 128) {\n inside = cls.bits[c] === 1\n } else {\n inside = false\n const r = cls.ranges\n for (let k = 0; k < r.length; k++) { if (c >= r[k][0] && c <= r[k][1]) { inside = true; break } }\n }\n return cls.neg ? !inside : inside\n}\n\nfunction makeRunner (prog) {\n const n = prog.length\n const ops = new Uint8Array(n)\n const xs = new Int32Array(n)\n const ys = new Int32Array(n)\n const cs = new Int32Array(n)\n const classes = new Array(n)\n for (let i = 0; i < n; i++) {\n const I = prog[i]\n switch (I.op) {\n case 'char': ops[i] = OP_CHAR; cs[i] = I.c; break\n case 'any': ops[i] = OP_ANY; break\n case 'class': ops[i] = OP_CLASS; classes[i] = classMatcher(I); break\n case 'split': ops[i] = OP_SPLIT; xs[i] = I.x; ys[i] = I.y; break\n case 'jmp': ops[i] = OP_JMP; xs[i] = I.x; break\n case 'bol': ops[i] = OP_BOL; break\n case 'eol': ops[i] = OP_EOL; break\n case 'match': ops[i] = OP_MATCH; break\n }\n }\n\n const lastGen = new Int32Array(n).fill(-1)\n let gen = 0\n // Each unvisited instruction is popped once and pushes at most two, so the\n // stack never holds more than 2n + 1 entries.\n const stack = new Int32Array(2 * n + 2)\n // Thread lists hold at most one entry per instruction per step.\n let clist = new Int32Array(n)\n let nlist = new Int32Array(n)\n let clen = 0\n let nlen = 0\n\n // Follows epsilon edges from `pc` and records every consuming instruction\n // (or match) reached in `list`. `lastGen` dedupes per step.\n function addThread (list, len0, pc, pos, len) {\n // Most transitions land directly on a consuming instruction; skip the\n // stack walk for those.\n if (ops[pc] <= OP_CLASS || ops[pc] === OP_MATCH) {\n if (lastGen[pc] === gen) return len0\n lastGen[pc] = gen\n list[len0] = pc\n return len0 + 1\n }\n let sp = 0\n stack[sp++] = pc\n let count = len0\n while (sp > 0) {\n const p = stack[--sp]\n if (lastGen[p] === gen) continue\n lastGen[p] = gen\n switch (ops[p]) {\n case OP_JMP: stack[sp++] = xs[p]; break\n case OP_SPLIT: stack[sp++] = ys[p]; stack[sp++] = xs[p]; break\n case OP_BOL: if (pos === 0) stack[sp++] = p + 1; break\n case OP_EOL: if (pos === len) stack[sp++] = p + 1; break\n default: list[count++] = p\n }\n }\n return count\n }\n\n // A pattern is anchored when starting it anywhere but position 0 yields no\n // thread, which is the case for `^...` and its alternations. The probe sits\n // at position 1 of a length-1 string so that only `^` can fail. For anchored\n // patterns the per-position restart below is skipped, and an empty thread\n // list means the match has already failed.\n gen++\n const anchored = addThread(nlist, 0, 0, 1, 1) === 0\n\n function testNFA (s) {\n const len = s.length\n gen++\n clen = addThread(clist, 0, 0, 0, len)\n for (let pos = 0; pos <= len; pos++) {\n const c = pos < len ? s.charCodeAt(pos) : -1\n gen++\n nlen = 0\n for (let k = 0; k < clen; k++) {\n const pc = clist[k]\n switch (ops[pc]) {\n case OP_MATCH: return true\n case OP_CHAR: if (c === cs[pc]) nlen = addThread(nlist, nlen, pc + 1, pos + 1, len); break\n case OP_ANY: if (c !== -1 && c !== 10) nlen = addThread(nlist, nlen, pc + 1, pos + 1, len); break\n case OP_CLASS: if (c !== -1 && matchClass(classes[pc], c)) nlen = addThread(nlist, nlen, pc + 1, pos + 1, len); break\n }\n }\n if (pos < len) {\n if (!anchored) nlen = addThread(nlist, nlen, 0, pos + 1, len)\n else if (nlen === 0) return false\n }\n const tmp = clist; clist = nlist; nlist = tmp\n clen = nlen\n }\n return false\n }\n\n // Lazy DFA on top of the NFA. A DFA state is the set of consuming\n // instructions live at a position; transitions are computed on first use\n // and cached per ASCII character. `^` and `$` depend on position, so the\n // closure is taken with flags for \"at start\" and \"at end\", which gives two\n // start states and two transition tables per state. The state count is\n // capped; past the cap the matcher falls back to the NFA walk above, so the\n // time bound stays linear either way.\n const MAX_STATES = 256\n const states = []\n const stateIds = new Map()\n let overflow = false\n\n function closure (list, count, pc, atStart, atEnd) {\n // Same walk as addThread, with the position replaced by the two flags.\n let sp = 0\n stack[sp++] = pc\n while (sp > 0) {\n const p = stack[--sp]\n if (lastGen[p] === gen) continue\n lastGen[p] = gen\n switch (ops[p]) {\n case OP_JMP: stack[sp++] = xs[p]; break\n case OP_SPLIT: stack[sp++] = ys[p]; stack[sp++] = xs[p]; break\n case OP_BOL: if (atStart) stack[sp++] = p + 1; break\n case OP_EOL: if (atEnd) stack[sp++] = p + 1; break\n default: list[count++] = p\n }\n }\n return count\n }\n\n function internState (list, count) {\n const pcs = Array.from(list.subarray(0, count)).sort((a, b) => a - b)\n const key = pcs.join(',')\n let id = stateIds.get(key)\n if (id !== undefined) return id\n if (states.length >= MAX_STATES) { overflow = true; return -1 }\n id = states.length\n let isMatch = false\n for (let k = 0; k < pcs.length; k++) if (ops[pcs[k]] === OP_MATCH) { isMatch = true; break }\n states.push({ pcs: Int32Array.from(pcs), isMatch, next: new Int32Array(128).fill(-2), nextEnd: new Int32Array(128).fill(-2) })\n stateIds.set(key, id)\n return id\n }\n\n function step (state, c, atEnd) {\n gen++\n let count = 0\n const pcs = state.pcs\n for (let k = 0; k < pcs.length; k++) {\n const pc = pcs[k]\n switch (ops[pc]) {\n case OP_CHAR: if (c === cs[pc]) count = closure(nlist, count, pc + 1, false, atEnd); break\n case OP_ANY: if (c !== 10) count = closure(nlist, count, pc + 1, false, atEnd); break\n case OP_CLASS: if (matchClass(classes[pc], c)) count = closure(nlist, count, pc + 1, false, atEnd); break\n }\n }\n if (!anchored) count = closure(nlist, count, 0, false, atEnd)\n return internState(nlist, count)\n }\n\n let startEmpty = -2\n let startNonEmpty = -2\n\n function startState (atEnd) {\n gen++\n const count = closure(nlist, 0, 0, true, atEnd)\n return internState(nlist, count)\n }\n\n function testDFA (s) {\n const len = s.length\n let id\n if (len === 0) {\n if (startEmpty === -2) startEmpty = startState(true)\n id = startEmpty\n } else {\n if (startNonEmpty === -2) startNonEmpty = startState(false)\n id = startNonEmpty\n }\n if (id < 0) return testNFA(s)\n let state = states[id]\n for (let pos = 0; pos < len; pos++) {\n if (state.isMatch) return true\n const c = s.charCodeAt(pos)\n const atEnd = pos + 1 === len\n let nid\n if (c < 128) {\n const table = atEnd ? state.nextEnd : state.next\n nid = table[c]\n if (nid === -2) { nid = step(state, c, atEnd); table[c] = nid }\n } else {\n nid = step(state, c, atEnd)\n }\n if (nid < 0) return testNFA(s)\n state = states[nid]\n if (anchored && state.pcs.length === 0) return false\n }\n return state.isMatch\n }\n\n return function test (s) {\n return overflow ? testNFA(s) : testDFA(s)\n }\n}\n\nfunction compileSafe (pattern) {\n const prog = compileProg(parse(pattern))\n const runner = makeRunner(prog)\n // `__ataSafe` brands the result so the standalone serializer can tell a safe\n // matcher apart from a RegExp and emit `__ataSafeRe(source)` instead.\n return { test: runner, source: pattern, __ataSafe: true }\n}\n\n// True when the linear engine can represent `src`. Used by the codegen to decide\n// between the safe matcher and a JS RegExp fallback for patterns outside the\n// supported (RE2) subset (backreferences, lookaround, etc.).\nfunction patternIsSafe (src) {\n try { compileSafe(src); return true } catch { return false }\n}\n\nmodule.exports = { compileSafe, patternIsSafe }\n";
|
|
8
|
+
module.exports = "'use strict'\n\n// Linear-time regex engine for JSON Schema `pattern`, used in place of JS RegExp\n// so an adversarial input cannot trigger catastrophic backtracking (ReDoS).\n//\n// It is a Pike VM: the pattern compiles to a small instruction program, and the\n// VM simulates all NFA threads in lockstep over the input, deduping by program\n// counter. Runtime is O(input * program), with no backtracking.\n//\n// Supported (the RE2 subset, which is what ata's native path also accepts):\n// literals, ., character classes, \\d \\w \\s \\D \\W \\S, anchors ^ $, quantifiers\n// * + ? {n} {n,} {n,m} (greedy or lazy, same language for a boolean test),\n// groups ( ) (?: ), alternation |. Backreferences and lookaround are not\n// supported by linear engines; compileSafe throws on them so the caller can\n// decide (ata's codegen rejects such schemas rather than risk a hang).\n\n// ECMA-262 WhiteSpace and LineTerminator, what `\\s` matches: tab, line feed,\n// vertical tab, form feed, carriage return, space, no-break space, the\n// Unicode space separators, the line and paragraph separators, and the byte\n// order mark. The set used to stop at U+00A0, so `^\\S+$` accepted a string\n// holding U+2028 or U+3000.\nconst WS = [[9, 13], [32, 32], [160, 160], [0x1680, 0x1680], [0x2000, 0x200a], [0x2028, 0x2029], [0x202f, 0x202f], [0x205f, 0x205f], [0x3000, 0x3000], [0xfeff, 0xfeff]]\n// `.` matches anything but a line terminator: LF, CR and U+2028/U+2029. It\n// excluded only LF, so `^.$` accepted \"\\r\".\nconst isLineTerminator = (c) => c === 10 || c === 13 || c === 0x2028 || c === 0x2029\nconst DIGIT = [[48, 57]]\nconst WORD = [[48, 57], [65, 90], [97, 122], [95, 95]]\n\nfunction parse (src) {\n let i = 0\n const len = src.length\n const peek = () => src[i]\n const eof = () => i >= len\n\n function parseAlt () {\n const opts = [parseConcat()]\n while (!eof() && peek() === '|') { i++; opts.push(parseConcat()) }\n return opts.length === 1 ? opts[0] : { t: 'alt', opts }\n }\n\n function parseConcat () {\n const parts = []\n while (!eof() && peek() !== '|' && peek() !== ')') parts.push(parseRepeat())\n if (parts.length === 0) return { t: 'empty' }\n return parts.length === 1 ? parts[0] : { t: 'concat', parts }\n }\n\n function parseRepeat () {\n let node = parseAtom()\n while (!eof()) {\n const ch = peek()\n if (ch === '*') { i++; node = { t: 'star', child: node } }\n else if (ch === '+') { i++; node = { t: 'plus', child: node } }\n else if (ch === '?') { i++; node = { t: 'quest', child: node } }\n else if (ch === '{') {\n const saved = i\n const q = tryQuantifier()\n if (!q) { i = saved; break }\n node = { t: 'repeat', child: node, min: q.min, max: q.max }\n } else break\n // a trailing ? makes the quantifier lazy; same language for a boolean test\n if (!eof() && peek() === '?') i++\n }\n return node\n }\n\n function tryQuantifier () {\n // assumes current char is '{'\n i++\n let min = ''\n while (!eof() && /[0-9]/.test(peek())) { min += peek(); i++ }\n if (min === '') return null\n let max\n if (peek() === '}') { i++; return { min: +min, max: +min } }\n if (peek() === ',') {\n i++\n let m = ''\n while (!eof() && /[0-9]/.test(peek())) { m += peek(); i++ }\n if (peek() !== '}') return null\n i++\n max = m === '' ? Infinity : +m\n return { min: +min, max }\n }\n return null\n }\n\n function parseAtom () {\n const ch = peek()\n if (ch === '(') {\n i++\n if (src[i] === '?') {\n if (src[i + 1] === ':') { i += 2 }\n else throw new Error('unsupported group (lookaround/named) in pattern')\n }\n const child = parseAlt()\n if (peek() !== ')') throw new Error('unbalanced ( in pattern')\n i++\n return { t: 'group', child }\n }\n if (ch === '[') return parseClass()\n if (ch === '.') { i++; return { t: 'any' } }\n if (ch === '^') { i++; return { t: 'bol' } }\n if (ch === '$') { i++; return { t: 'eol' } }\n if (ch === '\\\\') return parseEscape(false)\n if (ch === ')' || ch === '|') return { t: 'empty' }\n i++\n return { t: 'char', c: ch.charCodeAt(0) }\n }\n\n function parseClass () {\n i++ // [\n let neg = false\n if (peek() === '^') { neg = true; i++ }\n const ranges = []\n while (!eof() && peek() !== ']') {\n let lo\n if (peek() === '\\\\') {\n const esc = parseEscape(true)\n if (esc.t === 'classpart') { for (const r of esc.ranges) ranges.push(r); continue }\n lo = esc.c\n } else { lo = peek().charCodeAt(0); i++ }\n if (peek() === '-' && src[i + 1] !== ']' && i + 1 < len) {\n i++ // -\n let hi\n if (peek() === '\\\\') { const e = parseEscape(true); hi = e.c } else { hi = peek().charCodeAt(0); i++ }\n ranges.push([lo, hi])\n } else {\n ranges.push([lo, lo])\n }\n }\n if (peek() !== ']') throw new Error('unbalanced [ in pattern')\n i++\n return { t: 'class', neg, ranges }\n }\n\n function parseEscape (inClass) {\n i++ // backslash\n if (eof()) throw new Error('trailing backslash in pattern')\n const ch = peek(); i++\n switch (ch) {\n case 'd': return inClass ? { t: 'classpart', ranges: DIGIT } : { t: 'class', neg: false, ranges: DIGIT }\n case 'w': return inClass ? { t: 'classpart', ranges: WORD } : { t: 'class', neg: false, ranges: WORD }\n case 's': return inClass ? { t: 'classpart', ranges: WS } : { t: 'class', neg: false, ranges: WS }\n case 'D': if (inClass) throw new Error('\\\\D inside a class is not supported'); return { t: 'class', neg: true, ranges: DIGIT }\n case 'W': if (inClass) throw new Error('\\\\W inside a class is not supported'); return { t: 'class', neg: true, ranges: WORD }\n case 'S': if (inClass) throw new Error('\\\\S inside a class is not supported'); return { t: 'class', neg: true, ranges: WS }\n case 'n': return { t: 'char', c: 10 }\n case 'r': return { t: 'char', c: 13 }\n case 't': return { t: 'char', c: 9 }\n case 'f': return { t: 'char', c: 12 }\n case 'v': return { t: 'char', c: 11 }\n case '0': return { t: 'char', c: 0 }\n case 'x': { const h = src.slice(i, i + 2); i += 2; return { t: 'char', c: parseInt(h, 16) } }\n case 'u': { const h = src.slice(i, i + 4); i += 4; return { t: 'char', c: parseInt(h, 16) } }\n case 'b': if (inClass) return { t: 'char', c: 8 }; throw new Error('\\\\b word boundary is not supported')\n default:\n if (/[1-9]/.test(ch)) throw new Error('backreferences are not supported in pattern')\n return { t: 'char', c: ch.charCodeAt(0) }\n }\n }\n\n const ast = parseAlt()\n if (!eof()) throw new Error('unexpected \"' + peek() + '\" in pattern')\n return ast\n}\n\nfunction compileProg (ast) {\n const prog = []\n const emit = (op, extra) => { const idx = prog.length; prog.push(Object.assign({ op }, extra)); return idx }\n\n function rec (n) {\n switch (n.t) {\n case 'empty': break\n case 'char': emit('char', { c: n.c }); break\n case 'any': emit('any'); break\n case 'class': emit('class', { neg: n.neg, ranges: n.ranges }); break\n case 'bol': emit('bol'); break\n case 'eol': emit('eol'); break\n case 'group': rec(n.child); break\n case 'concat': for (const p of n.parts) rec(p); break\n case 'alt': {\n const jmps = []\n for (let k = 0; k < n.opts.length; k++) {\n if (k < n.opts.length - 1) {\n const sp = emit('split', { x: 0, y: 0 })\n prog[sp].x = prog.length\n rec(n.opts[k])\n jmps.push(emit('jmp', { x: 0 }))\n prog[sp].y = prog.length\n } else {\n rec(n.opts[k])\n }\n }\n for (const j of jmps) prog[j].x = prog.length\n break\n }\n case 'star': {\n const sp = emit('split', { x: 0, y: 0 })\n prog[sp].x = prog.length\n rec(n.child)\n emit('jmp', { x: sp })\n prog[sp].y = prog.length\n break\n }\n case 'plus': {\n const start = prog.length\n rec(n.child)\n const sp = emit('split', { x: start, y: 0 })\n prog[sp].y = prog.length\n break\n }\n case 'quest': {\n const sp = emit('split', { x: 0, y: 0 })\n prog[sp].x = prog.length\n rec(n.child)\n prog[sp].y = prog.length\n break\n }\n case 'repeat': {\n for (let k = 0; k < n.min; k++) rec(n.child)\n if (n.max === Infinity) {\n if (n.min === 0) rec({ t: 'star', child: n.child })\n else rec({ t: 'star', child: n.child })\n } else {\n for (let k = 0; k < n.max - n.min; k++) rec({ t: 'quest', child: n.child })\n }\n break\n }\n }\n }\n\n rec(ast)\n emit('match')\n return prog\n}\n\n// Numeric opcodes for the runner. The program is compiled once into flat\n// typed arrays so the inner loop does no property lookups or string compares.\nconst OP_CHAR = 0\nconst OP_ANY = 1\nconst OP_CLASS = 2\nconst OP_SPLIT = 3\nconst OP_JMP = 4\nconst OP_BOL = 5\nconst OP_EOL = 6\nconst OP_MATCH = 7\n\nfunction classMatcher (instr) {\n // ASCII is answered from a bitmap; anything above 0x7f walks the ranges.\n const bits = new Uint8Array(128)\n const r = instr.ranges\n for (let k = 0; k < r.length; k++) {\n const hi = Math.min(r[k][1], 127)\n for (let c = r[k][0]; c <= hi; c++) bits[c] = 1\n }\n return { bits, ranges: r, neg: instr.neg }\n}\n\nfunction matchClass (cls, c) {\n let inside\n if (c < 128) {\n inside = cls.bits[c] === 1\n } else {\n inside = false\n const r = cls.ranges\n for (let k = 0; k < r.length; k++) { if (c >= r[k][0] && c <= r[k][1]) { inside = true; break } }\n }\n return cls.neg ? !inside : inside\n}\n\nfunction makeRunner (prog) {\n const n = prog.length\n const ops = new Uint8Array(n)\n const xs = new Int32Array(n)\n const ys = new Int32Array(n)\n const cs = new Int32Array(n)\n const classes = new Array(n)\n for (let i = 0; i < n; i++) {\n const I = prog[i]\n switch (I.op) {\n case 'char': ops[i] = OP_CHAR; cs[i] = I.c; break\n case 'any': ops[i] = OP_ANY; break\n case 'class': ops[i] = OP_CLASS; classes[i] = classMatcher(I); break\n case 'split': ops[i] = OP_SPLIT; xs[i] = I.x; ys[i] = I.y; break\n case 'jmp': ops[i] = OP_JMP; xs[i] = I.x; break\n case 'bol': ops[i] = OP_BOL; break\n case 'eol': ops[i] = OP_EOL; break\n case 'match': ops[i] = OP_MATCH; break\n }\n }\n\n const lastGen = new Int32Array(n).fill(-1)\n let gen = 0\n // Each unvisited instruction is popped once and pushes at most two, so the\n // stack never holds more than 2n + 1 entries.\n const stack = new Int32Array(2 * n + 2)\n // Thread lists hold at most one entry per instruction per step.\n let clist = new Int32Array(n)\n let nlist = new Int32Array(n)\n let clen = 0\n let nlen = 0\n\n // Follows epsilon edges from `pc` and records every consuming instruction\n // (or match) reached in `list`. `lastGen` dedupes per step.\n function addThread (list, len0, pc, pos, len) {\n // Most transitions land directly on a consuming instruction; skip the\n // stack walk for those.\n if (ops[pc] <= OP_CLASS || ops[pc] === OP_MATCH) {\n if (lastGen[pc] === gen) return len0\n lastGen[pc] = gen\n list[len0] = pc\n return len0 + 1\n }\n let sp = 0\n stack[sp++] = pc\n let count = len0\n while (sp > 0) {\n const p = stack[--sp]\n if (lastGen[p] === gen) continue\n lastGen[p] = gen\n switch (ops[p]) {\n case OP_JMP: stack[sp++] = xs[p]; break\n case OP_SPLIT: stack[sp++] = ys[p]; stack[sp++] = xs[p]; break\n case OP_BOL: if (pos === 0) stack[sp++] = p + 1; break\n case OP_EOL: if (pos === len) stack[sp++] = p + 1; break\n default: list[count++] = p\n }\n }\n return count\n }\n\n // A pattern is anchored when starting it anywhere but position 0 yields no\n // thread, which is the case for `^...` and its alternations. The probe sits\n // at position 1 of a length-1 string so that only `^` can fail. For anchored\n // patterns the per-position restart below is skipped, and an empty thread\n // list means the match has already failed.\n gen++\n const anchored = addThread(nlist, 0, 0, 1, 1) === 0\n\n function testNFA (s) {\n const len = s.length\n gen++\n clen = addThread(clist, 0, 0, 0, len)\n for (let pos = 0; pos <= len; pos++) {\n const c = pos < len ? s.charCodeAt(pos) : -1\n gen++\n nlen = 0\n for (let k = 0; k < clen; k++) {\n const pc = clist[k]\n switch (ops[pc]) {\n case OP_MATCH: return true\n case OP_CHAR: if (c === cs[pc]) nlen = addThread(nlist, nlen, pc + 1, pos + 1, len); break\n case OP_ANY: if (c !== -1 && !isLineTerminator(c)) nlen = addThread(nlist, nlen, pc + 1, pos + 1, len); break\n case OP_CLASS: if (c !== -1 && matchClass(classes[pc], c)) nlen = addThread(nlist, nlen, pc + 1, pos + 1, len); break\n }\n }\n if (pos < len) {\n if (!anchored) nlen = addThread(nlist, nlen, 0, pos + 1, len)\n else if (nlen === 0) return false\n }\n const tmp = clist; clist = nlist; nlist = tmp\n clen = nlen\n }\n return false\n }\n\n // Lazy DFA on top of the NFA. A DFA state is the set of consuming\n // instructions live at a position; transitions are computed on first use\n // and cached per ASCII character. `^` and `$` depend on position, so the\n // closure is taken with flags for \"at start\" and \"at end\", which gives two\n // start states and two transition tables per state. The state count is\n // capped; past the cap the matcher falls back to the NFA walk above, so the\n // time bound stays linear either way.\n const MAX_STATES = 256\n const states = []\n const stateIds = new Map()\n let overflow = false\n\n function closure (list, count, pc, atStart, atEnd) {\n // Same walk as addThread, with the position replaced by the two flags.\n let sp = 0\n stack[sp++] = pc\n while (sp > 0) {\n const p = stack[--sp]\n if (lastGen[p] === gen) continue\n lastGen[p] = gen\n switch (ops[p]) {\n case OP_JMP: stack[sp++] = xs[p]; break\n case OP_SPLIT: stack[sp++] = ys[p]; stack[sp++] = xs[p]; break\n case OP_BOL: if (atStart) stack[sp++] = p + 1; break\n case OP_EOL: if (atEnd) stack[sp++] = p + 1; break\n default: list[count++] = p\n }\n }\n return count\n }\n\n function internState (list, count) {\n const pcs = Array.from(list.subarray(0, count)).sort((a, b) => a - b)\n const key = pcs.join(',')\n let id = stateIds.get(key)\n if (id !== undefined) return id\n if (states.length >= MAX_STATES) { overflow = true; return -1 }\n id = states.length\n let isMatch = false\n for (let k = 0; k < pcs.length; k++) if (ops[pcs[k]] === OP_MATCH) { isMatch = true; break }\n states.push({ pcs: Int32Array.from(pcs), isMatch, next: new Int32Array(128).fill(-2), nextEnd: new Int32Array(128).fill(-2) })\n stateIds.set(key, id)\n return id\n }\n\n function step (state, c, atEnd) {\n gen++\n let count = 0\n const pcs = state.pcs\n for (let k = 0; k < pcs.length; k++) {\n const pc = pcs[k]\n switch (ops[pc]) {\n case OP_CHAR: if (c === cs[pc]) count = closure(nlist, count, pc + 1, false, atEnd); break\n case OP_ANY: if (!isLineTerminator(c)) count = closure(nlist, count, pc + 1, false, atEnd); break\n case OP_CLASS: if (matchClass(classes[pc], c)) count = closure(nlist, count, pc + 1, false, atEnd); break\n }\n }\n if (!anchored) count = closure(nlist, count, 0, false, atEnd)\n return internState(nlist, count)\n }\n\n let startEmpty = -2\n let startNonEmpty = -2\n\n function startState (atEnd) {\n gen++\n const count = closure(nlist, 0, 0, true, atEnd)\n return internState(nlist, count)\n }\n\n function testDFA (s) {\n const len = s.length\n let id\n if (len === 0) {\n if (startEmpty === -2) startEmpty = startState(true)\n id = startEmpty\n } else {\n if (startNonEmpty === -2) startNonEmpty = startState(false)\n id = startNonEmpty\n }\n if (id < 0) return testNFA(s)\n let state = states[id]\n for (let pos = 0; pos < len; pos++) {\n if (state.isMatch) return true\n const c = s.charCodeAt(pos)\n const atEnd = pos + 1 === len\n let nid\n if (c < 128) {\n const table = atEnd ? state.nextEnd : state.next\n nid = table[c]\n if (nid === -2) { nid = step(state, c, atEnd); table[c] = nid }\n } else {\n nid = step(state, c, atEnd)\n }\n if (nid < 0) return testNFA(s)\n state = states[nid]\n if (anchored && state.pcs.length === 0) return false\n }\n return state.isMatch\n }\n\n return function test (s) {\n return overflow ? testNFA(s) : testDFA(s)\n }\n}\n\nfunction compileSafe (pattern) {\n const prog = compileProg(parse(pattern))\n const runner = makeRunner(prog)\n // `__ataSafe` brands the result so the standalone serializer can tell a safe\n // matcher apart from a RegExp and emit `__ataSafeRe(source)` instead.\n return { test: runner, source: pattern, __ataSafe: true }\n}\n\n// True when the linear engine can represent `src`. Used by the codegen to decide\n// between the safe matcher and a JS RegExp fallback for patterns outside the\n// supported (RE2) subset (backreferences, lookaround, etc.).\nfunction patternIsSafe (src) {\n try { compileSafe(src); return true } catch { return false }\n}\n\nmodule.exports = { compileSafe, patternIsSafe, parse }\n";
|
package/lib/safe-regex.js
CHANGED
|
@@ -14,7 +14,15 @@
|
|
|
14
14
|
// supported by linear engines; compileSafe throws on them so the caller can
|
|
15
15
|
// decide (ata's codegen rejects such schemas rather than risk a hang).
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// ECMA-262 WhiteSpace and LineTerminator, what `\s` matches: tab, line feed,
|
|
18
|
+
// vertical tab, form feed, carriage return, space, no-break space, the
|
|
19
|
+
// Unicode space separators, the line and paragraph separators, and the byte
|
|
20
|
+
// order mark. The set used to stop at U+00A0, so `^\S+$` accepted a string
|
|
21
|
+
// holding U+2028 or U+3000.
|
|
22
|
+
const WS = [[9, 13], [32, 32], [160, 160], [0x1680, 0x1680], [0x2000, 0x200a], [0x2028, 0x2029], [0x202f, 0x202f], [0x205f, 0x205f], [0x3000, 0x3000], [0xfeff, 0xfeff]]
|
|
23
|
+
// `.` matches anything but a line terminator: LF, CR and U+2028/U+2029. It
|
|
24
|
+
// excluded only LF, so `^.$` accepted "\r".
|
|
25
|
+
const isLineTerminator = (c) => c === 10 || c === 13 || c === 0x2028 || c === 0x2029
|
|
18
26
|
const DIGIT = [[48, 57]]
|
|
19
27
|
const WORD = [[48, 57], [65, 90], [97, 122], [95, 95]]
|
|
20
28
|
|
|
@@ -342,7 +350,7 @@ function makeRunner (prog) {
|
|
|
342
350
|
switch (ops[pc]) {
|
|
343
351
|
case OP_MATCH: return true
|
|
344
352
|
case OP_CHAR: if (c === cs[pc]) nlen = addThread(nlist, nlen, pc + 1, pos + 1, len); break
|
|
345
|
-
case OP_ANY: if (c !== -1 && c
|
|
353
|
+
case OP_ANY: if (c !== -1 && !isLineTerminator(c)) nlen = addThread(nlist, nlen, pc + 1, pos + 1, len); break
|
|
346
354
|
case OP_CLASS: if (c !== -1 && matchClass(classes[pc], c)) nlen = addThread(nlist, nlen, pc + 1, pos + 1, len); break
|
|
347
355
|
}
|
|
348
356
|
}
|
|
@@ -409,7 +417,7 @@ function makeRunner (prog) {
|
|
|
409
417
|
const pc = pcs[k]
|
|
410
418
|
switch (ops[pc]) {
|
|
411
419
|
case OP_CHAR: if (c === cs[pc]) count = closure(nlist, count, pc + 1, false, atEnd); break
|
|
412
|
-
case OP_ANY: if (c
|
|
420
|
+
case OP_ANY: if (!isLineTerminator(c)) count = closure(nlist, count, pc + 1, false, atEnd); break
|
|
413
421
|
case OP_CLASS: if (matchClass(classes[pc], c)) count = closure(nlist, count, pc + 1, false, atEnd); break
|
|
414
422
|
}
|
|
415
423
|
}
|
|
@@ -477,4 +485,4 @@ function patternIsSafe (src) {
|
|
|
477
485
|
try { compileSafe(src); return true } catch { return false }
|
|
478
486
|
}
|
|
479
487
|
|
|
480
|
-
module.exports = { compileSafe, patternIsSafe }
|
|
488
|
+
module.exports = { compileSafe, patternIsSafe, parse }
|
package/lib/validator-core.js
CHANGED
|
@@ -1293,8 +1293,14 @@ class Validator {
|
|
|
1293
1293
|
_installBufferApis(name) {
|
|
1294
1294
|
this._ensureCompiled();
|
|
1295
1295
|
const native = getNative();
|
|
1296
|
-
|
|
1296
|
+
// Without the addon the buffer APIs answer through validate(), as they do
|
|
1297
|
+
// for the shapes the gate routes: the same answers, slower. They threw
|
|
1298
|
+
// before, which made the addon a requirement for these five methods where
|
|
1299
|
+
// everywhere else it is an accelerator. Without Buffer, in a browser, there
|
|
1300
|
+
// is nothing to decode with and the error stays.
|
|
1301
|
+
if (!native && typeof Buffer === 'undefined') throw new Error(`Native addon required for ${name}(). Use validate(), isValidObject() or validateJSON(), which do not need it.`);
|
|
1297
1302
|
const { bufferNeedsSlowPath, installSlowBufferApis } = require('./buffer-gate.js');
|
|
1303
|
+
if (!native) return installSlowBufferApis(this);
|
|
1298
1304
|
if (bufferNeedsSlowPath(this._schemaObj, this._schemaMap, this._keywords)) return installSlowBufferApis(this);
|
|
1299
1305
|
this._ensureNative();
|
|
1300
1306
|
const slot = this._fastSlot;
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "JSON Schema
|
|
3
|
+
"version": "1.38.0",
|
|
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",
|
|
7
7
|
"types": "index.d.ts",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"release:check": "node scripts/regen-safe-regex-source.js && node tests/test_pack_purity.js && node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_source_sync.js && node tests/test_version_sync.js",
|
|
60
60
|
"build": "cmake-js build --target ata",
|
|
61
61
|
"rebuild": "cmake-js rebuild --target ata",
|
|
62
|
-
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_text_path_errors.js && node tests/test_compiled_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_standard_json_schema.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_esm_exports.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_native_lazy.js && node tests/test_cold_start_modules.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_uri_fast_path.js && node tests/test_extend_verdict.js && node tests/test_ipv4_regex.js && node tests/test_ipv6_fast_path.js && node tests/test_nested_recursion_decline.js && node tests/test_generator_error_content.js && node tests/test_inherited_key_presence.js && node tests/test_proto_accessor.js && node tests/test_hybrid_tier.js && node tests/test_first_rejection_builds_one.js && node tests/test_container_local.js && node tests/test_one_shot_validate.js && node tests/test_fixed_pattern_inline.js && node tests/test_plan_source.js && node tests/test_fused_remove_additional.js && node tests/test_runtime_parse.js && node tests/test_formats_single_pass.js && node tests/test_email_format_cost.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_ref_annotation_siblings.js && node tests/test_gate_wrapped_schemas.js && node tests/test_pattern_message_escape.js && node tests/test_nested_defaults_engines.js && node tests/test_nested_coercion_parity.js && node tests/test_preprocess_cache.js && node tests/test_large_schema_codegen.js && node tests/test_base_uri.js && node tests/test_hoisted_def_recursion.js && node tests/test_alias_definitions.js && node tests/test_property_names_ref.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_ref_error_order.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_single_pass_errors.js && node tests/test_lazy_json_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_lite_parity.js && node tests/test_lite_bundle_size.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
62
|
+
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_text_path_errors.js && node tests/test_compiled_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_standard_json_schema.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_esm_exports.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_native_lazy.js && node tests/test_buffer_apis_without_native.js && node tests/test_cold_start_modules.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_regex_ecma_whitespace.js && node tests/test_regex_native_linear.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_uri_fast_path.js && node tests/test_extend_verdict.js && node tests/test_ipv4_regex.js && node tests/test_ipv6_fast_path.js && node tests/test_nested_recursion_decline.js && node tests/test_generator_error_content.js && node tests/test_inherited_key_presence.js && node tests/test_proto_accessor.js && node tests/test_hybrid_tier.js && node tests/test_first_rejection_builds_one.js && node tests/test_container_local.js && node tests/test_one_shot_validate.js && node tests/test_fixed_pattern_inline.js && node tests/test_plan_source.js && node tests/test_fused_remove_additional.js && node tests/test_runtime_parse.js && node tests/test_formats_single_pass.js && node tests/test_email_format_cost.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_ref_annotation_siblings.js && node tests/test_gate_wrapped_schemas.js && node tests/test_pattern_message_escape.js && node tests/test_nested_defaults_engines.js && node tests/test_nested_coercion_parity.js && node tests/test_preprocess_cache.js && node tests/test_large_schema_codegen.js && node tests/test_base_uri.js && node tests/test_hoisted_def_recursion.js && node tests/test_alias_definitions.js && node tests/test_property_names_ref.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_ref_error_order.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_single_pass_errors.js && node tests/test_lazy_json_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_lite_parity.js && node tests/test_lite_bundle_size.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
63
63
|
"bench:size": "node benchmark/bench_aot_size.mjs",
|
|
64
64
|
"test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
|
|
65
65
|
"test:compat": "node tests/test_compat.js",
|
|
@@ -135,13 +135,13 @@
|
|
|
135
135
|
"LICENSE"
|
|
136
136
|
],
|
|
137
137
|
"optionalDependencies": {
|
|
138
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
139
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
140
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
141
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
142
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
143
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
144
|
-
"@ata-validator/native-win32-x64": "1.
|
|
138
|
+
"@ata-validator/native-darwin-arm64": "1.38.0",
|
|
139
|
+
"@ata-validator/native-darwin-x64": "1.38.0",
|
|
140
|
+
"@ata-validator/native-linux-arm64-gnu": "1.38.0",
|
|
141
|
+
"@ata-validator/native-linux-arm64-musl": "1.38.0",
|
|
142
|
+
"@ata-validator/native-linux-x64-gnu": "1.38.0",
|
|
143
|
+
"@ata-validator/native-linux-x64-musl": "1.38.0",
|
|
144
|
+
"@ata-validator/native-win32-x64": "1.38.0"
|
|
145
145
|
},
|
|
146
146
|
"peerDependencies": {
|
|
147
147
|
"yaml": "^2.0.0"
|