Repository: gohugoio/hugo (language: go)

Issue:
readDir/readFile should return nil when not found
Or to be more specific: `nil` for readFile and a `[]os.FileInfo` for readDir (so it can be ranged over even if empty).

This is in line with `resources.Get` etc. and what all would expect. 


Discussion / hints:
is the code located in @bep here?

https://github.com/gohugoio/hugo/blob/673cde1eb122888509cca32e322662af08187ff1/tpl/os/os.go#L71

and

https://github.com/gohugoio/hugo/blob/673cde1eb122888509cca32e322662af08187ff1/tpl/os/os.go#L102

can I jump in to help?

Yes, that's the code, and please create a PR. Looking at the code, `readFile` returns a string, so an empty string when not found would be the medicine.

It would also be great if you could update the relevant snippets in /docs (in this repo) with a note about this new behaviour.

Thinking about this, could you wait with this until we have merged #9613
sure no problem!
have you been able to address this @rubiagatra ? can I help?
still waiting mentioned PR to merge @dibrinsofor 
Has this been fixed? Can I jump in to help?
> so an empty string when not found should be the medicine

It looks like it is like that, or maybe I am missing something. Can we possibly have some test that fails with current implementation?

```go
func readFile(fs afero.Fs, filename string) (string, error) {
	filename = filepath.Clean(filename)
	if filename == "" || filename == "." || filename == string(_os.PathSeparator) {
		return "", errors.New("invalid filename")
	}

	b, err := afero.ReadFile(fs, filename)
	if err != nil {
		return "", err
	}

	return string(b), nil
}
```

The other method is already fixed in #9613 so I think you can close that one as fixed once #9613 gets merged.
@bep Can I pick this one if not already done?

Fix patch:
diff --git a/codegen/methods.go b/codegen/methods.go
index 9bc80cc3e25..65a7cc2b752 100644
--- a/codegen/methods.go
+++ b/codegen/methods.go
@@ -452,12 +452,16 @@ func collectMethodsRecursive(pkg string, f []*ast.Field) []string {
 		}
 
 		if ident, ok := m.Type.(*ast.Ident); ok && ident.Obj != nil {
-			// Embedded interface
-			methodNames = append(
-				methodNames,
-				collectMethodsRecursive(
-					pkg,
-					ident.Obj.Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType).Methods.List)...)
+			switch tt := ident.Obj.Decl.(*ast.TypeSpec).Type.(type) {
+			case *ast.InterfaceType:
+				// Embedded interface
+				methodNames = append(
+					methodNames,
+					collectMethodsRecursive(
+						pkg,
+						tt.Methods.List)...)
+			}
+
 		} else {
 			// Embedded, but in a different file/package. Return the
 			// package.Name and deal with that later.
diff --git a/commands/config.go b/commands/config.go
index 7fda2d40e97..a5d8aab22fe 100644
--- a/commands/config.go
+++ b/commands/config.go
@@ -126,6 +126,7 @@ type modMount struct {
 	Lang   string `json:"lang,omitempty"`
 }
 
+// MarshalJSON is for internal use only.
 func (m *modMounts) MarshalJSON() ([]byte, error) {
 	var mounts []modMount
 
diff --git a/common/docs.go b/common/docs.go
new file mode 100644
index 00000000000..041a62a0127
--- /dev/null
+++ b/common/docs.go
@@ -0,0 +1,2 @@
+// Package common provides common helper functionality for Hugo.
+package common
diff --git a/common/maps/scratch.go b/common/maps/scratch.go
index d4745d27c3b..e9f412540b2 100644
--- a/common/maps/scratch.go
+++ b/common/maps/scratch.go
@@ -30,6 +30,7 @@ type Scratch struct {
 
 // Scratcher provides a scratching service.
 type Scratcher interface {
+	// Scratch returns a "scratch pad" that can be used to store state.
 	Scratch() *Scratch
 }
 
diff --git a/common/paths/path.go b/common/paths/path.go
index 3a7f3e7908a..11d221bb153 100644
--- a/common/paths/path.go
+++ b/common/paths/path.go
@@ -263,3 +263,14 @@ func (n NamedSlice) String() string {
 	}
 	return fmt.Sprintf("%s%s{%s}", n.Name, FilePathSeparator, strings.Join(n.Slice, ","))
 }
+
+// DirFile holds the result from path.Split.
+type DirFile struct {
+	Dir  string
+	File string
+}
+
+// Used in test.
+func (df DirFile) String() string {
+	return fmt.Sprintf("%s|%s", df.Dir, df.File)
+}
diff --git a/common/text/position.go b/common/text/position.go
index cc1eda3540a..eb9de5624ef 100644
--- a/common/text/position.go
+++ b/common/text/position.go
@@ -24,6 +24,8 @@ import (
 // Positioner represents a thing that knows its position in a text file or stream,
 // typically an error.
 type Positioner interface {
+	// Position returns the current position.
+	// Useful in error logging, e.g. {{ errorf "error in code block: %s" .Position }}.
 	Position() Position
 }
 
diff --git a/config/security/whitelist.go b/config/security/whitelist.go
index 0d2c187c6e5..4aff9e9c479 100644
--- a/config/security/whitelist.go
+++ b/config/security/whitelist.go
@@ -33,6 +33,7 @@ type Whitelist struct {
 	patternsStrings []string
 }
 
+// MarshalJSON is for internal use only.
 func (w Whitelist) MarshalJSON() ([]byte, error) {
 	if w.acceptNone {
 		return json.Marshal(acceptNoneKeyword)
diff --git a/hugofs/fileinfo.go b/hugofs/fileinfo.go
index 1d46a74642c..c339972785a 100644
--- a/hugofs/fileinfo.go
+++ b/hugofs/fileinfo.go
@@ -130,6 +130,7 @@ func (f *FileMeta) JoinStat(name string) (FileMetaInfo, error) {
 
 type FileMetaInfo interface {
 	os.FileInfo
+	// Meta is for internal use.
 	Meta() *FileMeta
 }
 
diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go
index 7e6b6e67040..d8f28286cdf 100644
--- a/hugolib/content_map_page.go
+++ b/hugolib/content_map_page.go
@@ -266,7 +266,7 @@ func (m *pageMap) newResource(fim hugofs.FileMetaInfo, owner *pageState) (resour
 }
 
 func (m *pageMap) createSiteTaxonomies() error {
-	m.s.taxonomies = make(TaxonomyList)
+	m.s.taxonomies = make(page.TaxonomyList)
 	var walkErr error
 	m.taxonomies.Walk(func(s string, v any) bool {
 		n := v.(*contentNode)
@@ -275,7 +275,7 @@ func (m *pageMap) createSiteTaxonomies() error {
 		viewName := t.name
 
 		if t.termKey == "" {
-			m.s.taxonomies[viewName.plural] = make(Taxonomy)
+			m.s.taxonomies[viewName.plural] = make(page.Taxonomy)
 		} else {
 			taxonomy := m.s.taxonomies[viewName.plural]
 			if taxonomy == nil {
@@ -285,7 +285,7 @@ func (m *pageMap) createSiteTaxonomies() error {
 			m.taxonomyEntries.WalkPrefix(s, func(ss string, v any) bool {
 				b2 := v.(*contentNode)
 				info := b2.viewInfo
-				taxonomy.add(info.termKey, page.NewWeightedPage(info.weight, info.ref.p, n.p))
+				taxonomy[info.termKey] = append(taxonomy[info.termKey], page.NewWeightedPage(info.weight, info.ref.p, n.p))
 
 				return false
 			})
diff --git a/hugolib/gitinfo.go b/hugolib/gitinfo.go
index 17717ed529f..d051b10bc51 100644
--- a/hugolib/gitinfo.go
+++ b/hugolib/gitinfo.go
@@ -20,6 +20,7 @@ import (
 	"github.com/bep/gitmap"
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/resources/page"
+	"github.com/gohugoio/hugo/source"
 )
 
 type gitInfo struct {
@@ -27,11 +28,14 @@ type gitInfo struct {
 	repo       *gitmap.GitRepo
 }
 
-func (g *gitInfo) forPage(p page.Page) *gitmap.GitInfo {
+func (g *gitInfo) forPage(p page.Page) source.GitInfo {
 	name := strings.TrimPrefix(filepath.ToSlash(p.File().Filename()), g.contentDir)
 	name = strings.TrimPrefix(name, "/")
-
-	return g.repo.Files[name]
+	gi, found := g.repo.Files[name]
+	if !found {
+		return source.GitInfo{}
+	}
+	return source.NewGitInfo(*gi)
 }
 
 func newGitInfo(cfg config.Provider) (*gitInfo, error) {
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index 6be26d60e99..569c27be5c7 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -41,7 +41,6 @@ import (
 
 	"github.com/gohugoio/hugo/source"
 
-	"github.com/bep/gitmap"
 	"github.com/gohugoio/hugo/config"
 
 	"github.com/gohugoio/hugo/publisher"
@@ -202,13 +201,13 @@ func (h *HugoSites) Data() map[string]any {
 	return h.data
 }
 
-func (h *HugoSites) gitInfoForPage(p page.Page) (*gitmap.GitInfo, error) {
+func (h *HugoSites) gitInfoForPage(p page.Page) (source.GitInfo, error) {
 	if _, err := h.init.gitInfo.Do(); err != nil {
-		return nil, err
+		return source.GitInfo{}, err
 	}
 
 	if h.gitInfo == nil {
-		return nil, nil
+		return source.GitInfo{}, nil
 	}
 
 	return h.gitInfo.forPage(p), nil
diff --git a/hugolib/page.go b/hugolib/page.go
index 5acfbc67720..97f1ed351ba 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -31,8 +31,6 @@ import (
 
 	"github.com/gohugoio/hugo/hugofs/files"
 
-	"github.com/bep/gitmap"
-
 	"github.com/gohugoio/hugo/helpers"
 
 	"github.com/gohugoio/hugo/common/herrors"
@@ -150,7 +148,7 @@ func (p *pageState) GetIdentity() identity.Identity {
 	return identity.NewPathIdentity(files.ComponentFolderContent, filepath.FromSlash(p.Pathc()))
 }
 
-func (p *pageState) GitInfo() *gitmap.GitInfo {
+func (p *pageState) GitInfo() source.GitInfo {
 	return p.gitInfo
 }
 
diff --git a/hugolib/page__common.go b/hugolib/page__common.go
index 59f0bc77605..0527a0682c5 100644
--- a/hugolib/page__common.go
+++ b/hugolib/page__common.go
@@ -16,7 +16,6 @@ package hugolib
 import (
 	"sync"
 
-	"github.com/bep/gitmap"
 	"github.com/gohugoio/hugo/common/maps"
 	"github.com/gohugoio/hugo/compare"
 	"github.com/gohugoio/hugo/lazy"
@@ -24,6 +23,7 @@ import (
 	"github.com/gohugoio/hugo/output"
 	"github.com/gohugoio/hugo/resources/page"
 	"github.com/gohugoio/hugo/resources/resource"
+	"github.com/gohugoio/hugo/source"
 )
 
 type treeRefProvider interface {
@@ -106,7 +106,7 @@ type pageCommon struct {
 	shortcodeState *shortcodeHandler
 
 	// Set if feature enabled and this is in a Git repo.
-	gitInfo    *gitmap.GitInfo
+	gitInfo    source.GitInfo
 	codeowners []string
 
 	// Positional navigation
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
index daf989f42ba..8d2495ddc17 100644
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -404,7 +404,7 @@ func (pm *pageMeta) setMetadata(parentBucket *pagesMapBucket, p *pageState, fron
 	}
 
 	var gitAuthorDate time.Time
-	if p.gitInfo != nil {
+	if !p.gitInfo.IsZero() {
 		gitAuthorDate = p.gitInfo.AuthorDate
 	}
 
diff --git a/hugolib/site.go b/hugolib/site.go
index 8fb39a1eac8..2ffc3a346f3 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -110,9 +110,9 @@ type Site struct {
 
 	*PageCollections
 
-	taxonomies TaxonomyList
+	taxonomies page.TaxonomyList
 
-	Sections Taxonomy
+	Sections page.Taxonomy
 	Info     *SiteInfo
 
 	language   *langs.Language
@@ -172,7 +172,7 @@ type Site struct {
 	init *siteInit
 }
 
-func (s *Site) Taxonomies() TaxonomyList {
+func (s *Site) Taxonomies() page.TaxonomyList {
 	s.init.taxonomies.Do()
 	return s.taxonomies
 }
@@ -708,7 +708,7 @@ func (s *SiteInfo) Menus() navigation.Menus {
 }
 
 // TODO(bep) type
-func (s *SiteInfo) Taxonomies() any {
+func (s *SiteInfo) Taxonomies() page.TaxonomyList {
 	return s.s.Taxonomies()
 }
 
diff --git a/langs/language.go b/langs/language.go
index d6b30ec1005..42aefda5e07 100644
--- a/langs/language.go
+++ b/langs/language.go
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package langs contains the language related types and function.
 package langs
 
 import (
diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go
index a59da939e8b..7eede071033 100644
--- a/markup/converter/hooks/hooks.go
+++ b/markup/converter/hooks/hooks.go
@@ -26,30 +26,56 @@ import (
 var _ AttributesOptionsSliceProvider = (*attributes.AttributesHolder)(nil)
 
 type AttributesProvider interface {
+	// Attributes passed in from Markdown (e.g. { attrName1=attrValue1 attrName2="attr Value 2" }).
 	Attributes() map[string]any
 }
 
 type LinkContext interface {
+	// The Page being rendered.
 	Page() any
+
+	// The link URL.
 	Destination() string
+
+	// The link title attribute.
 	Title() string
+
+	// The rendered (HTML) text.
 	Text() hstring.RenderedString
+
+	// The plain variant of Text.
 	PlainText() string
 }
 
 type ImageLinkContext interface {
 	LinkContext
+
+	// Returns true if this is a standalone image and the config option
+	// markup.goldmark.parser.wrapStandAloneImageWithinParagraph is disabled.
 	IsBlock() bool
+
+	// Zero-based ordinal for all the images in the current document.
 	Ordinal() int
 }
 
+// CodeblockContext is the context passed to a code block render hook.
 type CodeblockContext interface {
 	AttributesProvider
 	text.Positioner
+
+	// Chroma highlighting processing options. This will only be filled if Type is a known Chroma Lexer.
 	Options() map[string]any
+
+	// The type of code block. This will be the programming language, e.g. bash, when doing code highlighting.
 	Type() string
+
+	// The text between the code fences.
 	Inner() string
+
+	// Zero-based ordinal for all code blocks in the current document.
 	Ordinal() int
+
+	// The owning Page.
 	Page() any
 }
 
diff --git a/markup/highlight/highlight.go b/markup/highlight/highlight.go
index 010c941f74a..b7499770055 100644
--- a/markup/highlight/highlight.go
+++ b/markup/highlight/highlight.go
@@ -157,10 +157,12 @@ type HightlightResult struct {
 	highlighted template.HTML
 }
 
+// Wrapped returns the highlighted code wrapped in a <div>, <pre> and <code> tag.
 func (h HightlightResult) Wrapped() template.HTML {
 	return h.highlighted
 }
 
+// Inner returns the highlighted code without the wrapping <div>, <pre> and <code> tag, suitable for inline use.
 func (h HightlightResult) Inner() template.HTML {
 	return h.highlighted[h.innerLow:h.innerHigh]
 }
diff --git a/markup/markup.go b/markup/markup.go
index 1345867f93d..aefa5086741 100644
--- a/markup/markup.go
+++ b/markup/markup.go
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package markup contains the markup handling (e.g. Markdown).
 package markup
 
 import (
diff --git a/media/mediaType.go b/media/mediaType.go
index e47acb1e34e..cdfb1c6542b 100644
--- a/media/mediaType.go
+++ b/media/mediaType.go
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package media containes Media Type (MIME type) related types and functions.
 package media
 
 import (
diff --git a/navigation/menu.go b/navigation/menu.go
index 5e4996f3964..cb280823cbb 100644
--- a/navigation/menu.go
+++ b/navigation/menu.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2023 The Hugo Authors. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package navigation provides the menu functionality.
 package navigation
 
 import (
diff --git a/navigation/menu_cache.go b/navigation/menu_cache.go
index 6a326643155..4287ed87529 100644
--- a/navigation/menu_cache.go
+++ b/navigation/menu_cache.go
@@ -10,7 +10,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-//
+
 package navigation
 
 import (
diff --git a/output/outputFormat.go b/output/outputFormat.go
index 722079df9a7..0bc08e4905d 100644
--- a/output/outputFormat.go
+++ b/output/outputFormat.go
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package output contains Output Format types and functions.
 package output
 
 import (
@@ -400,6 +401,7 @@ func (f Format) BaseFilename() string {
 }
 
 // MarshalJSON returns the JSON encoding of f.
+// For internal use only.
 func (f Format) MarshalJSON() ([]byte, error) {
 	type Alias Format
 	return json.Marshal(&struct {
diff --git a/resources/docs.go b/resources/docs.go
new file mode 100644
index 00000000000..f992893dab1
--- /dev/null
+++ b/resources/docs.go
@@ -0,0 +1,15 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package resources contains Resource related types.
+package resources
diff --git a/resources/images/exif/exif.go b/resources/images/exif/exif.go
index 487f250d519..8df348b23da 100644
--- a/resources/images/exif/exif.go
+++ b/resources/images/exif/exif.go
@@ -254,8 +254,10 @@ func init() {
 	}
 }
 
+// Tags is a map of EXIF tags.
 type Tags map[string]any
 
+// UnmarshalJSON is for internal use only.
 func (v *Tags) UnmarshalJSON(b []byte) error {
 	vv := make(map[string]any)
 	if err := tcodec.Unmarshal(b, &vv); err != nil {
@@ -267,6 +269,7 @@ func (v *Tags) UnmarshalJSON(b []byte) error {
 	return nil
 }
 
+// MarshalJSON is for internal use only.
 func (v Tags) MarshalJSON() ([]byte, error) {
 	return tcodec.Marshal(v)
 }
diff --git a/resources/images/image_resource.go b/resources/images/image_resource.go
index 4e66b010cbf..8469590063a 100644
--- a/resources/images/image_resource.go
+++ b/resources/images/image_resource.go
@@ -29,6 +29,7 @@ type ImageResource interface {
 type ImageResourceOps interface {
 	// Height returns the height of the Image.
 	Height() int
+
 	// Width returns the width of the Image.
 	Width() int
 
@@ -37,8 +38,17 @@ type ImageResourceOps interface {
 	// Use the anchor option to change the crop box anchor point.
 	//    {{ $image := $image.Crop "600x400" }}
 	Crop(spec string) (ImageResource, error)
+
+	// Fill scales the image to the smallest possible size that will cover the specified dimensions in spec,
+	// crops the resized image to the specified dimensions using the given anchor point.
+	// The spec is space delimited, e.g. `200x300 TopLeft`.
 	Fill(spec string) (ImageResource, error)
+
+	// Fit scales down the image using the given spec.
 	Fit(spec string) (ImageResource, error)
+
+	// Resize resizes the image to the given spec. If one of width or height is 0, the image aspect
+	// ratio is preserved.
 	Resize(spec string) (ImageResource, error)
 
 	// Filter applies one or more filters to an Image.
diff --git a/resources/page/page.go b/resources/page/page.go
index 929f04d9371..eeb2cdb2809 100644
--- a/resources/page/page.go
+++ b/resources/page/page.go
@@ -21,7 +21,6 @@ import (
 	"github.com/gohugoio/hugo/identity"
 	"github.com/gohugoio/hugo/markup/converter"
 
-	"github.com/bep/gitmap"
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/tpl"
 
@@ -61,18 +60,17 @@ type AuthorProvider interface {
 
 // ChildCareProvider provides accessors to child resources.
 type ChildCareProvider interface {
+	// Pages returns a list of pages of all kinds.
 	Pages() Pages
 
 	// RegularPages returns a list of pages of kind 'Page'.
-	// In Hugo 0.57 we changed the Pages method so it returns all page
-	// kinds, even sections. If you want the old behaviour, you can
-	// use RegularPages.
 	RegularPages() Pages
 
 	// RegularPagesRecursive returns all regular pages below the current
 	// section.
 	RegularPagesRecursive() Pages
 
+	// Resources returns a list of all resources.
 	Resources() resource.Resources
 }
 
@@ -103,16 +101,21 @@ type ContentProvider interface {
 	ReadingTime() int
 
 	// Len returns the length of the content.
+	// This is for internal use only.
 	Len() int
 }
 
 // ContentRenderer provides the content rendering methods for some content.
 type ContentRenderer interface {
+	// RenderContent renders the given content.
+	// For internal use only.
 	RenderContent(content []byte, renderTOC bool) (converter.Result, error)
 }
 
 // FileProvider provides the source file.
 type FileProvider interface {
+	// File returns the source file for this Page,
+	// or a zero File if this Page is not backed by a file.
 	File() source.File
 }
 
@@ -131,13 +134,17 @@ type GetPageProvider interface {
 
 // GitInfoProvider provides Git info.
 type GitInfoProvider interface {
-	GitInfo() *gitmap.GitInfo
+	// GitInfo returns the Git info for this object.
+	GitInfo() source.GitInfo
+	// CodeOwners returns the code owners for this object.
 	CodeOwners() []string
 }
 
 // InSectionPositioner provides section navigation.
 type InSectionPositioner interface {
+	// NextInSection returns the next page in the same section.
 	NextInSection() Page
+	// PrevInSection returns the previous page in the same section.
 	PrevInSection() Page
 }
 
@@ -149,6 +156,7 @@ type InternalDependencies interface {
 
 // OutputFormatsProvider provides the OutputFormats of a Page.
 type OutputFormatsProvider interface {
+	// OutputFormats returns the OutputFormats for this Page.
 	OutputFormats() OutputFormats
 }
 
@@ -229,6 +237,7 @@ type PageMetaProvider interface {
 	SectionsPath() string
 
 	// Sitemap returns the sitemap configuration for this page.
+	// This is for internal use only.
 	Sitemap() config.Sitemap
 
 	// Type is a discriminator used to select layouts etc. It is typically set
@@ -242,7 +251,15 @@ type PageMetaProvider interface {
 
 // PageRenderProvider provides a way for a Page to render content.
 type PageRenderProvider interface {
+	// Render renders the given layout with this Page as context.
 	Render(layout ...string) (template.HTML, error)
+	// RenderString renders the first value in args with tPaginatorhe content renderer defined
+	// for this Page.
+	// It takes an optional map as a second argument:
+	//
+	// display (“inline”):
+	// - inline or block. If inline (default), surrounding <p></p> on short snippets will be trimmed.
+	// markup (defaults to the Page’s markup)
 	RenderString(args ...any) (template.HTML, error)
 }
 
@@ -311,7 +328,9 @@ type PageWithoutContent interface {
 
 // Positioner provides next/prev navigation.
 type Positioner interface {
+	// Next points up to the next regular page (sorted by Hugo’s default sort).
 	Next() Page
+	// Prev points down to the previous regular page (sorted by Hugo’s default sort).
 	Prev() Page
 
 	// Deprecated: Use Prev. Will be removed in Hugo 0.57
@@ -323,16 +342,19 @@ type Positioner interface {
 
 // RawContentProvider provides the raw, unprocessed content of the page.
 type RawContentProvider interface {
+	// RawContent returns the raw, unprocessed content of the page excluding any front matter.
 	RawContent() string
 }
 
 // RefProvider provides the methods needed to create reflinks to pages.
 type RefProvider interface {
+	// Ref returns an absolute URl to a page.
 	Ref(argsm map[string]any) (string, error)
 
 	// RefFrom is for internal use only.
 	RefFrom(argsm map[string]any, source any) (string, error)
 
+	// RelRef returns a relative URL to a page.
 	RelRef(argsm map[string]any) (string, error)
 
 	// RefFrom is for internal use only.
@@ -356,12 +378,15 @@ type ShortcodeInfoProvider interface {
 
 // SitesProvider provide accessors to get sites.
 type SitesProvider interface {
+	// Site returns the current site.
 	Site() Site
+	// Sites returns all sites.
 	Sites() Sites
 }
 
 // TableOfContentsProvider provides the table of contents for a Page.
 type TableOfContentsProvider interface {
+	// TableOfContents returns the table of contents for the page rendered as HTML.
 	TableOfContents() template.HTML
 }
 
@@ -382,7 +407,7 @@ type TranslationsProvider interface {
 // TreeProvider provides section tree navigation.
 type TreeProvider interface {
 
-	// IsAncestor returns whether the current page is an ancestor of the given
+	// IsAncestor returns whether the current page is an ancestor of other.
 	// Note that this method is not relevant for taxonomy lists and taxonomy terms pages.
 	IsAncestor(other any) (bool, error)
 
@@ -390,7 +415,7 @@ type TreeProvider interface {
 	// Note that this will return nil for pages that is not regular, home or section pages.
 	CurrentSection() Page
 
-	// IsDescendant returns whether the current page is a descendant of the given
+	// IsDescendant returns whether the current page is a descendant of other.
 	// Note that this method is not relevant for taxonomy lists and taxonomy terms pages.
 	IsDescendant(other any) (bool, error)
 
@@ -398,7 +423,7 @@ type TreeProvider interface {
 	// For the home page, this will return itself.
 	FirstSection() Page
 
-	// InSection returns whether the given page is in the current section.
+	// InSection returns whether other is in the current section.
 	// Note that this will always return false for pages that are
 	// not either regular, home or section pages.
 	InSection(other any) (bool, error)
diff --git a/resources/page/page_marshaljson.autogen.go b/resources/page/page_marshaljson.autogen.go
index 0f73d81aec0..373257878eb 100644
--- a/resources/page/page_marshaljson.autogen.go
+++ b/resources/page/page_marshaljson.autogen.go
@@ -17,7 +17,6 @@ package page
 
 import (
 	"encoding/json"
-	"github.com/bep/gitmap"
 	"github.com/gohugoio/hugo/common/maps"
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/hugofs/files"
@@ -82,6 +81,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
 	language := p.Language()
 	file := p.File()
 	gitInfo := p.GitInfo()
+	codeOwners := p.CodeOwners()
 	outputFormats := p.OutputFormats()
 	alternativeOutputFormats := p.AlternativeOutputFormats()
 	menus := p.Menus()
@@ -89,10 +89,11 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
 	isTranslated := p.IsTranslated()
 	allTranslations := p.AllTranslations()
 	translations := p.Translations()
+	store := p.Store()
 	getIdentity := p.GetIdentity()
 
 	s := struct {
-		Content                  any
+		Content                  interface{}
 		Plain                    string
 		PlainWords               []string
 		Summary                  template.HTML
@@ -110,7 +111,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
 		Name                     string
 		Title                    string
 		Params                   maps.Params
-		Data                     any
+		Data                     interface{}
 		Date                     time.Time
 		Lastmod                  time.Time
 		PublishDate              time.Time
@@ -139,7 +140,8 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
 		Weight                   int
 		Language                 *langs.Language
 		File                     source.File
-		GitInfo                  *gitmap.GitInfo
+		GitInfo                  source.GitInfo
+		CodeOwners               []string
 		OutputFormats            OutputFormats
 		AlternativeOutputFormats OutputFormats
 		Menus                    navigation.PageMenus
@@ -147,6 +149,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
 		IsTranslated             bool
 		AllTranslations          Pages
 		Translations             Pages
+		Store                    *maps.Scratch
 		GetIdentity              identity.Identity
 	}{
 		Content:                  content,
@@ -197,6 +200,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
 		Language:                 language,
 		File:                     file,
 		GitInfo:                  gitInfo,
+		CodeOwners:               codeOwners,
 		OutputFormats:            outputFormats,
 		AlternativeOutputFormats: alternativeOutputFormats,
 		Menus:                    menus,
@@ -204,6 +208,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
 		IsTranslated:             isTranslated,
 		AllTranslations:          allTranslations,
 		Translations:             translations,
+		Store:                    store,
 		GetIdentity:              getIdentity,
 	}
 
diff --git a/resources/page/page_nop.go b/resources/page/page_nop.go
index 15f8c395086..c4af3f554e0 100644
--- a/resources/page/page_nop.go
+++ b/resources/page/page_nop.go
@@ -28,7 +28,6 @@ import (
 
 	"github.com/gohugoio/hugo/hugofs"
 
-	"github.com/bep/gitmap"
 	"github.com/gohugoio/hugo/navigation"
 
 	"github.com/gohugoio/hugo/common/hugo"
@@ -200,8 +199,8 @@ func (p *nopPage) GetTerms(taxonomy string) Pages {
 	return nil
 }
 
-func (p *nopPage) GitInfo() *gitmap.GitInfo {
-	return nil
+func (p *nopPage) GitInfo() source.GitInfo {
+	return source.GitInfo{}
 }
 
 func (p *nopPage) CodeOwners() []string {
diff --git a/resources/page/pagination.go b/resources/page/pagination.go
index 9f4bfcff566..46d9fda82a9 100644
--- a/resources/page/pagination.go
+++ b/resources/page/pagination.go
@@ -27,8 +27,10 @@ import (
 
 // PaginatorProvider provides two ways to create a page paginator.
 type PaginatorProvider interface {
+	// Paginator creates a paginator with the default page set.
 	Paginator(options ...any) (*Pager, error)
-	Paginate(seq any, options ...any) (*Pager, error)
+	// Paginate creates a paginator with the given page set in pages.
+	Paginate(pages any, options ...any) (*Pager, error)
 }
 
 // Pager represents one of the elements in a paginator.
diff --git a/resources/page/site.go b/resources/page/site.go
index f5806280c8f..8daff95aece 100644
--- a/resources/page/site.go
+++ b/resources/page/site.go
@@ -26,8 +26,7 @@ import (
 	"github.com/gohugoio/hugo/navigation"
 )
 
-// Site represents a site in the build. This is currently a very narrow interface,
-// but the actual implementation will be richer, see hugolib.SiteInfo.
+// Site represents a site. There can be multople sites in a multilingual setup.
 type Site interface {
 	// Returns the Language configured for this Site.
 	Language() *langs.Language
@@ -63,7 +62,7 @@ type Site interface {
 	BaseURL() template.URL
 
 	// Retuns a taxonomy map.
-	Taxonomies() any
+	Taxonomies() TaxonomyList
 
 	// Returns the last modification date of the content.
 	LastChange() time.Time
@@ -142,7 +141,7 @@ func (t testSite) Menus() navigation.Menus {
 	return nil
 }
 
-func (t testSite) Taxonomies() any {
+func (t testSite) Taxonomies() TaxonomyList {
 	return nil
 }
 
diff --git a/hugolib/taxonomy.go b/resources/page/taxonomy.go
similarity index 87%
rename from hugolib/taxonomy.go
rename to resources/page/taxonomy.go
index 6b3c2b961eb..f50152f901a 100644
--- a/hugolib/taxonomy.go
+++ b/resources/page/taxonomy.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2023 The Hugo Authors. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,7 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package hugolib
+package page
 
 import (
 	"fmt"
@@ -19,8 +19,6 @@ import (
 
 	"github.com/gohugoio/hugo/compare"
 	"github.com/gohugoio/hugo/langs"
-
-	"github.com/gohugoio/hugo/resources/page"
 )
 
 // The TaxonomyList is a list of all taxonomies and their values
@@ -33,9 +31,10 @@ func (tl TaxonomyList) String() string {
 
 // A Taxonomy is a map of keywords to a list of pages.
 // For example
-//    TagTaxonomy['technology'] = page.WeightedPages
-//    TagTaxonomy['go']  =  page.WeightedPages
-type Taxonomy map[string]page.WeightedPages
+//
+//	TagTaxonomy['technology'] = WeightedPages
+//	TagTaxonomy['go']  =  WeightedPages
+type Taxonomy map[string]WeightedPages
 
 // OrderedTaxonomy is another representation of an Taxonomy using an array rather than a map.
 // Important because you can't order a map.
@@ -43,7 +42,7 @@ type OrderedTaxonomy []OrderedTaxonomyEntry
 
 // getOneOPage returns one page in the taxonomy,
 // nil if there is none.
-func (t OrderedTaxonomy) getOneOPage() page.Page {
+func (t OrderedTaxonomy) getOneOPage() Page {
 	if len(t) == 0 {
 		return nil
 	}
@@ -51,24 +50,20 @@ func (t OrderedTaxonomy) getOneOPage() page.Page {
 }
 
 // OrderedTaxonomyEntry is similar to an element of a Taxonomy, but with the key embedded (as name)
-// e.g:  {Name: Technology, page.WeightedPages: TaxonomyPages}
+// e.g:  {Name: Technology, WeightedPages: TaxonomyPages}
 type OrderedTaxonomyEntry struct {
 	Name string
-	page.WeightedPages
+	WeightedPages
 }
 
 // Get the weighted pages for the given key.
-func (i Taxonomy) Get(key string) page.WeightedPages {
+func (i Taxonomy) Get(key string) WeightedPages {
 	return i[key]
 }
 
 // Count the weighted pages for the given key.
 func (i Taxonomy) Count(key string) int { return len(i[key]) }
 
-func (i Taxonomy) add(key string, w page.WeightedPage) {
-	i[key] = append(i[key], w)
-}
-
 // TaxonomyArray returns an ordered taxonomy with a non defined order.
 func (i Taxonomy) TaxonomyArray() OrderedTaxonomy {
 	ies := make([]OrderedTaxonomyEntry, len(i))
@@ -117,7 +112,7 @@ func (i Taxonomy) ByCount() OrderedTaxonomy {
 }
 
 // Pages returns the Pages for this taxonomy.
-func (ie OrderedTaxonomyEntry) Pages() page.Pages {
+func (ie OrderedTaxonomyEntry) Pages() Pages {
 	return ie.WeightedPages.Pages()
 }
 
diff --git a/resources/resource/resources.go b/resources/resource/resources.go
index a888d6fb4ba..a877c890638 100644
--- a/resources/resource/resources.go
+++ b/resources/resource/resources.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2023 The Hugo Authors. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package resource contains Resource related types.
 package resource
 
 import (
diff --git a/resources/resource/resourcetypes.go b/resources/resource/resourcetypes.go
index 4ba95c17053..4ef5fc5a0f8 100644
--- a/resources/resource/resourcetypes.go
+++ b/resources/resource/resourcetypes.go
@@ -64,6 +64,9 @@ type ResourceError interface {
 
 // ErrProvider provides an Err.
 type ErrProvider interface {
+
+	// Err returns an error if this resource is in an error state.
+	// This will currently only be set for resources obtained from resources.GetRemote.
 	Err() ResourceError
 }
 
diff --git a/source/fileInfo.go b/source/fileInfo.go
index f882eb898b0..9ce601b5bd2 100644
--- a/source/fileInfo.go
+++ b/source/fileInfo.go
@@ -18,7 +18,9 @@ import (
 	"path/filepath"
 	"strings"
 	"sync"
+	"time"
 
+	"github.com/bep/gitmap"
 	"github.com/gohugoio/hugo/common/paths"
 
 	"github.com/gohugoio/hugo/hugofs/files"
@@ -294,3 +296,31 @@ func (sp *SourceSpec) NewFileInfo(fi hugofs.FileMetaInfo) (*FileInfo, error) {
 
 	return f, nil
 }
+
+func NewGitInfo(info gitmap.GitInfo) GitInfo {
+	return GitInfo(info)
+}
+
+// GitInfo provides information about a version controled source file.
+type GitInfo struct {
+	// Commit hash.
+	Hash string `json:"hash"`
+	// Abbreviated commit hash.
+	AbbreviatedHash string `json:"abbreviatedHash"`
+	// The commit message's subject/title line.
+	Subject string `json:"subject"`
+	// The author name, respecting .mailmap.
+	AuthorName string `json:"authorName"`
+	// The author email address, respecting .mailmap.
+	AuthorEmail string `json:"authorEmail"`
+	// The author date.
+	AuthorDate time.Time `json:"authorDate"`
+	// The commit date.
+	CommitDate time.Time `json:"commitDate"`
+}
+
+// IsZero returns true if the GitInfo is empty,
+// meaning it will also be falsy in the Go templates.
+func (g GitInfo) IsZero() bool {
+	return g.Hash == ""
+}
diff --git a/source/sourceSpec.go b/source/sourceSpec.go
index 0b7306a7537..954167f28ca 100644
--- a/source/sourceSpec.go
+++ b/source/sourceSpec.go
@@ -1,4 +1,4 @@
-// Copyright 2017-present The Hugo Authors. All rights reserved.
+// Copyright 2023 The Hugo Authors. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package source contains the types and functions related to source files.
 package source
 
 import (
diff --git a/tpl/diagrams/init.go b/tpl/diagrams/init.go
index 005e942b720..1ed308c5730 100644
--- a/tpl/diagrams/init.go
+++ b/tpl/diagrams/init.go
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package diagrams provides template functions for generating diagrams.
 package diagrams
 
 import (
diff --git a/tpl/images/images.go b/tpl/images/images.go
index 1abee1b0c40..f4d9305708f 100644
--- a/tpl/images/images.go
+++ b/tpl/images/images.go
@@ -91,6 +91,7 @@ func (ns *Namespace) Config(path any) (image.Config, error) {
 	return config, nil
 }
 
+// Filter applies the given filters to the image given as the last element in args.
 func (ns *Namespace) Filter(args ...any) (images.ImageResource, error) {
 	if len(args) < 2 {
 		return nil, errors.New("must provide an image and one or more filters")
diff --git a/tpl/openapi/docs.go b/tpl/openapi/docs.go
new file mode 100644
index 00000000000..bb7ed9c7508
--- /dev/null
+++ b/tpl/openapi/docs.go
@@ -0,0 +1,2 @@
+// Package openapi provides functions for generating OpenAPI (Swagger) documentation.
+package openapi
diff --git a/tpl/openapi/openapi3/openapi3.go b/tpl/openapi/openapi3/openapi3.go
index 9b84e9fbe47..31d56289ec8 100644
--- a/tpl/openapi/openapi3/openapi3.go
+++ b/tpl/openapi/openapi3/openapi3.go
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package openapi3 provides functions for generating OpenAPI v3 (Swagger) documentation.
 package openapi3
 
 import (
@@ -54,6 +55,7 @@ type OpenAPIDocument struct {
 	*kopenapi3.T
 }
 
+// Unmarshal unmarshals the given resource into an OpenAPI 3 document.
 func (ns *Namespace) Unmarshal(r resource.UnmarshableResource) (*OpenAPIDocument, error) {
 	key := r.Key()
 	if key == "" {
diff --git a/tpl/os/os.go b/tpl/os/os.go
index e7fd0593901..4c7089779c0 100644
--- a/tpl/os/os.go
+++ b/tpl/os/os.go
@@ -22,6 +22,7 @@ import (
 	"path/filepath"
 
 	"github.com/bep/overlayfs"
+	"github.com/gohugoio/hugo/common/herrors"
 	"github.com/gohugoio/hugo/deps"
 	"github.com/spf13/afero"
 	"github.com/spf13/cast"
@@ -101,7 +102,11 @@ func (ns *Namespace) ReadFile(i any) (string, error) {
 		s = ns.deps.PathSpec.RelPathify(s)
 	}
 
-	return readFile(ns.readFileFs, s)
+	s, err = readFile(ns.readFileFs, s)
+	if err != nil && herrors.IsNotExist(err) {
+		return "", nil
+	}
+	return s, err
 }
 
 // ReadDir lists the directory contents relative to the configured WorkingDir.
diff --git a/tpl/path/path.go b/tpl/path/path.go
index 378b97e0335..59add2152da 100644
--- a/tpl/path/path.go
+++ b/tpl/path/path.go
@@ -15,11 +15,11 @@
 package path
 
 import (
-	"fmt"
 	_path "path"
 	"path/filepath"
 	"strings"
 
+	"github.com/gohugoio/hugo/common/paths"
 	"github.com/gohugoio/hugo/deps"
 	"github.com/spf13/cast"
 )
@@ -36,17 +36,6 @@ type Namespace struct {
 	deps *deps.Deps
 }
 
-// DirFile holds the result from path.Split.
-type DirFile struct {
-	Dir  string
-	File string
-}
-
-// Used in test.
-func (df DirFile) String() string {
-	return fmt.Sprintf("%s|%s", df.Dir, df.File)
-}
-
 // Ext returns the file name extension used by path.
 // The extension is the suffix beginning at the final dot
 // in the final slash-separated element of path;
@@ -117,15 +106,15 @@ func (ns *Namespace) BaseName(path any) (string, error) {
 // The input path is passed into filepath.ToSlash converting any Windows slashes
 // to forward slashes.
 // The returned values have the property that path = dir+file.
-func (ns *Namespace) Split(path any) (DirFile, error) {
+func (ns *Namespace) Split(path any) (paths.DirFile, error) {
 	spath, err := cast.ToStringE(path)
 	if err != nil {
-		return DirFile{}, err
+		return paths.DirFile{}, err
 	}
 	spath = filepath.ToSlash(spath)
 	dir, file := _path.Split(spath)
 
-	return DirFile{Dir: dir, File: file}, nil
+	return paths.DirFile{Dir: dir, File: file}, nil
 }
 
 // Join joins any number of path elements into a single path, adding a
diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go
index 9d663cb46d9..85323f057f6 100644
--- a/tpl/resources/resources.go
+++ b/tpl/resources/resources.go
@@ -414,6 +414,7 @@ func (ns *Namespace) PostCSS(args ...any) (resource.Resource, error) {
 	return ns.postcssClient.Process(r, m)
 }
 
+// PostProcess processes r after the build.
 func (ns *Namespace) PostProcess(r resource.Resource) (postpub.PostPublishedResource, error) {
 	return ns.deps.ResourceSpec.PostProcess(r)
 }
diff --git a/tpl/strings/truncate.go b/tpl/strings/truncate.go
index dd6267280fb..e8da7b84b50 100644
--- a/tpl/strings/truncate.go
+++ b/tpl/strings/truncate.go
@@ -39,9 +39,9 @@ type htmlTag struct {
 	openTag bool
 }
 
-// Truncate truncates a given string to the specified length.
-func (ns *Namespace) Truncate(a any, options ...any) (template.HTML, error) {
-	length, err := cast.ToIntE(a)
+// Truncate truncates the string in s to the specified length.
+func (ns *Namespace) Truncate(s any, options ...any) (template.HTML, error) {
+	length, err := cast.ToIntE(s)
 	if err != nil {
 		return "", err
 	}
diff --git a/tpl/template.go b/tpl/template.go
index 738750de70c..dd9249adef5 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -11,6 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package tpl contains template functions and related types.
 package tpl
 
 import (


Test patch:
diff --git a/resources/page/testhelpers_test.go b/resources/page/testhelpers_test.go
index 22346c3895b..e8275ba4050 100644
--- a/resources/page/testhelpers_test.go
+++ b/resources/page/testhelpers_test.go
@@ -26,7 +26,6 @@ import (
 
 	"github.com/gohugoio/hugo/modules"
 
-	"github.com/bep/gitmap"
 	"github.com/gohugoio/hugo/helpers"
 	"github.com/gohugoio/hugo/resources/resource"
 
@@ -250,8 +249,8 @@ func (p *testPage) GetRelatedDocsHandler() *RelatedDocsHandler {
 	return relatedDocsHandler
 }
 
-func (p *testPage) GitInfo() *gitmap.GitInfo {
-	return nil
+func (p *testPage) GitInfo() source.GitInfo {
+	return source.GitInfo{}
 }
 
 func (p *testPage) CodeOwners() []string {
diff --git a/tpl/os/integration_test.go b/tpl/os/integration_test.go
index fe1bb3d6e0b..d08374f8fb2 100644
--- a/tpl/os/integration_test.go
+++ b/tpl/os/integration_test.go
@@ -49,3 +49,29 @@ START:|{{ range $entry := $entries }}{{ if not $entry.IsDir }}{{ $entry.Name }}|
 START:|config.toml|myproject.txt|:END:
 `)
 }
+
+// Issue 9620
+func TestReadFileNotExists(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+-- layouts/index.html --
+{{ $fi := (readFile "doesnotexist") }}
+{{ if $fi }}Failed{{ else }}OK{{ end }}
+
+
+  `
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+			NeedsOsFS:   true,
+		},
+	).Build()
+
+	b.AssertFileContent("public/index.html", `
+OK
+`)
+}
diff --git a/tpl/os/os_test.go b/tpl/os/os_test.go
index 98befa06180..399adb3749a 100644
--- a/tpl/os/os_test.go
+++ b/tpl/os/os_test.go
@@ -38,15 +38,15 @@ func TestReadFile(t *testing.T) {
 	}{
 		{filepath.FromSlash("/f/f1.txt"), "f1-content"},
 		{filepath.FromSlash("f/f1.txt"), "f1-content"},
-		{filepath.FromSlash("../f2.txt"), false},
+		{filepath.FromSlash("../f2.txt"), ""},
 		{"", false},
-		{"b", false},
+		{"b", ""},
 	} {
 
 		result, err := ns.ReadFile(test.filename)
 
 		if bb, ok := test.expect.(bool); ok && !bb {
-			b.Assert(err, qt.Not(qt.IsNil))
+			b.Assert(err, qt.Not(qt.IsNil), qt.Commentf("filename: %q", test.filename))
 			continue
 		}
 
diff --git a/tpl/path/path_test.go b/tpl/path/path_test.go
index 599d8367a97..cc49bf28cff 100644
--- a/tpl/path/path_test.go
+++ b/tpl/path/path_test.go
@@ -18,6 +18,7 @@ import (
 	"testing"
 
 	qt "github.com/frankban/quicktest"
+	"github.com/gohugoio/hugo/common/paths"
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/deps"
 )
@@ -157,7 +158,7 @@ func TestJoin(t *testing.T) {
 			`baz/foo/bar.txt`,
 		},
 		{
-			[]any{"", "baz", DirFile{"big", "john"}, filepath.FromSlash(`foo/bar.txt`)},
+			[]any{"", "baz", paths.DirFile{Dir: "big", File: "john"}, filepath.FromSlash(`foo/bar.txt`)},
 			`baz/big|john/foo/bar.txt`,
 		},
 		{nil, ""},
@@ -186,10 +187,10 @@ func TestSplit(t *testing.T) {
 		path   any
 		expect any
 	}{
-		{filepath.FromSlash(`foo/bar.txt`), DirFile{`foo/`, `bar.txt`}},
-		{filepath.FromSlash(`foo/bar/txt `), DirFile{`foo/bar/`, `txt `}},
-		{`foo.bar.txt`, DirFile{``, `foo.bar.txt`}},
-		{``, DirFile{``, ``}},
+		{filepath.FromSlash(`foo/bar.txt`), paths.DirFile{Dir: `foo/`, File: `bar.txt`}},
+		{filepath.FromSlash(`foo/bar/txt `), paths.DirFile{Dir: `foo/bar/`, File: `txt `}},
+		{`foo.bar.txt`, paths.DirFile{Dir: ``, File: `foo.bar.txt`}},
+		{``, paths.DirFile{Dir: ``, File: ``}},
 		// errors
 		{tstNoStringer{}, false},
 	} {