ata-validator 1.56.0 → 1.58.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.
@@ -236,6 +236,10 @@ function installCodegenPaths (ctx) {
236
236
  // The boolean engine is the verdict authority for these paths; the
237
237
  // final lazy wrapper uses it to skip error construction entirely.
238
238
  if (!hasDynRef || _isCodegen) this._fastVerdict = preprocess ? null : jsFn;
239
+ // With a preprocess pass the verdict runs it first, for the lazy layer in
240
+ // lib/validator-core.js, which then builds the errors only when they are
241
+ // read, from the document as the pass left it.
242
+ if ((!hasDynRef || _isCodegen) && preprocess && !fusedRemove) { ctx.preVerdict = (data) => { preprocess(data); return jsFn(data) }; ctx.rawVerdict = jsFn; }
239
243
 
240
244
  if (options.abortEarly && jsFn && !hasDynRef) {
241
245
  // abortEarly: do NOT enrich. Skip position lookups, suggestions, source maps.
@@ -319,7 +323,7 @@ function installCodegenPaths (ctx) {
319
323
  // deciding again: the lazy layer in lib/validator-core.js has its verdict
320
324
  // and used to call validate() to get the errors, which ran the verdict a
321
325
  // second time and built a second rejection around it.
322
- if (!preprocess) ctx.rejectBase = onReject;
326
+ ctx.rejectBase = onReject;
323
327
  // The rich combined function, for a validator whose errors are being read
324
328
  // with richErrors on. See _ensureCompiled.
325
329
  if (!preprocess) {
@@ -360,7 +364,7 @@ function installCodegenPaths (ctx) {
360
364
  this.validate = preprocess
361
365
  ? (data) => { preprocess(data); return safeCombinedFn(data); }
362
366
  : safeCombinedFn;
363
- if (!preprocess) ctx.rejectBase = (data) => _mustReject(safeCombinedFn(data));
367
+ ctx.rejectBase = (data) => _mustReject(safeCombinedFn(data));
364
368
  } else {
365
369
  this.validate = preprocess
366
370
  ? (data) => {
@@ -368,7 +372,7 @@ function installCodegenPaths (ctx) {
368
372
  return jsFn(data) ? VALID_RESULT : errOnly(data);
369
373
  }
370
374
  : (data) => (jsFn(data) ? VALID_RESULT : errOnly(data));
371
- if (!preprocess) ctx.rejectBase = errOnly;
375
+ ctx.rejectBase = errOnly;
372
376
  }
373
377
  }
374
378
  // Verbose mode: populate parentSchema, schema and data on each error, the
@@ -803,18 +807,88 @@ function emitCoercions(node, ov, lines, st, depth) {
803
807
  }
804
808
  st.seen.delete(node);
805
809
  }
806
- function hasDefaultsInside(node, seen) {
807
- if (!node || typeof node !== 'object' || !node.properties || seen.has(node)) return false;
808
- seen.add(node);
809
- let found = false;
810
- for (const prop of Object.values(node.properties)) {
811
- if (prop && typeof prop === 'object' && (prop.default !== undefined || hasDefaultsInside(prop, seen))) { found = true; break; }
810
+ // From the closure pass, on the first reference a defaults walk meets: loaded
811
+ // with this module it joined every process's cold start.
812
+ let _localRef = null;
813
+ const localRef = (root, ref) => (_localRef || (_localRef = require('./defaults').localRef))(root, ref);
814
+ // Whether a default is reachable from a schema through the places the pass
815
+ // goes (lib/defaults.js reachesDefault, which this pass is held to): the
816
+ // same walk, with answers kept per schema object for the whole pass, except
817
+ // one that rests on a schema still being decided (a recursive reference). It
818
+ // started over for every property, quadratic in the depth of the schema.
819
+ function reachState(root) { return { root, memo: new Map() }; }
820
+ function hasDefaultsInside(node, rs) { return reachDefault(node, rs, new Set()) === 1; }
821
+ function reachDefault(node, rs, stack) {
822
+ if (!node || typeof node !== 'object') return 0;
823
+ const known = rs.memo.get(node);
824
+ if (known !== undefined) return known;
825
+ if (stack.has(node)) return 2;
826
+ stack.add(node);
827
+ let open = false;
828
+ const check = (n) => { const r = reachDefault(n, rs, stack); if (r === 2) open = true; return r === 1; };
829
+ let yes = false;
830
+ if (node.properties && typeof node.properties === 'object') {
831
+ for (const prop of Object.values(node.properties)) {
832
+ if (prop && typeof prop === 'object' && (prop.default !== undefined || check(prop))) { yes = true; break; }
833
+ }
812
834
  }
813
- seen.delete(node);
814
- return found;
835
+ if (!yes && typeof node.$ref === 'string') yes = check(localRef(rs.root, node.$ref));
836
+ if (!yes) for (const list of [node.allOf, node.prefixItems]) { if (Array.isArray(list) && list.some(check)) { yes = true; break; } }
837
+ if (!yes) for (const one of [node.items, node.additionalProperties]) { if (check(one)) { yes = true; break; } }
838
+ if (!yes) for (const map of [node.patternProperties, node.dependentSchemas]) { if (map && typeof map === 'object' && Object.keys(map).some((k) => check(map[k]))) { yes = true; break; } }
839
+ stack.delete(node);
840
+ if (yes) { rs.memo.set(node, 1); return 1; }
841
+ if (!open) { rs.memo.set(node, 0); return 0; }
842
+ return 2;
843
+ }
844
+ // Which keys `additionalProperties` covers depends on the patterns of
845
+ // `patternProperties`, which this pass does not test (a RegExp built on
846
+ // every call would cost more than the pass): a schema whose defaults depend
847
+ // on one is left to the closure pass.
848
+ function defaultsNeedPatterns(node, rs) {
849
+ const pp = node.patternProperties;
850
+ if (!pp || typeof pp !== 'object' || Object.keys(pp).length === 0) return false;
851
+ for (const k of Object.keys(pp)) if (hasDefaultsInside(pp[k], rs)) return true;
852
+ return hasDefaultsInside(node.additionalProperties, rs);
853
+ }
854
+ // The defaults of `schema` filled into the value of `expr`, when it holds an
855
+ // object (an array included), as the closure pass fills them.
856
+ function emitDefaultsAt(schema, expr, lines, st) {
857
+ const n = '_d' + st.n++;
858
+ lines.push(`{const ${n}=${expr};if(typeof ${n}==='object'&&${n}!==null){`);
859
+ emitDefaults(schema, n, lines, st);
860
+ lines.push('}}');
861
+ }
862
+ // A pass that would expand to more than this many lines (references written
863
+ // out again at every use) is left to the closure pass.
864
+ const DEFAULTS_MAX_LINES = 4000;
865
+ // How many schema objects emitDefaults would write out, references expanded
866
+ // at every use, or Infinity where it would decline (a cycle, or defaults
867
+ // under patternProperties). Each writes at least one line, so a count over
868
+ // DEFAULTS_MAX_LINES declines where emitDefaults would, before writing
869
+ // anything: SARIF's schema wrote 4000 lines in 2.7 ms and threw them away.
870
+ function defaultsVisits(node, st, stack) {
871
+ const known = st.visits.get(node);
872
+ if (known !== undefined) return known;
873
+ if (stack.has(node)) return Infinity;
874
+ stack.add(node);
875
+ let n = 1;
876
+ const add = (x) => { if (n <= DEFAULTS_MAX_LINES && hasDefaultsInside(x, st.reach)) n += defaultsVisits(x, st, stack); };
877
+ if (defaultsNeedPatterns(node, st.reach)) n = Infinity;
878
+ for (const prop of Object.values(node.properties || {})) if (prop && typeof prop === 'object') add(prop);
879
+ if (Array.isArray(node.prefixItems)) node.prefixItems.forEach(add);
880
+ add(node.items);
881
+ add(node.additionalProperties);
882
+ if (node.dependentSchemas && typeof node.dependentSchemas === 'object') Object.values(node.dependentSchemas).forEach(add);
883
+ if (typeof node.$ref === 'string') add(localRef(st.root, node.$ref));
884
+ if (Array.isArray(node.allOf)) node.allOf.forEach(add);
885
+ stack.delete(node);
886
+ if (n > DEFAULTS_MAX_LINES) n = Infinity;
887
+ st.visits.set(node, n);
888
+ return n;
815
889
  }
816
890
  function emitDefaults(node, ov, lines, st) {
817
- if (st.seen.has(node)) { st.cycle = true; return; }
891
+ if (st.seen.has(node) || lines.length > DEFAULTS_MAX_LINES) { st.cycle = true; return; }
818
892
  st.seen.add(node);
819
893
  // A default is written only where the key is not an own property, and the
820
894
  // own-property test used to be Object.hasOwn for every default on every
@@ -846,7 +920,7 @@ function emitDefaults(node, ov, lines, st) {
846
920
  // closure pass walks it. Object.prototype's own properties are functions
847
921
  // apart from the __proto__ accessor, so on a plain object a read that
848
922
  // gives an object is an own property for every other key.
849
- if (prop.properties && hasDefaultsInside(prop, new Set())) {
923
+ if (hasDefaultsInside(prop, st.reach)) {
850
924
  const n = '_d' + st.n++;
851
925
  const own = inherited ? `Object.hasOwn(${ov},${k})` : `(${pk}||Object.hasOwn(${ov},${k}))`;
852
926
  lines.push(`{const ${n}=${ov}[${k}];if(typeof ${n}==='object'&&${n}!==null&&${own}){`);
@@ -854,12 +928,68 @@ function emitDefaults(node, ov, lines, st) {
854
928
  lines.push('}}');
855
929
  }
856
930
  }
931
+ if (defaultsNeedPatterns(node, st.reach)) { st.cycle = true; st.seen.delete(node); return; }
932
+ // Elements of an array: `prefixItems` by position, `items` after them.
933
+ const has = (x) => hasDefaultsInside(x, st.reach);
934
+ const tuple = Array.isArray(node.prefixItems) ? node.prefixItems : [];
935
+ const tupleHas = tuple.map(has);
936
+ const itemsHas = has(node.items);
937
+ if (tupleHas.some(Boolean) || itemsHas) {
938
+ lines.push(`if(Array.isArray(${ov})){`);
939
+ for (let i = 0; i < tuple.length; i++) {
940
+ if (!tupleHas[i]) continue;
941
+ lines.push(`if(${ov}.length>${i})`);
942
+ emitDefaultsAt(tuple[i], `${ov}[${i}]`, lines, st);
943
+ }
944
+ if (itemsHas) {
945
+ const i = '_i' + st.n++;
946
+ lines.push(`for(let ${i}=${tuple.length};${i}<${ov}.length;${i}++)`);
947
+ emitDefaultsAt(node.items, `${ov}[${i}]`, lines, st);
948
+ }
949
+ lines.push('}');
950
+ }
951
+ // Keys of an object that `properties` does not name (no patterns here,
952
+ // see defaultsNeedPatterns).
953
+ if (has(node.additionalProperties)) {
954
+ const k = '_k' + st.n++;
955
+ const declared = Object.keys(node.properties || {});
956
+ lines.push(`if(!Array.isArray(${ov}))for(const ${k} of Object.keys(${ov})){`);
957
+ if (declared.length) lines.push(`if(${declared.map((d) => `${k}===${JSON.stringify(d)}`).join('||')})continue;`);
958
+ emitDefaultsAt(node.additionalProperties, `${ov}[${k}]`, lines, st);
959
+ lines.push('}');
960
+ }
961
+ // A schema for the same object, for each key present.
962
+ if (node.dependentSchemas && typeof node.dependentSchemas === 'object') {
963
+ for (const key of Object.keys(node.dependentSchemas)) {
964
+ const dep = node.dependentSchemas[key];
965
+ if (!has(dep)) continue;
966
+ lines.push(`if(!Array.isArray(${ov})&&Object.hasOwn(${ov},${JSON.stringify(key)})){`);
967
+ emitDefaults(dep, ov, lines, st);
968
+ lines.push('}');
969
+ }
970
+ }
971
+ // The schemas applied to the same value: a `$ref` into the same document,
972
+ // written out here (one back into what is being written is a cycle, left
973
+ // to the closure pass), then each `allOf` member, in the closure pass's order.
974
+ if (typeof node.$ref === 'string') {
975
+ const target = localRef(st.root, node.$ref);
976
+ if (target && hasDefaultsInside(target, st.reach)) emitDefaults(target, ov, lines, st);
977
+ }
978
+ if (Array.isArray(node.allOf)) {
979
+ for (const sub of node.allOf) if (hasDefaultsInside(sub, st.reach)) emitDefaults(sub, ov, lines, st);
980
+ }
857
981
  st.seen.delete(node);
858
982
  }
859
983
 
860
- // Generate a fast preprocess function via codegen instead of closure arrays
861
- function buildPreprocessCodegen(schema, options) {
862
- if (typeof schema !== 'object' || schema === null || !schema.properties) return null;
984
+ // The source of the preprocess pass, or null when there is nothing to do or
985
+ // the closure passes must take the schema. A module written ahead of time
986
+ // embeds it as applyDefaults (lib/aot-impl.js), so it runs the runtime's code.
987
+ function preprocessSource(schema, options) {
988
+ if (typeof schema !== 'object' || schema === null) return null;
989
+ // Defaults reach through `$ref` and `allOf` from the root too; the other
990
+ // passes start at `properties`.
991
+ const reach = reachState(schema);
992
+ if (!schema.properties && !(options.useDefaults !== false && hasDefaultsInside(schema, reach))) return null;
863
993
  const lines = [];
864
994
 
865
995
  // removeAdditional: strip unknown keys at every level the schema describes,
@@ -878,24 +1008,33 @@ function buildPreprocessCodegen(schema, options) {
878
1008
  // used to stop at the top-level properties. A schema object that contains
879
1009
  // itself declines here, and the closure passes, which carry a guard for it,
880
1010
  // take the schema.
881
- const st = { n: 0, seen: new Set(), cycle: false, arrayMode: options.coerceTypes === 'array', memo: new Map() };
1011
+ const st = { n: 0, seen: new Set(), cycle: false, arrayMode: options.coerceTypes === 'array', memo: new Map(), root: schema, reach, visits: new Map() };
882
1012
  if (options.coerceTypes) emitCoercions(schema, 'd', lines, st, 0);
883
1013
  // hasDefaultsInside answers without emitting; most schemas have no default,
884
1014
  // and the emitting walk allocates for every property it visits.
885
- if (options.useDefaults !== false && hasDefaultsInside(schema, new Set())) emitDefaults(schema, 'd', lines, st);
1015
+ if (options.useDefaults !== false && hasDefaultsInside(schema, reach)) {
1016
+ if (defaultsVisits(schema, st, new Set()) > DEFAULTS_MAX_LINES) return null;
1017
+ emitDefaults(schema, 'd', lines, st);
1018
+ }
886
1019
  if (st.cycle) return null;
887
1020
 
888
1021
  if (lines.length === 0) return null;
889
1022
  // Data may legitimately be null or a non-object (e.g. a `['object','null']`
890
1023
  // schema), so the per-property mutations must not run on it.
891
1024
  lines.unshift(`if(d===null||typeof d!=='object')return`);
1025
+ return lines.join('\n');
1026
+ }
1027
+
1028
+ // Generate a fast preprocess function via codegen instead of closure arrays
1029
+ function buildPreprocessCodegen(schema, options) {
1030
+ const src = preprocessSource(schema, options);
1031
+ if (src === null) return null;
892
1032
  // The pass depends on property names, types, defaults and which objects
893
1033
  // are closed, not on the constraints the verdict checks, so routes that
894
1034
  // take the same shape with different limits (a page/limit querystring, an
895
1035
  // id param) emit the same source. Compiling it is most of what building the
896
1036
  // pass costs, so the function is kept by its source and compiled once. It
897
1037
  // holds no state: it rewrites the object it is given and nothing else.
898
- const src = lines.join('\n');
899
1038
  let fn = _preprocessBySource.get(src);
900
1039
  if (fn === undefined) {
901
1040
  try {
@@ -1020,6 +1159,7 @@ module.exports = {
1020
1159
  compileVerdict: installCodegenPaths.compileVerdict,
1021
1160
  installScanner: installCodegenPaths.installScanner,
1022
1161
  buildPreprocess: buildPreprocessCodegen,
1162
+ preprocessSource,
1023
1163
  buildParse,
1024
1164
  aot: () => require('./aot.js'),
1025
1165
  };
package/lib/defaults.js CHANGED
@@ -2,19 +2,32 @@
2
2
 
3
3
  // The closure pass that fills `default` values, shared by the validator core
4
4
  // and by the wrapper around a compiled module, so both fill defaults the same
5
- // way. The generated pass in index.js is held to this one by
5
+ // way. The generated pass in lib/codegen-engine.js is held to this one by
6
6
  // tests/test_nested_defaults_engines.js.
7
+ //
8
+ // A default is filled in every schema applied to the value at that place, as
9
+ // ajv's `useDefaults` fills one: the schemas under `properties`, through a
10
+ // `$ref` into the same document (recursive ones included), each `allOf`
11
+ // member, `items` for every element and `prefixItems` by position,
12
+ // `patternProperties` for the keys they match, `additionalProperties` for the
13
+ // rest, and `dependentSchemas` for the keys present. The pass stopped at
14
+ // `properties`, so a default behind a reference or in an array element was
15
+ // not filled, and a `required` property with one was rejected where ajv
16
+ // accepts it. `anyOf`, `oneOf` and `not` fill nothing, as with ajv;
17
+ // `if`/`then`/`else` fill nothing here, since which branch applies is only
18
+ // known once the value has been checked.
7
19
 
8
- // Extract default values from a schema tree. Returns a function that applies
9
- // defaults to an object in-place (mutates), or null if no defaults exist.
20
+ // Below a value nested this deep the pass stops: data that contains itself,
21
+ // under a recursive schema, would otherwise never end.
22
+ const MAX_DEPTH = 512;
23
+
24
+ // Build the pass for a schema: a function that fills defaults into a value in
25
+ // place (mutates), or null when no default is reachable from the schema.
10
26
  function buildDefaultsApplier(schema) {
11
27
  if (typeof schema !== "object" || schema === null) return null;
12
- const actions = [];
13
- collectDefaults(schema, actions);
14
- if (actions.length === 0) return null;
15
- return (data) => {
16
- for (let i = 0; i < actions.length; i++) actions[i](data);
17
- };
28
+ const path = [];
29
+ const fn = compileNode(schema, schema, new Map(), new Map(), path);
30
+ return fn === null ? null : (data) => { path.length = 0; fn(data, 0, null); };
18
31
  }
19
32
 
20
33
  // Write an own property. Plain assignment of a key named `__proto__` does
@@ -34,55 +47,199 @@ function setOwn(obj, key, val) {
34
47
  }
35
48
  }
36
49
 
37
- function collectDefaults(schema, actions, path) {
38
- if (typeof schema !== "object" || schema === null) return;
39
- const props = schema.properties;
40
- if (!props) return;
41
- for (const [key, prop] of Object.entries(props)) {
42
- if (prop && typeof prop === "object" && prop.default !== undefined) {
43
- const defaultVal = prop.default;
44
- if (!path) {
45
- actions.push((data) => {
46
- if (typeof data === "object" && data !== null && !Object.hasOwn(data, key)) {
47
- setOwn(data,
48
- key,
49
- typeof defaultVal === "object" && defaultVal !== null
50
- ? JSON.parse(JSON.stringify(defaultVal))
51
- : defaultVal);
52
- }
53
- });
54
- } else {
55
- const parentPath = path;
56
- actions.push((data) => {
57
- let target = data;
58
- for (let j = 0; j < parentPath.length; j++) {
59
- if (typeof target !== "object" || target === null) return;
60
- // Own keys only. `target[key]` for an inherited name walks the
61
- // prototype chain: a parent named `__proto__` that the instance
62
- // does not carry resolved to Object.prototype, and the child
63
- // defaults were written onto it, for every object in the realm.
64
- if (!Object.hasOwn(target, parentPath[j])) return;
65
- target = target[parentPath[j]];
66
- }
67
- if (
68
- typeof target === "object" &&
69
- target !== null &&
70
- !Object.hasOwn(target, key)
71
- ) {
72
- setOwn(target,
73
- key,
74
- typeof defaultVal === "object" && defaultVal !== null
75
- ? JSON.parse(JSON.stringify(defaultVal))
76
- : defaultVal);
77
- }
78
- });
79
- }
50
+ // The schema a `$ref` names in the same document: `#`, or a JSON pointer
51
+ // after it. Null for anything else (another document, an anchor), which the
52
+ // pass leaves alone. Normalization renames draft-07 `definitions` to `$defs`
53
+ // and leaves the pointers as written, so either name reads as the other.
54
+ function localRef(root, ref) {
55
+ if (typeof ref !== "string" || ref.charCodeAt(0) !== 35) return null;
56
+ if (ref === "#") return root;
57
+ if (ref.charCodeAt(1) !== 47) return null;
58
+ let t = root;
59
+ for (let seg of ref.slice(2).split("/")) {
60
+ if (seg.indexOf("%") >= 0) { try { seg = decodeURIComponent(seg); } catch { return null; } }
61
+ if (seg.indexOf("~") >= 0) seg = seg.replace(/~1/g, "/").replace(/~0/g, "~");
62
+ if (t === null || typeof t !== "object") return null;
63
+ if (!Object.hasOwn(t, seg)) {
64
+ const alias = seg === "definitions" ? "$defs" : seg === "$defs" ? "definitions" : null;
65
+ if (alias === null || !Object.hasOwn(t, alias)) return null;
66
+ seg = alias;
67
+ }
68
+ t = t[seg];
69
+ }
70
+ return t !== null && typeof t === "object" ? t : null;
71
+ }
72
+
73
+ // Whether a default is reachable from a schema, through the same places the
74
+ // pass goes: 1 yes, 0 no, 2 not known yet because the walk came back to a
75
+ // schema it is still deciding (a recursive reference). Answers are kept per
76
+ // schema object in `memo`, except one that rests on an undecided schema,
77
+ // which is worked out again when asked; the walk used to start over for every
78
+ // schema object it compiled, quadratic in the depth of the schema.
79
+ function reachesDefault(node, root, memo, stack) {
80
+ if (node === null || typeof node !== "object") return 0;
81
+ const known = memo.get(node);
82
+ if (known !== undefined) return known;
83
+ if (stack.has(node)) return 2;
84
+ stack.add(node);
85
+ const props = valuesOf(node.properties);
86
+ let yes = props.some((p) => p && typeof p === "object" && p.default !== undefined);
87
+ let open = false;
88
+ const below = props.concat(localRef(root, node.$ref), listOf(node.allOf), listOf(node.prefixItems), node.items, node.additionalProperties, valuesOf(node.patternProperties), valuesOf(node.dependentSchemas));
89
+ for (let i = 0; !yes && i < below.length; i++) {
90
+ const r = reachesDefault(below[i], root, memo, stack);
91
+ if (r === 1) yes = true;
92
+ else if (r === 2) open = true;
93
+ }
94
+ stack.delete(node);
95
+ if (yes || !open) memo.set(node, yes ? 1 : 0);
96
+ return yes ? 1 : open ? 2 : 0;
97
+ }
98
+ function valuesOf(m) {
99
+ return m && typeof m === "object" && !Array.isArray(m) ? Object.values(m) : [];
100
+ }
101
+ function listOf(a) {
102
+ return Array.isArray(a) ? a : [];
103
+ }
104
+
105
+ // A pattern of `patternProperties` as the validator reads one.
106
+ function keyPattern(src) {
107
+ try { return new RegExp(src, "u"); } catch { try { return new RegExp(src); } catch { return null; } }
108
+ }
109
+
110
+ // A fresh copy of a default for each value it fills. `[]`, the most common
111
+ // object default (2916 of the 4130 SARIF's schema fills into one log), skips
112
+ // the JSON round trip.
113
+ function copyOf(v) {
114
+ return typeof v !== "object" || v === null ? v : Array.isArray(v) && v.length === 0 ? [] : JSON.parse(JSON.stringify(v));
115
+ }
116
+
117
+ // The pass for one schema object, built once: its own defaults, then the
118
+ // schemas below its properties, then its `$ref` and its `allOf` members, each
119
+ // applied to the same value. A schema reached again (a recursive reference)
120
+ // takes the function being built for it, so a cycle in the schema is a loop
121
+ // in the calls, bounded by the depth of the data.
122
+ function compileNode(node, root, memo, reach, path) {
123
+ if (node === null || typeof node !== "object") return null;
124
+ if (memo.has(node)) return memo.get(node);
125
+ if (reachesDefault(node, root, reach, new Set()) !== 1) { memo.set(node, null); return null; }
126
+ let body = null;
127
+ // `path`: the schemas applied to the enclosing values, outermost first.
128
+ // `made`: inside a value the pass itself made from a default, the same as a
129
+ // set, and null elsewhere. There a schema already applied to an enclosing
130
+ // value is not applied again: one that refers back to an enclosing schema,
131
+ // with an object default, would otherwise make a value, fill it, make the
132
+ // next one inside it, and never stop. ajv throws a RangeError on such a
133
+ // schema, and Renovate's took this pass to the heap limit.
134
+ const fn = (data, depth, made) => {
135
+ if (depth > MAX_DEPTH) return;
136
+ if (made !== null) {
137
+ if (made.has(node)) return;
138
+ made.add(node);
139
+ body(data, depth, made);
140
+ made.delete(node);
141
+ return;
80
142
  }
81
- // Recurse into nested object schemas
82
- if (prop && typeof prop === "object" && prop.properties) {
83
- collectDefaults(prop, actions, (path || []).concat(key));
143
+ path.push(node);
144
+ body(data, depth, null);
145
+ path.pop();
146
+ };
147
+ memo.set(node, fn);
148
+
149
+ const own = [];
150
+ const below = [];
151
+ const props = node.properties;
152
+ if (props && typeof props === "object") {
153
+ for (const key of Object.keys(props)) {
154
+ const p = props[key];
155
+ if (!p || typeof p !== "object") continue;
156
+ if (p.default !== undefined) own.push([key, p.default]);
157
+ const child = compileNode(p, root, memo, reach, path);
158
+ if (child !== null) below.push([key, child]);
159
+ }
160
+ }
161
+ // Arrays: every element under `items`, the elements `prefixItems` names by
162
+ // position (then `items` covers only the ones after them).
163
+ const tuple = Array.isArray(node.prefixItems) ? node.prefixItems.map((s) => compileNode(s, root, memo, reach, path)) : [];
164
+ const each = compileNode(node.items, root, memo, reach, path);
165
+ // Objects: keys a pattern matches, and the keys neither `properties` nor a
166
+ // pattern names; and a schema for each key present.
167
+ const patterns = [];
168
+ if (node.patternProperties && typeof node.patternProperties === "object") {
169
+ for (const src of Object.keys(node.patternProperties)) {
170
+ const re = keyPattern(src);
171
+ const t = compileNode(node.patternProperties[src], root, memo, reach, path);
172
+ if (re === null) continue;
173
+ patterns.push([re, t]);
174
+ }
175
+ }
176
+ const extra = compileNode(node.additionalProperties, root, memo, reach, path);
177
+ const declared = props && typeof props === "object" ? props : null;
178
+ const dependent = [];
179
+ if (node.dependentSchemas && typeof node.dependentSchemas === "object") {
180
+ for (const key of Object.keys(node.dependentSchemas)) {
181
+ const t = compileNode(node.dependentSchemas[key], root, memo, reach, path);
182
+ if (t !== null) dependent.push([key, t]);
183
+ }
184
+ }
185
+ const keyed = extra !== null || patterns.some((p) => p[1] !== null);
186
+
187
+ const also = [];
188
+ if (typeof node.$ref === "string") {
189
+ const t = compileNode(localRef(root, node.$ref), root, memo, reach, path);
190
+ if (t !== null) also.push(t);
191
+ }
192
+ if (Array.isArray(node.allOf)) {
193
+ for (const s of node.allOf) {
194
+ const t = compileNode(s, root, memo, reach, path);
195
+ if (t !== null) also.push(t);
84
196
  }
85
197
  }
198
+
199
+ body = (data, depth, made) => {
200
+ let fresh = null;
201
+ if (typeof data === "object" && data !== null) {
202
+ for (let i = 0; i < own.length; i++) {
203
+ const key = own[i][0];
204
+ if (!Object.hasOwn(data, key)) {
205
+ const v = copyOf(own[i][1]);
206
+ setOwn(data, key, v);
207
+ if (made === null && typeof v === "object" && v !== null) (fresh || (fresh = [])).push(key);
208
+ }
209
+ }
210
+ for (let i = 0; i < below.length; i++) {
211
+ // Own keys only. `data[key]` for an inherited name walks the
212
+ // prototype chain: a property named `__proto__` that the value does
213
+ // not carry resolved to Object.prototype, and the defaults below it
214
+ // were written onto it, for every object in the realm.
215
+ const key = below[i][0];
216
+ if (Object.hasOwn(data, key)) below[i][1](data[key], depth + 1, fresh !== null && fresh.includes(key) ? new Set(path) : made);
217
+ }
218
+ if (Array.isArray(data)) {
219
+ for (let i = 0; i < data.length; i++) {
220
+ const t = i < tuple.length ? tuple[i] : each;
221
+ if (t !== null && t !== undefined) t(data[i], depth + 1, made);
222
+ }
223
+ } else if (keyed) {
224
+ for (const key of Object.keys(data)) {
225
+ let matched = false;
226
+ for (let i = 0; i < patterns.length; i++) {
227
+ if (patterns[i][0].test(key)) { matched = true; if (patterns[i][1] !== null) patterns[i][1](data[key], depth + 1, made); }
228
+ }
229
+ if (extra !== null && !matched && !(declared !== null && Object.hasOwn(declared, key))) extra(data[key], depth + 1, made);
230
+ }
231
+ }
232
+ if (!Array.isArray(data)) {
233
+ for (let i = 0; i < dependent.length; i++) if (Object.hasOwn(data, dependent[i][0])) dependent[i][1](data, depth + 1, made);
234
+ }
235
+ }
236
+ for (let i = 0; i < also.length; i++) also[i](data, depth + 1, made);
237
+ };
238
+ return fn;
86
239
  }
87
240
 
88
- module.exports = { buildDefaultsApplier, setOwn };
241
+ // The functions a module written ahead of time embeds as its applyDefaults
242
+ // where the generated pass declines a schema (lib/aot-impl.js).
243
+ const EMBED = ['buildDefaultsApplier', 'setOwn', 'localRef', 'reachesDefault', 'valuesOf', 'listOf', 'keyPattern', 'copyOf', 'compileNode'];
244
+
245
+ module.exports = { buildDefaultsApplier, setOwn, localRef, MAX_DEPTH, EMBED };
@@ -136,6 +136,7 @@ const NATIVE_KEYWORDS = [
136
136
  'not', // 25 not_failed
137
137
  '$ref', // 26 ref_not_found
138
138
  'if', // 27 if_then_else_failed
139
+ '__depth__', // 28 depth_exceeded
139
140
  ]
140
141
 
141
142
  // Keyword and public code for a native ordinal, or null when the ordinal is
@@ -147,6 +148,11 @@ function fromNative (ordinal, format) {
147
148
  // codeFor('format') answers with the first format code, which names a
148
149
  // specific format. Without knowing which format failed, the generic one is
149
150
  // the honest answer.
151
+ // The walker stopped at its depth limit and rejected the document. No
152
+ // public code names that: the APIs that ask the walker route schemas deep or
153
+ // recursive enough to reach it to validate() first (lib/buffer-gate.js), so
154
+ // it is the generic failure.
155
+ if (keyword === '__depth__') return { keyword, code: 'ATA9000' }
150
156
  const code = keyword === 'format'
151
157
  ? (format ? codeFor('format', format) || 'ATA3099' : 'ATA3099')
152
158
  : codeFor(keyword) || 'ATA9001'
@@ -304,4 +304,4 @@ function durationSource (v, isStr) {
304
304
  const inner = `if(!${_formats().DURATION_RE.toString()}.test(${v}))return false`;
305
305
  return isStr ? inner : `if(typeof ${v}==='string'&&${inner.slice(3)}`;
306
306
  }
307
- module.exports = { uriHelperSource, uriHelperCompactSource, uriSource, uriReferenceSource, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
307
+ module.exports = { uriHelperSource, uriHelperCompactSource, uriSource, uriReferenceSource, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };