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:
@@ -34,11 +34,22 @@ type LineMatcher struct {
|
||||
}
|
||||
|
||||
// LineMatcherFn is used to match a line with an error.
|
||||
type LineMatcherFn func(m LineMatcher) bool
|
||||
// It returns the column number or 0 if the line was found, but column could not be determinde. Returns -1 if no line match.
|
||||
type LineMatcherFn func(m LineMatcher) int
|
||||
|
||||
// SimpleLineMatcher simply matches by line number.
|
||||
var SimpleLineMatcher = func(m LineMatcher) bool {
|
||||
return m.Position.LineNumber == m.LineNumber
|
||||
var SimpleLineMatcher = func(m LineMatcher) int {
|
||||
if m.Position.LineNumber == m.LineNumber {
|
||||
// We found the line, but don't know the column.
|
||||
return 0
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// NopLineMatcher is a matcher that always returns 1.
|
||||
// This will effectively give line 1, column 1.
|
||||
var NopLineMatcher = func(m LineMatcher) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// ErrorContext contains contextual information about an error. This will
|
||||
@@ -52,6 +63,10 @@ type ErrorContext struct {
|
||||
// The position of the error in the Lines above. 0 based.
|
||||
LinesPos int
|
||||
|
||||
// The position of the content in the file. Note that this may be different from the error's position set
|
||||
// in FileError.
|
||||
Position text.Position
|
||||
|
||||
// The lexer to use for syntax highlighting.
|
||||
// https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages
|
||||
ChromaLexer string
|
||||
@@ -78,31 +93,24 @@ func chromaLexerFromFilename(filename string) string {
|
||||
return chromaLexerFromType(ext)
|
||||
}
|
||||
|
||||
func locateErrorInString(src string, matcher LineMatcherFn) (*ErrorContext, text.Position) {
|
||||
func locateErrorInString(src string, matcher LineMatcherFn) *ErrorContext {
|
||||
return locateError(strings.NewReader(src), &fileError{}, matcher)
|
||||
}
|
||||
|
||||
func locateError(r io.Reader, le FileError, matches LineMatcherFn) (*ErrorContext, text.Position) {
|
||||
func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext {
|
||||
if le == nil {
|
||||
panic("must provide an error")
|
||||
}
|
||||
|
||||
errCtx := &ErrorContext{LinesPos: -1}
|
||||
pos := text.Position{LineNumber: -1, ColumnNumber: 1, Offset: -1}
|
||||
ectx := &ErrorContext{LinesPos: -1, Position: text.Position{Offset: -1}}
|
||||
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errCtx, pos
|
||||
return ectx
|
||||
}
|
||||
|
||||
lepos := le.Position()
|
||||
|
||||
lines := strings.Split(string(b), "\n")
|
||||
|
||||
if lepos.ColumnNumber >= 0 {
|
||||
pos.ColumnNumber = lepos.ColumnNumber
|
||||
}
|
||||
|
||||
lineNo := 0
|
||||
posBytes := 0
|
||||
|
||||
@@ -115,34 +123,36 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) (*ErrorContex
|
||||
Offset: posBytes,
|
||||
Line: line,
|
||||
}
|
||||
if errCtx.LinesPos == -1 && matches(m) {
|
||||
pos.LineNumber = lineNo
|
||||
v := matches(m)
|
||||
if ectx.LinesPos == -1 && v != -1 {
|
||||
ectx.Position.LineNumber = lineNo
|
||||
ectx.Position.ColumnNumber = v
|
||||
break
|
||||
}
|
||||
|
||||
posBytes += len(line)
|
||||
}
|
||||
|
||||
if pos.LineNumber != -1 {
|
||||
low := pos.LineNumber - 3
|
||||
if ectx.Position.LineNumber > 0 {
|
||||
low := ectx.Position.LineNumber - 3
|
||||
if low < 0 {
|
||||
low = 0
|
||||
}
|
||||
|
||||
if pos.LineNumber > 2 {
|
||||
errCtx.LinesPos = 2
|
||||
if ectx.Position.LineNumber > 2 {
|
||||
ectx.LinesPos = 2
|
||||
} else {
|
||||
errCtx.LinesPos = pos.LineNumber - 1
|
||||
ectx.LinesPos = ectx.Position.LineNumber - 1
|
||||
}
|
||||
|
||||
high := pos.LineNumber + 2
|
||||
high := ectx.Position.LineNumber + 2
|
||||
if high > len(lines) {
|
||||
high = len(lines)
|
||||
}
|
||||
|
||||
errCtx.Lines = lines[low:high]
|
||||
ectx.Lines = lines[low:high]
|
||||
|
||||
}
|
||||
|
||||
return errCtx, pos
|
||||
return ectx
|
||||
}
|
||||
|
@@ -24,8 +24,11 @@ import (
|
||||
func TestErrorLocator(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
lineMatcher := func(m LineMatcher) bool {
|
||||
return strings.Contains(m.Line, "THEONE")
|
||||
lineMatcher := func(m LineMatcher) int {
|
||||
if strings.Contains(m.Line, "THEONE") {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
lines := `LINE 1
|
||||
@@ -38,23 +41,25 @@ LINE 7
|
||||
LINE 8
|
||||
`
|
||||
|
||||
location, pos := locateErrorInString(lines, lineMatcher)
|
||||
location := locateErrorInString(lines, lineMatcher)
|
||||
pos := location.Position
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"LINE 3", "LINE 4", "This is THEONE", "LINE 6", "LINE 7"})
|
||||
|
||||
c.Assert(pos.LineNumber, qt.Equals, 5)
|
||||
c.Assert(location.LinesPos, qt.Equals, 2)
|
||||
|
||||
locate := func(s string, m LineMatcherFn) *ErrorContext {
|
||||
ctx, _ := locateErrorInString(s, m)
|
||||
ctx := locateErrorInString(s, m)
|
||||
return ctx
|
||||
}
|
||||
|
||||
c.Assert(locate(`This is THEONE`, lineMatcher).Lines, qt.DeepEquals, []string{"This is THEONE"})
|
||||
|
||||
location, pos = locateErrorInString(`L1
|
||||
location = locateErrorInString(`L1
|
||||
This is THEONE
|
||||
L2
|
||||
`, lineMatcher)
|
||||
pos = location.Position
|
||||
c.Assert(pos.LineNumber, qt.Equals, 2)
|
||||
c.Assert(location.LinesPos, qt.Equals, 1)
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "This is THEONE", "L2", ""})
|
||||
@@ -78,16 +83,20 @@ This THEONE
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "L2", "This THEONE", ""})
|
||||
c.Assert(location.LinesPos, qt.Equals, 2)
|
||||
|
||||
location, pos = locateErrorInString("NO MATCH", lineMatcher)
|
||||
c.Assert(pos.LineNumber, qt.Equals, -1)
|
||||
location = locateErrorInString("NO MATCH", lineMatcher)
|
||||
pos = location.Position
|
||||
c.Assert(pos.LineNumber, qt.Equals, 0)
|
||||
c.Assert(location.LinesPos, qt.Equals, -1)
|
||||
c.Assert(len(location.Lines), qt.Equals, 0)
|
||||
|
||||
lineMatcher = func(m LineMatcher) bool {
|
||||
return m.LineNumber == 6
|
||||
lineMatcher = func(m LineMatcher) int {
|
||||
if m.LineNumber == 6 {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
location, pos = locateErrorInString(`A
|
||||
location = locateErrorInString(`A
|
||||
B
|
||||
C
|
||||
D
|
||||
@@ -97,35 +106,46 @@ G
|
||||
H
|
||||
I
|
||||
J`, lineMatcher)
|
||||
pos = location.Position
|
||||
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"D", "E", "F", "G", "H"})
|
||||
c.Assert(pos.LineNumber, qt.Equals, 6)
|
||||
c.Assert(location.LinesPos, qt.Equals, 2)
|
||||
|
||||
// Test match EOF
|
||||
lineMatcher = func(m LineMatcher) bool {
|
||||
return m.LineNumber == 4
|
||||
lineMatcher = func(m LineMatcher) int {
|
||||
if m.LineNumber == 4 {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
location, pos = locateErrorInString(`A
|
||||
location = locateErrorInString(`A
|
||||
B
|
||||
C
|
||||
`, lineMatcher)
|
||||
|
||||
pos = location.Position
|
||||
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"B", "C", ""})
|
||||
c.Assert(pos.LineNumber, qt.Equals, 4)
|
||||
c.Assert(location.LinesPos, qt.Equals, 2)
|
||||
|
||||
offsetMatcher := func(m LineMatcher) bool {
|
||||
return m.Offset == 1
|
||||
offsetMatcher := func(m LineMatcher) int {
|
||||
if m.Offset == 1 {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
location, pos = locateErrorInString(`A
|
||||
location = locateErrorInString(`A
|
||||
B
|
||||
C
|
||||
D
|
||||
E`, offsetMatcher)
|
||||
|
||||
pos = location.Position
|
||||
|
||||
c.Assert(location.Lines, qt.DeepEquals, []string{"A", "B", "C", "D"})
|
||||
c.Assert(pos.LineNumber, qt.Equals, 2)
|
||||
c.Assert(location.LinesPos, qt.Equals, 1)
|
||||
|
@@ -73,37 +73,40 @@ func (fe *fileError) UpdateContent(r io.Reader, linematcher LineMatcherFn) FileE
|
||||
}
|
||||
|
||||
var (
|
||||
contentPos text.Position
|
||||
posle = fe.position
|
||||
errorContext *ErrorContext
|
||||
posle = fe.position
|
||||
ectx *ErrorContext
|
||||
)
|
||||
|
||||
if posle.LineNumber <= 1 && posle.Offset > 0 {
|
||||
// Try to locate the line number from the content if offset is set.
|
||||
errorContext, contentPos = locateError(r, fe, func(m LineMatcher) bool {
|
||||
ectx = locateError(r, fe, func(m LineMatcher) int {
|
||||
if posle.Offset >= m.Offset && posle.Offset < m.Offset+len(m.Line) {
|
||||
lno := posle.LineNumber - m.Position.LineNumber + m.LineNumber
|
||||
m.Position = text.Position{LineNumber: lno}
|
||||
return linematcher(m)
|
||||
}
|
||||
return false
|
||||
return -1
|
||||
})
|
||||
} else {
|
||||
errorContext, contentPos = locateError(r, fe, linematcher)
|
||||
ectx = locateError(r, fe, linematcher)
|
||||
}
|
||||
|
||||
if errorContext.ChromaLexer == "" {
|
||||
if ectx.ChromaLexer == "" {
|
||||
if fe.fileType != "" {
|
||||
errorContext.ChromaLexer = chromaLexerFromType(fe.fileType)
|
||||
ectx.ChromaLexer = chromaLexerFromType(fe.fileType)
|
||||
} else {
|
||||
errorContext.ChromaLexer = chromaLexerFromFilename(fe.Position().Filename)
|
||||
ectx.ChromaLexer = chromaLexerFromFilename(fe.Position().Filename)
|
||||
}
|
||||
}
|
||||
|
||||
fe.errorContext = errorContext
|
||||
fe.errorContext = ectx
|
||||
|
||||
if contentPos.LineNumber > 0 {
|
||||
fe.position.LineNumber = contentPos.LineNumber
|
||||
if ectx.Position.LineNumber > 0 {
|
||||
fe.position.LineNumber = ectx.Position.LineNumber
|
||||
}
|
||||
|
||||
if ectx.Position.ColumnNumber > 0 {
|
||||
fe.position.ColumnNumber = ectx.Position.ColumnNumber
|
||||
}
|
||||
|
||||
return fe
|
||||
@@ -144,10 +147,6 @@ func (e *fileError) Unwrap() error {
|
||||
// The value for name should identify the file, the best
|
||||
// being the full filename to the file on disk.
|
||||
func NewFileError(name string, err error) FileError {
|
||||
if err == nil {
|
||||
panic("err is nil")
|
||||
}
|
||||
|
||||
// Filetype is used to determine the Chroma lexer to use.
|
||||
fileType, pos := extractFileTypePos(err)
|
||||
pos.Filename = name
|
||||
@@ -155,10 +154,17 @@ func NewFileError(name string, err error) FileError {
|
||||
_, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(name))
|
||||
}
|
||||
|
||||
if pos.LineNumber < 0 {
|
||||
panic(fmt.Sprintf("invalid line number: %d", pos.LineNumber))
|
||||
}
|
||||
return &fileError{cause: err, fileType: fileType, position: pos}
|
||||
|
||||
}
|
||||
|
||||
// NewFileErrorFromPos will use the filename and line number from pos to create a new FileError, wrapping err.
|
||||
func NewFileErrorFromPos(pos text.Position, err error) FileError {
|
||||
// Filetype is used to determine the Chroma lexer to use.
|
||||
fileType, _ := extractFileTypePos(err)
|
||||
if fileType == "" {
|
||||
_, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(pos.Filename))
|
||||
}
|
||||
return &fileError{cause: err, fileType: fileType, position: pos}
|
||||
|
||||
}
|
||||
@@ -191,7 +197,7 @@ func extractFileTypePos(err error) (string, text.Position) {
|
||||
err = Cause(err)
|
||||
var fileType string
|
||||
|
||||
// Fall back to line/col 1:1 if we cannot find any better information.
|
||||
// Default to line 1 col 1 if we don't find any better.
|
||||
pos := text.Position{
|
||||
Offset: -1,
|
||||
LineNumber: 1,
|
||||
@@ -242,6 +248,18 @@ func UnwrapFileError(err error) FileError {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnwrapFileErrors tries to unwrap all FileError.
|
||||
func UnwrapFileErrors(err error) []FileError {
|
||||
var errs []FileError
|
||||
for err != nil {
|
||||
if v, ok := err.(FileError); ok {
|
||||
errs = append(errs, v)
|
||||
}
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext.
|
||||
func UnwrapFileErrorsWithErrorContext(err error) []FileError {
|
||||
var errs []FileError
|
||||
|
@@ -41,7 +41,7 @@ func TestNewFileError(t *testing.T) {
|
||||
fe.UpdatePosition(text.Position{LineNumber: 32, ColumnNumber: 2})
|
||||
c.Assert(fe.Error(), qt.Equals, `"foo.html:32:2": bar`)
|
||||
fe.UpdatePosition(text.Position{LineNumber: 0, ColumnNumber: 0, Offset: 212})
|
||||
fe.UpdateContent(strings.NewReader(lines), SimpleLineMatcher)
|
||||
fe.UpdateContent(strings.NewReader(lines), nil)
|
||||
c.Assert(fe.Error(), qt.Equals, `"foo.html:32:0": bar`)
|
||||
errorContext := fe.ErrorContext()
|
||||
c.Assert(errorContext, qt.IsNotNil)
|
||||
|
Reference in New Issue
Block a user