Skip to content

Commit 7c58a37

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 gohugoio#14333
1 parent f42c422 commit 7c58a37

File tree

2 files changed

+140
-2
lines changed

2 files changed

+140
-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: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,20 @@ 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+
if len(pn.Cmds) == 0 || pn.Cmds[0] == nil {
238+
return false
239+
}
240+
return c.isWithPartial(pn.Cmds[0].Args)
241+
}
242+
243+
if id1, ok := first.(*parse.IdentifierNode); ok && (id1.Ident == "partial" || id1.Ident == "partialCached") {
235244
return true
236245
}
237246

238-
if chain, ok := args[0].(*parse.ChainNode); ok {
247+
if chain, ok := first.(*parse.ChainNode); ok {
239248
if id2, ok := chain.Node.(*parse.IdentifierNode); !ok || (id2.Ident != "partials") {
240249
return false
241250
}
@@ -266,12 +275,52 @@ const PartialDecoratorPrefix = "_internal/decorator_"
266275

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

278+
// hasBreakOrContinueOutsideRange returns true if the given list node contains a break or continue statement without being nested in a range.
279+
func (c *templateTransformContext) hasBreakOrContinueOutsideRange(n *parse.ListNode) bool {
280+
if n == nil {
281+
return false
282+
}
283+
for _, node := range n.Nodes {
284+
switch x := node.(type) {
285+
case *parse.ListNode:
286+
if c.hasBreakOrContinueOutsideRange(x) {
287+
return true
288+
}
289+
case *parse.RangeNode:
290+
// skip
291+
case *parse.IfNode:
292+
if c.hasBreakOrContinueOutsideRange(x.List) {
293+
return true
294+
}
295+
if c.hasBreakOrContinueOutsideRange(x.ElseList) {
296+
return true
297+
}
298+
case *parse.WithNode:
299+
if c.hasBreakOrContinueOutsideRange(x.List) {
300+
return true
301+
}
302+
if c.hasBreakOrContinueOutsideRange(x.ElseList) {
303+
return true
304+
}
305+
case *parse.BreakNode, *parse.ContinueNode:
306+
return true
307+
308+
}
309+
}
310+
return false
311+
}
312+
269313
func (c *templateTransformContext) handleWithPartial(withNode *parse.WithNode) {
270314
withNodeInnerString := withNode.List.String()
271315
if templatesInnerRe.MatchString(withNodeInnerString) {
272316
c.err = fmt.Errorf("inner cannot be used inside a with block that wraps a partial decorator")
273317
return
274318
}
319+
320+
// See #14333. That is a very odd construct, but we need to guard against it.
321+
if c.hasBreakOrContinueOutsideRange(withNode.List) {
322+
return
323+
}
275324
innerHash := hashing.XxHashFromStringHexEncoded(c.t.Name() + withNodeInnerString)
276325
internalPartialName := fmt.Sprintf("_partials/%s%s", PartialDecoratorPrefix, innerHash)
277326

@@ -321,6 +370,9 @@ func (c *templateTransformContext) handleWithPartial(withNode *parse.WithNode) {
321370
sn2 := setContext.(*parse.PipeNode).Cmds[0].Args[1].(*parse.PipeNode).Cmds[0].Args[0].(*parse.StringNode)
322371
sn2.Text = innerHash
323372
sn2.Quoted = fmt.Sprintf("%q", sn2.Text)
373+
if pn, ok := withNode.Pipe.Cmds[0].Args[0].(*parse.PipeNode); ok {
374+
withNode.Pipe.Cmds[0].Args = pn.Cmds[0].Args
375+
}
324376
withNode.Pipe.Cmds = append(orNode.Pipe.Cmds, withNode.Pipe.Cmds...)
325377

326378
withNode.List = newInner

0 commit comments

Comments
 (0)