From c6e14efb7e02b6e52099f0869a4e56a55b58a3c6 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 16 Jul 2023 18:51:05 -0400 Subject: [PATCH] css: emit mappings for nesting selectors --- internal/css_ast/css_ast.go | 14 +++++++---- internal/css_parser/css_nesting.go | 17 +++++++------ internal/css_parser/css_parser.go | 2 +- internal/css_parser/css_parser_selector.go | 28 ++++++++++++---------- internal/css_printer/css_printer.go | 6 ++++- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/internal/css_ast/css_ast.go b/internal/css_ast/css_ast.go index 183fcb5d2a9..66727063cb0 100644 --- a/internal/css_ast/css_ast.go +++ b/internal/css_ast/css_ast.go @@ -640,7 +640,7 @@ func (s ComplexSelector) CloneWithoutLeadingCombinator() ComplexSelector { func (sel ComplexSelector) IsRelative() bool { if sel.Selectors[0].Combinator.Byte == 0 { for _, inner := range sel.Selectors { - if inner.HasNestingSelector { + if inner.HasNestingSelector() { return false } for _, ss := range inner.SubclassSelectors { @@ -699,7 +699,7 @@ func (a ComplexSelector) Equal(b ComplexSelector, check *CrossFileEqualityCheck) for i, ai := range a.Selectors { bi := b.Selectors[i] - if ai.HasNestingSelector != bi.HasNestingSelector || ai.Combinator.Byte != bi.Combinator.Byte { + if ai.HasNestingSelector() != bi.HasNestingSelector() || ai.Combinator.Byte != bi.Combinator.Byte { return false } @@ -730,12 +730,16 @@ type Combinator struct { type CompoundSelector struct { TypeSelector *NamespacedName SubclassSelectors []SubclassSelector - Combinator Combinator // Optional, may be 0 - HasNestingSelector bool // "&" + NestingSelectorLoc ast.Index32 // "&" + Combinator Combinator // Optional, may be 0 +} + +func (sel *CompoundSelector) HasNestingSelector() bool { + return sel.NestingSelectorLoc.IsValid() } func (sel CompoundSelector) IsSingleAmpersand() bool { - return sel.HasNestingSelector && sel.Combinator.Byte == 0 && sel.TypeSelector == nil && len(sel.SubclassSelectors) == 0 + return sel.HasNestingSelector() && sel.Combinator.Byte == 0 && sel.TypeSelector == nil && len(sel.SubclassSelectors) == 0 } func (sel CompoundSelector) Clone() CompoundSelector { diff --git a/internal/css_parser/css_nesting.go b/internal/css_parser/css_nesting.go index a73d36f9abf..acd0a342981 100644 --- a/internal/css_parser/css_nesting.go +++ b/internal/css_parser/css_nesting.go @@ -1,6 +1,7 @@ package css_parser import ( + "github.com/evanw/esbuild/internal/ast" "github.com/evanw/esbuild/internal/css_ast" ) @@ -115,7 +116,7 @@ func lowerNestingInRuleWithContext(rule css_ast.Rule, context *lowerNestingConte // Inject the implicit "&" now for simplicity later on if sel.IsRelative() { - sel.Selectors = append([]css_ast.CompoundSelector{{HasNestingSelector: true}}, sel.Selectors...) + sel.Selectors = append([]css_ast.CompoundSelector{{NestingSelectorLoc: ast.MakeIndex32(uint32(rule.Loc.Start))}}, sel.Selectors...) } // Pseudo-elements aren't supported by ":is" (i.e. ":is(div, div::before)" @@ -143,7 +144,7 @@ func lowerNestingInRuleWithContext(rule css_ast.Rule, context *lowerNestingConte } // Are all children of the form "&«something»"? - if first := sel.Selectors[0]; !first.HasNestingSelector || first.IsSingleAmpersand() { + if first := sel.Selectors[0]; !first.HasNestingSelector() || first.IsSingleAmpersand() { canUseGroupSubSelector = false } } @@ -152,22 +153,24 @@ func lowerNestingInRuleWithContext(rule css_ast.Rule, context *lowerNestingConte if canUseGroupDescendantCombinator { // "& a, & b {}" => "& :is(a, b) {}" // "& > a, & > b {}" => "& > :is(a, b) {}" + nestingSelectorLoc := r.Selectors[0].Selectors[0].NestingSelectorLoc for i := range r.Selectors { sel := &r.Selectors[i] sel.Selectors = sel.Selectors[1:] } merged := multipleComplexSelectorsToSingleComplexSelector(r.Selectors) - merged.Selectors = append([]css_ast.CompoundSelector{{HasNestingSelector: true}}, merged.Selectors...) + merged.Selectors = append([]css_ast.CompoundSelector{{NestingSelectorLoc: nestingSelectorLoc}}, merged.Selectors...) r.Selectors = []css_ast.ComplexSelector{merged} } else if canUseGroupSubSelector { // "&a, &b {}" => "&:is(a, b) {}" // "> &a, > &b {}" => "> &:is(a, b) {}" + nestingSelectorLoc := r.Selectors[0].Selectors[0].NestingSelectorLoc for i := range r.Selectors { sel := &r.Selectors[i] - sel.Selectors[0].HasNestingSelector = false + sel.Selectors[0].NestingSelectorLoc = ast.Index32{} } merged := multipleComplexSelectorsToSingleComplexSelector(r.Selectors) - merged.Selectors[0].HasNestingSelector = true + merged.Selectors[0].NestingSelectorLoc = nestingSelectorLoc r.Selectors = []css_ast.ComplexSelector{merged} } @@ -237,8 +240,8 @@ const ( ) func substituteAmpersandsInCompoundSelector(sel css_ast.CompoundSelector, replacement css_ast.ComplexSelector, results []css_ast.CompoundSelector, strip leadingCombinatorStrip) []css_ast.CompoundSelector { - if sel.HasNestingSelector { - sel.HasNestingSelector = false + if sel.HasNestingSelector() { + sel.NestingSelectorLoc = ast.Index32{} // Convert the replacement to a single compound selector var single css_ast.CompoundSelector diff --git a/internal/css_parser/css_parser.go b/internal/css_parser/css_parser.go index 7df7f87d533..0ff7f1e43c7 100644 --- a/internal/css_parser/css_parser.go +++ b/internal/css_parser/css_parser.go @@ -786,7 +786,7 @@ var nonDeprecatedElementsSupportedByIE7 = map[string]bool{ func isSafeSelectors(complexSelectors []css_ast.ComplexSelector) bool { for _, complex := range complexSelectors { for _, compound := range complex.Selectors { - if compound.HasNestingSelector { + if compound.HasNestingSelector() { // Bail because this is an extension: https://drafts.csswg.org/css-nesting-1/ return false } diff --git a/internal/css_parser/css_parser_selector.go b/internal/css_parser/css_parser_selector.go index 38e9e7867f3..77330b62965 100644 --- a/internal/css_parser/css_parser_selector.go +++ b/internal/css_parser/css_parser_selector.go @@ -65,7 +65,7 @@ skip: case canRemoveLeadingAmpersandIfNotFirst: for i := 1; i < len(list); i++ { - if sel := list[i].Selectors[0]; !sel.HasNestingSelector && (sel.Combinator.Byte != 0 || sel.TypeSelector == nil) { + if sel := list[i].Selectors[0]; !sel.HasNestingSelector() && (sel.Combinator.Byte != 0 || sel.TypeSelector == nil) { list[0].Selectors = list[0].Selectors[1:] list[0], list[i] = list[i], list[0] break @@ -85,7 +85,7 @@ func flattenLocalAndGlobalSelectors(list []css_ast.ComplexSelector, sel css_ast. // done separately from the loop below because inlining may produce // multiple complex selectors. if len(sel.Selectors) == 1 { - if single := sel.Selectors[0]; !single.HasNestingSelector && single.TypeSelector == nil && len(single.SubclassSelectors) == 1 && single.Combinator.Byte == 0 { + if single := sel.Selectors[0]; !single.HasNestingSelector() && single.TypeSelector == nil && len(single.SubclassSelectors) == 1 && single.Combinator.Byte == 0 { if pseudo, ok := single.SubclassSelectors[0].Data.(*css_ast.SSPseudoClassWithSelectorList); ok && (pseudo.Kind == css_ast.PseudoClassGlobal || pseudo.Kind == css_ast.PseudoClassLocal) { // ":local(.a, .b)" => ".a, .b" return append(list, pseudo.Selectors...) @@ -107,7 +107,7 @@ func flattenLocalAndGlobalSelectors(list []css_ast.ComplexSelector, sel css_ast. // contents can be inlined, then inline it directly. This has to be // done separately from the loop below because inlining may produce // multiple compound selectors. - if !s.HasNestingSelector && s.TypeSelector == nil && len(s.SubclassSelectors) == 1 { + if !s.HasNestingSelector() && s.TypeSelector == nil && len(s.SubclassSelectors) == 1 { if pseudo, ok := s.SubclassSelectors[0].Data.(*css_ast.SSPseudoClassWithSelectorList); ok && (pseudo.Kind == css_ast.PseudoClassGlobal || pseudo.Kind == css_ast.PseudoClassLocal) && len(pseudo.Selectors) == 1 { if nested := pseudo.Selectors[0].Selectors; ok && (s.Combinator.Byte == 0 || nested[0].Combinator.Byte == 0) { @@ -132,9 +132,9 @@ func flattenLocalAndGlobalSelectors(list []css_ast.ComplexSelector, sel css_ast. // ".foo:local(div)" => "div.foo" s.TypeSelector = single.TypeSelector } - if single.HasNestingSelector { + if single.HasNestingSelector() { // ".foo:local(&)" => "&.foo" - s.HasNestingSelector = true + s.NestingSelectorLoc = single.NestingSelectorLoc } // ".foo:local(.bar)" => ".foo.bar" subclassSelectors = append(subclassSelectors, single.SubclassSelectors...) @@ -171,7 +171,7 @@ const ( func analyzeLeadingAmpersand(sel css_ast.ComplexSelector, isDeclarationContext bool) leadingAmpersand { if len(sel.Selectors) > 1 { if first := sel.Selectors[0]; first.IsSingleAmpersand() { - if second := sel.Selectors[1]; second.Combinator.Byte == 0 && second.HasNestingSelector { + if second := sel.Selectors[1]; second.Combinator.Byte == 0 && second.HasNestingSelector() { // ".foo { & &.bar {} }" => ".foo { & &.bar {} }" } else if second.Combinator.Byte != 0 || second.TypeSelector == nil || !isDeclarationContext { // "& + div {}" => "+ div {}" @@ -264,7 +264,7 @@ func (p *parser) parseCompoundSelector(opts parseComplexSelectorOpts) (sel css_a hasLeadingNestingSelector := p.peek(css_lexer.TDelimAmpersand) if hasLeadingNestingSelector { p.reportUseOfNesting(p.current().Range, opts.isDeclarationContext) - sel.HasNestingSelector = true + sel.NestingSelectorLoc = ast.MakeIndex32(uint32(startLoc.Start)) p.advance() } @@ -296,12 +296,14 @@ func (p *parser) parseCompoundSelector(opts parseComplexSelectorOpts) (sel css_a // Parse the subclass selectors subclassSelectors: for { - switch p.current().Kind { + subclassToken := p.current() + + switch subclassToken.Kind { case css_lexer.THash: - if (p.current().Flags & css_lexer.IsID) == 0 { + if (subclassToken.Flags & css_lexer.IsID) == 0 { break subclassSelectors } - nameLoc := logger.Loc{Start: p.current().Range.Loc.Start + 1} + nameLoc := logger.Loc{Start: subclassToken.Range.Loc.Start + 1} name := p.decoded() sel.SubclassSelectors = append(sel.SubclassSelectors, css_ast.SubclassSelector{ Data: &css_ast.SSHash{ @@ -368,8 +370,8 @@ subclassSelectors: case css_lexer.TDelimAmpersand: // This is an extension: https://drafts.csswg.org/css-nesting-1/ - p.reportUseOfNesting(p.current().Range, sel.HasNestingSelector) - sel.HasNestingSelector = true + p.reportUseOfNesting(subclassToken.Range, sel.HasNestingSelector()) + sel.NestingSelectorLoc = ast.MakeIndex32(uint32(subclassToken.Range.Loc.Start)) p.advance() default: @@ -378,7 +380,7 @@ subclassSelectors: } // The compound selector must be non-empty - if !sel.HasNestingSelector && sel.TypeSelector == nil && len(sel.SubclassSelectors) == 0 { + if !sel.HasNestingSelector() && sel.TypeSelector == nil && len(sel.SubclassSelectors) == 0 { p.unexpected() return } diff --git a/internal/css_printer/css_printer.go b/internal/css_printer/css_printer.go index f50407cc15e..5a95fa01e5b 100644 --- a/internal/css_printer/css_printer.go +++ b/internal/css_printer/css_printer.go @@ -449,7 +449,11 @@ func (p *printer) printCompoundSelector(sel css_ast.CompoundSelector, isFirst bo p.printNamespacedName(*sel.TypeSelector, whitespace) } - if sel.HasNestingSelector { + if sel.HasNestingSelector() { + if p.options.AddSourceMappings { + p.builder.AddSourceMapping(logger.Loc{Start: int32(sel.NestingSelectorLoc.GetIndex())}, "", p.css) + } + p.print("&") }