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.
		
		
		
		
		
			
		
			
				
					625 lines
				
				22 KiB
			
		
		
			
		
	
	
					625 lines
				
				22 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_Assign, | ||
|  |     AST_Block, | ||
|  |     AST_Call, | ||
|  |     AST_Catch, | ||
|  |     AST_Class, | ||
|  |     AST_ClassExpression, | ||
|  |     AST_DefaultAssign, | ||
|  |     AST_DefClass, | ||
|  |     AST_Defun, | ||
|  |     AST_Destructuring, | ||
|  |     AST_EmptyStatement, | ||
|  |     AST_Expansion, | ||
|  |     AST_Export, | ||
|  |     AST_Function, | ||
|  |     AST_IterationStatement, | ||
|  |     AST_Lambda, | ||
|  |     AST_Node, | ||
|  |     AST_Number, | ||
|  |     AST_Object, | ||
|  |     AST_ObjectKeyVal, | ||
|  |     AST_PropAccess, | ||
|  |     AST_Return, | ||
|  |     AST_Scope, | ||
|  |     AST_SimpleStatement, | ||
|  |     AST_Statement, | ||
|  |     AST_SymbolDefun, | ||
|  |     AST_SymbolFunarg, | ||
|  |     AST_SymbolLambda, | ||
|  |     AST_SymbolRef, | ||
|  |     AST_SymbolVar, | ||
|  |     AST_This, | ||
|  |     AST_Toplevel, | ||
|  |     AST_UnaryPrefix, | ||
|  |     AST_Undefined, | ||
|  |     AST_Var, | ||
|  |     AST_VarDef, | ||
|  | 
 | ||
|  |     walk, | ||
|  | 
 | ||
|  |     _INLINE, | ||
|  |     _NOINLINE, | ||
|  |     _PURE | ||
|  | } from "../ast.js"; | ||
|  | import { make_node, has_annotation } from "../utils/index.js"; | ||
|  | import "../size.js"; | ||
|  | 
 | ||
|  | import "./evaluate.js"; | ||
|  | import "./drop-side-effect-free.js"; | ||
|  | import "./reduce-vars.js"; | ||
|  | import { is_lhs } from "./inference.js"; | ||
|  | import { | ||
|  |     SQUEEZED, | ||
|  |     INLINED, | ||
|  |     UNUSED, | ||
|  | 
 | ||
|  |     has_flag, | ||
|  |     set_flag, | ||
|  | } from "./compressor-flags.js"; | ||
|  | import { | ||
|  |     make_sequence, | ||
|  |     best_of, | ||
|  |     make_node_from_constant, | ||
|  |     identifier_atom, | ||
|  |     is_empty, | ||
|  |     is_func_expr, | ||
|  |     is_iife_call, | ||
|  |     is_reachable, | ||
|  |     is_recursive_ref, | ||
|  |     retain_top_func, | ||
|  | } from "./common.js"; | ||
|  | 
 | ||
|  | 
 | ||
|  | function within_array_or_object_literal(compressor) { | ||
|  |     var node, level = 0; | ||
|  |     while (node = compressor.parent(level++)) { | ||
|  |         if (node instanceof AST_Statement) return false; | ||
|  |         if (node instanceof AST_Array | ||
|  |             || node instanceof AST_ObjectKeyVal | ||
|  |             || node instanceof AST_Object) { | ||
|  |             return true; | ||
|  |         } | ||
|  |     } | ||
|  |     return false; | ||
|  | } | ||
|  | 
 | ||
|  | function scope_encloses_variables_in_this_scope(scope, pulled_scope) { | ||
|  |     for (const enclosed of pulled_scope.enclosed) { | ||
|  |         if (pulled_scope.variables.has(enclosed.name)) { | ||
|  |             continue; | ||
|  |         } | ||
|  |         const looked_up = scope.find_variable(enclosed.name); | ||
|  |         if (looked_up) { | ||
|  |             if (looked_up === enclosed) continue; | ||
|  |             return true; | ||
|  |         } | ||
|  |     } | ||
|  |     return false; | ||
|  | } | ||
|  | 
 | ||
|  | export function inline_into_symbolref(self, compressor) { | ||
|  |     const parent = compressor.parent(); | ||
|  |     if (compressor.option("reduce_vars") && is_lhs(self, parent) !== self) { | ||
|  |         const def = self.definition(); | ||
|  |         const nearest_scope = compressor.find_scope(); | ||
|  |         if (compressor.top_retain && def.global && compressor.top_retain(def)) { | ||
|  |             def.fixed = false; | ||
|  |             def.single_use = false; | ||
|  |             return self; | ||
|  |         } | ||
|  | 
 | ||
|  |         let fixed = self.fixed_value(); | ||
|  |         let single_use = def.single_use | ||
|  |             && !(parent instanceof AST_Call | ||
|  |                 && (parent.is_callee_pure(compressor)) | ||
|  |                     || has_annotation(parent, _NOINLINE)) | ||
|  |             && !(parent instanceof AST_Export | ||
|  |                 && fixed instanceof AST_Lambda | ||
|  |                 && fixed.name); | ||
|  | 
 | ||
|  |         if (single_use && fixed instanceof AST_Node) { | ||
|  |             single_use = | ||
|  |                 !fixed.has_side_effects(compressor) | ||
|  |                 && !fixed.may_throw(compressor); | ||
|  |         } | ||
|  | 
 | ||
|  |         if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) { | ||
|  |             if (retain_top_func(fixed, compressor)) { | ||
|  |                 single_use = false; | ||
|  |             } else if (def.scope !== self.scope | ||
|  |                 && (def.escaped == 1 | ||
|  |                     || has_flag(fixed, INLINED) | ||
|  |                     || within_array_or_object_literal(compressor) | ||
|  |                     || !compressor.option("reduce_funcs"))) { | ||
|  |                 single_use = false; | ||
|  |             } else if (is_recursive_ref(compressor, def)) { | ||
|  |                 single_use = false; | ||
|  |             } else if (def.scope !== self.scope || def.orig[0] instanceof AST_SymbolFunarg) { | ||
|  |                 single_use = fixed.is_constant_expression(self.scope); | ||
|  |                 if (single_use == "f") { | ||
|  |                     var scope = self.scope; | ||
|  |                     do { | ||
|  |                         if (scope instanceof AST_Defun || is_func_expr(scope)) { | ||
|  |                             set_flag(scope, INLINED); | ||
|  |                         } | ||
|  |                     } while (scope = scope.parent_scope); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) { | ||
|  |             single_use = | ||
|  |                 def.scope === self.scope | ||
|  |                     && !scope_encloses_variables_in_this_scope(nearest_scope, fixed) | ||
|  |                 || parent instanceof AST_Call | ||
|  |                     && parent.expression === self | ||
|  |                     && !scope_encloses_variables_in_this_scope(nearest_scope, fixed) | ||
|  |                     && !(fixed.name && fixed.name.definition().recursive_refs > 0); | ||
|  |         } | ||
|  | 
 | ||
|  |         if (single_use && fixed) { | ||
|  |             if (fixed instanceof AST_DefClass) { | ||
|  |                 set_flag(fixed, SQUEEZED); | ||
|  |                 fixed = make_node(AST_ClassExpression, fixed, fixed); | ||
|  |             } | ||
|  |             if (fixed instanceof AST_Defun) { | ||
|  |                 set_flag(fixed, SQUEEZED); | ||
|  |                 fixed = make_node(AST_Function, fixed, fixed); | ||
|  |             } | ||
|  |             if (def.recursive_refs > 0 && fixed.name instanceof AST_SymbolDefun) { | ||
|  |                 const defun_def = fixed.name.definition(); | ||
|  |                 let lambda_def = fixed.variables.get(fixed.name.name); | ||
|  |                 let name = lambda_def && lambda_def.orig[0]; | ||
|  |                 if (!(name instanceof AST_SymbolLambda)) { | ||
|  |                     name = make_node(AST_SymbolLambda, fixed.name, fixed.name); | ||
|  |                     name.scope = fixed; | ||
|  |                     fixed.name = name; | ||
|  |                     lambda_def = fixed.def_function(name); | ||
|  |                 } | ||
|  |                 walk(fixed, node => { | ||
|  |                     if (node instanceof AST_SymbolRef && node.definition() === defun_def) { | ||
|  |                         node.thedef = lambda_def; | ||
|  |                         lambda_def.references.push(node); | ||
|  |                     } | ||
|  |                 }); | ||
|  |             } | ||
|  |             if ( | ||
|  |                 (fixed instanceof AST_Lambda || fixed instanceof AST_Class) | ||
|  |                 && fixed.parent_scope !== nearest_scope | ||
|  |             ) { | ||
|  |                 fixed = fixed.clone(true, compressor.get_toplevel()); | ||
|  | 
 | ||
|  |                 nearest_scope.add_child_scope(fixed); | ||
|  |             } | ||
|  |             return fixed.optimize(compressor); | ||
|  |         } | ||
|  | 
 | ||
|  |         // multiple uses
 | ||
|  |         if (fixed) { | ||
|  |             let replace; | ||
|  | 
 | ||
|  |             if (fixed instanceof AST_This) { | ||
|  |                 if (!(def.orig[0] instanceof AST_SymbolFunarg) | ||
|  |                     && def.references.every((ref) => | ||
|  |                         def.scope === ref.scope | ||
|  |                     )) { | ||
|  |                     replace = fixed; | ||
|  |                 } | ||
|  |             } else { | ||
|  |                 var ev = fixed.evaluate(compressor); | ||
|  |                 if ( | ||
|  |                     ev !== fixed | ||
|  |                     && (compressor.option("unsafe_regexp") || !(ev instanceof RegExp)) | ||
|  |                 ) { | ||
|  |                     replace = make_node_from_constant(ev, fixed); | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             if (replace) { | ||
|  |                 const name_length = self.size(compressor); | ||
|  |                 const replace_size = replace.size(compressor); | ||
|  | 
 | ||
|  |                 let overhead = 0; | ||
|  |                 if (compressor.option("unused") && !compressor.exposed(def)) { | ||
|  |                     overhead = | ||
|  |                         (name_length + 2 + replace_size) / | ||
|  |                         (def.references.length - def.assignments); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (replace_size <= name_length + overhead) { | ||
|  |                     return replace; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     return self; | ||
|  | } | ||
|  | 
 | ||
|  | export function inline_into_call(self, fn, compressor) { | ||
|  |     var exp = self.expression; | ||
|  |     var simple_args = self.args.every((arg) => !(arg instanceof AST_Expansion)); | ||
|  | 
 | ||
|  |     if (compressor.option("reduce_vars") | ||
|  |         && fn instanceof AST_SymbolRef | ||
|  |         && !has_annotation(self, _NOINLINE) | ||
|  |     ) { | ||
|  |         const fixed = fn.fixed_value(); | ||
|  |         if (!retain_top_func(fixed, compressor)) { | ||
|  |             fn = fixed; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     var is_func = fn instanceof AST_Lambda; | ||
|  | 
 | ||
|  |     var stat = is_func && fn.body[0]; | ||
|  |     var is_regular_func = is_func && !fn.is_generator && !fn.async; | ||
|  |     var can_inline = is_regular_func && compressor.option("inline") && !self.is_callee_pure(compressor); | ||
|  |     if (can_inline && stat instanceof AST_Return) { | ||
|  |         let returned = stat.value; | ||
|  |         if (!returned || returned.is_constant_expression()) { | ||
|  |             if (returned) { | ||
|  |                 returned = returned.clone(true); | ||
|  |             } else { | ||
|  |                 returned = make_node(AST_Undefined, self); | ||
|  |             } | ||
|  |             const args = self.args.concat(returned); | ||
|  |             return make_sequence(self, args).optimize(compressor); | ||
|  |         } | ||
|  | 
 | ||
|  |         // optimize identity function
 | ||
|  |         if ( | ||
|  |             fn.argnames.length === 1 | ||
|  |             && (fn.argnames[0] instanceof AST_SymbolFunarg) | ||
|  |             && self.args.length < 2 | ||
|  |             && !(self.args[0] instanceof AST_Expansion) | ||
|  |             && returned instanceof AST_SymbolRef | ||
|  |             && returned.name === fn.argnames[0].name | ||
|  |         ) { | ||
|  |             const replacement = | ||
|  |                 (self.args[0] || make_node(AST_Undefined)).optimize(compressor); | ||
|  | 
 | ||
|  |             let parent; | ||
|  |             if ( | ||
|  |                 replacement instanceof AST_PropAccess | ||
|  |                 && (parent = compressor.parent()) instanceof AST_Call | ||
|  |                 && parent.expression === self | ||
|  |             ) { | ||
|  |                 // identity function was being used to remove `this`, like in
 | ||
|  |                 //
 | ||
|  |                 // id(bag.no_this)(...)
 | ||
|  |                 //
 | ||
|  |                 // Replace with a larger but more effish (0, bag.no_this) wrapper.
 | ||
|  | 
 | ||
|  |                 return make_sequence(self, [ | ||
|  |                     make_node(AST_Number, self, { value: 0 }), | ||
|  |                     replacement | ||
|  |                 ]); | ||
|  |             } | ||
|  |             // replace call with first argument or undefined if none passed
 | ||
|  |             return replacement; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     if (can_inline) { | ||
|  |         var scope, in_loop, level = -1; | ||
|  |         let def; | ||
|  |         let returned_value; | ||
|  |         let nearest_scope; | ||
|  |         if (simple_args | ||
|  |             && !fn.uses_arguments | ||
|  |             && !(compressor.parent() instanceof AST_Class) | ||
|  |             && !(fn.name && fn instanceof AST_Function) | ||
|  |             && (returned_value = can_flatten_body(stat)) | ||
|  |             && (exp === fn | ||
|  |                 || has_annotation(self, _INLINE) | ||
|  |                 || compressor.option("unused") | ||
|  |                     && (def = exp.definition()).references.length == 1 | ||
|  |                     && !is_recursive_ref(compressor, def) | ||
|  |                     && fn.is_constant_expression(exp.scope)) | ||
|  |             && !has_annotation(self, _PURE | _NOINLINE) | ||
|  |             && !fn.contains_this() | ||
|  |             && can_inject_symbols() | ||
|  |             && (nearest_scope = compressor.find_scope()) | ||
|  |             && !scope_encloses_variables_in_this_scope(nearest_scope, fn) | ||
|  |             && !(function in_default_assign() { | ||
|  |                     // Due to the fact function parameters have their own scope
 | ||
|  |                     // which can't use `var something` in the function body within,
 | ||
|  |                     // we simply don't inline into DefaultAssign.
 | ||
|  |                     let i = 0; | ||
|  |                     let p; | ||
|  |                     while ((p = compressor.parent(i++))) { | ||
|  |                         if (p instanceof AST_DefaultAssign) return true; | ||
|  |                         if (p instanceof AST_Block) break; | ||
|  |                     } | ||
|  |                     return false; | ||
|  |                 })() | ||
|  |             && !(scope instanceof AST_Class) | ||
|  |         ) { | ||
|  |             set_flag(fn, SQUEEZED); | ||
|  |             nearest_scope.add_child_scope(fn); | ||
|  |             return make_sequence(self, flatten_fn(returned_value)).optimize(compressor); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     if (can_inline && has_annotation(self, _INLINE)) { | ||
|  |         set_flag(fn, SQUEEZED); | ||
|  |         fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn); | ||
|  |         fn = fn.clone(true); | ||
|  |         fn.figure_out_scope({}, { | ||
|  |             parent_scope: compressor.find_scope(), | ||
|  |             toplevel: compressor.get_toplevel() | ||
|  |         }); | ||
|  | 
 | ||
|  |         return make_node(AST_Call, self, { | ||
|  |             expression: fn, | ||
|  |             args: self.args, | ||
|  |         }).optimize(compressor); | ||
|  |     } | ||
|  | 
 | ||
|  |     const can_drop_this_call = is_regular_func && compressor.option("side_effects") && fn.body.every(is_empty); | ||
|  |     if (can_drop_this_call) { | ||
|  |         var args = self.args.concat(make_node(AST_Undefined, self)); | ||
|  |         return make_sequence(self, args).optimize(compressor); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (compressor.option("negate_iife") | ||
|  |         && compressor.parent() instanceof AST_SimpleStatement | ||
|  |         && is_iife_call(self)) { | ||
|  |         return self.negate(compressor, true); | ||
|  |     } | ||
|  | 
 | ||
|  |     var ev = self.evaluate(compressor); | ||
|  |     if (ev !== self) { | ||
|  |         ev = make_node_from_constant(ev, self).optimize(compressor); | ||
|  |         return best_of(compressor, ev, self); | ||
|  |     } | ||
|  | 
 | ||
|  |     return self; | ||
|  | 
 | ||
|  |     function return_value(stat) { | ||
|  |         if (!stat) return make_node(AST_Undefined, self); | ||
|  |         if (stat instanceof AST_Return) { | ||
|  |             if (!stat.value) return make_node(AST_Undefined, self); | ||
|  |             return stat.value.clone(true); | ||
|  |         } | ||
|  |         if (stat instanceof AST_SimpleStatement) { | ||
|  |             return make_node(AST_UnaryPrefix, stat, { | ||
|  |                 operator: "void", | ||
|  |                 expression: stat.body.clone(true) | ||
|  |             }); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     function can_flatten_body(stat) { | ||
|  |         var body = fn.body; | ||
|  |         var len = body.length; | ||
|  |         if (compressor.option("inline") < 3) { | ||
|  |             return len == 1 && return_value(stat); | ||
|  |         } | ||
|  |         stat = null; | ||
|  |         for (var i = 0; i < len; i++) { | ||
|  |             var line = body[i]; | ||
|  |             if (line instanceof AST_Var) { | ||
|  |                 if (stat && !line.definitions.every((var_def) => | ||
|  |                     !var_def.value | ||
|  |                 )) { | ||
|  |                     return false; | ||
|  |                 } | ||
|  |             } else if (stat) { | ||
|  |                 return false; | ||
|  |             } else if (!(line instanceof AST_EmptyStatement)) { | ||
|  |                 stat = line; | ||
|  |             } | ||
|  |         } | ||
|  |         return return_value(stat); | ||
|  |     } | ||
|  | 
 | ||
|  |     function can_inject_args(block_scoped, safe_to_inject) { | ||
|  |         for (var i = 0, len = fn.argnames.length; i < len; i++) { | ||
|  |             var arg = fn.argnames[i]; | ||
|  |             if (arg instanceof AST_DefaultAssign) { | ||
|  |                 if (has_flag(arg.left, UNUSED)) continue; | ||
|  |                 return false; | ||
|  |             } | ||
|  |             if (arg instanceof AST_Destructuring) return false; | ||
|  |             if (arg instanceof AST_Expansion) { | ||
|  |                 if (has_flag(arg.expression, UNUSED)) continue; | ||
|  |                 return false; | ||
|  |             } | ||
|  |             if (has_flag(arg, UNUSED)) continue; | ||
|  |             if (!safe_to_inject | ||
|  |                 || block_scoped.has(arg.name) | ||
|  |                 || identifier_atom.has(arg.name) | ||
|  |                 || scope.conflicting_def(arg.name)) { | ||
|  |                 return false; | ||
|  |             } | ||
|  |             if (in_loop) in_loop.push(arg.definition()); | ||
|  |         } | ||
|  |         return true; | ||
|  |     } | ||
|  | 
 | ||
|  |     function can_inject_vars(block_scoped, safe_to_inject) { | ||
|  |         var len = fn.body.length; | ||
|  |         for (var i = 0; i < len; i++) { | ||
|  |             var stat = fn.body[i]; | ||
|  |             if (!(stat instanceof AST_Var)) continue; | ||
|  |             if (!safe_to_inject) return false; | ||
|  |             for (var j = stat.definitions.length; --j >= 0;) { | ||
|  |                 var name = stat.definitions[j].name; | ||
|  |                 if (name instanceof AST_Destructuring | ||
|  |                     || block_scoped.has(name.name) | ||
|  |                     || identifier_atom.has(name.name) | ||
|  |                     || scope.conflicting_def(name.name)) { | ||
|  |                     return false; | ||
|  |                 } | ||
|  |                 if (in_loop) in_loop.push(name.definition()); | ||
|  |             } | ||
|  |         } | ||
|  |         return true; | ||
|  |     } | ||
|  | 
 | ||
|  |     function can_inject_symbols() { | ||
|  |         var block_scoped = new Set(); | ||
|  |         do { | ||
|  |             scope = compressor.parent(++level); | ||
|  |             if (scope.is_block_scope() && scope.block_scope) { | ||
|  |                 // TODO this is sometimes undefined during compression.
 | ||
|  |                 // But it should always have a value!
 | ||
|  |                 scope.block_scope.variables.forEach(function (variable) { | ||
|  |                     block_scoped.add(variable.name); | ||
|  |                 }); | ||
|  |             } | ||
|  |             if (scope instanceof AST_Catch) { | ||
|  |                 // TODO can we delete? AST_Catch is a block scope.
 | ||
|  |                 if (scope.argname) { | ||
|  |                     block_scoped.add(scope.argname.name); | ||
|  |                 } | ||
|  |             } else if (scope instanceof AST_IterationStatement) { | ||
|  |                 in_loop = []; | ||
|  |             } else if (scope instanceof AST_SymbolRef) { | ||
|  |                 if (scope.fixed_value() instanceof AST_Scope) return false; | ||
|  |             } | ||
|  |         } while (!(scope instanceof AST_Scope)); | ||
|  | 
 | ||
|  |         var safe_to_inject = !(scope instanceof AST_Toplevel) || compressor.toplevel.vars; | ||
|  |         var inline = compressor.option("inline"); | ||
|  |         if (!can_inject_vars(block_scoped, inline >= 3 && safe_to_inject)) return false; | ||
|  |         if (!can_inject_args(block_scoped, inline >= 2 && safe_to_inject)) return false; | ||
|  |         return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop); | ||
|  |     } | ||
|  | 
 | ||
|  |     function append_var(decls, expressions, name, value) { | ||
|  |         var def = name.definition(); | ||
|  | 
 | ||
|  |         // Name already exists, only when a function argument had the same name
 | ||
|  |         const already_appended = scope.variables.has(name.name); | ||
|  |         if (!already_appended) { | ||
|  |             scope.variables.set(name.name, def); | ||
|  |             scope.enclosed.push(def); | ||
|  |             decls.push(make_node(AST_VarDef, name, { | ||
|  |                 name: name, | ||
|  |                 value: null | ||
|  |             })); | ||
|  |         } | ||
|  | 
 | ||
|  |         var sym = make_node(AST_SymbolRef, name, name); | ||
|  |         def.references.push(sym); | ||
|  |         if (value) expressions.push(make_node(AST_Assign, self, { | ||
|  |             operator: "=", | ||
|  |             logical: false, | ||
|  |             left: sym, | ||
|  |             right: value.clone() | ||
|  |         })); | ||
|  |     } | ||
|  | 
 | ||
|  |     function flatten_args(decls, expressions) { | ||
|  |         var len = fn.argnames.length; | ||
|  |         for (var i = self.args.length; --i >= len;) { | ||
|  |             expressions.push(self.args[i]); | ||
|  |         } | ||
|  |         for (i = len; --i >= 0;) { | ||
|  |             var name = fn.argnames[i]; | ||
|  |             var value = self.args[i]; | ||
|  |             if (has_flag(name, UNUSED) || !name.name || scope.conflicting_def(name.name)) { | ||
|  |                 if (value) expressions.push(value); | ||
|  |             } else { | ||
|  |                 var symbol = make_node(AST_SymbolVar, name, name); | ||
|  |                 name.definition().orig.push(symbol); | ||
|  |                 if (!value && in_loop) value = make_node(AST_Undefined, self); | ||
|  |                 append_var(decls, expressions, symbol, value); | ||
|  |             } | ||
|  |         } | ||
|  |         decls.reverse(); | ||
|  |         expressions.reverse(); | ||
|  |     } | ||
|  | 
 | ||
|  |     function flatten_vars(decls, expressions) { | ||
|  |         var pos = expressions.length; | ||
|  |         for (var i = 0, lines = fn.body.length; i < lines; i++) { | ||
|  |             var stat = fn.body[i]; | ||
|  |             if (!(stat instanceof AST_Var)) continue; | ||
|  |             for (var j = 0, defs = stat.definitions.length; j < defs; j++) { | ||
|  |                 var var_def = stat.definitions[j]; | ||
|  |                 var name = var_def.name; | ||
|  |                 append_var(decls, expressions, name, var_def.value); | ||
|  |                 if (in_loop && fn.argnames.every((argname) => | ||
|  |                     argname.name != name.name | ||
|  |                 )) { | ||
|  |                     var def = fn.variables.get(name.name); | ||
|  |                     var sym = make_node(AST_SymbolRef, name, name); | ||
|  |                     def.references.push(sym); | ||
|  |                     expressions.splice(pos++, 0, make_node(AST_Assign, var_def, { | ||
|  |                         operator: "=", | ||
|  |                         logical: false, | ||
|  |                         left: sym, | ||
|  |                         right: make_node(AST_Undefined, name) | ||
|  |                     })); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     function flatten_fn(returned_value) { | ||
|  |         var decls = []; | ||
|  |         var expressions = []; | ||
|  |         flatten_args(decls, expressions); | ||
|  |         flatten_vars(decls, expressions); | ||
|  |         expressions.push(returned_value); | ||
|  | 
 | ||
|  |         if (decls.length) { | ||
|  |             const i = scope.body.indexOf(compressor.parent(level - 1)) + 1; | ||
|  |             scope.body.splice(i, 0, make_node(AST_Var, fn, { | ||
|  |                 definitions: decls | ||
|  |             })); | ||
|  |         } | ||
|  | 
 | ||
|  |         return expressions.map(exp => exp.clone(true)); | ||
|  |     } | ||
|  | } |