SandpointsGitHook/vendor/github.com/tdewolff/minify/v2/js/util.go
2021-03-20 23:21:23 +01:00

747 lines
21 KiB
Go

package js
import (
"bytes"
"encoding/hex"
"github.com/tdewolff/minify/v2"
"github.com/tdewolff/parse/v2/js"
"github.com/tdewolff/parse/v2/strconv"
)
var (
spaceBytes = []byte(" ")
newlineBytes = []byte("\n")
starBytes = []byte("*")
colonBytes = []byte(":")
semicolonBytes = []byte(";")
commaBytes = []byte(",")
dotBytes = []byte(".")
ellipsisBytes = []byte("...")
openBraceBytes = []byte("{")
closeBraceBytes = []byte("}")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
openParenBracketBytes = []byte("({")
closeBracketParenBytes = []byte("})")
closeParenOpenBracketBytes = []byte("){")
notBytes = []byte("!")
questionBytes = []byte("?")
equalBytes = []byte("=")
notNotBytes = []byte("!!")
andBytes = []byte("&&")
orBytes = []byte("||")
optChainBytes = []byte("?.")
nullishBytes = []byte("??")
arrowBytes = []byte("=>")
zeroBytes = []byte("0")
oneBytes = []byte("1")
letBytes = []byte("let")
getBytes = []byte("get")
setBytes = []byte("set")
asyncBytes = []byte("async")
functionBytes = []byte("function")
staticBytes = []byte("static")
ifOpenBytes = []byte("if(")
elseBytes = []byte("else")
withOpenBytes = []byte("with(")
doBytes = []byte("do")
whileOpenBytes = []byte("while(")
forOpenBytes = []byte("for(")
forAwaitOpenBytes = []byte("for await(")
inBytes = []byte("in")
ofBytes = []byte("of")
switchOpenBytes = []byte("switch(")
throwBytes = []byte("throw")
tryBytes = []byte("try")
catchBytes = []byte("catch")
finallyBytes = []byte("finally")
importBytes = []byte("import")
exportBytes = []byte("export")
fromBytes = []byte("from")
returnBytes = []byte("return")
classBytes = []byte("class")
asSpaceBytes = []byte("as ")
asyncSpaceBytes = []byte("async ")
spaceDefaultBytes = []byte(" default")
spaceExtendsBytes = []byte(" extends")
yieldBytes = []byte("yield")
newBytes = []byte("new")
openNewBytes = []byte("(new")
newTargetBytes = []byte("new.target")
importMetaBytes = []byte("import.meta")
varBytes = []byte("var")
varSpaceBytes = []byte("var ")
nanBytes = []byte("NaN")
undefinedBytes = []byte("undefined")
infinityBytes = []byte("Infinity")
voidZeroBytes = []byte("void 0")
groupedVoidZeroBytes = []byte("(void 0)")
oneDivZeroBytes = []byte("1/0")
groupedOneDivZeroBytes = []byte("(1/0)")
notZeroBytes = []byte("!0")
groupedNotZeroBytes = []byte("(!0)")
notOneBytes = []byte("!1")
groupedNotOneBytes = []byte("(!1)")
useStrictBytes = []byte(`"use strict"`)
debuggerBytes = []byte("debugger")
)
// precedence maps for the precedence inside the operation
var unaryPrecMap = map[js.TokenType]js.OpPrec{
js.PostIncrToken: js.OpLHS,
js.PostDecrToken: js.OpLHS,
js.PreIncrToken: js.OpUnary,
js.PreDecrToken: js.OpUnary,
js.NotToken: js.OpUnary,
js.BitNotToken: js.OpUnary,
js.TypeofToken: js.OpUnary,
js.VoidToken: js.OpUnary,
js.DeleteToken: js.OpUnary,
js.PosToken: js.OpUnary,
js.NegToken: js.OpUnary,
js.AwaitToken: js.OpUnary,
}
var binaryLeftPrecMap = map[js.TokenType]js.OpPrec{
js.EqToken: js.OpLHS,
js.MulEqToken: js.OpLHS,
js.DivEqToken: js.OpLHS,
js.ModEqToken: js.OpLHS,
js.ExpEqToken: js.OpLHS,
js.AddEqToken: js.OpLHS,
js.SubEqToken: js.OpLHS,
js.LtLtEqToken: js.OpLHS,
js.GtGtEqToken: js.OpLHS,
js.GtGtGtEqToken: js.OpLHS,
js.BitAndEqToken: js.OpLHS,
js.BitXorEqToken: js.OpLHS,
js.BitOrEqToken: js.OpLHS,
js.ExpToken: js.OpUpdate,
js.MulToken: js.OpMul,
js.DivToken: js.OpMul,
js.ModToken: js.OpMul,
js.AddToken: js.OpAdd,
js.SubToken: js.OpAdd,
js.LtLtToken: js.OpShift,
js.GtGtToken: js.OpShift,
js.GtGtGtToken: js.OpShift,
js.LtToken: js.OpCompare,
js.LtEqToken: js.OpCompare,
js.GtToken: js.OpCompare,
js.GtEqToken: js.OpCompare,
js.InToken: js.OpCompare,
js.InstanceofToken: js.OpCompare,
js.EqEqToken: js.OpEquals,
js.NotEqToken: js.OpEquals,
js.EqEqEqToken: js.OpEquals,
js.NotEqEqToken: js.OpEquals,
js.BitAndToken: js.OpBitAnd,
js.BitXorToken: js.OpBitXor,
js.BitOrToken: js.OpBitOr,
js.AndToken: js.OpAnd,
js.OrToken: js.OpOr,
js.NullishToken: js.OpBitOr, // or OpCoalesce
js.CommaToken: js.OpExpr,
}
var binaryRightPrecMap = map[js.TokenType]js.OpPrec{
js.EqToken: js.OpAssign,
js.MulEqToken: js.OpAssign,
js.DivEqToken: js.OpAssign,
js.ModEqToken: js.OpAssign,
js.ExpEqToken: js.OpAssign,
js.AddEqToken: js.OpAssign,
js.SubEqToken: js.OpAssign,
js.LtLtEqToken: js.OpAssign,
js.GtGtEqToken: js.OpAssign,
js.GtGtGtEqToken: js.OpAssign,
js.BitAndEqToken: js.OpAssign,
js.BitXorEqToken: js.OpAssign,
js.BitOrEqToken: js.OpAssign,
js.ExpToken: js.OpExp,
js.MulToken: js.OpExp,
js.DivToken: js.OpExp,
js.ModToken: js.OpExp,
js.AddToken: js.OpMul,
js.SubToken: js.OpMul,
js.LtLtToken: js.OpAdd,
js.GtGtToken: js.OpAdd,
js.GtGtGtToken: js.OpAdd,
js.LtToken: js.OpShift,
js.LtEqToken: js.OpShift,
js.GtToken: js.OpShift,
js.GtEqToken: js.OpShift,
js.InToken: js.OpShift,
js.InstanceofToken: js.OpShift,
js.EqEqToken: js.OpCompare,
js.NotEqToken: js.OpCompare,
js.EqEqEqToken: js.OpCompare,
js.NotEqEqToken: js.OpCompare,
js.BitAndToken: js.OpEquals,
js.BitXorToken: js.OpBitAnd,
js.BitOrToken: js.OpBitXor,
js.AndToken: js.OpAnd, // changes order in AST but not in execution
js.OrToken: js.OpOr, // changes order in AST but not in execution
js.NullishToken: js.OpBitOr, // or OpCoalesce
js.CommaToken: js.OpAssign,
}
// precedence maps of the operation itself
var unaryOpPrecMap = map[js.TokenType]js.OpPrec{
js.PostIncrToken: js.OpUpdate,
js.PostDecrToken: js.OpUpdate,
js.PreIncrToken: js.OpUpdate,
js.PreDecrToken: js.OpUpdate,
js.NotToken: js.OpUnary,
js.BitNotToken: js.OpUnary,
js.TypeofToken: js.OpUnary,
js.VoidToken: js.OpUnary,
js.DeleteToken: js.OpUnary,
js.PosToken: js.OpUnary,
js.NegToken: js.OpUnary,
js.AwaitToken: js.OpUnary,
}
var binaryOpPrecMap = map[js.TokenType]js.OpPrec{
js.EqToken: js.OpAssign,
js.MulEqToken: js.OpAssign,
js.DivEqToken: js.OpAssign,
js.ModEqToken: js.OpAssign,
js.ExpEqToken: js.OpAssign,
js.AddEqToken: js.OpAssign,
js.SubEqToken: js.OpAssign,
js.LtLtEqToken: js.OpAssign,
js.GtGtEqToken: js.OpAssign,
js.GtGtGtEqToken: js.OpAssign,
js.BitAndEqToken: js.OpAssign,
js.BitXorEqToken: js.OpAssign,
js.BitOrEqToken: js.OpAssign,
js.ExpToken: js.OpExp,
js.MulToken: js.OpMul,
js.DivToken: js.OpMul,
js.ModToken: js.OpMul,
js.AddToken: js.OpAdd,
js.SubToken: js.OpAdd,
js.LtLtToken: js.OpShift,
js.GtGtToken: js.OpShift,
js.GtGtGtToken: js.OpShift,
js.LtToken: js.OpCompare,
js.LtEqToken: js.OpCompare,
js.GtToken: js.OpCompare,
js.GtEqToken: js.OpCompare,
js.InToken: js.OpCompare,
js.InstanceofToken: js.OpCompare,
js.EqEqToken: js.OpEquals,
js.NotEqToken: js.OpEquals,
js.EqEqEqToken: js.OpEquals,
js.NotEqEqToken: js.OpEquals,
js.BitAndToken: js.OpBitAnd,
js.BitXorToken: js.OpBitXor,
js.BitOrToken: js.OpBitOr,
js.AndToken: js.OpAnd,
js.OrToken: js.OpOr,
js.NullishToken: js.OpCoalesce,
js.CommaToken: js.OpExpr,
}
func exprPrec(i js.IExpr) js.OpPrec {
switch expr := i.(type) {
case *js.Var, *js.LiteralExpr, *js.ArrayExpr, *js.ObjectExpr, *js.FuncDecl, *js.ClassDecl:
return js.OpPrimary
case *js.UnaryExpr:
return unaryOpPrecMap[expr.Op]
case *js.BinaryExpr:
return binaryOpPrecMap[expr.Op]
case *js.NewExpr:
if expr.Args == nil {
return js.OpNew
}
return js.OpMember
case *js.TemplateExpr:
if expr.Tag == nil {
return js.OpPrimary
}
return expr.Prec
case *js.DotExpr:
return expr.Prec
case *js.IndexExpr:
return expr.Prec
case *js.NewTargetExpr, *js.ImportMetaExpr:
return js.OpMember
case *js.OptChainExpr, *js.CallExpr:
return js.OpCall
case *js.CondExpr, *js.YieldExpr, *js.ArrowFunc:
return js.OpAssign
case *js.GroupExpr:
return exprPrec(expr.X)
}
return js.OpExpr // does not happen
}
// TODO: use in more cases
func groupExpr(i js.IExpr, prec js.OpPrec) js.IExpr {
precInside := exprPrec(i)
if _, ok := i.(*js.GroupExpr); !ok && precInside < prec && (precInside != js.OpCoalesce || prec != js.OpBitOr) {
return &js.GroupExpr{X: i}
}
return i
}
// TODO: use in more cases
func condExpr(cond, x, y js.IExpr) js.IExpr {
return &js.CondExpr{
Cond: groupExpr(cond, js.OpCoalesce),
X: groupExpr(x, js.OpAssign),
Y: groupExpr(y, js.OpAssign),
}
}
func (m *jsMinifier) isEmptyStmt(stmt js.IStmt) bool {
if stmt == nil {
return true
} else if _, ok := stmt.(*js.EmptyStmt); ok {
return true
} else if decl, ok := stmt.(*js.VarDecl); ok && m.varsHoisted != nil && decl != m.varsHoisted {
for _, item := range decl.List {
if item.Default != nil {
return false
}
}
return true
} else if block, ok := stmt.(*js.BlockStmt); ok {
for _, item := range block.List {
if ok := m.isEmptyStmt(item); !ok {
return false
}
}
return true
}
return false
}
func finalExpr(expr js.IExpr) js.IExpr {
if group, ok := expr.(*js.GroupExpr); ok {
expr = group.X
}
if binary, ok := expr.(*js.BinaryExpr); ok && binary.Op == js.CommaToken {
expr = binary.Y
}
if binary, ok := expr.(*js.BinaryExpr); ok && binary.Op == js.EqToken {
expr = binary.X
}
return expr
}
func isFlowStmt(stmt js.IStmt) bool {
if _, ok := stmt.(*js.ReturnStmt); ok {
return true
} else if _, ok := stmt.(*js.ThrowStmt); ok {
return true
} else if _, ok := stmt.(*js.BranchStmt); ok {
return true
}
return false
}
func lastStmt(stmt js.IStmt) js.IStmt {
if block, ok := stmt.(*js.BlockStmt); ok && 0 < len(block.List) {
return block.List[len(block.List)-1]
}
return stmt
}
func (m *jsMinifier) isTrue(i js.IExpr) bool {
if lit, ok := i.(*js.LiteralExpr); ok && lit.TokenType == js.TrueToken {
return true
} else if unary, ok := i.(*js.UnaryExpr); ok && unary.Op == js.NotToken {
ret, _ := m.isFalsy(unary.X)
return ret
}
return false
}
func (m *jsMinifier) isFalse(i js.IExpr) bool {
if lit, ok := i.(*js.LiteralExpr); ok {
return lit.TokenType == js.FalseToken
} else if unary, ok := i.(*js.UnaryExpr); ok && unary.Op == js.NotToken {
ret, _ := m.isTruthy(unary.X)
return ret
}
return false
}
func (m *jsMinifier) toNullishExpr(condExpr *js.CondExpr) (js.IExpr, js.IExpr, bool) {
// convert conditional expression to nullish: a!=null?a:b => a??b
if binaryExpr, ok := condExpr.Cond.(*js.BinaryExpr); ok && (binaryExpr.Op == js.EqEqToken || binaryExpr.Op == js.NotEqToken) {
var left, right js.IExpr
if binaryExpr.Op == js.EqEqToken {
left = condExpr.Y
right = condExpr.X
} else {
left = condExpr.X
right = condExpr.Y
}
if lit, ok := binaryExpr.X.(*js.LiteralExpr); ((ok && lit.TokenType == js.NullToken) || m.isUndefined(binaryExpr.X)) && m.isEqualExpr(binaryExpr.Y, left) {
return left, right, true
} else if lit, ok := binaryExpr.Y.(*js.LiteralExpr); ((ok && lit.TokenType == js.NullToken) || m.isUndefined(binaryExpr.Y)) && m.isEqualExpr(binaryExpr.X, left) {
return left, right, true
}
}
return nil, nil, false
}
func (m *jsMinifier) isUndefined(i js.IExpr) bool {
if v, ok := i.(*js.Var); ok {
if bytes.Equal(v.Name(), undefinedBytes) { // TODO: only if not defined
return true
}
} else if unary, ok := i.(*js.UnaryExpr); ok && unary.Op == js.VoidToken {
return true
}
return false
}
// returns whether truthy and whether it could be coerced to a boolean (i.e. when returns (false,true) this means it is falsy)
func (m *jsMinifier) isTruthy(i js.IExpr) (bool, bool) {
if falsy, ok := m.isFalsy(i); ok {
return !falsy, true
}
return false, false
}
// returns whether falsy and whether it could be coerced to a boolean (i.e. when returns (false,true) this means it is truthy)
func (m *jsMinifier) isFalsy(i js.IExpr) (bool, bool) {
negated := false
group, isGroup := i.(*js.GroupExpr)
unary, isUnary := i.(*js.UnaryExpr)
for isGroup || isUnary && unary.Op == js.NotToken {
if isGroup {
i = group.X
} else {
i = unary.X
negated = !negated
}
group, isGroup = i.(*js.GroupExpr)
unary, isUnary = i.(*js.UnaryExpr)
}
if lit, ok := i.(*js.LiteralExpr); ok {
tt := lit.TokenType
d := lit.Data
if tt == js.FalseToken || tt == js.NullToken || tt == js.StringToken && len(lit.Data) == 0 {
return !negated, true // falsy
} else if tt == js.TrueToken || tt == js.StringToken {
return negated, true // truthy
} else if tt == js.DecimalToken || tt == js.BinaryToken || tt == js.OctalToken || tt == js.HexadecimalToken || tt == js.BigIntToken {
for _, c := range d {
if c == 'e' || c == 'E' || c == 'n' {
break
} else if c != '0' && c != '.' && c != 'x' && c != 'X' && c != 'b' && c != 'B' && c != 'o' && c != 'O' {
return negated, true // truthy
}
}
return !negated, true // falsy
}
} else if m.isUndefined(i) {
return !negated, true // falsy
} else if v, ok := i.(*js.Var); ok && bytes.Equal(v.Name(), nanBytes) {
return !negated, true // falsy
}
return false, false // unknown
}
func (m *jsMinifier) isEqualExpr(a, b js.IExpr) bool {
if group, ok := a.(*js.GroupExpr); ok {
a = group.X
}
if group, ok := b.(*js.GroupExpr); ok {
b = group.X
}
if left, ok := a.(*js.Var); ok {
if right, ok := b.(*js.Var); ok {
return bytes.Equal(left.Name(), right.Name())
}
}
// TODO: use reflect.DeepEqual?
return false
}
func isBooleanExpr(expr js.IExpr) bool {
if unaryExpr, ok := expr.(*js.UnaryExpr); ok {
return unaryExpr.Op == js.NotToken
} else if binaryExpr, ok := expr.(*js.BinaryExpr); ok {
op := binaryOpPrecMap[binaryExpr.Op]
return op == js.OpCompare || op == js.OpEquals
} else if litExpr, ok := expr.(*js.LiteralExpr); ok {
return litExpr.TokenType == js.TrueToken || litExpr.TokenType == js.FalseToken
} else if groupExpr, ok := expr.(*js.GroupExpr); ok {
return isBooleanExpr(groupExpr.X)
}
return false
}
func (m *jsMinifier) minifyBooleanExpr(expr js.IExpr, invert bool, prec js.OpPrec) {
if invert {
// unary !(boolean) has already been handled
if binaryExpr, ok := expr.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
if binaryExpr.Op == js.EqEqToken {
binaryExpr.Op = js.NotEqToken
} else if binaryExpr.Op == js.NotEqToken {
binaryExpr.Op = js.EqEqToken
} else if binaryExpr.Op == js.EqEqEqToken {
binaryExpr.Op = js.NotEqEqToken
} else if binaryExpr.Op == js.NotEqEqToken {
binaryExpr.Op = js.EqEqEqToken
}
m.minifyExpr(expr, prec)
} else {
m.write(notBytes)
m.minifyExpr(&js.GroupExpr{X: expr}, js.OpUnary)
}
} else if isBooleanExpr(expr) {
m.minifyExpr(&js.GroupExpr{X: expr}, prec)
} else {
m.write(notNotBytes)
m.minifyExpr(&js.GroupExpr{X: expr}, js.OpUnary)
}
}
func endsInIf(istmt js.IStmt) bool {
switch stmt := istmt.(type) {
case *js.IfStmt:
if stmt.Else == nil {
return true
}
return endsInIf(stmt.Else)
case *js.BlockStmt:
if 0 < len(stmt.List) {
return endsInIf(stmt.List[len(stmt.List)-1])
}
case *js.LabelledStmt:
return endsInIf(stmt.Value)
case *js.WithStmt:
return endsInIf(stmt.Body)
case *js.WhileStmt:
return endsInIf(stmt.Body)
case *js.ForStmt:
return endsInIf(&stmt.Body)
case *js.ForInStmt:
return endsInIf(&stmt.Body)
case *js.ForOfStmt:
return endsInIf(&stmt.Body)
}
return false
}
func isHexDigit(b byte) bool {
return '0' <= b && b <= '9' || 'a' <= b && b <= 'f' || 'A' <= b && b <= 'F'
}
func minifyString(b []byte) []byte {
if len(b) < 3 {
return b
}
// switch quotes if more optimal
singleQuotes := 0
doubleQuotes := 0
for i := 1; i < len(b)-1; i++ {
if b[i] == '\'' {
singleQuotes++
} else if b[i] == '"' {
doubleQuotes++
}
}
quote := b[0]
if singleQuotes < doubleQuotes {
quote = byte('\'')
} else if doubleQuotes < singleQuotes {
quote = byte('"')
}
b[0] = quote
b[len(b)-1] = quote
// strip unnecessary escapes
j := 0
start := 0
for i := 1; i < len(b)-1; i++ {
if c := b[i]; c == '\\' {
c = b[i+1]
if c == '0' && (i+2 == len(b)-1 || b[i+2] < '0' || '7' < b[i+2]) || c == '\\' || c == quote || c == 'n' || c == 'r' || c == 'u' {
// keep escape sequence
i++
continue
}
n := 1
if c == '\n' || c == '\r' || c == 0xE2 && i+3 < len(b)-1 && b[i+2] == 0x80 && (b[i+3] == 0xA8 || b[i+3] == 0xA9) {
// line continuations
if c == 0xE2 {
n = 4
} else if c == '\r' && i+2 < len(b)-1 && b[i+2] == '\n' {
n = 3
} else {
n = 2
}
} else if c == 'x' {
if i+3 < len(b)-1 && isHexDigit(b[i+2]) && b[i+2] < '8' && isHexDigit(b[i+3]) {
// hexadecimal escapes
_, _ = hex.Decode(b[i+3:i+4:i+4], b[i+2:i+4])
n = 3
if b[i+3] == 0 || b[i+3] == '\\' || b[i+3] == quote || b[i+3] == '\n' || b[i+3] == '\r' {
if b[i+3] == 0 {
b[i+3] = '0'
} else if b[i+3] == '\n' {
b[i+3] = 'n'
} else if b[i+3] == '\r' {
b[i+3] = 'r'
}
n--
b[i+2] = '\\'
}
} else {
i++
continue
}
} else if '0' <= c && c <= '7' {
// octal escapes (legacy), \0 already handled
num := byte(c - '0')
if i+2 < len(b)-1 && '0' <= b[i+2] && b[i+2] <= '7' {
num = num*8 + byte(b[i+2]-'0')
n++
if num < 32 && i+3 < len(b)-1 && '0' <= b[i+3] && b[i+3] <= '7' {
num = num*8 + byte(b[i+3]-'0')
n++
}
}
b[i+n] = num
if num == 0 || num == '\\' || num == quote || num == '\n' || num == '\r' {
if num == 0 {
b[i+n] = '0'
} else if num == '\n' {
b[i+n] = 'n'
} else if num == '\r' {
b[i+n] = 'r'
}
n--
b[i+n] = '\\'
}
} else if c == 't' {
b[i+1] = '\t'
} else if c == 'f' {
b[i+1] = '\f'
} else if c == 'v' {
b[i+1] = '\v'
} else if c == 'b' {
b[i+1] = '\b'
}
// remove unnecessary escape character, anything but 0x00, 0x0A, 0x0D, \, ' or "
if start != 0 {
j += copy(b[j:], b[start:i])
} else {
j = i
}
start = i + n
i += n - 1
} else if c == quote {
// may not be escaped properly when changing quotes
if j < start {
// avoid append
j += copy(b[j:], b[start:i])
b[j] = '\\'
j++
start = i
} else {
b = append(append(b[:i], '\\'), b[i:]...)
i++
b[i] = quote // was overwritten above
}
} else if c == '<' && 9 <= len(b)-1-i {
if b[i+1] == '\\' && 10 <= len(b)-1-i && bytes.Equal(b[i+2:i+10], []byte("/script>")) {
i += 9
} else if bytes.Equal(b[i+1:i+9], []byte("/script>")) {
i++
if j < start {
// avoid append
j += copy(b[j:], b[start:i])
b[j] = '\\'
j++
start = i
} else {
b = append(append(b[:i], '\\'), b[i:]...)
i++
b[i] = '/' // was overwritten above
}
}
}
}
if start != 0 {
j += copy(b[j:], b[start:])
return b[:j]
}
return b
}
func binaryNumber(b []byte, prec int) []byte {
if len(b) <= 2 || 65 < len(b) {
return b
}
var n int64
for _, c := range b[2:] {
n *= 2
n += int64(c - '0')
}
i := strconv.LenInt(n) - 1
b = b[:i+1]
for 0 <= i {
b[i] = byte('0' + n%10)
n /= 10
i--
}
return minify.Number(b, prec)
}
func octalNumber(b []byte, prec int) []byte {
if len(b) <= 2 || 23 < len(b) {
return b
}
var n int64
for _, c := range b[2:] {
n *= 8
n += int64(c - '0')
}
i := strconv.LenInt(n) - 1
b = b[:i+1]
for 0 <= i {
b[i] = byte('0' + n%10)
n /= 10
i--
}
return minify.Number(b, prec)
}
func hexadecimalNumber(b []byte, prec int) []byte {
if len(b) <= 2 || 12 < len(b) || len(b) == 12 && ('D' < b[2] && b[2] <= 'F' || 'd' < b[2]) {
return b
}
var n int64
for _, c := range b[2:] {
n *= 16
if c <= '9' {
n += int64(c - '0')
} else if c <= 'F' {
n += 10 + int64(c-'A')
} else {
n += 10 + int64(c-'a')
}
}
i := strconv.LenInt(n) - 1
b = b[:i+1]
for 0 <= i {
b[i] = byte('0' + n%10)
n /= 10
i--
}
return minify.Number(b, prec)
}