diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-05-12 11:43:20 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-05-14 13:40:56 +0200 |
commit | 5c96bda70a7afb2ce97cbb3cd70c64fc8cb94446 (patch) | |
tree | 394a557b0dc7db1f6753cf2a09e8cb0577f18442 /common | |
parent | 4a96df96d958a8ce122f103c4b417eaba52e6cb1 (diff) |
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
Diffstat (limited to 'common')
-rw-r--r-- | common/herrors/error_locator.go | 58 | ||||
-rw-r--r-- | common/herrors/error_locator_test.go | 52 | ||||
-rw-r--r-- | common/herrors/file_error.go | 58 | ||||
-rw-r--r-- | common/herrors/file_error_test.go | 2 | ||||
-rw-r--r-- | common/loggers/loggers.go | 5 | ||||
-rw-r--r-- | common/loggers/loggers_test.go | 12 |
6 files changed, 126 insertions, 61 deletions
diff --git a/common/herrors/error_locator.go b/common/herrors/error_locator.go index 5d2c10c04..0f22f545f 100644 --- a/common/herrors/error_locator.go +++ b/common/herrors/error_locator.go @@ -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 } diff --git a/common/herrors/error_locator_test.go b/common/herrors/error_locator_test.go index 10b016fa8..6135657d8 100644 --- a/common/herrors/error_locator_test.go +++ b/common/herrors/error_locator_test.go @@ -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) diff --git a/common/herrors/file_error.go b/common/herrors/file_error.go index abd36cfbc..5cdb0e53b 100644 --- a/common/herrors/file_error.go +++ b/common/herrors/file_error.go @@ -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 diff --git a/common/herrors/file_error_test.go b/common/herrors/file_error_test.go index e6595aa28..41e244109 100644 --- a/common/herrors/file_error_test.go +++ b/common/herrors/file_error_test.go @@ -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) diff --git a/common/loggers/loggers.go b/common/loggers/loggers.go index 14c76ae45..c61c55a67 100644 --- a/common/loggers/loggers.go +++ b/common/loggers/loggers.go @@ -240,6 +240,11 @@ func NewBasicLoggerForWriter(t jww.Threshold, w io.Writer) Logger { return newLogger(t, jww.LevelError, w, ioutil.Discard, false) } +// RemoveANSIColours removes all ANSI colours from the given string. +func RemoveANSIColours(s string) string { + return ansiColorRe.ReplaceAllString(s, "") +} + var ( ansiColorRe = regexp.MustCompile("(?s)\\033\\[\\d*(;\\d*)*m") errorRe = regexp.MustCompile("^(ERROR|FATAL|WARN)") diff --git a/common/loggers/loggers_test.go b/common/loggers/loggers_test.go index 0c0cc859b..a7bd1ae12 100644 --- a/common/loggers/loggers_test.go +++ b/common/loggers/loggers_test.go @@ -46,3 +46,15 @@ func TestLoggerToWriterWithPrefix(t *testing.T) { c.Assert(b.String(), qt.Equals, "myprefix: Hello Hugo!\n") } + +func TestRemoveANSIColours(t *testing.T) { + c := qt.New(t) + + c.Assert(RemoveANSIColours(""), qt.Equals, "") + c.Assert(RemoveANSIColours("\033[31m"), qt.Equals, "") + c.Assert(RemoveANSIColours("\033[31mHello"), qt.Equals, "Hello") + c.Assert(RemoveANSIColours("\033[31mHello\033[0m"), qt.Equals, "Hello") + c.Assert(RemoveANSIColours("\033[31mHello\033[0m World"), qt.Equals, "Hello World") + c.Assert(RemoveANSIColours("\033[31mHello\033[0m World\033[31m!"), qt.Equals, "Hello World!") + c.Assert(RemoveANSIColours("\x1b[90m 5 |"), qt.Equals, " 5 |") +} |