mirror of
https://github.com/zhigang1992/esbuild.git
synced 2026-06-15 01:49:44 +08:00
310 lines
6.9 KiB
Go
310 lines
6.9 KiB
Go
package css_parser
|
|
|
|
import (
|
|
"github.com/evanw/esbuild/internal/css_ast"
|
|
"github.com/evanw/esbuild/internal/css_lexer"
|
|
)
|
|
|
|
func (p *parser) parseSelectorList() (list []css_ast.ComplexSelector, ok bool) {
|
|
// Parse the first selector
|
|
p.eat(css_lexer.TWhitespace)
|
|
sel, good := p.parseComplexSelector()
|
|
if !good {
|
|
return
|
|
}
|
|
list = append(list, sel)
|
|
|
|
// Parse the remaining selectors
|
|
for {
|
|
p.eat(css_lexer.TWhitespace)
|
|
if !p.eat(css_lexer.TComma) {
|
|
break
|
|
}
|
|
p.eat(css_lexer.TWhitespace)
|
|
sel, good := p.parseComplexSelector()
|
|
if !good {
|
|
return
|
|
}
|
|
list = append(list, sel)
|
|
}
|
|
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseComplexSelector() (result css_ast.ComplexSelector, ok bool) {
|
|
// Parent
|
|
sel, good := p.parseCompoundSelector()
|
|
if !good {
|
|
return
|
|
}
|
|
result.Selectors = append(result.Selectors, sel)
|
|
|
|
for {
|
|
p.eat(css_lexer.TWhitespace)
|
|
if p.peek(css_lexer.TEndOfFile) || p.peek(css_lexer.TComma) || p.peek(css_lexer.TOpenBrace) {
|
|
break
|
|
}
|
|
|
|
// Optional combinator
|
|
combinator := p.parseCombinator()
|
|
if combinator != "" {
|
|
p.eat(css_lexer.TWhitespace)
|
|
}
|
|
|
|
// Child
|
|
sel, good := p.parseCompoundSelector()
|
|
if !good {
|
|
return
|
|
}
|
|
sel.Combinator = combinator
|
|
result.Selectors = append(result.Selectors, sel)
|
|
}
|
|
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseCompoundSelector() (sel css_ast.CompoundSelector, ok bool) {
|
|
// This is an extension: https://drafts.csswg.org/css-nesting-1/
|
|
if p.eat(css_lexer.TDelimAmpersand) {
|
|
sel.HasNestPrefix = true
|
|
}
|
|
|
|
// Parse the type selector
|
|
switch p.current().Kind {
|
|
case css_lexer.TDelimBar, css_lexer.TIdent, css_lexer.TDelimAsterisk:
|
|
nsName := css_ast.NamespacedName{}
|
|
if !p.peek(css_lexer.TDelimBar) {
|
|
nsName.Name = p.text()
|
|
p.advance()
|
|
}
|
|
if p.eat(css_lexer.TDelimBar) {
|
|
if !p.peek(css_lexer.TIdent) && !p.peek(css_lexer.TDelimAsterisk) {
|
|
p.expect(css_lexer.TIdent)
|
|
return
|
|
}
|
|
prefix := nsName.Name
|
|
nsName.NamespacePrefix = &prefix
|
|
nsName.Name = p.text()
|
|
p.advance()
|
|
}
|
|
sel.TypeSelector = &nsName
|
|
}
|
|
|
|
// Parse the subclass selectors
|
|
subclassSelectors:
|
|
for {
|
|
switch p.current().Kind {
|
|
case css_lexer.THashID:
|
|
name := p.text()[1:]
|
|
sel.SubclassSelectors = append(sel.SubclassSelectors, &css_ast.SSHash{Name: name})
|
|
p.advance()
|
|
|
|
case css_lexer.TDelimDot:
|
|
p.advance()
|
|
name := p.text()
|
|
sel.SubclassSelectors = append(sel.SubclassSelectors, &css_ast.SSClass{Name: name})
|
|
p.expect(css_lexer.TIdent)
|
|
|
|
case css_lexer.TOpenBracket:
|
|
p.advance()
|
|
attr, good := p.parseAttributeSelector()
|
|
if !good {
|
|
return
|
|
}
|
|
sel.SubclassSelectors = append(sel.SubclassSelectors, &attr)
|
|
|
|
case css_lexer.TColon:
|
|
if p.next().Kind == css_lexer.TColon {
|
|
// Stop if this is the start of the pseudo-element selector section
|
|
break subclassSelectors
|
|
}
|
|
pseudo := p.parsePseudoElementSelector()
|
|
sel.SubclassSelectors = append(sel.SubclassSelectors, &pseudo)
|
|
|
|
default:
|
|
break subclassSelectors
|
|
}
|
|
}
|
|
|
|
// Parse the pseudo-element selectors
|
|
if p.eat(css_lexer.TColon) {
|
|
pseudo := p.parsePseudoElementSelector()
|
|
sel.PseudoClassSelectors = append(sel.PseudoClassSelectors, pseudo)
|
|
for p.peek(css_lexer.TColon) {
|
|
pseudo := p.parsePseudoElementSelector()
|
|
sel.PseudoClassSelectors = append(sel.PseudoClassSelectors, pseudo)
|
|
}
|
|
}
|
|
|
|
// The compound selector must be non-empty
|
|
if !sel.HasNestPrefix && sel.TypeSelector == nil && len(sel.SubclassSelectors) == 0 && len(sel.PseudoClassSelectors) == 0 {
|
|
p.unexpected()
|
|
return
|
|
}
|
|
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseAttributeSelector() (attr css_ast.SSAttribute, ok bool) {
|
|
// Parse the namespaced name
|
|
switch p.current().Kind {
|
|
case css_lexer.TDelimBar, css_lexer.TDelimAsterisk:
|
|
// "[|x]"
|
|
// "[*|x]"
|
|
prefix := ""
|
|
if p.peek(css_lexer.TDelimAsterisk) {
|
|
prefix = "*"
|
|
p.advance()
|
|
}
|
|
attr.NamespacedName.NamespacePrefix = &prefix
|
|
if !p.expect(css_lexer.TDelimBar) {
|
|
return
|
|
}
|
|
attr.NamespacedName.Name = p.text()
|
|
if !p.expect(css_lexer.TIdent) {
|
|
return
|
|
}
|
|
|
|
default:
|
|
// "[x]"
|
|
// "[x|y]"
|
|
attr.NamespacedName.Name = p.text()
|
|
if !p.expect(css_lexer.TIdent) {
|
|
return
|
|
}
|
|
if p.next().Kind != css_lexer.TDelimEquals && p.eat(css_lexer.TDelimBar) {
|
|
prefix := attr.NamespacedName.Name
|
|
attr.NamespacedName.NamespacePrefix = &prefix
|
|
attr.NamespacedName.Name = p.text()
|
|
if !p.expect(css_lexer.TIdent) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse the optional matcher operator
|
|
p.eat(css_lexer.TWhitespace)
|
|
if p.eat(css_lexer.TDelimEquals) {
|
|
attr.MatcherOp = "="
|
|
} else {
|
|
switch p.current().Kind {
|
|
case css_lexer.TDelimTilde:
|
|
attr.MatcherOp = "~="
|
|
case css_lexer.TDelimBar:
|
|
attr.MatcherOp = "|="
|
|
case css_lexer.TDelimCaret:
|
|
attr.MatcherOp = "^="
|
|
case css_lexer.TDelimDollar:
|
|
attr.MatcherOp = "$="
|
|
case css_lexer.TDelimAsterisk:
|
|
attr.MatcherOp = "*="
|
|
}
|
|
if attr.MatcherOp != "" {
|
|
p.advance()
|
|
p.expect(css_lexer.TDelimEquals)
|
|
}
|
|
}
|
|
|
|
// Parse the optional matcher value
|
|
if attr.MatcherOp != "" {
|
|
p.eat(css_lexer.TWhitespace)
|
|
if !p.peek(css_lexer.TString) && !p.peek(css_lexer.TIdent) {
|
|
p.unexpected()
|
|
}
|
|
attr.MatcherValue = p.text()
|
|
p.advance()
|
|
p.eat(css_lexer.TWhitespace)
|
|
if p.peek(css_lexer.TIdent) {
|
|
if modifier := p.text(); len(modifier) == 1 {
|
|
if c := modifier[0]; c == 'i' || c == 'I' {
|
|
attr.MatcherModifier = c
|
|
p.advance()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
p.expect(css_lexer.TCloseBracket)
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
func (p *parser) parsePseudoElementSelector() css_ast.SSPseudoClass {
|
|
p.advance()
|
|
|
|
if p.peek(css_lexer.TFunction) {
|
|
text := p.text()
|
|
p.advance()
|
|
args := p.convertTokens(p.parseAnyValue())
|
|
p.expect(css_lexer.TCloseParen)
|
|
return css_ast.SSPseudoClass{Name: text[:len(text)-1], Args: args}
|
|
}
|
|
|
|
sel := css_ast.SSPseudoClass{Name: p.text()}
|
|
p.expect(css_lexer.TIdent)
|
|
return sel
|
|
}
|
|
|
|
func (p *parser) parseAnyValue() []css_lexer.Token {
|
|
// Reference: https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value
|
|
|
|
p.stack = p.stack[:0] // Reuse allocated memory
|
|
start := p.index
|
|
|
|
loop:
|
|
for {
|
|
switch p.current().Kind {
|
|
case css_lexer.TCloseParen, css_lexer.TCloseBracket, css_lexer.TCloseBrace:
|
|
last := len(p.stack) - 1
|
|
if last < 0 || !p.peek(p.stack[last]) {
|
|
break loop
|
|
}
|
|
p.stack = p.stack[:last]
|
|
|
|
case css_lexer.TSemicolon, css_lexer.TDelimExclamation:
|
|
if len(p.stack) == 0 {
|
|
break loop
|
|
}
|
|
|
|
case css_lexer.TOpenParen, css_lexer.TFunction:
|
|
p.stack = append(p.stack, css_lexer.TCloseParen)
|
|
|
|
case css_lexer.TOpenBracket:
|
|
p.stack = append(p.stack, css_lexer.TCloseBracket)
|
|
|
|
case css_lexer.TOpenBrace:
|
|
p.stack = append(p.stack, css_lexer.TCloseBrace)
|
|
}
|
|
|
|
p.advance()
|
|
}
|
|
|
|
tokens := p.tokens[start:p.index]
|
|
if len(tokens) == 0 {
|
|
p.unexpected()
|
|
}
|
|
return tokens
|
|
}
|
|
|
|
func (p *parser) parseCombinator() string {
|
|
switch p.current().Kind {
|
|
case css_lexer.TDelimGreaterThan:
|
|
p.advance()
|
|
return ">"
|
|
|
|
case css_lexer.TDelimPlus:
|
|
p.advance()
|
|
return "+"
|
|
|
|
case css_lexer.TDelimTilde:
|
|
p.advance()
|
|
return "~"
|
|
|
|
default:
|
|
return ""
|
|
}
|
|
}
|