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) IsZero() bool { 209 return t.Kind == css_lexer.TNumber && t.Text == "0" 210} 211 212func (t Token) IsOne() bool { 213 return t.Kind == css_lexer.TNumber && t.Text == "1" 214} 215 216type Rule struct { 217 Loc logger.Loc 218 Data R 219} 220 221type R interface { 222 Equal(rule R) bool 223 Hash() (uint32, bool) 224} 225 226func RulesEqual(a []Rule, b []Rule) bool { 227 if len(a) != len(b) { 228 return false 229 } 230 for i, c := range a { 231 if !c.Data.Equal(b[i].Data) { 232 return false 233 } 234 } 235 return true 236} 237 238func HashRules(hash uint32, rules []Rule) uint32 { 239 hash = helpers.HashCombine(hash, uint32(len(rules))) 240 for _, child := range rules { 241 if childHash, ok := child.Data.Hash(); ok { 242 hash = helpers.HashCombine(hash, childHash) 243 } else { 244 hash = helpers.HashCombine(hash, 0) 245 } 246 } 247 return hash 248} 249 250type RAtCharset struct { 251 Encoding string 252} 253 254func (a *RAtCharset) Equal(rule R) bool { 255 b, ok := rule.(*RAtCharset) 256 return ok && a.Encoding == b.Encoding 257} 258 259func (r *RAtCharset) Hash() (uint32, bool) { 260 hash := uint32(1) 261 hash = helpers.HashCombineString(hash, r.Encoding) 262 return hash, true 263} 264 265type RAtImport struct { 266 ImportRecordIndex uint32 267 ImportConditions []Token 268} 269 270func (*RAtImport) Equal(rule R) bool { 271 return false 272} 273 274func (r *RAtImport) Hash() (uint32, bool) { 275 return 0, false 276} 277 278type RAtKeyframes struct { 279 AtToken string 280 Name string 281 Blocks []KeyframeBlock 282} 283 284type KeyframeBlock struct { 285 Selectors []string 286 Rules []Rule 287} 288 289func (a *RAtKeyframes) Equal(rule R) bool { 290 b, ok := rule.(*RAtKeyframes) 291 if ok && a.AtToken == b.AtToken && a.Name == b.Name && len(a.Blocks) == len(b.Blocks) { 292 for i, ai := range a.Blocks { 293 bi := b.Blocks[i] 294 if len(ai.Selectors) != len(bi.Selectors) { 295 return false 296 } 297 for j, aj := range ai.Selectors { 298 if aj != bi.Selectors[j] { 299 return false 300 } 301 } 302 if !RulesEqual(ai.Rules, bi.Rules) { 303 return false 304 } 305 } 306 return true 307 } 308 return false 309} 310 311func (r *RAtKeyframes) Hash() (uint32, bool) { 312 hash := uint32(2) 313 hash = helpers.HashCombineString(hash, r.AtToken) 314 hash = helpers.HashCombineString(hash, r.Name) 315 hash = helpers.HashCombine(hash, uint32(len(r.Blocks))) 316 for _, block := range r.Blocks { 317 hash = helpers.HashCombine(hash, uint32(len(block.Selectors))) 318 for _, sel := range block.Selectors { 319 hash = helpers.HashCombineString(hash, sel) 320 } 321 hash = HashRules(hash, block.Rules) 322 } 323 return hash, true 324} 325 326type RKnownAt struct { 327 AtToken string 328 Prelude []Token 329 Rules []Rule 330} 331 332func (a *RKnownAt) Equal(rule R) bool { 333 b, ok := rule.(*RKnownAt) 334 return ok && a.AtToken == b.AtToken && TokensEqual(a.Prelude, b.Prelude) && RulesEqual(a.Rules, a.Rules) 335} 336 337func (r *RKnownAt) Hash() (uint32, bool) { 338 hash := uint32(3) 339 hash = helpers.HashCombineString(hash, r.AtToken) 340 hash = HashTokens(hash, r.Prelude) 341 hash = HashRules(hash, r.Rules) 342 return hash, true 343} 344 345type RUnknownAt struct { 346 AtToken string 347 Prelude []Token 348 Block []Token 349} 350 351func (a *RUnknownAt) Equal(rule R) bool { 352 b, ok := rule.(*RUnknownAt) 353 return ok && a.AtToken == b.AtToken && TokensEqual(a.Prelude, b.Prelude) && TokensEqual(a.Block, a.Block) 354} 355 356func (r *RUnknownAt) Hash() (uint32, bool) { 357 hash := uint32(4) 358 hash = helpers.HashCombineString(hash, r.AtToken) 359 hash = HashTokens(hash, r.Prelude) 360 hash = HashTokens(hash, r.Block) 361 return hash, true 362} 363 364type RSelector struct { 365 Selectors []ComplexSelector 366 Rules []Rule 367} 368 369func (a *RSelector) Equal(rule R) bool { 370 b, ok := rule.(*RSelector) 371 if ok && len(a.Selectors) == len(b.Selectors) { 372 for i, ai := range a.Selectors { 373 bi := b.Selectors[i] 374 if len(ai.Selectors) != len(bi.Selectors) { 375 return false 376 } 377 378 for j, aj := range ai.Selectors { 379 bj := bi.Selectors[j] 380 if aj.HasNestPrefix != bj.HasNestPrefix || aj.Combinator != bj.Combinator { 381 return false 382 } 383 384 if ats, bts := aj.TypeSelector, bj.TypeSelector; (ats == nil) != (bts == nil) { 385 return false 386 } else if ats != nil && bts != nil && !ats.Equal(*bts) { 387 return false 388 } 389 390 if len(aj.SubclassSelectors) != len(bj.SubclassSelectors) { 391 return false 392 } 393 for k, ak := range aj.SubclassSelectors { 394 if !ak.Equal(bj.SubclassSelectors[k]) { 395 return false 396 } 397 } 398 } 399 } 400 401 return RulesEqual(a.Rules, b.Rules) 402 } 403 404 return false 405} 406 407func (r *RSelector) Hash() (uint32, bool) { 408 hash := uint32(5) 409 hash = helpers.HashCombine(hash, uint32(len(r.Selectors))) 410 for _, complex := range r.Selectors { 411 hash = helpers.HashCombine(hash, uint32(len(complex.Selectors))) 412 for _, sel := range complex.Selectors { 413 if sel.TypeSelector != nil { 414 hash = helpers.HashCombineString(hash, sel.TypeSelector.Name.Text) 415 } else { 416 hash = helpers.HashCombine(hash, 0) 417 } 418 hash = helpers.HashCombine(hash, uint32(len(sel.SubclassSelectors))) 419 for _, sub := range sel.SubclassSelectors { 420 hash = helpers.HashCombine(hash, sub.Hash()) 421 } 422 hash = helpers.HashCombineString(hash, sel.Combinator) 423 } 424 } 425 hash = HashRules(hash, r.Rules) 426 return hash, true 427} 428 429type RQualified struct { 430 Prelude []Token 431 Rules []Rule 432} 433 434func (a *RQualified) Equal(rule R) bool { 435 b, ok := rule.(*RQualified) 436 return ok && TokensEqual(a.Prelude, b.Prelude) && RulesEqual(a.Rules, b.Rules) 437} 438 439func (r *RQualified) Hash() (uint32, bool) { 440 hash := uint32(6) 441 hash = HashTokens(hash, r.Prelude) 442 hash = HashRules(hash, r.Rules) 443 return hash, true 444} 445 446type RDeclaration struct { 447 KeyText string 448 Value []Token 449 KeyRange logger.Range 450 Key D // Compare using this instead of "Key" for speed 451 Important bool 452} 453 454func (a *RDeclaration) Equal(rule R) bool { 455 b, ok := rule.(*RDeclaration) 456 return ok && a.KeyText == b.KeyText && TokensEqual(a.Value, b.Value) && a.Important == b.Important 457} 458 459func (r *RDeclaration) Hash() (uint32, bool) { 460 hash := uint32(7) 461 hash = helpers.HashCombine(hash, uint32(r.Key)) 462 hash = HashTokens(hash, r.Value) 463 return hash, true 464} 465 466type RBadDeclaration struct { 467 Tokens []Token 468} 469 470func (a *RBadDeclaration) Equal(rule R) bool { 471 b, ok := rule.(*RBadDeclaration) 472 return ok && TokensEqual(a.Tokens, b.Tokens) 473} 474 475func (r *RBadDeclaration) Hash() (uint32, bool) { 476 hash := uint32(8) 477 hash = HashTokens(hash, r.Tokens) 478 return hash, true 479} 480 481type ComplexSelector struct { 482 Selectors []CompoundSelector 483} 484 485type CompoundSelector struct { 486 HasNestPrefix bool // "&" 487 Combinator string // Optional, may be "" 488 TypeSelector *NamespacedName 489 SubclassSelectors []SS 490} 491 492type NameToken struct { 493 Kind css_lexer.T 494 Text string 495} 496 497type NamespacedName struct { 498 // If present, this is an identifier or "*" and is followed by a "|" character 499 NamespacePrefix *NameToken 500 501 // This is an identifier or "*" or "&" 502 Name NameToken 503} 504 505func (a NamespacedName) Equal(b NamespacedName) bool { 506 return a.Name == b.Name && (a.NamespacePrefix == nil) == (b.NamespacePrefix == nil) && 507 (a.NamespacePrefix == nil || b.NamespacePrefix == nil || *a.NamespacePrefix == *b.NamespacePrefix) 508} 509 510type SS interface { 511 Equal(ss SS) bool 512 Hash() uint32 513} 514 515type SSHash struct { 516 Name string 517} 518 519func (a *SSHash) Equal(ss SS) bool { 520 b, ok := ss.(*SSHash) 521 return ok && a.Name == b.Name 522} 523 524func (ss *SSHash) Hash() uint32 { 525 hash := uint32(1) 526 hash = helpers.HashCombineString(hash, ss.Name) 527 return hash 528} 529 530type SSClass struct { 531 Name string 532} 533 534func (a *SSClass) Equal(ss SS) bool { 535 b, ok := ss.(*SSClass) 536 return ok && a.Name == b.Name 537} 538 539func (ss *SSClass) Hash() uint32 { 540 hash := uint32(2) 541 hash = helpers.HashCombineString(hash, ss.Name) 542 return hash 543} 544 545type SSAttribute struct { 546 NamespacedName NamespacedName 547 MatcherOp string 548 MatcherValue string 549 MatcherModifier byte 550} 551 552func (a *SSAttribute) Equal(ss SS) bool { 553 b, ok := ss.(*SSAttribute) 554 return ok && a.NamespacedName.Equal(b.NamespacedName) && a.MatcherOp == b.MatcherOp && 555 a.MatcherValue == b.MatcherValue && a.MatcherModifier == b.MatcherModifier 556} 557 558func (ss *SSAttribute) Hash() uint32 { 559 hash := uint32(3) 560 hash = helpers.HashCombineString(hash, ss.NamespacedName.Name.Text) 561 hash = helpers.HashCombineString(hash, ss.MatcherOp) 562 hash = helpers.HashCombineString(hash, ss.MatcherValue) 563 return hash 564} 565 566type SSPseudoClass struct { 567 Name string 568 Args []Token 569 IsElement bool // If true, this is prefixed by "::" instead of ":" 570} 571 572func (a *SSPseudoClass) Equal(ss SS) bool { 573 b, ok := ss.(*SSPseudoClass) 574 return ok && a.Name == b.Name && TokensEqual(a.Args, b.Args) && a.IsElement == b.IsElement 575} 576 577func (ss *SSPseudoClass) Hash() uint32 { 578 hash := uint32(4) 579 hash = helpers.HashCombineString(hash, ss.Name) 580 hash = HashTokens(hash, ss.Args) 581 return hash 582} 583