mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-09-24 16:58:50 +00:00
207 lines
5.5 KiB
Go
207 lines
5.5 KiB
Go
package css_parser
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"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, 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(len(rules) - 1),
|
|
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, 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 && strings.EqualFold(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(len(rules) - 1),
|
|
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) {
|
|
// Don't compact if the shorthand form is unsupported
|
|
if box.key == css_ast.DUnknown {
|
|
return
|
|
}
|
|
|
|
// 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
|
|
var minLoc logger.Loc
|
|
for i, side := range box.sides {
|
|
if loc := rules[side.ruleIndex].Loc; i == 0 || loc.Start < minLoc.Start {
|
|
minLoc = loc
|
|
}
|
|
rules[side.ruleIndex] = css_ast.Rule{}
|
|
}
|
|
|
|
// Insert the combined declaration where the last rule was
|
|
rules[box.sides[3].ruleIndex] = css_ast.Rule{Loc: minLoc, Data: &css_ast.RDeclaration{
|
|
Key: box.key,
|
|
KeyText: box.keyText,
|
|
Value: tokens,
|
|
KeyRange: keyRange,
|
|
Important: box.important,
|
|
}}
|
|
}
|