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