summaryrefslogtreecommitdiffstats
path: root/common/herrors
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-05-12 11:43:20 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-05-14 13:40:56 +0200
commit5c96bda70a7afb2ce97cbb3cd70c64fc8cb94446 (patch)
tree394a557b0dc7db1f6753cf2a09e8cb0577f18442 /common/herrors
parent4a96df96d958a8ce122f103c4b417eaba52e6cb1 (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/herrors')
-rw-r--r--common/herrors/error_locator.go58
-rw-r--r--common/herrors/error_locator_test.go52
-rw-r--r--common/herrors/file_error.go58
-rw-r--r--common/herrors/file_error_test.go2
4 files changed, 109 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)