From f4cc689b85b2d6cd7c2ff5ad63e6f76961d9832b Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Mon, 3 Feb 2025 21:41:19 -0500 Subject: [PATCH] fix additional comment on #3877 about `:has(> &)` --- internal/css_ast/css_ast.go | 33 ++++++++++++++++---------- internal/css_parser/css_parser_test.go | 2 ++ scripts/browser/index.html | 16 +++++++++++++ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/internal/css_ast/css_ast.go b/internal/css_ast/css_ast.go index e9082aaec45..69d02e3f37d 100644 --- a/internal/css_ast/css_ast.go +++ b/internal/css_ast/css_ast.go @@ -796,23 +796,32 @@ func (s ComplexSelector) Clone() ComplexSelector { return clone } -func (sel ComplexSelector) IsRelative() bool { - if sel.Selectors[0].Combinator.Byte == 0 { - for _, inner := range sel.Selectors { - if len(inner.NestingSelectorLocs) > 0 { - return false - } - for _, ss := range inner.SubclassSelectors { - if pseudo, ok := ss.Data.(*SSPseudoClassWithSelectorList); ok { - for _, nested := range pseudo.Selectors { - if !nested.IsRelative() { - return false - } +func (sel ComplexSelector) ContainsNestingCombinator() bool { + for _, inner := range sel.Selectors { + if len(inner.NestingSelectorLocs) > 0 { + return true + } + for _, ss := range inner.SubclassSelectors { + if pseudo, ok := ss.Data.(*SSPseudoClassWithSelectorList); ok { + for _, nested := range pseudo.Selectors { + if nested.ContainsNestingCombinator() { + return true } } } } } + return false +} + +func (sel ComplexSelector) IsRelative() bool { + // https://www.w3.org/TR/css-nesting-1/#syntax + // "If a selector in the does not start with a + // combinator but does contain the nesting selector, it is interpreted + // as a non-relative selector." + if sel.Selectors[0].Combinator.Byte == 0 && sel.ContainsNestingCombinator() { + return false + } return true } diff --git a/internal/css_parser/css_parser_test.go b/internal/css_parser/css_parser_test.go index 607663de2ed..58de307f406 100644 --- a/internal/css_parser/css_parser_test.go +++ b/internal/css_parser/css_parser_test.go @@ -1263,6 +1263,8 @@ func TestNestedSelector(t *testing.T) { expectPrintedLowerUnsupported(t, nesting, ".demo { .lg { .triangle, .circle { color: red } } }", ".demo .lg .triangle,\n.demo .lg .circle {\n color: red;\n}\n", "") expectPrintedLowerUnsupported(t, nesting, ".card { .featured & & & { color: red } }", ".featured .card .card .card {\n color: red;\n}\n", "") expectPrintedLowerUnsupported(t, nesting, ".a :has(> .c) { .b & { color: red } }", ".b :is(.a :has(> .c)) {\n color: red;\n}\n", "") + expectPrintedLowerUnsupported(t, nesting, "a { :has(&) { color: red } }", ":has(a) {\n color: red;\n}\n", "") + expectPrintedLowerUnsupported(t, nesting, "a { :has(> &) { color: red } }", ":has(> a) {\n color: red;\n}\n", "") // Duplicate "&" may be used to increase specificity expectPrintedLowerUnsupported(t, nesting, ".foo { &&&.bar { color: red } }", ".foo.foo.foo.bar {\n color: red;\n}\n", "") diff --git a/scripts/browser/index.html b/scripts/browser/index.html index d0046f6f927..b5e0e4e1ef5 100644 --- a/scripts/browser/index.html +++ b/scripts/browser/index.html @@ -231,6 +231,22 @@ }) }, + // See: https://github.com/evanw/esbuild/issues/3877#issuecomment-2631385559 + async cssNestingIssue3877Comment2631385559() { + await assertSameColorsWithNestingTransform(esbuild, { + css: ` + .a { + :has(>&) { + color: red; + } + } + `, + html: ` +
a
+ `, + }) + }, + // See: https://github.com/evanw/esbuild/issues/3997 async cssNestingIssue3997() { await assertSameColorsWithNestingTransform(esbuild, {