page: Add IsBranch and deprecate IsNode

IsNode's meaning was murky. Add IsBranch, defined as the set of branch
node kinds (home, section, taxonomy, term), and make IsNode a deprecated
alias for it.

Fixes #11574

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bjørn Erik Pedersen
2026-06-04 20:18:16 +02:00
parent e8fefc8388
commit b89e7fe675
11 changed files with 65 additions and 13 deletions
+1 -1
View File
@@ -488,7 +488,7 @@ func (m *pageMap) forEachResourceInPage(
prefix := paths.AddTrailingSlash(ps.Path())
isBranch := ps.IsNode()
isBranch := ps.IsBranch()
rwr := &doctree.NodeShiftTreeWalker[contentNode]{
Tree: m.treeResources,
+1 -1
View File
@@ -303,7 +303,7 @@ func (h helperContentNode) isBranchNode(n contentNode) bool {
case *pageMetaSource:
return nn.pathInfo.IsBranchBundle()
case *pageState:
return nn.IsNode()
return nn.IsBranch()
case contentNodeSampleProvider:
return h.isBranchNode(nn.sample())
default:
+6 -1
View File
@@ -469,7 +469,12 @@ func (m *pageMeta) Name() string {
}
func (m *pageMeta) IsNode() bool {
return !m.IsPage()
hugo.Deprecate(".Page.IsNode", "Use .Page.IsBranch or not .Page.IsPage instead.", "v0.163.0")
return m.IsBranch()
}
func (m *pageMeta) IsBranch() bool {
return kinds.IsBranch(m.Kind())
}
func (m *pageMeta) IsPage() bool {
+1 -1
View File
@@ -48,7 +48,7 @@ func newPageOutput(
var paginatorProvider page.PaginatorProvider
var pag *pagePaginator
if render && ps.IsNode() {
if render && ps.IsBranch() {
pag = newPagePaginator(ps)
paginatorProvider = pag
} else {
+36
View File
@@ -1994,6 +1994,42 @@ func content(c resource.ContentProvider) string {
return ccs
}
// See issue 11574.
func TestPageIsBranch(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss", "sitemap", "robotsTXT", "404"]
-- content/_index.md --
-- content/sect/_index.md --
-- content/sect/p1.md --
-- layouts/all.html --
{{ .Kind }}|IsBranch={{ .IsBranch }}|IsPage={{ .IsPage }}
`
b := Test(t, files)
b.AssertFileContent("public/index.html", "home|IsBranch=true|IsPage=false")
b.AssertFileContent("public/sect/index.html", "section|IsBranch=true|IsPage=false")
b.AssertFileContent("public/sect/p1/index.html", "page|IsBranch=false|IsPage=true")
}
// See issue 11574.
func TestPageIsNodeDeprecated(t *testing.T) {
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss", "sitemap", "robotsTXT", "404"]
-- content/_index.md --
-- layouts/all.html --
{{ .IsNode }}
`
b := Test(t, files, TestOptInfo())
b.AssertLogContains(".Page.IsNode was deprecated")
}
func BenchmarkIsTranslatedOneLanguage(b *testing.B) {
// Set it reasonably high to get a balance between cached and uncached calls to IsTranslated.
const numPages = 3000
+7 -2
View File
@@ -221,10 +221,15 @@ type PageMetaProvider interface {
// The title used for links.
LinkTitle() string
// IsNode returns whether this is an item of one of the list types in Hugo,
// i.e. not a regular content
// IsNode returns whether this is a branch node (e.g. a section).
//
// Deprecated: Use IsBranch or "not IsPage" instead.
IsNode() bool
// IsBranch returns whether this is a branch node, i.e. a node that
// can have descendants (home, section, taxonomy or term).
IsBranch() bool
// IsPage returns whether this is a regular content
IsPage() bool
@@ -80,6 +80,7 @@ func generateMarshalJSON(c *codegen.Inspector) error {
"github.com/gohugoio/hugo/resources/page",
// Exclusion regexps. Matches method names.
`\bPage\b`,
`\bIsNode\b`,
)
fmt.Fprintf(f, `%s
+3 -6
View File
@@ -26,7 +26,6 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
lastmod := p.Lastmod()
publishDate := p.PublishDate()
expiryDate := p.ExpiryDate()
aliases := p.Aliases()
bundleType := p.BundleType()
description := p.Description()
draft := p.Draft()
@@ -36,10 +35,10 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
layout := p.Layout()
linkTitle := p.LinkTitle()
isNode := p.IsNode()
isBranch := p.IsBranch()
isPage := p.IsPage()
path := p.Path()
slug := p.Slug()
lang := p.Lang()
isSection := p.IsSection()
section := p.Section()
sitemap := p.Sitemap()
@@ -51,7 +50,6 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
Lastmod time.Time
PublishDate time.Time
ExpiryDate time.Time
Aliases []string
BundleType string
Description string
Draft bool
@@ -61,10 +59,10 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
Layout string
LinkTitle string
IsNode bool
IsBranch bool
IsPage bool
Path string
Slug string
Lang string
IsSection bool
Section string
Sitemap config.SitemapConfig
@@ -75,7 +73,6 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
Lastmod: lastmod,
PublishDate: publishDate,
ExpiryDate: expiryDate,
Aliases: aliases,
BundleType: bundleType,
Description: description,
Draft: draft,
@@ -85,10 +82,10 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
Layout: layout,
LinkTitle: linkTitle,
IsNode: isNode,
IsBranch: isBranch,
IsPage: isPage,
Path: path,
Slug: slug,
Lang: lang,
IsSection: isSection,
Section: section,
Sitemap: sitemap,
+4
View File
@@ -227,6 +227,10 @@ func (p *nopPage) IsNode() bool {
return false
}
func (p *nopPage) IsBranch() bool {
return false
}
func (p *nopPage) IsPage() bool {
return false
}
+4
View File
@@ -281,6 +281,10 @@ func (p *testPage) IsNode() bool {
panic("testpage: not implemented")
}
func (p *testPage) IsBranch() bool {
panic("testpage: not implemented")
}
func (p *testPage) IsPage() bool {
panic("testpage: not implemented")
}
+1 -1
View File
@@ -95,7 +95,7 @@ type StringBool struct {
}
type page interface {
IsNode() bool
IsBranch() bool
}
type site interface {