scope.js (34832B)
1 /*********************************************************************** 2 3 A JavaScript tokenizer / parser / beautifier / compressor. 4 https://github.com/mishoo/UglifyJS2 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 46 import { 47 defaults, 48 keep_name, 49 mergeSort, 50 push_uniq, 51 make_node, 52 return_false, 53 return_this, 54 return_true, 55 string_template, 56 } from "./utils/index.js"; 57 import { 58 AST_Arrow, 59 AST_Block, 60 AST_Call, 61 AST_Catch, 62 AST_Class, 63 AST_Conditional, 64 AST_DefClass, 65 AST_Defun, 66 AST_Destructuring, 67 AST_Dot, 68 AST_DotHash, 69 AST_Export, 70 AST_For, 71 AST_ForIn, 72 AST_Function, 73 AST_Import, 74 AST_IterationStatement, 75 AST_Label, 76 AST_LabeledStatement, 77 AST_LabelRef, 78 AST_Lambda, 79 AST_LoopControl, 80 AST_NameMapping, 81 AST_Node, 82 AST_Scope, 83 AST_Sequence, 84 AST_String, 85 AST_Sub, 86 AST_Switch, 87 AST_SwitchBranch, 88 AST_Symbol, 89 AST_SymbolBlockDeclaration, 90 AST_SymbolCatch, 91 AST_SymbolClass, 92 AST_SymbolConst, 93 AST_SymbolDefClass, 94 AST_SymbolDefun, 95 AST_SymbolExport, 96 AST_SymbolFunarg, 97 AST_SymbolImport, 98 AST_SymbolLambda, 99 AST_SymbolLet, 100 AST_SymbolMethod, 101 AST_SymbolRef, 102 AST_SymbolVar, 103 AST_Toplevel, 104 AST_VarDef, 105 AST_With, 106 TreeWalker, 107 walk 108 } from "./ast.js"; 109 import { 110 ALL_RESERVED_WORDS, 111 js_error, 112 } from "./parse.js"; 113 114 const MASK_EXPORT_DONT_MANGLE = 1 << 0; 115 const MASK_EXPORT_WANT_MANGLE = 1 << 1; 116 117 let function_defs = null; 118 let unmangleable_names = null; 119 /** 120 * When defined, there is a function declaration somewhere that's inside of a block. 121 * See https://tc39.es/ecma262/multipage/additional-ecmascript-features-for-web-browsers.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics 122 */ 123 let scopes_with_block_defuns = null; 124 125 class SymbolDef { 126 constructor(scope, orig, init) { 127 this.name = orig.name; 128 this.orig = [ orig ]; 129 this.init = init; 130 this.eliminated = 0; 131 this.assignments = 0; 132 this.scope = scope; 133 this.replaced = 0; 134 this.global = false; 135 this.export = 0; 136 this.mangled_name = null; 137 this.undeclared = false; 138 this.id = SymbolDef.next_id++; 139 this.chained = false; 140 this.direct_access = false; 141 this.escaped = 0; 142 this.recursive_refs = 0; 143 this.references = []; 144 this.should_replace = undefined; 145 this.single_use = false; 146 this.fixed = false; 147 Object.seal(this); 148 } 149 fixed_value() { 150 if (!this.fixed || this.fixed instanceof AST_Node) return this.fixed; 151 return this.fixed(); 152 } 153 unmangleable(options) { 154 if (!options) options = {}; 155 156 if ( 157 function_defs && 158 function_defs.has(this.id) && 159 keep_name(options.keep_fnames, this.orig[0].name) 160 ) return true; 161 162 return this.global && !options.toplevel 163 || (this.export & MASK_EXPORT_DONT_MANGLE) 164 || this.undeclared 165 || !options.eval && this.scope.pinned() 166 || (this.orig[0] instanceof AST_SymbolLambda 167 || this.orig[0] instanceof AST_SymbolDefun) && keep_name(options.keep_fnames, this.orig[0].name) 168 || this.orig[0] instanceof AST_SymbolMethod 169 || (this.orig[0] instanceof AST_SymbolClass 170 || this.orig[0] instanceof AST_SymbolDefClass) && keep_name(options.keep_classnames, this.orig[0].name); 171 } 172 mangle(options) { 173 const cache = options.cache && options.cache.props; 174 if (this.global && cache && cache.has(this.name)) { 175 this.mangled_name = cache.get(this.name); 176 } else if (!this.mangled_name && !this.unmangleable(options)) { 177 var s = this.scope; 178 var sym = this.orig[0]; 179 if (options.ie8 && sym instanceof AST_SymbolLambda) 180 s = s.parent_scope; 181 const redefinition = redefined_catch_def(this); 182 this.mangled_name = redefinition 183 ? redefinition.mangled_name || redefinition.name 184 : s.next_mangled(options, this); 185 if (this.global && cache) { 186 cache.set(this.name, this.mangled_name); 187 } 188 } 189 } 190 } 191 192 SymbolDef.next_id = 1; 193 194 function redefined_catch_def(def) { 195 if (def.orig[0] instanceof AST_SymbolCatch 196 && def.scope.is_block_scope() 197 ) { 198 return def.scope.get_defun_scope().variables.get(def.name); 199 } 200 } 201 202 AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = null, toplevel = this } = {}) { 203 options = defaults(options, { 204 cache: null, 205 ie8: false, 206 safari10: false, 207 }); 208 209 if (!(toplevel instanceof AST_Toplevel)) { 210 throw new Error("Invalid toplevel scope"); 211 } 212 213 // pass 1: setup scope chaining and handle definitions 214 var scope = this.parent_scope = parent_scope; 215 var labels = new Map(); 216 var defun = null; 217 var in_destructuring = null; 218 var for_scopes = []; 219 var tw = new TreeWalker((node, descend) => { 220 if (node.is_block_scope()) { 221 const save_scope = scope; 222 node.block_scope = scope = new AST_Scope(node); 223 scope._block_scope = true; 224 // AST_Try in the AST sadly *is* (not has) a body itself, 225 // and its catch and finally branches are children of the AST_Try itself 226 const parent_scope = node instanceof AST_Catch 227 ? save_scope.parent_scope 228 : save_scope; 229 scope.init_scope_vars(parent_scope); 230 scope.uses_with = save_scope.uses_with; 231 scope.uses_eval = save_scope.uses_eval; 232 if (options.safari10) { 233 if (node instanceof AST_For || node instanceof AST_ForIn) { 234 for_scopes.push(scope); 235 } 236 } 237 238 if (node instanceof AST_Switch) { 239 // XXX: HACK! Ensure the switch expression gets the correct scope (the parent scope) and the body gets the contained scope 240 // AST_Switch has a scope within the body, but it itself "is a block scope" 241 // This means the switched expression has to belong to the outer scope 242 // while the body inside belongs to the switch itself. 243 // This is pretty nasty and warrants an AST change similar to AST_Try (read above) 244 const the_block_scope = scope; 245 scope = save_scope; 246 node.expression.walk(tw); 247 scope = the_block_scope; 248 for (let i = 0; i < node.body.length; i++) { 249 node.body[i].walk(tw); 250 } 251 } else { 252 descend(); 253 } 254 scope = save_scope; 255 return true; 256 } 257 if (node instanceof AST_Destructuring) { 258 const save_destructuring = in_destructuring; 259 in_destructuring = node; 260 descend(); 261 in_destructuring = save_destructuring; 262 return true; 263 } 264 if (node instanceof AST_Scope) { 265 node.init_scope_vars(scope); 266 var save_scope = scope; 267 var save_defun = defun; 268 var save_labels = labels; 269 defun = scope = node; 270 labels = new Map(); 271 descend(); 272 scope = save_scope; 273 defun = save_defun; 274 labels = save_labels; 275 return true; // don't descend again in TreeWalker 276 } 277 if (node instanceof AST_LabeledStatement) { 278 var l = node.label; 279 if (labels.has(l.name)) { 280 throw new Error(string_template("Label {name} defined twice", l)); 281 } 282 labels.set(l.name, l); 283 descend(); 284 labels.delete(l.name); 285 return true; // no descend again 286 } 287 if (node instanceof AST_With) { 288 for (var s = scope; s; s = s.parent_scope) 289 s.uses_with = true; 290 return; 291 } 292 if (node instanceof AST_Symbol) { 293 node.scope = scope; 294 } 295 if (node instanceof AST_Label) { 296 node.thedef = node; 297 node.references = []; 298 } 299 if (node instanceof AST_SymbolLambda) { 300 defun.def_function(node, node.name == "arguments" ? undefined : defun); 301 } else if (node instanceof AST_SymbolDefun) { 302 // Careful here, the scope where this should be defined is 303 // the parent scope. The reason is that we enter a new 304 // scope when we encounter the AST_Defun node (which is 305 // instanceof AST_Scope) but we get to the symbol a bit 306 // later. 307 const closest_scope = defun.parent_scope; 308 309 // In strict mode, function definitions are block-scoped 310 node.scope = tw.directives["use strict"] 311 ? closest_scope 312 : closest_scope.get_defun_scope(); 313 314 mark_export(node.scope.def_function(node, defun), 1); 315 } else if (node instanceof AST_SymbolClass) { 316 mark_export(defun.def_variable(node, defun), 1); 317 } else if (node instanceof AST_SymbolImport) { 318 scope.def_variable(node); 319 } else if (node instanceof AST_SymbolDefClass) { 320 // This deals with the name of the class being available 321 // inside the class. 322 mark_export((node.scope = defun.parent_scope).def_function(node, defun), 1); 323 } else if ( 324 node instanceof AST_SymbolVar 325 || node instanceof AST_SymbolLet 326 || node instanceof AST_SymbolConst 327 || node instanceof AST_SymbolCatch 328 ) { 329 var def; 330 if (node instanceof AST_SymbolBlockDeclaration) { 331 def = scope.def_variable(node, null); 332 } else { 333 def = defun.def_variable(node, node.TYPE == "SymbolVar" ? null : undefined); 334 } 335 if (!def.orig.every((sym) => { 336 if (sym === node) return true; 337 if (node instanceof AST_SymbolBlockDeclaration) { 338 return sym instanceof AST_SymbolLambda; 339 } 340 return !(sym instanceof AST_SymbolLet || sym instanceof AST_SymbolConst); 341 })) { 342 js_error( 343 `"${node.name}" is redeclared`, 344 node.start.file, 345 node.start.line, 346 node.start.col, 347 node.start.pos 348 ); 349 } 350 if (!(node instanceof AST_SymbolFunarg)) mark_export(def, 2); 351 if (defun !== scope) { 352 node.mark_enclosed(); 353 var def = scope.find_variable(node); 354 if (node.thedef !== def) { 355 node.thedef = def; 356 node.reference(); 357 } 358 } 359 } else if (node instanceof AST_LabelRef) { 360 var sym = labels.get(node.name); 361 if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", { 362 name: node.name, 363 line: node.start.line, 364 col: node.start.col 365 })); 366 node.thedef = sym; 367 } 368 if (!(scope instanceof AST_Toplevel) && (node instanceof AST_Export || node instanceof AST_Import)) { 369 js_error( 370 `"${node.TYPE}" statement may only appear at the top level`, 371 node.start.file, 372 node.start.line, 373 node.start.col, 374 node.start.pos 375 ); 376 } 377 }); 378 this.walk(tw); 379 380 function mark_export(def, level) { 381 if (in_destructuring) { 382 var i = 0; 383 do { 384 level++; 385 } while (tw.parent(i++) !== in_destructuring); 386 } 387 var node = tw.parent(level); 388 if (def.export = node instanceof AST_Export ? MASK_EXPORT_DONT_MANGLE : 0) { 389 var exported = node.exported_definition; 390 if ((exported instanceof AST_Defun || exported instanceof AST_DefClass) && node.is_default) { 391 def.export = MASK_EXPORT_WANT_MANGLE; 392 } 393 } 394 } 395 396 // pass 2: find back references and eval 397 const is_toplevel = this instanceof AST_Toplevel; 398 if (is_toplevel) { 399 this.globals = new Map(); 400 } 401 402 var tw = new TreeWalker(node => { 403 if (node instanceof AST_LoopControl && node.label) { 404 node.label.thedef.references.push(node); 405 return true; 406 } 407 if (node instanceof AST_SymbolRef) { 408 var name = node.name; 409 if (name == "eval" && tw.parent() instanceof AST_Call) { 410 for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) { 411 s.uses_eval = true; 412 } 413 } 414 var sym; 415 if (tw.parent() instanceof AST_NameMapping && tw.parent(1).module_name 416 || !(sym = node.scope.find_variable(name))) { 417 418 sym = toplevel.def_global(node); 419 if (node instanceof AST_SymbolExport) sym.export = MASK_EXPORT_DONT_MANGLE; 420 } else if (sym.scope instanceof AST_Lambda && name == "arguments") { 421 sym.scope.uses_arguments = true; 422 } 423 node.thedef = sym; 424 node.reference(); 425 if (node.scope.is_block_scope() 426 && !(sym.orig[0] instanceof AST_SymbolBlockDeclaration)) { 427 node.scope = node.scope.get_defun_scope(); 428 } 429 return true; 430 } 431 // ensure mangling works if catch reuses a scope variable 432 var def; 433 if (node instanceof AST_SymbolCatch && (def = redefined_catch_def(node.definition()))) { 434 var s = node.scope; 435 while (s) { 436 push_uniq(s.enclosed, def); 437 if (s === def.scope) break; 438 s = s.parent_scope; 439 } 440 } 441 }); 442 this.walk(tw); 443 444 // pass 3: work around IE8 and Safari catch scope bugs 445 if (options.ie8 || options.safari10) { 446 walk(this, node => { 447 if (node instanceof AST_SymbolCatch) { 448 var name = node.name; 449 var refs = node.thedef.references; 450 var scope = node.scope.get_defun_scope(); 451 var def = scope.find_variable(name) 452 || toplevel.globals.get(name) 453 || scope.def_variable(node); 454 refs.forEach(function(ref) { 455 ref.thedef = def; 456 ref.reference(); 457 }); 458 node.thedef = def; 459 node.reference(); 460 return true; 461 } 462 }); 463 } 464 465 // pass 4: add symbol definitions to loop scopes 466 // Safari/Webkit bug workaround - loop init let variable shadowing argument. 467 // https://github.com/mishoo/UglifyJS2/issues/1753 468 // https://bugs.webkit.org/show_bug.cgi?id=171041 469 if (options.safari10) { 470 for (const scope of for_scopes) { 471 scope.parent_scope.variables.forEach(function(def) { 472 push_uniq(scope.enclosed, def); 473 }); 474 } 475 } 476 }); 477 478 AST_Toplevel.DEFMETHOD("def_global", function(node) { 479 var globals = this.globals, name = node.name; 480 if (globals.has(name)) { 481 return globals.get(name); 482 } else { 483 var g = new SymbolDef(this, node); 484 g.undeclared = true; 485 g.global = true; 486 globals.set(name, g); 487 return g; 488 } 489 }); 490 491 AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope) { 492 this.variables = new Map(); // map name to AST_SymbolVar (variables defined in this scope; includes functions) 493 this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement 494 this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval` 495 this.parent_scope = parent_scope; // the parent scope 496 this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes 497 this.cname = -1; // the current index for mangling functions/variables 498 }); 499 500 AST_Scope.DEFMETHOD("conflicting_def", function (name) { 501 return ( 502 this.enclosed.find(def => def.name === name) 503 || this.variables.has(name) 504 || (this.parent_scope && this.parent_scope.conflicting_def(name)) 505 ); 506 }); 507 508 AST_Scope.DEFMETHOD("conflicting_def_shallow", function (name) { 509 return ( 510 this.enclosed.find(def => def.name === name) 511 || this.variables.has(name) 512 ); 513 }); 514 515 AST_Scope.DEFMETHOD("add_child_scope", function (scope) { 516 // `scope` is going to be moved into `this` right now. 517 // Update the required scopes' information 518 519 if (scope.parent_scope === this) return; 520 521 scope.parent_scope = this; 522 523 // TODO uses_with, uses_eval, etc 524 525 const scope_ancestry = (() => { 526 const ancestry = []; 527 let cur = this; 528 do { 529 ancestry.push(cur); 530 } while ((cur = cur.parent_scope)); 531 ancestry.reverse(); 532 return ancestry; 533 })(); 534 535 const new_scope_enclosed_set = new Set(scope.enclosed); 536 const to_enclose = []; 537 for (const scope_topdown of scope_ancestry) { 538 to_enclose.forEach(e => push_uniq(scope_topdown.enclosed, e)); 539 for (const def of scope_topdown.variables.values()) { 540 if (new_scope_enclosed_set.has(def)) { 541 push_uniq(to_enclose, def); 542 push_uniq(scope_topdown.enclosed, def); 543 } 544 } 545 } 546 }); 547 548 function find_scopes_visible_from(scopes) { 549 const found_scopes = new Set(); 550 551 for (const scope of new Set(scopes)) { 552 (function bubble_up(scope) { 553 if (scope == null || found_scopes.has(scope)) return; 554 555 found_scopes.add(scope); 556 557 bubble_up(scope.parent_scope); 558 })(scope); 559 } 560 561 return [...found_scopes]; 562 } 563 564 // Creates a symbol during compression 565 AST_Scope.DEFMETHOD("create_symbol", function(SymClass, { 566 source, 567 tentative_name, 568 scope, 569 conflict_scopes = [scope], 570 init = null 571 } = {}) { 572 let symbol_name; 573 574 conflict_scopes = find_scopes_visible_from(conflict_scopes); 575 576 if (tentative_name) { 577 // Implement hygiene (no new names are conflicting with existing names) 578 tentative_name = 579 symbol_name = 580 tentative_name.replace(/(?:^[^a-z_$]|[^a-z0-9_$])/ig, "_"); 581 582 let i = 0; 583 while (conflict_scopes.find(s => s.conflicting_def_shallow(symbol_name))) { 584 symbol_name = tentative_name + "$" + i++; 585 } 586 } 587 588 if (!symbol_name) { 589 throw new Error("No symbol name could be generated in create_symbol()"); 590 } 591 592 const symbol = make_node(SymClass, source, { 593 name: symbol_name, 594 scope 595 }); 596 597 this.def_variable(symbol, init || null); 598 599 symbol.mark_enclosed(); 600 601 return symbol; 602 }); 603 604 605 AST_Node.DEFMETHOD("is_block_scope", return_false); 606 AST_Class.DEFMETHOD("is_block_scope", return_false); 607 AST_Lambda.DEFMETHOD("is_block_scope", return_false); 608 AST_Toplevel.DEFMETHOD("is_block_scope", return_false); 609 AST_SwitchBranch.DEFMETHOD("is_block_scope", return_false); 610 AST_Block.DEFMETHOD("is_block_scope", return_true); 611 AST_Scope.DEFMETHOD("is_block_scope", function () { 612 return this._block_scope || false; 613 }); 614 AST_IterationStatement.DEFMETHOD("is_block_scope", return_true); 615 616 AST_Lambda.DEFMETHOD("init_scope_vars", function() { 617 AST_Scope.prototype.init_scope_vars.apply(this, arguments); 618 this.uses_arguments = false; 619 this.def_variable(new AST_SymbolFunarg({ 620 name: "arguments", 621 start: this.start, 622 end: this.end 623 })); 624 }); 625 626 AST_Arrow.DEFMETHOD("init_scope_vars", function() { 627 AST_Scope.prototype.init_scope_vars.apply(this, arguments); 628 this.uses_arguments = false; 629 }); 630 631 AST_Symbol.DEFMETHOD("mark_enclosed", function() { 632 var def = this.definition(); 633 var s = this.scope; 634 while (s) { 635 push_uniq(s.enclosed, def); 636 if (s === def.scope) break; 637 s = s.parent_scope; 638 } 639 }); 640 641 AST_Symbol.DEFMETHOD("reference", function() { 642 this.definition().references.push(this); 643 this.mark_enclosed(); 644 }); 645 646 AST_Scope.DEFMETHOD("find_variable", function(name) { 647 if (name instanceof AST_Symbol) name = name.name; 648 return this.variables.get(name) 649 || (this.parent_scope && this.parent_scope.find_variable(name)); 650 }); 651 652 AST_Scope.DEFMETHOD("def_function", function(symbol, init) { 653 var def = this.def_variable(symbol, init); 654 if (!def.init || def.init instanceof AST_Defun) def.init = init; 655 return def; 656 }); 657 658 AST_Scope.DEFMETHOD("def_variable", function(symbol, init) { 659 var def = this.variables.get(symbol.name); 660 if (def) { 661 def.orig.push(symbol); 662 if (def.init && (def.scope !== symbol.scope || def.init instanceof AST_Function)) { 663 def.init = init; 664 } 665 } else { 666 def = new SymbolDef(this, symbol, init); 667 this.variables.set(symbol.name, def); 668 def.global = !this.parent_scope; 669 } 670 return symbol.thedef = def; 671 }); 672 673 function next_mangled(scope, options) { 674 let defun_scope; 675 if ( 676 scopes_with_block_defuns 677 && (defun_scope = scope.get_defun_scope()) 678 && scopes_with_block_defuns.has(defun_scope) 679 ) { 680 scope = defun_scope; 681 } 682 683 var ext = scope.enclosed; 684 var nth_identifier = options.nth_identifier; 685 out: while (true) { 686 var m = nth_identifier.get(++scope.cname); 687 if (ALL_RESERVED_WORDS.has(m)) continue; // skip over "do" 688 689 // https://github.com/mishoo/UglifyJS2/issues/242 -- do not 690 // shadow a name reserved from mangling. 691 if (options.reserved.has(m)) continue; 692 693 // Functions with short names might collide with base54 output 694 // and therefore cause collisions when keep_fnames is true. 695 if (unmangleable_names && unmangleable_names.has(m)) continue out; 696 697 // we must ensure that the mangled name does not shadow a name 698 // from some parent scope that is referenced in this or in 699 // inner scopes. 700 for (let i = ext.length; --i >= 0;) { 701 const def = ext[i]; 702 const name = def.mangled_name || (def.unmangleable(options) && def.name); 703 if (m == name) continue out; 704 } 705 return m; 706 } 707 } 708 709 AST_Scope.DEFMETHOD("next_mangled", function(options) { 710 return next_mangled(this, options); 711 }); 712 713 AST_Toplevel.DEFMETHOD("next_mangled", function(options) { 714 let name; 715 const mangled_names = this.mangled_names; 716 do { 717 name = next_mangled(this, options); 718 } while (mangled_names.has(name)); 719 return name; 720 }); 721 722 AST_Function.DEFMETHOD("next_mangled", function(options, def) { 723 // #179, #326 724 // in Safari strict mode, something like (function x(x){...}) is a syntax error; 725 // a function expression's argument cannot shadow the function expression's name 726 727 var tricky_def = def.orig[0] instanceof AST_SymbolFunarg && this.name && this.name.definition(); 728 729 // the function's mangled_name is null when keep_fnames is true 730 var tricky_name = tricky_def ? tricky_def.mangled_name || tricky_def.name : null; 731 732 while (true) { 733 var name = next_mangled(this, options); 734 if (!tricky_name || tricky_name != name) 735 return name; 736 } 737 }); 738 739 AST_Symbol.DEFMETHOD("unmangleable", function(options) { 740 var def = this.definition(); 741 return !def || def.unmangleable(options); 742 }); 743 744 // labels are always mangleable 745 AST_Label.DEFMETHOD("unmangleable", return_false); 746 747 AST_Symbol.DEFMETHOD("unreferenced", function() { 748 return !this.definition().references.length && !this.scope.pinned(); 749 }); 750 751 AST_Symbol.DEFMETHOD("definition", function() { 752 return this.thedef; 753 }); 754 755 AST_Symbol.DEFMETHOD("global", function() { 756 return this.thedef.global; 757 }); 758 759 AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options) { 760 options = defaults(options, { 761 eval : false, 762 nth_identifier : base54, 763 ie8 : false, 764 keep_classnames: false, 765 keep_fnames : false, 766 module : false, 767 reserved : [], 768 toplevel : false, 769 }); 770 if (options.module) options.toplevel = true; 771 if (!Array.isArray(options.reserved) 772 && !(options.reserved instanceof Set) 773 ) { 774 options.reserved = []; 775 } 776 options.reserved = new Set(options.reserved); 777 // Never mangle arguments 778 options.reserved.add("arguments"); 779 return options; 780 }); 781 782 AST_Toplevel.DEFMETHOD("mangle_names", function(options) { 783 options = this._default_mangler_options(options); 784 var nth_identifier = options.nth_identifier; 785 786 // We only need to mangle declaration nodes. Special logic wired 787 // into the code generator will display the mangled name if it's 788 // present (and for AST_SymbolRef-s it'll use the mangled name of 789 // the AST_SymbolDeclaration that it points to). 790 var lname = -1; 791 var to_mangle = []; 792 793 if (options.keep_fnames) { 794 function_defs = new Set(); 795 } 796 797 const mangled_names = this.mangled_names = new Set(); 798 unmangleable_names = new Set(); 799 800 if (options.cache) { 801 this.globals.forEach(collect); 802 if (options.cache.props) { 803 options.cache.props.forEach(function(mangled_name) { 804 mangled_names.add(mangled_name); 805 }); 806 } 807 } 808 809 var tw = new TreeWalker(function(node, descend) { 810 if (node instanceof AST_LabeledStatement) { 811 // lname is incremented when we get to the AST_Label 812 var save_nesting = lname; 813 descend(); 814 lname = save_nesting; 815 return true; // don't descend again in TreeWalker 816 } 817 if ( 818 node instanceof AST_Defun 819 && !(tw.parent() instanceof AST_Scope) 820 ) { 821 scopes_with_block_defuns = scopes_with_block_defuns || new Set(); 822 scopes_with_block_defuns.add(node.parent_scope.get_defun_scope()); 823 } 824 if (node instanceof AST_Scope) { 825 node.variables.forEach(collect); 826 return; 827 } 828 if (node.is_block_scope()) { 829 node.block_scope.variables.forEach(collect); 830 return; 831 } 832 if ( 833 function_defs 834 && node instanceof AST_VarDef 835 && node.value instanceof AST_Lambda 836 && !node.value.name 837 && keep_name(options.keep_fnames, node.name.name) 838 ) { 839 function_defs.add(node.name.definition().id); 840 return; 841 } 842 if (node instanceof AST_Label) { 843 let name; 844 do { 845 name = nth_identifier.get(++lname); 846 } while (ALL_RESERVED_WORDS.has(name)); 847 node.mangled_name = name; 848 return true; 849 } 850 if (!(options.ie8 || options.safari10) && node instanceof AST_SymbolCatch) { 851 to_mangle.push(node.definition()); 852 return; 853 } 854 }); 855 856 this.walk(tw); 857 858 if (options.keep_fnames || options.keep_classnames) { 859 // Collect a set of short names which are unmangleable, 860 // for use in avoiding collisions in next_mangled. 861 to_mangle.forEach(def => { 862 if (def.name.length < 6 && def.unmangleable(options)) { 863 unmangleable_names.add(def.name); 864 } 865 }); 866 } 867 868 to_mangle.forEach(def => { def.mangle(options); }); 869 870 function_defs = null; 871 unmangleable_names = null; 872 scopes_with_block_defuns = null; 873 874 function collect(symbol) { 875 if (symbol.export & MASK_EXPORT_DONT_MANGLE) { 876 unmangleable_names.add(symbol.name); 877 } else if (!options.reserved.has(symbol.name)) { 878 to_mangle.push(symbol); 879 } 880 } 881 }); 882 883 AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) { 884 const cache = options.cache && options.cache.props; 885 const avoid = new Set(); 886 options.reserved.forEach(to_avoid); 887 this.globals.forEach(add_def); 888 this.walk(new TreeWalker(function(node) { 889 if (node instanceof AST_Scope) node.variables.forEach(add_def); 890 if (node instanceof AST_SymbolCatch) add_def(node.definition()); 891 })); 892 return avoid; 893 894 function to_avoid(name) { 895 avoid.add(name); 896 } 897 898 function add_def(def) { 899 var name = def.name; 900 if (def.global && cache && cache.has(name)) name = cache.get(name); 901 else if (!def.unmangleable(options)) return; 902 to_avoid(name); 903 } 904 }); 905 906 AST_Toplevel.DEFMETHOD("expand_names", function(options) { 907 options = this._default_mangler_options(options); 908 var nth_identifier = options.nth_identifier; 909 if (nth_identifier.reset && nth_identifier.sort) { 910 nth_identifier.reset(); 911 nth_identifier.sort(); 912 } 913 var avoid = this.find_colliding_names(options); 914 var cname = 0; 915 this.globals.forEach(rename); 916 this.walk(new TreeWalker(function(node) { 917 if (node instanceof AST_Scope) node.variables.forEach(rename); 918 if (node instanceof AST_SymbolCatch) rename(node.definition()); 919 })); 920 921 function next_name() { 922 var name; 923 do { 924 name = nth_identifier.get(cname++); 925 } while (avoid.has(name) || ALL_RESERVED_WORDS.has(name)); 926 return name; 927 } 928 929 function rename(def) { 930 if (def.global && options.cache) return; 931 if (def.unmangleable(options)) return; 932 if (options.reserved.has(def.name)) return; 933 const redefinition = redefined_catch_def(def); 934 const name = def.name = redefinition ? redefinition.name : next_name(); 935 def.orig.forEach(function(sym) { 936 sym.name = name; 937 }); 938 def.references.forEach(function(sym) { 939 sym.name = name; 940 }); 941 } 942 }); 943 944 AST_Node.DEFMETHOD("tail_node", return_this); 945 AST_Sequence.DEFMETHOD("tail_node", function() { 946 return this.expressions[this.expressions.length - 1]; 947 }); 948 949 AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options) { 950 options = this._default_mangler_options(options); 951 var nth_identifier = options.nth_identifier; 952 if (!nth_identifier.reset || !nth_identifier.consider || !nth_identifier.sort) { 953 // If the identifier mangler is invariant, skip computing character frequency. 954 return; 955 } 956 nth_identifier.reset(); 957 958 try { 959 AST_Node.prototype.print = function(stream, force_parens) { 960 this._print(stream, force_parens); 961 if (this instanceof AST_Symbol && !this.unmangleable(options)) { 962 nth_identifier.consider(this.name, -1); 963 } else if (options.properties) { 964 if (this instanceof AST_DotHash) { 965 nth_identifier.consider("#" + this.property, -1); 966 } else if (this instanceof AST_Dot) { 967 nth_identifier.consider(this.property, -1); 968 } else if (this instanceof AST_Sub) { 969 skip_string(this.property); 970 } 971 } 972 }; 973 nth_identifier.consider(this.print_to_string(), 1); 974 } finally { 975 AST_Node.prototype.print = AST_Node.prototype._print; 976 } 977 nth_identifier.sort(); 978 979 function skip_string(node) { 980 if (node instanceof AST_String) { 981 nth_identifier.consider(node.value, -1); 982 } else if (node instanceof AST_Conditional) { 983 skip_string(node.consequent); 984 skip_string(node.alternative); 985 } else if (node instanceof AST_Sequence) { 986 skip_string(node.tail_node()); 987 } 988 } 989 }); 990 991 const base54 = (() => { 992 const leading = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_".split(""); 993 const digits = "0123456789".split(""); 994 let chars; 995 let frequency; 996 function reset() { 997 frequency = new Map(); 998 leading.forEach(function(ch) { 999 frequency.set(ch, 0); 1000 }); 1001 digits.forEach(function(ch) { 1002 frequency.set(ch, 0); 1003 }); 1004 } 1005 function consider(str, delta) { 1006 for (var i = str.length; --i >= 0;) { 1007 frequency.set(str[i], frequency.get(str[i]) + delta); 1008 } 1009 } 1010 function compare(a, b) { 1011 return frequency.get(b) - frequency.get(a); 1012 } 1013 function sort() { 1014 chars = mergeSort(leading, compare).concat(mergeSort(digits, compare)); 1015 } 1016 // Ensure this is in a usable initial state. 1017 reset(); 1018 sort(); 1019 function base54(num) { 1020 var ret = "", base = 54; 1021 num++; 1022 do { 1023 num--; 1024 ret += chars[num % base]; 1025 num = Math.floor(num / base); 1026 base = 64; 1027 } while (num > 0); 1028 return ret; 1029 } 1030 1031 return { 1032 get: base54, 1033 consider, 1034 reset, 1035 sort 1036 }; 1037 })(); 1038 1039 export { 1040 base54, 1041 SymbolDef, 1042 };