github.com/evanw/esbuild@v0.21.4/internal/css_parser/css_decls_border_radius.go (about) 1 package css_parser 2 3 import ( 4 "github.com/evanw/esbuild/internal/css_ast" 5 "github.com/evanw/esbuild/internal/css_lexer" 6 "github.com/evanw/esbuild/internal/logger" 7 ) 8 9 const ( 10 borderRadiusTopLeft = iota 11 borderRadiusTopRight 12 borderRadiusBottomRight 13 borderRadiusBottomLeft 14 ) 15 16 type borderRadiusCorner struct { 17 firstToken css_ast.Token 18 secondToken css_ast.Token 19 unitSafety unitSafetyTracker 20 ruleIndex uint32 // The index of the originating rule in the rules array 21 wasSingleRule bool // True if the originating rule was just for this side 22 } 23 24 type borderRadiusTracker struct { 25 corners [4]borderRadiusCorner 26 important bool // True if all active rules were flagged as "!important" 27 } 28 29 func (borderRadius *borderRadiusTracker) updateCorner(rules []css_ast.Rule, corner int, new borderRadiusCorner) { 30 if old := borderRadius.corners[corner]; old.firstToken.Kind != css_lexer.TEndOfFile && 31 (!new.wasSingleRule || old.wasSingleRule) && 32 old.unitSafety.status == unitSafe && new.unitSafety.status == unitSafe { 33 rules[old.ruleIndex] = css_ast.Rule{} 34 } 35 borderRadius.corners[corner] = new 36 } 37 38 func (borderRadius *borderRadiusTracker) mangleCorners(rules []css_ast.Rule, decl *css_ast.RDeclaration, minifyWhitespace bool) { 39 // Reset if we see a change in the "!important" flag 40 if borderRadius.important != decl.Important { 41 borderRadius.corners = [4]borderRadiusCorner{} 42 borderRadius.important = decl.Important 43 } 44 45 tokens := decl.Value 46 beforeSplit := len(tokens) 47 afterSplit := len(tokens) 48 49 // Search for the single slash if present 50 for i, t := range tokens { 51 if t.Kind == css_lexer.TDelimSlash { 52 if beforeSplit == len(tokens) { 53 beforeSplit = i 54 afterSplit = i + 1 55 } else { 56 // Multiple slashes are an error 57 borderRadius.corners = [4]borderRadiusCorner{} 58 return 59 } 60 } 61 } 62 63 // Use a single tracker for the whole rule 64 unitSafety := unitSafetyTracker{} 65 for _, t := range tokens[:beforeSplit] { 66 unitSafety.includeUnitOf(t) 67 } 68 for _, t := range tokens[afterSplit:] { 69 unitSafety.includeUnitOf(t) 70 } 71 72 firstRadii, firstRadiiOk := expandTokenQuad(tokens[:beforeSplit], "") 73 lastRadii, lastRadiiOk := expandTokenQuad(tokens[afterSplit:], "") 74 75 // Stop now if the pattern wasn't matched 76 if !firstRadiiOk || (beforeSplit < afterSplit && !lastRadiiOk) { 77 borderRadius.corners = [4]borderRadiusCorner{} 78 return 79 } 80 81 // Handle the first radii 82 for corner, t := range firstRadii { 83 if unitSafety.status == unitSafe { 84 t.TurnLengthIntoNumberIfZero() 85 } 86 borderRadius.updateCorner(rules, corner, borderRadiusCorner{ 87 firstToken: t, 88 secondToken: t, 89 unitSafety: unitSafety, 90 ruleIndex: uint32(len(rules) - 1), 91 }) 92 } 93 94 // Handle the last radii 95 if lastRadiiOk { 96 for corner, t := range lastRadii { 97 if unitSafety.status == unitSafe { 98 t.TurnLengthIntoNumberIfZero() 99 } 100 borderRadius.corners[corner].secondToken = t 101 } 102 } 103 104 // Success 105 borderRadius.compactRules(rules, decl.KeyRange, minifyWhitespace) 106 } 107 108 func (borderRadius *borderRadiusTracker) mangleCorner(rules []css_ast.Rule, decl *css_ast.RDeclaration, minifyWhitespace bool, corner int) { 109 // Reset if we see a change in the "!important" flag 110 if borderRadius.important != decl.Important { 111 borderRadius.corners = [4]borderRadiusCorner{} 112 borderRadius.important = decl.Important 113 } 114 115 if tokens := decl.Value; (len(tokens) == 1 && tokens[0].Kind.IsNumeric()) || 116 (len(tokens) == 2 && tokens[0].Kind.IsNumeric() && tokens[1].Kind.IsNumeric()) { 117 firstToken := tokens[0] 118 secondToken := firstToken 119 if len(tokens) == 2 { 120 secondToken = tokens[1] 121 } 122 123 // Check to see if these units are safe to use in every browser 124 unitSafety := unitSafetyTracker{} 125 unitSafety.includeUnitOf(firstToken) 126 unitSafety.includeUnitOf(secondToken) 127 128 // Only collapse "0unit" into "0" if the unit is safe 129 if unitSafety.status == unitSafe && firstToken.TurnLengthIntoNumberIfZero() { 130 tokens[0] = firstToken 131 } 132 if len(tokens) == 2 { 133 if unitSafety.status == unitSafe && secondToken.TurnLengthIntoNumberIfZero() { 134 tokens[1] = secondToken 135 } 136 137 // If both tokens are equal, merge them into one 138 if firstToken.EqualIgnoringWhitespace(secondToken) { 139 tokens[0].Whitespace &= ^css_ast.WhitespaceAfter 140 decl.Value = tokens[:1] 141 } 142 } 143 144 borderRadius.updateCorner(rules, corner, borderRadiusCorner{ 145 firstToken: firstToken, 146 secondToken: secondToken, 147 unitSafety: unitSafety, 148 ruleIndex: uint32(len(rules) - 1), 149 wasSingleRule: true, 150 }) 151 borderRadius.compactRules(rules, decl.KeyRange, minifyWhitespace) 152 } else { 153 borderRadius.corners = [4]borderRadiusCorner{} 154 } 155 } 156 157 func (borderRadius *borderRadiusTracker) compactRules(rules []css_ast.Rule, keyRange logger.Range, minifyWhitespace bool) { 158 // All tokens must be present 159 if eof := css_lexer.TEndOfFile; borderRadius.corners[0].firstToken.Kind == eof || borderRadius.corners[1].firstToken.Kind == eof || 160 borderRadius.corners[2].firstToken.Kind == eof || borderRadius.corners[3].firstToken.Kind == eof { 161 return 162 } 163 164 // All tokens must have the same unit 165 for _, side := range borderRadius.corners[1:] { 166 if !side.unitSafety.isSafeWith(borderRadius.corners[0].unitSafety) { 167 return 168 } 169 } 170 171 // Generate the most minimal representation 172 tokens := compactTokenQuad( 173 borderRadius.corners[0].firstToken, 174 borderRadius.corners[1].firstToken, 175 borderRadius.corners[2].firstToken, 176 borderRadius.corners[3].firstToken, 177 minifyWhitespace, 178 ) 179 secondTokens := compactTokenQuad( 180 borderRadius.corners[0].secondToken, 181 borderRadius.corners[1].secondToken, 182 borderRadius.corners[2].secondToken, 183 borderRadius.corners[3].secondToken, 184 minifyWhitespace, 185 ) 186 if !css_ast.TokensEqualIgnoringWhitespace(tokens, secondTokens) { 187 var whitespace css_ast.WhitespaceFlags 188 if !minifyWhitespace { 189 whitespace = css_ast.WhitespaceBefore | css_ast.WhitespaceAfter 190 } 191 tokens = append(tokens, css_ast.Token{ 192 Loc: tokens[len(tokens)-1].Loc, 193 Kind: css_lexer.TDelimSlash, 194 Text: "/", 195 Whitespace: whitespace, 196 }) 197 tokens = append(tokens, secondTokens...) 198 } 199 200 // Remove all of the existing declarations 201 var minLoc logger.Loc 202 for i, corner := range borderRadius.corners { 203 if loc := rules[corner.ruleIndex].Loc; i == 0 || loc.Start < minLoc.Start { 204 minLoc = loc 205 } 206 rules[corner.ruleIndex] = css_ast.Rule{} 207 } 208 209 // Insert the combined declaration where the last rule was 210 rules[borderRadius.corners[3].ruleIndex] = css_ast.Rule{Loc: minLoc, Data: &css_ast.RDeclaration{ 211 Key: css_ast.DBorderRadius, 212 KeyText: "border-radius", 213 Value: tokens, 214 KeyRange: keyRange, 215 Important: borderRadius.important, 216 }} 217 }