mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
errors: Misc improvements
* Redo the server error template * Always add the content file context if relevant * Remove some now superflous error string matching * Move the server error template to _server/error.html * Add file context (with position) to codeblock render blocks * Improve JS build errors Fixes #9892 Fixes #9891 Fixes #9893
This commit is contained in:
@@ -77,7 +77,7 @@ kind = '{section,term}'
|
||||
}
|
||||
builders := make([]*IntegrationTestBuilder, b.N)
|
||||
|
||||
for i, _ := range builders {
|
||||
for i := range builders {
|
||||
builders[i] = NewIntegrationTestBuilder(cfg)
|
||||
}
|
||||
|
||||
|
@@ -511,5 +511,5 @@ func (configLoader) loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err er
|
||||
}
|
||||
|
||||
func (l configLoader) wrapFileError(err error, filename string) error {
|
||||
return herrors.NewFileErrorFromFile(err, filename, filename, l.Fs, herrors.SimpleLineMatcher)
|
||||
return herrors.NewFileErrorFromFile(err, filename, filename, l.Fs, nil)
|
||||
}
|
||||
|
@@ -968,7 +968,7 @@ func (h *HugoSites) errWithFileContext(err error, f source.File) error {
|
||||
}
|
||||
realFilename := fim.Meta().Filename
|
||||
|
||||
return herrors.NewFileErrorFromFile(err, realFilename, realFilename, h.SourceSpec.Fs.Source, herrors.SimpleLineMatcher)
|
||||
return herrors.NewFileErrorFromFile(err, realFilename, realFilename, h.SourceSpec.Fs.Source, nil)
|
||||
|
||||
}
|
||||
|
||||
|
@@ -347,13 +347,21 @@ minify = true
|
||||
|
||||
}
|
||||
|
||||
func TestErrorNested(t *testing.T) {
|
||||
func TestErrorNestedRender(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: "Home"
|
||||
---
|
||||
-- layouts/index.html --
|
||||
line 1
|
||||
line 2
|
||||
1{{ .Render "myview" }}
|
||||
-- layouts/_default/myview.html --
|
||||
line 1
|
||||
12{{ partial "foo.html" . }}
|
||||
line 4
|
||||
line 5
|
||||
@@ -373,15 +381,237 @@ line 4
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
errors := herrors.UnwrapFileErrorsWithErrorContext(err)
|
||||
b.Assert(errors, qt.HasLen, 4)
|
||||
b.Assert(errors[0].Position().LineNumber, qt.Equals, 3)
|
||||
b.Assert(errors[0].Position().ColumnNumber, qt.Equals, 4)
|
||||
b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/layouts/index.html:3:4": execute of template failed`))
|
||||
b.Assert(errors[0].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "1{{ .Render \"myview\" }}"})
|
||||
b.Assert(errors[2].Position().LineNumber, qt.Equals, 2)
|
||||
b.Assert(errors[2].Position().ColumnNumber, qt.Equals, 5)
|
||||
b.Assert(errors[2].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "12{{ partial \"foo.html\" . }}", "line 4", "line 5"})
|
||||
|
||||
b.Assert(errors[3].Position().LineNumber, qt.Equals, 3)
|
||||
b.Assert(errors[3].Position().ColumnNumber, qt.Equals, 6)
|
||||
b.Assert(errors[3].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "123{{ .ThisDoesNotExist }}", "line 4"})
|
||||
|
||||
}
|
||||
|
||||
func TestErrorNestedShortocde(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: "Home"
|
||||
---
|
||||
|
||||
## Hello
|
||||
{{< hello >}}
|
||||
|
||||
-- layouts/index.html --
|
||||
line 1
|
||||
line 2
|
||||
{{ .Content }}
|
||||
line 5
|
||||
-- layouts/shortcodes/hello.html --
|
||||
line 1
|
||||
12{{ partial "foo.html" . }}
|
||||
line 4
|
||||
line 5
|
||||
-- layouts/partials/foo.html --
|
||||
line 1
|
||||
line 2
|
||||
123{{ .ThisDoesNotExist }}
|
||||
line 4
|
||||
`
|
||||
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
errors := herrors.UnwrapFileErrorsWithErrorContext(err)
|
||||
|
||||
b.Assert(errors, qt.HasLen, 3)
|
||||
|
||||
b.Assert(errors[0].Position().LineNumber, qt.Equals, 6)
|
||||
b.Assert(errors[0].Position().ColumnNumber, qt.Equals, 1)
|
||||
b.Assert(errors[0].ErrorContext().ChromaLexer, qt.Equals, "md")
|
||||
b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/content/_index.md:6:1": failed to render shortcode "hello": failed to process shortcode: "/layouts/shortcodes/hello.html:2:5":`))
|
||||
b.Assert(errors[0].ErrorContext().Lines, qt.DeepEquals, []string{"", "## Hello", "{{< hello >}}", ""})
|
||||
b.Assert(errors[1].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "12{{ partial \"foo.html\" . }}", "line 4", "line 5"})
|
||||
b.Assert(errors[2].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "123{{ .ThisDoesNotExist }}", "line 4"})
|
||||
|
||||
}
|
||||
|
||||
func TestErrorRenderHookHeading(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: "Home"
|
||||
---
|
||||
|
||||
## Hello
|
||||
|
||||
-- layouts/index.html --
|
||||
line 1
|
||||
line 2
|
||||
{{ .Content }}
|
||||
line 5
|
||||
-- layouts/_default/_markup/render-heading.html --
|
||||
line 1
|
||||
12{{ .Levels }}
|
||||
line 4
|
||||
line 5
|
||||
`
|
||||
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
errors := herrors.UnwrapFileErrorsWithErrorContext(err)
|
||||
|
||||
b.Assert(errors, qt.HasLen, 2)
|
||||
fmt.Println(errors[0])
|
||||
b.Assert(errors[0].Position().LineNumber, qt.Equals, 2)
|
||||
b.Assert(errors[0].Position().ColumnNumber, qt.Equals, 5)
|
||||
b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/layouts/index.html:2:5": execute of template failed`))
|
||||
b.Assert(errors[0].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "12{{ partial \"foo.html\" . }}", "line 4", "line 5"})
|
||||
b.Assert(errors[1].Position().LineNumber, qt.Equals, 3)
|
||||
b.Assert(errors[1].Position().ColumnNumber, qt.Equals, 6)
|
||||
b.Assert(errors[1].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "123{{ .ThisDoesNotExist }}", "line 4"})
|
||||
b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/content/_index.md:1:1": "/layouts/_default/_markup/render-heading.html:2:5": execute of template failed`))
|
||||
|
||||
}
|
||||
|
||||
func TestErrorRenderHookCodeblock(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: "Home"
|
||||
---
|
||||
|
||||
## Hello
|
||||
|
||||
§§§ foo
|
||||
bar
|
||||
§§§
|
||||
|
||||
|
||||
-- layouts/index.html --
|
||||
line 1
|
||||
line 2
|
||||
{{ .Content }}
|
||||
line 5
|
||||
-- layouts/_default/_markup/render-codeblock-foo.html --
|
||||
line 1
|
||||
12{{ .Foo }}
|
||||
line 4
|
||||
line 5
|
||||
`
|
||||
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
errors := herrors.UnwrapFileErrorsWithErrorContext(err)
|
||||
|
||||
b.Assert(errors, qt.HasLen, 2)
|
||||
first := errors[0]
|
||||
b.Assert(first.Error(), qt.Contains, filepath.FromSlash(`"/content/_index.md:7:1": "/layouts/_default/_markup/render-codeblock-foo.html:2:5": execute of template failed`))
|
||||
|
||||
}
|
||||
|
||||
func TestErrorInBaseTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
filesTemplate := `
|
||||
-- config.toml --
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: "Home"
|
||||
---
|
||||
-- layouts/baseof.html --
|
||||
line 1 base
|
||||
line 2 base
|
||||
{{ block "main" . }}empty{{ end }}
|
||||
line 4 base
|
||||
{{ block "toc" . }}empty{{ end }}
|
||||
-- layouts/index.html --
|
||||
{{ define "main" }}
|
||||
line 2 index
|
||||
line 3 index
|
||||
line 4 index
|
||||
{{ end }}
|
||||
{{ define "toc" }}
|
||||
TOC: {{ partial "toc.html" . }}
|
||||
{{ end }}
|
||||
-- layouts/partials/toc.html --
|
||||
toc line 1
|
||||
toc line 2
|
||||
toc line 3
|
||||
toc line 4
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
|
||||
t.Run("base template", func(t *testing.T) {
|
||||
files := strings.Replace(filesTemplate, "line 4 base", "123{{ .ThisDoesNotExist \"abc\" }}", 1)
|
||||
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`render of "home" failed: "/layouts/baseof.html:4:6"`))
|
||||
|
||||
})
|
||||
|
||||
t.Run("index template", func(t *testing.T) {
|
||||
files := strings.Replace(filesTemplate, "line 3 index", "1234{{ .ThisDoesNotExist \"abc\" }}", 1)
|
||||
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`render of "home" failed: "/layouts/index.html:3:7"`))
|
||||
|
||||
})
|
||||
|
||||
t.Run("partial from define", func(t *testing.T) {
|
||||
files := strings.Replace(filesTemplate, "toc line 2", "12345{{ .ThisDoesNotExist \"abc\" }}", 1)
|
||||
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`render of "home" failed: "/layouts/index.html:7:8": execute of template failed`))
|
||||
b.Assert(err.Error(), qt.Contains, `execute of template failed: template: partials/toc.html:2:8: executing "partials/toc.html"`)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
@@ -572,25 +572,23 @@ func (p *pageState) wrapError(err error) error {
|
||||
|
||||
filename := p.File().Filename()
|
||||
|
||||
if ferr := herrors.UnwrapFileError(err); ferr != nil {
|
||||
// Check if it's already added.
|
||||
for _, ferr := range herrors.UnwrapFileErrors(err) {
|
||||
errfilename := ferr.Position().Filename
|
||||
if ferr.ErrorContext() != nil || errfilename == "" || !(errfilename == pageFileErrorName || filepath.IsAbs(errfilename)) {
|
||||
if errfilename == filename {
|
||||
if ferr.ErrorContext() == nil {
|
||||
f, ioerr := p.s.SourceSpec.Fs.Source.Open(filename)
|
||||
if ioerr != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
ferr.UpdateContent(f, nil)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if filepath.IsAbs(errfilename) {
|
||||
filename = errfilename
|
||||
}
|
||||
f, ferr2 := p.s.SourceSpec.Fs.Source.Open(filename)
|
||||
if ferr2 != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
pos := ferr.Position()
|
||||
pos.Filename = filename
|
||||
return ferr.UpdatePosition(pos).UpdateContent(f, herrors.SimpleLineMatcher)
|
||||
}
|
||||
|
||||
return herrors.NewFileErrorFromFile(err, filename, filename, p.s.SourceSpec.Fs.Source, herrors.SimpleLineMatcher)
|
||||
return herrors.NewFileErrorFromFile(err, filename, filename, p.s.SourceSpec.Fs.Source, herrors.NopLineMatcher)
|
||||
|
||||
}
|
||||
|
||||
@@ -644,8 +642,10 @@ Loop:
|
||||
m, err := metadecoders.Default.UnmarshalToMap(it.Val, f)
|
||||
if err != nil {
|
||||
if fe, ok := err.(herrors.FileError); ok {
|
||||
// Offset the starting position of front matter.
|
||||
pos := fe.Position()
|
||||
// Apply the error to the content file.
|
||||
pos.Filename = p.File().Filename()
|
||||
// Offset the starting position of front matter.
|
||||
offset := iter.LineNumber() - 1
|
||||
if f == metadecoders.YAML {
|
||||
offset -= 1
|
||||
@@ -788,7 +788,7 @@ func (p *pageState) outputFormat() (f output.Format) {
|
||||
|
||||
func (p *pageState) parseError(err error, input []byte, offset int) error {
|
||||
pos := p.posFromInput(input, offset)
|
||||
return herrors.NewFileError("page.md", err).UpdatePosition(pos)
|
||||
return herrors.NewFileError(p.File().Filename(), err).UpdatePosition(pos)
|
||||
}
|
||||
|
||||
func (p *pageState) pathOrTitle() string {
|
||||
|
@@ -417,7 +417,7 @@ func (p *pageContentOutput) Render(layout ...string) (template.HTML, error) {
|
||||
// Make sure to send the *pageState and not the *pageContentOutput to the template.
|
||||
res, err := executeToString(p.p.s.Tmpl(), templ, p.p)
|
||||
if err != nil {
|
||||
return "", p.p.wrapError(fmt.Errorf("failed to execute template %q v: %w", layout, err))
|
||||
return "", p.p.wrapError(fmt.Errorf("failed to execute template %s: %w", templ.Name(), err))
|
||||
}
|
||||
return template.HTML(res), nil
|
||||
}
|
||||
|
@@ -270,7 +270,6 @@ const (
|
||||
innerNewlineRegexp = "\n"
|
||||
innerCleanupRegexp = `\A<p>(.*)</p>\n\z`
|
||||
innerCleanupExpand = "$1"
|
||||
pageFileErrorName = "page.md"
|
||||
)
|
||||
|
||||
func renderShortcode(
|
||||
@@ -299,7 +298,7 @@ func renderShortcode(
|
||||
var err error
|
||||
tmpl, err = s.TextTmpl().Parse(templName, templStr)
|
||||
if err != nil {
|
||||
fe := herrors.NewFileError(pageFileErrorName, err)
|
||||
fe := herrors.NewFileError(p.File().Filename(), err)
|
||||
pos := fe.Position()
|
||||
pos.LineNumber += p.posOffset(sc.pos).LineNumber
|
||||
fe = fe.UpdatePosition(pos)
|
||||
@@ -392,7 +391,7 @@ func renderShortcode(
|
||||
result, err := renderShortcodeWithPage(s.Tmpl(), tmpl, data)
|
||||
|
||||
if err != nil && sc.isInline {
|
||||
fe := herrors.NewFileError("shortcode.md", err)
|
||||
fe := herrors.NewFileError(p.File().Filename(), err)
|
||||
pos := fe.Position()
|
||||
pos.LineNumber += p.posOffset(sc.pos).LineNumber
|
||||
fe = fe.UpdatePosition(pos)
|
||||
|
Reference in New Issue
Block a user