12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382 |
- 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-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 || 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 {
- // 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
- }
- utf8.EncodeRune(b[i:], rune(num))
- i += m
- n -= m
- }
- } else if '0' <= c && c <= '7' {
- // octal escapes (legacy), \0 already handled
- 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 || 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 c == 'n' {
- b[i] = '\n' // only for template literals
- i++
- } else if c == 'r' {
- b[i] = '\r' // only for template literals
- 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 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)
- }
|