Skip to content

Commit e76780c

Browse files
committed
css: further changes to css nesting syntax
1 parent 71f4a5a commit e76780c

File tree

4 files changed

+143
-104
lines changed

4 files changed

+143
-104
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
## Unreleased
44

5+
* Update how CSS nesting is parsed again
6+
7+
CSS nesting syntax has been changed again, and esbuild has been updated to match. Type selectors may now be used with CSS nesting:
8+
9+
```css
10+
.foo {
11+
div {
12+
color: red;
13+
}
14+
}
15+
```
16+
17+
Previously this was disallowed in the CSS specification because it's ambiguous whether an identifier is a declaration or a nested rule starting with a type selector without requiring unbounded lookahead in the parser. It has now been allowed because the CSS working group has decided that requiring unbounded lookahead is acceptible after all.
18+
19+
Note that this change means esbuild no longer considers any existing browser to support CSS nesting since none of the existing browsers support this new syntax. CSS nesting will now always be transformed when targeting a browser. This situation will change in the future as browsers add support for this new syntax.
20+
521
* Make renamed CSS names unique across entry points ([#3295](https://github.com/evanw/esbuild/issues/3295))
622

723
Previously esbuild's generated names for local names in CSS were only unique within a given entry point (or across all entry points when code splitting was enabled). That meant that building multiple entry points with esbuild could result in local names being renamed to the same identifier even when those entry points were built simultaneously within a single esbuild API call. This problem was especially likely to happen with minification enabled. With this release, esbuild will now avoid renaming local names from two separate entry points to the same name if those entry points were built with a single esbuild API call, even when code splitting is disabled.

internal/css_parser/css_parser.go

Lines changed: 95 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ type parser struct {
3636
enclosingLayer []string
3737
anonLayerCount int
3838
index int
39-
end int
4039
legalCommentIndex int
4140
inSelectorSubtree int
4241
prevError logger.Loc
@@ -138,7 +137,6 @@ func Parse(log logger.Log, source logger.Source, options Options) css_ast.AST {
138137
globalScope: make(map[string]ast.LocRef),
139138
makeLocalSymbols: options.symbolMode == symbolModeLocal,
140139
}
141-
p.end = len(p.tokens)
142140
rules := p.parseListOfRules(ruleContext{
143141
isTopLevel: true,
144142
parseSelectors: true,
@@ -196,21 +194,15 @@ func (p *parser) computeCharacterFrequency() *ast.CharFreq {
196194
}
197195

198196
func (p *parser) advance() {
199-
if p.index < p.end {
197+
if p.index < len(p.tokens) {
200198
p.index++
201199
}
202200
}
203201

204202
func (p *parser) at(index int) css_lexer.Token {
205-
if index < p.end {
203+
if index < len(p.tokens) {
206204
return p.tokens[index]
207205
}
208-
if p.end < len(p.tokens) {
209-
return css_lexer.Token{
210-
Kind: css_lexer.TEndOfFile,
211-
Range: logger.Range{Loc: p.tokens[p.end].Range.Loc},
212-
}
213-
}
214206
return css_lexer.Token{
215207
Kind: css_lexer.TEndOfFile,
216208
Range: logger.Range{Loc: logger.Loc{Start: int32(len(p.source.Contents))}},
@@ -475,6 +467,18 @@ loop:
475467
atRuleContext.importValidity = atRuleInvalidAfter
476468
}
477469

470+
// Note: CSS recently changed to parse and discard declarations
471+
// here instead of treating them as the start of a qualified rule.
472+
// See also: https://github.com/w3c/csswg-drafts/issues/8834
473+
if !context.isTopLevel {
474+
if scan, index := p.scanForEndOfRule(); scan == endOfRuleSemicolon {
475+
tokens := p.convertTokens(p.tokens[p.index:index])
476+
rules = append(rules, css_ast.Rule{Loc: p.current().Range.Loc, Data: &css_ast.RBadDeclaration{Tokens: tokens}})
477+
p.index = index + 1
478+
continue
479+
}
480+
}
481+
478482
var rule css_ast.Rule
479483
if context.parseSelectors {
480484
rule = p.parseSelectorRule(context.isTopLevel, parseSelectorOpts{})
@@ -550,41 +554,33 @@ func (p *parser) parseListOfDeclarations(opts listOfDeclarationsOpts) (list []cs
550554
}))
551555

552556
// Reference: https://drafts.csswg.org/css-nesting-1/
553-
case css_lexer.TDelimAmpersand,
554-
css_lexer.TDelimDot,
555-
css_lexer.THash,
556-
css_lexer.TColon,
557-
css_lexer.TOpenBracket,
558-
css_lexer.TDelimAsterisk,
559-
css_lexer.TDelimBar,
560-
css_lexer.TDelimPlus,
561-
css_lexer.TDelimGreaterThan,
562-
css_lexer.TDelimTilde:
563-
p.nestingIsPresent = true
564-
foundNesting = true
565-
rule := p.parseSelectorRule(false, parseSelectorOpts{
566-
isDeclarationContext: true,
567-
composesContext: opts.composesContext,
568-
})
557+
default:
558+
if scan, _ := p.scanForEndOfRule(); scan == endOfRuleOpenBrace {
559+
p.nestingIsPresent = true
560+
foundNesting = true
561+
rule := p.parseSelectorRule(false, parseSelectorOpts{
562+
isDeclarationContext: true,
563+
composesContext: opts.composesContext,
564+
})
569565

570-
// If this rule was a single ":global" or ":local", inline it here. This
571-
// is handled differently than a bare "&" with normal CSS nesting because
572-
// that would be inlined at the end of the parent rule's body instead,
573-
// which is probably unexpected (e.g. it would trip people up when trying
574-
// to write rules in a specific order).
575-
if sel, ok := rule.Data.(*css_ast.RSelector); ok && len(sel.Selectors) == 1 {
576-
if first := sel.Selectors[0]; len(first.Selectors) == 1 {
577-
if first := first.Selectors[0]; first.WasEmptyFromLocalOrGlobal && first.IsSingleAmpersand() {
578-
list = append(list, sel.Rules...)
579-
continue
566+
// If this rule was a single ":global" or ":local", inline it here. This
567+
// is handled differently than a bare "&" with normal CSS nesting because
568+
// that would be inlined at the end of the parent rule's body instead,
569+
// which is probably unexpected (e.g. it would trip people up when trying
570+
// to write rules in a specific order).
571+
if sel, ok := rule.Data.(*css_ast.RSelector); ok && len(sel.Selectors) == 1 {
572+
if first := sel.Selectors[0]; len(first.Selectors) == 1 {
573+
if first := first.Selectors[0]; first.WasEmptyFromLocalOrGlobal && first.IsSingleAmpersand() {
574+
list = append(list, sel.Rules...)
575+
continue
576+
}
580577
}
581578
}
582-
}
583-
584-
list = append(list, rule)
585579

586-
default:
587-
list = append(list, p.parseDeclaration())
580+
list = append(list, rule)
581+
} else {
582+
list = append(list, p.parseDeclaration())
583+
}
588584
}
589585
}
590586
}
@@ -2168,6 +2164,56 @@ loop:
21682164
return css_ast.Rule{Loc: preludeLoc, Data: &qualified}
21692165
}
21702166

2167+
type endOfRuleScan uint8
2168+
2169+
const (
2170+
endOfRuleUnknown endOfRuleScan = iota
2171+
endOfRuleSemicolon
2172+
endOfRuleOpenBrace
2173+
)
2174+
2175+
// Note: This was a late change to the CSS nesting syntax.
2176+
// See also: https://github.com/w3c/csswg-drafts/issues/7961
2177+
func (p *parser) scanForEndOfRule() (endOfRuleScan, int) {
2178+
var initialStack [4]css_lexer.T
2179+
stack := initialStack[:0]
2180+
2181+
for i, t := range p.tokens[p.index:] {
2182+
switch t.Kind {
2183+
case css_lexer.TSemicolon:
2184+
if len(stack) == 0 {
2185+
return endOfRuleSemicolon, p.index + i
2186+
}
2187+
2188+
case css_lexer.TFunction, css_lexer.TOpenParen:
2189+
stack = append(stack, css_lexer.TCloseParen)
2190+
2191+
case css_lexer.TOpenBracket:
2192+
stack = append(stack, css_lexer.TCloseBracket)
2193+
2194+
case css_lexer.TOpenBrace:
2195+
if len(stack) == 0 {
2196+
return endOfRuleOpenBrace, p.index + i
2197+
}
2198+
stack = append(stack, css_lexer.TCloseBrace)
2199+
2200+
case css_lexer.TCloseParen, css_lexer.TCloseBracket:
2201+
if n := len(stack); n > 0 && t.Kind == stack[n-1] {
2202+
stack = stack[:n-1]
2203+
}
2204+
2205+
case css_lexer.TCloseBrace:
2206+
if n := len(stack); n > 0 && t.Kind == stack[n-1] {
2207+
stack = stack[:n-1]
2208+
} else {
2209+
return endOfRuleUnknown, -1
2210+
}
2211+
}
2212+
}
2213+
2214+
return endOfRuleUnknown, -1
2215+
}
2216+
21712217
func (p *parser) parseDeclaration() css_ast.Rule {
21722218
// Parse the key
21732219
keyStart := p.index
@@ -2181,17 +2227,12 @@ func (p *parser) parseDeclaration() css_ast.Rule {
21812227

21822228
// Parse the value
21832229
valueStart := p.index
2184-
foundOpenBrace := false
21852230
stop:
21862231
for {
21872232
switch p.current().Kind {
21882233
case css_lexer.TEndOfFile, css_lexer.TSemicolon, css_lexer.TCloseBrace:
21892234
break stop
21902235

2191-
case css_lexer.TOpenBrace:
2192-
foundOpenBrace = true
2193-
p.parseComponentValue()
2194-
21952236
default:
21962237
p.parseComponentValue()
21972238
}
@@ -2200,32 +2241,14 @@ stop:
22002241
// Stop now if this is not a valid declaration
22012242
if !ok {
22022243
if keyIsIdent {
2203-
if foundOpenBrace {
2204-
// If we encountered a "{", assume this is someone trying to make a nested style rule
2205-
if keyRange.Loc.Start > p.prevError.Start {
2206-
p.prevError.Start = keyRange.Loc.Start
2207-
key := p.tokens[keyStart].DecodedText(p.source.Contents)
2208-
data := p.tracker.MsgData(keyRange, fmt.Sprintf("A nested style rule cannot start with %q because it looks like the start of a declaration", key))
2209-
data.Location.Suggestion = fmt.Sprintf(":is(%s)", p.source.TextForRange(keyRange))
2210-
p.log.AddMsgID(logger.MsgID_CSS_CSSSyntaxError, logger.Msg{
2211-
Kind: logger.Warning,
2212-
Data: data,
2213-
Notes: []logger.MsgData{{
2214-
Text: "To start a nested style rule with an identifier, you need to wrap the " +
2215-
"identifier in \":is(...)\" to prevent the rule from being parsed as a declaration."}},
2216-
})
2217-
}
2218-
} else {
2219-
// Otherwise, show a generic error about a missing ":"
2220-
if end := keyRange.End(); end > p.prevError.Start {
2221-
p.prevError.Start = end
2222-
data := p.tracker.MsgData(logger.Range{Loc: logger.Loc{Start: end}}, "Expected \":\"")
2223-
data.Location.Suggestion = ":"
2224-
p.log.AddMsgID(logger.MsgID_CSS_CSSSyntaxError, logger.Msg{
2225-
Kind: logger.Warning,
2226-
Data: data,
2227-
})
2228-
}
2244+
if end := keyRange.End(); end > p.prevError.Start {
2245+
p.prevError.Start = end
2246+
data := p.tracker.MsgData(logger.Range{Loc: logger.Loc{Start: end}}, "Expected \":\"")
2247+
data.Location.Suggestion = ":"
2248+
p.log.AddMsgID(logger.MsgID_CSS_CSSSyntaxError, logger.Msg{
2249+
Kind: logger.Warning,
2250+
Data: data,
2251+
})
22292252
}
22302253
}
22312254

internal/css_parser/css_parser_test.go

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -600,16 +600,12 @@ func TestDeclaration(t *testing.T) {
600600
expectPrinted(t, ".decl { a: b; }", ".decl {\n a: b;\n}\n", "")
601601
expectPrinted(t, ".decl { a: b; c: d }", ".decl {\n a: b;\n c: d;\n}\n", "")
602602
expectPrinted(t, ".decl { a: b; c: d; }", ".decl {\n a: b;\n c: d;\n}\n", "")
603-
expectPrinted(t, ".decl { a { b: c; } }", ".decl {\n a { b: c; };\n}\n",
604-
"<stdin>: WARNING: A nested style rule cannot start with \"a\" because it looks like the start of a declaration\n"+
605-
"NOTE: To start a nested style rule with an identifier, you need to wrap the identifier in \":is(...)\" to prevent the rule from being parsed as a declaration.\n")
603+
expectPrinted(t, ".decl { a { b: c; } }", ".decl {\n a {\n b: c;\n }\n}\n", "")
606604
expectPrinted(t, ".decl { & a { b: c; } }", ".decl {\n & a {\n b: c;\n }\n}\n", "")
607605

608606
// See http://browserhacks.com/
609-
expectPrinted(t, ".selector { (;property: value;); }", ".selector {\n (;property: value;);\n}\n",
610-
"<stdin>: WARNING: Expected identifier but found \"(\"\n")
611-
expectPrinted(t, ".selector { [;property: value;]; }", ".selector {\n [;property: value;];\n}\n",
612-
"<stdin>: WARNING: Expected identifier but found \";\"\n") // Note: This now overlaps with CSS nesting syntax
607+
expectPrinted(t, ".selector { (;property: value;); }", ".selector {\n (;property: value;);\n}\n", "<stdin>: WARNING: Expected identifier but found \"(\"\n")
608+
expectPrinted(t, ".selector { [;property: value;]; }", ".selector {\n [;property: value;];\n}\n", "<stdin>: WARNING: Expected identifier but found \"[\"\n")
613609
expectPrinted(t, ".selector, {}", ".selector, {\n}\n", "<stdin>: WARNING: Unexpected \"{\"\n")
614610
expectPrinted(t, ".selector\\ {}", ".selector\\ {\n}\n", "")
615611
expectPrinted(t, ".selector { property: value\\9; }", ".selector {\n property: value\\\t;\n}\n", "")
@@ -791,10 +787,8 @@ func TestNestedSelector(t *testing.T) {
791787
expectPrinted(t, "a { >b {} }", "a {\n > b {\n }\n}\n", "")
792788
expectPrinted(t, "a { +b {} }", "a {\n + b {\n }\n}\n", "")
793789
expectPrinted(t, "a { ~b {} }", "a {\n ~ b {\n }\n}\n", "")
794-
expectPrinted(t, "a { b {} }", "a {\n b {};\n}\n",
795-
"<stdin>: WARNING: A nested style rule cannot start with \"b\" because it looks like the start of a declaration\n"+
796-
"NOTE: To start a nested style rule with an identifier, you need to wrap the identifier in \":is(...)\" to prevent the rule from being parsed as a declaration.\n")
797-
expectPrinted(t, "a { b() {} }", "a {\n b() {};\n}\n", "<stdin>: WARNING: Expected identifier but found \"b(\"\n")
790+
expectPrinted(t, "a { b {} }", "a {\n b {\n }\n}\n", "")
791+
expectPrinted(t, "a { b() {} }", "a {\n b() {\n }\n}\n", "<stdin>: WARNING: Unexpected \"b(\"\n")
798792

799793
// Note: CSS nesting no longer requires each complex selector to contain "&"
800794
expectPrinted(t, "a { & b, c {} }", "a {\n & b,\n c {\n }\n}\n", "")
@@ -1033,20 +1027,23 @@ func TestNestedSelector(t *testing.T) {
10331027
"@supports (selector(&)) {\n .card:hover {\n color: red;\n }\n}\n", "")
10341028
expectPrintedLower(t, "html { @layer base { color: blue; @layer support { & body { color: red } } } }",
10351029
"@layer base {\n html {\n color: blue;\n }\n @layer support {\n html body {\n color: red;\n }\n }\n}\n", "")
1030+
1031+
// https://github.com/w3c/csswg-drafts/issues/7961#issuecomment-1549874958
1032+
expectPrinted(t, "@media screen { a { x: y } x: y; b { x: y } }", "@media screen {\n a {\n x: y;\n }\n x: y;\n b {\n x: y;\n }\n}\n", "")
1033+
expectPrinted(t, ":root { @media screen { a { x: y } x: y; b { x: y } } }", ":root {\n @media screen {\n a {\n x: y;\n }\n x: y;\n b {\n x: y;\n }\n }\n}\n", "")
10361034
}
10371035

10381036
func TestBadQualifiedRules(t *testing.T) {
10391037
expectPrinted(t, "$bad: rule;", "$bad: rule; {\n}\n", "<stdin>: WARNING: Unexpected \"$\"\n")
10401038
expectPrinted(t, "$bad: rule; div { color: red }", "$bad: rule; div {\n color: red;\n}\n", "<stdin>: WARNING: Unexpected \"$\"\n")
10411039
expectPrinted(t, "$bad { color: red }", "$bad {\n color: red;\n}\n", "<stdin>: WARNING: Unexpected \"$\"\n")
1042-
expectPrinted(t, "a { div.major { color: blue } color: red }", "a {\n div.major { color: blue } color: red;\n}\n",
1043-
"<stdin>: WARNING: A nested style rule cannot start with \"div\" because it looks like the start of a declaration\n"+
1044-
"NOTE: To start a nested style rule with an identifier, you need to wrap the identifier in \":is(...)\" to prevent the rule from being parsed as a declaration.\n")
1045-
expectPrinted(t, "a { div:hover { color: blue } color: red }", "a {\n div: hover { color: blue } color: red;\n}\n", "")
1046-
expectPrinted(t, "a { div:hover { color: blue }; color: red }", "a {\n div: hover { color: blue };\n color: red;\n}\n", "")
1047-
expectPrinted(t, "a { div:hover { color: blue } ; color: red }", "a {\n div: hover { color: blue };\n color: red;\n}\n", "")
1048-
expectPrinted(t, "! { x: {} }", "! {\n x: {};\n}\n", "<stdin>: WARNING: Unexpected \"!\"\n")
1049-
expectPrinted(t, "a { *width: 100%; height: 1px }", "a {\n *width: 100%;\n height: 1px;\n}\n", "<stdin>: WARNING: Unexpected \"width\"\n")
1040+
expectPrinted(t, "a { div.major { color: blue } color: red }", "a {\n div.major {\n color: blue;\n }\n color: red;\n}\n", "")
1041+
expectPrinted(t, "a { div:hover { color: blue } color: red }", "a {\n div:hover {\n color: blue;\n }\n color: red;\n}\n", "")
1042+
expectPrinted(t, "a { div:hover { color: blue }; color: red }", "a {\n div:hover {\n color: blue;\n }\n color: red;\n}\n", "")
1043+
expectPrinted(t, "a { div:hover { color: blue } ; color: red }", "a {\n div:hover {\n color: blue;\n }\n color: red;\n}\n", "")
1044+
expectPrinted(t, "! { x: y; }", "! {\n x: y;\n}\n", "<stdin>: WARNING: Unexpected \"!\"\n")
1045+
expectPrinted(t, "! { x: {} }", "! {\n x: {\n }\n}\n", "<stdin>: WARNING: Unexpected \"!\"\n<stdin>: WARNING: Expected identifier but found whitespace\n")
1046+
expectPrinted(t, "a { *width: 100%; height: 1px }", "a {\n *width: 100%;\n height: 1px;\n}\n", "<stdin>: WARNING: Expected identifier but found \"*\"\n")
10501047
expectPrinted(t, "a { garbage; height: 1px }", "a {\n garbage;\n height: 1px;\n}\n", "<stdin>: WARNING: Expected \":\"\n")
10511048
expectPrinted(t, "a { !; height: 1px }", "a {\n !;\n height: 1px;\n}\n", "<stdin>: WARNING: Expected identifier but found \"!\"\n")
10521049
}
@@ -2251,7 +2248,8 @@ func TestParseErrorRecovery(t *testing.T) {
22512248
expectPrinted(t, "x { y: z", "x {\n y: z;\n}\n", "<stdin>: WARNING: Expected \"}\" to go with \"{\"\n<stdin>: NOTE: The unbalanced \"{\" is here:\n")
22522249
expectPrinted(t, "x { y: (", "x {\n y: ();\n}\n", "<stdin>: WARNING: Expected \")\" to go with \"(\"\n<stdin>: NOTE: The unbalanced \"(\" is here:\n")
22532250
expectPrinted(t, "x { y: [", "x {\n y: [];\n}\n", "<stdin>: WARNING: Expected \"]\" to go with \"[\"\n<stdin>: NOTE: The unbalanced \"[\" is here:\n")
2254-
expectPrinted(t, "x { y: {", "x {\n y: {};\n}\n", "<stdin>: WARNING: Expected \"}\" to go with \"{\"\n<stdin>: NOTE: The unbalanced \"{\" is here:\n")
2251+
expectPrinted(t, "x { y: {", "x {\n y: {\n }\n}\n",
2252+
"<stdin>: WARNING: Expected identifier but found whitespace\n<stdin>: WARNING: Expected \"}\" to go with \"{\"\n<stdin>: NOTE: The unbalanced \"{\" is here:\n")
22552253
expectPrinted(t, "x { y: z(", "x {\n y: z();\n}\n", "<stdin>: WARNING: Expected \")\" to go with \"(\"\n<stdin>: NOTE: The unbalanced \"(\" is here:\n")
22562254
expectPrinted(t, "x { y: z(abc", "x {\n y: z(abc);\n}\n", "<stdin>: WARNING: Expected \")\" to go with \"(\"\n<stdin>: NOTE: The unbalanced \"(\" is here:\n")
22572255
expectPrinted(t, "x { y: url(", "x {\n y: url();\n}\n",

internal/css_printer/css_printer_test.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,9 @@ func TestBadQualifiedRules(t *testing.T) {
155155
expectPrinted(t, ";", "; {\n}\n")
156156
expectPrinted(t, "$bad: rule;", "$bad: rule; {\n}\n")
157157
expectPrinted(t, "a {}; b {};", "a {\n}\n; b {\n}\n; {\n}\n")
158-
expectPrinted(t, "a { div.major { color: blue } color: red }", "a {\n div.major { color: blue } color: red;\n}\n")
159-
expectPrinted(t, "a { div:hover { color: blue } color: red }", "a {\n div: hover { color: blue } color: red;\n}\n")
160-
expectPrinted(t, "a { div:hover { color: blue }; color: red }", "a {\n div: hover { color: blue };\n color: red;\n}\n")
158+
expectPrinted(t, "a { div.major { color: blue } color: red }", "a {\n div.major {\n color: blue;\n }\n color: red;\n}\n")
159+
expectPrinted(t, "a { div:hover { color: blue } color: red }", "a {\n div:hover {\n color: blue;\n }\n color: red;\n}\n")
160+
expectPrinted(t, "a { div:hover { color: blue }; color: red }", "a {\n div:hover {\n color: blue;\n }\n color: red;\n}\n")
161161

162162
expectPrinted(t, "$bad{ color: red }", "$bad {\n color: red;\n}\n")
163163
expectPrinted(t, "$bad { color: red }", "$bad {\n color: red;\n}\n")
@@ -276,17 +276,19 @@ func TestVerbatimWhitespace(t *testing.T) {
276276
expectPrintedMinify(t, "* { --x:[y ]; }", "*{--x:[y ]}")
277277
expectPrintedMinify(t, "* { --x:[ y]; }", "*{--x:[ y]}")
278278

279-
expectPrinted(t, "* { --x:{y}; }", "* {\n --x:{y};\n}\n")
280-
expectPrinted(t, "* { --x:{y} ; }", "* {\n --x:{y} ;\n}\n")
281-
expectPrinted(t, "* { --x: {y}; }", "* {\n --x: {y};\n}\n")
282-
expectPrinted(t, "* { --x:{y }; }", "* {\n --x:{y };\n}\n")
283-
expectPrinted(t, "* { --x:{ y}; }", "* {\n --x:{ y};\n}\n")
279+
// Note: These cases now behave like qualified rules
280+
expectPrinted(t, "* { --x:{y}; }", "* {\n --x: {\n y;\n }\n}\n")
281+
expectPrinted(t, "* { --x:{y} ; }", "* {\n --x: {\n y;\n }\n}\n")
282+
expectPrinted(t, "* { --x: {y}; }", "* {\n --x: {\n y;\n }\n}\n")
283+
expectPrinted(t, "* { --x:{y }; }", "* {\n --x: {\n y;\n }\n}\n")
284+
expectPrinted(t, "* { --x:{ y}; }", "* {\n --x: {\n y;\n }\n}\n")
284285

286+
// Note: These cases now behave like qualified rules
285287
expectPrintedMinify(t, "* { --x:{y}; }", "*{--x:{y}}")
286-
expectPrintedMinify(t, "* { --x:{y} ; }", "*{--x:{y} }")
287-
expectPrintedMinify(t, "* { --x: {y}; }", "*{--x: {y}}")
288-
expectPrintedMinify(t, "* { --x:{y }; }", "*{--x:{y }}")
289-
expectPrintedMinify(t, "* { --x:{ y}; }", "*{--x:{ y}}")
288+
expectPrintedMinify(t, "* { --x:{y} ; }", "*{--x:{y}}")
289+
expectPrintedMinify(t, "* { --x: {y}; }", "*{--x:{y}}")
290+
expectPrintedMinify(t, "* { --x:{y }; }", "*{--x:{y}}")
291+
expectPrintedMinify(t, "* { --x:{ y}; }", "*{--x:{y}}")
290292

291293
expectPrintedMinify(t, "@supports ( --x : y , z ) { a { color: red; } }", "@supports ( --x : y , z ){a{color:red}}")
292294
expectPrintedMinify(t, "@supports ( --x : ) { a { color: red; } }", "@supports ( --x : ){a{color:red}}")

0 commit comments

Comments
 (0)