diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2024-10-25 00:47:47 +0200 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2024-10-25 00:47:47 +0200 |
| commit | c6cc0108ca7738023b45e0eeac0fa2390532dd93 (patch) | |
| tree | 36890e6cd3091bbab8efbe686cc56f467f645bfd /vendor/github.com/tdewolff/minify/v2/js/stmtlist.go | |
| parent | 0130404a1dc663d4aa68d780c9bcb23a4243e68d (diff) | |
| download | jbmafp-c6cc0108ca7738023b45e0eeac0fa2390532dd93.tar.gz | |
Diffstat (limited to 'vendor/github.com/tdewolff/minify/v2/js/stmtlist.go')
| -rw-r--r-- | vendor/github.com/tdewolff/minify/v2/js/stmtlist.go | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/vendor/github.com/tdewolff/minify/v2/js/stmtlist.go b/vendor/github.com/tdewolff/minify/v2/js/stmtlist.go new file mode 100644 index 0000000..a1d3e2e --- /dev/null +++ b/vendor/github.com/tdewolff/minify/v2/js/stmtlist.go @@ -0,0 +1,341 @@ +package js + +import ( + "github.com/tdewolff/parse/v2/js" +) + +func optimizeStmt(i js.IStmt) js.IStmt { + // convert if/else into expression statement, and optimize blocks + if ifStmt, ok := i.(*js.IfStmt); ok { + hasIf := !isEmptyStmt(ifStmt.Body) + hasElse := !isEmptyStmt(ifStmt.Else) + if unaryExpr, ok := ifStmt.Cond.(*js.UnaryExpr); ok && unaryExpr.Op == js.NotToken && hasElse { + ifStmt.Cond = unaryExpr.X + ifStmt.Body, ifStmt.Else = ifStmt.Else, ifStmt.Body + hasIf, hasElse = hasElse, hasIf + } + if !hasIf && !hasElse { + return &js.ExprStmt{Value: ifStmt.Cond} + } else if hasIf && !hasElse { + ifStmt.Body = optimizeStmt(ifStmt.Body) + if X, isExprBody := ifStmt.Body.(*js.ExprStmt); isExprBody { + if unaryExpr, ok := ifStmt.Cond.(*js.UnaryExpr); ok && unaryExpr.Op == js.NotToken { + left := groupExpr(unaryExpr.X, binaryLeftPrecMap[js.OrToken]) + right := groupExpr(X.Value, binaryRightPrecMap[js.OrToken]) + return &js.ExprStmt{&js.BinaryExpr{js.OrToken, left, right}} + } + left := groupExpr(ifStmt.Cond, binaryLeftPrecMap[js.AndToken]) + right := groupExpr(X.Value, binaryRightPrecMap[js.AndToken]) + return &js.ExprStmt{&js.BinaryExpr{js.AndToken, left, right}} + } else if X, isIfStmt := ifStmt.Body.(*js.IfStmt); isIfStmt && isEmptyStmt(X.Else) { + left := groupExpr(ifStmt.Cond, binaryLeftPrecMap[js.AndToken]) + right := groupExpr(X.Cond, binaryRightPrecMap[js.AndToken]) + ifStmt.Cond = &js.BinaryExpr{js.AndToken, left, right} + ifStmt.Body = X.Body + return ifStmt + } + } else if !hasIf && hasElse { + ifStmt.Else = optimizeStmt(ifStmt.Else) + if X, isExprElse := ifStmt.Else.(*js.ExprStmt); isExprElse { + left := groupExpr(ifStmt.Cond, binaryLeftPrecMap[js.OrToken]) + right := groupExpr(X.Value, binaryRightPrecMap[js.OrToken]) + return &js.ExprStmt{&js.BinaryExpr{js.OrToken, left, right}} + } + } else if hasIf && hasElse { + ifStmt.Body = optimizeStmt(ifStmt.Body) + ifStmt.Else = optimizeStmt(ifStmt.Else) + XExpr, isExprBody := ifStmt.Body.(*js.ExprStmt) + YExpr, isExprElse := ifStmt.Else.(*js.ExprStmt) + if isExprBody && isExprElse { + return &js.ExprStmt{condExpr(ifStmt.Cond, XExpr.Value, YExpr.Value)} + } + XReturn, isReturnBody := ifStmt.Body.(*js.ReturnStmt) + YReturn, isReturnElse := ifStmt.Else.(*js.ReturnStmt) + if isReturnBody && isReturnElse { + if XReturn.Value == nil && YReturn.Value == nil { + return &js.ReturnStmt{commaExpr(ifStmt.Cond, &js.UnaryExpr{ + Op: js.VoidToken, + X: &js.LiteralExpr{js.NumericToken, zeroBytes}, + })} + } else if XReturn.Value != nil && YReturn.Value != nil { + return &js.ReturnStmt{condExpr(ifStmt.Cond, XReturn.Value, YReturn.Value)} + } + return ifStmt + } + XThrow, isThrowBody := ifStmt.Body.(*js.ThrowStmt) + YThrow, isThrowElse := ifStmt.Else.(*js.ThrowStmt) + if isThrowBody && isThrowElse { + return &js.ThrowStmt{condExpr(ifStmt.Cond, XThrow.Value, YThrow.Value)} + } + } + } else if decl, ok := i.(*js.VarDecl); ok { + // TODO: remove function name in var name=function name(){} + //for _, item := range decl.List { + // if v, ok := item.Binding.(*js.Var); ok && item.Default != nil { + // if fun, ok := item.Default.(*js.FuncDecl); ok && fun.Name != nil && bytes.Equal(v.Data, fun.Name.Data) { + // scope := fun.Body.Scope + // for i, vorig := range scope.Declared { + // if fun.Name == vorig { + // scope.Declared = append(scope.Declared[:i], scope.Declared[i+1:]...) + // } + // } + // scope.AddUndeclared(v) + // v.Uses += fun.Name.Uses - 1 + // fun.Name.Link = v + // fun.Name = nil + // } + // } + //} + + if decl.TokenType == js.ErrorToken { + // convert hoisted var declaration to expression or empty (if there are no defines) statement + for _, item := range decl.List { + if item.Default != nil { + return &js.ExprStmt{Value: decl} + } + } + return &js.EmptyStmt{} + } + // TODO: remove unused declarations + //for i := 0; i < len(decl.List); i++ { + // if v, ok := decl.List[i].Binding.(*js.Var); ok && v.Uses < 2 { + // decl.List = append(decl.List[:i], decl.List[i+1:]...) + // i-- + // } + //} + //if len(decl.List) == 0 { + // return &js.EmptyStmt{} + //} + return decl + } else if blockStmt, ok := i.(*js.BlockStmt); ok { + // merge body and remove braces if it is not a lexical declaration + blockStmt.List = optimizeStmtList(blockStmt.List, defaultBlock) + if len(blockStmt.List) == 1 { + if _, ok := blockStmt.List[0].(*js.ClassDecl); ok { + return &js.EmptyStmt{} + } else if varDecl, ok := blockStmt.List[0].(*js.VarDecl); ok && varDecl.TokenType != js.VarToken { + // remove let or const declaration in otherwise empty scope, but keep assignments + exprs := []js.IExpr{} + for _, item := range varDecl.List { + if item.Default != nil && hasSideEffects(item.Default) { + exprs = append(exprs, item.Default) + } + } + if len(exprs) == 0 { + return &js.EmptyStmt{} + } else if len(exprs) == 1 { + return &js.ExprStmt{exprs[0]} + } + return &js.ExprStmt{&js.CommaExpr{exprs}} + } + return optimizeStmt(blockStmt.List[0]) + } else if len(blockStmt.List) == 0 { + return &js.EmptyStmt{} + } + return blockStmt + } + return i +} + +func optimizeStmtList(list []js.IStmt, blockType blockType) []js.IStmt { + // merge expression statements as well as if/else statements followed by flow control statements + if len(list) == 0 { + return list + } + j := 0 // write index + for i := 0; i < len(list); i++ { // read index + if ifStmt, ok := list[i].(*js.IfStmt); ok && !isEmptyStmt(ifStmt.Else) { + // if(!a)b;else c => if(a)c; else b + if unary, ok := ifStmt.Cond.(*js.UnaryExpr); ok && unary.Op == js.NotToken && isFlowStmt(lastStmt(ifStmt.Else)) { + ifStmt.Cond = unary.X + ifStmt.Body, ifStmt.Else = ifStmt.Else, ifStmt.Body + } + if isFlowStmt(lastStmt(ifStmt.Body)) { + // if body ends in flow statement (return, throw, break, continue), we can remove the else statement and put its body in the current scope + if blockStmt, ok := ifStmt.Else.(*js.BlockStmt); ok { + blockStmt.Scope.Unscope() + list = append(list[:i+1], append(blockStmt.List, list[i+1:]...)...) + } else { + list = append(list[:i+1], append([]js.IStmt{ifStmt.Else}, list[i+1:]...)...) + } + ifStmt.Else = nil + } + } + + list[i] = optimizeStmt(list[i]) + + if _, ok := list[i].(*js.EmptyStmt); ok { + k := i + 1 + for ; k < len(list); k++ { + if _, ok := list[k].(*js.EmptyStmt); !ok { + break + } + } + list = append(list[:i], list[k:]...) + i-- + continue + } + + if 0 < i { + // merge expression statements with expression, return, and throw statements + if left, ok := list[i-1].(*js.ExprStmt); ok { + if right, ok := list[i].(*js.ExprStmt); ok { + right.Value = commaExpr(left.Value, right.Value) + j-- + } else if returnStmt, ok := list[i].(*js.ReturnStmt); ok && returnStmt.Value != nil { + returnStmt.Value = commaExpr(left.Value, returnStmt.Value) + j-- + } else if throwStmt, ok := list[i].(*js.ThrowStmt); ok { + throwStmt.Value = commaExpr(left.Value, throwStmt.Value) + j-- + } else if forStmt, ok := list[i].(*js.ForStmt); ok { + if varDecl, ok := forStmt.Init.(*js.VarDecl); ok && len(varDecl.List) == 0 || forStmt.Init == nil { + // TODO: only merge statements that don't have 'in' or 'of' keywords (slow to check?) + forStmt.Init = left.Value + j-- + } + } else if whileStmt, ok := list[i].(*js.WhileStmt); ok { + // TODO: only merge statements that don't have 'in' or 'of' keywords (slow to check?) + var body *js.BlockStmt + if blockStmt, ok := whileStmt.Body.(*js.BlockStmt); ok { + body = blockStmt + } else { + body = &js.BlockStmt{} + body.List = []js.IStmt{whileStmt.Body} + } + list[i] = &js.ForStmt{Init: left.Value, Cond: whileStmt.Cond, Post: nil, Body: body} + j-- + } else if switchStmt, ok := list[i].(*js.SwitchStmt); ok { + switchStmt.Init = commaExpr(left.Value, switchStmt.Init) + j-- + } else if withStmt, ok := list[i].(*js.WithStmt); ok { + withStmt.Cond = commaExpr(left.Value, withStmt.Cond) + j-- + } else if ifStmt, ok := list[i].(*js.IfStmt); ok { + ifStmt.Cond = commaExpr(left.Value, ifStmt.Cond) + j-- + } else if varDecl, ok := list[i].(*js.VarDecl); ok && varDecl.TokenType == js.VarToken { + if merge := mergeVarDeclExprStmt(varDecl, left, true); merge { + j-- + } + } + } else if left, ok := list[i-1].(*js.VarDecl); ok { + if right, ok := list[i].(*js.VarDecl); ok && left.TokenType == right.TokenType { + // merge const and let declarations, or non-hoisted var declarations + right.List = append(left.List, right.List...) + j-- + + // remove from vardecls list of scope + scope := left.Scope.Func + for i, decl := range scope.VarDecls { + if left == decl { + scope.VarDecls = append(scope.VarDecls[:i], scope.VarDecls[i+1:]...) + break + } + } + } else if left.TokenType == js.VarToken { + if exprStmt, ok := list[i].(*js.ExprStmt); ok { + // pull in assignments to variables into the declaration, e.g. var a;a=5 => var a=5 + if merge := mergeVarDeclExprStmt(left, exprStmt, false); merge { + list[i] = list[i-1] + j-- + } + } else if forStmt, ok := list[i].(*js.ForStmt); ok { + // TODO: only merge statements that don't have 'in' or 'of' keywords (slow to check?) + if forStmt.Init == nil { + forStmt.Init = left + j-- + } else if decl, ok := forStmt.Init.(*js.VarDecl); ok && decl.TokenType == js.ErrorToken && !hasDefines(decl) { + forStmt.Init = left + j-- + } else if ok && (decl.TokenType == js.VarToken || decl.TokenType == js.ErrorToken) { + // this is the second VarDecl, so we are hoisting var declarations, which means the forInit variables are already in 'left' + mergeVarDecls(left, decl, false) + decl.TokenType = js.VarToken + forStmt.Init = left + j-- + } + } else if whileStmt, ok := list[i].(*js.WhileStmt); ok { + // TODO: only merge statements that don't have 'in' or 'of' keywords (slow to check?) + var body *js.BlockStmt + if blockStmt, ok := whileStmt.Body.(*js.BlockStmt); ok { + body = blockStmt + } else { + body = &js.BlockStmt{} + body.List = []js.IStmt{whileStmt.Body} + } + list[i] = &js.ForStmt{Init: left, Cond: whileStmt.Cond, Post: nil, Body: body} + j-- + } + } + } + } + list[j] = list[i] + + // merge if/else with return/throw when followed by return/throw + MergeIfReturnThrow: + if 0 < j { + // separate from expression merging in case of: if(a)return b;b=c;return d + if ifStmt, ok := list[j-1].(*js.IfStmt); ok && isEmptyStmt(ifStmt.Body) != isEmptyStmt(ifStmt.Else) { + // either the if body is empty or the else body is empty. In case where both bodies have return/throw, we already rewrote that if statement to an return/throw statement + if returnStmt, ok := list[j].(*js.ReturnStmt); ok { + if returnStmt.Value == nil { + if left, ok := ifStmt.Body.(*js.ReturnStmt); ok && left.Value == nil { + list[j-1] = &js.ExprStmt{Value: ifStmt.Cond} + } else if left, ok := ifStmt.Else.(*js.ReturnStmt); ok && left.Value == nil { + list[j-1] = &js.ExprStmt{Value: ifStmt.Cond} + } + } else { + if left, ok := ifStmt.Body.(*js.ReturnStmt); ok && left.Value != nil { + returnStmt.Value = condExpr(ifStmt.Cond, left.Value, returnStmt.Value) + list[j-1] = returnStmt + j-- + goto MergeIfReturnThrow + } else if left, ok := ifStmt.Else.(*js.ReturnStmt); ok && left.Value != nil { + returnStmt.Value = condExpr(ifStmt.Cond, returnStmt.Value, left.Value) + list[j-1] = returnStmt + j-- + goto MergeIfReturnThrow + } + } + } else if throwStmt, ok := list[j].(*js.ThrowStmt); ok { + if left, ok := ifStmt.Body.(*js.ThrowStmt); ok { + throwStmt.Value = condExpr(ifStmt.Cond, left.Value, throwStmt.Value) + list[j-1] = throwStmt + j-- + goto MergeIfReturnThrow + } else if left, ok := ifStmt.Else.(*js.ThrowStmt); ok { + throwStmt.Value = condExpr(ifStmt.Cond, throwStmt.Value, left.Value) + list[j-1] = throwStmt + j-- + goto MergeIfReturnThrow + } + } + } + } + j++ + } + + // remove superfluous return or continue + if 0 < j { + if blockType == functionBlock { + if returnStmt, ok := list[j-1].(*js.ReturnStmt); ok { + if returnStmt.Value == nil || isUndefined(returnStmt.Value) { + j-- + } else if commaExpr, ok := returnStmt.Value.(*js.CommaExpr); ok && isUndefined(commaExpr.List[len(commaExpr.List)-1]) { + // rewrite function f(){return a,void 0} => function f(){a} + if len(commaExpr.List) == 2 { + list[j-1] = &js.ExprStmt{Value: commaExpr.List[0]} + } else { + commaExpr.List = commaExpr.List[:len(commaExpr.List)-1] + } + } + } + } else if blockType == iterationBlock { + if branchStmt, ok := list[j-1].(*js.BranchStmt); ok && branchStmt.Type == js.ContinueToken && branchStmt.Label == nil { + j-- + } + } + } + return list[:j] +} |
