1/*********************************************************************** 2 3 A JavaScript tokenizer / parser / beautifier / compressor. 4 https://github.com/mishoo/UglifyJS 5 6 -------------------------------- (C) --------------------------------- 7 8 Author: Mihai Bazon 9 <mihai.bazon@gmail.com> 10 http://mihai.bazon.net/blog 11 12 Distributed under the BSD license: 13 14 Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> 15 16 Redistribution and use in source and binary forms, with or without 17 modification, are permitted provided that the following conditions 18 are met: 19 20 * Redistributions of source code must retain the above 21 copyright notice, this list of conditions and the following 22 disclaimer. 23 24 * Redistributions in binary form must reproduce the above 25 copyright notice, this list of conditions and the following 26 disclaimer in the documentation and/or other materials 27 provided with the distribution. 28 29 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 30 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 33 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 34 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 38 TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 39 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 SUCH DAMAGE. 41 42 ***********************************************************************/ 43 44"use strict"; 45 46function get_builtins() { 47 var names = new Dictionary(); 48 // constants 49 [ 50 "NaN", 51 "null", 52 "true", 53 "false", 54 "Infinity", 55 "-Infinity", 56 "undefined", 57 ].forEach(add); 58 // global functions 59 [ 60 "encodeURI", 61 "encodeURIComponent", 62 "escape", 63 "eval", 64 "decodeURI", 65 "decodeURIComponent", 66 "isFinite", 67 "isNaN", 68 "parseFloat", 69 "parseInt", 70 "unescape", 71 ].forEach(add); 72 // global constructors & objects 73 var global = Function("return this")(); 74 [ 75 "Array", 76 "ArrayBuffer", 77 "Atomics", 78 "BigInt", 79 "Boolean", 80 "console", 81 "DataView", 82 "Date", 83 "Error", 84 "Function", 85 "Int8Array", 86 "Intl", 87 "JSON", 88 "Map", 89 "Math", 90 "Number", 91 "Object", 92 "Promise", 93 "Proxy", 94 "Reflect", 95 "RegExp", 96 "Set", 97 "String", 98 "Symbol", 99 "WebAssembly", 100 ].forEach(function(name) { 101 add(name); 102 var ctor = global[name]; 103 if (!ctor) return; 104 Object.getOwnPropertyNames(ctor).map(add); 105 if (typeof ctor != "function") return; 106 if (ctor.__proto__) Object.getOwnPropertyNames(ctor.__proto__).map(add); 107 if (ctor.prototype) Object.getOwnPropertyNames(ctor.prototype).map(add); 108 try { 109 Object.getOwnPropertyNames(new ctor()).map(add); 110 } catch (e) { 111 try { 112 Object.getOwnPropertyNames(ctor()).map(add); 113 } catch (e) {} 114 } 115 }); 116 return (get_builtins = function() { 117 return names.clone(); 118 })(); 119 120 function add(name) { 121 names.set(name, true); 122 } 123} 124 125function reserve_quoted_keys(ast, reserved) { 126 ast.walk(new TreeWalker(function(node) { 127 if (node instanceof AST_ClassProperty 128 || node instanceof AST_DestructuredKeyVal 129 || node instanceof AST_ObjectProperty) { 130 if (node.key instanceof AST_Node) { 131 addStrings(node.key, add); 132 } else if (node.start && node.start.quote) { 133 add(node.key); 134 } 135 } else if (node instanceof AST_Dot) { 136 if (node.quoted) add(node.property); 137 } else if (node instanceof AST_Sub) { 138 addStrings(node.property, add); 139 } 140 })); 141 142 function add(name) { 143 push_uniq(reserved, name); 144 } 145} 146 147function addStrings(node, add) { 148 if (node instanceof AST_Conditional) { 149 addStrings(node.consequent, add); 150 addStrings(node.alternative, add); 151 } else if (node instanceof AST_Sequence) { 152 addStrings(node.tail_node(), add); 153 } else if (node instanceof AST_String) { 154 add(node.value); 155 } 156} 157 158function mangle_properties(ast, options) { 159 options = defaults(options, { 160 builtins: false, 161 cache: null, 162 debug: false, 163 domprops: false, 164 keep_quoted: false, 165 regex: null, 166 reserved: null, 167 }, true); 168 169 var reserved = options.builtins ? new Dictionary() : get_builtins(); 170 if (!options.domprops && typeof domprops !== "undefined") domprops.forEach(function(name) { 171 reserved.set(name, true); 172 }); 173 if (Array.isArray(options.reserved)) options.reserved.forEach(function(name) { 174 reserved.set(name, true); 175 }); 176 177 var cname = -1; 178 var cache; 179 if (options.cache) { 180 cache = options.cache.props; 181 cache.each(function(name) { 182 reserved.set(name, true); 183 }); 184 } else { 185 cache = new Dictionary(); 186 } 187 188 var regex = options.regex; 189 190 // note debug is either false (disabled), or a string of the debug suffix to use (enabled). 191 // note debug may be enabled as an empty string, which is falsy. Also treat passing 'true' 192 // the same as passing an empty string. 193 var debug = options.debug !== false; 194 var debug_suffix; 195 if (debug) debug_suffix = options.debug === true ? "" : options.debug; 196 197 var names_to_mangle = new Dictionary(); 198 var unmangleable = reserved.clone(); 199 200 // step 1: find candidates to mangle 201 ast.walk(new TreeWalker(function(node) { 202 if (node.TYPE == "Call") { 203 var exp = node.expression; 204 if (exp instanceof AST_Dot) switch (exp.property) { 205 case "defineProperty": 206 case "getOwnPropertyDescriptor": 207 if (node.args.length < 2) break; 208 exp = exp.expression; 209 if (!(exp instanceof AST_SymbolRef)) break; 210 if (exp.name != "Object") break; 211 if (!exp.definition().undeclared) break; 212 addStrings(node.args[1], add); 213 break; 214 case "hasOwnProperty": 215 if (node.args.length < 1) break; 216 addStrings(node.args[0], add); 217 break; 218 } 219 } else if (node instanceof AST_ClassProperty 220 || node instanceof AST_DestructuredKeyVal 221 || node instanceof AST_ObjectProperty) { 222 if (node.key instanceof AST_Node) { 223 addStrings(node.key, add); 224 } else { 225 add(node.key); 226 } 227 } else if (node instanceof AST_Dot) { 228 if (is_lhs(node, this.parent())) add(node.property); 229 } else if (node instanceof AST_Sub) { 230 if (is_lhs(node, this.parent())) addStrings(node.property, add); 231 } 232 })); 233 234 // step 2: renaming properties 235 ast.walk(new TreeWalker(function(node) { 236 if (node instanceof AST_Binary) { 237 if (node.operator == "in") mangleStrings(node.left); 238 } else if (node.TYPE == "Call") { 239 var exp = node.expression; 240 if (exp instanceof AST_Dot) switch (exp.property) { 241 case "defineProperty": 242 case "getOwnPropertyDescriptor": 243 if (node.args.length < 2) break; 244 exp = exp.expression; 245 if (!(exp instanceof AST_SymbolRef)) break; 246 if (exp.name != "Object") break; 247 if (!exp.definition().undeclared) break; 248 mangleStrings(node.args[1]); 249 break; 250 case "hasOwnProperty": 251 if (node.args.length < 1) break; 252 mangleStrings(node.args[0]); 253 break; 254 } 255 } else if (node instanceof AST_ClassProperty 256 || node instanceof AST_DestructuredKeyVal 257 || node instanceof AST_ObjectProperty) { 258 if (node.key instanceof AST_Node) { 259 mangleStrings(node.key); 260 } else { 261 node.key = mangle(node.key); 262 } 263 } else if (node instanceof AST_Dot) { 264 node.property = mangle(node.property); 265 } else if (node instanceof AST_Sub) { 266 if (!options.keep_quoted) mangleStrings(node.property); 267 } 268 })); 269 270 // only function declarations after this line 271 272 function can_mangle(name) { 273 if (unmangleable.has(name)) return false; 274 if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false; 275 return true; 276 } 277 278 function should_mangle(name) { 279 if (reserved.has(name)) { 280 AST_Node.info("Preserving reserved property {this}", name); 281 return false; 282 } 283 if (regex && !regex.test(name)) { 284 AST_Node.info("Preserving excluded property {this}", name); 285 return false; 286 } 287 return cache.has(name) || names_to_mangle.has(name); 288 } 289 290 function add(name) { 291 if (can_mangle(name)) names_to_mangle.set(name, true); 292 if (!should_mangle(name)) unmangleable.set(name, true); 293 } 294 295 function mangle(name) { 296 if (!should_mangle(name)) return name; 297 var mangled = cache.get(name); 298 if (!mangled) { 299 if (debug) { 300 // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo ---> o._$foo$NNN_. 301 var debug_mangled = "_$" + name + "$" + debug_suffix + "_"; 302 if (can_mangle(debug_mangled)) mangled = debug_mangled; 303 } 304 // either debug mode is off, or it is on and we could not use the mangled name 305 if (!mangled) do { 306 mangled = base54(++cname); 307 } while (!can_mangle(mangled)); 308 if (/^#/.test(name)) mangled = "#" + mangled; 309 cache.set(name, mangled); 310 } 311 AST_Node.info("Mapping property {name} to {mangled}", { 312 mangled: mangled, 313 name: name, 314 }); 315 return mangled; 316 } 317 318 function mangleStrings(node) { 319 if (node instanceof AST_Sequence) { 320 mangleStrings(node.tail_node()); 321 } else if (node instanceof AST_String) { 322 node.value = mangle(node.value); 323 } else if (node instanceof AST_Conditional) { 324 mangleStrings(node.consequent); 325 mangleStrings(node.alternative); 326 } 327 } 328} 329