1package js 2 3import ( 4 "bytes" 5 "sort" 6 7 "github.com/tdewolff/parse/v2" 8 "github.com/tdewolff/parse/v2/js" 9) 10 11type renamer struct { 12 ast *js.AST 13 reserved map[string]struct{} 14 rename bool 15} 16 17func newRenamer(ast *js.AST, undeclared js.VarArray, rename bool) *renamer { 18 reserved := make(map[string]struct{}, len(js.Keywords)) 19 for name := range js.Keywords { 20 reserved[name] = struct{}{} 21 } 22 return &renamer{ 23 ast: ast, 24 reserved: reserved, 25 rename: rename, 26 } 27} 28 29func (r *renamer) renameScope(scope js.Scope) { 30 if !r.rename { 31 return 32 } 33 34 rename := []byte("`") // so that the next is 'a' 35 sort.Sort(js.VarsByUses(scope.Declared)) 36 for _, v := range scope.Declared { 37 rename = r.next(rename) 38 for r.isReserved(rename, scope.Undeclared) { 39 rename = r.next(rename) 40 } 41 v.Data = parse.Copy(rename) 42 } 43} 44 45func (r *renamer) isReserved(name []byte, undeclared js.VarArray) bool { 46 if 1 < len(name) { // there are no keywords or known globals that are one character long 47 if _, ok := r.reserved[string(name)]; ok { 48 return true 49 } 50 } 51 for _, v := range undeclared { 52 for v.Link != nil { 53 v = v.Link 54 } 55 if bytes.Equal(v.Data, name) { 56 return true 57 } 58 } 59 return false 60} 61 62func (r *renamer) next(name []byte) []byte { 63 // Generate new names for variables where the last character is (a-zA-Z$_) and others are (a-zA-Z). 64 // Thus we can have 54 one-character names and 52*54=2808 two-character names for every branch leaf. 65 // That is sufficient for virtually all input. 66 if name[len(name)-1] == 'z' { 67 name[len(name)-1] = 'A' 68 } else if name[len(name)-1] == 'Z' { 69 name[len(name)-1] = '_' 70 } else if name[len(name)-1] == '_' { 71 name[len(name)-1] = '$' 72 } else if name[len(name)-1] == '$' { 73 i := len(name) - 2 74 for ; 0 <= i; i-- { 75 if name[i] == 'Z' { 76 continue // happens after 52*54=2808 variables 77 } else if name[i] == 'z' { 78 name[i] = 'A' // happens after 26*54=1404 variables 79 } else { 80 name[i]++ 81 break 82 } 83 } 84 for j := i + 1; j < len(name); j++ { 85 name[j] = 'a' 86 } 87 if i < 0 { 88 name = append(name, 'a') 89 } 90 } else { 91 name[len(name)-1]++ 92 } 93 return name 94} 95 96//////////////////////////////////////////////////////////////// 97 98func bindingRefs(ibinding js.IBinding) (refs []*js.Var) { 99 switch binding := ibinding.(type) { 100 case *js.Var: 101 refs = append(refs, binding) 102 case *js.BindingArray: 103 for _, item := range binding.List { 104 if item.Binding != nil { 105 refs = append(refs, bindingRefs(item.Binding)...) 106 } 107 } 108 if binding.Rest != nil { 109 refs = append(refs, bindingRefs(binding.Rest)...) 110 } 111 case *js.BindingObject: 112 for _, item := range binding.List { 113 if item.Value.Binding != nil { 114 refs = append(refs, bindingRefs(item.Value.Binding)...) 115 } 116 } 117 if binding.Rest != nil { 118 refs = append(refs, binding.Rest) 119 } 120 } 121 return 122} 123 124func appendBindingVars(vars *[]*js.Var, binding js.IBinding) { 125 if v, ok := binding.(*js.Var); ok { 126 *vars = append(*vars, v) 127 } else if array, ok := binding.(*js.BindingArray); ok { 128 for _, item := range array.List { 129 appendBindingVars(vars, item.Binding) 130 } 131 if array.Rest != nil { 132 appendBindingVars(vars, array.Rest) 133 } 134 } else if object, ok := binding.(*js.BindingObject); ok { 135 for _, item := range object.List { 136 appendBindingVars(vars, item.Value.Binding) 137 } 138 if object.Rest != nil { 139 *vars = append(*vars, object.Rest) 140 } 141 } 142} 143 144func addDefinition(decl *js.VarDecl, iDefines int, binding js.IBinding, value js.IExpr) bool { 145 // see if not already defined in variable declaration list 146 if vdef, ok := binding.(*js.Var); ok { 147 for i, item := range decl.List[iDefines:] { 148 if v, ok := item.Binding.(*js.Var); ok && v == vdef { 149 decl.List[iDefines+i].Default = value 150 if 0 < i { 151 decl.List[iDefines], decl.List[iDefines+i] = decl.List[iDefines+i], decl.List[iDefines] 152 } 153 return true 154 } 155 } 156 } else { 157 vars := []*js.Var{} 158 appendBindingVars(&vars, binding) 159 if len(vars) == 0 { 160 return false 161 } 162 locs := make([]int, len(vars)) 163 for i, vdef := range vars { 164 locs[i] = -1 165 for loc, item := range decl.List[iDefines:] { 166 if v, ok := item.Binding.(*js.Var); ok && v == vdef { 167 locs[i] = loc 168 break 169 } 170 } 171 if locs[i] == -1 { 172 return false // cannot (probably) happen if we hoist variables 173 } 174 } 175 sort.Ints(locs) 176 if iDefines != locs[0] { 177 decl.List[iDefines], decl.List[iDefines+locs[0]] = decl.List[iDefines+locs[0]], decl.List[iDefines] 178 } 179 decl.List[iDefines].Binding = binding 180 decl.List[iDefines].Default = value 181 for i := len(locs) - 1; 1 <= i; i-- { 182 if locs[i] != locs[i-1] { // ignore duplicates, otherwise remove items from hoisted var declaration 183 decl.List = append(decl.List[:locs[i]], decl.List[locs[i]+1:]...) 184 } 185 } 186 return true 187 } 188 return false 189} 190 191func (m *jsMinifier) hoistVars(body *js.BlockStmt) *js.VarDecl { 192 // Hoist all variable declarations in the current module/function scope to the top. 193 // If the first statement is a var declaration, expand it. Otherwise prepend a new var declaration. 194 // Except for the first var declaration, all others are converted to expressions. This is possible because an ArrayBindingPattern and ObjectBindingPattern can be converted to an ArrayLiteral or ObjectLiteral respectively, as they are supersets of the BindingPatterns. 195 parentVarsHoisted := m.varsHoisted 196 m.varsHoisted = nil 197 if 1 < body.Scope.NumVarDecls { 198 iDefines := 0 // position past last variable definition in declaration 199 mergeStatements := true 200 201 // ignore "use strict" 202 declStart := 0 203 for { 204 if _, ok := body.List[declStart].(*js.DirectivePrologueStmt); ok { 205 declStart++ 206 } else { 207 break 208 } 209 } 210 211 var decl *js.VarDecl 212 if varDecl, ok := body.List[declStart].(*js.VarDecl); ok && varDecl.TokenType == js.VarToken { 213 decl = varDecl 214 } else if forStmt, ok := body.List[declStart].(*js.ForStmt); ok { 215 // TODO: only merge statements that don't have 'in' or 'of' keywords (slow to check?) 216 if forStmt.Init == nil { 217 decl = &js.VarDecl{TokenType: js.VarToken, List: nil} 218 forStmt.Init = decl 219 } else if varDecl, ok := forStmt.Init.(*js.VarDecl); ok && varDecl.TokenType == js.VarToken { 220 decl = varDecl 221 } 222 mergeStatements = false 223 } else if whileStmt, ok := body.List[declStart].(*js.WhileStmt); ok { 224 // TODO: only merge statements that don't have 'in' or 'of' keywords (slow to check?) 225 decl = &js.VarDecl{TokenType: js.VarToken, List: nil} 226 var forBody *js.BlockStmt 227 if blockStmt, ok := whileStmt.Body.(*js.BlockStmt); ok { 228 forBody = blockStmt 229 } else { 230 forBody = &js.BlockStmt{} 231 forBody.List = []js.IStmt{whileStmt.Body} 232 } 233 body.List[declStart] = &js.ForStmt{Init: decl, Cond: whileStmt.Cond, Post: nil, Body: forBody} 234 mergeStatements = false 235 } 236 if decl != nil { 237 // original declarations 238 vs := []*js.Var{} 239 for i, item := range decl.List { 240 if item.Default != nil { 241 iDefines = i + 1 242 } 243 vs = append(vs, bindingRefs(item.Binding)...) 244 } 245 246 // hoist other variable declarations in this function scope but don't initialize yet 247 DeclaredLoop: 248 for _, v := range body.Scope.Declared { 249 if v.Decl == js.VariableDecl { 250 for _, vdef := range vs { 251 if v == vdef { 252 continue DeclaredLoop 253 } 254 } 255 //v.Uses++ // might be inaccurate as we remove non-defining variable declarations later on 256 decl.List = append(decl.List, js.BindingElement{Binding: v, Default: nil}) 257 } 258 } 259 } else { 260 decl = &js.VarDecl{TokenType: js.VarToken, List: nil} 261 for _, v := range body.Scope.Declared { 262 if v.Decl == js.VariableDecl { 263 v.Uses++ // might be inaccurate as we remove non-defining variable declarations later on 264 decl.List = append(decl.List, js.BindingElement{Binding: v, Default: nil}) 265 } 266 } 267 body.List = append(body.List[:declStart], append([]js.IStmt{decl}, body.List[declStart:]...)...) 268 } 269 270 if mergeStatements { 271 // pull in assignments to variables into the declaration, e.g. var a;a=5 => var a=5 272 // sort in order of definitions 273 nMerged := 0 274 declEnd := declStart + 1 275 FindDefinitionsLoop: 276 for k, item := range body.List[declEnd:] { 277 if exprStmt, ok := item.(*js.ExprStmt); ok { 278 if binaryExpr, ok := exprStmt.Value.(*js.BinaryExpr); ok && binaryExpr.Op == js.EqToken { 279 if v, ok := binaryExpr.X.(*js.Var); ok && v.Decl == js.VariableDecl { 280 if addDefinition(decl, iDefines, v, binaryExpr.Y) { 281 iDefines++ 282 nMerged++ 283 continue 284 } 285 } 286 } 287 } else if varDecl, ok := item.(*js.VarDecl); ok && varDecl.TokenType == js.VarToken { 288 for j := 0; j < len(varDecl.List); j++ { 289 item := varDecl.List[j] 290 if item.Default != nil { 291 if addDefinition(decl, iDefines, item.Binding, item.Default) { 292 iDefines++ 293 varDecl.List = append(varDecl.List[:j], varDecl.List[j+1:]...) 294 j-- 295 } else { 296 break FindDefinitionsLoop 297 } 298 } 299 // declaration has no definition, that's fine as it's already merged previously 300 } 301 body.List[declEnd+k] = varDecl // update varDecl.List 302 nMerged++ 303 continue // all variable declarations were matched, keep looking 304 } 305 break // not ExprStmt nor VarDecl 306 } 307 if 0 < nMerged { 308 body.List = append(body.List[:declEnd], body.List[declEnd+nMerged:]...) 309 } 310 } 311 m.varsHoisted = decl 312 } 313 return parentVarsHoisted 314} 315