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