@@ -295,7 +295,11 @@ loop:
295
295
return rules
296
296
}
297
297
298
- func (p * parser ) parseListOfDeclarations () (list []css_ast.Rule ) {
298
+ type listOfDeclarationsOpts struct {
299
+ canInlineNoOpNesting bool
300
+ }
301
+
302
+ func (p * parser ) parseListOfDeclarations (opts listOfDeclarationsOpts ) (list []css_ast.Rule ) {
299
303
list = []css_ast.Rule {}
300
304
foundNesting := false
301
305
@@ -310,29 +314,35 @@ func (p *parser) parseListOfDeclarations() (list []css_ast.Rule) {
310
314
list = p .mangleRules (list , false /* isTopLevel */ )
311
315
312
316
// Pull out all unnecessarily-nested declarations and stick them at the end
313
- // "a { & { b: c } d: e }" => "a { d: e; b: c; }"
314
- if foundNesting {
315
- var inlineDecls []css_ast.Rule
316
- n := 0
317
- for _ , rule := range list {
318
- if rule , ok := rule .Data .(* css_ast.RSelector ); ok && len (rule .Selectors ) == 1 {
319
- if sel := rule .Selectors [0 ]; len (sel .Selectors ) == 1 && sel .Selectors [0 ].IsSingleAmpersand () {
320
- inlineDecls = append (inlineDecls , rule .Rules ... )
321
- continue
317
+ if opts .canInlineNoOpNesting {
318
+ // "a { & { x: y } }" => "a { x: y }"
319
+ // "a { & { b: c } d: e }" => "a { d: e; b: c }"
320
+ if foundNesting {
321
+ var inlineDecls []css_ast.Rule
322
+ n := 0
323
+ for _ , rule := range list {
324
+ if rule , ok := rule .Data .(* css_ast.RSelector ); ok && len (rule .Selectors ) == 1 {
325
+ if sel := rule .Selectors [0 ]; len (sel .Selectors ) == 1 && sel .Selectors [0 ].IsSingleAmpersand () {
326
+ inlineDecls = append (inlineDecls , rule .Rules ... )
327
+ continue
328
+ }
322
329
}
330
+ list [n ] = rule
331
+ n ++
323
332
}
324
- list [n ] = rule
325
- n ++
333
+ list = append (list [:n ], inlineDecls ... )
326
334
}
327
- list = append (list [:n ], inlineDecls ... )
335
+ } else {
336
+ // "a, b::before { & { x: y } }" => "a, b::before { & { x: y } }"
328
337
}
329
338
}
330
339
return
331
340
332
341
case css_lexer .TAtKeyword :
333
342
p .maybeWarnAboutNesting (p .current ().Range )
334
343
list = append (list , p .parseAtRule (atRuleContext {
335
- isDeclarationList : true ,
344
+ isDeclarationList : true ,
345
+ canInlineNoOpNesting : opts .canInlineNoOpNesting ,
336
346
}))
337
347
338
348
// Reference: https://drafts.csswg.org/css-nesting-1/
@@ -848,11 +858,12 @@ const (
848
858
)
849
859
850
860
type atRuleContext struct {
851
- afterLoc logger.Loc
852
- charsetValidity atRuleValidity
853
- importValidity atRuleValidity
854
- isDeclarationList bool
855
- isTopLevel bool
861
+ afterLoc logger.Loc
862
+ charsetValidity atRuleValidity
863
+ importValidity atRuleValidity
864
+ canInlineNoOpNesting bool
865
+ isDeclarationList bool
866
+ isTopLevel bool
856
867
}
857
868
858
869
func (p * parser ) parseAtRule (context atRuleContext ) css_ast.Rule {
@@ -1006,7 +1017,7 @@ abortRuleParser:
1006
1017
case css_lexer .TOpenBrace :
1007
1018
blockMatchingLoc := p .current ().Range .Loc
1008
1019
p .advance ()
1009
- rules := p .parseListOfDeclarations ()
1020
+ rules := p .parseListOfDeclarations (listOfDeclarationsOpts {} )
1010
1021
p .expectWithMatchingLoc (css_lexer .TCloseBrace , blockMatchingLoc )
1011
1022
1012
1023
// "@keyframes { from {} to { color: red } }" => "@keyframes { to { color: red } }"
@@ -1108,7 +1119,9 @@ abortRuleParser:
1108
1119
if len (names ) <= 1 && p .eat (css_lexer .TOpenBrace ) {
1109
1120
var rules []css_ast.Rule
1110
1121
if context .isDeclarationList {
1111
- rules = p .parseListOfDeclarations ()
1122
+ rules = p .parseListOfDeclarations (listOfDeclarationsOpts {
1123
+ canInlineNoOpNesting : context .canInlineNoOpNesting ,
1124
+ })
1112
1125
} else {
1113
1126
rules = p .parseListOfRules (ruleContext {
1114
1127
parseSelectors : true ,
@@ -1210,7 +1223,7 @@ prelude:
1210
1223
// Parse known rules whose blocks always consist of declarations
1211
1224
matchingLoc := p .current ().Range .Loc
1212
1225
p .expect (css_lexer .TOpenBrace )
1213
- rules := p .parseListOfDeclarations ()
1226
+ rules := p .parseListOfDeclarations (listOfDeclarationsOpts {} )
1214
1227
p .expectWithMatchingLoc (css_lexer .TCloseBrace , matchingLoc )
1215
1228
return css_ast.Rule {Loc : atRange .Loc , Data : & css_ast.RKnownAt {AtToken : atToken , Prelude : prelude , Rules : rules }}
1216
1229
@@ -1220,7 +1233,9 @@ prelude:
1220
1233
p .expect (css_lexer .TOpenBrace )
1221
1234
var rules []css_ast.Rule
1222
1235
if context .isDeclarationList {
1223
- rules = p .parseListOfDeclarations ()
1236
+ rules = p .parseListOfDeclarations (listOfDeclarationsOpts {
1237
+ canInlineNoOpNesting : context .canInlineNoOpNesting ,
1238
+ })
1224
1239
} else {
1225
1240
rules = p .parseListOfRules (ruleContext {
1226
1241
parseSelectors : true ,
@@ -1624,10 +1639,26 @@ func mangleNumber(t string) (string, bool) {
1624
1639
func (p * parser ) parseSelectorRuleFrom (preludeStart int , isTopLevel bool , opts parseSelectorOpts ) css_ast.Rule {
1625
1640
// Try parsing the prelude as a selector list
1626
1641
if list , ok := p .parseSelectorList (opts ); ok {
1642
+ canInlineNoOpNesting := true
1643
+ for _ , sel := range list {
1644
+ // We cannot transform the CSS "a, b::before { & { color: red } }" into
1645
+ // "a, b::before { color: red }" because it's basically equivalent to
1646
+ // ":is(a, b::before) { color: red }" which only applies to "a", not to
1647
+ // "b::before" because pseudo-elements are not valid within :is():
1648
+ // https://www.w3.org/TR/selectors-4/#matches-pseudo. This restriction
1649
+ // may be relaxed in the future, but this restriction hash shipped so
1650
+ // we're stuck with it: https://github.com/w3c/csswg-drafts/issues/7433.
1651
+ if sel .UsesPseudoElement () {
1652
+ canInlineNoOpNesting = false
1653
+ break
1654
+ }
1655
+ }
1627
1656
selector := css_ast.RSelector {Selectors : list }
1628
1657
matchingLoc := p .current ().Range .Loc
1629
1658
if p .expect (css_lexer .TOpenBrace ) {
1630
- selector .Rules = p .parseListOfDeclarations ()
1659
+ selector .Rules = p .parseListOfDeclarations (listOfDeclarationsOpts {
1660
+ canInlineNoOpNesting : canInlineNoOpNesting ,
1661
+ })
1631
1662
p .expectWithMatchingLoc (css_lexer .TCloseBrace , matchingLoc )
1632
1663
return css_ast.Rule {Loc : p .tokens [preludeStart ].Range .Loc , Data : & selector }
1633
1664
}
@@ -1671,7 +1702,7 @@ loop:
1671
1702
1672
1703
matchingLoc := p .current ().Range .Loc
1673
1704
if p .eat (css_lexer .TOpenBrace ) {
1674
- qualified .Rules = p .parseListOfDeclarations ()
1705
+ qualified .Rules = p .parseListOfDeclarations (listOfDeclarationsOpts {} )
1675
1706
p .expectWithMatchingLoc (css_lexer .TCloseBrace , matchingLoc )
1676
1707
} else if ! opts .isAlreadyInvalid {
1677
1708
p .expect (css_lexer .TOpenBrace )
0 commit comments