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