diff --git a/internal/css_parser/css_parser.go b/internal/css_parser/css_parser.go index 06e490ca4d5..5af12a1e7cf 100644 --- a/internal/css_parser/css_parser.go +++ b/internal/css_parser/css_parser.go @@ -181,6 +181,7 @@ func (p *parser) parseListOfRules(context ruleContext) []css_ast.Rule { if context.isTopLevel { atRuleContext.charsetValidity = atRuleValid atRuleContext.importValidity = atRuleValid + atRuleContext.isTopLevel = true } rules := []css_ast.Rule{} didFindAtImport := false @@ -271,9 +272,9 @@ loop: } if context.parseSelectors { - rules = append(rules, p.parseSelectorRuleFrom(p.index, parseSelectorOpts{})) + rules = append(rules, p.parseSelectorRuleFrom(p.index, parseSelectorOpts{isTopLevel: context.isTopLevel})) } else { - rules = append(rules, p.parseQualifiedRuleFrom(p.index, false /* isAlreadyInvalid */)) + rules = append(rules, p.parseQualifiedRuleFrom(p.index, parseQualifiedRuleOpts{isTopLevel: context.isTopLevel})) } } @@ -704,6 +705,7 @@ type atRuleContext struct { importValidity atRuleValidity isDeclarationList bool allowNesting bool + isTopLevel bool } func (p *parser) parseAtRule(context atRuleContext) css_ast.Rule { @@ -919,7 +921,11 @@ abortRuleParser: p.eat(css_lexer.TWhitespace) if kind := p.current().Kind; kind != css_lexer.TSemicolon && kind != css_lexer.TOpenBrace && kind != css_lexer.TCloseBrace && kind != css_lexer.TEndOfFile { - return p.parseSelectorRuleFrom(preludeStart-1, parseSelectorOpts{atNestRange: atRange, allowNesting: context.allowNesting}) + return p.parseSelectorRuleFrom(preludeStart-1, parseSelectorOpts{ + atNestRange: atRange, + allowNesting: context.allowNesting, + isTopLevel: context.isTopLevel, + }) } case "layer": @@ -1473,10 +1479,18 @@ func (p *parser) parseSelectorRuleFrom(preludeStart int, opts parseSelectorOpts) } // Otherwise, parse a generic qualified rule - return p.parseQualifiedRuleFrom(preludeStart, true /* isAlreadyInvalid */) + return p.parseQualifiedRuleFrom(preludeStart, parseQualifiedRuleOpts{ + isAlreadyInvalid: true, + isTopLevel: opts.isTopLevel, + }) } -func (p *parser) parseQualifiedRuleFrom(preludeStart int, isAlreadyInvalid bool) css_ast.Rule { +type parseQualifiedRuleOpts struct { + isAlreadyInvalid bool + isTopLevel bool +} + +func (p *parser) parseQualifiedRuleFrom(preludeStart int, opts parseQualifiedRuleOpts) css_ast.Rule { preludeLoc := p.tokens[preludeStart].Range.Loc loop: @@ -1485,6 +1499,12 @@ loop: case css_lexer.TOpenBrace, css_lexer.TEndOfFile: break loop + case css_lexer.TCloseBrace: + if !opts.isTopLevel { + break loop + } + p.parseComponentValue() + default: p.parseComponentValue() } @@ -1497,7 +1517,7 @@ loop: if p.eat(css_lexer.TOpenBrace) { qualified.Rules = p.parseListOfDeclarations() p.expect(css_lexer.TCloseBrace) - } else if !isAlreadyInvalid { + } else if !opts.isAlreadyInvalid { p.expect(css_lexer.TOpenBrace) } diff --git a/internal/css_parser/css_parser_selector.go b/internal/css_parser/css_parser_selector.go index 43b14f1cad7..8acb298e10c 100644 --- a/internal/css_parser/css_parser_selector.go +++ b/internal/css_parser/css_parser_selector.go @@ -51,6 +51,7 @@ func (p *parser) parseSelectorList(opts parseSelectorOpts) (list []css_ast.Compl type parseSelectorOpts struct { atNestRange logger.Range allowNesting bool + isTopLevel bool } func (p *parser) parseComplexSelector(opts parseSelectorOpts) (result css_ast.ComplexSelector, ok bool, hasNestPrefix bool) { diff --git a/internal/css_parser/css_parser_test.go b/internal/css_parser/css_parser_test.go index 7629160fc87..01d641e1c83 100644 --- a/internal/css_parser/css_parser_test.go +++ b/internal/css_parser/css_parser_test.go @@ -775,6 +775,8 @@ func TestAtRule(t *testing.T) { expectPrinted(t, "@-moz-document url-prefix() { h1 { color: green } }", "@-moz-document url-prefix() {\n h1 {\n color: green;\n }\n}\n") + expectPrinted(t, "@media foo { bar }", "@media foo {\n bar {\n }\n}\n") + // https://www.w3.org/TR/css-page-3/#syntax-page-selector expectPrinted(t, ` @page :first { margin: 0 } @@ -1100,6 +1102,9 @@ func TestEmptyRule(t *testing.T) { expectPrintedMangleMinify(t, "@page { color: red; @top-left {} }", "@page{color:red}") expectPrintedMangleMinify(t, "@keyframes test { from {} to { color: red } }", "@keyframes test{to{color:red}}") expectPrintedMangleMinify(t, "@keyframes test { from { color: red } to {} }", "@keyframes test{0%{color:red}}") + + expectPrinted(t, "invalid", "invalid {\n}\n") + expectPrinted(t, "invalid }", "invalid } {\n}\n") } func TestMarginAndPaddingAndInset(t *testing.T) {