123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 |
- 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 {
- // TODO: only merge lhs expression that don't have 'in' or 'of' keywords (slow to check?)
- if varDecl, ok := forStmt.Init.(*js.VarDecl); ok {
- if len(varDecl.List) == 0 || forStmt.Init == nil {
- forStmt.Init = left.Value
- j--
- } else if mergeVarDeclExprStmt(varDecl, left, true) {
- j--
- }
- }
- } else if whileStmt, ok := list[i].(*js.WhileStmt); ok {
- // TODO: only merge lhs expression 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 lhs expression 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 lhs expression 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]
- }
|