simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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 };