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