ata-validator 1.61.2 → 1.62.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.
Files changed (52) hide show
  1. package/README.md +159 -37
  2. package/bin/ata.js +35 -2
  3. package/compat.js +15 -2
  4. package/compiled-standard.d.ts +6 -0
  5. package/compiled-standard.mjs +66 -0
  6. package/index.d.ts +7 -0
  7. package/index.js +10 -0
  8. package/lib/aot-build.js +27 -1
  9. package/lib/aot-impl.js +108 -17
  10. package/lib/buffer-gate.js +25 -14
  11. package/lib/bun-target.js +21 -5
  12. package/lib/clone-emit.js +115 -10
  13. package/lib/codegen-engine.js +94 -18
  14. package/lib/compiled-common.js +11 -4
  15. package/lib/compiled-standard.js +33 -0
  16. package/lib/compiled-verdict.js +2 -2
  17. package/lib/compiled.js +1 -1
  18. package/lib/data-positions.js +18 -0
  19. package/lib/defaults.js +101 -23
  20. package/lib/describe-schema.js +38 -5
  21. package/lib/diagnose.js +51 -29
  22. package/lib/enrich-error.js +23 -1
  23. package/lib/error-sort-rank.js +12 -0
  24. package/lib/formats-source.js +12 -1
  25. package/lib/formats.js +19 -19
  26. package/lib/interpreter-core.js +23 -4
  27. package/lib/js-compiler-errors.js +70 -72
  28. package/lib/js-compiler.js +212 -72
  29. package/lib/json-rejections.js +1 -1
  30. package/lib/migrate.js +24 -5
  31. package/lib/native-attach.browser.js +7 -0
  32. package/lib/native-attach.js +76 -0
  33. package/lib/native-positions.js +7 -0
  34. package/lib/plan-compiler.js +7 -1
  35. package/lib/regex-linear.js +88 -0
  36. package/lib/rejections.js +13 -3
  37. package/lib/render-compact.js +5 -5
  38. package/lib/render-pretty.js +23 -10
  39. package/lib/safe-regex.js +29 -4
  40. package/lib/scan-compiler.js +16 -4
  41. package/lib/schema-diff.js +103 -0
  42. package/lib/stream.js +293 -0
  43. package/lib/tier0.js +6 -2
  44. package/lib/ts-gen.js +25 -12
  45. package/lib/unique-items.js +11 -3
  46. package/lib/validator-core.js +61 -66
  47. package/lib/verify.js +25 -6
  48. package/lib/version.js +1 -1
  49. package/package.json +24 -8
  50. package/stream.d.ts +51 -0
  51. package/stream.js +3 -0
  52. package/stream.mjs +3 -0
@@ -230,6 +230,17 @@ const useSafeEngine = (src) => {
230
230
  if (_nativeIsLinear(src)) return false
231
231
  return patternIsSafe(src)
232
232
  }
233
+ // Whether the generators may carry a pattern at all. A pattern with a
234
+ // Unicode property escape cannot take the linear engine, so it is written
235
+ // for the platform RegExp only when that is shown to run it in linear time
236
+ // (nativeIsLinear reads the escape as one class); otherwise the schema is
237
+ // declined to the interpreted engine. `^(\p{L}+)+$` went to the RegExp
238
+ // unchecked and took a second on 28 letters and a '!'.
239
+ function patternEngineOk (src) {
240
+ if (typeof src !== 'string' || !PROP_ESCAPE.test(src)) return true
241
+ if (_nativeIsLinear === null) _nativeIsLinear = require('./regex-linear').nativeIsLinear
242
+ return _nativeIsLinear(src)
243
+ }
233
244
 
234
245
  // Closure value for a patternProperties/propertyNames regex: the linear-time
235
246
  // safe matcher when the pattern is in the supported subset (and flag the ctx so
@@ -568,10 +579,11 @@ function compileToJS(schema, defs, schemaMap) {
568
579
 
569
580
  // format — hand-written fast checks. A format the code generator asserts
570
581
  // but this path cannot is a decline, not a pass.
571
- if (schema.format) {
572
- const fc = formatChecks()[schema.format]
573
- if (fc) checks.push((d) => typeof d !== 'string' || fc(d))
574
- else if (FORMAT_CODEGEN[schema.format]) return null
582
+ if (typeof schema.format === 'string') {
583
+ const table = formatChecks()
584
+ const fc = Object.hasOwn(table, schema.format) ? table[schema.format] : undefined
585
+ if (typeof fc === 'function') checks.push((d) => typeof d !== 'string' || fc(d))
586
+ else if (Object.hasOwn(FORMAT_CODEGEN, schema.format)) return null
575
587
  }
576
588
 
577
589
  // array size
@@ -836,8 +848,52 @@ function buildTypeCheck(types) {
836
848
  // 0.1 with -0.09999999999999995, which is neither near zero nor near the
837
849
  // divisor, so it called -1 not a multiple of 0.1 when -1 / 0.1 is exactly -10.
838
850
  // Emitted from one place because the expression stood in six.
851
+ // Whether a value of `known` type (proven by a parent) also satisfies
852
+ // `types`, so the child's own type check can be skipped: the same type, or
853
+ // an integer where a number is asked for.
854
+ function typeImplied (known, types) {
855
+ for (let i = 0; i < types.length; i++) {
856
+ if (types[i] === known) return true
857
+ if (types[i] === 'number' && known === 'integer') return true
858
+ }
859
+ return false
860
+ }
861
+ // The helper name a user format function is bound as. One name per format
862
+ // name, whatever the characters: 'foo-bar' and 'foo_bar' both came out as
863
+ // `_uf_foo_bar`, and the first function bound answered for both. Characters
864
+ // outside [A-Za-z0-9_] are written as `$` and four hex digits, so the map
865
+ // is one to one and both generators and the ahead-of-time output agree on it.
866
+ function ufName (format) {
867
+ return '_uf_' + String(format).replace(/[^A-Za-z0-9_]/g, (c) => '$' + c.charCodeAt(0).toString(16).padStart(4, '0'))
868
+ }
869
+ // The caller's own format function by that name, or null. An inherited
870
+ // value (`constructor`) is not one, as tier 0 and the interpreted engine
871
+ // read the option.
872
+ // Binds the function as a closure parameter of the generated function, once
873
+ // per format name, and remembers the name for the emitters.
874
+ function bindUserFormat (ctx, format, fn) {
875
+ const closureName = ufName(format)
876
+ if (!ctx.closureVars) { ctx.closureVars = []; ctx.closureVals = [] }
877
+ if (!ctx.closureVars.includes(closureName)) {
878
+ ctx.closureVars.push(closureName)
879
+ ctx.closureVals.push(fn)
880
+ }
881
+ if (!ctx.formatNames) ctx.formatNames = new Map()
882
+ ctx.formatNames.set(closureName, format)
883
+ return closureName
884
+ }
885
+ function formatNameOf (ctx, closureName) {
886
+ const name = ctx.formatNames ? ctx.formatNames.get(closureName) : undefined
887
+ return name === undefined ? null : name
888
+ }
889
+ function userFormatFn (userFormats, format) {
890
+ if (!userFormats || typeof format !== 'string' || !Object.prototype.hasOwnProperty.call(userFormats, format)) return null
891
+ const fn = userFormats[format]
892
+ return typeof fn === 'function' ? fn : null
893
+ }
839
894
  function multipleOfBad(v, m) {
840
- if (m === 0) return 'true'
895
+ m = numLit(m)
896
+ if (+m === 0) return 'true'
841
897
  // Negated rather than written as "the distance is too large": a quotient
842
898
  // that overflows to Infinity makes the distance NaN, and every comparison
843
899
  // with NaN is false, so the direct form called 1e308 a multiple of
@@ -1444,6 +1500,36 @@ function sharedDefNames (root, defs) {
1444
1500
  const def = defs[name]
1445
1501
  if (n >= 2 && def && typeof def === 'object' && JSON.stringify(def).length >= SHARED_DEF_MIN_CHARS) out.add(name)
1446
1502
  }
1503
+ // A definition small as written can still be large once its references
1504
+ // are followed: one that applies the next one twice, layer after layer, is
1505
+ // 2^n expanded. The $dynamicRef unrolling makes exactly that, and hands
1506
+ // the same copy to both places, so the count above (by node, from the
1507
+ // schema as given, whose references name the original definitions) saw
1508
+ // none of it, and sixteen layers came to a verdict function too large for
1509
+ // the stack. Here references are counted at every place they are reached
1510
+ // from in the definitions the generators read, and a definition applied
1511
+ // twice whose expansion passes SHARED_DEF_EXPANDED_MIN is shared as well.
1512
+ // The bound is high so ordinary definitions stay inline: at 256 KB the set
1513
+ // is unchanged on every SchemaStore schema (at 8 KB six changed, and
1514
+ // web-types measured about 3% slower).
1515
+ const edges = new Map()
1516
+ const seen2 = new WeakSet()
1517
+ const walk2 = (node) => {
1518
+ if (node === null || typeof node !== 'object') return
1519
+ if (typeof node.$ref === 'string') {
1520
+ const m = /^#\/(?:\$defs|definitions)\/([^/]+)$/.exec(node.$ref)
1521
+ if (m) edges.set(m[1], (edges.get(m[1]) || 0) + 1)
1522
+ }
1523
+ if (seen2.has(node)) return
1524
+ seen2.add(node)
1525
+ for (const v of Object.values(node)) walk2(v)
1526
+ }
1527
+ walk2(defs)
1528
+ for (const [name, n] of edges) {
1529
+ if (n < 2 || out.has(name)) continue
1530
+ const def = defs[name]
1531
+ if (def && typeof def === 'object' && expandedChars(def, SHARED_DEF_EXPANDED_MIN, defs) >= SHARED_DEF_EXPANDED_MIN) out.add(name)
1532
+ }
1447
1533
  return out
1448
1534
  }
1449
1535
  // Raising this to inline small definitions at every site was measured and
@@ -1455,6 +1541,7 @@ function sharedDefNames (root, defs) {
1455
1541
  // was tried too and gave 1.5x, not enough to carry its complexity. Left as a
1456
1542
  // known trade: on that shape the shared function costs 27 ns a call.
1457
1543
  const SHARED_DEF_MIN_CHARS = 120
1544
+ const SHARED_DEF_EXPANDED_MIN = 256 * 1024
1458
1545
 
1459
1546
  // A small definition applied at many places is called with objects of many
1460
1547
  // shapes, one or two per place, and V8 gives a function's property reads up
@@ -1661,6 +1748,27 @@ function refStringsOf(node) {
1661
1748
  return set
1662
1749
  }
1663
1750
 
1751
+ // The keywords whose values the generators write into source as numbers. A
1752
+ // value that is not a finite number is not written at all: the gate hands
1753
+ // the schema to the interpreted engine. Written as it came, a string value
1754
+ // was source, so `minimum: '0||(process.exitCode=7)'` ran.
1755
+ const NUMERIC_KEYWORDS = ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf', 'minLength', 'maxLength', 'minItems', 'maxItems', 'minProperties', 'maxProperties', 'minContains', 'maxContains']
1756
+ function hasNonNumericBound (node) {
1757
+ for (let i = 0; i < NUMERIC_KEYWORDS.length; i++) {
1758
+ const v = node[NUMERIC_KEYWORDS[i]]
1759
+ // v - v is 0 for a finite number and NaN for NaN and the infinities.
1760
+ if (v !== undefined && (typeof v !== 'number' || v - v !== 0)) return true
1761
+ }
1762
+ return false
1763
+ }
1764
+ // Every number the generators write into source goes through here. The gate
1765
+ // has already turned away a value that is not a finite number; should one
1766
+ // reach this anyway, the schema declines rather than becoming code.
1767
+ function numLit (x) {
1768
+ if (typeof x !== 'number' || !Number.isFinite(x)) throw DECLINE
1769
+ return String(x)
1770
+ }
1771
+
1664
1772
  function codegenSafe(schema, schemaMap) {
1665
1773
  if (typeof schema === 'boolean') return true
1666
1774
  if (typeof schema !== 'object' || schema === null) return true
@@ -1670,6 +1778,17 @@ function codegenSafe(schema, schemaMap) {
1670
1778
  // vacuous, so hand the schema over rather than silently ignore a constraint.
1671
1779
  if (schema.propertyDependencies !== undefined) return false
1672
1780
 
1781
+ if (hasNonNumericBound(schema)) return false
1782
+
1783
+ // A pattern the linear engine cannot take (a Unicode property escape) runs
1784
+ // on the platform RegExp only when that is shown to be linear too; any
1785
+ // other goes to the interpreted engine, like a pattern with a backreference.
1786
+ if (typeof schema.pattern === 'string' && !patternEngineOk(schema.pattern)) return false
1787
+ if (schema.patternProperties && typeof schema.patternProperties === 'object') {
1788
+ for (const pat of Object.keys(schema.patternProperties)) if (!patternEngineOk(pat)) return false
1789
+ }
1790
+ if (schema.propertyNames && typeof schema.propertyNames === 'object' && typeof schema.propertyNames.pattern === 'string' && !patternEngineOk(schema.propertyNames.pattern)) return false
1791
+
1673
1792
  // Only bail on $dynamicRef if it can't be resolved at compile time
1674
1793
  if (schema.$dynamicRef && !schema.$dynamicRef.startsWith('#')) return false
1675
1794
 
@@ -2572,6 +2691,7 @@ const _refFreeLength = new WeakMap()
2572
2691
  const DYN_DROP = new Set(['$id', '$anchor', '$dynamicAnchor', '$defs', 'definitions'])
2573
2692
  const BOOL_TRUE = { bool: true }
2574
2693
  const BOOL_FALSE = { bool: false }
2694
+ const DYN_COPY_BUDGET = 4096
2575
2695
  function expandDynamicScopes(root, schemaMap) {
2576
2696
  if (root === null || typeof root !== 'object' || Array.isArray(root)) return null
2577
2697
  const { indexSchemas, resolveRef, splitFragment, resolveUri } = require('./interpreter')._refInternals
@@ -2634,6 +2754,11 @@ function expandDynamicScopes(root, schemaMap) {
2634
2754
  const e = { copy: Array.isArray(node) ? [] : {}, sig, name: null, referenced: false }
2635
2755
  bySig.set(sig, e)
2636
2756
  order.push(e)
2757
+ // One copy per node and scope chain: two embedded resources per layer
2758
+ // that both reference the next layer make 2^i copies of layer i, and
2759
+ // sixteen layers ran out of stack after a second. Past the budget the
2760
+ // unrolling declines and the interpreted engine answers.
2761
+ if (order.length > DYN_COPY_BUDGET) { fail = true; return e }
2637
2762
  if (Array.isArray(node)) { for (const x of node) e.copy.push(copyValue(x, base, chain)); return e }
2638
2763
  const out = e.copy
2639
2764
  // Only a schema node reads `$ref` as a reference and carries an `$id`; in
@@ -2664,7 +2789,8 @@ function expandDynamicScopes(root, schemaMap) {
2664
2789
  // (a type name, a limit, a required name) is kept as it is.
2665
2790
  const copyValue = (v, base, chain) => (v !== null && typeof v === 'object') || typeof v === 'boolean' ? entryFor(v, base, chain).copy : v
2666
2791
  const rootBase = state.nodeBase.get(root)
2667
- const rootEntry = entryFor(root, rootBase, [rootBase])
2792
+ let rootEntry
2793
+ try { rootEntry = entryFor(root, rootBase, [rootBase]) } catch (e) { if (e instanceof RangeError) return null; throw e }
2668
2794
  if (fail) return null
2669
2795
  const rootCopy = rootEntry.copy
2670
2796
  const defsKey = root.$defs && typeof root.$defs === 'object' ? '$defs' : (root.definitions && typeof root.definitions === 'object' ? 'definitions' : '$defs')
@@ -2815,8 +2941,8 @@ const VERDICT_SPLIT = 64 * 1024
2815
2941
  // stay expanded and keep their speed.
2816
2942
  const EXPANDED_SHARE = 400 * 1024
2817
2943
  const SIZE_SKIPS = new Set(['$defs', 'definitions', 'description', 'title', 'examples', 'default', '$comment', 'markdownDescription', 'deprecated', 'readOnly', 'writeOnly'])
2818
- function expandedChars (root, limit) {
2819
- const defs = (root && (root.$defs || root.definitions)) || {}
2944
+ function expandedChars (root, limit, rootDefs) {
2945
+ const defs = rootDefs || (root && (root.$defs || root.definitions)) || {}
2820
2946
  const memo = new Map()
2821
2947
  const size = (n, stack) => {
2822
2948
  if (n === null || typeof n !== 'object') return 8
@@ -3185,11 +3311,7 @@ function attachHeavy (boolFn, P) {
3185
3311
  if (closureNames[i].startsWith('_uf_')) {
3186
3312
  // The format's own name, so an emitter can look it up at runtime
3187
3313
  // instead of embedding the function.
3188
- let format = null
3189
- for (const key of Object.keys(ctx.userFormats)) {
3190
- if (ctx.userFormats[key] === closureValues[i]) { format = key; break }
3191
- }
3192
- fmtEntries.push({ name: closureNames[i], fn: closureValues[i], format })
3314
+ fmtEntries.push({ name: closureNames[i], fn: closureValues[i], format: formatNameOf(ctx, closureNames[i]) })
3193
3315
  }
3194
3316
  }
3195
3317
  if (fmtEntries.length) boolFn._formatClosures = fmtEntries
@@ -3312,7 +3434,7 @@ function needsLocal(schema) {
3312
3434
  if (schema.minLength !== undefined) checkCount++
3313
3435
  if (schema.maxLength !== undefined) checkCount++
3314
3436
  if (schema.pattern) checkCount++
3315
- if (schema.format) checkCount++
3437
+ if (schema.format !== undefined) checkCount++
3316
3438
  } else if (t === 'integer' || t === 'number') {
3317
3439
  if (schema.minimum !== undefined) checkCount++
3318
3440
  if (schema.maximum !== undefined) checkCount++
@@ -3345,26 +3467,26 @@ function tryGenCombined(schema, access, ctx) {
3345
3467
  : `{const _v=${access};if(${conds.join('||')})return false}`
3346
3468
 
3347
3469
  if (t === 'string') {
3348
- if (schema.pattern || schema.format) return null
3470
+ if (schema.pattern || schema.format !== undefined) return null
3349
3471
  // s.length is an upper bound on cpLen and at least cpLen / 2 (worst case
3350
3472
  // all-surrogate). Use s.length fast paths and only call _cpLen in the
3351
3473
  // uncertain band; ASCII strings (>99% of real data) skip _cpLen entirely.
3352
3474
  if (schema.minLength !== undefined && schema.maxLength !== undefined) {
3353
- const M = schema.minLength
3354
- const X = schema.maxLength
3475
+ const M = +numLit(schema.minLength)
3476
+ const X = +numLit(schema.maxLength)
3355
3477
  const v2 = isIdent ? access : '_v'
3356
3478
  const prelude = isIdent ? '' : `const _v=${access};`
3357
3479
  return `{${prelude}if(typeof ${v2}!=='string')return false;const _lv=${v2}.length;if(_lv<${M}||_lv>${X * 2})return false;if(_lv<${M * 2}||_lv>${X}){const _cp=_cpLen(${v2});if(_cp<${M}||_cp>${X})return false}}`
3358
3480
  }
3359
3481
  const conds = [`typeof _v!=='string'`]
3360
3482
  if (schema.minLength !== undefined && schema.minLength > 0) {
3361
- const M = schema.minLength
3483
+ const M = +numLit(schema.minLength)
3362
3484
  conds.push(`_v.length<${M}`)
3363
3485
  // For M=1, length<1 already catches all failures (any non-empty string has cpLen>=1).
3364
3486
  if (M > 1) conds.push(`_v.length<${M * 2}&&_cpLen(_v)<${M}`)
3365
3487
  }
3366
3488
  if (schema.maxLength !== undefined) {
3367
- const X = schema.maxLength
3489
+ const X = +numLit(schema.maxLength)
3368
3490
  // For X=0, only empty string passes, length>0 fails — no cpLen needed.
3369
3491
  if (X === 0) {
3370
3492
  conds.push(`_v.length>0`)
@@ -3379,10 +3501,10 @@ function tryGenCombined(schema, access, ctx) {
3379
3501
 
3380
3502
  if (t === 'integer') {
3381
3503
  const conds = [`!Number.isInteger(_v)`]
3382
- if (schema.minimum !== undefined) conds.push(`_v<${schema.minimum}`)
3383
- if (schema.maximum !== undefined) conds.push(`_v>${schema.maximum}`)
3384
- if (schema.exclusiveMinimum !== undefined) conds.push(`_v<=${schema.exclusiveMinimum}`)
3385
- if (schema.exclusiveMaximum !== undefined) conds.push(`_v>=${schema.exclusiveMaximum}`)
3504
+ if (schema.minimum !== undefined) conds.push(`_v<${numLit(schema.minimum)}`)
3505
+ if (schema.maximum !== undefined) conds.push(`_v>${numLit(schema.maximum)}`)
3506
+ if (schema.exclusiveMinimum !== undefined) conds.push(`_v<=${numLit(schema.exclusiveMinimum)}`)
3507
+ if (schema.exclusiveMaximum !== undefined) conds.push(`_v>=${numLit(schema.exclusiveMaximum)}`)
3386
3508
  if (schema.multipleOf !== undefined) conds.push(`(${multipleOfBad('_v', schema.multipleOf)})`)
3387
3509
  if (conds.length < 2) return null
3388
3510
  return bind(conds)
@@ -3390,10 +3512,10 @@ function tryGenCombined(schema, access, ctx) {
3390
3512
 
3391
3513
  if (t === 'number') {
3392
3514
  const conds = [`!Number.isFinite(_v)`]
3393
- if (schema.minimum !== undefined) conds.push(`_v<${schema.minimum}`)
3394
- if (schema.maximum !== undefined) conds.push(`_v>${schema.maximum}`)
3395
- if (schema.exclusiveMinimum !== undefined) conds.push(`_v<=${schema.exclusiveMinimum}`)
3396
- if (schema.exclusiveMaximum !== undefined) conds.push(`_v>=${schema.exclusiveMaximum}`)
3515
+ if (schema.minimum !== undefined) conds.push(`_v<${numLit(schema.minimum)}`)
3516
+ if (schema.maximum !== undefined) conds.push(`_v>${numLit(schema.maximum)}`)
3517
+ if (schema.exclusiveMinimum !== undefined) conds.push(`_v<=${numLit(schema.exclusiveMinimum)}`)
3518
+ if (schema.exclusiveMaximum !== undefined) conds.push(`_v>=${numLit(schema.exclusiveMaximum)}`)
3397
3519
  if (schema.multipleOf !== undefined) conds.push(`(${multipleOfBad('_v', schema.multipleOf)})`)
3398
3520
  if (conds.length < 2) return null
3399
3521
  return bind(conds)
@@ -3575,7 +3697,11 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3575
3697
  const types = schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : null
3576
3698
  let effectiveType = knownType
3577
3699
  if (types) {
3578
- if (!knownType) {
3700
+ // The parent's proven type stands in for this node's check only when it
3701
+ // implies it. A narrower or different type (`type: number` over `allOf:
3702
+ // [{type: integer}]`, a dependentSchemas branch of another type) keeps
3703
+ // its own check: it used to be dropped, and 1.5 passed as an integer.
3704
+ if (!knownType || !typeImplied(knownType, types)) {
3579
3705
  // Emit the type check — use direct negation for single types (avoids !() wrapper)
3580
3706
  if (types.length === 1) {
3581
3707
  switch (types[0]) {
@@ -3708,13 +3834,12 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3708
3834
  // and the interpreted engine reads it the same way. With `type: number` the
3709
3835
  // type check has already turned it away, so the guard is only needed here.
3710
3836
  const numGuard = isNum ? '' : `Number.isFinite(${v})&&`
3711
- if (schema.minimum !== undefined) lines.push(`if(${numGuard}${v}<${schema.minimum})return false`)
3712
- if (schema.maximum !== undefined) lines.push(`if(${numGuard}${v}>${schema.maximum})return false`)
3713
- if (schema.exclusiveMinimum !== undefined) lines.push(`if(${numGuard}${v}<=${schema.exclusiveMinimum})return false`)
3714
- if (schema.exclusiveMaximum !== undefined) lines.push(`if(${numGuard}${v}>=${schema.exclusiveMaximum})return false`)
3837
+ if (schema.minimum !== undefined) lines.push(`if(${numGuard}${v}<${numLit(schema.minimum)})return false`)
3838
+ if (schema.maximum !== undefined) lines.push(`if(${numGuard}${v}>${numLit(schema.maximum)})return false`)
3839
+ if (schema.exclusiveMinimum !== undefined) lines.push(`if(${numGuard}${v}<=${numLit(schema.exclusiveMinimum)})return false`)
3840
+ if (schema.exclusiveMaximum !== undefined) lines.push(`if(${numGuard}${v}>=${numLit(schema.exclusiveMaximum)})return false`)
3715
3841
  if (schema.multipleOf !== undefined) {
3716
- const m = schema.multipleOf
3717
- const bad = `(${multipleOfBad(v, m)})`
3842
+ const bad = `(${multipleOfBad(v, schema.multipleOf)})`
3718
3843
  lines.push(`if(${numGuard}${bad})return false`)
3719
3844
  }
3720
3845
 
@@ -3727,15 +3852,15 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3727
3852
  // Only call _cpLen in the uncertain band. ASCII strings (>99% of real data)
3728
3853
  // never enter the band.
3729
3854
  if (schema.minLength !== undefined && schema.maxLength !== undefined) {
3730
- const M = schema.minLength
3731
- const X = schema.maxLength
3855
+ const M = +numLit(schema.minLength)
3856
+ const X = +numLit(schema.maxLength)
3732
3857
  const li = ctx.varCounter++
3733
3858
  const lv = `_l${li}`
3734
3859
  const body = `{const ${lv}=${v}.length;if(${lv}<${M}||${lv}>${X * 2})return false;if(${lv}<${M * 2}||${lv}>${X}){const _cp=_cpLen(${v});if(_cp<${M}||_cp>${X})return false}}`
3735
3860
  lines.push(isStr ? body : `if(typeof ${v}==='string')${body}`)
3736
3861
  } else {
3737
3862
  if (schema.minLength !== undefined && schema.minLength > 0) {
3738
- const M = schema.minLength
3863
+ const M = +numLit(schema.minLength)
3739
3864
  // M==1: length<1 already catches empty strings (any non-empty string has cpLen>=1).
3740
3865
  const body = M === 1
3741
3866
  ? `if(${v}.length<1)return false`
@@ -3743,7 +3868,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3743
3868
  lines.push(isStr ? body : `if(typeof ${v}==='string'){${body}}`)
3744
3869
  }
3745
3870
  if (schema.maxLength !== undefined) {
3746
- const X = schema.maxLength
3871
+ const X = +numLit(schema.maxLength)
3747
3872
  // X==0: only empty string passes, no cpLen needed.
3748
3873
  const body = X === 0
3749
3874
  ? `if(${v}.length>0)return false`
@@ -3753,12 +3878,12 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3753
3878
  }
3754
3879
 
3755
3880
  // array size — skip guard if known array
3756
- if (schema.minItems !== undefined) lines.push(isArr ? `if(${v}.length<${schema.minItems})return false` : `if(Array.isArray(${v})&&${v}.length<${schema.minItems})return false`)
3757
- if (schema.maxItems !== undefined) lines.push(isArr ? `if(${v}.length>${schema.maxItems})return false` : `if(Array.isArray(${v})&&${v}.length>${schema.maxItems})return false`)
3881
+ if (schema.minItems !== undefined) lines.push(isArr ? `if(${v}.length<${numLit(schema.minItems)})return false` : `if(Array.isArray(${v})&&${v}.length<${numLit(schema.minItems)})return false`)
3882
+ if (schema.maxItems !== undefined) lines.push(isArr ? `if(${v}.length>${numLit(schema.maxItems)})return false` : `if(Array.isArray(${v})&&${v}.length>${numLit(schema.maxItems)})return false`)
3758
3883
 
3759
3884
  // object size
3760
- if (schema.minProperties !== undefined) lines.push(`if(${objGuard}Object.keys(${v}).length<${schema.minProperties})return false`)
3761
- if (schema.maxProperties !== undefined) lines.push(`if(${objGuard}Object.keys(${v}).length>${schema.maxProperties})return false`)
3885
+ if (schema.minProperties !== undefined) lines.push(`if(${objGuard}Object.keys(${v}).length<${numLit(schema.minProperties)})return false`)
3886
+ if (schema.maxProperties !== undefined) lines.push(`if(${objGuard}Object.keys(${v}).length>${numLit(schema.maxProperties)})return false`)
3762
3887
 
3763
3888
  if (schema.pattern) {
3764
3889
  // Try inline charCode compilation for simple patterns (avoids RegExp engine)
@@ -3782,20 +3907,18 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
3782
3907
  }
3783
3908
  }
3784
3909
 
3785
- if (schema.format) {
3786
- const fc = FORMAT_CODEGEN[schema.format]
3787
- if (fc) {
3788
- lines.push(fc(v, isStr, ctx))
3789
- } else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
3910
+ // The caller's function for the format wins over the built-in check, as
3911
+ // the interpreted engine and tier 0 read the option; a format named ''
3912
+ // is a name like any other.
3913
+ if (typeof schema.format === 'string') {
3914
+ const uf = userFormatFn(ctx.userFormats, schema.format)
3915
+ if (uf !== null) {
3790
3916
  // User-supplied format checker: thread the function via closure and call at runtime.
3791
- const safeName = schema.format.replace(/[^a-zA-Z0-9_]/g, '_')
3792
- const closureName = `_uf_${safeName}`
3793
- if (!ctx.closureVars.includes(closureName)) {
3794
- ctx.closureVars.push(closureName)
3795
- ctx.closureVals.push(ctx.userFormats[schema.format])
3796
- }
3917
+ const closureName = bindUserFormat(ctx, schema.format, uf)
3797
3918
  const guard = isStr ? '' : `typeof ${v}==='string'&&`
3798
3919
  lines.push(`if(${guard}!${closureName}(${v}))return false`)
3920
+ } else if (Object.hasOwn(FORMAT_CODEGEN, schema.format)) {
3921
+ lines.push(FORMAT_CODEGEN[schema.format](v, isStr, ctx))
3799
3922
  }
3800
3923
  }
3801
3924
 
@@ -4144,8 +4267,8 @@ function genPropertyNamesNode(schema, v, lines, ctx, knownType, isObj) {
4144
4267
  return
4145
4268
  }
4146
4269
  lines.push(`${guard}{${ownHead(ctx, v, `_k${ki}`, 'const')}{`)
4147
- if (pn.minLength !== undefined) lines.push(`if(_k${ki}.length<${pn.minLength})return false`)
4148
- if (pn.maxLength !== undefined) lines.push(`if(_k${ki}.length>${pn.maxLength})return false`)
4270
+ if (pn.minLength !== undefined) lines.push(`if(_k${ki}.length<${numLit(pn.minLength)})return false`)
4271
+ if (pn.maxLength !== undefined) lines.push(`if(_k${ki}.length>${numLit(pn.maxLength)})return false`)
4149
4272
  if (pn.pattern) {
4150
4273
  const fast = fastPrefixCheck(pn.pattern, `_k${ki}`)
4151
4274
  if (fast) {
@@ -4181,8 +4304,8 @@ function genPrefixItemsNode(schema, v, lines, ctx, knownType, isArr) {
4181
4304
 
4182
4305
  function genContainsNode(schema, v, lines, ctx, knownType, isArr) {
4183
4306
  const ci = ctx.varCounter++
4184
- const minC = schema.minContains !== undefined ? schema.minContains : 1
4185
- const maxC = schema.maxContains !== undefined ? schema.maxContains : Infinity
4307
+ const minC = schema.minContains !== undefined ? numLit(schema.minContains) : 1
4308
+ const maxC = schema.maxContains !== undefined ? numLit(schema.maxContains) : Infinity
4186
4309
  const subLines = []
4187
4310
  genCode(schema.contains, `_cv`, subLines, ctx)
4188
4311
  const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
@@ -4601,11 +4724,15 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
4601
4724
  ctx.refStack.add(schema.$ref)
4602
4725
  const outerReuse = ctx.plainReuse
4603
4726
  ctx.plainReuse = undefined
4727
+ // Hoisted like the cyclic ones, so a `$ref: '#'` inside reaches the
4728
+ // root through the forwarder rather than naming `_validate`.
4729
+ ctx.inDefFn = (ctx.inDefFn || 0) + 1
4604
4730
  try {
4605
4731
  genIsolated(ctx, () => genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx))
4606
4732
  } finally {
4607
4733
  ctx.refStack.delete(schema.$ref)
4608
4734
  ctx.plainReuse = outerReuse
4735
+ ctx.inDefFn--
4609
4736
  }
4610
4737
  if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
4611
4738
  ctx.deferredChecks = outerDeferred
@@ -4715,8 +4842,8 @@ function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
4715
4842
  lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{`)
4716
4843
  // propertyNames checks (merged into same loop)
4717
4844
  if (pn) {
4718
- if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength})return false`)
4719
- if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength})return false`)
4845
+ if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${numLit(pn.minLength)})return false`)
4846
+ if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${numLit(pn.maxLength)})return false`)
4720
4847
  if (pn.pattern) {
4721
4848
  const fast = fastPrefixCheck(pn.pattern, kVar)
4722
4849
  if (fast) {
@@ -4773,8 +4900,8 @@ function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
4773
4900
  lines.push(`${guard}{${ownHead(ctx, v, kVar, 'const')}{`)
4774
4901
  // propertyNames checks (merged)
4775
4902
  if (pn) {
4776
- if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength})return false`)
4777
- if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength})return false`)
4903
+ if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${numLit(pn.minLength)})return false`)
4904
+ if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${numLit(pn.maxLength)})return false`)
4778
4905
  if (pn.pattern) {
4779
4906
  const fast = fastPrefixCheck(pn.pattern, kVar)
4780
4907
  if (fast) {
@@ -5016,7 +5143,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
5016
5143
  const bfi = ctx.varCounter++
5017
5144
  ctx.closureVars.push(`_bk${bfi}`)
5018
5145
  ctx.closureVals.push(branchProps)
5019
- lines.push(`{const ${evVar}={}`)
5146
+ lines.push(`{const ${evVar}=Object.create(null)`)
5020
5147
  for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
5021
5148
  lines.push(`const _bf${bfi}=[${fns.join(',')}]`)
5022
5149
  if (branchKeyword === 'oneOf') {
@@ -5033,7 +5160,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
5033
5160
  // dependentSchemas: conditional merge at runtime
5034
5161
  const ei = ctx.varCounter++
5035
5162
  const evVar = `_ev${ei}`
5036
- lines.push(`{const ${evVar}={}`)
5163
+ lines.push(`{const ${evVar}=Object.create(null)`)
5037
5164
  for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
5038
5165
  for (const [trigger, depSchema] of Object.entries(schema.dependentSchemas)) {
5039
5166
  const depResult = collectEvaluated(depSchema, ctx.schemaMap, ctx.rootDefs)
@@ -5064,7 +5191,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
5064
5191
  if (allPatterns.length > 0) {
5065
5192
  const ei = ctx.varCounter++
5066
5193
  const evVar = `_ev${ei}`
5067
- lines.push(`{const ${evVar}={}`)
5194
+ lines.push(`{const ${evVar}=Object.create(null)`)
5068
5195
  for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
5069
5196
  const reVars = []
5070
5197
  for (const pat of allPatterns) {
@@ -5113,7 +5240,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
5113
5240
  // used to emit nothing, and nothing read as valid.
5114
5241
  const ei = ctx.varCounter++
5115
5242
  const evVar = `_ev${ei}`
5116
- lines.push(`{const ${evVar}={}`)
5243
+ lines.push(`{const ${evVar}=Object.create(null)`)
5117
5244
  for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
5118
5245
  const inner = `for(var _k in ${v}){if(!${evVar}[_k]&&${ownFail(ctx, v)})return false}`
5119
5246
  _deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
@@ -5124,7 +5251,7 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
5124
5251
  const ei = ctx.varCounter++
5125
5252
  const evVar = `_ev${ei}`
5126
5253
  const ukVar = `_uk${ei}`
5127
- lines.push(`{const ${evVar}={}`)
5254
+ lines.push(`{const ${evVar}=Object.create(null)`)
5128
5255
  for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
5129
5256
 
5130
5257
  if (branchKeyword) {
@@ -5346,7 +5473,10 @@ const emailHelper = () => `function _em(_s){${getFormats().emailSource('_s', tru
5346
5473
  // each call site, where the tables would have to be rebuilt.
5347
5474
  const uriHelper = () => getFormats().uriHelperSource('_uri')
5348
5475
 
5349
- const FORMAT_CODEGEN = {
5476
+ // A table with no prototype, read with Object.hasOwn: `format: 'constructor'`
5477
+ // used to find Object here and `format: 'hasOwnProperty'` threw while
5478
+ // compiling.
5479
+ const FORMAT_CODEGEN = Object.assign(Object.create(null), {
5350
5480
  email: (v, isStr, ctx) => {
5351
5481
  if (!ctx) return getFormats().emailSource(v, isStr)
5352
5482
  hoistOnce(ctx, '_emHoisted', emailHelper())
@@ -5374,7 +5504,11 @@ const FORMAT_CODEGEN = {
5374
5504
  return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`
5375
5505
  },
5376
5506
  date: (...a) => getFormats().dateSource(...a),
5377
- uuid: (...a) => getFormats().uuidSource(...a),
5507
+ uuid: (v, isStr, ctx) => {
5508
+ if (!ctx) return getFormats().uuidSource(v, isStr)
5509
+ hoistOnce(ctx, '_uuHoisted', getFormats().uuidHelperSource('_uu'))
5510
+ return isStr ? `if(!_uu(${v}))return false` : `if(typeof ${v}==='string'&&!_uu(${v}))return false`
5511
+ },
5378
5512
  'date-time': (...a) => getFormats().dateTimeSource(...a),
5379
5513
  time: (...a) => getFormats().timeSource(...a),
5380
5514
  duration: (...a) => getFormats().durationSource(...a),
@@ -5403,7 +5537,7 @@ const FORMAT_CODEGEN = {
5403
5537
  return isStr ? body : `if(typeof ${v}==='string'){${body}}`
5404
5538
  },
5405
5539
  hostname: (...a) => getFormats().hostnameSource(...a),
5406
- }
5540
+ })
5407
5541
 
5408
5542
  // `^[class]+$`, `^[class]{m,n}$` and `^[class]{n}$` for a class of ASCII
5409
5543
  // letters, digits, `_` and `-`: a function in the preamble that reads each
@@ -5525,6 +5659,10 @@ function fixedSequenceInline(pattern, varName) {
5525
5659
  const q = /^\{(\d+)\}/.exec(pattern.slice(i))
5526
5660
  if (!q) return null
5527
5661
  count = Number(q[1])
5662
+ // Checked against the limit before the pieces are pushed: a{100000000}
5663
+ // pushed a hundred million entries first, and a{2000000000} threw a
5664
+ // RangeError out of validate().
5665
+ if (!Number.isSafeInteger(count) || count > 32 - pieces.length) return null
5528
5666
  i += q[0].length
5529
5667
  } else if (i < end && '*+?'.includes(pattern[i])) {
5530
5668
  return null
@@ -5631,8 +5769,10 @@ function fastPrefixCheck(pattern, keyVar) {
5631
5769
  if (alts.length <= 8) return `(${alts.map((a) => `${keyVar}===${JSON.stringify(a)}`).join('||')})`
5632
5770
  }
5633
5771
  if (pattern === '.*') return 'true'
5634
- // Match patterns like ^literal (no regex metacharacters after ^)
5635
- const m = pattern.match(/^\^([a-zA-Z0-9_\-./]+)$/)
5772
+ // Match patterns like ^literal (no regex metacharacters after ^). A dot is
5773
+ // a wildcard, so it is not literal: `^a.b` read as the prefix 'a.b' let
5774
+ // 'axb' skip its value schema.
5775
+ const m = pattern.match(/^\^([a-zA-Z0-9_\-/]+)$/)
5636
5776
  if (!m) return null
5637
5777
  const prefix = m[1]
5638
5778
  if (prefix.length === 0 || prefix.length > 8) return null // too long = diminishing returns
@@ -6121,4 +6261,4 @@ Object.defineProperty(module.exports, '_expandDynamicScopes', { value: expandDyn
6121
6261
  // For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
6122
6262
  Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })
6123
6263
 
6124
- module.exports._internal = { ownHead, plainFlag, hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, enumOperand, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun, keyLoopEligible, SAME_KEYS_HELPER, seqTag }
6264
+ module.exports._internal = { ownHead, plainFlag, hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, enumOperand, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun, keyLoopEligible, SAME_KEYS_HELPER, seqTag, numLit, hasNonNumericBound, patternEngineOk, ufName, userFormatFn, bindUserFormat, formatNameOf }
@@ -97,7 +97,7 @@ Object.defineProperty(LazyJsonRejection.prototype, 'errors', {
97
97
  if (e && !e.dataFrame) {
98
98
  const path = e.path != null ? e.path : (e.instancePath || '');
99
99
  const p = positions[path];
100
- if (p) e.dataFrame = { byteOffset: p.byteOffset, length: p.length, line: p.line, col: p.col, text: p.text };
100
+ if (p) { e.dataFrame = { byteOffset: p.byteOffset, length: p.length, line: p.line, col: p.col, text: p.text }; if (p.textCol !== undefined) e.dataFrame.textCol = p.textCol; }
101
101
  }
102
102
  }
103
103
  self._posCache.reset();
package/lib/migrate.js CHANGED
@@ -68,10 +68,29 @@ function mask (src) {
68
68
  return out.join('');
69
69
  }
70
70
 
71
+ // The line of `index` in `src`. The callers ask for ascending indexes of one
72
+ // file, so the count continues from the last answer rather than from the
73
+ // start: counted from the start for each match, 40000 requires took 16 s.
74
+ let lineSrc = null;
75
+ let lineIdx = 0;
76
+ let lineNo = 1;
71
77
  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;
78
+ if (src !== lineSrc || index < lineIdx) { lineSrc = src; lineIdx = 0; lineNo = 1; }
79
+ for (let i = lineIdx; i < index; i++) if (src.charCodeAt(i) === 10) lineNo++;
80
+ lineIdx = index;
81
+ return lineNo;
82
+ }
83
+
84
+ // A file name or a source line as the report prints it: control characters
85
+ // and the Unicode line separators written as escapes, so a name cannot
86
+ // forge a line or a colour on the terminal.
87
+ function plain (s) {
88
+ return String(s).replace(/[\x00-\x1f\x7f\u2028\u2029]/g, (c) => {
89
+ if (c === '\n') return '\\n';
90
+ if (c === '\r') return '\\r';
91
+ if (c === '\t') return '\\t';
92
+ return '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0');
93
+ });
75
94
  }
76
95
 
77
96
  // Every module specifier in `src`: the string following `from`, inside
@@ -238,8 +257,8 @@ function formatReport (result, opts = {}) {
238
257
  }
239
258
  const label = { rewrite: 'rewrite', delete: 'delete ', flag: 'flag ' };
240
259
  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}`);
260
+ lines.push(plain(r.file));
261
+ for (const f of r.findings) lines.push(` ${label[f.kind]} line ${String(f.line).padStart(4)} ${plain(f.text)}`);
243
262
  }
244
263
  const rewrites = result.report.reduce((n, r) => n + r.findings.filter((f) => f.kind !== 'flag').length, 0);
245
264
  lines.push('');
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ // The browser has no native engine; see lib/native-attach.js.
4
+ function attachNative() {}
5
+ // Unreachable without an addon; validator-core returns before it.
6
+ attachNative.installFast = function installFast() {};
7
+ module.exports = attachNative;