diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2018-10-21 12:20:21 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2018-10-22 20:46:14 +0200 |
commit | d1661b823af25c50d3bbe5366ea40a3cdd52e237 (patch) | |
tree | cd84d18229fb9c294ff1be56d7c0ce92a8f46761 | |
parent | 7930d2132a3c36c1aaca20f16f56978c84656b0a (diff) |
hugolib: Continue the file context/line number errors work
See #5324
-rw-r--r-- | commands/server_errors.go | 2 | ||||
-rw-r--r-- | common/herrors/error_locator.go | 49 | ||||
-rw-r--r-- | common/herrors/error_locator_test.go | 6 | ||||
-rw-r--r-- | common/herrors/file_error.go | 41 | ||||
-rw-r--r-- | common/herrors/file_error_test.go | 19 | ||||
-rw-r--r-- | common/herrors/line_number_extractors.go | 27 | ||||
-rw-r--r-- | deps/deps.go | 30 | ||||
-rw-r--r-- | hugolib/hugo_sites.go | 36 | ||||
-rw-r--r-- | hugolib/hugo_sites_build.go | 75 | ||||
-rw-r--r-- | hugolib/hugo_sites_build_errors_test.go | 87 | ||||
-rw-r--r-- | hugolib/page.go | 53 | ||||
-rw-r--r-- | hugolib/page_content.go | 35 | ||||
-rw-r--r-- | hugolib/page_errors.go | 47 | ||||
-rw-r--r-- | hugolib/shortcode.go | 30 | ||||
-rw-r--r-- | hugolib/site.go | 39 | ||||
-rw-r--r-- | hugolib/testhelpers_test.go | 6 | ||||
-rw-r--r-- | parser/pageparser/pagelexer.go | 17 | ||||
-rw-r--r-- | parser/pageparser/pageparser.go | 5 | ||||
-rw-r--r-- | parser/pageparser/pageparser_intro_test.go | 8 | ||||
-rw-r--r-- | tpl/data/data.go | 24 | ||||
-rw-r--r-- | tpl/data/data_test.go | 21 | ||||
-rw-r--r-- | tpl/template.go | 19 | ||||
-rw-r--r-- | tpl/tplimpl/template_errors.go | 4 |
23 files changed, 444 insertions, 236 deletions
diff --git a/commands/server_errors.go b/commands/server_errors.go index 1a469dac8..8ee02e5f2 100644 --- a/commands/server_errors.go +++ b/commands/server_errors.go @@ -72,7 +72,7 @@ var buildErrorTemplate = `<!doctype html> <main> {{ highlight .Error "apl" "noclasses=true,style=monokai" }} {{ with .File }} - {{ $params := printf "noclasses=true,style=monokai,linenos=table,hl_lines=%d,linenostart=%d" (add .Pos 1) .LineNumber }} + {{ $params := printf "noclasses=true,style=monokai,linenos=table,hl_lines=%d,linenostart=%d" (add .Pos 1) (sub .LineNumber .Pos) }} {{ $lexer := .ChromaLexer | default "go-html-template" }} {{ highlight (delimit .Lines "\n") $lexer $params }} {{ end }} diff --git a/common/herrors/error_locator.go b/common/herrors/error_locator.go index cc41e8868..306f8f46b 100644 --- a/common/herrors/error_locator.go +++ b/common/herrors/error_locator.go @@ -16,12 +16,17 @@ package herrors import ( "bufio" + "fmt" "io" "strings" + "github.com/gohugoio/hugo/helpers" + "github.com/spf13/afero" ) +var fileErrorFormat = "\"%s:%d:%d\": %s" + // LineMatcher is used to match a line with an error. type LineMatcher func(le FileError, lineNumber int, line string) bool @@ -34,6 +39,8 @@ var SimpleLineMatcher = func(le FileError, lineNumber int, line string) bool { // ErrorContext contains contextual information about an error. This will // typically be the lines surrounding some problem in a file. type ErrorContext struct { + // The source filename. + Filename string // If a match will contain the matched line and up to 2 lines before and after. // Will be empty if no match. @@ -45,6 +52,9 @@ type ErrorContext struct { // The linenumber in the source file from where the Lines start. Starting at 1. LineNumber int + // The column number in the source file. Starting at 1. + ColumnNumber int + // The lexer to use for syntax highlighting. // https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages ChromaLexer string @@ -60,7 +70,7 @@ type ErrorWithFileContext struct { } func (e *ErrorWithFileContext) Error() string { - return e.cause.Error() + return fmt.Sprintf(fileErrorFormat, e.Filename, e.LineNumber, e.ColumnNumber, e.cause.Error()) } func (e *ErrorWithFileContext) Cause() error { @@ -69,39 +79,40 @@ func (e *ErrorWithFileContext) Cause() error { // WithFileContextForFile will try to add a file context with lines matching the given matcher. // If no match could be found, the original error is returned with false as the second return value. -func WithFileContextForFile(e error, filename string, fs afero.Fs, chromaLexer string, matcher LineMatcher) (error, bool) { +func WithFileContextForFile(e error, realFilename, filename string, fs afero.Fs, matcher LineMatcher) (error, bool) { f, err := fs.Open(filename) if err != nil { return e, false } defer f.Close() - return WithFileContext(e, f, chromaLexer, matcher) + return WithFileContext(e, realFilename, f, matcher) } // WithFileContextForFile will try to add a file context with lines matching the given matcher. // If no match could be found, the original error is returned with false as the second return value. -func WithFileContext(e error, r io.Reader, chromaLexer string, matcher LineMatcher) (error, bool) { +func WithFileContext(e error, realFilename string, r io.Reader, matcher LineMatcher) (error, bool) { if e == nil { panic("error missing") } le := UnwrapFileError(e) if le == nil { var ok bool - if le, ok = ToFileError("bash", e).(FileError); !ok { + if le, ok = ToFileError("", e).(FileError); !ok { return e, false } } errCtx := locateError(r, le, matcher) + errCtx.Filename = realFilename if errCtx.LineNumber == -1 { return e, false } - if chromaLexer != "" { - errCtx.ChromaLexer = chromaLexer - } else { + if le.Type() != "" { errCtx.ChromaLexer = chromaLexerFromType(le.Type()) + } else { + errCtx.ChromaLexer = chromaLexerFromFilename(realFilename) } return &ErrorWithFileContext{cause: e, ErrorContext: errCtx}, true @@ -124,9 +135,22 @@ func UnwrapErrorWithFileContext(err error) *ErrorWithFileContext { } func chromaLexerFromType(fileType string) string { + switch fileType { + case "html", "htm": + return "go-html-template" + } return fileType } +func chromaLexerFromFilename(filename string) string { + if strings.Contains(filename, "layouts") { + return "go-html-template" + } + + ext := helpers.ExtNoDelimiter(filename) + return chromaLexerFromType(ext) +} + func locateErrorInString(le FileError, src string, matcher LineMatcher) ErrorContext { return locateError(strings.NewReader(src), nil, matcher) } @@ -135,6 +159,11 @@ func locateError(r io.Reader, le FileError, matches LineMatcher) ErrorContext { var errCtx ErrorContext s := bufio.NewScanner(r) + errCtx.ColumnNumber = 1 + if le != nil { + errCtx.ColumnNumber = le.ColumnNumber() + } + lineNo := 0 var buff [6]string @@ -152,7 +181,7 @@ func locateError(r io.Reader, le FileError, matches LineMatcher) ErrorContext { if errCtx.Pos == -1 && matches(le, lineNo, txt) { errCtx.Pos = i - errCtx.LineNumber = lineNo - i + errCtx.LineNumber = lineNo } if errCtx.Pos == -1 && i == 2 { @@ -171,7 +200,7 @@ func locateError(r io.Reader, le FileError, matches LineMatcher) ErrorContext { if matches(le, lineNo, "") { buff[i] = "" errCtx.Pos = i - errCtx.LineNumber = lineNo - 1 + errCtx.LineNumber = lineNo i++ } diff --git a/common/herrors/error_locator_test.go b/common/herrors/error_locator_test.go index 6c879727e..caa6e6385 100644 --- a/common/herrors/error_locator_test.go +++ b/common/herrors/error_locator_test.go @@ -41,7 +41,7 @@ LINE 8 location := locateErrorInString(nil, lines, lineMatcher) assert.Equal([]string{"LINE 3", "LINE 4", "This is THEONE", "LINE 6", "LINE 7"}, location.Lines) - assert.Equal(3, location.LineNumber) + assert.Equal(5, location.LineNumber) assert.Equal(2, location.Pos) assert.Equal([]string{"This is THEONE"}, locateErrorInString(nil, `This is THEONE`, lineMatcher).Lines) @@ -92,7 +92,7 @@ I J`, lineMatcher) assert.Equal([]string{"D", "E", "F", "G", "H"}, location.Lines) - assert.Equal(4, location.LineNumber) + assert.Equal(6, location.LineNumber) assert.Equal(2, location.Pos) // Test match EOF @@ -106,7 +106,7 @@ C `, lineMatcher) assert.Equal([]string{"B", "C", ""}, location.Lines) - assert.Equal(3, location.LineNumber) + assert.Equal(4, location.LineNumber) assert.Equal(2, location.Pos) } diff --git a/common/herrors/file_error.go b/common/herrors/file_error.go index f29f91fcc..86ccfcefb 100644 --- a/common/herrors/file_error.go +++ b/common/herrors/file_error.go @@ -13,10 +13,6 @@ package herrors -import ( - "fmt" -) - var _ causer = (*fileError)(nil) // FileError represents an error when handling a file: Parsing a config file, @@ -27,6 +23,8 @@ type FileError interface { // LineNumber gets the error location, starting at line 1. LineNumber() int + ColumnNumber() int + // A string identifying the type of file, e.g. JSON, TOML, markdown etc. Type() string } @@ -34,9 +32,9 @@ type FileError interface { var _ FileError = (*fileError)(nil) type fileError struct { - lineNumber int - fileType string - msg string + lineNumber int + columnNumber int + fileType string cause error } @@ -45,32 +43,28 @@ func (e *fileError) LineNumber() int { return e.lineNumber } +func (e *fileError) ColumnNumber() int { + return e.columnNumber +} + func (e *fileError) Type() string { return e.fileType } func (e *fileError) Error() string { - return e.msg + if e.cause == nil { + return "" + } + return e.cause.Error() } func (f *fileError) Cause() error { return f.cause } -func (e *fileError) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - fallthrough - case 's': - fmt.Fprintf(s, "%s:%d: %s:%s", e.fileType, e.lineNumber, e.msg, e.cause) - case 'q': - fmt.Fprintf(s, "%q:%d: %q:%q", e.fileType, e.lineNumber, e.msg, e.cause) - } -} - // NewFileError creates a new FileError. -func NewFileError(fileType string, lineNumber int, msg string, err error) FileError { - return &fileError{cause: err, fileType: fileType, lineNumber: lineNumber, msg: msg} +func NewFileError(fileType string, lineNumber, columnNumber int, err error) FileError { + return &fileError{cause: err, fileType: fileType, lineNumber: lineNumber, columnNumber: columnNumber} } // UnwrapFileError tries to unwrap a FileError from err. @@ -101,9 +95,10 @@ func ToFileError(fileType string, err error) error { // If will fall back to returning the original error if a line number cannot be extracted. func ToFileErrorWithOffset(fileType string, err error, offset int) error { for _, handle := range lineNumberExtractors { - lno, msg := handle(err, offset) + + lno, col := handle(err) if lno > 0 { - return NewFileError(fileType, lno, msg, err) + return NewFileError(fileType, lno+offset, col, err) } } // Fall back to the original. diff --git a/common/herrors/file_error_test.go b/common/herrors/file_error_test.go index e266ff1dc..0d4e82f66 100644 --- a/common/herrors/file_error_test.go +++ b/common/herrors/file_error_test.go @@ -28,16 +28,16 @@ func TestToLineNumberError(t *testing.T) { assert := require.New(t) for i, test := range []struct { - in error - offset int - lineNumber int + in error + offset int + lineNumber int + columnNumber int }{ - {errors.New("no line number for you"), 0, -1}, - {errors.New(`template: _default/single.html:2:15: executing "_default/single.html" at <.Titles>: can't evaluate field`), 0, 2}, - {errors.New("parse failed: template: _default/bundle-resource-meta.html:11: unexpected in operand"), 0, 11}, - {errors.New(`failed:: template: _default/bundle-resource-meta.html:2:7: executing "main" at <.Titles>`), 0, 2}, - {errors.New("error in front matter: Near line 32 (last key parsed 'title')"), 0, 32}, - {errors.New("error in front matter: Near line 32 (last key parsed 'title')"), 2, 34}, + {errors.New("no line number for you"), 0, -1, 1}, + {errors.New(`template: _default/single.html:4:15: executing "_default/single.html" at <.Titles>: can't evaluate field Titles in type *hugolib.PageOutput`), 0, 4, 15}, + {errors.New("parse failed: template: _default/bundle-resource-meta.html:11: unexpected in operand"), 0, 11, 1}, + {errors.New(`failed:: template: _default/bundle-resource-meta.html:2:7: executing "main" at <.Titles>`), 0, 2, 7}, + {errors.New("error in front matter: Near line 32 (last key parsed 'title')"), 0, 32, 1}, } { got := ToFileErrorWithOffset("template", test.in, test.offset) @@ -48,6 +48,7 @@ func TestToLineNumberError(t *testing.T) { if test.lineNumber > 0 { assert.True(ok) assert.Equal(test.lineNumber, le.LineNumber(), errMsg) + assert.Equal(test.columnNumber, le.ColumnNumber(), errMsg) assert.Contains(got.Error(), strconv.Itoa(le.LineNumber())) } else { assert.False(ok) diff --git a/common/herrors/line_number_extractors.go b/common/herrors/line_number_extractors.go index 01a7450f9..8740afdf7 100644 --- a/common/herrors/line_number_extractors.go +++ b/common/herrors/line_number_extractors.go @@ -14,14 +14,13 @@ package herrors import ( - "fmt" "regexp" "strconv" ) var lineNumberExtractors = []lineNumberExtractor{ // Template/shortcode parse errors - newLineNumberErrHandlerFromRegexp("(.*?:)(\\d+)(:.*)"), + newLineNumberErrHandlerFromRegexp("(.*?:)(\\d+)(:)(\\d+)?(.*)"), // TOML parse errors newLineNumberErrHandlerFromRegexp("(.*Near line )(\\d+)(\\s.*)"), @@ -30,7 +29,7 @@ var lineNumberExtractors = []lineNumberExtractor{ newLineNumberErrHandlerFromRegexp("(line )(\\d+)(:)"), } -type lineNumberExtractor func(e error, offset int) (int, string) +type lineNumberExtractor func(e error) (int, int) func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor { re := regexp.MustCompile(expression) @@ -38,22 +37,26 @@ func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor { } func extractLineNo(re *regexp.Regexp) lineNumberExtractor { - return func(e error, offset int) (int, string) { + return func(e error) (int, int) { if e == nil { panic("no error") } + col := 1 s := e.Error() m := re.FindStringSubmatch(s) - if len(m) == 4 { - i, _ := strconv.Atoi(m[2]) - msg := e.Error() - if offset != 0 { - i = i + offset - msg = re.ReplaceAllString(s, fmt.Sprintf("${1}%d${3}", i)) + if len(m) >= 4 { + lno, _ := strconv.Atoi(m[2]) + if len(m) > 4 { + col, _ = strconv.Atoi(m[4]) } - return i, msg + + if col <= 0 { + col = 1 + } + + return lno, col } - return -1, "" + return -1, col } } diff --git a/deps/deps.go b/deps/deps.go index 1e2686421..db59ad212 100644 --- a/deps/deps.go +++ b/deps/deps.go @@ -5,7 +5,6 @@ import ( "time" "github.com/gohugoio/hugo/common/loggers" - "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/hugofs" @@ -16,6 +15,7 @@ import ( "github.com/gohugoio/hugo/resource" "github.com/gohugoio/hugo/source" "github.com/gohugoio/hugo/tpl" + jww "github.com/spf13/jwalterweatherman" ) // Deps holds dependencies used by many. @@ -73,6 +73,33 @@ type Deps struct { // BuildStartListeners will be notified before a build starts. BuildStartListeners *Listeners + + *globalErrHandler +} + +type globalErrHandler struct { + // Channel for some "hard to get to" build errors + buildErrors chan error +} + +// SendErr sends the error on a channel to be handled later. +// This can be used in situations where returning and aborting the current +// operation isn't practical. +func (e *globalErrHandler) SendError(err error) { + if e.buildErrors != nil { + select { + case e.buildErrors <- err: + default: + } + return + } + + jww.ERROR.Println(err) +} + +func (e *globalErrHandler) StartErrorCollector() chan error { + e.buildErrors = make(chan error, 10) + return e.buildErrors } // Listeners represents an event listener. @@ -194,6 +221,7 @@ func New(cfg DepsCfg) (*Deps, error) { Language: cfg.Language, BuildStartListeners: &Listeners{}, Timeout: time.Duration(timeoutms) * time.Millisecond, + globalErrHandler: &globalErrHandler{}, } if cfg.Cfg.GetBool("templateMetrics") { diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 7f70967d6..a184e8877 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -21,6 +21,7 @@ import ( "strings" "sync" + "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/helpers" @@ -53,6 +54,40 @@ type HugoSites struct { gitInfo *gitInfo } +func (h *HugoSites) pickOneAndLogTheRest(errors []error) error { + if len(errors) == 0 { + return nil + } + + var i int + + for j, err := range errors { + // If this is in server mode, we want to return an error to the client + // with a file context, if possible. + if herrors.UnwrapErrorWithFileContext(err) != nil { + i = j + break + } + } + + // Log the rest, but add a threshold to avoid flooding the log. + const errLogThreshold = 5 + + for j, err := range errors { + if j == i || err == nil { + continue + } + + if j >= errLogThreshold { + break + } + + h.Log.ERROR.Println(err) + } + + return errors[i] +} + func (h *HugoSites) IsMultihost() bool { return h != nil && h.multihost } @@ -636,6 +671,7 @@ func handleShortcodes(p *PageWithoutContent, rawContentCopy []byte) ([]byte, err err := p.shortcodeState.executeShortcodesForDelta(p) if err != nil { + return rawContentCopy, err } diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index 13fbfd57e..4c275f55b 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -26,13 +26,29 @@ import ( // Build builds all sites. If filesystem events are provided, // this is considered to be a potential partial rebuild. func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error { + errCollector := h.StartErrorCollector() + errs := make(chan error) + + go func(from, to chan error) { + var errors []error + i := 0 + for e := range from { + i++ + if i > 50 { + break + } + errors = append(errors, e) + } + to <- h.pickOneAndLogTheRest(errors) + + close(to) + + }(errCollector, errs) if h.Metrics != nil { h.Metrics.Reset() } - //t0 := time.Now() - // Need a pointer as this may be modified. conf := &config @@ -41,33 +57,46 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error { conf.whatChanged = &whatChanged{source: true, other: true} } + var prepareErr error + if !config.PartialReRender { - for _, s := range h.Sites { - s.Deps.BuildStartListeners.Notify() - } + prepare := func() error { + for _, s := range h.Sites { + s.Deps.BuildStartListeners.Notify() + } + + if len(events) > 0 { + // Rebuild + if err := h.initRebuild(conf); err != nil { + return err + } + } else { + if err := h.init(conf); err != nil { + return err + } + } - if len(events) > 0 { - // Rebuild - if err := h.initRebuild(conf); err != nil { + if err := h.process(conf, events...); err != nil { return err } - } else { - if err := h.init(conf); err != nil { + + if err := h.assemble(conf); err != nil { return err } + return nil } - if err := h.process(conf, events...); err != nil { - return err + prepareErr = prepare() + if prepareErr != nil { + h.SendError(prepareErr) } - if err := h.assemble(conf); err != nil { - return err - } } - if err := h.render(conf); err != nil { - return err + if prepareErr == nil { + if err := h.render(conf); err != nil { + h.SendError(err) + } } if h.Metrics != nil { @@ -79,6 +108,18 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error { h.Log.FEEDBACK.Println() } + select { + // Make sure the channel always gets something. + case errCollector <- nil: + default: + } + close(errCollector) + + err := <-errs + if err != nil { + return err + } + errorCount := h.Log.ErrorCounter.Count() if errorCount > 0 { return fmt.Errorf("logged %d error(s)", errorCount) diff --git a/hugolib/hugo_sites_build_errors_test.go b/hugolib/hugo_sites_build_errors_test.go index 6b44bea88..2e8eb99ea 100644 --- a/hugolib/hugo_sites_build_errors_test.go +++ b/hugolib/hugo_sites_build_errors_test.go @@ -2,6 +2,7 @@ package hugolib import ( "fmt" + "path/filepath" "strings" "testing" @@ -17,13 +18,20 @@ type testSiteBuildErrorAsserter struct { func (t testSiteBuildErrorAsserter) getFileError(err error) *herrors.ErrorWithFileContext { t.assert.NotNil(err, t.name) ferr := herrors.UnwrapErrorWithFileContext(err) - t.assert.NotNil(ferr, fmt.Sprintf("[%s] got %T: %+v", t.name, err, err)) + t.assert.NotNil(ferr, fmt.Sprintf("[%s] got %T: %+v\n%s", t.name, err, err, trace())) return ferr } func (t testSiteBuildErrorAsserter) assertLineNumber(lineNumber int, err error) { fe := t.getFileError(err) - t.assert.Equal(lineNumber, fe.LineNumber, fmt.Sprintf("[%s] got => %s", t.name, fe)) + t.assert.Equal(lineNumber, fe.LineNumber, fmt.Sprintf("[%s] got => %s\n%s", t.name, fe, trace())) +} + +func (t testSiteBuildErrorAsserter) assertErrorMessage(e1, e2 string) { + // The error message will contain filenames with OS slashes. Normalize before compare. + e1, e2 = filepath.ToSlash(e1), filepath.ToSlash(e2) + t.assert.Equal(e1, e2, trace()) + } func TestSiteBuildErrors(t *testing.T) { @@ -32,6 +40,7 @@ func TestSiteBuildErrors(t *testing.T) { const ( yamlcontent = "yamlcontent" + tomlcontent = "tomlcontent" shortcode = "shortcode" base = "base" single = "single" @@ -55,7 +64,7 @@ func TestSiteBuildErrors(t *testing.T) { return strings.Replace(content, ".Title }}", ".Title }", 1) }, assertCreateError: func(a testSiteBuildErrorAsserter, err error) { - a.assertLineNumber(2, err) + a.assertLineNumber(4, err) }, }, { @@ -65,7 +74,7 @@ func TestSiteBuildErrors(t *testing.T) { return strings.Replace(content, ".Title", ".Titles", 1) }, assertBuildError: func(a testSiteBuildErrorAsserter, err error) { - a.assertLineNumber(2, err) + a.assertLineNumber(4, err) }, }, { @@ -75,7 +84,12 @@ func TestSiteBuildErrors(t *testing.T) { return strings.Replace(content, ".Title }}", ".Title }", 1) }, assertCreateError: func(a testSiteBuildErrorAsserter, err error) { - a.assertLineNumber(3, err) + fe := a.getFileError(err) + assert.Equal(5, fe.LineNumber) + assert.Equal(1, fe.ColumnNumber) + assert.Equal("go-html-template", fe.ChromaLexer) + a.assertErrorMessage("\"layouts/_default/single.html:5:1\": parse failed: template: _default/single.html:5: unexpected \"}\" in operand", fe.Error()) + }, }, { @@ -85,7 +99,12 @@ func TestSiteBuildErrors(t *testing.T) { return strings.Replace(content, ".Title", ".Titles", 1) }, assertBuildError: func(a testSiteBuildErrorAsserter, err error) { - a.assertLineNumber(3, err) + fe := a.getFileError(err) + assert.Equal(5, fe.LineNumber) + assert.Equal(14, fe.ColumnNumber) + assert.Equal("md", fe.ChromaLexer) + a.assertErrorMessage("asdfadf", f |