summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2025-04-14 11:20:36 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2025-04-14 14:38:22 +0200
commit8a2830f2dcbf2c9b799ff8d1db9601da58f98494 (patch)
tree05c0c66d1f8da8c17f1bc06df5c9a5176a7a391a
parent1e0287f4729f477c6b956fc50f0bfde0edecaa33 (diff)
downloadhugo-8a2830f2dcbf2c9b799ff8d1db9601da58f98494.tar.gz
hugo-8a2830f2dcbf2c9b799ff8d1db9601da58f98494.zip
tpl: Add proper file context to template parse errors
Fixes #13604
-rw-r--r--tpl/templates/templates_integration_test.go33
-rw-r--r--tpl/tplimpl/templates.go11
-rw-r--r--tpl/tplimpl/templatestore.go32
3 files changed, 60 insertions, 16 deletions
diff --git a/tpl/templates/templates_integration_test.go b/tpl/templates/templates_integration_test.go
index a0a5e385a..635d521d7 100644
--- a/tpl/templates/templates_integration_test.go
+++ b/tpl/templates/templates_integration_test.go
@@ -17,6 +17,7 @@ import (
"path/filepath"
"testing"
+ qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugolib"
)
@@ -229,3 +230,35 @@ layouts/section/section.html
b := hugolib.Test(t, files)
b.AssertFileContent("public/mysection/index.html", "layouts/section/section.html")
}
+
+func TestErrorMessageParseError(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+-- layouts/home.html --
+Line 1.
+Line 2. {{ foo }} <- this func does not exist.
+Line 3.
+`
+
+ b, err := hugolib.TestE(t, files)
+ b.Assert(err, qt.IsNotNil)
+ b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`"/layouts/home.html:2:1": parse of template failed: template: home.html:2: function "foo" not defined`))
+}
+
+func TestErrorMessageExecuteError(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+-- layouts/home.html --
+Line 1.
+Line 2. {{ .Foo }} <- this method does not exist.
+Line 3.
+`
+
+ b, err := hugolib.TestE(t, files)
+ b.Assert(err, qt.IsNotNil)
+ b.Assert(err.Error(), qt.Contains, filepath.FromSlash(` "/layouts/home.html:2:11": execute of template failed`))
+}
diff --git a/tpl/tplimpl/templates.go b/tpl/tplimpl/templates.go
index f3f98f622..19de48e38 100644
--- a/tpl/tplimpl/templates.go
+++ b/tpl/tplimpl/templates.go
@@ -44,7 +44,16 @@ var embeddedTemplatesAliases = map[string][]string{
"_shortcodes/twitter.html": {"_shortcodes/tweet.html"},
}
-func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
+func (s *TemplateStore) parseTemplate(ti *TemplInfo) error {
+ err := s.tns.doParseTemplate(ti)
+ if err != nil {
+ return s.addFileContext(ti, "parse of template failed", err)
+ }
+
+ return err
+}
+
+func (t *templateNamespace) doParseTemplate(ti *TemplInfo) error {
if !ti.noBaseOf || ti.category == CategoryBaseof {
// Delay parsing until we have the base template.
return nil
diff --git a/tpl/tplimpl/templatestore.go b/tpl/tplimpl/templatestore.go
index 2e332e382..7544b5625 100644
--- a/tpl/tplimpl/templatestore.go
+++ b/tpl/tplimpl/templatestore.go
@@ -495,7 +495,7 @@ func (t *TemplateStore) ExecuteWithContext(ctx context.Context, ti *TemplInfo, w
execErr := t.storeSite.executer.ExecuteWithContext(ctx, ti, wr, data)
if execErr != nil {
- return t.addFileContext(ti, execErr)
+ return t.addFileContext(ti, "execute of template failed", execErr)
}
return nil
}
@@ -822,7 +822,7 @@ func (t *TemplateStore) addDeferredTemplate(owner *TemplInfo, name string, n *pa
return nil
}
-func (s *TemplateStore) addFileContext(ti *TemplInfo, inerr error) error {
+func (s *TemplateStore) addFileContext(ti *TemplInfo, what string, inerr error) error {
if ti.Fi == nil {
return inerr
}
@@ -854,25 +854,27 @@ func (s *TemplateStore) addFileContext(ti *TemplInfo, inerr error) error {
fe := herrors.NewFileErrorFromName(inErr, fi.Meta().Filename)
fe.UpdateContent(f, lineMatcher)
- if !fe.ErrorContext().Position.IsValid() {
- return inErr, false
- }
- return fe, true
+ return fe, fe.ErrorContext().Position.IsValid()
}
- inerr = fmt.Errorf("execute of template failed: %w", inerr)
+ inerr = fmt.Errorf("%s: %w", what, inerr)
- if err, ok := checkFilename(ti.Fi, inerr); ok {
- return err
+ var (
+ currentErr error
+ ok bool
+ )
+
+ if currentErr, ok = checkFilename(ti.Fi, inerr); ok {
+ return currentErr
}
if ti.base != nil {
- if err, ok := checkFilename(ti.base.Fi, inerr); ok {
- return err
+ if currentErr, ok = checkFilename(ti.base.Fi, inerr); ok {
+ return currentErr
}
}
- return inerr
+ return currentErr
}
func (s *TemplateStore) extractIdentifiers(line string) []string {
@@ -1389,7 +1391,7 @@ func (s *TemplateStore) parseTemplates() error {
if vv.state == processingStateTransformed {
continue
}
- if err := s.tns.parseTemplate(vv); err != nil {
+ if err := s.parseTemplate(vv); err != nil {
return err
}
}
@@ -1409,7 +1411,7 @@ func (s *TemplateStore) parseTemplates() error {
// The regular expression used to detect if a template needs a base template has some
// rare false positives. Assume we don't need one.
vv.noBaseOf = true
- if err := s.tns.parseTemplate(vv); err != nil {
+ if err := s.parseTemplate(vv); err != nil {
return err
}
continue
@@ -1438,7 +1440,7 @@ func (s *TemplateStore) parseTemplates() error {
if vvv.state == processingStateTransformed {
continue
}
- if err := s.tns.parseTemplate(vvv); err != nil {
+ if err := s.parseTemplate(vvv); err != nil {
return err
}
}