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.
		
		
		
		
		
			
		
			
				
					1462 lines
				
				57 KiB
			
		
		
			
		
	
	
					1462 lines
				
				57 KiB
			| 
											3 years ago
										 | /*********************************************************************** | ||
|  | 
 | ||
|  |   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_Array, | ||
|  |     AST_Arrow, | ||
|  |     AST_Assign, | ||
|  |     AST_Await, | ||
|  |     AST_Binary, | ||
|  |     AST_Block, | ||
|  |     AST_BlockStatement, | ||
|  |     AST_Break, | ||
|  |     AST_Call, | ||
|  |     AST_Case, | ||
|  |     AST_Catch, | ||
|  |     AST_Chain, | ||
|  |     AST_Class, | ||
|  |     AST_Conditional, | ||
|  |     AST_Const, | ||
|  |     AST_Constant, | ||
|  |     AST_Continue, | ||
|  |     AST_Debugger, | ||
|  |     AST_Default, | ||
|  |     AST_Definitions, | ||
|  |     AST_Defun, | ||
|  |     AST_Destructuring, | ||
|  |     AST_Directive, | ||
|  |     AST_Dot, | ||
|  |     AST_DWLoop, | ||
|  |     AST_EmptyStatement, | ||
|  |     AST_Exit, | ||
|  |     AST_Expansion, | ||
|  |     AST_Export, | ||
|  |     AST_Finally, | ||
|  |     AST_For, | ||
|  |     AST_ForIn, | ||
|  |     AST_If, | ||
|  |     AST_Import, | ||
|  |     AST_IterationStatement, | ||
|  |     AST_Lambda, | ||
|  |     AST_Let, | ||
|  |     AST_LoopControl, | ||
|  |     AST_Node, | ||
|  |     AST_Number, | ||
|  |     AST_Object, | ||
|  |     AST_ObjectKeyVal, | ||
|  |     AST_PropAccess, | ||
|  |     AST_RegExp, | ||
|  |     AST_Return, | ||
|  |     AST_Scope, | ||
|  |     AST_Sequence, | ||
|  |     AST_SimpleStatement, | ||
|  |     AST_Sub, | ||
|  |     AST_Switch, | ||
|  |     AST_Symbol, | ||
|  |     AST_SymbolConst, | ||
|  |     AST_SymbolDeclaration, | ||
|  |     AST_SymbolDefun, | ||
|  |     AST_SymbolFunarg, | ||
|  |     AST_SymbolLambda, | ||
|  |     AST_SymbolLet, | ||
|  |     AST_SymbolRef, | ||
|  |     AST_SymbolVar, | ||
|  |     AST_This, | ||
|  |     AST_Try, | ||
|  |     AST_Unary, | ||
|  |     AST_UnaryPostfix, | ||
|  |     AST_UnaryPrefix, | ||
|  |     AST_Undefined, | ||
|  |     AST_Var, | ||
|  |     AST_VarDef, | ||
|  |     AST_With, | ||
|  |     AST_Yield, | ||
|  | 
 | ||
|  |     TreeTransformer, | ||
|  |     TreeWalker, | ||
|  |     walk, | ||
|  |     walk_abort, | ||
|  | 
 | ||
|  |     _NOINLINE | ||
|  | } from "../ast.js"; | ||
|  | import { | ||
|  |     make_node, | ||
|  |     MAP, | ||
|  |     member, | ||
|  |     remove, | ||
|  |     has_annotation | ||
|  | } from "../utils/index.js"; | ||
|  | 
 | ||
|  | import { pure_prop_access_globals } from "./native-objects.js"; | ||
|  | import { | ||
|  |     lazy_op, | ||
|  |     unary_side_effects, | ||
|  |     is_modified, | ||
|  |     is_lhs, | ||
|  |     aborts | ||
|  | } from "./inference.js"; | ||
|  | import { WRITE_ONLY, clear_flag } from "./compressor-flags.js"; | ||
|  | import { | ||
|  |     make_sequence, | ||
|  |     merge_sequence, | ||
|  |     maintain_this_binding, | ||
|  |     is_func_expr, | ||
|  |     is_identifier_atom, | ||
|  |     is_ref_of, | ||
|  |     can_be_evicted_from_block, | ||
|  |     as_statement_array, | ||
|  | } from "./common.js"; | ||
|  | 
 | ||
|  | function loop_body(x) { | ||
|  |     if (x instanceof AST_IterationStatement) { | ||
|  |         return x.body instanceof AST_BlockStatement ? x.body : x; | ||
|  |     } | ||
|  |     return x; | ||
|  | } | ||
|  | 
 | ||
|  | function is_lhs_read_only(lhs) { | ||
|  |     if (lhs instanceof AST_This) return true; | ||
|  |     if (lhs instanceof AST_SymbolRef) return lhs.definition().orig[0] instanceof AST_SymbolLambda; | ||
|  |     if (lhs instanceof AST_PropAccess) { | ||
|  |         lhs = lhs.expression; | ||
|  |         if (lhs instanceof AST_SymbolRef) { | ||
|  |             if (lhs.is_immutable()) return false; | ||
|  |             lhs = lhs.fixed_value(); | ||
|  |         } | ||
|  |         if (!lhs) return true; | ||
|  |         if (lhs instanceof AST_RegExp) return false; | ||
|  |         if (lhs instanceof AST_Constant) return true; | ||
|  |         return is_lhs_read_only(lhs); | ||
|  |     } | ||
|  |     return false; | ||
|  | } | ||
|  | 
 | ||
|  | // Remove code which we know is unreachable.
 | ||
|  | export function trim_unreachable_code(compressor, stat, target) { | ||
|  |     walk(stat, node => { | ||
|  |         if (node instanceof AST_Var) { | ||
|  |             node.remove_initializers(); | ||
|  |             target.push(node); | ||
|  |             return true; | ||
|  |         } | ||
|  |         if ( | ||
|  |             node instanceof AST_Defun | ||
|  |             && (node === stat || !compressor.has_directive("use strict")) | ||
|  |         ) { | ||
|  |             target.push(node === stat ? node : make_node(AST_Var, node, { | ||
|  |                 definitions: [ | ||
|  |                     make_node(AST_VarDef, node, { | ||
|  |                         name: make_node(AST_SymbolVar, node.name, node.name), | ||
|  |                         value: null | ||
|  |                     }) | ||
|  |                 ] | ||
|  |             })); | ||
|  |             return true; | ||
|  |         } | ||
|  |         if (node instanceof AST_Export || node instanceof AST_Import) { | ||
|  |             target.push(node); | ||
|  |             return true; | ||
|  |         } | ||
|  |         if (node instanceof AST_Scope) { | ||
|  |             return true; | ||
|  |         } | ||
|  |     }); | ||
|  | } | ||
|  | 
 | ||
|  | /** Tighten a bunch of statements together, and perform statement-level optimization. */ | ||
|  | export function tighten_body(statements, compressor) { | ||
|  |     const nearest_scope = compressor.find_scope(); | ||
|  |     const defun_scope = nearest_scope.get_defun_scope(); | ||
|  |     const { in_loop, in_try } = find_loop_scope_try(); | ||
|  | 
 | ||
|  |     var CHANGED, max_iter = 10; | ||
|  |     do { | ||
|  |         CHANGED = false; | ||
|  |         eliminate_spurious_blocks(statements); | ||
|  |         if (compressor.option("dead_code")) { | ||
|  |             eliminate_dead_code(statements, compressor); | ||
|  |         } | ||
|  |         if (compressor.option("if_return")) { | ||
|  |             handle_if_return(statements, compressor); | ||
|  |         } | ||
|  |         if (compressor.sequences_limit > 0) { | ||
|  |             sequencesize(statements, compressor); | ||
|  |             sequencesize_2(statements, compressor); | ||
|  |         } | ||
|  |         if (compressor.option("join_vars")) { | ||
|  |             join_consecutive_vars(statements); | ||
|  |         } | ||
|  |         if (compressor.option("collapse_vars")) { | ||
|  |             collapse(statements, compressor); | ||
|  |         } | ||
|  |     } while (CHANGED && max_iter-- > 0); | ||
|  | 
 | ||
|  |     function find_loop_scope_try() { | ||
|  |         var node = compressor.self(), level = 0, in_loop = false, in_try = false; | ||
|  |         do { | ||
|  |             if (node instanceof AST_Catch || node instanceof AST_Finally) { | ||
|  |                 level++; | ||
|  |             } else if (node instanceof AST_IterationStatement) { | ||
|  |                 in_loop = true; | ||
|  |             } else if (node instanceof AST_Scope) { | ||
|  |                 break; | ||
|  |             } else if (node instanceof AST_Try) { | ||
|  |                 in_try = true; | ||
|  |             } | ||
|  |         } while (node = compressor.parent(level++)); | ||
|  | 
 | ||
|  |         return { in_loop, in_try }; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Search from right to left for assignment-like expressions:
 | ||
|  |     // - `var a = x;`
 | ||
|  |     // - `a = x;`
 | ||
|  |     // - `++a`
 | ||
|  |     // For each candidate, scan from left to right for first usage, then try
 | ||
|  |     // to fold assignment into the site for compression.
 | ||
|  |     // Will not attempt to collapse assignments into or past code blocks
 | ||
|  |     // which are not sequentially executed, e.g. loops and conditionals.
 | ||
|  |     function collapse(statements, compressor) { | ||
|  |         if (nearest_scope.pinned() || defun_scope.pinned()) | ||
|  |             return statements; | ||
|  |         var args; | ||
|  |         var candidates = []; | ||
|  |         var stat_index = statements.length; | ||
|  |         var scanner = new TreeTransformer(function (node) { | ||
|  |             if (abort) | ||
|  |                 return node; | ||
|  |             // Skip nodes before `candidate` as quickly as possible
 | ||
|  |             if (!hit) { | ||
|  |                 if (node !== hit_stack[hit_index]) | ||
|  |                     return node; | ||
|  |                 hit_index++; | ||
|  |                 if (hit_index < hit_stack.length) | ||
|  |                     return handle_custom_scan_order(node); | ||
|  |                 hit = true; | ||
|  |                 stop_after = find_stop(node, 0); | ||
|  |                 if (stop_after === node) | ||
|  |                     abort = true; | ||
|  |                 return node; | ||
|  |             } | ||
|  |             // Stop immediately if these node types are encountered
 | ||
|  |             var parent = scanner.parent(); | ||
|  |             if (node instanceof AST_Assign | ||
|  |                     && (node.logical || node.operator != "=" && lhs.equivalent_to(node.left)) | ||
|  |                 || node instanceof AST_Await | ||
|  |                 || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression) | ||
|  |                 || node instanceof AST_Debugger | ||
|  |                 || node instanceof AST_Destructuring | ||
|  |                 || node instanceof AST_Expansion | ||
|  |                     && node.expression instanceof AST_Symbol | ||
|  |                     && ( | ||
|  |                         node.expression instanceof AST_This | ||
|  |                         || node.expression.definition().references.length > 1 | ||
|  |                     ) | ||
|  |                 || node instanceof AST_IterationStatement && !(node instanceof AST_For) | ||
|  |                 || node instanceof AST_LoopControl | ||
|  |                 || node instanceof AST_Try | ||
|  |                 || node instanceof AST_With | ||
|  |                 || node instanceof AST_Yield | ||
|  |                 || node instanceof AST_Export | ||
|  |                 || node instanceof AST_Class | ||
|  |                 || parent instanceof AST_For && node !== parent.init | ||
|  |                 || !replace_all | ||
|  |                     && ( | ||
|  |                         node instanceof AST_SymbolRef | ||
|  |                         && !node.is_declared(compressor) | ||
|  |                         && !pure_prop_access_globals.has(node) | ||
|  |                     ) | ||
|  |                 || node instanceof AST_SymbolRef | ||
|  |                     && parent instanceof AST_Call | ||
|  |                     && has_annotation(parent, _NOINLINE) | ||
|  |             ) { | ||
|  |                 abort = true; | ||
|  |                 return node; | ||
|  |             } | ||
|  |             // Stop only if candidate is found within conditional branches
 | ||
|  |             if (!stop_if_hit && (!lhs_local || !replace_all) | ||
|  |                 && (parent instanceof AST_Binary && lazy_op.has(parent.operator) && parent.left !== node | ||
|  |                     || parent instanceof AST_Conditional && parent.condition !== node | ||
|  |                     || parent instanceof AST_If && parent.condition !== node)) { | ||
|  |                 stop_if_hit = parent; | ||
|  |             } | ||
|  |             // Replace variable with assignment when found
 | ||
|  |             if ( | ||
|  |                 can_replace | ||
|  |                 && !(node instanceof AST_SymbolDeclaration) | ||
|  |                 && lhs.equivalent_to(node) | ||
|  |                 && !shadows(scanner.find_scope() || nearest_scope, lvalues) | ||
|  |             ) { | ||
|  |                 if (stop_if_hit) { | ||
|  |                     abort = true; | ||
|  |                     return node; | ||
|  |                 } | ||
|  |                 if (is_lhs(node, parent)) { | ||
|  |                     if (value_def) | ||
|  |                         replaced++; | ||
|  |                     return node; | ||
|  |                 } else { | ||
|  |                     replaced++; | ||
|  |                     if (value_def && candidate instanceof AST_VarDef) | ||
|  |                         return node; | ||
|  |                 } | ||
|  |                 CHANGED = abort = true; | ||
|  |                 if (candidate instanceof AST_UnaryPostfix) { | ||
|  |                     return make_node(AST_UnaryPrefix, candidate, candidate); | ||
|  |                 } | ||
|  |                 if (candidate instanceof AST_VarDef) { | ||
|  |                     var def = candidate.name.definition(); | ||
|  |                     var value = candidate.value; | ||
|  |                     if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) { | ||
|  |                         def.replaced++; | ||
|  |                         if (funarg && is_identifier_atom(value)) { | ||
|  |                             return value.transform(compressor); | ||
|  |                         } else { | ||
|  |                             return maintain_this_binding(parent, node, value); | ||
|  |                         } | ||
|  |                     } | ||
|  |                     return make_node(AST_Assign, candidate, { | ||
|  |                         operator: "=", | ||
|  |                         logical: false, | ||
|  |                         left: make_node(AST_SymbolRef, candidate.name, candidate.name), | ||
|  |                         right: value | ||
|  |                     }); | ||
|  |                 } | ||
|  |                 clear_flag(candidate, WRITE_ONLY); | ||
|  |                 return candidate; | ||
|  |             } | ||
|  |             // These node types have child nodes that execute sequentially,
 | ||
|  |             // but are otherwise not safe to scan into or beyond them.
 | ||
|  |             var sym; | ||
|  |             if (node instanceof AST_Call | ||
|  |                 || node instanceof AST_Exit | ||
|  |                 && (side_effects || lhs instanceof AST_PropAccess || may_modify(lhs)) | ||
|  |                 || node instanceof AST_PropAccess | ||
|  |                 && (side_effects || node.expression.may_throw_on_access(compressor)) | ||
|  |                 || node instanceof AST_SymbolRef | ||
|  |                 && ((lvalues.has(node.name) && lvalues.get(node.name).modified) || side_effects && may_modify(node)) | ||
|  |                 || node instanceof AST_VarDef && node.value | ||
|  |                 && (lvalues.has(node.name.name) || side_effects && may_modify(node.name)) | ||
|  |                 || (sym = is_lhs(node.left, node)) | ||
|  |                 && (sym instanceof AST_PropAccess || lvalues.has(sym.name)) | ||
|  |                 || may_throw | ||
|  |                 && (in_try ? node.has_side_effects(compressor) : side_effects_external(node))) { | ||
|  |                 stop_after = node; | ||
|  |                 if (node instanceof AST_Scope) | ||
|  |                     abort = true; | ||
|  |             } | ||
|  |             return handle_custom_scan_order(node); | ||
|  |         }, function (node) { | ||
|  |             if (abort) | ||
|  |                 return; | ||
|  |             if (stop_after === node) | ||
|  |                 abort = true; | ||
|  |             if (stop_if_hit === node) | ||
|  |                 stop_if_hit = null; | ||
|  |         }); | ||
|  | 
 | ||
|  |         var multi_replacer = new TreeTransformer(function (node) { | ||
|  |             if (abort) | ||
|  |                 return node; | ||
|  |             // Skip nodes before `candidate` as quickly as possible
 | ||
|  |             if (!hit) { | ||
|  |                 if (node !== hit_stack[hit_index]) | ||
|  |                     return node; | ||
|  |                 hit_index++; | ||
|  |                 if (hit_index < hit_stack.length) | ||
|  |                     return; | ||
|  |                 hit = true; | ||
|  |                 return node; | ||
|  |             } | ||
|  |             // Replace variable when found
 | ||
|  |             if (node instanceof AST_SymbolRef | ||
|  |                 && node.name == def.name) { | ||
|  |                 if (!--replaced) | ||
|  |                     abort = true; | ||
|  |                 if (is_lhs(node, multi_replacer.parent())) | ||
|  |                     return node; | ||
|  |                 def.replaced++; | ||
|  |                 value_def.replaced--; | ||
|  |                 return candidate.value; | ||
|  |             } | ||
|  |             // Skip (non-executed) functions and (leading) default case in switch statements
 | ||
|  |             if (node instanceof AST_Default || node instanceof AST_Scope) | ||
|  |                 return node; | ||
|  |         }); | ||
|  | 
 | ||
|  |         while (--stat_index >= 0) { | ||
|  |             // Treat parameters as collapsible in IIFE, i.e.
 | ||
|  |             //   function(a, b){ ... }(x());
 | ||
|  |             // would be translated into equivalent assignments:
 | ||
|  |             //   var a = x(), b = undefined;
 | ||
|  |             if (stat_index == 0 && compressor.option("unused")) | ||
|  |                 extract_args(); | ||
|  |             // Find collapsible assignments
 | ||
|  |             var hit_stack = []; | ||
|  |             extract_candidates(statements[stat_index]); | ||
|  |             while (candidates.length > 0) { | ||
|  |                 hit_stack = candidates.pop(); | ||
|  |                 var hit_index = 0; | ||
|  |                 var candidate = hit_stack[hit_stack.length - 1]; | ||
|  |                 var value_def = null; | ||
|  |                 var stop_after = null; | ||
|  |                 var stop_if_hit = null; | ||
|  |                 var lhs = get_lhs(candidate); | ||
|  |                 if (!lhs || is_lhs_read_only(lhs) || lhs.has_side_effects(compressor)) | ||
|  |                     continue; | ||
|  |                 // Locate symbols which may execute code outside of scanning range
 | ||
|  |                 var lvalues = get_lvalues(candidate); | ||
|  |                 var lhs_local = is_lhs_local(lhs); | ||
|  |                 if (lhs instanceof AST_SymbolRef) { | ||
|  |                     lvalues.set(lhs.name, { def: lhs.definition(), modified: false }); | ||
|  |                 } | ||
|  |                 var side_effects = value_has_side_effects(candidate); | ||
|  |                 var replace_all = replace_all_symbols(); | ||
|  |                 var may_throw = candidate.may_throw(compressor); | ||
|  |                 var funarg = candidate.name instanceof AST_SymbolFunarg; | ||
|  |                 var hit = funarg; | ||
|  |                 var abort = false, replaced = 0, can_replace = !args || !hit; | ||
|  |                 if (!can_replace) { | ||
|  |                     for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) { | ||
|  |                         args[j].transform(scanner); | ||
|  |                     } | ||
|  |                     can_replace = true; | ||
|  |                 } | ||
|  |                 for (var i = stat_index; !abort && i < statements.length; i++) { | ||
|  |                     statements[i].transform(scanner); | ||
|  |                 } | ||
|  |                 if (value_def) { | ||
|  |                     var def = candidate.name.definition(); | ||
|  |                     if (abort && def.references.length - def.replaced > replaced) | ||
|  |                         replaced = false; | ||
|  |                     else { | ||
|  |                         abort = false; | ||
|  |                         hit_index = 0; | ||
|  |                         hit = funarg; | ||
|  |                         for (var i = stat_index; !abort && i < statements.length; i++) { | ||
|  |                             statements[i].transform(multi_replacer); | ||
|  |                         } | ||
|  |                         value_def.single_use = false; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 if (replaced && !remove_candidate(candidate)) | ||
|  |                     statements.splice(stat_index, 1); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         function handle_custom_scan_order(node) { | ||
|  |             // Skip (non-executed) functions
 | ||
|  |             if (node instanceof AST_Scope) | ||
|  |                 return node; | ||
|  | 
 | ||
|  |             // Scan case expressions first in a switch statement
 | ||
|  |             if (node instanceof AST_Switch) { | ||
|  |                 node.expression = node.expression.transform(scanner); | ||
|  |                 for (var i = 0, len = node.body.length; !abort && i < len; i++) { | ||
|  |                     var branch = node.body[i]; | ||
|  |                     if (branch instanceof AST_Case) { | ||
|  |                         if (!hit) { | ||
|  |                             if (branch !== hit_stack[hit_index]) | ||
|  |                                 continue; | ||
|  |                             hit_index++; | ||
|  |                         } | ||
|  |                         branch.expression = branch.expression.transform(scanner); | ||
|  |                         if (!replace_all) | ||
|  |                             break; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 abort = true; | ||
|  |                 return node; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         function redefined_within_scope(def, scope) { | ||
|  |             if (def.global) | ||
|  |                 return false; | ||
|  |             let cur_scope = def.scope; | ||
|  |             while (cur_scope && cur_scope !== scope) { | ||
|  |                 if (cur_scope.variables.has(def.name)) { | ||
|  |                     return true; | ||
|  |                 } | ||
|  |                 cur_scope = cur_scope.parent_scope; | ||
|  |             } | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         function has_overlapping_symbol(fn, arg, fn_strict) { | ||
|  |             var found = false, scan_this = !(fn instanceof AST_Arrow); | ||
|  |             arg.walk(new TreeWalker(function (node, descend) { | ||
|  |                 if (found) | ||
|  |                     return true; | ||
|  |                 if (node instanceof AST_SymbolRef && (fn.variables.has(node.name) || redefined_within_scope(node.definition(), fn))) { | ||
|  |                     var s = node.definition().scope; | ||
|  |                     if (s !== defun_scope) | ||
|  |                         while (s = s.parent_scope) { | ||
|  |                             if (s === defun_scope) | ||
|  |                                 return true; | ||
|  |                         } | ||
|  |                     return found = true; | ||
|  |                 } | ||
|  |                 if ((fn_strict || scan_this) && node instanceof AST_This) { | ||
|  |                     return found = true; | ||
|  |                 } | ||
|  |                 if (node instanceof AST_Scope && !(node instanceof AST_Arrow)) { | ||
|  |                     var prev = scan_this; | ||
|  |                     scan_this = false; | ||
|  |                     descend(); | ||
|  |                     scan_this = prev; | ||
|  |                     return true; | ||
|  |                 } | ||
|  |             })); | ||
|  |             return found; | ||
|  |         } | ||
|  | 
 | ||
|  |         function extract_args() { | ||
|  |             var iife, fn = compressor.self(); | ||
|  |             if (is_func_expr(fn) | ||
|  |                 && !fn.name | ||
|  |                 && !fn.uses_arguments | ||
|  |                 && !fn.pinned() | ||
|  |                 && (iife = compressor.parent()) instanceof AST_Call | ||
|  |                 && iife.expression === fn | ||
|  |                 && iife.args.every((arg) => !(arg instanceof AST_Expansion))) { | ||
|  |                 var fn_strict = compressor.has_directive("use strict"); | ||
|  |                 if (fn_strict && !member(fn_strict, fn.body)) | ||
|  |                     fn_strict = false; | ||
|  |                 var len = fn.argnames.length; | ||
|  |                 args = iife.args.slice(len); | ||
|  |                 var names = new Set(); | ||
|  |                 for (var i = len; --i >= 0;) { | ||
|  |                     var sym = fn.argnames[i]; | ||
|  |                     var arg = iife.args[i]; | ||
|  |                     // The following two line fix is a duplicate of the fix at
 | ||
|  |                     // https://github.com/terser/terser/commit/011d3eb08cefe6922c7d1bdfa113fc4aeaca1b75
 | ||
|  |                     // This might mean that these two pieces of code (one here in collapse_vars and another in reduce_vars
 | ||
|  |                     // Might be doing the exact same thing.
 | ||
|  |                     const def = sym.definition && sym.definition(); | ||
|  |                     const is_reassigned = def && def.orig.length > 1; | ||
|  |                     if (is_reassigned) | ||
|  |                         continue; | ||
|  |                     args.unshift(make_node(AST_VarDef, sym, { | ||
|  |                         name: sym, | ||
|  |                         value: arg | ||
|  |                     })); | ||
|  |                     if (names.has(sym.name)) | ||
|  |                         continue; | ||
|  |                     names.add(sym.name); | ||
|  |                     if (sym instanceof AST_Expansion) { | ||
|  |                         var elements = iife.args.slice(i); | ||
|  |                         if (elements.every((arg) => !has_overlapping_symbol(fn, arg, fn_strict) | ||
|  |                         )) { | ||
|  |                             candidates.unshift([make_node(AST_VarDef, sym, { | ||
|  |                                 name: sym.expression, | ||
|  |                                 value: make_node(AST_Array, iife, { | ||
|  |                                     elements: elements | ||
|  |                                 }) | ||
|  |                             })]); | ||
|  |                         } | ||
|  |                     } else { | ||
|  |                         if (!arg) { | ||
|  |                             arg = make_node(AST_Undefined, sym).transform(compressor); | ||
|  |                         } else if (arg instanceof AST_Lambda && arg.pinned() | ||
|  |                             || has_overlapping_symbol(fn, arg, fn_strict)) { | ||
|  |                             arg = null; | ||
|  |                         } | ||
|  |                         if (arg) | ||
|  |                             candidates.unshift([make_node(AST_VarDef, sym, { | ||
|  |                                 name: sym, | ||
|  |                                 value: arg | ||
|  |                             })]); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         function extract_candidates(expr) { | ||
|  |             hit_stack.push(expr); | ||
|  |             if (expr instanceof AST_Assign) { | ||
|  |                 if (!expr.left.has_side_effects(compressor) | ||
|  |                     && !(expr.right instanceof AST_Chain)) { | ||
|  |                     candidates.push(hit_stack.slice()); | ||
|  |                 } | ||
|  |                 extract_candidates(expr.right); | ||
|  |             } else if (expr instanceof AST_Binary) { | ||
|  |                 extract_candidates(expr.left); | ||
|  |                 extract_candidates(expr.right); | ||
|  |             } else if (expr instanceof AST_Call && !has_annotation(expr, _NOINLINE)) { | ||
|  |                 extract_candidates(expr.expression); | ||
|  |                 expr.args.forEach(extract_candidates); | ||
|  |             } else if (expr instanceof AST_Case) { | ||
|  |                 extract_candidates(expr.expression); | ||
|  |             } else if (expr instanceof AST_Conditional) { | ||
|  |                 extract_candidates(expr.condition); | ||
|  |                 extract_candidates(expr.consequent); | ||
|  |                 extract_candidates(expr.alternative); | ||
|  |             } else if (expr instanceof AST_Definitions) { | ||
|  |                 var len = expr.definitions.length; | ||
|  |                 // limit number of trailing variable definitions for consideration
 | ||
|  |                 var i = len - 200; | ||
|  |                 if (i < 0) | ||
|  |                     i = 0; | ||
|  |                 for (; i < len; i++) { | ||
|  |                     extract_candidates(expr.definitions[i]); | ||
|  |                 } | ||
|  |             } else if (expr instanceof AST_DWLoop) { | ||
|  |                 extract_candidates(expr.condition); | ||
|  |                 if (!(expr.body instanceof AST_Block)) { | ||
|  |                     extract_candidates(expr.body); | ||
|  |                 } | ||
|  |             } else if (expr instanceof AST_Exit) { | ||
|  |                 if (expr.value) | ||
|  |                     extract_candidates(expr.value); | ||
|  |             } else if (expr instanceof AST_For) { | ||
|  |                 if (expr.init) | ||
|  |                     extract_candidates(expr.init); | ||
|  |                 if (expr.condition) | ||
|  |                     extract_candidates(expr.condition); | ||
|  |                 if (expr.step) | ||
|  |                     extract_candidates(expr.step); | ||
|  |                 if (!(expr.body instanceof AST_Block)) { | ||
|  |                     extract_candidates(expr.body); | ||
|  |                 } | ||
|  |             } else if (expr instanceof AST_ForIn) { | ||
|  |                 extract_candidates(expr.object); | ||
|  |                 if (!(expr.body instanceof AST_Block)) { | ||
|  |                     extract_candidates(expr.body); | ||
|  |                 } | ||
|  |             } else if (expr instanceof AST_If) { | ||
|  |                 extract_candidates(expr.condition); | ||
|  |                 if (!(expr.body instanceof AST_Block)) { | ||
|  |                     extract_candidates(expr.body); | ||
|  |                 } | ||
|  |                 if (expr.alternative && !(expr.alternative instanceof AST_Block)) { | ||
|  |                     extract_candidates(expr.alternative); | ||
|  |                 } | ||
|  |             } else if (expr instanceof AST_Sequence) { | ||
|  |                 expr.expressions.forEach(extract_candidates); | ||
|  |             } else if (expr instanceof AST_SimpleStatement) { | ||
|  |                 extract_candidates(expr.body); | ||
|  |             } else if (expr instanceof AST_Switch) { | ||
|  |                 extract_candidates(expr.expression); | ||
|  |                 expr.body.forEach(extract_candidates); | ||
|  |             } else if (expr instanceof AST_Unary) { | ||
|  |                 if (expr.operator == "++" || expr.operator == "--") { | ||
|  |                     candidates.push(hit_stack.slice()); | ||
|  |                 } | ||
|  |             } else if (expr instanceof AST_VarDef) { | ||
|  |                 if (expr.value && !(expr.value instanceof AST_Chain)) { | ||
|  |                     candidates.push(hit_stack.slice()); | ||
|  |                     extract_candidates(expr.value); | ||
|  |                 } | ||
|  |             } | ||
|  |             hit_stack.pop(); | ||
|  |         } | ||
|  | 
 | ||
|  |         function find_stop(node, level, write_only) { | ||
|  |             var parent = scanner.parent(level); | ||
|  |             if (parent instanceof AST_Assign) { | ||
|  |                 if (write_only | ||
|  |                     && !parent.logical | ||
|  |                     && !(parent.left instanceof AST_PropAccess | ||
|  |                         || lvalues.has(parent.left.name))) { | ||
|  |                     return find_stop(parent, level + 1, write_only); | ||
|  |                 } | ||
|  |                 return node; | ||
|  |             } | ||
|  |             if (parent instanceof AST_Binary) { | ||
|  |                 if (write_only && (!lazy_op.has(parent.operator) || parent.left === node)) { | ||
|  |                     return find_stop(parent, level + 1, write_only); | ||
|  |                 } | ||
|  |                 return node; | ||
|  |             } | ||
|  |             if (parent instanceof AST_Call) | ||
|  |                 return node; | ||
|  |             if (parent instanceof AST_Case) | ||
|  |                 return node; | ||
|  |             if (parent instanceof AST_Conditional) { | ||
|  |                 if (write_only && parent.condition === node) { | ||
|  |                     return find_stop(parent, level + 1, write_only); | ||
|  |                 } | ||
|  |                 return node; | ||
|  |             } | ||
|  |             if (parent instanceof AST_Definitions) { | ||
|  |                 return find_stop(parent, level + 1, true); | ||
|  |             } | ||
|  |             if (parent instanceof AST_Exit) { | ||
|  |                 return write_only ? find_stop(parent, level + 1, write_only) : node; | ||
|  |             } | ||
|  |             if (parent instanceof AST_If) { | ||
|  |                 if (write_only && parent.condition === node) { | ||
|  |                     return find_stop(parent, level + 1, write_only); | ||
|  |                 } | ||
|  |                 return node; | ||
|  |             } | ||
|  |             if (parent instanceof AST_IterationStatement) | ||
|  |                 return node; | ||
|  |             if (parent instanceof AST_Sequence) { | ||
|  |                 return find_stop(parent, level + 1, parent.tail_node() !== node); | ||
|  |             } | ||
|  |             if (parent instanceof AST_SimpleStatement) { | ||
|  |                 return find_stop(parent, level + 1, true); | ||
|  |             } | ||
|  |             if (parent instanceof AST_Switch) | ||
|  |                 return node; | ||
|  |             if (parent instanceof AST_VarDef) | ||
|  |                 return node; | ||
|  |             return null; | ||
|  |         } | ||
|  | 
 | ||
|  |         function mangleable_var(var_def) { | ||
|  |             var value = var_def.value; | ||
|  |             if (!(value instanceof AST_SymbolRef)) | ||
|  |                 return; | ||
|  |             if (value.name == "arguments") | ||
|  |                 return; | ||
|  |             var def = value.definition(); | ||
|  |             if (def.undeclared) | ||
|  |                 return; | ||
|  |             return value_def = def; | ||
|  |         } | ||
|  | 
 | ||
|  |         function get_lhs(expr) { | ||
|  |             if (expr instanceof AST_Assign && expr.logical) { | ||
|  |                 return false; | ||
|  |             } else if (expr instanceof AST_VarDef && expr.name instanceof AST_SymbolDeclaration) { | ||
|  |                 var def = expr.name.definition(); | ||
|  |                 if (!member(expr.name, def.orig)) | ||
|  |                     return; | ||
|  |                 var referenced = def.references.length - def.replaced; | ||
|  |                 if (!referenced) | ||
|  |                     return; | ||
|  |                 var declared = def.orig.length - def.eliminated; | ||
|  |                 if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg) | ||
|  |                     || (referenced > 1 ? mangleable_var(expr) : !compressor.exposed(def))) { | ||
|  |                     return make_node(AST_SymbolRef, expr.name, expr.name); | ||
|  |                 } | ||
|  |             } else { | ||
|  |                 const lhs = expr instanceof AST_Assign | ||
|  |                     ? expr.left | ||
|  |                     : expr.expression; | ||
|  |                 return !is_ref_of(lhs, AST_SymbolConst) | ||
|  |                     && !is_ref_of(lhs, AST_SymbolLet) && lhs; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         function get_rvalue(expr) { | ||
|  |             if (expr instanceof AST_Assign) { | ||
|  |                 return expr.right; | ||
|  |             } else { | ||
|  |                 return expr.value; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         function get_lvalues(expr) { | ||
|  |             var lvalues = new Map(); | ||
|  |             if (expr instanceof AST_Unary) | ||
|  |                 return lvalues; | ||
|  |             var tw = new TreeWalker(function (node) { | ||
|  |                 var sym = node; | ||
|  |                 while (sym instanceof AST_PropAccess) | ||
|  |                     sym = sym.expression; | ||
|  |                 if (sym instanceof AST_SymbolRef) { | ||
|  |                     const prev = lvalues.get(sym.name); | ||
|  |                     if (!prev || !prev.modified) { | ||
|  |                         lvalues.set(sym.name, { | ||
|  |                             def: sym.definition(), | ||
|  |                             modified: is_modified(compressor, tw, node, node, 0) | ||
|  |                         }); | ||
|  |                     } | ||
|  |                 } | ||
|  |             }); | ||
|  |             get_rvalue(expr).walk(tw); | ||
|  |             return lvalues; | ||
|  |         } | ||
|  | 
 | ||
|  |         function remove_candidate(expr) { | ||
|  |             if (expr.name instanceof AST_SymbolFunarg) { | ||
|  |                 var iife = compressor.parent(), argnames = compressor.self().argnames; | ||
|  |                 var index = argnames.indexOf(expr.name); | ||
|  |                 if (index < 0) { | ||
|  |                     iife.args.length = Math.min(iife.args.length, argnames.length - 1); | ||
|  |                 } else { | ||
|  |                     var args = iife.args; | ||
|  |                     if (args[index]) | ||
|  |                         args[index] = make_node(AST_Number, args[index], { | ||
|  |                             value: 0 | ||
|  |                         }); | ||
|  |                 } | ||
|  |                 return true; | ||
|  |             } | ||
|  |             var found = false; | ||
|  |             return statements[stat_index].transform(new TreeTransformer(function (node, descend, in_list) { | ||
|  |                 if (found) | ||
|  |                     return node; | ||
|  |                 if (node === expr || node.body === expr) { | ||
|  |                     found = true; | ||
|  |                     if (node instanceof AST_VarDef) { | ||
|  |                         node.value = node.name instanceof AST_SymbolConst | ||
|  |                             ? make_node(AST_Undefined, node.value) // `const` always needs value.
 | ||
|  |                             : null; | ||
|  |                         return node; | ||
|  |                     } | ||
|  |                     return in_list ? MAP.skip : null; | ||
|  |                 } | ||
|  |             }, function (node) { | ||
|  |                 if (node instanceof AST_Sequence) | ||
|  |                     switch (node.expressions.length) { | ||
|  |                         case 0: return null; | ||
|  |                         case 1: return node.expressions[0]; | ||
|  |                     } | ||
|  |             })); | ||
|  |         } | ||
|  | 
 | ||
|  |         function is_lhs_local(lhs) { | ||
|  |             while (lhs instanceof AST_PropAccess) | ||
|  |                 lhs = lhs.expression; | ||
|  |             return lhs instanceof AST_SymbolRef | ||
|  |                 && lhs.definition().scope.get_defun_scope() === defun_scope | ||
|  |                 && !(in_loop | ||
|  |                     && (lvalues.has(lhs.name) | ||
|  |                         || candidate instanceof AST_Unary | ||
|  |                         || (candidate instanceof AST_Assign | ||
|  |                             && !candidate.logical | ||
|  |                             && candidate.operator != "="))); | ||
|  |         } | ||
|  | 
 | ||
|  |         function value_has_side_effects(expr) { | ||
|  |             if (expr instanceof AST_Unary) | ||
|  |                 return unary_side_effects.has(expr.operator); | ||
|  |             return get_rvalue(expr).has_side_effects(compressor); | ||
|  |         } | ||
|  | 
 | ||
|  |         function replace_all_symbols() { | ||
|  |             if (side_effects) | ||
|  |                 return false; | ||
|  |             if (value_def) | ||
|  |                 return true; | ||
|  |             if (lhs instanceof AST_SymbolRef) { | ||
|  |                 var def = lhs.definition(); | ||
|  |                 if (def.references.length - def.replaced == (candidate instanceof AST_VarDef ? 1 : 2)) { | ||
|  |                     return true; | ||
|  |                 } | ||
|  |             } | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         function may_modify(sym) { | ||
|  |             if (!sym.definition) | ||
|  |                 return true; // AST_Destructuring
 | ||
|  |             var def = sym.definition(); | ||
|  |             if (def.orig.length == 1 && def.orig[0] instanceof AST_SymbolDefun) | ||
|  |                 return false; | ||
|  |             if (def.scope.get_defun_scope() !== defun_scope) | ||
|  |                 return true; | ||
|  |             return def.references.some((ref) => | ||
|  |                 ref.scope.get_defun_scope() !== defun_scope | ||
|  |             ); | ||
|  |         } | ||
|  | 
 | ||
|  |         function side_effects_external(node, lhs) { | ||
|  |             if (node instanceof AST_Assign) | ||
|  |                 return side_effects_external(node.left, true); | ||
|  |             if (node instanceof AST_Unary) | ||
|  |                 return side_effects_external(node.expression, true); | ||
|  |             if (node instanceof AST_VarDef) | ||
|  |                 return node.value && side_effects_external(node.value); | ||
|  |             if (lhs) { | ||
|  |                 if (node instanceof AST_Dot) | ||
|  |                     return side_effects_external(node.expression, true); | ||
|  |                 if (node instanceof AST_Sub) | ||
|  |                     return side_effects_external(node.expression, true); | ||
|  |                 if (node instanceof AST_SymbolRef) | ||
|  |                     return node.definition().scope.get_defun_scope() !== defun_scope; | ||
|  |             } | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         /** | ||
|  |          * Will any of the pulled-in lvalues shadow a variable in newScope or parents? | ||
|  |          * similar to scope_encloses_variables_in_this_scope */ | ||
|  |         function shadows(my_scope, lvalues) { | ||
|  |             for (const { def } of lvalues.values()) { | ||
|  |                 const looked_up = my_scope.find_variable(def.name); | ||
|  |                 if (looked_up) { | ||
|  |                     if (looked_up === def) continue; | ||
|  |                     return true; | ||
|  |                 } | ||
|  |             } | ||
|  |             return false; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     function eliminate_spurious_blocks(statements) { | ||
|  |         var seen_dirs = []; | ||
|  |         for (var i = 0; i < statements.length;) { | ||
|  |             var stat = statements[i]; | ||
|  |             if (stat instanceof AST_BlockStatement && stat.body.every(can_be_evicted_from_block)) { | ||
|  |                 CHANGED = true; | ||
|  |                 eliminate_spurious_blocks(stat.body); | ||
|  |                 statements.splice(i, 1, ...stat.body); | ||
|  |                 i += stat.body.length; | ||
|  |             } else if (stat instanceof AST_EmptyStatement) { | ||
|  |                 CHANGED = true; | ||
|  |                 statements.splice(i, 1); | ||
|  |             } else if (stat instanceof AST_Directive) { | ||
|  |                 if (seen_dirs.indexOf(stat.value) < 0) { | ||
|  |                     i++; | ||
|  |                     seen_dirs.push(stat.value); | ||
|  |                 } else { | ||
|  |                     CHANGED = true; | ||
|  |                     statements.splice(i, 1); | ||
|  |                 } | ||
|  |             } else | ||
|  |                 i++; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     function handle_if_return(statements, compressor) { | ||
|  |         var self = compressor.self(); | ||
|  |         var multiple_if_returns = has_multiple_if_returns(statements); | ||
|  |         var in_lambda = self instanceof AST_Lambda; | ||
|  |         for (var i = statements.length; --i >= 0;) { | ||
|  |             var stat = statements[i]; | ||
|  |             var j = next_index(i); | ||
|  |             var next = statements[j]; | ||
|  | 
 | ||
|  |             if (in_lambda && !next && stat instanceof AST_Return) { | ||
|  |                 if (!stat.value) { | ||
|  |                     CHANGED = true; | ||
|  |                     statements.splice(i, 1); | ||
|  |                     continue; | ||
|  |                 } | ||
|  |                 if (stat.value instanceof AST_UnaryPrefix && stat.value.operator == "void") { | ||
|  |                     CHANGED = true; | ||
|  |                     statements[i] = make_node(AST_SimpleStatement, stat, { | ||
|  |                         body: stat.value.expression | ||
|  |                     }); | ||
|  |                     continue; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             if (stat instanceof AST_If) { | ||
|  |                 var ab = aborts(stat.body); | ||
|  |                 if (can_merge_flow(ab)) { | ||
|  |                     if (ab.label) { | ||
|  |                         remove(ab.label.thedef.references, ab); | ||
|  |                     } | ||
|  |                     CHANGED = true; | ||
|  |                     stat = stat.clone(); | ||
|  |                     stat.condition = stat.condition.negate(compressor); | ||
|  |                     var body = as_statement_array_with_return(stat.body, ab); | ||
|  |                     stat.body = make_node(AST_BlockStatement, stat, { | ||
|  |                         body: as_statement_array(stat.alternative).concat(extract_functions()) | ||
|  |                     }); | ||
|  |                     stat.alternative = make_node(AST_BlockStatement, stat, { | ||
|  |                         body: body | ||
|  |                     }); | ||
|  |                     statements[i] = stat.transform(compressor); | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 var ab = aborts(stat.alternative); | ||
|  |                 if (can_merge_flow(ab)) { | ||
|  |                     if (ab.label) { | ||
|  |                         remove(ab.label.thedef.references, ab); | ||
|  |                     } | ||
|  |                     CHANGED = true; | ||
|  |                     stat = stat.clone(); | ||
|  |                     stat.body = make_node(AST_BlockStatement, stat.body, { | ||
|  |                         body: as_statement_array(stat.body).concat(extract_functions()) | ||
|  |                     }); | ||
|  |                     var body = as_statement_array_with_return(stat.alternative, ab); | ||
|  |                     stat.alternative = make_node(AST_BlockStatement, stat.alternative, { | ||
|  |                         body: body | ||
|  |                     }); | ||
|  |                     statements[i] = stat.transform(compressor); | ||
|  |                     continue; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             if (stat instanceof AST_If && stat.body instanceof AST_Return) { | ||
|  |                 var value = stat.body.value; | ||
|  |                 //---
 | ||
|  |                 // pretty silly case, but:
 | ||
|  |                 // if (foo()) return; return; ==> foo(); return;
 | ||
|  |                 if (!value && !stat.alternative | ||
|  |                     && (in_lambda && !next || next instanceof AST_Return && !next.value)) { | ||
|  |                     CHANGED = true; | ||
|  |                     statements[i] = make_node(AST_SimpleStatement, stat.condition, { | ||
|  |                         body: stat.condition | ||
|  |                     }); | ||
|  |                     continue; | ||
|  |                 } | ||
|  |                 //---
 | ||
|  |                 // if (foo()) return x; return y; ==> return foo() ? x : y;
 | ||
|  |                 if (value && !stat.alternative && next instanceof AST_Return && next.value) { | ||
|  |                     CHANGED = true; | ||
|  |                     stat = stat.clone(); | ||
|  |                     stat.alternative = next; | ||
|  |                     statements[i] = stat.transform(compressor); | ||
|  |                     statements.splice(j, 1); | ||
|  |                     continue; | ||
|  |                 } | ||
|  |                 //---
 | ||
|  |                 // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined;
 | ||
|  |                 if (value && !stat.alternative | ||
|  |                     && (!next && in_lambda && multiple_if_returns | ||
|  |                         || next instanceof AST_Return)) { | ||
|  |                     CHANGED = true; | ||
|  |                     stat = stat.clone(); | ||
|  |                     stat.alternative = next || make_node(AST_Return, stat, { | ||
|  |                         value: null | ||
|  |                     }); | ||
|  |                     statements[i] = stat.transform(compressor); | ||
|  |                     if (next) | ||
|  |                         statements.splice(j, 1); | ||
|  |                     continue; | ||
|  |                 } | ||
|  |                 //---
 | ||
|  |                 // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e;
 | ||
|  |                 //
 | ||
|  |                 // if sequences is not enabled, this can lead to an endless loop (issue #866).
 | ||
|  |                 // however, with sequences on this helps producing slightly better output for
 | ||
|  |                 // the example code.
 | ||
|  |                 var prev = statements[prev_index(i)]; | ||
|  |                 if (compressor.option("sequences") && in_lambda && !stat.alternative | ||
|  |                     && prev instanceof AST_If && prev.body instanceof AST_Return | ||
|  |                     && next_index(j) == statements.length && next instanceof AST_SimpleStatement) { | ||
|  |                     CHANGED = true; | ||
|  |                     stat = stat.clone(); | ||
|  |                     stat.alternative = make_node(AST_BlockStatement, next, { | ||
|  |                         body: [ | ||
|  |                             next, | ||
|  |                             make_node(AST_Return, next, { | ||
|  |                                 value: null | ||
|  |                             }) | ||
|  |                         ] | ||
|  |                     }); | ||
|  |                     statements[i] = stat.transform(compressor); | ||
|  |                     statements.splice(j, 1); | ||
|  |                     continue; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         function has_multiple_if_returns(statements) { | ||
|  |             var n = 0; | ||
|  |             for (var i = statements.length; --i >= 0;) { | ||
|  |                 var stat = statements[i]; | ||
|  |                 if (stat instanceof AST_If && stat.body instanceof AST_Return) { | ||
|  |                     if (++n > 1) | ||
|  |                         return true; | ||
|  |                 } | ||
|  |             } | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         function is_return_void(value) { | ||
|  |             return !value || value instanceof AST_UnaryPrefix && value.operator == "void"; | ||
|  |         } | ||
|  | 
 | ||
|  |         function can_merge_flow(ab) { | ||
|  |             if (!ab) | ||
|  |                 return false; | ||
|  |             for (var j = i + 1, len = statements.length; j < len; j++) { | ||
|  |                 var stat = statements[j]; | ||
|  |                 if (stat instanceof AST_Const || stat instanceof AST_Let) | ||
|  |                     return false; | ||
|  |             } | ||
|  |             var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null; | ||
|  |             return ab instanceof AST_Return && in_lambda && is_return_void(ab.value) | ||
|  |                 || ab instanceof AST_Continue && self === loop_body(lct) | ||
|  |                 || ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct; | ||
|  |         } | ||
|  | 
 | ||
|  |         function extract_functions() { | ||
|  |             var tail = statements.slice(i + 1); | ||
|  |             statements.length = i + 1; | ||
|  |             return tail.filter(function (stat) { | ||
|  |                 if (stat instanceof AST_Defun) { | ||
|  |                     statements.push(stat); | ||
|  |                     return false; | ||
|  |                 } | ||
|  |                 return true; | ||
|  |             }); | ||
|  |         } | ||
|  | 
 | ||
|  |         function as_statement_array_with_return(node, ab) { | ||
|  |             var body = as_statement_array(node).slice(0, -1); | ||
|  |             if (ab.value) { | ||
|  |                 body.push(make_node(AST_SimpleStatement, ab.value, { | ||
|  |                     body: ab.value.expression | ||
|  |                 })); | ||
|  |             } | ||
|  |             return body; | ||
|  |         } | ||
|  | 
 | ||
|  |         function next_index(i) { | ||
|  |             for (var j = i + 1, len = statements.length; j < len; j++) { | ||
|  |                 var stat = statements[j]; | ||
|  |                 if (!(stat instanceof AST_Var && declarations_only(stat))) { | ||
|  |                     break; | ||
|  |                 } | ||
|  |             } | ||
|  |             return j; | ||
|  |         } | ||
|  | 
 | ||
|  |         function prev_index(i) { | ||
|  |             for (var j = i; --j >= 0;) { | ||
|  |                 var stat = statements[j]; | ||
|  |                 if (!(stat instanceof AST_Var && declarations_only(stat))) { | ||
|  |                     break; | ||
|  |                 } | ||
|  |             } | ||
|  |             return j; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     function eliminate_dead_code(statements, compressor) { | ||
|  |         var has_quit; | ||
|  |         var self = compressor.self(); | ||
|  |         for (var i = 0, n = 0, len = statements.length; i < len; i++) { | ||
|  |             var stat = statements[i]; | ||
|  |             if (stat instanceof AST_LoopControl) { | ||
|  |                 var lct = compressor.loopcontrol_target(stat); | ||
|  |                 if (stat instanceof AST_Break | ||
|  |                     && !(lct instanceof AST_IterationStatement) | ||
|  |                     && loop_body(lct) === self | ||
|  |                     || stat instanceof AST_Continue | ||
|  |                     && loop_body(lct) === self) { | ||
|  |                     if (stat.label) { | ||
|  |                         remove(stat.label.thedef.references, stat); | ||
|  |                     } | ||
|  |                 } else { | ||
|  |                     statements[n++] = stat; | ||
|  |                 } | ||
|  |             } else { | ||
|  |                 statements[n++] = stat; | ||
|  |             } | ||
|  |             if (aborts(stat)) { | ||
|  |                 has_quit = statements.slice(i + 1); | ||
|  |                 break; | ||
|  |             } | ||
|  |         } | ||
|  |         statements.length = n; | ||
|  |         CHANGED = n != len; | ||
|  |         if (has_quit) | ||
|  |             has_quit.forEach(function (stat) { | ||
|  |                 trim_unreachable_code(compressor, stat, statements); | ||
|  |             }); | ||
|  |     } | ||
|  | 
 | ||
|  |     function declarations_only(node) { | ||
|  |         return node.definitions.every((var_def) => !var_def.value); | ||
|  |     } | ||
|  | 
 | ||
|  |     function sequencesize(statements, compressor) { | ||
|  |         if (statements.length < 2) | ||
|  |             return; | ||
|  |         var seq = [], n = 0; | ||
|  |         function push_seq() { | ||
|  |             if (!seq.length) | ||
|  |                 return; | ||
|  |             var body = make_sequence(seq[0], seq); | ||
|  |             statements[n++] = make_node(AST_SimpleStatement, body, { body: body }); | ||
|  |             seq = []; | ||
|  |         } | ||
|  |         for (var i = 0, len = statements.length; i < len; i++) { | ||
|  |             var stat = statements[i]; | ||
|  |             if (stat instanceof AST_SimpleStatement) { | ||
|  |                 if (seq.length >= compressor.sequences_limit) | ||
|  |                     push_seq(); | ||
|  |                 var body = stat.body; | ||
|  |                 if (seq.length > 0) | ||
|  |                     body = body.drop_side_effect_free(compressor); | ||
|  |                 if (body) | ||
|  |                     merge_sequence(seq, body); | ||
|  |             } else if (stat instanceof AST_Definitions && declarations_only(stat) | ||
|  |                 || stat instanceof AST_Defun) { | ||
|  |                 statements[n++] = stat; | ||
|  |             } else { | ||
|  |                 push_seq(); | ||
|  |                 statements[n++] = stat; | ||
|  |             } | ||
|  |         } | ||
|  |         push_seq(); | ||
|  |         statements.length = n; | ||
|  |         if (n != len) | ||
|  |             CHANGED = true; | ||
|  |     } | ||
|  | 
 | ||
|  |     function to_simple_statement(block, decls) { | ||
|  |         if (!(block instanceof AST_BlockStatement)) | ||
|  |             return block; | ||
|  |         var stat = null; | ||
|  |         for (var i = 0, len = block.body.length; i < len; i++) { | ||
|  |             var line = block.body[i]; | ||
|  |             if (line instanceof AST_Var && declarations_only(line)) { | ||
|  |                 decls.push(line); | ||
|  |             } else if (stat || line instanceof AST_Const || line instanceof AST_Let) { | ||
|  |                 return false; | ||
|  |             } else { | ||
|  |                 stat = line; | ||
|  |             } | ||
|  |         } | ||
|  |         return stat; | ||
|  |     } | ||
|  | 
 | ||
|  |     function sequencesize_2(statements, compressor) { | ||
|  |         function cons_seq(right) { | ||
|  |             n--; | ||
|  |             CHANGED = true; | ||
|  |             var left = prev.body; | ||
|  |             return make_sequence(left, [left, right]).transform(compressor); | ||
|  |         } | ||
|  |         var n = 0, prev; | ||
|  |         for (var i = 0; i < statements.length; i++) { | ||
|  |             var stat = statements[i]; | ||
|  |             if (prev) { | ||
|  |                 if (stat instanceof AST_Exit) { | ||
|  |                     stat.value = cons_seq(stat.value || make_node(AST_Undefined, stat).transform(compressor)); | ||
|  |                 } else if (stat instanceof AST_For) { | ||
|  |                     if (!(stat.init instanceof AST_Definitions)) { | ||
|  |                         const abort = walk(prev.body, node => { | ||
|  |                             if (node instanceof AST_Scope) | ||
|  |                                 return true; | ||
|  |                             if (node instanceof AST_Binary | ||
|  |                                 && node.operator === "in") { | ||
|  |                                 return walk_abort; | ||
|  |                             } | ||
|  |                         }); | ||
|  |                         if (!abort) { | ||
|  |                             if (stat.init) | ||
|  |                                 stat.init = cons_seq(stat.init); | ||
|  |                             else { | ||
|  |                                 stat.init = prev.body; | ||
|  |                                 n--; | ||
|  |                                 CHANGED = true; | ||
|  |                             } | ||
|  |                         } | ||
|  |                     } | ||
|  |                 } else if (stat instanceof AST_ForIn) { | ||
|  |                     if (!(stat.init instanceof AST_Const) && !(stat.init instanceof AST_Let)) { | ||
|  |                         stat.object = cons_seq(stat.object); | ||
|  |                     } | ||
|  |                 } else if (stat instanceof AST_If) { | ||
|  |                     stat.condition = cons_seq(stat.condition); | ||
|  |                 } else if (stat instanceof AST_Switch) { | ||
|  |                     stat.expression = cons_seq(stat.expression); | ||
|  |                 } else if (stat instanceof AST_With) { | ||
|  |                     stat.expression = cons_seq(stat.expression); | ||
|  |                 } | ||
|  |             } | ||
|  |             if (compressor.option("conditionals") && stat instanceof AST_If) { | ||
|  |                 var decls = []; | ||
|  |                 var body = to_simple_statement(stat.body, decls); | ||
|  |                 var alt = to_simple_statement(stat.alternative, decls); | ||
|  |                 if (body !== false && alt !== false && decls.length > 0) { | ||
|  |                     var len = decls.length; | ||
|  |                     decls.push(make_node(AST_If, stat, { | ||
|  |                         condition: stat.condition, | ||
|  |                         body: body || make_node(AST_EmptyStatement, stat.body), | ||
|  |                         alternative: alt | ||
|  |                     })); | ||
|  |                     decls.unshift(n, 1); | ||
|  |                     [].splice.apply(statements, decls); | ||
|  |                     i += len; | ||
|  |                     n += len + 1; | ||
|  |                     prev = null; | ||
|  |                     CHANGED = true; | ||
|  |                     continue; | ||
|  |                 } | ||
|  |             } | ||
|  |             statements[n++] = stat; | ||
|  |             prev = stat instanceof AST_SimpleStatement ? stat : null; | ||
|  |         } | ||
|  |         statements.length = n; | ||
|  |     } | ||
|  | 
 | ||
|  |     function join_object_assignments(defn, body) { | ||
|  |         if (!(defn instanceof AST_Definitions)) | ||
|  |             return; | ||
|  |         var def = defn.definitions[defn.definitions.length - 1]; | ||
|  |         if (!(def.value instanceof AST_Object)) | ||
|  |             return; | ||
|  |         var exprs; | ||
|  |         if (body instanceof AST_Assign && !body.logical) { | ||
|  |             exprs = [body]; | ||
|  |         } else if (body instanceof AST_Sequence) { | ||
|  |             exprs = body.expressions.slice(); | ||
|  |         } | ||
|  |         if (!exprs) | ||
|  |             return; | ||
|  |         var trimmed = false; | ||
|  |         do { | ||
|  |             var node = exprs[0]; | ||
|  |             if (!(node instanceof AST_Assign)) | ||
|  |                 break; | ||
|  |             if (node.operator != "=") | ||
|  |                 break; | ||
|  |             if (!(node.left instanceof AST_PropAccess)) | ||
|  |                 break; | ||
|  |             var sym = node.left.expression; | ||
|  |             if (!(sym instanceof AST_SymbolRef)) | ||
|  |                 break; | ||
|  |             if (def.name.name != sym.name) | ||
|  |                 break; | ||
|  |             if (!node.right.is_constant_expression(nearest_scope)) | ||
|  |                 break; | ||
|  |             var prop = node.left.property; | ||
|  |             if (prop instanceof AST_Node) { | ||
|  |                 prop = prop.evaluate(compressor); | ||
|  |             } | ||
|  |             if (prop instanceof AST_Node) | ||
|  |                 break; | ||
|  |             prop = "" + prop; | ||
|  |             var diff = compressor.option("ecma") < 2015 | ||
|  |                 && compressor.has_directive("use strict") ? function (node) { | ||
|  |                     return node.key != prop && (node.key && node.key.name != prop); | ||
|  |                 } : function (node) { | ||
|  |                     return node.key && node.key.name != prop; | ||
|  |                 }; | ||
|  |             if (!def.value.properties.every(diff)) | ||
|  |                 break; | ||
|  |             var p = def.value.properties.filter(function (p) { return p.key === prop; })[0]; | ||
|  |             if (!p) { | ||
|  |                 def.value.properties.push(make_node(AST_ObjectKeyVal, node, { | ||
|  |                     key: prop, | ||
|  |                     value: node.right | ||
|  |                 })); | ||
|  |             } else { | ||
|  |                 p.value = new AST_Sequence({ | ||
|  |                     start: p.start, | ||
|  |                     expressions: [p.value.clone(), node.right.clone()], | ||
|  |                     end: p.end | ||
|  |                 }); | ||
|  |             } | ||
|  |             exprs.shift(); | ||
|  |             trimmed = true; | ||
|  |         } while (exprs.length); | ||
|  |         return trimmed && exprs; | ||
|  |     } | ||
|  | 
 | ||
|  |     function join_consecutive_vars(statements) { | ||
|  |         var defs; | ||
|  |         for (var i = 0, j = -1, len = statements.length; i < len; i++) { | ||
|  |             var stat = statements[i]; | ||
|  |             var prev = statements[j]; | ||
|  |             if (stat instanceof AST_Definitions) { | ||
|  |                 if (prev && prev.TYPE == stat.TYPE) { | ||
|  |                     prev.definitions = prev.definitions.concat(stat.definitions); | ||
|  |                     CHANGED = true; | ||
|  |                 } else if (defs && defs.TYPE == stat.TYPE && declarations_only(stat)) { | ||
|  |                     defs.definitions = defs.definitions.concat(stat.definitions); | ||
|  |                     CHANGED = true; | ||
|  |                 } else { | ||
|  |                     statements[++j] = stat; | ||
|  |                     defs = stat; | ||
|  |                 } | ||
|  |             } else if (stat instanceof AST_Exit) { | ||
|  |                 stat.value = extract_object_assignments(stat.value); | ||
|  |             } else if (stat instanceof AST_For) { | ||
|  |                 var exprs = join_object_assignments(prev, stat.init); | ||
|  |                 if (exprs) { | ||
|  |                     CHANGED = true; | ||
|  |                     stat.init = exprs.length ? make_sequence(stat.init, exprs) : null; | ||
|  |                     statements[++j] = stat; | ||
|  |                 } else if (prev instanceof AST_Var && (!stat.init || stat.init.TYPE == prev.TYPE)) { | ||
|  |                     if (stat.init) { | ||
|  |                         prev.definitions = prev.definitions.concat(stat.init.definitions); | ||
|  |                     } | ||
|  |                     stat.init = prev; | ||
|  |                     statements[j] = stat; | ||
|  |                     CHANGED = true; | ||
|  |                 } else if (defs && stat.init && defs.TYPE == stat.init.TYPE && declarations_only(stat.init)) { | ||
|  |                     defs.definitions = defs.definitions.concat(stat.init.definitions); | ||
|  |                     stat.init = null; | ||
|  |                     statements[++j] = stat; | ||
|  |                     CHANGED = true; | ||
|  |                 } else { | ||
|  |                     statements[++j] = stat; | ||
|  |                 } | ||
|  |             } else if (stat instanceof AST_ForIn) { | ||
|  |                 stat.object = extract_object_assignments(stat.object); | ||
|  |             } else if (stat instanceof AST_If) { | ||
|  |                 stat.condition = extract_object_assignments(stat.condition); | ||
|  |             } else if (stat instanceof AST_SimpleStatement) { | ||
|  |                 var exprs = join_object_assignments(prev, stat.body); | ||
|  |                 if (exprs) { | ||
|  |                     CHANGED = true; | ||
|  |                     if (!exprs.length) | ||
|  |                         continue; | ||
|  |                     stat.body = make_sequence(stat.body, exprs); | ||
|  |                 } | ||
|  |                 statements[++j] = stat; | ||
|  |             } else if (stat instanceof AST_Switch) { | ||
|  |                 stat.expression = extract_object_assignments(stat.expression); | ||
|  |             } else if (stat instanceof AST_With) { | ||
|  |                 stat.expression = extract_object_assignments(stat.expression); | ||
|  |             } else { | ||
|  |                 statements[++j] = stat; | ||
|  |             } | ||
|  |         } | ||
|  |         statements.length = j + 1; | ||
|  | 
 | ||
|  |         function extract_object_assignments(value) { | ||
|  |             statements[++j] = stat; | ||
|  |             var exprs = join_object_assignments(prev, value); | ||
|  |             if (exprs) { | ||
|  |                 CHANGED = true; | ||
|  |                 if (exprs.length) { | ||
|  |                     return make_sequence(value, exprs); | ||
|  |                 } else if (value instanceof AST_Sequence) { | ||
|  |                     return value.tail_node().left; | ||
|  |                 } else { | ||
|  |                     return value.left; | ||
|  |                 } | ||
|  |             } | ||
|  |             return value; | ||
|  |         } | ||
|  |     } | ||
|  | } |