Skip to content

Commit

Permalink
css: wrap subclass selectors in a struct
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jul 16, 2023
1 parent 1bce9c1 commit 987b08a
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 37 deletions.
23 changes: 14 additions & 9 deletions internal/css_ast/css_ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,8 +617,8 @@ func HashComplexSelectors(hash uint32, selectors []ComplexSelector) uint32 {
hash = helpers.HashCombine(hash, 0)
}
hash = helpers.HashCombine(hash, uint32(len(sel.SubclassSelectors)))
for _, sub := range sel.SubclassSelectors {
hash = helpers.HashCombine(hash, sub.Hash())
for _, ss := range sel.SubclassSelectors {
hash = helpers.HashCombine(hash, ss.Data.Hash())
}
hash = helpers.HashCombine(hash, uint32(sel.Combinator.Byte))
}
Expand All @@ -644,7 +644,7 @@ func (sel ComplexSelector) IsRelative() bool {
return false
}
for _, ss := range inner.SubclassSelectors {
if pseudo, ok := ss.(*SSPseudoClassWithSelectorList); ok {
if pseudo, ok := ss.Data.(*SSPseudoClassWithSelectorList); ok {
for _, nested := range pseudo.Selectors {
if !nested.IsRelative() {
return false
Expand All @@ -671,8 +671,8 @@ func tokensContainAmpersandRecursive(tokens []Token) bool {

func (sel ComplexSelector) UsesPseudoElement() bool {
for _, sel := range sel.Selectors {
for _, sub := range sel.SubclassSelectors {
if class, ok := sub.(*SSPseudoClass); ok {
for _, ss := range sel.SubclassSelectors {
if class, ok := ss.Data.(*SSPseudoClass); ok {
if class.IsElement {
return true
}
Expand Down Expand Up @@ -713,7 +713,7 @@ func (a ComplexSelector) Equal(b ComplexSelector, check *CrossFileEqualityCheck)
return false
}
for j, aj := range ai.SubclassSelectors {
if !aj.Equal(bi.SubclassSelectors[j], check) {
if !aj.Data.Equal(bi.SubclassSelectors[j].Data, check) {
return false
}
}
Expand All @@ -729,7 +729,7 @@ type Combinator struct {

type CompoundSelector struct {
TypeSelector *NamespacedName
SubclassSelectors []SS
SubclassSelectors []SubclassSelector
Combinator Combinator // Optional, may be 0
HasNestingSelector bool // "&"
}
Expand All @@ -747,9 +747,10 @@ func (sel CompoundSelector) Clone() CompoundSelector {
}

if sel.SubclassSelectors != nil {
selectors := make([]SS, len(sel.SubclassSelectors))
selectors := make([]SubclassSelector, len(sel.SubclassSelectors))
for i, ss := range sel.SubclassSelectors {
selectors[i] = ss.Clone()
ss.Data = ss.Data.Clone()
selectors[i] = ss
}
clone.SubclassSelectors = selectors
}
Expand Down Expand Up @@ -789,6 +790,10 @@ func (a NamespacedName) Equal(b NamespacedName) bool {
(a.NamespacePrefix == nil || b.NamespacePrefix == nil || a.NamespacePrefix.Equal(b.Name))
}

type SubclassSelector struct {
Data SS
}

type SS interface {
Equal(ss SS, check *CrossFileEqualityCheck) bool
Hash() uint32
Expand Down
32 changes: 20 additions & 12 deletions internal/css_parser/css_nesting.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ func lowerNestingInRule(rule css_ast.Rule, results []css_ast.Rule) []css_ast.Rul
case *css_ast.RSelector:
scope := css_ast.ComplexSelector{
Selectors: []css_ast.CompoundSelector{{
SubclassSelectors: []css_ast.SS{&css_ast.SSPseudoClass{Name: "scope"}},
SubclassSelectors: []css_ast.SubclassSelector{{
Data: &css_ast.SSPseudoClass{Name: "scope"},
}},
}},
}

Expand Down Expand Up @@ -260,21 +262,25 @@ func substituteAmpersandsInCompoundSelector(sel css_ast.CompoundSelector, replac
// ".foo .bar { :hover & {} }" => ":hover :is(.foo .bar) {}"
// ".foo .bar { > &:hover {} }" => ".foo .bar > :is(.foo .bar):hover {}"
single = css_ast.CompoundSelector{
SubclassSelectors: []css_ast.SS{&css_ast.SSPseudoClassWithSelectorList{
Kind: css_ast.PseudoClassIs,
Selectors: []css_ast.ComplexSelector{replacement.CloneWithoutLeadingCombinator()},
SubclassSelectors: []css_ast.SubclassSelector{{
Data: &css_ast.SSPseudoClassWithSelectorList{
Kind: css_ast.PseudoClassIs,
Selectors: []css_ast.ComplexSelector{replacement.CloneWithoutLeadingCombinator()},
},
}},
}
}

var subclassSelectorPrefix []css_ast.SS
var subclassSelectorPrefix []css_ast.SubclassSelector

// Insert the type selector
if single.TypeSelector != nil {
if sel.TypeSelector != nil {
subclassSelectorPrefix = append(subclassSelectorPrefix, &css_ast.SSPseudoClassWithSelectorList{
Kind: css_ast.PseudoClassIs,
Selectors: []css_ast.ComplexSelector{{Selectors: []css_ast.CompoundSelector{{TypeSelector: sel.TypeSelector}}}},
subclassSelectorPrefix = append(subclassSelectorPrefix, css_ast.SubclassSelector{
Data: &css_ast.SSPseudoClassWithSelectorList{
Kind: css_ast.PseudoClassIs,
Selectors: []css_ast.ComplexSelector{{Selectors: []css_ast.CompoundSelector{{TypeSelector: sel.TypeSelector}}}},
},
})
}
sel.TypeSelector = single.TypeSelector
Expand All @@ -291,7 +297,7 @@ func substituteAmpersandsInCompoundSelector(sel css_ast.CompoundSelector, replac

// "div { :is(&.foo) {} }" => ":is(div.foo) {}"
for _, ss := range sel.SubclassSelectors {
if class, ok := ss.(*css_ast.SSPseudoClassWithSelectorList); ok {
if class, ok := ss.Data.(*css_ast.SSPseudoClassWithSelectorList); ok {
outer := make([]css_ast.ComplexSelector, 0, len(class.Selectors))
for _, complex := range class.Selectors {
inner := make([]css_ast.CompoundSelector, 0, len(complex.Selectors))
Expand Down Expand Up @@ -327,9 +333,11 @@ func multipleComplexSelectorsToSingleComplexSelector(selectors []css_ast.Complex
return css_ast.ComplexSelector{
Selectors: []css_ast.CompoundSelector{{
Combinator: leadingCombinator,
SubclassSelectors: []css_ast.SS{&css_ast.SSPseudoClassWithSelectorList{
Kind: css_ast.PseudoClassIs,
Selectors: clones,
SubclassSelectors: []css_ast.SubclassSelector{{
Data: &css_ast.SSPseudoClassWithSelectorList{
Kind: css_ast.PseudoClassIs,
Selectors: clones,
},
}},
}},
}
Expand Down
2 changes: 1 addition & 1 deletion internal/css_parser/css_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ func isSafeSelectors(complexSelectors []css_ast.ComplexSelector) bool {
}

for _, ss := range compound.SubclassSelectors {
switch s := ss.(type) {
switch s := ss.Data.(type) {
case *css_ast.SSAttribute:
if s.MatcherModifier != 0 {
// Bail if we hit a case modifier, which doesn't work in IE at all
Expand Down
35 changes: 22 additions & 13 deletions internal/css_parser/css_parser_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func flattenLocalAndGlobalSelectors(list []css_ast.ComplexSelector, sel css_ast.
// 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 pseudo, ok := single.SubclassSelectors[0].(*css_ast.SSPseudoClassWithSelectorList); ok && (pseudo.Kind == css_ast.PseudoClassGlobal || pseudo.Kind == css_ast.PseudoClassLocal) {
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...)
}
Expand All @@ -99,7 +99,7 @@ func flattenLocalAndGlobalSelectors(list []css_ast.ComplexSelector, sel css_ast.
// But if we can't, we just turn it into an ":is()" instead.
for _, s := range sel.Selectors {
for _, ss := range s.SubclassSelectors {
if pseudo, ok := ss.(*css_ast.SSPseudoClassWithSelectorList); ok && (pseudo.Kind == css_ast.PseudoClassGlobal || pseudo.Kind == css_ast.PseudoClassLocal) {
if pseudo, ok := ss.Data.(*css_ast.SSPseudoClassWithSelectorList); ok && (pseudo.Kind == css_ast.PseudoClassGlobal || pseudo.Kind == css_ast.PseudoClassLocal) {
// Only do the work to flatten the whole list if there's a ":local" or a ":global"
var selectors []css_ast.CompoundSelector
for _, s := range sel.Selectors {
Expand All @@ -108,7 +108,7 @@ func flattenLocalAndGlobalSelectors(list []css_ast.ComplexSelector, sel css_ast.
// done separately from the loop below because inlining may produce
// multiple compound selectors.
if !s.HasNestingSelector && s.TypeSelector == nil && len(s.SubclassSelectors) == 1 {
if pseudo, ok := s.SubclassSelectors[0].(*css_ast.SSPseudoClassWithSelectorList); ok &&
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) {
if s.Combinator.Byte != 0 {
Expand All @@ -122,9 +122,9 @@ func flattenLocalAndGlobalSelectors(list []css_ast.ComplexSelector, sel css_ast.
}
}

var subclassSelectors []css_ast.SS
var subclassSelectors []css_ast.SubclassSelector
for _, ss := range s.SubclassSelectors {
if pseudo, ok := ss.(*css_ast.SSPseudoClassWithSelectorList); ok && (pseudo.Kind == css_ast.PseudoClassGlobal || pseudo.Kind == css_ast.PseudoClassLocal) {
if pseudo, ok := ss.Data.(*css_ast.SSPseudoClassWithSelectorList); ok && (pseudo.Kind == css_ast.PseudoClassGlobal || pseudo.Kind == css_ast.PseudoClassLocal) {
// If the contents are a single compound selector, try to merge the contents into this compound selector
if len(pseudo.Selectors) == 1 && len(pseudo.Selectors[0].Selectors) == 1 {
if single := pseudo.Selectors[0].Selectors[0]; single.Combinator.Byte == 0 && (s.TypeSelector == nil || single.TypeSelector == nil) {
Expand Down Expand Up @@ -303,17 +303,21 @@ subclassSelectors:
}
nameLoc := logger.Loc{Start: p.current().Range.Loc.Start + 1}
name := p.decoded()
sel.SubclassSelectors = append(sel.SubclassSelectors, &css_ast.SSHash{
Name: ast.LocRef{Loc: nameLoc, Ref: p.symbolForName(name)},
sel.SubclassSelectors = append(sel.SubclassSelectors, css_ast.SubclassSelector{
Data: &css_ast.SSHash{
Name: ast.LocRef{Loc: nameLoc, Ref: p.symbolForName(name)},
},
})
p.advance()

case css_lexer.TDelimDot:
p.advance()
nameLoc := p.current().Range.Loc
name := p.decoded()
sel.SubclassSelectors = append(sel.SubclassSelectors, &css_ast.SSClass{
Name: ast.LocRef{Loc: nameLoc, Ref: p.symbolForName(name)},
sel.SubclassSelectors = append(sel.SubclassSelectors, css_ast.SubclassSelector{
Data: &css_ast.SSClass{
Name: ast.LocRef{Loc: nameLoc, Ref: p.symbolForName(name)},
},
})
if !p.expect(css_lexer.TIdent) {
return
Expand All @@ -324,7 +328,9 @@ subclassSelectors:
if !good {
return
}
sel.SubclassSelectors = append(sel.SubclassSelectors, &attr)
sel.SubclassSelectors = append(sel.SubclassSelectors, css_ast.SubclassSelector{
Data: &attr,
})

case css_lexer.TColon:
if p.next().Kind == css_lexer.TColon {
Expand All @@ -350,12 +356,15 @@ subclassSelectors:
}
}

sel.SubclassSelectors = append(sel.SubclassSelectors, pseudo)
sel.SubclassSelectors = append(sel.SubclassSelectors, css_ast.SubclassSelector{
Data: pseudo,
})
}
break subclassSelectors
}
pseudo := p.parsePseudoClassSelector(false)
sel.SubclassSelectors = append(sel.SubclassSelectors, pseudo)
sel.SubclassSelectors = append(sel.SubclassSelectors, css_ast.SubclassSelector{
Data: p.parsePseudoClassSelector(false),
})

case css_lexer.TDelimAmpersand:
// This is an extension: https://drafts.csswg.org/css-nesting-1/
Expand Down
4 changes: 2 additions & 2 deletions internal/css_printer/css_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,15 +453,15 @@ func (p *printer) printCompoundSelector(sel css_ast.CompoundSelector, isFirst bo
p.print("&")
}

for i, sub := range sel.SubclassSelectors {
for i, ss := range sel.SubclassSelectors {
whitespace := mayNeedWhitespaceAfter

// There is no chance of whitespace between subclass selectors
if i+1 < len(sel.SubclassSelectors) {
whitespace = canDiscardWhitespaceAfter
}

switch s := sub.(type) {
switch s := ss.Data.(type) {
case *css_ast.SSHash:
p.print("#")

Expand Down

0 comments on commit 987b08a

Please sign in to comment.