Skip to content

Commit 98495c2

Browse files
committed
fix: dynamic recursion should always return dynamic deltas
1 parent e3224b1 commit 98495c2

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
@@ -191,6 +191,7 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
191191
const recursiveLog = []
192192
const getMeta = () => rootMeta.get(root)
193193
const basePathStack = basePathRoot ? [basePathRoot] : []
194+
const recursiveDelta = isPlainObject(schema) && (schema.$dynamicAnchor || schema.$recursiveAnchor)
194195
const visit = (errors, history, current, node, schemaPath, trace = {}, { constProp } = {}) => {
195196
// e.g. top-level data and property names, OR already checked by present() in history, OR in keys and not undefined
196197
const isSub = history.length > 0 && history[history.length - 1].prop === current
@@ -254,7 +255,10 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
254255

255256
// evaluated tracing
256257
const stat = initTracing()
257-
const evaluateDelta = (delta) => applyDelta(stat, delta)
258+
const evaluateDelta = (delta) => {
259+
applyDelta(stat, delta)
260+
if (recursiveDelta && node === schema) evaluateDeltaDynamic(delta, true)
261+
}
258262

259263
if (typeof node === 'boolean') {
260264
if (node === true) {
@@ -369,30 +373,36 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
369373

370374
// evaluated: declare dynamic
371375
const needUnevaluated = (rule) =>
372-
opts[optDynamic] && (node[rule] || node[rule] === false || node === schema)
376+
(opts[optDynamic] && (node[rule] || node[rule] === false || node === schema)) ||
377+
(recursiveDelta && node === schema) // FIXME
373378
const local = Object.freeze({
374379
item: needUnevaluated('unevaluatedItems') ? gensym('evaluatedItem') : null,
375380
items: needUnevaluated('unevaluatedItems') ? gensym('evaluatedItems') : null,
376381
props: needUnevaluated('unevaluatedProperties') ? gensym('evaluatedProps') : null,
377382
})
383+
// RECHECK!
378384
const dyn = Object.freeze({
379385
item: local.item || trace.item,
380386
items: local.items || trace.items,
381387
props: local.props || trace.props,
382388
})
389+
// RECHECK!
383390
const canSkipDynamic = () =>
384391
(!dyn.items || stat.items === Infinity) && (!dyn.props || stat.properties.includes(true))
385-
const evaluateDeltaDynamic = (delta) => {
386-
// Skips applying those that have already been proved statically
387-
if (dyn.item && delta.item && stat.items !== Infinity)
392+
const evaluateDeltaDynamic = (delta, noskip = false) => {
393+
// Skips applying those that have already been proven statically (if not enforced for dynamic recursion)
394+
if (dyn.item && delta.item && (noskip || stat.items !== Infinity)) {
388395
fun.write('%s.push(%s)', dyn.item, delta.item)
389-
if (dyn.items && delta.items > stat.items) fun.write('%s.push(%d)', dyn.items, delta.items)
390-
if (dyn.props && (delta.properties || []).includes(true) && !stat.properties.includes(true)) {
391-
fun.write('%s[0].push(true)', dyn.props)
396+
}
397+
if (dyn.items && delta.items && (noskip || delta.items > stat.items)) {
398+
fun.write('%s.push(%d)', dyn.items, delta.items)
399+
}
400+
if (dyn.props && (delta.properties || []).includes(true)) {
401+
if (noskip || !stat.properties.includes(true)) fun.write('%s[0].push(true)', dyn.props)
392402
} else if (dyn.props) {
393403
const inStat = (properties, patterns) => inProperties(stat, { properties, patterns })
394-
const properties = (delta.properties || []).filter((x) => !inStat([x], []))
395-
const patterns = (delta.patterns || []).filter((x) => !inStat([], [x]))
404+
const properties = (delta.properties || []).filter((x) => noskip || !inStat([x], []))
405+
const patterns = (delta.patterns || []).filter((x) => noskip || !inStat([], [x]))
396406
if (properties.length > 0) fun.write('%s[0].push(...%j)', dyn.props, properties)
397407
if (patterns.length > 0) fun.write('%s[1].push(...%j)', dyn.props, patterns)
398408
for (const sym of delta.propertiesVars || []) fun.write('%s[0].push(%s)', dyn.props, sym)
@@ -1351,8 +1361,11 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
13511361
if (refsNeedFullValidation.has(funname)) throw new Error('Unexpected: unvalidated cyclic ref')
13521362

13531363
// evaluated: return dynamic for refs
1354-
if (opts[optDynamic] && (isDynamic(stat).items || isDynamic(stat).properties)) {
1355-
if (!local) throw new Error('Failed to trace dynamic properties') // Unreachable
1364+
if (
1365+
(opts[optDynamic] && (isDynamic(stat).items || isDynamic(stat).properties)) ||
1366+
recursiveDelta
1367+
) {
1368+
if (!local || !local.props) throw new Error('Failed to trace dynamic properties') // Unreachable
13561369
fun.write('validate.evaluatedDynamic = [%s, %s, %s]', local.item, local.items, local.props)
13571370
}
13581371

0 commit comments

Comments
 (0)