/
css_decls_box.go
197 lines (174 loc) · 5.37 KB
/
css_decls_box.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package css_parser
import (
"github.com/evanw/esbuild/internal/css_ast"
"github.com/evanw/esbuild/internal/css_lexer"
"github.com/evanw/esbuild/internal/logger"
)
const (
boxTop = iota
boxRight
boxBottom
boxLeft
)
type boxSide struct {
token css_ast.Token
unitSafety unitSafetyTracker
ruleIndex uint32 // The index of the originating rule in the rules array
wasSingleRule bool // True if the originating rule was just for this side
}
type boxTracker struct {
keyText string
sides [4]boxSide
allowAuto bool // If true, allow the "auto" keyword
important bool // True if all active rules were flagged as "!important"
key css_ast.D
}
type unitSafetyStatus uint8
const (
unitSafe unitSafetyStatus = iota // "margin: 0 1px 2cm 3%;"
unitUnsafeSingle // "margin: 0 1vw 2vw 3vw;"
unitUnsafeMixed // "margin: 0 1vw 2vh 3ch;"
)
// We can only compact rules together if they have the same unit safety level.
// We want to avoid a situation where the browser treats some of the original
// rules as valid and others as invalid.
//
// Safe:
// top: 1px; left: 0; bottom: 1px; right: 0;
// top: 1Q; left: 2Q; bottom: 3Q; right: 4Q;
//
// Unsafe:
// top: 1vh; left: 2vw; bottom: 3vh; right: 4vw;
// top: 1Q; left: 2Q; bottom: 3Q; right: 0;
// inset: 1Q 0 0 0; top: 0;
//
type unitSafetyTracker struct {
unit string
status unitSafetyStatus
}
func (a unitSafetyTracker) isSafeWith(b unitSafetyTracker) bool {
return a.status == b.status && a.status != unitUnsafeMixed && (a.status != unitUnsafeSingle || a.unit == b.unit)
}
func (t *unitSafetyTracker) includeUnitOf(token css_ast.Token) {
switch token.Kind {
case css_lexer.TNumber:
if token.Text == "0" {
return
}
case css_lexer.TPercentage:
return
case css_lexer.TDimension:
if token.DimensionUnitIsSafeLength() {
return
} else if unit := token.DimensionUnit(); t.status == unitSafe {
t.status = unitUnsafeSingle
t.unit = unit
return
} else if t.status == unitUnsafeSingle && t.unit == unit {
return
}
}
t.status = unitUnsafeMixed
}
func (box *boxTracker) updateSide(rules []css_ast.Rule, side int, new boxSide) {
if old := box.sides[side]; old.token.Kind != css_lexer.TEndOfFile &&
(!new.wasSingleRule || old.wasSingleRule) &&
old.unitSafety.status == unitSafe && new.unitSafety.status == unitSafe {
rules[old.ruleIndex] = css_ast.Rule{}
}
box.sides[side] = new
}
func (box *boxTracker) mangleSides(rules []css_ast.Rule, decl *css_ast.RDeclaration, index int, minifyWhitespace bool) {
// Reset if we see a change in the "!important" flag
if box.important != decl.Important {
box.sides = [4]boxSide{}
box.important = decl.Important
}
allowedIdent := ""
if box.allowAuto {
allowedIdent = "auto"
}
if quad, ok := expandTokenQuad(decl.Value, allowedIdent); ok {
// Use a single tracker for the whole rule
unitSafety := unitSafetyTracker{}
for _, t := range quad {
if !box.allowAuto || t.Kind.IsNumeric() {
unitSafety.includeUnitOf(t)
}
}
for side, t := range quad {
if unitSafety.status == unitSafe {
t.TurnLengthIntoNumberIfZero()
}
box.updateSide(rules, side, boxSide{
token: t,
ruleIndex: uint32(index),
unitSafety: unitSafety,
})
}
box.compactRules(rules, decl.KeyRange, minifyWhitespace)
} else {
box.sides = [4]boxSide{}
}
}
func (box *boxTracker) mangleSide(rules []css_ast.Rule, decl *css_ast.RDeclaration, index int, minifyWhitespace bool, side int) {
// Reset if we see a change in the "!important" flag
if box.important != decl.Important {
box.sides = [4]boxSide{}
box.important = decl.Important
}
if tokens := decl.Value; len(tokens) == 1 {
if t := tokens[0]; t.Kind.IsNumeric() || (t.Kind == css_lexer.TIdent && box.allowAuto && t.Text == "auto") {
unitSafety := unitSafetyTracker{}
if !box.allowAuto || t.Kind.IsNumeric() {
unitSafety.includeUnitOf(t)
}
if unitSafety.status == unitSafe && t.TurnLengthIntoNumberIfZero() {
tokens[0] = t
}
box.updateSide(rules, side, boxSide{
token: t,
ruleIndex: uint32(index),
wasSingleRule: true,
unitSafety: unitSafety,
})
box.compactRules(rules, decl.KeyRange, minifyWhitespace)
return
}
}
box.sides = [4]boxSide{}
}
func (box *boxTracker) compactRules(rules []css_ast.Rule, keyRange logger.Range, minifyWhitespace bool) {
// All tokens must be present
if eof := css_lexer.TEndOfFile; box.sides[0].token.Kind == eof || box.sides[1].token.Kind == eof ||
box.sides[2].token.Kind == eof || box.sides[3].token.Kind == eof {
return
}
// All tokens must have the same unit
for _, side := range box.sides[1:] {
if !side.unitSafety.isSafeWith(box.sides[0].unitSafety) {
return
}
}
// Generate the most minimal representation
tokens := compactTokenQuad(
box.sides[0].token,
box.sides[1].token,
box.sides[2].token,
box.sides[3].token,
minifyWhitespace,
)
// Remove all of the existing declarations
rules[box.sides[0].ruleIndex] = css_ast.Rule{}
rules[box.sides[1].ruleIndex] = css_ast.Rule{}
rules[box.sides[2].ruleIndex] = css_ast.Rule{}
rules[box.sides[3].ruleIndex] = css_ast.Rule{}
// Insert the combined declaration where the last rule was
rules[box.sides[3].ruleIndex].Data = &css_ast.RDeclaration{
Key: box.key,
KeyText: box.keyText,
Value: tokens,
KeyRange: keyRange,
Important: box.important,
}
}