1package css_ast 2 3import ( 4 "strconv" 5 6 "github.com/evanw/esbuild/internal/ast" 7 "github.com/evanw/esbuild/internal/css_lexer" 8 "github.com/evanw/esbuild/internal/helpers" 9 "github.com/evanw/esbuild/internal/logger" 10) 11 12// CSS syntax comes in two layers: a minimal syntax that generally accepts 13// anything that looks vaguely like CSS, and a large set of built-in rules 14// (the things browsers actually interpret). That way CSS parsers can read 15// unknown rules and skip over them without having to stop due to errors. 16// 17// This AST format is mostly just the minimal syntax. It parses unknown rules 18// into a tree with enough information that it can write them back out again. 19// There are some additional layers of syntax including selectors and @-rules 20// which allow for better pretty-printing and minification. 21// 22// Most of the AST just references ranges of the original file by keeping the 23// original "Token" values around from the lexer. This is a memory-efficient 24// representation that helps provide good parsing and printing performance. 25 26type AST struct { 27 ImportRecords []ast.ImportRecord 28 Rules []Rule 29 SourceMapComment logger.Span 30 ApproximateLineCount int32 31} 32 33// We create a lot of tokens, so make sure this layout is memory-efficient. 34// The layout here isn't optimal because it biases for convenience (e.g. 35// "string" could be shorter) but at least the ordering of fields was 36// deliberately chosen to minimize size. 37type Token struct { 38 // This is the raw contents of the token most of the time. However, it 39 // contains the decoded string contents for "TString" tokens. 40 Text string // 16 bytes 41 42 // Contains the child tokens for component values that are simple blocks. 43 // These are either "(", "{", "[", or function tokens. The closing token is 44 // implicit and is not stored. 45 Children *[]Token // 8 bytes 46 47 // URL tokens have an associated import record at the top-level of the AST. 48 // This index points to that import record. 49 ImportRecordIndex uint32 // 4 bytes 50 51 // The division between the number and the unit for "TDimension" tokens. 52 UnitOffset uint16 // 2 bytes 53 54 // This will never be "TWhitespace" because whitespace isn't stored as a 55 // token directly. Instead it is stored in "HasWhitespaceAfter" on the 56 // previous token. This is to make it easier to pattern-match against 57 // tokens when handling CSS rules, since whitespace almost always doesn't 58 // matter. That way you can pattern match against e.g. "rgb(r, g, b)" and 59 // not have to handle all possible combinations of embedded whitespace 60 // tokens. 61 // 62 // There is one exception to this: when in verbatim whitespace mode and 63 // the token list is non-empty and is only whitespace tokens. In that case 64 // a single whitespace token is emitted. This is because otherwise there 65 // would be no tokens to attach the whitespace before/after flags to. 66 Kind css_lexer.T // 1 byte 67 68 // These flags indicate the presence of a "TWhitespace" token before or after 69 // this token. There should be whitespace printed between two tokens if either 70 // token indicates that there should be whitespace. Note that whitespace may 71 // be altered by processing in certain situations (e.g. minification). 72 Whitespace WhitespaceFlags // 1 byte 73} 74 75type WhitespaceFlags uint8 76 77const ( 78 WhitespaceBefore WhitespaceFlags = 1 << iota 79 WhitespaceAfter 80) 81 82func (a Token) Equal(b Token) bool { 83 if a.Kind == b.Kind && a.Text == b.Text && a.ImportRecordIndex == b.ImportRecordIndex && a.Whitespace == b.Whitespace { 84 if a.Children == nil && b.Children == nil { 85 return true 86 } 87 88 if a.Children != nil && b.Children != nil && TokensEqual(*a.Children, *b.Children) { 89 return true 90 } 91 } 92 93 return false 94} 95 96func TokensEqual(a []Token, b []Token) bool { 97 if len(a) != len(b) { 98 return false 99 } 100 for i, c := range a { 101 if !c.Equal(b[i]) { 102 return false 103 } 104 } 105 return true 106} 107 108func HashTokens(hash uint32, tokens []Token) uint32 { 109 hash = helpers.HashCombine(hash, uint32(len(tokens))) 110 111 for _, t := range tokens { 112 hash = helpers.HashCombine(hash, uint32(t.Kind)) 113 hash = helpers.HashCombineString(hash, t.Text) 114 if t.Children != nil { 115 hash = HashTokens(hash, *t.Children) 116 } 117 } 118 119 return hash 120} 121 122func (a Token) EqualIgnoringWhitespace(b Token) bool { 123 if a.Kind == b.Kind && a.Text == b.Text && a.ImportRecordIndex == b.ImportRecordIndex { 124 if a.Children == nil && b.Children == nil { 125 return true 126 } 127 128 if a.Children != nil && b.Children != nil && TokensEqualIgnoringWhitespace(*a.Children, *b.Children) { 129 return true 130 } 131 } 132 133 return false 134} 135 136func TokensEqualIgnoringWhitespace(a []Token, b []Token) bool { 137 if len(a) != len(b) { 138 return false 139 } 140 for i, c := range a { 141 if !c.EqualIgnoringWhitespace(b[i]) { 142 return false 143 } 144 } 145 return true 146} 147 148func TokensAreCommaSeparated(tokens []Token) bool { 149 if n := len(tokens); (n & 1) != 0 { 150 for i := 1; i < n; i += 2 { 151 if tokens[i].Kind != css_lexer.TComma { 152 return false 153 } 154 } 155 return true 156 } 157 return false 158} 159 160func (t Token) FractionForPercentage() (float64, bool) { 161 if t.Kind == css_lexer.TPercentage { 162 if f, err := strconv.ParseFloat(t.PercentageValue(), 64); err == nil { 163 if f < 0 { 164 return 0, true 165 } 166 if f > 100 { 167 return 1, true 168 } 169 return f / 100.0, true 170 } 171 } 172 return 0, false 173} 174 175// https://drafts.csswg.org/css-values-3/#lengths 176// For zero lengths the unit identifier is optional 177// (i.e. can be syntactically represented as the <number> 0). 178func (t *Token) TurnLengthIntoNumberIfZero() bool { 179 if t.Kind == css_lexer.TDimension && t.DimensionValue() == "0" { 180 t.Kind = css_lexer.TNumber 181 t.Text = "0" 182 return true 183 } 184 return false 185} 186 187func (t *Token) TurnLengthOrPercentageIntoNumberIfZero() bool { 188 if t.Kind == css_lexer.TPercentage && t.PercentageValue() == "0" { 189 t.Kind = css_lexer.TNumber 190 t.Text = "0" 191 return true 192 } 193 return t.TurnLengthIntoNumberIfZero() 194} 195 196func (t Token) PercentageValue() string { 197 return t.Text[:len(t.Text)-1] 198} 199 200func (t Token) DimensionValue() string { 201 return t.Text[:t.UnitOffset] 202} 203 204func (t Token) DimensionUnit() string { 205 return t.Text[t.UnitOffset:] 206} 207 208func (t Token) DimensionUnitIsSafeLength() bool { 209 switch t.DimensionUnit() { 210 // These units can be reasonably expected to be supported everywhere. 211 // Information used: https://developer.mozilla.org/en-US/docs/Web/CSS/length 212 case "cm", "em", "in", "mm", "pc", "pt", "px": 213 return true 214 } 215 return false 216} 217 218func (t Token) IsZero() bool { 219 return t.Kind == css_lexer.TNumber && t.Text == "0" 220} 221 222func (t Token) IsOne() bool { 223 return t.Kind == css_lexer.TNumber && t.Text == "1" 224} 225 226func (t Token) IsAngle() bool { 227 if t.Kind == css_lexer.TDimension { 228 unit := t.DimensionUnit() 229 return unit == "deg" || unit == "grad" || unit == "rad" || unit == "turn" 230 } 231 return false 232} 233 234func CloneTokensWithImportRecords( 235 tokensIn []Token, importRecordsIn []ast.ImportRecord, 236 tokensOut []Token, importRecordsOut []ast.ImportRecord, 237) ([]Token, []ast.ImportRecord) { 238 for _, t := range tokensIn { 239 // If this is a URL token, also clone the import record 240 if t.Kind == css_lexer.TURL { 241 importRecordIndex := uint32(len(importRecordsOut)) 242 importRecordsOut = append(importRecordsOut, importRecordsIn[t.ImportRecordIndex]) 243 t.ImportRecordIndex = importRecordIndex 244 } 245 246 // Also search for URL tokens in this token's children 247 if t.Children != nil { 248 var children []Token 249 children, importRecordsOut = CloneTokensWithImportRecords(*t.Children, importRecordsIn, children, importRecordsOut) 250 t.Children = &children 251 } 252 253 tokensOut = append(tokensOut, t) 254 } 255 256 return tokensOut, importRecordsOut 257} 258 259type Rule struct { 260 Loc logger.Loc 261 Data R 262} 263 264type R interface { 265 Equal(rule R) bool 266 Hash() (uint32, bool) 267} 268 269func RulesEqual(a []Rule, b []Rule) bool { 270 if len(a) != len(b) { 271 return false 272 } 273 for i, c := range a { 274 if !c.Data.Equal(b[i].Data) { 275 return false 276 } 277 } 278 return true 279} 280 281func HashRules(hash uint32, rules []Rule) uint32 { 282 hash = helpers.HashCombine(hash, uint32(len(rules))) 283 for _, child := range rules { 284 if childHash, ok := child.Data.Hash(); ok { 285 hash = helpers.HashCombine(hash, childHash) 286 } else { 287 hash = helpers.HashCombine(hash, 0) 288 } 289 } 290 return hash 291} 292 293type RAtCharset struct { 294 Encoding string 295} 296 297func (a *RAtCharset) Equal(rule R) bool { 298 b, ok := rule.(*RAtCharset) 299 return ok && a.Encoding == b.Encoding 300} 301 302func (r *RAtCharset) Hash() (uint32, bool) { 303 hash := uint32(1) 304 hash = helpers.HashCombineString(hash, r.Encoding) 305 return hash, true 306} 307 308type RAtImport struct { 309 ImportRecordIndex uint32 310 ImportConditions []Token 311} 312 313func (*RAtImport) Equal(rule R) bool { 314 return false 315} 316 317func (r *RAtImport) Hash() (uint32, bool) { 318 return 0, false 319} 320 321type RAtKeyframes struct { 322 AtToken string 323 Name string 324 Blocks []KeyframeBlock 325} 326 327type KeyframeBlock struct { 328 Selectors []string 329 Rules []Rule 330} 331 332func (a *RAtKeyframes) Equal(rule R) bool { 333 b, ok := rule.(*RAtKeyframes) 334 if ok && a.AtToken == b.AtToken && a.Name == b.Name && len(a.Blocks) == len(b.Blocks) { 335 for i, ai := range a.Blocks { 336 bi := b.Blocks[i] 337 if len(ai.Selectors) != len(bi.Selectors) { 338 return false 339 } 340 for j, aj := range ai.Selectors { 341 if aj != bi.Selectors[j] { 342 return false 343 } 344 } 345 if !RulesEqual(ai.Rules, bi.Rules) { 346 return false 347 } 348 } 349 return true 350 } 351 return false 352} 353 354func (r *RAtKeyframes) Hash() (uint32, bool) { 355 hash := uint32(2) 356 hash = helpers.HashCombineString(hash, r.AtToken) 357 hash = helpers.HashCombineString(hash, r.Name) 358 hash = helpers.HashCombine(hash, uint32(len(r.Blocks))) 359 for _, block := range r.Blocks { 360 hash = helpers.HashCombine(hash, uint32(len(block.Selectors))) 361 for _, sel := range block.Selectors { 362 hash = helpers.HashCombineString(hash, sel) 363 } 364 hash = HashRules(hash, block.Rules) 365 } 366 return hash, true 367} 368 369type RKnownAt struct { 370 AtToken string 371 Prelude []Token 372 Rules []Rule 373} 374 375func (a *RKnownAt) Equal(rule R) bool { 376 b, ok := rule.(*RKnownAt) 377 return ok && a.AtToken == b.AtToken && TokensEqual(a.Prelude, b.Prelude) && RulesEqual(a.Rules, a.Rules) 378} 379 380func (r *RKnownAt) Hash() (uint32, bool) { 381 hash := uint32(3) 382 hash = helpers.HashCombineString(hash, r.AtToken) 383 hash = HashTokens(hash, r.Prelude) 384 hash = HashRules(hash, r.Rules) 385 return hash, true 386} 387 388type RUnknownAt struct { 389 AtToken string 390 Prelude []Token 391 Block []Token 392} 393 394func (a *RUnknownAt) Equal(rule R) bool { 395 b, ok := rule.(*RUnknownAt) 396 return ok && a.AtToken == b.AtToken && TokensEqual(a.Prelude, b.Prelude) && TokensEqual(a.Block, a.Block) 397} 398 399func (r *RUnknownAt) Hash() (uint32, bool) { 400 hash := uint32(4) 401 hash = helpers.HashCombineString(hash, r.AtToken) 402 hash = HashTokens(hash, r.Prelude) 403 hash = HashTokens(hash, r.Block) 404 return hash, true 405} 406 407type RSelector struct { 408 Selectors []ComplexSelector 409 Rules []Rule 410} 411 412func (a *RSelector) Equal(rule R) bool { 413 b, ok := rule.(*RSelector) 414 if ok && len(a.Selectors) == len(b.Selectors) { 415 for i, sel := range a.Selectors { 416 if !sel.Equal(b.Selectors[i]) { 417 return false 418 } 419 } 420 return RulesEqual(a.Rules, b.Rules) 421 } 422 423 return false 424} 425 426func (r *RSelector) Hash() (uint32, bool) { 427 hash := uint32(5) 428 hash = helpers.HashCombine(hash, uint32(len(r.Selectors))) 429 for _, complex := range r.Selectors { 430 hash = helpers.HashCombine(hash, uint32(len(complex.Selectors))) 431 for _, sel := range complex.Selectors { 432 if sel.TypeSelector != nil { 433 hash = helpers.HashCombineString(hash, sel.TypeSelector.Name.Text) 434 } else { 435 hash = helpers.HashCombine(hash, 0) 436 } 437 hash = helpers.HashCombine(hash, uint32(len(sel.SubclassSelectors))) 438 for _, sub := range sel.SubclassSelectors { 439 hash = helpers.HashCombine(hash, sub.Hash()) 440 } 441 hash = helpers.HashCombineString(hash, sel.Combinator) 442 } 443 } 444 hash = HashRules(hash, r.Rules) 445 return hash, true 446} 447 448type RQualified struct { 449 Prelude []Token 450 Rules []Rule 451} 452 453func (a *RQualified) Equal(rule R) bool { 454 b, ok := rule.(*RQualified) 455 return ok && TokensEqual(a.Prelude, b.Prelude) && RulesEqual(a.Rules, b.Rules) 456} 457 458func (r *RQualified) Hash() (uint32, bool) { 459 hash := uint32(6) 460 hash = HashTokens(hash, r.Prelude) 461 hash = HashRules(hash, r.Rules) 462 return hash, true 463} 464 465type RDeclaration struct { 466 KeyText string 467 Value []Token 468 KeyRange logger.Range 469 Key D // Compare using this instead of "Key" for speed 470 Important bool 471} 472 473func (a *RDeclaration) Equal(rule R) bool { 474 b, ok := rule.(*RDeclaration) 475 return ok && a.KeyText == b.KeyText && TokensEqual(a.Value, b.Value) && a.Important == b.Important 476} 477 478func (r *RDeclaration) Hash() (uint32, bool) { 479 hash := uint32(7) 480 hash = helpers.HashCombine(hash, uint32(r.Key)) 481 hash = HashTokens(hash, r.Value) 482 return hash, true 483} 484 485type RBadDeclaration struct { 486 Tokens []Token 487} 488 489func (a *RBadDeclaration) Equal(rule R) bool { 490 b, ok := rule.(*RBadDeclaration) 491 return ok && TokensEqual(a.Tokens, b.Tokens) 492} 493 494func (r *RBadDeclaration) Hash() (uint32, bool) { 495 hash := uint32(8) 496 hash = HashTokens(hash, r.Tokens) 497 return hash, true 498} 499 500type RComment struct { 501 Text string 502} 503 504func (a *RComment) Equal(rule R) bool { 505 b, ok := rule.(*RComment) 506 return ok && a.Text == b.Text 507} 508 509func (r *RComment) Hash() (uint32, bool) { 510 hash := uint32(9) 511 hash = helpers.HashCombineString(hash, r.Text) 512 return hash, true 513} 514 515type ComplexSelector struct { 516 Selectors []CompoundSelector 517} 518 519func (a ComplexSelector) Equal(b ComplexSelector) bool { 520 if len(a.Selectors) != len(b.Selectors) { 521 return false 522 } 523 524 for i, ai := range a.Selectors { 525 bi := b.Selectors[i] 526 if ai.HasNestPrefix != bi.HasNestPrefix || ai.Combinator != bi.Combinator { 527 return false 528 } 529 530 if ats, bts := ai.TypeSelector, bi.TypeSelector; (ats == nil) != (bts == nil) { 531 return false 532 } else if ats != nil && bts != nil && !ats.Equal(*bts) { 533 return false 534 } 535 536 if len(ai.SubclassSelectors) != len(bi.SubclassSelectors) { 537 return false 538 } 539 for j, aj := range ai.SubclassSelectors { 540 if !aj.Equal(bi.SubclassSelectors[j]) { 541 return false 542 } 543 } 544 } 545 546 return true 547} 548 549type CompoundSelector struct { 550 HasNestPrefix bool // "&" 551 Combinator string // Optional, may be "" 552 TypeSelector *NamespacedName 553 SubclassSelectors []SS 554} 555 556type NameToken struct { 557 Kind css_lexer.T 558 Text string 559} 560 561type NamespacedName struct { 562 // If present, this is an identifier or "*" and is followed by a "|" character 563 NamespacePrefix *NameToken 564 565 // This is an identifier or "*" 566 Name NameToken 567} 568 569func (a NamespacedName) Equal(b NamespacedName) bool { 570 return a.Name == b.Name && (a.NamespacePrefix == nil) == (b.NamespacePrefix == nil) && 571 (a.NamespacePrefix == nil || b.NamespacePrefix == nil || *a.NamespacePrefix == *b.NamespacePrefix) 572} 573 574type SS interface { 575 Equal(ss SS) bool 576 Hash() uint32 577} 578 579type SSHash struct { 580 Name string 581} 582 583func (a *SSHash) Equal(ss SS) bool { 584 b, ok := ss.(*SSHash) 585 return ok && a.Name == b.Name 586} 587 588func (ss *SSHash) Hash() uint32 { 589 hash := uint32(1) 590 hash = helpers.HashCombineString(hash, ss.Name) 591 return hash 592} 593 594type SSClass struct { 595 Name string 596} 597 598func (a *SSClass) Equal(ss SS) bool { 599 b, ok := ss.(*SSClass) 600 return ok && a.Name == b.Name 601} 602 603func (ss *SSClass) Hash() uint32 { 604 hash := uint32(2) 605 hash = helpers.HashCombineString(hash, ss.Name) 606 return hash 607} 608 609type SSAttribute struct { 610 NamespacedName NamespacedName 611 MatcherOp string // Either "" or one of: "=" "~=" "|=" "^=" "$=" "*=" 612 MatcherValue string 613 MatcherModifier byte // Either 0 or one of: 'i' 'I' 's' 'S' 614} 615 616func (a *SSAttribute) Equal(ss SS) bool { 617 b, ok := ss.(*SSAttribute) 618 return ok && a.NamespacedName.Equal(b.NamespacedName) && a.MatcherOp == b.MatcherOp && 619 a.MatcherValue == b.MatcherValue && a.MatcherModifier == b.MatcherModifier 620} 621 622func (ss *SSAttribute) Hash() uint32 { 623 hash := uint32(3) 624 hash = helpers.HashCombineString(hash, ss.NamespacedName.Name.Text) 625 hash = helpers.HashCombineString(hash, ss.MatcherOp) 626 hash = helpers.HashCombineString(hash, ss.MatcherValue) 627 return hash 628} 629 630type SSPseudoClass struct { 631 Name string 632 Args []Token 633 IsElement bool // If true, this is prefixed by "::" instead of ":" 634} 635 636func (a *SSPseudoClass) Equal(ss SS) bool { 637 b, ok := ss.(*SSPseudoClass) 638 return ok && a.Name == b.Name && TokensEqual(a.Args, b.Args) && a.IsElement == b.IsElement 639} 640 641func (ss *SSPseudoClass) Hash() uint32 { 642 hash := uint32(4) 643 hash = helpers.HashCombineString(hash, ss.Name) 644 hash = HashTokens(hash, ss.Args) 645 return hash 646} 647