Skip to content

Commit

Permalink
css: fix parser recovery for rules without blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw authored and zhusjfaker committed Mar 28, 2022
1 parent 481288f commit 03fdb1a
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 6 deletions.
32 changes: 26 additions & 6 deletions internal/css_parser/css_parser.go
Expand Up @@ -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
Expand Down Expand Up @@ -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}))
}
}

Expand Down Expand Up @@ -704,6 +705,7 @@ type atRuleContext struct {
importValidity atRuleValidity
isDeclarationList bool
allowNesting bool
isTopLevel bool
}

func (p *parser) parseAtRule(context atRuleContext) css_ast.Rule {
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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:
Expand All @@ -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()
}
Expand All @@ -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)
}

Expand Down
1 change: 1 addition & 0 deletions internal/css_parser/css_parser_selector.go
Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions internal/css_parser/css_parser_test.go
Expand Up @@ -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 }
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 03fdb1a

Please sign in to comment.