1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456 |
- 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.IntegerToken {
- 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
- 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] == '$' && i+1 < len(b) && b[i+1] == '{' {
- 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' && i+2 < len(b) {
- if b[i+1] == '1' && (b[i+2] == '2' || b[i+2] == '5') {
- newlines++
- } else if b[i+1] == '4' && b[i+2] == '2' {
- doubleQuotes++
- } else if b[i+1] == '4' && b[i+2] == '7' {
- singleQuotes++
- } else if i+3 < len(b) && b[i+1] == '1' && b[i+2] == '4' && b[i+3] == '0' {
- backtickQuotes++
- }
- } else if b[i+1] == 'x' && i+3 < len(b) {
- if b[i+2] == '0' && (b[i+3]|0x20 == 'a' || b[i+3]|0x20 == 'd') {
- newlines++
- } else if b[i+2] == '2' && b[i+3] == '2' {
- doubleQuotes++
- } else if b[i+2] == '2' && b[i+3] == '7' {
- singleQuotes++
- } else if b[i+2] == '6' && b[i+3] == '0' {
- backtickQuotes++
- }
- } else if b[i+1] == 'u' && i+5 < len(b) && b[i+2] == '0' && b[i+3] == '0' {
- if b[i+4] == '0' && (b[i+5]|0x20 == 'a' || b[i+5]|0x20 == 'd') {
- newlines++
- } else if b[i+4] == '2' && b[i+5] == '2' {
- doubleQuotes++
- } else if b[i+4] == '2' && b[i+5] == '7' {
- singleQuotes++
- } else if b[i+4] == '6' && b[i+5] == '0' {
- backtickQuotes++
- }
- } else if b[i+1] == 'u' && i+4 < len(b) && b[i+2] == '{' {
- j := i + 3
- for j < len(b) && b[j] == '0' {
- j++
- }
- if j+1 < len(b) && (b[j]|0x20 == 'a' || b[j]|0x20 == 'd') && b[j+1] == '}' {
- newlines++
- } else if j+2 < len(b) && b[j+2] == '}' {
- if b[j] == '2' && b[j+1] == '2' {
- doubleQuotes++
- } else if b[j] == '2' && b[j+1] == '7' {
- singleQuotes++
- } else if b[j] == '6' && b[j+1] == '0' {
- backtickQuotes++
- }
- }
- }
- }
- }
- quote := byte('"') // default to " for better GZIP compression
- quotes := doubleQuotes
- if doubleQuotes < singleQuotes {
- quote = byte('"')
- } else if singleQuotes < doubleQuotes {
- quote = byte('\'')
- quotes = singleQuotes
- }
- if allowTemplate && 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-1; i++ {
- if c := b[i]; c == '\\' {
- c = b[i+1]
- if c == quote || c == '\\' || quote != '`' && (c == 'n' || c == 'r') || c == '0' && (len(b)-suffix <= i+2 || 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:i+1:i+1], b[i+2:i+4])
- n = 4
- if b[i] == '\\' || b[i] == quote || quote != '`' && (b[i] == '\n' || b[i] == '\r') || b[i] == 0 {
- if b[i] == '\n' {
- b[i+1] = 'n'
- } else if b[i] == '\r' {
- b[i+1] = 'r'
- } else {
- b[i+1] = b[i]
- }
- b[i] = '\\'
- i++
- n--
- }
- i++
- n--
- } 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 || len(b) <= r || b[r] != '}') || 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
- }
- n = 2 + r - l
- if b[i+2] == '{' {
- n += 2
- }
- 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[i+1] = '0'
- i += 2
- n -= 2
- } else {
- // don't convert NULL to \0 (may be an octal number)
- b[i+1] = 'x'
- b[i+2] = '0'
- b[i+3] = '0'
- i += 4
- n -= 4
- }
- } else if quote == '`' || num != 10 && num != 13 {
- // 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
- m := utf8.RuneLen(rune(num))
- if m == -1 {
- i++
- continue
- } else if num < 256 && quote == byte(num) {
- b[i] = '\\'
- i++
- n--
- }
- utf8.EncodeRune(b[i:], rune(num))
- i += m
- n -= m
- } else {
- if num == 10 {
- b[i+1] = 'n'
- } else {
- b[i+1] = 'r'
- }
- i += 2
- n -= 2
- }
- } else if '0' <= c && c <= '7' {
- // octal escapes (legacy), \0 already handled (quote != `)
- num := c - '0'
- n++
- 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] = num
- if num == 0 || num == '\\' || num == quote || quote != '`' && (num == '\n' || num == '\r') {
- if num == 0 {
- b[i+1] = '0'
- } else if num == '\n' {
- b[i+1] = 'n'
- } else if num == '\r' {
- b[i+1] = 'r'
- } else {
- b[i+1] = b[i]
- }
- b[i] = '\\'
- i++
- n--
- }
- i++
- n--
- } else if quote == '`' && c == 'n' {
- b[i] = '\n'
- i++
- } else if quote == '`' && c == 'r' {
- b[i] = '\r'
- i++
- } else if c == 't' {
- b[i] = '\t'
- i++
- } else if c == 'f' {
- b[i] = '\f'
- i++
- } else if c == 'v' {
- b[i] = '\v'
- i++
- } else if c == 'b' {
- b[i] = '\b'
- i++
- }
- // 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 removeUnderscoresAndSuffix(b []byte) ([]byte, bool) {
- for i := 0; i < len(b); i++ {
- if b[i] == '_' {
- b = append(b[:i], b[i+1:]...)
- i--
- }
- }
- if 0 < len(b) && b[len(b)-1] == 'n' {
- return b[:len(b)-1], true
- }
- return b, false
- }
- func decimalNumber(b []byte, prec int) []byte {
- var suffix bool
- b, suffix = removeUnderscoresAndSuffix(b)
- if suffix {
- return append(b, 'n')
- }
- return minify.Number(b, prec)
- }
- func binaryNumber(b []byte, prec int) []byte {
- var suffix bool
- b, suffix = removeUnderscoresAndSuffix(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--
- }
- if suffix {
- return append(b, 'n')
- }
- return minify.Number(b, prec)
- }
- func octalNumber(b []byte, prec int) []byte {
- var suffix bool
- b, suffix = removeUnderscoresAndSuffix(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--
- }
- if suffix {
- return append(b, 'n')
- }
- return minify.Number(b, prec)
- }
- func hexadecimalNumber(b []byte, prec int) []byte {
- var suffix bool
- b, suffix = removeUnderscoresAndSuffix(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--
- }
- if suffix {
- return append(b, 'n')
- }
- return minify.Number(b, prec)
- }
|