From 8b00030b344d21635e5634698700f1d13145fead Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 8 Apr 2026 13:30:14 +0200 Subject: [PATCH] Fix panic when passthrough elements are used in headings Fixes #14677 Co-Authored-By: xingzihai <1315258019@qq.com> Co-Authored-By: Claude Opus 4.6 (1M context) --- markup/goldmark/convert.go | 6 +++++ markup/goldmark/toc.go | 29 ++++++++++++++++++++- markup/goldmark/toc_integration_test.go | 34 +++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go index f4519dff4..8ed16b0c8 100644 --- a/markup/goldmark/convert.go +++ b/markup/goldmark/convert.go @@ -122,6 +122,12 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown { ) } + if cfg.Extensions.Passthrough.Enable { + tocRendererOptions = append(tocRendererOptions, + renderer.WithNodeRenderers(util.Prioritized(&tocPassthroughRenderer{}, 90)), + ) + } + var ( extensions = []goldmark.Extender{ hugocontext.New(pcfg.Logger), diff --git a/markup/goldmark/toc.go b/markup/goldmark/toc.go index 3fb53f875..dd653c9e2 100644 --- a/markup/goldmark/toc.go +++ b/markup/goldmark/toc.go @@ -24,6 +24,7 @@ import ( emojiAst "github.com/yuin/goldmark-emoji/ast" "github.com/gohugoio/hugo-goldmark-extensions/extras" + "github.com/gohugoio/hugo-goldmark-extensions/passthrough" "github.com/gohugoio/hugo/markup/tableofcontents" "github.com/yuin/goldmark" @@ -116,7 +117,9 @@ func (t *tocTransformer) Transform(n *ast.Document, reader text.Reader, pc parse ast.KindAutoLink, ast.KindRawHTML, ast.KindText, - ast.KindString: + ast.KindString, + passthrough.KindPassthroughInline, + passthrough.KindPassthroughBlock: err := t.r.Render(&headingText, reader.Source(), n) if err != nil { return s, err @@ -171,6 +174,30 @@ func newTOCSanitizerPolicy() *bluemonday.Policy { return p } +// tocPassthroughRenderer renders passthrough nodes as raw text for the TOC. +type tocPassthroughRenderer struct{} + +func (r *tocPassthroughRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + reg.Register(passthrough.KindPassthroughInline, r.render) + reg.Register(passthrough.KindPassthroughBlock, r.render) +} + +func (r *tocPassthroughRenderer) render(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + switch nn := node.(type) { + case *passthrough.PassthroughInline: + w.Write(nn.Segment.Value(src)) + case *passthrough.PassthroughBlock: + for i := range nn.Lines().Len() { + line := nn.Lines().At(i) + w.Write(line.Value(src)) + } + } + return ast.WalkSkipChildren, nil +} + var whiteSpaceRe = regexp.MustCompile(`\s+`) // sanitizeTOCHeadingTitle sanitizes s for use as a TOC heading title. diff --git a/markup/goldmark/toc_integration_test.go b/markup/goldmark/toc_integration_test.go index 715828253..105e1a744 100644 --- a/markup/goldmark/toc_integration_test.go +++ b/markup/goldmark/toc_integration_test.go @@ -364,3 +364,37 @@ title: home `
  • 1^st^
  • `, ) } + +// Issue 14677 +func TestTableOfContentsWithPassthrough(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableKinds = ['page','rss','section','sitemap','taxonomy','term'] +[markup.goldmark.extensions.passthrough] + enable = true +[markup.goldmark.extensions.passthrough.delimiters] + inline = [['$', '$']] +-- content/_index.md -- +--- +title: home +--- +## Heading with **$a$** + +## Heading with $b$ + +## Heading with *$x + y$* + +-- layouts/home.html -- +{{ .TableOfContents }} +` + + b := hugolib.Test(t, files) + + b.AssertFileContent("public/index.html", + `
  • Heading with $a$
  • `, + `
  • Heading with $b$
  • `, + `
  • Heading with $x + y$
  • `, + ) +} -- 2.39.5