Skip to content

Commit df31713

Browse files
committed
Fix partial decorator detection in partial with blocks with outer range break or continue
E.g.: ```handlebars {{- $items := slice "a" "b" "c" }} {{- range $items }} {{- with partial "b" . -}} {{break}} {{- else }} else: {{ . -}} {{- end }} {{- end }} ``` Fixes #14333
1 parent f42c422 commit df31713

File tree

2 files changed

+134
-2
lines changed

2 files changed

+134
-2
lines changed

‎tpl/templates/decorator_integration_test.go‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,3 +367,89 @@ This construct creates a loop: {{PLACEHOLDER . }}
367367
b.Assert(err.Error(), qt.Contains, "inner cannot be used inside a with block that wraps a partial decorator")
368368
}
369369
}
370+
371+
func TestPartialDecoratorInParens(t *testing.T) {
372+
t.Parallel()
373+
374+
files := `
375+
-- hugo.toml --
376+
-- layouts/home.html --
377+
Home.
378+
{{ define "_partials/b.html" }}
379+
<b>{{ inner . }}</b>
380+
{{ end }}
381+
{{ with (partial "b.html" "Important!") }}Notice: {{ . }}{{ end }}
382+
`
383+
384+
b := hugolib.Test(t, files)
385+
386+
b.AssertFileContent("public/index.html", "<b>Notice: Important!</b>")
387+
}
388+
389+
func TestPartialDecoratorBreakInWith(t *testing.T) {
390+
t.Parallel()
391+
392+
filesTemplate := `
393+
-- hugo.toml --
394+
-- layouts/home.html --
395+
Home.
396+
{{ define "_partials/b.html" }}
397+
<b>{{ inner . }}</b>
398+
{{ end }}
399+
{{ with (partial "b.html" "Important!") }}
400+
{{ range seq 1 5 }}
401+
{{ if eq . 3 }}
402+
PLACEHOLDER
403+
{{ end }}
404+
Notice: {{ . }}
405+
{{ end }}
406+
{{ end }}
407+
`
408+
409+
for _, placeholder := range []string{"{{ break }}", "{{ continue }}"} {
410+
files := strings.ReplaceAll(filesTemplate, "PLACEHOLDER", placeholder)
411+
b := hugolib.Test(t, files)
412+
b.AssertFileContent("public/index.html", "Notice: 1", "Notice: 2", "! Notice: 3")
413+
if strings.Contains(placeholder, "continue") {
414+
b.AssertFileContent("public/index.html", "Notice: 4", "Notice: 5")
415+
} else {
416+
b.AssertFileContent("public/index.html", "! Notice: 4", "! Notice: 5")
417+
}
418+
}
419+
}
420+
421+
func TestPartialWithBreakOutsideRange14333(t *testing.T) {
422+
t.Parallel()
423+
424+
filesTemplate := `
425+
-- hugo.toml --
426+
-- layouts/home.html --
427+
Home. {{ partial "a" }}:Done.
428+
{{- define "_partials/a" }}
429+
{{- $items := slice "a" "b" "c" }}
430+
{{- range $items }}
431+
{{- with partial "b" . -}}
432+
PLACEHOLDER
433+
{{- else }}
434+
else: {{ . -}}
435+
{{- end }}
436+
{{- end }}
437+
{{- end }}
438+
{{- define "_partials/b" }}
439+
{{ $b := true }}
440+
{{ if ne . "b" }}
441+
{{ $b = false }}
442+
{{ end }}
443+
{{ return $b }}
444+
{{ end }}
445+
`
446+
for _, placeholder := range []string{"{{ break }}", "{{ continue }}", "{{- break }}"} {
447+
files := strings.ReplaceAll(filesTemplate, "PLACEHOLDER", placeholder)
448+
b := hugolib.Test(t, files)
449+
if strings.Contains(placeholder, "continue") {
450+
b.AssertFileContent("public/index.html", "else: c:Done.")
451+
} else {
452+
b.AssertFileContent("public/index.html", "else: a:Done.")
453+
}
454+
}
455+
}

‎tpl/tplimpl/templatetransform.go‎

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,17 @@ func (c *templateTransformContext) isWithPartial(args []parse.Node) bool {
231231
return false
232232
}
233233

234-
if id1, ok := args[0].(*parse.IdentifierNode); ok && (id1.Ident == "partial" || id1.Ident == "partialCached") {
234+
first := args[0]
235+
236+
if pn, ok := first.(*parse.PipeNode); ok {
237+
return c.isWithPartial(pn.Cmds[0].Args)
238+
}
239+
240+
if id1, ok := first.(*parse.IdentifierNode); ok && (id1.Ident == "partial" || id1.Ident == "partialCached") {
235241
return true
236242
}
237243

238-
if chain, ok := args[0].(*parse.ChainNode); ok {
244+
if chain, ok := first.(*parse.ChainNode); ok {
239245
if id2, ok := chain.Node.(*parse.IdentifierNode); !ok || (id2.Ident != "partials") {
240246
return false
241247
}
@@ -266,12 +272,49 @@ const PartialDecoratorPrefix = "_internal/decorator_"
266272

267273
var templatesInnerRe = regexp.MustCompile(`{{\s*(templates\.Inner\b|inner\b)`)
268274

275+
// hasBreakOrContinueNotInRange returns true if the given list node contains a break or continue statement without being nested in a range.
276+
func (c *templateTransformContext) hasBreakOrContinueNotInRange(n *parse.ListNode) bool {
277+
for _, node := range n.Nodes {
278+
switch x := node.(type) {
279+
case *parse.ListNode:
280+
if c.hasBreakOrContinueNotInRange(x) {
281+
return true
282+
}
283+
case *parse.RangeNode:
284+
// skip
285+
case *parse.IfNode:
286+
if c.hasBreakOrContinueNotInRange(x.List) {
287+
return true
288+
}
289+
if c.hasBreakOrContinueNotInRange(x.ElseList) {
290+
return true
291+
}
292+
case *parse.WithNode:
293+
if c.hasBreakOrContinueNotInRange(x.List) {
294+
return true
295+
}
296+
if c.hasBreakOrContinueNotInRange(x.ElseList) {
297+
return true
298+
}
299+
case *parse.BreakNode, *parse.ContinueNode:
300+
return true
301+
302+
}
303+
}
304+
return false
305+
}
306+
269307
func (c *templateTransformContext) handleWithPartial(withNode *parse.WithNode) {
270308
withNodeInnerString := withNode.List.String()
271309
if templatesInnerRe.MatchString(withNodeInnerString) {
272310
c.err = fmt.Errorf("inner cannot be used inside a with block that wraps a partial decorator")
273311
return
274312
}
313+
314+
// See #14333. That is a very odd construct, but we need to guard against it.
315+
if c.hasBreakOrContinueNotInRange(withNode.List) {
316+
return
317+
}
275318
innerHash := hashing.XxHashFromStringHexEncoded(c.t.Name() + withNodeInnerString)
276319
internalPartialName := fmt.Sprintf("_partials/%s%s", PartialDecoratorPrefix, innerHash)
277320

@@ -321,6 +364,9 @@ func (c *templateTransformContext) handleWithPartial(withNode *parse.WithNode) {
321364
sn2 := setContext.(*parse.PipeNode).Cmds[0].Args[1].(*parse.PipeNode).Cmds[0].Args[0].(*parse.StringNode)
322365
sn2.Text = innerHash
323366
sn2.Quoted = fmt.Sprintf("%q", sn2.Text)
367+
if pn, ok := withNode.Pipe.Cmds[0].Args[0].(*parse.PipeNode); ok {
368+
withNode.Pipe.Cmds[0].Args = pn.Cmds[0].Args
369+
}
324370
withNode.Pipe.Cmds = append(orNode.Pipe.Cmds, withNode.Pipe.Cmds...)
325371

326372
withNode.List = newInner

0 commit comments

Comments
 (0)