stmtlist.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. package js
  2. import (
  3. "github.com/tdewolff/parse/v2/js"
  4. )
  5. func optimizeStmt(i js.IStmt) js.IStmt {
  6. // convert if/else into expression statement, and optimize blocks
  7. if ifStmt, ok := i.(*js.IfStmt); ok {
  8. hasIf := !isEmptyStmt(ifStmt.Body)
  9. hasElse := !isEmptyStmt(ifStmt.Else)
  10. if unaryExpr, ok := ifStmt.Cond.(*js.UnaryExpr); ok && unaryExpr.Op == js.NotToken && hasElse {
  11. ifStmt.Cond = unaryExpr.X
  12. ifStmt.Body, ifStmt.Else = ifStmt.Else, ifStmt.Body
  13. hasIf, hasElse = hasElse, hasIf
  14. }
  15. if !hasIf && !hasElse {
  16. return &js.ExprStmt{Value: ifStmt.Cond}
  17. } else if hasIf && !hasElse {
  18. ifStmt.Body = optimizeStmt(ifStmt.Body)
  19. if X, isExprBody := ifStmt.Body.(*js.ExprStmt); isExprBody {
  20. if unaryExpr, ok := ifStmt.Cond.(*js.UnaryExpr); ok && unaryExpr.Op == js.NotToken {
  21. left := groupExpr(unaryExpr.X, binaryLeftPrecMap[js.OrToken])
  22. right := groupExpr(X.Value, binaryRightPrecMap[js.OrToken])
  23. return &js.ExprStmt{&js.BinaryExpr{js.OrToken, left, right}}
  24. }
  25. left := groupExpr(ifStmt.Cond, binaryLeftPrecMap[js.AndToken])
  26. right := groupExpr(X.Value, binaryRightPrecMap[js.AndToken])
  27. return &js.ExprStmt{&js.BinaryExpr{js.AndToken, left, right}}
  28. } else if X, isIfStmt := ifStmt.Body.(*js.IfStmt); isIfStmt && isEmptyStmt(X.Else) {
  29. left := groupExpr(ifStmt.Cond, binaryLeftPrecMap[js.AndToken])
  30. right := groupExpr(X.Cond, binaryRightPrecMap[js.AndToken])
  31. ifStmt.Cond = &js.BinaryExpr{js.AndToken, left, right}
  32. ifStmt.Body = X.Body
  33. return ifStmt
  34. }
  35. } else if !hasIf && hasElse {
  36. ifStmt.Else = optimizeStmt(ifStmt.Else)
  37. if X, isExprElse := ifStmt.Else.(*js.ExprStmt); isExprElse {
  38. left := groupExpr(ifStmt.Cond, binaryLeftPrecMap[js.OrToken])
  39. right := groupExpr(X.Value, binaryRightPrecMap[js.OrToken])
  40. return &js.ExprStmt{&js.BinaryExpr{js.OrToken, left, right}}
  41. }
  42. } else if hasIf && hasElse {
  43. ifStmt.Body = optimizeStmt(ifStmt.Body)
  44. ifStmt.Else = optimizeStmt(ifStmt.Else)
  45. XExpr, isExprBody := ifStmt.Body.(*js.ExprStmt)
  46. YExpr, isExprElse := ifStmt.Else.(*js.ExprStmt)
  47. if isExprBody && isExprElse {
  48. return &js.ExprStmt{condExpr(ifStmt.Cond, XExpr.Value, YExpr.Value)}
  49. }
  50. XReturn, isReturnBody := ifStmt.Body.(*js.ReturnStmt)
  51. YReturn, isReturnElse := ifStmt.Else.(*js.ReturnStmt)
  52. if isReturnBody && isReturnElse {
  53. if XReturn.Value == nil && YReturn.Value == nil {
  54. return &js.ReturnStmt{commaExpr(ifStmt.Cond, &js.UnaryExpr{
  55. Op: js.VoidToken,
  56. X: &js.LiteralExpr{js.NumericToken, zeroBytes},
  57. })}
  58. } else if XReturn.Value != nil && YReturn.Value != nil {
  59. return &js.ReturnStmt{condExpr(ifStmt.Cond, XReturn.Value, YReturn.Value)}
  60. }
  61. return ifStmt
  62. }
  63. XThrow, isThrowBody := ifStmt.Body.(*js.ThrowStmt)
  64. YThrow, isThrowElse := ifStmt.Else.(*js.ThrowStmt)
  65. if isThrowBody && isThrowElse {
  66. return &js.ThrowStmt{condExpr(ifStmt.Cond, XThrow.Value, YThrow.Value)}
  67. }
  68. }
  69. } else if decl, ok := i.(*js.VarDecl); ok {
  70. // TODO: remove function name in var name=function name(){}
  71. //for _, item := range decl.List {
  72. // if v, ok := item.Binding.(*js.Var); ok && item.Default != nil {
  73. // if fun, ok := item.Default.(*js.FuncDecl); ok && fun.Name != nil && bytes.Equal(v.Data, fun.Name.Data) {
  74. // scope := fun.Body.Scope
  75. // for i, vorig := range scope.Declared {
  76. // if fun.Name == vorig {
  77. // scope.Declared = append(scope.Declared[:i], scope.Declared[i+1:]...)
  78. // }
  79. // }
  80. // scope.AddUndeclared(v)
  81. // v.Uses += fun.Name.Uses - 1
  82. // fun.Name.Link = v
  83. // fun.Name = nil
  84. // }
  85. // }
  86. //}
  87. if decl.TokenType == js.ErrorToken {
  88. // convert hoisted var declaration to expression or empty (if there are no defines) statement
  89. for _, item := range decl.List {
  90. if item.Default != nil {
  91. return &js.ExprStmt{Value: decl}
  92. }
  93. }
  94. return &js.EmptyStmt{}
  95. }
  96. // TODO: remove unused declarations
  97. //for i := 0; i < len(decl.List); i++ {
  98. // if v, ok := decl.List[i].Binding.(*js.Var); ok && v.Uses < 2 {
  99. // decl.List = append(decl.List[:i], decl.List[i+1:]...)
  100. // i--
  101. // }
  102. //}
  103. //if len(decl.List) == 0 {
  104. // return &js.EmptyStmt{}
  105. //}
  106. return decl
  107. } else if blockStmt, ok := i.(*js.BlockStmt); ok {
  108. // merge body and remove braces if it is not a lexical declaration
  109. blockStmt.List = optimizeStmtList(blockStmt.List, defaultBlock)
  110. if len(blockStmt.List) == 1 {
  111. if _, ok := blockStmt.List[0].(*js.ClassDecl); ok {
  112. return &js.EmptyStmt{}
  113. } else if varDecl, ok := blockStmt.List[0].(*js.VarDecl); ok && varDecl.TokenType != js.VarToken {
  114. // remove let or const declaration in otherwise empty scope, but keep assignments
  115. exprs := []js.IExpr{}
  116. for _, item := range varDecl.List {
  117. if item.Default != nil && hasSideEffects(item.Default) {
  118. exprs = append(exprs, item.Default)
  119. }
  120. }
  121. if len(exprs) == 0 {
  122. return &js.EmptyStmt{}
  123. } else if len(exprs) == 1 {
  124. return &js.ExprStmt{exprs[0]}
  125. }
  126. return &js.ExprStmt{&js.CommaExpr{exprs}}
  127. }
  128. return optimizeStmt(blockStmt.List[0])
  129. } else if len(blockStmt.List) == 0 {
  130. return &js.EmptyStmt{}
  131. }
  132. return blockStmt
  133. }
  134. return i
  135. }
  136. func optimizeStmtList(list []js.IStmt, blockType blockType) []js.IStmt {
  137. // merge expression statements as well as if/else statements followed by flow control statements
  138. if len(list) == 0 {
  139. return list
  140. }
  141. j := 0 // write index
  142. for i := 0; i < len(list); i++ { // read index
  143. if ifStmt, ok := list[i].(*js.IfStmt); ok && !isEmptyStmt(ifStmt.Else) {
  144. // if(!a)b;else c => if(a)c; else b
  145. if unary, ok := ifStmt.Cond.(*js.UnaryExpr); ok && unary.Op == js.NotToken && isFlowStmt(lastStmt(ifStmt.Else)) {
  146. ifStmt.Cond = unary.X
  147. ifStmt.Body, ifStmt.Else = ifStmt.Else, ifStmt.Body
  148. }
  149. if isFlowStmt(lastStmt(ifStmt.Body)) {
  150. // if body ends in flow statement (return, throw, break, continue), we can remove the else statement and put its body in the current scope
  151. if blockStmt, ok := ifStmt.Else.(*js.BlockStmt); ok {
  152. blockStmt.Scope.Unscope()
  153. list = append(list[:i+1], append(blockStmt.List, list[i+1:]...)...)
  154. } else {
  155. list = append(list[:i+1], append([]js.IStmt{ifStmt.Else}, list[i+1:]...)...)
  156. }
  157. ifStmt.Else = nil
  158. }
  159. }
  160. list[i] = optimizeStmt(list[i])
  161. if _, ok := list[i].(*js.EmptyStmt); ok {
  162. k := i + 1
  163. for ; k < len(list); k++ {
  164. if _, ok := list[k].(*js.EmptyStmt); !ok {
  165. break
  166. }
  167. }
  168. list = append(list[:i], list[k:]...)
  169. i--
  170. continue
  171. }
  172. if 0 < i {
  173. // merge expression statements with expression, return, and throw statements
  174. if left, ok := list[i-1].(*js.ExprStmt); ok {
  175. if right, ok := list[i].(*js.ExprStmt); ok {
  176. right.Value = commaExpr(left.Value, right.Value)
  177. j--
  178. } else if returnStmt, ok := list[i].(*js.ReturnStmt); ok && returnStmt.Value != nil {
  179. returnStmt.Value = commaExpr(left.Value, returnStmt.Value)
  180. j--
  181. } else if throwStmt, ok := list[i].(*js.ThrowStmt); ok {
  182. throwStmt.Value = commaExpr(left.Value, throwStmt.Value)
  183. j--
  184. } else if forStmt, ok := list[i].(*js.ForStmt); ok {
  185. // TODO: only merge lhs expression that don't have 'in' or 'of' keywords (slow to check?)
  186. if varDecl, ok := forStmt.Init.(*js.VarDecl); ok {
  187. if len(varDecl.List) == 0 || forStmt.Init == nil {
  188. forStmt.Init = left.Value
  189. j--
  190. } else if mergeVarDeclExprStmt(varDecl, left, true) {
  191. j--
  192. }
  193. }
  194. } else if whileStmt, ok := list[i].(*js.WhileStmt); ok {
  195. // TODO: only merge lhs expression that don't have 'in' or 'of' keywords (slow to check?)
  196. var body *js.BlockStmt
  197. if blockStmt, ok := whileStmt.Body.(*js.BlockStmt); ok {
  198. body = blockStmt
  199. } else {
  200. body = &js.BlockStmt{}
  201. body.List = []js.IStmt{whileStmt.Body}
  202. }
  203. list[i] = &js.ForStmt{Init: left.Value, Cond: whileStmt.Cond, Post: nil, Body: body}
  204. j--
  205. } else if switchStmt, ok := list[i].(*js.SwitchStmt); ok {
  206. switchStmt.Init = commaExpr(left.Value, switchStmt.Init)
  207. j--
  208. } else if withStmt, ok := list[i].(*js.WithStmt); ok {
  209. withStmt.Cond = commaExpr(left.Value, withStmt.Cond)
  210. j--
  211. } else if ifStmt, ok := list[i].(*js.IfStmt); ok {
  212. ifStmt.Cond = commaExpr(left.Value, ifStmt.Cond)
  213. j--
  214. } else if varDecl, ok := list[i].(*js.VarDecl); ok && varDecl.TokenType == js.VarToken {
  215. if merge := mergeVarDeclExprStmt(varDecl, left, true); merge {
  216. j--
  217. }
  218. }
  219. } else if left, ok := list[i-1].(*js.VarDecl); ok {
  220. if right, ok := list[i].(*js.VarDecl); ok && left.TokenType == right.TokenType {
  221. // merge const and let declarations, or non-hoisted var declarations
  222. right.List = append(left.List, right.List...)
  223. j--
  224. // remove from vardecls list of scope
  225. scope := left.Scope.Func
  226. for i, decl := range scope.VarDecls {
  227. if left == decl {
  228. scope.VarDecls = append(scope.VarDecls[:i], scope.VarDecls[i+1:]...)
  229. break
  230. }
  231. }
  232. } else if left.TokenType == js.VarToken {
  233. if exprStmt, ok := list[i].(*js.ExprStmt); ok {
  234. // pull in assignments to variables into the declaration, e.g. var a;a=5 => var a=5
  235. if merge := mergeVarDeclExprStmt(left, exprStmt, false); merge {
  236. list[i] = list[i-1]
  237. j--
  238. }
  239. } else if forStmt, ok := list[i].(*js.ForStmt); ok {
  240. // TODO: only merge lhs expression that don't have 'in' or 'of' keywords (slow to check?)
  241. if forStmt.Init == nil {
  242. forStmt.Init = left
  243. j--
  244. } else if decl, ok := forStmt.Init.(*js.VarDecl); ok && decl.TokenType == js.ErrorToken && !hasDefines(decl) {
  245. forStmt.Init = left
  246. j--
  247. } else if ok && (decl.TokenType == js.VarToken || decl.TokenType == js.ErrorToken) {
  248. // this is the second VarDecl, so we are hoisting var declarations, which means the forInit variables are already in 'left'
  249. mergeVarDecls(left, decl, false)
  250. decl.TokenType = js.VarToken
  251. forStmt.Init = left
  252. j--
  253. }
  254. } else if whileStmt, ok := list[i].(*js.WhileStmt); ok {
  255. // TODO: only merge lhs expression that don't have 'in' or 'of' keywords (slow to check?)
  256. var body *js.BlockStmt
  257. if blockStmt, ok := whileStmt.Body.(*js.BlockStmt); ok {
  258. body = blockStmt
  259. } else {
  260. body = &js.BlockStmt{}
  261. body.List = []js.IStmt{whileStmt.Body}
  262. }
  263. list[i] = &js.ForStmt{Init: left, Cond: whileStmt.Cond, Post: nil, Body: body}
  264. j--
  265. }
  266. }
  267. }
  268. }
  269. list[j] = list[i]
  270. // merge if/else with return/throw when followed by return/throw
  271. MergeIfReturnThrow:
  272. if 0 < j {
  273. // separate from expression merging in case of: if(a)return b;b=c;return d
  274. if ifStmt, ok := list[j-1].(*js.IfStmt); ok && isEmptyStmt(ifStmt.Body) != isEmptyStmt(ifStmt.Else) {
  275. // 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
  276. if returnStmt, ok := list[j].(*js.ReturnStmt); ok {
  277. if returnStmt.Value == nil {
  278. if left, ok := ifStmt.Body.(*js.ReturnStmt); ok && left.Value == nil {
  279. list[j-1] = &js.ExprStmt{Value: ifStmt.Cond}
  280. } else if left, ok := ifStmt.Else.(*js.ReturnStmt); ok && left.Value == nil {
  281. list[j-1] = &js.ExprStmt{Value: ifStmt.Cond}
  282. }
  283. } else {
  284. if left, ok := ifStmt.Body.(*js.ReturnStmt); ok && left.Value != nil {
  285. returnStmt.Value = condExpr(ifStmt.Cond, left.Value, returnStmt.Value)
  286. list[j-1] = returnStmt
  287. j--
  288. goto MergeIfReturnThrow
  289. } else if left, ok := ifStmt.Else.(*js.ReturnStmt); ok && left.Value != nil {
  290. returnStmt.Value = condExpr(ifStmt.Cond, returnStmt.Value, left.Value)
  291. list[j-1] = returnStmt
  292. j--
  293. goto MergeIfReturnThrow
  294. }
  295. }
  296. } else if throwStmt, ok := list[j].(*js.ThrowStmt); ok {
  297. if left, ok := ifStmt.Body.(*js.ThrowStmt); ok {
  298. throwStmt.Value = condExpr(ifStmt.Cond, left.Value, throwStmt.Value)
  299. list[j-1] = throwStmt
  300. j--
  301. goto MergeIfReturnThrow
  302. } else if left, ok := ifStmt.Else.(*js.ThrowStmt); ok {
  303. throwStmt.Value = condExpr(ifStmt.Cond, throwStmt.Value, left.Value)
  304. list[j-1] = throwStmt
  305. j--
  306. goto MergeIfReturnThrow
  307. }
  308. }
  309. }
  310. }
  311. j++
  312. }
  313. // remove superfluous return or continue
  314. if 0 < j {
  315. if blockType == functionBlock {
  316. if returnStmt, ok := list[j-1].(*js.ReturnStmt); ok {
  317. if returnStmt.Value == nil || isUndefined(returnStmt.Value) {
  318. j--
  319. } else if commaExpr, ok := returnStmt.Value.(*js.CommaExpr); ok && isUndefined(commaExpr.List[len(commaExpr.List)-1]) {
  320. // rewrite function f(){return a,void 0} => function f(){a}
  321. if len(commaExpr.List) == 2 {
  322. list[j-1] = &js.ExprStmt{Value: commaExpr.List[0]}
  323. } else {
  324. commaExpr.List = commaExpr.List[:len(commaExpr.List)-1]
  325. }
  326. }
  327. }
  328. } else if blockType == iterationBlock {
  329. if branchStmt, ok := list[j-1].(*js.BranchStmt); ok && branchStmt.Type == js.ContinueToken && branchStmt.Label == nil {
  330. j--
  331. }
  332. }
  333. }
  334. return list[:j]
  335. }