summaryrefslogtreecommitdiff
path: root/vendor/github.com/tdewolff/minify/v2/js/util.go
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2024-10-25 00:47:47 +0200
committerMitja Felicijan <mitja.felicijan@gmail.com>2024-10-25 00:47:47 +0200
commitc6cc0108ca7738023b45e0eeac0fa2390532dd93 (patch)
tree36890e6cd3091bbab8efbe686cc56f467f645bfd /vendor/github.com/tdewolff/minify/v2/js/util.go
parent0130404a1dc663d4aa68d780c9bcb23a4243e68d (diff)
downloadjbmafp-master.tar.gz
Added vendor lock on depsHEADmaster
Diffstat (limited to 'vendor/github.com/tdewolff/minify/v2/js/util.go')
-rw-r--r--vendor/github.com/tdewolff/minify/v2/js/util.go1361
1 files changed, 1361 insertions, 0 deletions
diff --git a/vendor/github.com/tdewolff/minify/v2/js/util.go b/vendor/github.com/tdewolff/minify/v2/js/util.go
new file mode 100644
index 0000000..6883d93
--- /dev/null
+++ b/vendor/github.com/tdewolff/minify/v2/js/util.go
@@ -0,0 +1,1361 @@
+package js
+
+import (
+ "bytes"
+ "encoding/hex"
+ stdStrconv "strconv"
+ "unicode/utf8"
+
+ "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("({")
+ closeParenOpenBracketBytes = []byte("){")
+ notBytes = []byte("!")
+ questionBytes = []byte("?")
+ equalBytes = []byte("=")
+ optChainBytes = []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")
+ nanBytes = []byte("NaN")
+ undefinedBytes = []byte("undefined")
+ infinityBytes = []byte("Infinity")
+ nullBytes = []byte("null")
+ 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)")
+ debuggerBytes = []byte("debugger")
+ regExpScriptBytes = []byte("/script>")
+)
+
+func 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 && decl.TokenType == js.ErrorToken {
+ 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 := isEmptyStmt(item); !ok {
+ return false
+ }
+ }
+ return true
+ }
+ return false
+}
+
+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 lastStmt(block.List[len(block.List)-1])
+ }
+ return stmt
+}
+
+func endsInIf(istmt js.IStmt) bool {
+ switch stmt := istmt.(type) {
+ case *js.IfStmt:
+ if stmt.Else == nil {
+ _, ok := optimizeStmt(stmt).(*js.IfStmt)
+ return ok
+ }
+ 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
+}
+
+// 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.CallExpr:
+ return js.OpCall
+ case *js.CondExpr, *js.YieldExpr, *js.ArrowFunc:
+ return js.OpAssign
+ case *js.GroupExpr:
+ return exprPrec(expr.X)
+ }
+ return js.OpExpr // CommaExpr
+}
+
+func hasSideEffects(i js.IExpr) bool {
+ // assume that variable usage and that the index operator themselves have no side effects
+ switch expr := i.(type) {
+ case *js.Var, *js.LiteralExpr, *js.FuncDecl, *js.ClassDecl, *js.ArrowFunc, *js.NewTargetExpr, *js.ImportMetaExpr:
+ return false
+ case *js.NewExpr, *js.CallExpr, *js.YieldExpr:
+ return true
+ case *js.GroupExpr:
+ return hasSideEffects(expr.X)
+ case *js.DotExpr:
+ return hasSideEffects(expr.X)
+ case *js.IndexExpr:
+ return hasSideEffects(expr.X) || hasSideEffects(expr.Y)
+ case *js.CondExpr:
+ return hasSideEffects(expr.Cond) || hasSideEffects(expr.X) || hasSideEffects(expr.Y)
+ case *js.CommaExpr:
+ for _, item := range expr.List {
+ if hasSideEffects(item) {
+ return true
+ }
+ }
+ case *js.ArrayExpr:
+ for _, item := range expr.List {
+ if hasSideEffects(item.Value) {
+ return true
+ }
+ }
+ return false
+ case *js.ObjectExpr:
+ for _, item := range expr.List {
+ if hasSideEffects(item.Value) || item.Init != nil && hasSideEffects(item.Init) || item.Name != nil && item.Name.IsComputed() && hasSideEffects(item.Name.Computed) {
+ return true
+ }
+ }
+ return false
+ case *js.TemplateExpr:
+ if hasSideEffects(expr.Tag) {
+ return true
+ }
+ for _, item := range expr.List {
+ if hasSideEffects(item.Expr) {
+ return true
+ }
+ }
+ return false
+ case *js.UnaryExpr:
+ if expr.Op == js.DeleteToken || expr.Op == js.PreIncrToken || expr.Op == js.PreDecrToken || expr.Op == js.PostIncrToken || expr.Op == js.PostDecrToken {
+ return true
+ }
+ return hasSideEffects(expr.X)
+ case *js.BinaryExpr:
+ return binaryOpPrecMap[expr.Op] == js.OpAssign
+ }
+ return true
+}
+
+// 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 {
+ if comma, ok := cond.(*js.CommaExpr); ok {
+ comma.List[len(comma.List)-1] = &js.CondExpr{
+ Cond: groupExpr(comma.List[len(comma.List)-1], js.OpCoalesce),
+ X: groupExpr(x, js.OpAssign),
+ Y: groupExpr(y, js.OpAssign),
+ }
+ return comma
+ }
+ return &js.CondExpr{
+ Cond: groupExpr(cond, js.OpCoalesce),
+ X: groupExpr(x, js.OpAssign),
+ Y: groupExpr(y, js.OpAssign),
+ }
+}
+
+func commaExpr(x, y js.IExpr) js.IExpr {
+ comma, ok := x.(*js.CommaExpr)
+ if !ok {
+ comma = &js.CommaExpr{List: []js.IExpr{x}}
+ }
+ if comma2, ok := y.(*js.CommaExpr); ok {
+ comma.List = append(comma.List, comma2.List...)
+ } else {
+ comma.List = append(comma.List, y)
+ }
+ return comma
+}
+
+func innerExpr(i js.IExpr) js.IExpr {
+ for {
+ if group, ok := i.(*js.GroupExpr); ok {
+ i = group.X
+ } else {
+ return i
+ }
+ }
+}
+
+func finalExpr(i js.IExpr) js.IExpr {
+ i = innerExpr(i)
+ if comma, ok := i.(*js.CommaExpr); ok {
+ i = comma.List[len(comma.List)-1]
+ }
+ if binary, ok := i.(*js.BinaryExpr); ok && binary.Op == js.EqToken {
+ i = binary.X // return first
+ }
+ return i
+}
+
+func isTrue(i js.IExpr) bool {
+ i = innerExpr(i)
+ 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, _ := isFalsy(unary.X)
+ return ret
+ }
+ return false
+}
+
+func isFalse(i js.IExpr) bool {
+ i = innerExpr(i)
+ 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, _ := isTruthy(unary.X)
+ return ret
+ }
+ return false
+}
+
+func isEqualExpr(a, b js.IExpr) bool {
+ a = innerExpr(a)
+ b = innerExpr(b)
+ 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 toNullishExpr(condExpr *js.CondExpr) (js.IExpr, bool) {
+ if v, not, ok := isUndefinedOrNullVar(condExpr.Cond); ok {
+ left, right := condExpr.X, condExpr.Y
+ if not {
+ left, right = right, left
+ }
+ if isEqualExpr(v, right) {
+ // convert conditional expression to nullish: a==null?b:a => a??b
+ return &js.BinaryExpr{js.NullishToken, groupExpr(right, binaryLeftPrecMap[js.NullishToken]), groupExpr(left, binaryRightPrecMap[js.NullishToken])}, true
+ } else if isUndefined(left) {
+ // convert conditional expression to optional expr: a==null?undefined:a.b => a?.b
+ expr := right
+ var parent js.IExpr
+ for {
+ prevExpr := expr
+ if callExpr, ok := expr.(*js.CallExpr); ok {
+ expr = callExpr.X
+ } else if dotExpr, ok := expr.(*js.DotExpr); ok {
+ expr = dotExpr.X
+ } else if indexExpr, ok := expr.(*js.IndexExpr); ok {
+ expr = indexExpr.X
+ } else if templateExpr, ok := expr.(*js.TemplateExpr); ok {
+ expr = templateExpr.Tag
+ } else {
+ break
+ }
+ parent = prevExpr
+ }
+ if parent != nil && isEqualExpr(v, expr) {
+ if callExpr, ok := parent.(*js.CallExpr); ok {
+ callExpr.Optional = true
+ } else if dotExpr, ok := parent.(*js.DotExpr); ok {
+ dotExpr.Optional = true
+ } else if indexExpr, ok := parent.(*js.IndexExpr); ok {
+ indexExpr.Optional = true
+ } else if templateExpr, ok := parent.(*js.TemplateExpr); ok {
+ templateExpr.Optional = true
+ }
+ return right, true
+ }
+ }
+ }
+ return nil, false
+}
+
+func isUndefinedOrNullVar(i js.IExpr) (*js.Var, bool, bool) {
+ i = innerExpr(i)
+ if binary, ok := i.(*js.BinaryExpr); ok && (binary.Op == js.OrToken || binary.Op == js.AndToken) {
+ eqEqOp := js.EqEqToken
+ eqEqEqOp := js.EqEqEqToken
+ if binary.Op == js.AndToken {
+ eqEqOp = js.NotEqToken
+ eqEqEqOp = js.NotEqEqToken
+ }
+
+ left, isBinaryX := innerExpr(binary.X).(*js.BinaryExpr)
+ right, isBinaryY := innerExpr(binary.Y).(*js.BinaryExpr)
+ if isBinaryX && isBinaryY && (left.Op == eqEqOp || left.Op == eqEqEqOp) && (right.Op == eqEqOp || right.Op == eqEqEqOp) {
+ var leftVar, rightVar *js.Var
+ if v, ok := left.X.(*js.Var); ok && isUndefinedOrNull(left.Y) {
+ leftVar = v
+ } else if v, ok := left.Y.(*js.Var); ok && isUndefinedOrNull(left.X) {
+ leftVar = v
+ }
+ if v, ok := right.X.(*js.Var); ok && isUndefinedOrNull(right.Y) {
+ rightVar = v
+ } else if v, ok := right.Y.(*js.Var); ok && isUndefinedOrNull(right.X) {
+ rightVar = v
+ }
+ if leftVar != nil && leftVar == rightVar {
+ return leftVar, binary.Op == js.AndToken, true
+ }
+ }
+ } else if ok && (binary.Op == js.EqEqToken || binary.Op == js.NotEqToken) {
+ var variable *js.Var
+ if v, ok := binary.X.(*js.Var); ok && isUndefinedOrNull(binary.Y) {
+ variable = v
+ } else if v, ok := binary.Y.(*js.Var); ok && isUndefinedOrNull(binary.X) {
+ variable = v
+ }
+ if variable != nil {
+ return variable, binary.Op == js.NotEqToken, true
+ }
+ }
+ return nil, false, false
+}
+
+func isUndefinedOrNull(i js.IExpr) bool {
+ i = innerExpr(i)
+ if lit, ok := i.(*js.LiteralExpr); ok {
+ return lit.TokenType == js.NullToken
+ }
+ return isUndefined(i)
+}
+
+func isUndefined(i js.IExpr) bool {
+ i = innerExpr(i)
+ 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 !hasSideEffects(unary.X)
+ }
+ 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 isTruthy(i js.IExpr) (bool, bool) {
+ if falsy, ok := 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 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 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 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]
+ if op == js.OpAnd || op == js.OpOr {
+ return isBooleanExpr(binaryExpr.X) && isBooleanExpr(binaryExpr.Y)
+ }
+ 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 invertBooleanOp(op js.TokenType) js.TokenType {
+ if op == js.EqEqToken {
+ return js.NotEqToken
+ } else if op == js.NotEqToken {
+ return js.EqEqToken
+ } else if op == js.EqEqEqToken {
+ return js.NotEqEqToken
+ } else if op == js.NotEqEqToken {
+ return js.EqEqEqToken
+ }
+ return js.ErrorToken
+}
+
+func optimizeBooleanExpr(expr js.IExpr, invert bool, prec js.OpPrec) js.IExpr {
+ if invert {
+ // unary !(boolean) has already been handled
+ if binaryExpr, ok := expr.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
+ binaryExpr.Op = invertBooleanOp(binaryExpr.Op)
+ return expr
+ } else {
+ return optimizeUnaryExpr(&js.UnaryExpr{js.NotToken, groupExpr(expr, js.OpUnary)}, prec)
+ }
+ } else if isBooleanExpr(expr) {
+ return groupExpr(expr, prec)
+ } else {
+ return &js.UnaryExpr{js.NotToken, &js.UnaryExpr{js.NotToken, groupExpr(expr, js.OpUnary)}}
+ }
+}
+
+func optimizeUnaryExpr(expr *js.UnaryExpr, prec js.OpPrec) js.IExpr {
+ if expr.Op == js.NotToken {
+ invert := true
+ var expr2 js.IExpr = expr.X
+ for {
+ if unary, ok := expr2.(*js.UnaryExpr); ok && unary.Op == js.NotToken {
+ invert = !invert
+ expr2 = unary.X
+ } else if group, ok := expr2.(*js.GroupExpr); ok {
+ expr2 = group.X
+ } else {
+ break
+ }
+ }
+ if !invert && isBooleanExpr(expr2) {
+ return groupExpr(expr2, prec)
+ } else if binary, ok := expr2.(*js.BinaryExpr); ok && invert {
+ if binaryOpPrecMap[binary.Op] == js.OpEquals {
+ binary.Op = invertBooleanOp(binary.Op)
+ return groupExpr(binary, prec)
+ } else if binary.Op == js.AndToken || binary.Op == js.OrToken {
+ op := js.AndToken
+ if binary.Op == js.AndToken {
+ op = js.OrToken
+ }
+ precInside := binaryOpPrecMap[op]
+ needsGroup := precInside < prec && (precInside != js.OpCoalesce || prec != js.OpBitOr)
+
+ // rewrite !(a||b) to !a&&!b
+ // rewrite !(a==0||b==0) to a!=0&&b!=0
+ score := 3 // savings if rewritten (group parentheses and not-token)
+ if needsGroup {
+ score -= 2
+ }
+ score -= 2 // add two not-tokens for left and right
+
+ // == and === can become != and !==
+ var isEqX, isEqY bool
+ if binaryExpr, ok := binary.X.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
+ score += 1
+ isEqX = true
+ }
+ if binaryExpr, ok := binary.Y.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
+ score += 1
+ isEqY = true
+ }
+
+ // add group if it wasn't already there
+ var needsGroupX, needsGroupY bool
+ if !isEqX && binaryLeftPrecMap[binary.Op] <= exprPrec(binary.X) && exprPrec(binary.X) < js.OpUnary {
+ score -= 2
+ needsGroupX = true
+ }
+ if !isEqY && binaryRightPrecMap[binary.Op] <= exprPrec(binary.Y) && exprPrec(binary.Y) < js.OpUnary {
+ score -= 2
+ needsGroupY = true
+ }
+
+ // remove group
+ if op == js.OrToken {
+ if exprPrec(binary.X) == js.OpOr {
+ score += 2
+ }
+ if exprPrec(binary.Y) == js.OpAnd {
+ score += 2
+ }
+ }
+
+ if 0 < score {
+ binary.Op = op
+ if isEqX {
+ binary.X.(*js.BinaryExpr).Op = invertBooleanOp(binary.X.(*js.BinaryExpr).Op)
+ }
+ if isEqY {
+ binary.Y.(*js.BinaryExpr).Op = invertBooleanOp(binary.Y.(*js.BinaryExpr).Op)
+ }
+ if needsGroupX {
+ binary.X = &js.GroupExpr{binary.X}
+ }
+ if needsGroupY {
+ binary.Y = &js.GroupExpr{binary.Y}
+ }
+ if !isEqX {
+ binary.X = &js.UnaryExpr{js.NotToken, binary.X}
+ }
+ if !isEqY {
+ binary.Y = &js.UnaryExpr{js.NotToken, binary.Y}
+ }
+ if needsGroup {
+ return &js.GroupExpr{binary}
+ }
+ return binary
+ }
+ }
+ }
+ }
+ return expr
+}
+
+func (m *jsMinifier) optimizeCondExpr(expr *js.CondExpr, prec js.OpPrec) js.IExpr {
+ // remove double negative !! in condition, or switch cases for single negative !
+ if unary1, ok := expr.Cond.(*js.UnaryExpr); ok && unary1.Op == js.NotToken {
+ if unary2, ok := unary1.X.(*js.UnaryExpr); ok && unary2.Op == js.NotToken {
+ if isBooleanExpr(unary2.X) {
+ expr.Cond = unary2.X
+ }
+ } else {
+ expr.Cond = unary1.X
+ expr.X, expr.Y = expr.Y, expr.X
+ }
+ }
+
+ finalCond := finalExpr(expr.Cond)
+ if truthy, ok := isTruthy(expr.Cond); truthy && ok {
+ // if condition is truthy
+ return expr.X
+ } else if !truthy && ok {
+ // if condition is falsy
+ return expr.Y
+ } else if isEqualExpr(finalCond, expr.X) && (exprPrec(finalCond) < js.OpAssign || binaryLeftPrecMap[js.OrToken] <= exprPrec(finalCond)) && (exprPrec(expr.Y) < js.OpAssign || binaryRightPrecMap[js.OrToken] <= exprPrec(expr.Y)) {
+ // if condition is equal to true body
+ // for higher prec we need to add group parenthesis, and for lower prec we have parenthesis anyways. This only is shorter if len(expr.X) >= 3. isEqualExpr only checks for literal variables, which is a name will be minified to a one or two character name.
+ return &js.BinaryExpr{js.OrToken, groupExpr(expr.Cond, binaryLeftPrecMap[js.OrToken]), expr.Y}
+ } else if isEqualExpr(finalCond, expr.Y) && (exprPrec(finalCond) < js.OpAssign || binaryLeftPrecMap[js.AndToken] <= exprPrec(finalCond)) && (exprPrec(expr.X) < js.OpAssign || binaryRightPrecMap[js.AndToken] <= exprPrec(expr.X)) {
+ // if condition is equal to false body
+ // for higher prec we need to add group parenthesis, and for lower prec we have parenthesis anyways. This only is shorter if len(expr.X) >= 3. isEqualExpr only checks for literal variables, which is a name will be minified to a one or two character name.
+ return &js.BinaryExpr{js.AndToken, groupExpr(expr.Cond, binaryLeftPrecMap[js.AndToken]), expr.X}
+ } else if isEqualExpr(expr.X, expr.Y) {
+ // if true and false bodies are equal
+ return groupExpr(&js.CommaExpr{[]js.IExpr{expr.Cond, expr.X}}, prec)
+ } else if nullishExpr, ok := toNullishExpr(expr); ok && m.o.minVersion(2020) {
+ // no need to check whether left/right need to add groups, as the space saving is always more
+ return nullishExpr
+ } else {
+ callX, isCallX := expr.X.(*js.CallExpr)
+ callY, isCallY := expr.Y.(*js.CallExpr)
+ if isCallX && isCallY && len(callX.Args.List) == 1 && len(callY.Args.List) == 1 && !callX.Args.List[0].Rest && !callY.Args.List[0].Rest && isEqualExpr(callX.X, callY.X) {
+ expr.X = callX.Args.List[0].Value
+ expr.Y = callY.Args.List[0].Value
+ return &js.CallExpr{callX.X, js.Args{[]js.Arg{{expr, false}}}, false} // recompress the conditional expression inside
+ }
+
+ // shorten when true and false bodies are true and false
+ trueX, falseX := isTrue(expr.X), isFalse(expr.X)
+ trueY, falseY := isTrue(expr.Y), isFalse(expr.Y)
+ if trueX && falseY || falseX && trueY {
+ return optimizeBooleanExpr(expr.Cond, falseX, prec)
+ } else if trueX || trueY {
+ // trueX != trueY
+ cond := optimizeBooleanExpr(expr.Cond, trueY, binaryLeftPrecMap[js.OrToken])
+ if trueY {
+ return &js.BinaryExpr{js.OrToken, cond, groupExpr(expr.X, binaryRightPrecMap[js.OrToken])}
+ } else {
+ return &js.BinaryExpr{js.OrToken, cond, groupExpr(expr.Y, binaryRightPrecMap[js.OrToken])}
+ }
+ } else if falseX || falseY {
+ // falseX != falseY
+ cond := optimizeBooleanExpr(expr.Cond, falseX, binaryLeftPrecMap[js.AndToken])
+ if falseX {
+ return &js.BinaryExpr{js.AndToken, cond, groupExpr(expr.Y, binaryRightPrecMap[js.AndToken])}
+ } else {
+ return &js.BinaryExpr{js.AndToken, cond, groupExpr(expr.X, binaryRightPrecMap[js.AndToken])}
+ }
+ } else if condExpr, ok := expr.X.(*js.CondExpr); ok && isEqualExpr(expr.Y, condExpr.Y) {
+ // nested conditional expression with same false bodies
+ return &js.CondExpr{&js.BinaryExpr{js.AndToken, groupExpr(expr.Cond, binaryLeftPrecMap[js.AndToken]), groupExpr(condExpr.Cond, binaryRightPrecMap[js.AndToken])}, condExpr.X, expr.Y}
+ } else if prec <= js.OpExpr {
+ // regular conditional expression
+ // convert (a,b)?c:d => a,b?c:d
+ if group, ok := expr.Cond.(*js.GroupExpr); ok {
+ if comma, ok := group.X.(*js.CommaExpr); ok && js.OpCoalesce <= exprPrec(comma.List[len(comma.List)-1]) {
+ expr.Cond = comma.List[len(comma.List)-1]
+ comma.List[len(comma.List)-1] = expr
+ return comma // recompress the conditional expression inside
+ }
+ }
+ }
+ }
+ return expr
+}
+
+func isHexDigit(b byte) bool {
+ return '0' <= b && b <= '9' || 'a' <= b && b <= 'f' || 'A' <= b && b <= 'F'
+}
+
+func mergeBinaryExpr(expr *js.BinaryExpr) {
+ // merge string concatenations which may be intertwined with other additions
+ var ok bool
+ for expr.Op == js.AddToken {
+ if lit, ok := expr.Y.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken {
+ left := expr
+ strings := []*js.LiteralExpr{lit}
+ n := len(lit.Data) - 2
+ for left.Op == js.AddToken {
+ if 50 < len(strings) {
+ return // limit recursion
+ }
+ if lit, ok := left.X.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken {
+ strings = append(strings, lit)
+ n += len(lit.Data) - 2
+ left.X = nil
+ } else if newLeft, ok := left.X.(*js.BinaryExpr); ok {
+ if lit, ok := newLeft.Y.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken {
+ strings = append(strings, lit)
+ n += len(lit.Data) - 2
+ left = newLeft
+ continue
+ }
+ }
+ break
+ }
+
+ if 1 < len(strings) {
+ // unescaped quotes will be repaired in minifyString later on
+ b := make([]byte, 0, n+2)
+ b = append(b, strings[len(strings)-1].Data[:len(strings[len(strings)-1].Data)-1]...)
+ for i := len(strings) - 2; 0 < i; i-- {
+ b = append(b, strings[i].Data[1:len(strings[i].Data)-1]...)
+ }
+ b = append(b, strings[0].Data[1:]...)
+ b[len(b)-1] = b[0]
+
+ expr.X = left.X
+ expr.Y.(*js.LiteralExpr).Data = b
+ }
+ }
+ if expr, ok = expr.X.(*js.BinaryExpr); !ok {
+ break
+ }
+ }
+}
+
+func minifyString(b []byte, allowTemplate bool) []byte {
+ if len(b) < 3 {
+ return []byte("\"\"")
+ }
+
+ // switch quotes if more optimal
+ singleQuotes := 0
+ doubleQuotes := 0
+ backtickQuotes := 0
+ newlines := 0
+ dollarSigns := 0
+ notEscapes := false
+ for i := 1; i < len(b)-1; i++ {
+ if b[i] == '\'' {
+ singleQuotes++
+ } else if b[i] == '"' {
+ doubleQuotes++
+ } else if b[i] == '`' {
+ backtickQuotes++
+ } else if b[i] == '$' {
+ dollarSigns++
+ } else if b[i] == '\\' && i+1 < len(b) {
+ if b[i+1] == 'n' || b[i+1] == 'r' {
+ newlines++
+ } else if '1' <= b[i+1] && b[i+1] <= '9' || b[i+1] == '0' && i+2 < len(b) && '0' <= b[i+2] && b[i+2] <= '9' {
+ notEscapes = true
+ }
+ }
+ }
+ quote := byte('"') // default to " for better GZIP compression
+ quotes := singleQuotes
+ if doubleQuotes < singleQuotes {
+ quote = byte('"')
+ quotes = doubleQuotes
+ } else if singleQuotes < doubleQuotes {
+ quote = byte('\'')
+ }
+ if allowTemplate && !notEscapes && backtickQuotes+dollarSigns < quotes+newlines {
+ quote = byte('`')
+ }
+ b[0] = quote
+ b[len(b)-1] = quote
+
+ // strip unnecessary escapes
+ return replaceEscapes(b, quote, 1, 1)
+}
+
+func replaceEscapes(b []byte, quote byte, prefix, suffix int) []byte {
+ // strip unnecessary escapes
+ j := 0
+ start := 0
+ for i := prefix; i < len(b)-suffix; i++ {
+ if c := b[i]; c == '\\' {
+ c = b[i+1]
+ if c == quote || c == '\\' || quote != '`' && (c == 'n' || c == 'r') || c == '0' && (i+2 == len(b)-1 || b[i+2] < '0' || '7' < b[i+2]) {
+ // keep escape sequence
+ i++
+ continue
+ }
+ n := 1 // number of characters to skip
+ 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]) && (!(b[i+2] == '0' && b[i+3] == '0') || i+3 == len(b) || b[i+3] != '\\' && (b[i+3] < '0' && '7' < b[i+3])) {
+ // don't convert \x00 to \0 if it may be an octal number
+ // hexadecimal escapes
+ _, _ = hex.Decode(b[i+3:i+4:i+4], b[i+2:i+4])
+ n = 3
+ if b[i+3] == '\\' || b[i+3] == quote || b[i+3] == '\n' || b[i+3] == '\r' || b[i+3] == 0 {
+ 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 c == 'u' && i+2 < len(b) {
+ l := i + 2
+ if b[i+2] == '{' {
+ l++
+ }
+ r := l
+ for ; r < len(b) && (b[i+2] == '{' || r < l+4); r++ {
+ if b[r] < '0' || '9' < b[r] && b[r] < 'A' || 'F' < b[r] && b[r] < 'a' || 'f' < b[r] {
+ break
+ }
+ }
+ if b[i+2] == '{' && 6 < r-l || b[i+2] != '{' && r-l != 4 {
+ i++
+ continue
+ }
+ num, err := stdStrconv.ParseInt(string(b[l:r]), 16, 32)
+ if err != nil || 0x10FFFF <= num {
+ i++
+ continue
+ }
+
+ if num == 0 {
+ // don't convert NULL to literal NULL (gives JS parsing problems)
+ if r == len(b) || b[r] != '\\' && (b[r] < '0' && '7' < b[r]) {
+ b[r-2] = '\\'
+ n = r - l
+ } else {
+ // don't convert NULL to \0 (may be an octal number)
+ b[r-4] = '\\'
+ b[r-3] = 'x'
+ n = r - l - 2
+ }
+ } else {
+ // decode unicode character to UTF-8 and put at the end of the escape sequence
+ // then skip the first part of the escape sequence until the decoded character
+ n = 2 + r - l
+ if b[i+2] == '{' {
+ n += 2
+ }
+ m := utf8.RuneLen(rune(num))
+ if m == -1 {
+ i++
+ continue
+ }
+ utf8.EncodeRune(b[i+n-m:], rune(num))
+ n -= m
+ }
+ } else if '0' <= c && c <= '7' {
+ // octal escapes (legacy), \0 already handled
+ num := c - '0'
+ if i+2 < len(b)-1 && '0' <= b[i+2] && b[i+2] <= '7' {
+ num = num*8 + b[i+2] - '0'
+ n++
+ if num < 32 && i+3 < len(b)-1 && '0' <= b[i+3] && b[i+3] <= '7' {
+ num = num*8 + 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 == 'n' {
+ b[i+1] = '\n' // only for template literals
+ } else if c == 'r' {
+ b[i+1] = '\r' // only for template literals
+ } 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 || c == '$' && quote == '`' && (i+1 < len(b) && b[i+1] == '{' || i+2 < len(b) && b[i+1] == '\\' && b[i+2] == '{') {
+ // 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] = c // 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
+}
+
+var regexpEscapeTable = [256]bool{
+ // ASCII
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+
+ false, false, false, false, true, false, false, false, // $
+ true, true, true, true, false, false, true, true, // (, ), *, +, ., /
+ true, true, true, true, true, true, true, true, // 0, 1, 2, 3, 4, 5, 6, 7
+ true, true, false, false, false, false, false, true, // 8, 9, ?
+
+ false, false, true, false, true, false, false, false, // B, D
+ false, false, false, false, false, false, false, false,
+ true, false, false, true, false, false, false, true, // P, S, W
+ false, false, false, true, true, true, true, false, // [, \, ], ^
+
+ false, false, true, true, true, false, true, false, // b, c, d, f
+ false, false, false, true, false, false, true, false, // k, n
+ true, false, true, true, true, true, true, true, // p, r, s, t, u, v, w
+ true, false, false, true, true, true, false, false, // x, {, |, }
+
+ // non-ASCII
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+}
+
+var regexpClassEscapeTable = [256]bool{
+ // ASCII
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ true, true, true, true, true, true, true, true, // 0, 1, 2, 3, 4, 5, 6, 7
+ true, true, false, false, false, false, false, false, // 8, 9
+
+ false, false, false, false, true, false, false, false, // D
+ false, false, false, false, false, false, false, false,
+ true, false, false, true, false, false, false, true, // P, S, W
+ false, false, false, false, true, true, false, false, // \, ]
+
+ false, false, true, true, true, false, true, false, // b, c, d, f
+ false, false, false, false, false, false, true, false, // n
+ true, false, true, true, true, true, true, true, // p, r, s, t, u, v, w
+ true, false, false, false, false, false, false, false, // x
+
+ // non-ASCII
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+}
+
+func minifyRegExp(b []byte) []byte {
+ inClass := false
+ afterDash := 0
+ iClass := 0
+ for i := 1; i < len(b)-1; i++ {
+ if inClass {
+ afterDash++
+ }
+ if b[i] == '\\' {
+ c := b[i+1]
+ escape := true
+ if inClass {
+ escape = regexpClassEscapeTable[c] || c == '-' && 2 < afterDash && i+2 < len(b) && b[i+2] != ']' || c == '^' && i == iClass+1
+ } else {
+ escape = regexpEscapeTable[c]
+ }
+ if !escape {
+ b = append(b[:i], b[i+1:]...)
+ if inClass && 2 < afterDash && c == '-' {
+ afterDash = 0
+ } else if inClass && c == '^' {
+ afterDash = 1
+ }
+ } else {
+ i++
+ }
+ } else if b[i] == '[' {
+ if b[i+1] == '^' {
+ i++
+ }
+ afterDash = 1
+ inClass = true
+ iClass = i
+ } else if inClass && b[i] == ']' {
+ inClass = false
+ } else if b[i] == '/' {
+ break
+ } else if inClass && 2 < afterDash && b[i] == '-' {
+ afterDash = 0
+ }
+ }
+ return b
+}
+
+func removeUnderscores(b []byte) []byte {
+ for i := 0; i < len(b); i++ {
+ if b[i] == '_' {
+ b = append(b[:i], b[i+1:]...)
+ i--
+ }
+ }
+ return b
+}
+
+func decimalNumber(b []byte, prec int) []byte {
+ b = removeUnderscores(b)
+ return minify.Number(b, prec)
+}
+
+func binaryNumber(b []byte, prec int) []byte {
+ b = removeUnderscores(b)
+ 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 {
+ b = removeUnderscores(b)
+ 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 {
+ b = removeUnderscores(b)
+ 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)
+}