You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							483 lines
						
					
					
						
							20 KiB
						
					
					
				
			
		
		
	
	
							483 lines
						
					
					
						
							20 KiB
						
					
					
				/***********************************************************************
 | 
						|
 | 
						|
  A JavaScript tokenizer / parser / beautifier / compressor.
 | 
						|
  https://github.com/mishoo/UglifyJS2
 | 
						|
 | 
						|
  -------------------------------- (C) ---------------------------------
 | 
						|
 | 
						|
                           Author: Mihai Bazon
 | 
						|
                         <mihai.bazon@gmail.com>
 | 
						|
                       http://mihai.bazon.net/blog
 | 
						|
 | 
						|
  Distributed under the BSD license:
 | 
						|
 | 
						|
    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
 | 
						|
 | 
						|
    Redistribution and use in source and binary forms, with or without
 | 
						|
    modification, are permitted provided that the following conditions
 | 
						|
    are met:
 | 
						|
 | 
						|
        * Redistributions of source code must retain the above
 | 
						|
          copyright notice, this list of conditions and the following
 | 
						|
          disclaimer.
 | 
						|
 | 
						|
        * Redistributions in binary form must reproduce the above
 | 
						|
          copyright notice, this list of conditions and the following
 | 
						|
          disclaimer in the documentation and/or other materials
 | 
						|
          provided with the distribution.
 | 
						|
 | 
						|
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
 | 
						|
    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 | 
						|
    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
 | 
						|
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 | 
						|
    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | 
						|
    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 | 
						|
    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
						|
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 | 
						|
    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 | 
						|
    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | 
						|
    SUCH DAMAGE.
 | 
						|
 | 
						|
 ***********************************************************************/
 | 
						|
 | 
						|
import {
 | 
						|
    AST_Accessor,
 | 
						|
    AST_Assign,
 | 
						|
    AST_BlockStatement,
 | 
						|
    AST_Class,
 | 
						|
    AST_ClassExpression,
 | 
						|
    AST_DefaultAssign,
 | 
						|
    AST_DefClass,
 | 
						|
    AST_Definitions,
 | 
						|
    AST_Defun,
 | 
						|
    AST_Destructuring,
 | 
						|
    AST_EmptyStatement,
 | 
						|
    AST_Expansion,
 | 
						|
    AST_Export,
 | 
						|
    AST_For,
 | 
						|
    AST_ForIn,
 | 
						|
    AST_Function,
 | 
						|
    AST_LabeledStatement,
 | 
						|
    AST_Lambda,
 | 
						|
    AST_Number,
 | 
						|
    AST_Scope,
 | 
						|
    AST_SimpleStatement,
 | 
						|
    AST_SymbolBlockDeclaration,
 | 
						|
    AST_SymbolCatch,
 | 
						|
    AST_SymbolDeclaration,
 | 
						|
    AST_SymbolFunarg,
 | 
						|
    AST_SymbolRef,
 | 
						|
    AST_SymbolVar,
 | 
						|
    AST_Toplevel,
 | 
						|
    AST_Unary,
 | 
						|
    AST_Var,
 | 
						|
 | 
						|
    TreeTransformer,
 | 
						|
    TreeWalker,
 | 
						|
    walk,
 | 
						|
 | 
						|
    _INLINE,
 | 
						|
    _NOINLINE,
 | 
						|
    _PURE
 | 
						|
} from "../ast.js";
 | 
						|
import {
 | 
						|
    keep_name,
 | 
						|
    make_node,
 | 
						|
    map_add,
 | 
						|
    MAP,
 | 
						|
    remove,
 | 
						|
    return_false,
 | 
						|
} from "../utils/index.js";
 | 
						|
import { SymbolDef } from "../scope.js";
 | 
						|
 | 
						|
import {
 | 
						|
    WRITE_ONLY,
 | 
						|
    UNUSED,
 | 
						|
 | 
						|
    has_flag,
 | 
						|
    set_flag,
 | 
						|
} from "./compressor-flags.js";
 | 
						|
import {
 | 
						|
    make_sequence,
 | 
						|
    maintain_this_binding,
 | 
						|
    is_empty,
 | 
						|
    is_ref_of,
 | 
						|
    can_be_evicted_from_block,
 | 
						|
} from "./common.js";
 | 
						|
 | 
						|
const r_keep_assign = /keep_assign/;
 | 
						|
 | 
						|
/** Drop unused variables from this scope */
 | 
						|
AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
 | 
						|
    if (!compressor.option("unused")) return;
 | 
						|
    if (compressor.has_directive("use asm")) return;
 | 
						|
    var self = this;
 | 
						|
    if (self.pinned()) return;
 | 
						|
    var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
 | 
						|
    var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
 | 
						|
    const assign_as_unused = r_keep_assign.test(compressor.option("unused")) ? return_false : function(node) {
 | 
						|
        if (node instanceof AST_Assign
 | 
						|
            && !node.logical
 | 
						|
            && (has_flag(node, WRITE_ONLY) || node.operator == "=")
 | 
						|
        ) {
 | 
						|
            return node.left;
 | 
						|
        }
 | 
						|
        if (node instanceof AST_Unary && has_flag(node, WRITE_ONLY)) {
 | 
						|
            return node.expression;
 | 
						|
        }
 | 
						|
    };
 | 
						|
    var in_use_ids = new Map();
 | 
						|
    var fixed_ids = new Map();
 | 
						|
    if (self instanceof AST_Toplevel && compressor.top_retain) {
 | 
						|
        self.variables.forEach(function(def) {
 | 
						|
            if (compressor.top_retain(def) && !in_use_ids.has(def.id)) {
 | 
						|
                in_use_ids.set(def.id, def);
 | 
						|
            }
 | 
						|
        });
 | 
						|
    }
 | 
						|
    var var_defs_by_id = new Map();
 | 
						|
    var initializations = new Map();
 | 
						|
    // pass 1: find out which symbols are directly used in
 | 
						|
    // this scope (not in nested scopes).
 | 
						|
    var scope = this;
 | 
						|
    var tw = new TreeWalker(function(node, descend) {
 | 
						|
        if (node instanceof AST_Lambda && node.uses_arguments && !tw.has_directive("use strict")) {
 | 
						|
            node.argnames.forEach(function(argname) {
 | 
						|
                if (!(argname instanceof AST_SymbolDeclaration)) return;
 | 
						|
                var def = argname.definition();
 | 
						|
                if (!in_use_ids.has(def.id)) {
 | 
						|
                    in_use_ids.set(def.id, def);
 | 
						|
                }
 | 
						|
            });
 | 
						|
        }
 | 
						|
        if (node === self) return;
 | 
						|
        if (node instanceof AST_Defun || node instanceof AST_DefClass) {
 | 
						|
            var node_def = node.name.definition();
 | 
						|
            const in_export = tw.parent() instanceof AST_Export;
 | 
						|
            if (in_export || !drop_funcs && scope === self) {
 | 
						|
                if (node_def.global && !in_use_ids.has(node_def.id)) {
 | 
						|
                    in_use_ids.set(node_def.id, node_def);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if (node instanceof AST_DefClass) {
 | 
						|
                if (
 | 
						|
                    node.extends
 | 
						|
                    && (node.extends.has_side_effects(compressor)
 | 
						|
                    || node.extends.may_throw(compressor))
 | 
						|
                ) {
 | 
						|
                    node.extends.walk(tw);
 | 
						|
                }
 | 
						|
                for (const prop of node.properties) {
 | 
						|
                    if (
 | 
						|
                        prop.has_side_effects(compressor) ||
 | 
						|
                        prop.may_throw(compressor)
 | 
						|
                    ) {
 | 
						|
                        prop.walk(tw);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            map_add(initializations, node_def.id, node);
 | 
						|
            return true; // don't go in nested scopes
 | 
						|
        }
 | 
						|
        if (node instanceof AST_SymbolFunarg && scope === self) {
 | 
						|
            map_add(var_defs_by_id, node.definition().id, node);
 | 
						|
        }
 | 
						|
        if (node instanceof AST_Definitions && scope === self) {
 | 
						|
            const in_export = tw.parent() instanceof AST_Export;
 | 
						|
            node.definitions.forEach(function(def) {
 | 
						|
                if (def.name instanceof AST_SymbolVar) {
 | 
						|
                    map_add(var_defs_by_id, def.name.definition().id, def);
 | 
						|
                }
 | 
						|
                if (in_export || !drop_vars) {
 | 
						|
                    walk(def.name, node => {
 | 
						|
                        if (node instanceof AST_SymbolDeclaration) {
 | 
						|
                            const def = node.definition();
 | 
						|
                            if (def.global && !in_use_ids.has(def.id)) {
 | 
						|
                                in_use_ids.set(def.id, def);
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    });
 | 
						|
                }
 | 
						|
                if (def.name instanceof AST_Destructuring) {
 | 
						|
                    def.walk(tw);
 | 
						|
                }
 | 
						|
                if (def.name instanceof AST_SymbolDeclaration && def.value) {
 | 
						|
                    var node_def = def.name.definition();
 | 
						|
                    map_add(initializations, node_def.id, def.value);
 | 
						|
                    if (!node_def.chained && def.name.fixed_value() === def.value) {
 | 
						|
                        fixed_ids.set(node_def.id, def);
 | 
						|
                    }
 | 
						|
                    if (def.value.has_side_effects(compressor)) {
 | 
						|
                        def.value.walk(tw);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            });
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        return scan_ref_scoped(node, descend);
 | 
						|
    });
 | 
						|
    self.walk(tw);
 | 
						|
    // pass 2: for every used symbol we need to walk its
 | 
						|
    // initialization code to figure out if it uses other
 | 
						|
    // symbols (that may not be in_use).
 | 
						|
    tw = new TreeWalker(scan_ref_scoped);
 | 
						|
    in_use_ids.forEach(function (def) {
 | 
						|
        var init = initializations.get(def.id);
 | 
						|
        if (init) init.forEach(function(init) {
 | 
						|
            init.walk(tw);
 | 
						|
        });
 | 
						|
    });
 | 
						|
    // pass 3: we should drop declarations not in_use
 | 
						|
    var tt = new TreeTransformer(
 | 
						|
        function before(node, descend, in_list) {
 | 
						|
            var parent = tt.parent();
 | 
						|
            if (drop_vars) {
 | 
						|
                const sym = assign_as_unused(node);
 | 
						|
                if (sym instanceof AST_SymbolRef) {
 | 
						|
                    var def = sym.definition();
 | 
						|
                    var in_use = in_use_ids.has(def.id);
 | 
						|
                    if (node instanceof AST_Assign) {
 | 
						|
                        if (!in_use || fixed_ids.has(def.id) && fixed_ids.get(def.id) !== node) {
 | 
						|
                            return maintain_this_binding(parent, node, node.right.transform(tt));
 | 
						|
                        }
 | 
						|
                    } else if (!in_use) return in_list ? MAP.skip : make_node(AST_Number, node, {
 | 
						|
                        value: 0
 | 
						|
                    });
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if (scope !== self) return;
 | 
						|
            var def;
 | 
						|
            if (node.name
 | 
						|
                && (node instanceof AST_ClassExpression
 | 
						|
                    && !keep_name(compressor.option("keep_classnames"), (def = node.name.definition()).name)
 | 
						|
                || node instanceof AST_Function
 | 
						|
                    && !keep_name(compressor.option("keep_fnames"), (def = node.name.definition()).name))) {
 | 
						|
                // any declarations with same name will overshadow
 | 
						|
                // name of this anonymous function and can therefore
 | 
						|
                // never be used anywhere
 | 
						|
                if (!in_use_ids.has(def.id) || def.orig.length > 1) node.name = null;
 | 
						|
            }
 | 
						|
            if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
 | 
						|
                var trim = !compressor.option("keep_fargs");
 | 
						|
                for (var a = node.argnames, i = a.length; --i >= 0;) {
 | 
						|
                    var sym = a[i];
 | 
						|
                    if (sym instanceof AST_Expansion) {
 | 
						|
                        sym = sym.expression;
 | 
						|
                    }
 | 
						|
                    if (sym instanceof AST_DefaultAssign) {
 | 
						|
                        sym = sym.left;
 | 
						|
                    }
 | 
						|
                    // Do not drop destructuring arguments.
 | 
						|
                    // They constitute a type assertion of sorts
 | 
						|
                    if (
 | 
						|
                        !(sym instanceof AST_Destructuring)
 | 
						|
                        && !in_use_ids.has(sym.definition().id)
 | 
						|
                    ) {
 | 
						|
                        set_flag(sym, UNUSED);
 | 
						|
                        if (trim) {
 | 
						|
                            a.pop();
 | 
						|
                        }
 | 
						|
                    } else {
 | 
						|
                        trim = false;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if ((node instanceof AST_Defun || node instanceof AST_DefClass) && node !== self) {
 | 
						|
                const def = node.name.definition();
 | 
						|
                const keep = def.global && !drop_funcs || in_use_ids.has(def.id);
 | 
						|
                // Class "extends" and static blocks may have side effects
 | 
						|
                const has_side_effects = !keep
 | 
						|
                    && node instanceof AST_Class
 | 
						|
                    && node.has_side_effects(compressor);
 | 
						|
                if (!keep && !has_side_effects) {
 | 
						|
                    def.eliminated++;
 | 
						|
                    return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) {
 | 
						|
                var drop_block = !(parent instanceof AST_Toplevel) && !(node instanceof AST_Var);
 | 
						|
                // place uninitialized names at the start
 | 
						|
                var body = [], head = [], tail = [];
 | 
						|
                // for unused names whose initialization has
 | 
						|
                // side effects, we can cascade the init. code
 | 
						|
                // into the next one, or next statement.
 | 
						|
                var side_effects = [];
 | 
						|
                node.definitions.forEach(function(def) {
 | 
						|
                    if (def.value) def.value = def.value.transform(tt);
 | 
						|
                    var is_destructure = def.name instanceof AST_Destructuring;
 | 
						|
                    var sym = is_destructure
 | 
						|
                        ? new SymbolDef(null, { name: "<destructure>" }) /* fake SymbolDef */
 | 
						|
                        : def.name.definition();
 | 
						|
                    if (drop_block && sym.global) return tail.push(def);
 | 
						|
                    if (!(drop_vars || drop_block)
 | 
						|
                        || is_destructure
 | 
						|
                            && (def.name.names.length
 | 
						|
                                || def.name.is_array
 | 
						|
                                || compressor.option("pure_getters") != true)
 | 
						|
                        || in_use_ids.has(sym.id)
 | 
						|
                    ) {
 | 
						|
                        if (def.value && fixed_ids.has(sym.id) && fixed_ids.get(sym.id) !== def) {
 | 
						|
                            def.value = def.value.drop_side_effect_free(compressor);
 | 
						|
                        }
 | 
						|
                        if (def.name instanceof AST_SymbolVar) {
 | 
						|
                            var var_defs = var_defs_by_id.get(sym.id);
 | 
						|
                            if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
 | 
						|
                                if (def.value) {
 | 
						|
                                    var ref = make_node(AST_SymbolRef, def.name, def.name);
 | 
						|
                                    sym.references.push(ref);
 | 
						|
                                    var assign = make_node(AST_Assign, def, {
 | 
						|
                                        operator: "=",
 | 
						|
                                        logical: false,
 | 
						|
                                        left: ref,
 | 
						|
                                        right: def.value
 | 
						|
                                    });
 | 
						|
                                    if (fixed_ids.get(sym.id) === def) {
 | 
						|
                                        fixed_ids.set(sym.id, assign);
 | 
						|
                                    }
 | 
						|
                                    side_effects.push(assign.transform(tt));
 | 
						|
                                }
 | 
						|
                                remove(var_defs, def);
 | 
						|
                                sym.eliminated++;
 | 
						|
                                return;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        if (def.value) {
 | 
						|
                            if (side_effects.length > 0) {
 | 
						|
                                if (tail.length > 0) {
 | 
						|
                                    side_effects.push(def.value);
 | 
						|
                                    def.value = make_sequence(def.value, side_effects);
 | 
						|
                                } else {
 | 
						|
                                    body.push(make_node(AST_SimpleStatement, node, {
 | 
						|
                                        body: make_sequence(node, side_effects)
 | 
						|
                                    }));
 | 
						|
                                }
 | 
						|
                                side_effects = [];
 | 
						|
                            }
 | 
						|
                            tail.push(def);
 | 
						|
                        } else {
 | 
						|
                            head.push(def);
 | 
						|
                        }
 | 
						|
                    } else if (sym.orig[0] instanceof AST_SymbolCatch) {
 | 
						|
                        var value = def.value && def.value.drop_side_effect_free(compressor);
 | 
						|
                        if (value) side_effects.push(value);
 | 
						|
                        def.value = null;
 | 
						|
                        head.push(def);
 | 
						|
                    } else {
 | 
						|
                        var value = def.value && def.value.drop_side_effect_free(compressor);
 | 
						|
                        if (value) {
 | 
						|
                            side_effects.push(value);
 | 
						|
                        }
 | 
						|
                        sym.eliminated++;
 | 
						|
                    }
 | 
						|
                });
 | 
						|
                if (head.length > 0 || tail.length > 0) {
 | 
						|
                    node.definitions = head.concat(tail);
 | 
						|
                    body.push(node);
 | 
						|
                }
 | 
						|
                if (side_effects.length > 0) {
 | 
						|
                    body.push(make_node(AST_SimpleStatement, node, {
 | 
						|
                        body: make_sequence(node, side_effects)
 | 
						|
                    }));
 | 
						|
                }
 | 
						|
                switch (body.length) {
 | 
						|
                  case 0:
 | 
						|
                    return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
 | 
						|
                  case 1:
 | 
						|
                    return body[0];
 | 
						|
                  default:
 | 
						|
                    return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
 | 
						|
                        body: body
 | 
						|
                    });
 | 
						|
                }
 | 
						|
            }
 | 
						|
            // certain combination of unused name + side effect leads to:
 | 
						|
            //    https://github.com/mishoo/UglifyJS2/issues/44
 | 
						|
            //    https://github.com/mishoo/UglifyJS2/issues/1830
 | 
						|
            //    https://github.com/mishoo/UglifyJS2/issues/1838
 | 
						|
            // that's an invalid AST.
 | 
						|
            // We fix it at this stage by moving the `var` outside the `for`.
 | 
						|
            if (node instanceof AST_For) {
 | 
						|
                descend(node, this);
 | 
						|
                var block;
 | 
						|
                if (node.init instanceof AST_BlockStatement) {
 | 
						|
                    block = node.init;
 | 
						|
                    node.init = block.body.pop();
 | 
						|
                    block.body.push(node);
 | 
						|
                }
 | 
						|
                if (node.init instanceof AST_SimpleStatement) {
 | 
						|
                    node.init = node.init.body;
 | 
						|
                } else if (is_empty(node.init)) {
 | 
						|
                    node.init = null;
 | 
						|
                }
 | 
						|
                return !block ? node : in_list ? MAP.splice(block.body) : block;
 | 
						|
            }
 | 
						|
            if (node instanceof AST_LabeledStatement
 | 
						|
                && node.body instanceof AST_For
 | 
						|
            ) {
 | 
						|
                descend(node, this);
 | 
						|
                if (node.body instanceof AST_BlockStatement) {
 | 
						|
                    var block = node.body;
 | 
						|
                    node.body = block.body.pop();
 | 
						|
                    block.body.push(node);
 | 
						|
                    return in_list ? MAP.splice(block.body) : block;
 | 
						|
                }
 | 
						|
                return node;
 | 
						|
            }
 | 
						|
            if (node instanceof AST_BlockStatement) {
 | 
						|
                descend(node, this);
 | 
						|
                if (in_list && node.body.every(can_be_evicted_from_block)) {
 | 
						|
                    return MAP.splice(node.body);
 | 
						|
                }
 | 
						|
                return node;
 | 
						|
            }
 | 
						|
            if (node instanceof AST_Scope) {
 | 
						|
                const save_scope = scope;
 | 
						|
                scope = node;
 | 
						|
                descend(node, this);
 | 
						|
                scope = save_scope;
 | 
						|
                return node;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    );
 | 
						|
 | 
						|
    self.transform(tt);
 | 
						|
 | 
						|
    function scan_ref_scoped(node, descend) {
 | 
						|
        var node_def;
 | 
						|
        const sym = assign_as_unused(node);
 | 
						|
        if (sym instanceof AST_SymbolRef
 | 
						|
            && !is_ref_of(node.left, AST_SymbolBlockDeclaration)
 | 
						|
            && self.variables.get(sym.name) === (node_def = sym.definition())
 | 
						|
        ) {
 | 
						|
            if (node instanceof AST_Assign) {
 | 
						|
                node.right.walk(tw);
 | 
						|
                if (!node_def.chained && node.left.fixed_value() === node.right) {
 | 
						|
                    fixed_ids.set(node_def.id, node);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        if (node instanceof AST_SymbolRef) {
 | 
						|
            node_def = node.definition();
 | 
						|
            if (!in_use_ids.has(node_def.id)) {
 | 
						|
                in_use_ids.set(node_def.id, node_def);
 | 
						|
                if (node_def.orig[0] instanceof AST_SymbolCatch) {
 | 
						|
                    const redef = node_def.scope.is_block_scope()
 | 
						|
                        && node_def.scope.get_defun_scope().variables.get(node_def.name);
 | 
						|
                    if (redef) in_use_ids.set(redef.id, redef);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        if (node instanceof AST_Scope) {
 | 
						|
            var save_scope = scope;
 | 
						|
            scope = node;
 | 
						|
            descend();
 | 
						|
            scope = save_scope;
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
    }
 | 
						|
});
 | 
						|
 |