diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-05-14 15:51:04 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-05-15 20:25:25 +0200 |
commit | 4b189d8fd93d3fa326b31d451d5594c917e6c714 (patch) | |
tree | c3515876c474caab2df6d0a34291bcc3ca8e75df /resources/resource_transformers/postcss | |
parent | c2fa0a33209da7cfd9a09f8fc528415578a9edf8 (diff) |
postcss: Fix import error handling
Note that we will now fail if `inlineImports` is enabled and we cannot resolve an import.
You can work around this by either:
* Use url imports or imports with media queries.
* Set `skipInlineImportsNotFound=true` in the options
Also get the argument order in the different NewFileError* funcs in line.
Fixes #9895
Diffstat (limited to 'resources/resource_transformers/postcss')
3 files changed, 84 insertions, 15 deletions
diff --git a/resources/resource_transformers/postcss/integration_test.go b/resources/resource_transformers/postcss/integration_test.go index 4101818be..69f0964d0 100644 --- a/resources/resource_transformers/postcss/integration_test.go +++ b/resources/resource_transformers/postcss/integration_test.go @@ -48,7 +48,7 @@ class-in-b { @tailwind base; @tailwind components; @tailwind utilities; -@import "components/all.css"; + @import "components/all.css"; h1 { @apply text-2xl font-bold; } @@ -140,3 +140,49 @@ func TestTransformPostCSSError(t *testing.T) { c.Assert(err.Error(), qt.Contains, "a.css:4:2") } + +// #9895 +func TestTransformPostCSSImportError(t *testing.T) { + if !htesting.IsCI() { + t.Skip("Skip long running test when running locally") + } + + c := qt.New(t) + + s, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + NeedsNpmInstall: true, + LogLevel: jww.LevelInfo, + TxtarString: strings.ReplaceAll(postCSSIntegrationTestFiles, `@import "components/all.css";`, `@import "components/doesnotexist.css";`), + }).BuildE() + + s.AssertIsFileError(err) + c.Assert(err.Error(), qt.Contains, "styles.css:4:3") + c.Assert(err.Error(), qt.Contains, filepath.FromSlash(`failed to resolve CSS @import "css/components/doesnotexist.css"`)) + +} + +func TestTransformPostCSSImporSkipInlineImportsNotFound(t *testing.T) { + if !htesting.IsCI() { + t.Skip("Skip long running test when running locally") + } + + c := qt.New(t) + + files := strings.ReplaceAll(postCSSIntegrationTestFiles, `@import "components/all.css";`, `@import "components/doesnotexist.css";`) + files = strings.ReplaceAll(files, `{{ $options := dict "inlineImports" true }}`, `{{ $options := dict "inlineImports" true "skipInlineImportsNotFound" true }}`) + + s := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: c, + NeedsOsFS: true, + NeedsNpmInstall: true, + LogLevel: jww.LevelInfo, + TxtarString: files, + }).Build() + + s.AssertFileContent("public/css/styles.css", filepath.FromSlash(`@import "components/doesnotexist.css";`)) + +} diff --git a/resources/resource_transformers/postcss/postcss.go b/resources/resource_transformers/postcss/postcss.go index a5c86df6f..325dc1ec2 100644 --- a/resources/resource_transformers/postcss/postcss.go +++ b/resources/resource_transformers/postcss/postcss.go @@ -28,6 +28,7 @@ import ( "github.com/gohugoio/hugo/common/collections" "github.com/gohugoio/hugo/common/hexec" + "github.com/gohugoio/hugo/common/text" "github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/common/hugo" @@ -49,9 +50,10 @@ import ( const importIdentifier = "@import" -var cssSyntaxErrorRe = regexp.MustCompile(`> (\d+) \|`) - -var shouldImportRe = regexp.MustCompile(`^@import ["'].*["'];?\s*(/\*.*\*/)?$`) +var ( + cssSyntaxErrorRe = regexp.MustCompile(`> (\d+) \|`) + shouldImportRe = regexp.MustCompile(`^@import ["'].*["'];?\s*(/\*.*\*/)?$`) +) // New creates a new Client with the given specification. func New(rs *resources.Spec) *Client { @@ -100,6 +102,12 @@ type Options struct { // so you can have @import anywhere in the file. InlineImports bool + // When InlineImports is enabled, we fail the build if an import cannot be resolved. + // You can enable this to allow the build to continue and leave the import statement in place. + // Note that the inline importer does not process url location or imports with media queries, + // so those will be left as-is even without enabling this option. + SkipInlineImportsNotFound bool + // Options for when not using a config file Use string // List of postcss plugins to use Parser string // Custom postcss parser @@ -204,6 +212,7 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC imp := newImportResolver( ctx.From, ctx.InPath, + t.options, t.rs.Assets.Fs, t.rs.Logger, ) @@ -239,6 +248,7 @@ type fileOffset struct { type importResolver struct { r io.Reader inPath string + opts Options contentSeen map[string]bool linemap map[int]fileOffset @@ -246,12 +256,13 @@ type importResolver struct { logger loggers.Logger } -func newImportResolver(r io.Reader, inPath string, fs afero.Fs, logger loggers.Logger) *importResolver { +func newImportResolver(r io.Reader, inPath string, opts Options, fs afero.Fs, logger loggers.Logger) *importResolver { return &importResolver{ r: r, inPath: inPath, fs: fs, logger: logger, linemap: make(map[int]fileOffset), contentSeen: make(map[string]bool), + opts: opts, } } @@ -282,21 +293,32 @@ func (imp *importResolver) importRecursive( i := 0 for offset, line := range lines { i++ - line = strings.TrimSpace(line) + lineTrimmed := strings.TrimSpace(line) + column := strings.Index(line, lineTrimmed) + line = lineTrimmed if !imp.shouldImport(line) { trackLine(i, offset, line) } else { - i-- path := strings.Trim(strings.TrimPrefix(line, importIdentifier), " \"';") filename := filepath.Join(basePath, path) importContent, hash := imp.contentHash(filename) + if importContent == nil { - trackLine(i, offset, "ERROR") - imp.logger.Warnf("postcss: Failed to resolve CSS @import in %q for path %q", inPath, filename) - continue + if imp.opts.SkipInlineImportsNotFound { + trackLine(i, offset, line) + continue + } + pos := text.Position{ + Filename: inPath, + LineNumber: offset + 1, + ColumnNumber: column + 1, + } + return 0, "", herrors.NewFileErrorFromFileInPos(fmt.Errorf("failed to resolve CSS @import %q", filename), pos, imp.fs, nil) } + i-- + if imp.contentSeen[hash] { i++ // Just replace the line with an empty string. @@ -399,7 +421,7 @@ func (imp *importResolver) toFileError(output string) error { } defer f.Close() - ferr := herrors.NewFileError(realFilename, inErr) + ferr := herrors.NewFileError(inErr, realFilename) pos := ferr.Position() pos.LineNumber = file.Offset + 1 return ferr.UpdatePosition(pos).UpdateContent(f, nil) diff --git a/resources/resource_transformers/postcss/postcss_test.go b/resources/resource_transformers/postcss/postcss_test.go index 4548bca98..f5d49300a 100644 --- a/resources/resource_transformers/postcss/postcss_test.go +++ b/resources/resource_transformers/postcss/postcss_test.go @@ -60,6 +60,7 @@ func TestShouldImport(t *testing.T) { {input: `@import 'navigation.css';`, expect: true}, {input: `@import url("navigation.css");`, expect: false}, {input: `@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,400i,800,800i&display=swap');`, expect: false}, + {input: `@import "printstyle.css" print;`, expect: false}, } { c.Assert(imp.shouldImport(test.input), qt.Equals, test.expect) } @@ -88,12 +89,12 @@ A_STYLE2 @import "b.css"; LOCAL_STYLE @import "c.css"; -@import "e.css"; -@import "missing.css";`) +@import "e.css";`) imp := newImportResolver( mainStyles, "styles.css", + Options{}, fs, loggers.NewErrorLogger(), ) @@ -108,8 +109,7 @@ C_STYLE A_STYLE1 A_STYLE2 LOCAL_STYLE -E_STYLE -@import "missing.css";`) +E_STYLE`) dline := imp.linemap[3] c.Assert(dline, qt.DeepEquals, fileOffset{ @@ -151,6 +151,7 @@ LOCAL_STYLE imp := newImportResolver( strings.NewReader(mainStyles), "styles.css", + Options{}, fs, logger, ) |