Pin the per-head walk, and say where the hold stops

Sharing renders' seen set across heads left the suite green while silently
under-rejecting: the first head's walk marks the only route to the second
head's draw. This PR widens heads from the bound levels to every held name,
so two heads in one template is now the ordinary case; a rejected case whose
violation sits on the later head pins the scoping.

An operand rendering one field twice covers the revisit guard, taking
coverage to 97.8% against main's 97.4%.

The README said the two spellings are rejected alike without saying what the
hold follows, which is not true where the operand reaches its own field
through a reference. It now names the plain {field} tokens the walk follows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
M
2026-08-31 21:44:24 +02:00
committed by lilleman-tw
parent 21e7af6c1f
commit 4b3dd011fc
3 changed files with 23 additions and 9 deletions
+15
View File
@@ -200,6 +200,16 @@ func TestACalcOperandIsHeldAgainstEveryRoute(t *testing.T) {
},
`{q} renders "net"`,
},
// Each head is walked against its own seen set. Sharing one across heads
// would let the first head's walk mark the only route to the second's draw,
// and this violation sits on the later head in sorted order.
"a violation on the second of two held heads": {
map[string]string{
"cat": `{"format":"{calc(a + b, 0)} {w}","a":{"format":"{x}","x":["1","2"]},` +
`"b":{"format":"{y}","y":["3","4"]},"w":{"format":"{..cat.b}"}}`,
},
`{w} renders "b"`,
},
}
for name, c := range rejected {
_, err := New([]string{writeData(t, c.files)})
@@ -242,6 +252,11 @@ func TestACalcOperandIsHeldAgainstEveryRoute(t *testing.T) {
"a literal operand named twice": {
"cat": `{"format":"{calc(n * 2, 0)} {..cat.n}","n":"5"}`,
},
// One node reached twice while walking the operand: the walk must not
// revisit it, and the repeat is not a second route to anything.
"an operand that renders one field twice": {
"cat": `{"format":"{calc(v * 2, 0)}","v":{"format":"{a}{a}","a":["1","2"]}}`,
},
}
for name, files := range accepted {
if _, err := New([]string{writeData(t, files)}); err != nil {