mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-27 22:09:53 +02:00
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
This commit is contained in:
@@ -165,7 +165,7 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
|
||||
|
||||
if err == nil {
|
||||
fe := herrors.
|
||||
NewFileError(path, errors.New(errorMessage)).
|
||||
NewFileError(errors.New(errorMessage), path).
|
||||
UpdatePosition(text.Position{Offset: -1, LineNumber: loc.Line, ColumnNumber: loc.Column}).
|
||||
UpdateContent(f, nil)
|
||||
|
||||
|
@@ -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";`))
|
||||
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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,
|
||||
)
|
||||
|
||||
|
@@ -125,7 +125,7 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
|
||||
return -1
|
||||
}
|
||||
|
||||
return herrors.NewFileErrorFromFile(sassErr, filename, filename, hugofs.Os, offsetMatcher)
|
||||
return herrors.NewFileErrorFromFile(sassErr, filename, hugofs.Os, offsetMatcher)
|
||||
|
||||
}
|
||||
return err
|
||||
|
Reference in New Issue
Block a user