]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Improve SASS errors
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 15 May 2022 09:40:34 +0000 (11:40 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 15 May 2022 18:25:25 +0000 (20:25 +0200)
Fixes #9897

24 files changed:
commands/server.go
common/herrors/error_locator.go
common/herrors/file_error.go
common/herrors/file_error_test.go
common/paths/url.go
go.mod
go.sum
hugolib/integrationtest_builder.go
hugolib/page.go
hugolib/shortcode.go
langs/i18n/translationProvider.go
parser/metadecoders/decoder.go
resources/resource_transformers/js/build.go
resources/resource_transformers/postcss/integration_test.go
resources/resource_transformers/postcss/postcss.go
resources/resource_transformers/tocss/dartsass/client.go
resources/resource_transformers/tocss/dartsass/integration_test.go
resources/resource_transformers/tocss/dartsass/transform.go
resources/resource_transformers/tocss/scss/client_extended.go
resources/resource_transformers/tocss/scss/integration_test.go
resources/resource_transformers/tocss/scss/tocss.go
tpl/tplimpl/template.go
tpl/tplimpl/template_errors.go
transform/chain.go

index 036fa9eaaa450ed307e9cf9fec403c2810adcf13..f616810039c9b2e9030b7c310a395ece5141340d 100644 (file)
@@ -482,6 +482,7 @@ func removeErrorPrefixFromLog(content string) string {
 var logReplacer = strings.NewReplacer(
        "can't", "can’t", // Chroma lexer does'nt do well with "can't"
        "*hugolib.pageState", "page.Page", // Page is the public interface.
+       "Rebuild failed:", "",
 )
 
 func cleanErrorLog(content string) string {
index 0f22f545f4060d5c4b2aec5a19066647d1a3eeab..18c21e51b2c6e2143457fd70000c549f305a5edf 100644 (file)
@@ -52,6 +52,16 @@ var NopLineMatcher = func(m LineMatcher) int {
        return 1
 }
 
+// OffsetMatcher is a line matcher that matches by offset.
+var OffsetMatcher = func(m LineMatcher) int {
+       if m.Offset+len(m.Line) >= m.Position.Offset {
+               // We found the line, but return 0 to signal that we want to determine
+               // the column from the error.
+               return 0
+       }
+       return -1
+}
+
 // ErrorContext contains contextual information about an error. This will
 // typically be the lines surrounding some problem in a file.
 type ErrorContext struct {
index 85007a0573618f1c832b57bba3be2897ee7be143..e6baaf6e37eb1964ff2998867f72613c6cba2e49 100644 (file)
@@ -19,6 +19,8 @@ import (
        "io"
        "path/filepath"
 
+       "github.com/bep/godartsass"
+       "github.com/bep/golibsass/libsass/libsasserrors"
        "github.com/gohugoio/hugo/common/paths"
        "github.com/gohugoio/hugo/common/text"
        "github.com/pelletier/go-toml/v2"
@@ -132,7 +134,22 @@ func (e fileError) Position() text.Position {
 }
 
 func (e *fileError) Error() string {
-       return fmt.Sprintf("%s: %s", e.position, e.cause)
+       return fmt.Sprintf("%s: %s", e.position, e.causeString())
+}
+
+func (e *fileError) causeString() string {
+       if e.cause == nil {
+               return ""
+       }
+       switch v := e.cause.(type) {
+       // Avoid repeating the file info in the error message.
+       case godartsass.SassError:
+               return v.Message
+       case libsasserrors.Error:
+               return v.Message
+       default:
+               return v.Error()
+       }
 }
 
 func (e *fileError) Unwrap() error {
@@ -140,9 +157,17 @@ func (e *fileError) Unwrap() error {
 }
 
 // NewFileError creates a new FileError that wraps err.
+// It will try to extract the filename and line number from err.
+func NewFileError(err error) FileError {
+       // Filetype is used to determine the Chroma lexer to use.
+       fileType, pos := extractFileTypePos(err)
+       return &fileError{cause: err, fileType: fileType, position: pos}
+}
+
+// NewFileErrorFromName creates a new FileError that wraps err.
 // The value for name should identify the file, the best
 // being the full filename to the file on disk.
-func NewFileError(err error, name string) FileError {
+func NewFileErrorFromName(err error, name string) FileError {
        // Filetype is used to determine the Chroma lexer to use.
        fileType, pos := extractFileTypePos(err)
        pos.Filename = name
@@ -165,6 +190,23 @@ func NewFileErrorFromPos(err error, pos text.Position) FileError {
 
 }
 
+func NewFileErrorFromFileInErr(err error, fs afero.Fs, linematcher LineMatcherFn) FileError {
+       fe := NewFileError(err)
+       pos := fe.Position()
+       if pos.Filename == "" {
+               return fe
+       }
+
+       f, realFilename, err2 := openFile(pos.Filename, fs)
+       if err2 != nil {
+               return fe
+       }
+
+       pos.Filename = realFilename
+       defer f.Close()
+       return fe.UpdateContent(f, linematcher)
+}
+
 func NewFileErrorFromFileInPos(err error, pos text.Position, fs afero.Fs, linematcher LineMatcherFn) FileError {
        if err == nil {
                panic("err is nil")
@@ -185,10 +227,10 @@ func NewFileErrorFromFile(err error, filename string, fs afero.Fs, linematcher L
        }
        f, realFilename, err2 := openFile(filename, fs)
        if err2 != nil {
-               return NewFileError(err, realFilename)
+               return NewFileErrorFromName(err, realFilename)
        }
        defer f.Close()
-       return NewFileError(err, realFilename).UpdateContent(f, linematcher)
+       return NewFileErrorFromName(err, realFilename).UpdateContent(f, linematcher)
 }
 
 func openFile(filename string, fs afero.Fs) (afero.File, string, error) {
@@ -223,8 +265,15 @@ func Cause(err error) error {
 
 func extractFileTypePos(err error) (string, text.Position) {
        err = Cause(err)
+
        var fileType string
 
+       // LibSass, DartSass
+       if pos := extractPosition(err); pos.LineNumber > 0 || pos.Offset > 0 {
+               _, fileType = paths.FileAndExtNoDelimiter(pos.Filename)
+               return fileType, pos
+       }
+
        // Default to line 1 col 1 if we don't find any better.
        pos := text.Position{
                Offset:       -1,
@@ -259,6 +308,10 @@ func extractFileTypePos(err error) (string, text.Position) {
                }
        }
 
+       if fileType == "" && pos.Filename != "" {
+               _, fileType = paths.FileAndExtNoDelimiter(pos.Filename)
+       }
+
        return fileType, pos
 }
 
@@ -322,3 +375,20 @@ func exctractLineNumberAndColumnNumber(e error) (int, int) {
 
        return -1, -1
 }
+
+func extractPosition(e error) (pos text.Position) {
+       switch v := e.(type) {
+       case godartsass.SassError:
+               span := v.Span
+               start := span.Start
+               filename, _ := paths.UrlToFilename(span.Url)
+               pos.Filename = filename
+               pos.Offset = start.Offset
+               pos.ColumnNumber = start.Column
+       case libsasserrors.Error:
+               pos.Filename = v.File
+               pos.LineNumber = v.Line
+               pos.ColumnNumber = v.Column
+       }
+       return
+}
index 04baadf1694cf73d9fc143e9c13997859e333cbc..0b260a2550e3649f833dd9d961afe89192ce3bb5 100644 (file)
@@ -30,7 +30,7 @@ func TestNewFileError(t *testing.T) {
 
        c := qt.New(t)
 
-       fe := NewFileError(errors.New("bar"), "foo.html")
+       fe := NewFileErrorFromName(errors.New("bar"), "foo.html")
        c.Assert(fe.Error(), qt.Equals, `"foo.html:1:1": bar`)
 
        lines := ""
@@ -70,7 +70,7 @@ func TestNewFileErrorExtractFromMessage(t *testing.T) {
                {errors.New(`execute of template failed: template: index.html:2:5: executing "index.html" at <partial "foo.html" .>: error calling partial: "/layouts/partials/foo.html:3:6": execute of template failed: template: partials/foo.html:3:6: executing "partials/foo.html" at <.ThisDoesNotExist>: can't evaluate field ThisDoesNotExist in type *hugolib.pageStat`), 0, 2, 5},
        } {
 
-               got := NewFileError(test.in, "test.txt")
+               got := NewFileErrorFromName(test.in, "test.txt")
 
                errMsg := qt.Commentf("[%d][%T]", i, got)
 
index 193b0cd0e043dfe4261e658675b696b8969f0757..c538d8f2cbe20264748420010d1a7916189baf01 100644 (file)
@@ -17,6 +17,7 @@ import (
        "fmt"
        "net/url"
        "path"
+       "path/filepath"
        "strings"
 )
 
@@ -152,3 +153,29 @@ func Uglify(in string) string {
        // /section/name.html -> /section/name.html
        return path.Clean(in)
 }
+
+// UrlToFilename converts the URL s to a filename.
+// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
+func UrlToFilename(s string) (string, bool) {
+       u, err := url.ParseRequestURI(s)
+
+       if err != nil {
+               return filepath.FromSlash(s), false
+       }
+
+       p := u.Path
+
+       if p == "" {
+               p, _ = url.QueryUnescape(u.Opaque)
+               return filepath.FromSlash(p), true
+       }
+
+       p = filepath.FromSlash(p)
+
+       if u.Host != "" {
+               // C:\data\file.txt
+               p = strings.ToUpper(u.Host) + ":" + p
+       }
+
+       return p, true
+}
diff --git a/go.mod b/go.mod
index 0bd9cf233e2552b9d3aac59c5d3906f6281f7f6c..45fc1ad612227eab4dc388a20ac1acf7ec79835e 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
        github.com/bep/gitmap v1.1.2
        github.com/bep/goat v0.5.0
        github.com/bep/godartsass v0.14.0
-       github.com/bep/golibsass v1.0.0
+       github.com/bep/golibsass v1.1.0
        github.com/bep/gowebp v0.1.0
        github.com/bep/overlayfs v0.6.0
        github.com/bep/tmc v0.5.1
diff --git a/go.sum b/go.sum
index d40817e3616d31956c426cb8b23182435a98ce1f..506f3244291f9a9a96ad8244af43390c451e2771 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -175,6 +175,8 @@ github.com/bep/godartsass v0.14.0 h1:pPb6XkpyDEppS+wK0veh7OXDQc4xzOJI9Qcjb743UeQ
 github.com/bep/godartsass v0.14.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8=
 github.com/bep/golibsass v1.0.0 h1:gNguBMSDi5yZEZzVZP70YpuFQE3qogJIGUlrVILTmOw=
 github.com/bep/golibsass v1.0.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
+github.com/bep/golibsass v1.1.0 h1:pjtXr00IJZZaOdfryNa9wARTB3Q0BmxC3/V1KNcgyTw=
+github.com/bep/golibsass v1.1.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
 github.com/bep/gowebp v0.1.0 h1:4/iQpfnxHyXs3x/aTxMMdOpLEQQhFmF6G7EieWPTQyo=
 github.com/bep/gowebp v0.1.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
 github.com/bep/overlayfs v0.6.0 h1:sgLcq/qtIzbaQNl2TldGXOkHvqeZB025sPvHOQL+DYo=
index c3fb9fffc1c50fe9cb0cea38e10b2d03b8728440..0ec202f89b44ec7b0ec71e8c9da585bbe4cd6c9f 100644 (file)
@@ -168,8 +168,9 @@ func (s *IntegrationTestBuilder) destinationExists(filename string) bool {
        return b
 }
 
-func (s *IntegrationTestBuilder) AssertIsFileError(err error) {
+func (s *IntegrationTestBuilder) AssertIsFileError(err error) herrors.FileError {
        s.Assert(err, qt.ErrorAs, new(herrors.FileError))
+       return herrors.UnwrapFileError(err)
 }
 
 func (s *IntegrationTestBuilder) AssertRenderCountContent(count int) {
index e6dd8fc2eaf84d7cc113250dbd863ec725f3e4ba..19d9695326af6e1b74246fa904cb250b3a70b669 100644 (file)
@@ -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(err, p.File().Filename()).UpdatePosition(pos)
+       return herrors.NewFileErrorFromName(err, p.File().Filename()).UpdatePosition(pos)
 }
 
 func (p *pageState) pathOrTitle() string {
index 41121700d441e8dfab19c8d8f572493539e758d8..45d5f1ca8dd7710b2fac27cc942b18296b21a4d0 100644 (file)
@@ -298,7 +298,7 @@ func renderShortcode(
                        var err error
                        tmpl, err = s.TextTmpl().Parse(templName, templStr)
                        if err != nil {
-                               fe := herrors.NewFileError(err, p.File().Filename())
+                               fe := herrors.NewFileErrorFromName(err, p.File().Filename())
                                pos := fe.Position()
                                pos.LineNumber += p.posOffset(sc.pos).LineNumber
                                fe = fe.UpdatePosition(pos)
@@ -391,7 +391,7 @@ func renderShortcode(
        result, err := renderShortcodeWithPage(s.Tmpl(), tmpl, data)
 
        if err != nil && sc.isInline {
-               fe := herrors.NewFileError(err, p.File().Filename())
+               fe := herrors.NewFileErrorFromName(err, p.File().Filename())
                pos := fe.Position()
                pos.LineNumber += p.posOffset(sc.pos).LineNumber
                fe = fe.UpdatePosition(pos)
index fbeea71d45a1a5fd5248a6592e32053fb985ff14..d9d334567f9423b53771ef453f8ee9bff88cb7ce 100644 (file)
@@ -138,6 +138,6 @@ func errWithFileContext(inerr error, r source.File) error {
        }
        defer f.Close()
 
-       return herrors.NewFileError(inerr, realFilename).UpdateContent(f, nil)
+       return herrors.NewFileErrorFromName(inerr, realFilename).UpdateContent(f, nil)
 
 }
index 42e2ee65af4d4500b4ad228d342b5af32c76bf7c..7a76b8eea0f1a694588100338cd06efd73c4869f 100644 (file)
@@ -260,7 +260,7 @@ func (d Decoder) unmarshalORG(data []byte, v any) error {
 }
 
 func toFileError(f Format, data []byte, err error) error {
-       return herrors.NewFileError(err, fmt.Sprintf("_stream.%s", f)).UpdateContent(bytes.NewReader(data), nil)
+       return herrors.NewFileErrorFromName(err, fmt.Sprintf("_stream.%s", f)).UpdateContent(bytes.NewReader(data), nil)
 }
 
 // stringifyMapKeys recurses into in and changes all instances of
index 34dae4666567f287a9eed5ec34f8aa064fbe78e7..23e28f6754d87f192eb6750bf7189ae952d2f46f 100644 (file)
@@ -165,7 +165,7 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
 
                        if err == nil {
                                fe := herrors.
-                                       NewFileError(errors.New(errorMessage), path).
+                                       NewFileErrorFromName(errors.New(errorMessage), path).
                                        UpdatePosition(text.Position{Offset: -1, LineNumber: loc.Line, ColumnNumber: loc.Column}).
                                        UpdateContent(f, nil)
 
index 69f0964d0a9dc9f8d209ba64d03417bdfee55b5d..fdebcc52c37e98bd20303be2cb3388406eaf86bb 100644 (file)
@@ -183,6 +183,6 @@ func TestTransformPostCSSImporSkipInlineImportsNotFound(t *testing.T) {
                        TxtarString:     files,
                }).Build()
 
-       s.AssertFileContent("public/css/styles.css", filepath.FromSlash(`@import "components/doesnotexist.css";`))
+       s.AssertFileContent("public/css/styles.css", `@import "components/doesnotexist.css";`)
 
 }
index 325dc1ec2061d6e8ea0b034d040244d80d1136b7..ed08f7202fe63bf9db1df7c08f58b7b1de224adc 100644 (file)
@@ -314,7 +314,7 @@ func (imp *importResolver) importRecursive(
                                        LineNumber:   offset + 1,
                                        ColumnNumber: column + 1,
                                }
-                               return 0, "", herrors.NewFileErrorFromFileInPos(fmt.Errorf("failed to resolve CSS @import %q", filename), pos, imp.fs, nil)
+                               return 0, "", herrors.NewFileErrorFromFileInPos(fmt.Errorf("failed to resolve CSS @import \"%s\"", filename), pos, imp.fs, nil)
                        }
 
                        i--
@@ -421,7 +421,7 @@ func (imp *importResolver) toFileError(output string) error {
        }
        defer f.Close()
 
-       ferr := herrors.NewFileError(inErr, realFilename)
+       ferr := herrors.NewFileErrorFromName(inErr, realFilename)
        pos := ferr.Position()
        pos.LineNumber = file.Offset + 1
        return ferr.UpdatePosition(pos).UpdateContent(f, nil)
index fb794f4e8c76698bbee0c8ff8ebcad8fe0490d23..7c3a7ecbab7efa90c2f1c8a2b40715b0c2f027a7 100644 (file)
@@ -20,7 +20,9 @@ import (
        "io"
        "strings"
 
+       "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/helpers"
+       "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/hugolib/filesystems"
        "github.com/gohugoio/hugo/resources"
        "github.com/gohugoio/hugo/resources/resource"
@@ -33,6 +35,10 @@ import (
 // used as part of the cache key.
 const transformationName = "tocss-dart"
 
+// See https://github.com/sass/dart-sass-embedded/issues/24
+// Note: This prefix must be all lower case.
+const dartSassStdinPrefix = "hugostdin:"
+
 func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) (*Client, error) {
        if !Supports() {
                return &Client{dartSassNotAvailable: true}, nil
@@ -44,7 +50,7 @@ func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) (*Client, error)
 
        transpiler, err := godartsass.Start(godartsass.Options{
                LogEventHandler: func(event godartsass.LogEvent) {
-                       message := strings.ReplaceAll(event.Message, stdinPrefix, "")
+                       message := strings.ReplaceAll(event.Message, dartSassStdinPrefix, "")
                        switch event.Type {
                        case godartsass.LogEventTypeDebug:
                                // Log as Info for now, we may adjust this if it gets too chatty.
@@ -94,7 +100,7 @@ func (c *Client) toCSS(args godartsass.Args, src io.Reader) (godartsass.Result,
                if err.Error() == "unexpected EOF" {
                        return res, fmt.Errorf("got unexpected EOF when executing %q. The user running hugo must have read and execute permissions on this program. With execute permissions only, this error is thrown.", dartSassEmbeddedBinaryName)
                }
-               return res, err
+               return res, herrors.NewFileErrorFromFileInErr(err, hugofs.Os, herrors.OffsetMatcher)
        }
 
        return res, err
index a1ac1d59f13f21977a3ba2d9697535550e19d3c7..c127057a5a40e3b42d85a3924cb49b4000ddb040 100644 (file)
 package dartsass_test
 
 import (
+       "strings"
        "testing"
 
+       qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/hugolib"
        "github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
        jww "github.com/spf13/jwalterweatherman"
@@ -196,3 +198,76 @@ T1: {{ $r.Content }}
        b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:1:0: bar`)
 
 }
+
+func TestTransformErrors(t *testing.T) {
+       if !dartsass.Supports() {
+               t.Skip()
+       }
+
+       c := qt.New(t)
+
+       const filesTemplate = `
+-- config.toml --
+-- assets/scss/components/_foo.scss --
+/* comment line 1 */
+$foocolor: #ccc;
+
+foo {
+       color: $foocolor;
+}
+-- assets/scss/main.scss --
+/* comment line 1 */
+/* comment line 2 */
+@import "components/foo";
+/* comment line 4 */
+
+  $maincolor: #eee;
+
+body {
+       color: $maincolor;
+}
+
+-- layouts/index.html --
+{{ $cssOpts := dict "transpiler" "dartsass" }}
+{{ $r := resources.Get "scss/main.scss" |  toCSS $cssOpts  | minify  }}
+T1: {{ $r.Content }}
+
+       `
+
+       c.Run("error in main", func(c *qt.C) {
+               b, err := hugolib.NewIntegrationTestBuilder(
+                       hugolib.IntegrationTestConfig{
+                               T:           c,
+                               TxtarString: strings.Replace(filesTemplate, "$maincolor: #eee;", "$maincolor #eee;", 1),
+                               NeedsOsFS:   true,
+                       }).BuildE()
+
+               b.Assert(err, qt.IsNotNil)
+               b.Assert(err.Error(), qt.Contains, `main.scss:8:13":`)
+               b.Assert(err.Error(), qt.Contains, `: expected ":".`)
+               fe := b.AssertIsFileError(err)
+               b.Assert(fe.ErrorContext(), qt.IsNotNil)
+               b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"  $maincolor #eee;", "", "body {", "\tcolor: $maincolor;", "}"})
+               b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss")
+
+       })
+
+       c.Run("error in import", func(c *qt.C) {
+               b, err := hugolib.NewIntegrationTestBuilder(
+                       hugolib.IntegrationTestConfig{
+                               T:           c,
+                               TxtarString: strings.Replace(filesTemplate, "$foocolor: #ccc;", "$foocolor #ccc;", 1),
+                               NeedsOsFS:   true,
+                       }).BuildE()
+
+               b.Assert(err, qt.IsNotNil)
+               b.Assert(err.Error(), qt.Contains, `_foo.scss:2:10":`)
+               b.Assert(err.Error(), qt.Contains, `: expected ":".`)
+               fe := b.AssertIsFileError(err)
+               b.Assert(fe.ErrorContext(), qt.IsNotNil)
+               b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 1 */", "$foocolor #ccc;", "", "foo {"})
+               b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss")
+
+       })
+
+}
index ba3517e265531337959f5cf94f886c6d9364cb47..9d17d3bccf83985843b5ca6d6a308c21e5f6c920 100644 (file)
@@ -16,13 +16,12 @@ package dartsass
 import (
        "fmt"
        "io"
-       "net/url"
        "path"
        "path/filepath"
        "strings"
 
-       "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/common/hexec"
+       "github.com/gohugoio/hugo/common/paths"
        "github.com/gohugoio/hugo/htesting"
        "github.com/gohugoio/hugo/media"
 
@@ -38,9 +37,6 @@ import (
 )
 
 const (
-       // See https://github.com/sass/dart-sass-embedded/issues/24
-       // Note: This prefix must be all lower case.
-       stdinPrefix                = "hugostdin:"
        dartSassEmbeddedBinaryName = "dart-sass-embedded"
 )
 
@@ -76,7 +72,7 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
        }
 
        baseDir := path.Dir(ctx.SourcePath)
-       filename := stdinPrefix
+       filename := dartSassStdinPrefix
 
        if ctx.SourcePath != "" {
                filename += t.c.sfs.RealFilename(ctx.SourcePath)
@@ -108,26 +104,6 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
 
        res, err := t.c.toCSS(args, ctx.From)
        if err != nil {
-               if sassErr, ok := err.(godartsass.SassError); ok {
-                       start := sassErr.Span.Start
-                       context := strings.TrimSpace(sassErr.Span.Context)
-                       filename, _ := urlToFilename(sassErr.Span.Url)
-                       if strings.HasPrefix(filename, stdinPrefix) {
-                               filename = filename[len(stdinPrefix):]
-                       }
-
-                       offsetMatcher := func(m herrors.LineMatcher) int {
-                               if m.Offset+len(m.Line) >= start.Offset && strings.Contains(m.Line, context) {
-                                       // We found the line, but return 0 to signal that we want to determine
-                                       // the column from the error.
-                                       return 0
-                               }
-                               return -1
-                       }
-
-                       return herrors.NewFileErrorFromFile(sassErr, filename, hugofs.Os, offsetMatcher)
-
-               }
                return err
        }
 
@@ -154,7 +130,7 @@ type importResolver struct {
 }
 
 func (t importResolver) CanonicalizeURL(url string) (string, error) {
-       filePath, isURL := urlToFilename(url)
+       filePath, isURL := paths.UrlToFilename(url)
        var prevDir string
        var pathDir string
        if isURL {
@@ -200,23 +176,7 @@ func (t importResolver) CanonicalizeURL(url string) (string, error) {
 }
 
 func (t importResolver) Load(url string) (string, error) {
-       filename, _ := urlToFilename(url)
+       filename, _ := paths.UrlToFilename(url)
        b, err := afero.ReadFile(hugofs.Os, filename)
        return string(b), err
 }
-
-// TODO(bep) add tests
-func urlToFilename(urls string) (string, bool) {
-       u, err := url.ParseRequestURI(urls)
-       if err != nil {
-               return filepath.FromSlash(urls), false
-       }
-       p := filepath.FromSlash(u.Path)
-
-       if u.Host != "" {
-               // C:\data\file.txt
-               p = strings.ToUpper(u.Host) + ":" + p
-       }
-
-       return p, true
-}
index c9b347c4cf41ed50578ff59e61235481362104eb..bfee39499d16e9dd69acc2913da66abf6cdffad0 100644 (file)
@@ -47,6 +47,7 @@ func (c *Client) ToCSS(res resources.ResourceTransformer, opts Options) (resourc
        }
 
        return res.Transform(&toCSSTransformation{c: c, options: internalOptions})
+
 }
 
 type toCSSTransformation struct {
index cbc7e192bc2f839ffa96528b861e65c4cb922782..72c0fd988e94fe229106e40371ca190ed4e39725 100644 (file)
@@ -14,6 +14,8 @@
 package scss_test
 
 import (
+       "path/filepath"
+       "strings"
        "testing"
 
        qt "github.com/frankban/quicktest"
@@ -133,7 +135,7 @@ moo {
 -- config.toml --
 theme = 'mytheme'
 -- layouts/index.html --
-{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo" ) "transpiler" "dartsass" ) }}
+{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo" ) ) }}
 {{ $r := resources.Get "scss/main.scss" |  toCSS $cssOpts  | minify  }}
 T1: {{ $r.Content }}
 -- themes/mytheme/assets/scss/components/_boo.scss --
@@ -171,3 +173,75 @@ zoo {
 
        b.AssertFileContent("public/index.html", `T1: moo{color:#ccc}boo{color:green}zoo{color:pink}`)
 }
+
+func TestTransformErrors(t *testing.T) {
+       if !scss.Supports() {
+               t.Skip()
+       }
+
+       c := qt.New(t)
+
+       const filesTemplate = `
+-- config.toml --
+theme = 'mytheme'
+-- assets/scss/components/_foo.scss --
+/* comment line 1 */
+$foocolor: #ccc;
+
+foo {
+       color: $foocolor;
+}
+-- themes/mytheme/assets/scss/main.scss --
+/* comment line 1 */
+/* comment line 2 */
+@import "components/foo";
+/* comment line 4 */
+
+$maincolor: #eee;
+
+body {
+       color: $maincolor;
+}
+
+-- layouts/index.html --
+{{ $cssOpts := dict }}
+{{ $r := resources.Get "scss/main.scss" |  toCSS $cssOpts  | minify  }}
+T1: {{ $r.Content }}
+
+       `
+
+       c.Run("error in main", func(c *qt.C) {
+               b, err := hugolib.NewIntegrationTestBuilder(
+                       hugolib.IntegrationTestConfig{
+                               T:           c,
+                               TxtarString: strings.Replace(filesTemplate, "$maincolor: #eee;", "$maincolor #eee;", 1),
+                               NeedsOsFS:   true,
+                       }).BuildE()
+
+               b.Assert(err, qt.IsNotNil)
+               b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`themes/mytheme/assets/scss/main.scss:6:1": expected ':' after $maincolor in assignment statement`))
+               fe := b.AssertIsFileError(err)
+               b.Assert(fe.ErrorContext(), qt.IsNotNil)
+               b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 4 */", "", "$maincolor #eee;", "", "body {"})
+               b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss")
+
+       })
+
+       c.Run("error in import", func(c *qt.C) {
+               b, err := hugolib.NewIntegrationTestBuilder(
+                       hugolib.IntegrationTestConfig{
+                               T:           c,
+                               TxtarString: strings.Replace(filesTemplate, "$foocolor: #ccc;", "$foocolor #ccc;", 1),
+                               NeedsOsFS:   true,
+                       }).BuildE()
+
+               b.Assert(err, qt.IsNotNil)
+               b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`assets/scss/components/_foo.scss:2:1": expected ':' after $foocolor in assignment statement`))
+               fe := b.AssertIsFileError(err)
+               b.Assert(fe.ErrorContext(), qt.IsNotNil)
+               b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 1 */", "$foocolor #ccc;", "", "foo {"})
+               b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss")
+
+       })
+
+}
index f9f4786f572bd14bd60a01cd700234327138702a..57ac16711550fea3d38cda6bc9b20e803149d294 100644 (file)
@@ -20,10 +20,13 @@ import (
        "fmt"
        "io"
        "path"
+
        "path/filepath"
        "strings"
 
        "github.com/bep/golibsass/libsass"
+       "github.com/bep/golibsass/libsass/libsasserrors"
+       "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/media"
@@ -136,7 +139,14 @@ func (t *toCSSTransformation) Transform(ctx *resources.ResourceTransformationCtx
 
        res, err := t.c.toCSS(options.to, ctx.To, ctx.From)
        if err != nil {
-               return err
+               if sasserr, ok := err.(libsasserrors.Error); ok {
+                       if sasserr.File == "stdin" && ctx.SourcePath != "" {
+                               sasserr.File = t.c.sfs.RealFilename(ctx.SourcePath)
+                               err = sasserr
+                       }
+               }
+               return herrors.NewFileErrorFromFileInErr(err, hugofs.Os, nil)
+
        }
 
        if options.from.EnableSourceMap && res.SourceMapContent != "" {
@@ -180,7 +190,7 @@ func (c *Client) toCSS(options libsass.Options, dst io.Writer, src io.Reader) (l
 
        res, err = transpiler.Execute(in)
        if err != nil {
-               return res, fmt.Errorf("SCSS processing failed: %w", err)
+               return res, err
        }
 
        out := res.CSS
index 42a324e9c17973663279466f28402b3c1d650754..c79605cbceb22197c49f76e21e22e746c53eeea9 100644 (file)
@@ -554,7 +554,7 @@ func (t *templateHandler) addFileContext(templ tpl.Template, inerr error) error
                }
                defer f.Close()
 
-               fe := herrors.NewFileError(inErr, info.realFilename)
+               fe := herrors.NewFileErrorFromName(inErr, info.realFilename)
                fe.UpdateContent(f, lineMatcher)
 
                if !fe.ErrorContext().Position.IsValid() {
index a444899aa9dc1d7081e8c5ec1b3c0eb5db61b45d..ac8a72df5e043d8f4af5af8e7958edb04acb33b5 100644 (file)
@@ -53,7 +53,7 @@ func (t templateInfo) resolveType() templateType {
 
 func (info templateInfo) errWithFileContext(what string, err error) error {
        err = fmt.Errorf(what+": %w", err)
-       fe := herrors.NewFileError(err, info.realFilename)
+       fe := herrors.NewFileErrorFromName(err, info.realFilename)
        f, err := info.fs.Open(info.filename)
        if err != nil {
                return err
index 31d1e804b6439559332b213c00b37dd46572af39..a5f042d96662f08feba423f421432136e18720fa 100644 (file)
@@ -115,7 +115,7 @@ func (c *Chain) Apply(to io.Writer, from io.Reader) error {
                                _, _ = io.Copy(tempfile, fb.from)
                                return herrors.NewFileErrorFromFile(err, filename, hugofs.Os, nil)
                        }
-                       return herrors.NewFileError(err, filename).UpdateContent(fb.from, nil)
+                       return herrors.NewFileErrorFromName(err, filename).UpdateContent(fb.from, nil)
 
                }
        }