725 lines
20 KiB
JavaScript
725 lines
20 KiB
JavaScript
/***********************************************************************
|
|
|
|
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 {
|
|
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';
|
|
|
|
/**
|
|
* Module that contains the inlining logic.
|
|
*
|
|
* @module
|
|
*
|
|
* The stars of the show are `inline_into_symbolref` and `inline_into_call`.
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* An extra check function for `top_retain` option, compare the length of const identifier
|
|
* and init value length and return true if init value is longer than identifier. for example:
|
|
* ```
|
|
* // top_retain: ["example"]
|
|
* const example = 100
|
|
* ```
|
|
* it will return false because length of "100" is short than identifier "example".
|
|
*/
|
|
function is_const_symbol_short_than_init_value(def, fixed_value) {
|
|
if (def.orig.length === 1 && fixed_value) {
|
|
const init_value_length = fixed_value.size();
|
|
const identifer_length = def.name.length;
|
|
return init_value_length > identifer_length;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function inline_into_symbolref(self, compressor) {
|
|
if (compressor.in_computed_key()) return self;
|
|
|
|
const parent = compressor.parent();
|
|
const def = self.definition();
|
|
const nearest_scope = compressor.find_scope();
|
|
let fixed = self.fixed_value();
|
|
if (
|
|
compressor.top_retain &&
|
|
def.global &&
|
|
compressor.top_retain(def) &&
|
|
// when identifier is in top_retain option dose not mean we can always inline it.
|
|
// if identifier name is longer then init value, we can replace it.
|
|
is_const_symbol_short_than_init_value(def, fixed)
|
|
) {
|
|
// keep it
|
|
def.fixed = false;
|
|
def.single_use = false;
|
|
return self;
|
|
}
|
|
|
|
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 (fixed instanceof AST_Class && def.scope !== self.scope) {
|
|
return self;
|
|
}
|
|
|
|
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 + fixed.size(compressor)) /
|
|
(def.references.length - def.assignments);
|
|
}
|
|
|
|
if (replace_size <= name_length + overhead) {
|
|
return replace;
|
|
}
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
export function inline_into_call(self, compressor) {
|
|
if (compressor.in_computed_key()) return self;
|
|
|
|
var exp = self.expression;
|
|
var fn = exp;
|
|
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) ||
|
|
(!compressor.toplevel.funcs && exp.definition().global)
|
|
) {
|
|
return self;
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|