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  }