Skip to content

Commit 4208043

Browse files
committed
fix: dynamic recursion should always return dynamic deltas
1 parent 4adf1ed commit 4208043

File tree

2 files changed

+26
-13
lines changed

2 files changed

+26
-13
lines changed

src/compile.js

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
201201
const recursiveLog = []
202202
const getMeta = () => rootMeta.get(root)
203203
const basePathStack = basePathRoot ? [basePathRoot] : []
204+
const recursiveDelta = isPlainObject(schema) && (schema.$dynamicAnchor || schema.$recursiveAnchor)
204205
const visit = (errors, history, current, node, schemaPath, trace = {}, { constProp } = {}) => {
205206
// e.g. top-level data and property names, OR already checked by present() in history, OR in keys and not undefined
206207
const isSub = history.length > 0 && history[history.length - 1].prop === current
@@ -264,7 +265,10 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
264265

265266
// evaluated tracing
266267
const stat = initTracing()
267-
const evaluateDelta = (delta) => applyDelta(stat, delta)
268+
const evaluateDelta = (delta) => {
269+
applyDelta(stat, delta)
270+
if (recursiveDelta && node === schema) evaluateDeltaDynamic(delta, true)
271+
}
268272

269273
if (typeof node === 'boolean') {
270274
if (node === true) {
@@ -379,30 +383,36 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
379383

380384
// evaluated: declare dynamic
381385
const needUnevaluated = (rule) =>
382-
opts[optDynamic] && (node[rule] || node[rule] === false || node === schema)
386+
(opts[optDynamic] && (node[rule] || node[rule] === false || node === schema)) ||
387+
(recursiveDelta && node === schema) // FIXME
383388
const local = Object.freeze({
384389
item: needUnevaluated('unevaluatedItems') ? gensym('evaluatedItem') : null,
385390
items: needUnevaluated('unevaluatedItems') ? gensym('evaluatedItems') : null,
386391
props: needUnevaluated('unevaluatedProperties') ? gensym('evaluatedProps') : null,
387392
})
393+
// RECHECK!
388394
const dyn = Object.freeze({
389395
item: local.item || trace.item,
390396
items: local.items || trace.items,
391397
props: local.props || trace.props,
392398
})
399+
// RECHECK!
393400
const canSkipDynamic = () =>
394401
(!dyn.items || stat.items === Infinity) && (!dyn.props || stat.properties.includes(true))
395-
const evaluateDeltaDynamic = (delta) => {
396-
// Skips applying those that have already been proved statically
397-
if (dyn.item && delta.item && stat.items !== Infinity)
402+
const evaluateDeltaDynamic = (delta, noskip = false) => {
403+
// Skips applying those that have already been proven statically (if not enforced for dynamic recursion)
404+
if (dyn.item && delta.item && (noskip || stat.items !== Infinity)) {
398405
fun.write('%s.push(%s)', dyn.item, delta.item)
399-
if (dyn.items && delta.items > stat.items) fun.write('%s.push(%d)', dyn.items, delta.items)
400-
if (dyn.props && (delta.properties || []).includes(true) && !stat.properties.includes(true)) {
401-
fun.write('%s[0].push(true)', dyn.props)
406+
}
407+
if (dyn.items && delta.items && (noskip || delta.items > stat.items)) {
408+
fun.write('%s.push(%d)', dyn.items, delta.items)
409+
}
410+
if (dyn.props && (delta.properties || []).includes(true)) {
411+
if (noskip || !stat.properties.includes(true)) fun.write('%s[0].push(true)', dyn.props)
402412
} else if (dyn.props) {
403413
const inStat = (properties, patterns) => inProperties(stat, { properties, patterns })
404-
const properties = (delta.properties || []).filter((x) => !inStat([x], []))
405-
const patterns = (delta.patterns || []).filter((x) => !inStat([], [x]))
414+
const properties = (delta.properties || []).filter((x) => noskip || !inStat([x], []))
415+
const patterns = (delta.patterns || []).filter((x) => noskip || !inStat([], [x]))
406416
if (properties.length > 0) fun.write('%s[0].push(...%j)', dyn.props, properties)
407417
if (patterns.length > 0) fun.write('%s[1].push(...%j)', dyn.props, patterns)
408418
for (const sym of delta.propertiesVars || []) fun.write('%s[0].push(%s)', dyn.props, sym)
@@ -1360,8 +1370,11 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
13601370
if (refsNeedFullValidation.has(funname)) throw new Error('Unexpected: unvalidated cyclic ref')
13611371

13621372
// evaluated: return dynamic for refs
1363-
if (opts[optDynamic] && (isDynamic(stat).items || isDynamic(stat).properties)) {
1364-
if (!local) throw new Error('Failed to trace dynamic properties') // Unreachable
1373+
if (
1374+
(opts[optDynamic] && (isDynamic(stat).items || isDynamic(stat).properties)) ||
1375+
recursiveDelta
1376+
) {
1377+
if (!local || !local.props) throw new Error('Failed to trace dynamic properties') // Unreachable
13651378
fun.write('validate.evaluatedDynamic = [%s, %s, %s]', local.item, local.items, local.props)
13661379
}
13671380

0 commit comments

Comments
 (0)