aboutsummaryrefslogtreecommitdiff
path: root/node_modules/uglify-js/lib/compress.js
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-12-10 21:51:33 +0100
committerFlorian Dold <florian.dold@gmail.com>2017-12-10 21:51:33 +0100
commit0469abd4a9c9270a1fdc962969e36e63699af8b4 (patch)
treef9864d4a4148621378958794cbbfdc2393733283 /node_modules/uglify-js/lib/compress.js
parent6947e79bbc258f7bc96af424ddb71a511f0c15a3 (diff)
upgrade dependencies
Diffstat (limited to 'node_modules/uglify-js/lib/compress.js')
-rw-r--r--node_modules/uglify-js/lib/compress.js1662
1 files changed, 1069 insertions, 593 deletions
diff --git a/node_modules/uglify-js/lib/compress.js b/node_modules/uglify-js/lib/compress.js
index d4a72d749..ce0fbdd56 100644
--- a/node_modules/uglify-js/lib/compress.js
+++ b/node_modules/uglify-js/lib/compress.js
@@ -60,6 +60,7 @@ function Compressor(options, false_by_default) {
expression : false,
global_defs : {},
hoist_funs : !false_by_default,
+ hoist_props : !false_by_default,
hoist_vars : false,
ie8 : false,
if_return : !false_by_default,
@@ -74,6 +75,7 @@ function Compressor(options, false_by_default) {
properties : !false_by_default,
pure_getters : !false_by_default && "strict",
pure_funcs : null,
+ reduce_funcs : !false_by_default,
reduce_vars : !false_by_default,
sequences : !false_by_default,
side_effects : !false_by_default,
@@ -143,15 +145,38 @@ merge(Compressor.prototype, {
return true;
return false;
},
+ in_boolean_context: function() {
+ if (!this.option("booleans")) return false;
+ var self = this.self();
+ for (var i = 0, p; p = this.parent(i); i++) {
+ if (p instanceof AST_SimpleStatement
+ || p instanceof AST_Conditional && p.condition === self
+ || p instanceof AST_DWLoop && p.condition === self
+ || p instanceof AST_For && p.condition === self
+ || p instanceof AST_If && p.condition === self
+ || p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) {
+ return true;
+ }
+ if (p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||")
+ || p instanceof AST_Conditional
+ || p.tail_node() === self) {
+ self = p;
+ } else {
+ return false;
+ }
+ }
+ },
compress: function(node) {
if (this.option("expression")) {
node.process_expression(true);
}
var passes = +this.options.passes || 1;
var last_count = 1 / 0;
+ var mangle = { ie8: this.option("ie8") };
for (var pass = 0; pass < passes; pass++) {
+ node.figure_out_scope(mangle);
if (pass > 0 || this.option("reduce_vars"))
- node.reset_opt_flags(this, true);
+ node.reset_opt_flags(this);
node = node.transform(this);
if (passes > 1) {
var count = 0;
@@ -190,6 +215,7 @@ merge(Compressor.prototype, {
if (node._squeezed) return node;
var was_scope = false;
if (node instanceof AST_Scope) {
+ node = node.hoist_properties(this);
node = node.hoist_declarations(this);
was_scope = true;
}
@@ -283,8 +309,9 @@ merge(Compressor.prototype, {
self.transform(tt);
});
- AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan) {
- var reduce_vars = rescan && compressor.option("reduce_vars");
+ AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) {
+ var reduce_vars = compressor.option("reduce_vars");
+ var unused = compressor.option("unused");
// Stack of look-up tables to keep track of whether a `SymbolDef` has been
// properly assigned before use:
// - `push()` & `pop()` when visiting conditional branches
@@ -297,6 +324,8 @@ merge(Compressor.prototype, {
if (node instanceof AST_SymbolRef) d.references.push(node);
d.fixed = false;
});
+ var in_loop = null;
+ var loop_ids = Object.create(null);
var tw = new TreeWalker(function(node, descend) {
node._squeezed = false;
node._optimized = false;
@@ -306,18 +335,26 @@ merge(Compressor.prototype, {
if (node instanceof AST_SymbolRef) {
var d = node.definition();
d.references.push(node);
- if (d.fixed === undefined || !safe_to_read(d)
- || is_modified(node, 0, is_immutable(node.fixed_value()))) {
+ var value;
+ if (d.fixed === undefined || !safe_to_read(d) || d.single_use == "m") {
d.fixed = false;
- } else {
- var parent = tw.parent();
- if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right
- || parent instanceof AST_Call && node !== parent.expression
- || parent instanceof AST_Return && node === parent.value && node.scope !== d.scope
- || parent instanceof AST_VarDef && node === parent.value) {
- d.escaped = true;
+ } else if (d.fixed) {
+ value = node.fixed_value();
+ if (value && ref_once(d)) {
+ d.single_use = value instanceof AST_Lambda
+ || d.scope === node.scope && value.is_constant_expression();
+ } else {
+ d.single_use = false;
+ }
+ if (is_modified(node, value, 0, is_immutable(value))) {
+ if (d.single_use) {
+ d.single_use = "m";
+ } else {
+ d.fixed = false;
+ }
}
}
+ mark_escaped(d, node.scope, node, value, 0);
}
if (node instanceof AST_SymbolCatch) {
node.definition().fixed = false;
@@ -329,6 +366,7 @@ merge(Compressor.prototype, {
d.fixed = function() {
return node.value;
};
+ loop_ids[d.id] = in_loop;
mark(d, false);
descend();
} else {
@@ -356,12 +394,15 @@ merge(Compressor.prototype, {
}
}
if (node instanceof AST_Defun) {
+ node.inlined = false;
var d = node.name.definition();
if (compressor.exposed(d) || safe_to_read(d)) {
d.fixed = false;
} else {
d.fixed = node;
+ loop_ids[d.id] = in_loop;
mark(d, true);
+ d.single_use = ref_once(d);
}
var save_ids = safe_ids;
safe_ids = Object.create(null);
@@ -370,6 +411,7 @@ merge(Compressor.prototype, {
return true;
}
if (node instanceof AST_Function) {
+ node.inlined = false;
push();
var iife;
if (!node.name
@@ -384,6 +426,7 @@ merge(Compressor.prototype, {
d.fixed = function() {
return iife.args[i] || make_node(AST_Undefined, iife);
};
+ loop_ids[d.id] = in_loop;
mark(d, true);
} else {
d.fixed = false;
@@ -400,8 +443,7 @@ merge(Compressor.prototype, {
pop();
return true;
}
- if (node instanceof AST_Binary
- && (node.operator == "&&" || node.operator == "||")) {
+ if (node instanceof AST_Binary && lazy_op(node.operator)) {
node.left.walk(tw);
push();
node.right.walk(tw);
@@ -431,10 +473,13 @@ merge(Compressor.prototype, {
return true;
}
if (node instanceof AST_DWLoop) {
+ var saved_loop = in_loop;
+ in_loop = node;
push();
node.condition.walk(tw);
node.body.walk(tw);
pop();
+ in_loop = saved_loop;
return true;
}
if (node instanceof AST_LabeledStatement) {
@@ -445,6 +490,8 @@ merge(Compressor.prototype, {
}
if (node instanceof AST_For) {
if (node.init) node.init.walk(tw);
+ var saved_loop = in_loop;
+ in_loop = node;
if (node.condition) {
push();
node.condition.walk(tw);
@@ -458,14 +505,18 @@ merge(Compressor.prototype, {
node.step.walk(tw);
pop();
}
+ in_loop = saved_loop;
return true;
}
if (node instanceof AST_ForIn) {
node.init.walk(suppressor);
node.object.walk(tw);
+ var saved_loop = in_loop;
+ in_loop = node;
push();
node.body.walk(tw);
pop();
+ in_loop = saved_loop;
return true;
}
if (node instanceof AST_Try) {
@@ -503,6 +554,7 @@ merge(Compressor.prototype, {
}
return true;
}
+ return def.fixed instanceof AST_Defun;
}
function safe_to_assign(def, value) {
@@ -525,8 +577,9 @@ merge(Compressor.prototype, {
}
function reset_def(def) {
+ def.direct_access = false;
def.escaped = false;
- if (def.scope.uses_eval) {
+ if (def.scope.uses_eval || def.scope.uses_with) {
def.fixed = false;
} else if (!compressor.exposed(def)) {
def.fixed = undefined;
@@ -535,24 +588,91 @@ merge(Compressor.prototype, {
}
def.references = [];
def.should_replace = undefined;
+ def.single_use = undefined;
+ }
+
+ function ref_once(def) {
+ return unused
+ && !def.scope.uses_eval
+ && !def.scope.uses_with
+ && def.references.length == 1
+ && loop_ids[def.id] === in_loop;
}
function is_immutable(value) {
- return value && value.is_constant() || value instanceof AST_Lambda;
+ if (!value) return false;
+ return value.is_constant()
+ || value instanceof AST_Lambda
+ || value instanceof AST_This;
+ }
+
+ function read_property(obj, key) {
+ if (key instanceof AST_Constant) key = key.getValue();
+ if (key instanceof AST_Node) return null;
+ var value;
+ if (obj instanceof AST_Array) {
+ var elements = obj.elements;
+ if (key == "length") return make_node_from_constant(elements.length, obj);
+ if (typeof key == "number" && key in elements) value = elements[key];
+ } else if (obj instanceof AST_Object) {
+ var props = obj.properties;
+ for (var i = props.length; --i >= 0;) {
+ var prop = props[i];
+ if (!(prop instanceof AST_ObjectKeyVal)) return;
+ if (!value && props[i].key === key) value = props[i].value;
+ }
+ }
+ return value instanceof AST_SymbolRef && value.fixed_value() || value;
}
- function is_modified(node, level, immutable) {
+ function is_modified(node, value, level, immutable) {
var parent = tw.parent(level);
if (is_lhs(node, parent)
- || !immutable && parent instanceof AST_Call && parent.expression === node) {
+ || !immutable
+ && parent instanceof AST_Call
+ && parent.expression === node
+ && (!(value instanceof AST_Function)
+ || !(parent instanceof AST_New) && value.contains_this())) {
return true;
+ } else if (parent instanceof AST_Array) {
+ return is_modified(parent, parent, level + 1);
+ } else if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
+ var obj = tw.parent(level + 1);
+ return is_modified(obj, obj, level + 2);
} else if (parent instanceof AST_PropAccess && parent.expression === node) {
- return !immutable && is_modified(parent, level + 1);
+ return !immutable && is_modified(parent, read_property(value, parent.property), level + 1);
}
}
+
+ function mark_escaped(d, scope, node, value, level) {
+ var parent = tw.parent(level);
+ if (value) {
+ if (value.is_constant()) return;
+ if (level > 0 && value.is_constant_expression(scope)) return;
+ }
+ if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right
+ || parent instanceof AST_Call && node !== parent.expression
+ || parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope
+ || parent instanceof AST_VarDef && node === parent.value) {
+ d.escaped = true;
+ return;
+ } else if (parent instanceof AST_Array
+ || parent instanceof AST_Conditional && node !== parent.condition
+ || parent instanceof AST_Sequence && node === parent.tail_node()) {
+ mark_escaped(d, scope, parent, parent, level + 1);
+ } else if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
+ var obj = tw.parent(level + 1);
+ mark_escaped(d, scope, obj, obj, level + 2);
+ } else if (parent instanceof AST_PropAccess && node === parent.expression) {
+ value = read_property(value, parent.property);
+ mark_escaped(d, scope, parent, value, level + 1);
+ if (value) return;
+ }
+ if (level == 0) d.direct_access = true;
+ }
});
- AST_SymbolRef.DEFMETHOD("fixed_value", function() {
+ AST_Symbol.DEFMETHOD("fixed_value", function() {
var fixed = this.definition().fixed;
if (!fixed || fixed instanceof AST_Node) return fixed;
return fixed();
@@ -604,7 +724,7 @@ merge(Compressor.prototype, {
function make_sequence(orig, expressions) {
if (expressions.length == 1) return expressions[0];
return make_node(AST_Sequence, orig, {
- expressions: expressions
+ expressions: expressions.reduce(merge_sequence, [])
});
}
@@ -661,6 +781,7 @@ merge(Compressor.prototype, {
} else {
array.push(node);
}
+ return array;
}
function as_statement_array(thing) {
@@ -703,6 +824,12 @@ merge(Compressor.prototype, {
|| compressor.option("unsafe") && global_names(this.name);
});
+ function is_identifier_atom(node) {
+ return node instanceof AST_Infinity
+ || node instanceof AST_NaN
+ || node instanceof AST_Undefined;
+ }
+
function tighten_body(statements, compressor) {
var CHANGED, max_iter = 10;
do {
@@ -736,8 +863,121 @@ merge(Compressor.prototype, {
function collapse(statements, compressor) {
var scope = compressor.find_parent(AST_Scope);
if (scope.uses_eval || scope.uses_with) return statements;
+ var args;
var candidates = [];
var stat_index = statements.length;
+ var scanner = new TreeTransformer(function(node, descend) {
+ if (abort) return node;
+ // Skip nodes before `candidate` as quickly as possible
+ if (!hit) {
+ if (node === candidate) {
+ hit = true;
+ return node;
+ }
+ return;
+ }
+ // Stop immediately if these node types are encountered
+ var parent = scanner.parent();
+ if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left)
+ || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression)
+ || node instanceof AST_Debugger
+ || node instanceof AST_IterationStatement && !(node instanceof AST_For)
+ || node instanceof AST_SymbolRef && !node.is_declared(compressor)
+ || node instanceof AST_Try
+ || node instanceof AST_With
+ || parent instanceof AST_For && node !== parent.init) {
+ abort = true;
+ return node;
+ }
+ // Replace variable with assignment when found
+ if (can_replace
+ && !(node instanceof AST_SymbolDeclaration)
+ && lhs.equivalent_to(node)) {
+ if (is_lhs(node, parent)) {
+ if (value_def) replaced++;
+ return node;
+ }
+ CHANGED = abort = true;
+ replaced++;
+ compressor.info("Collapsing {name} [{file}:{line},{col}]", {
+ name: node.print_to_string(),
+ file: node.start.file,
+ line: node.start.line,
+ col: node.start.col
+ });
+ if (candidate instanceof AST_UnaryPostfix) {
+ return make_node(AST_UnaryPrefix, candidate, candidate);
+ }
+ if (candidate instanceof AST_VarDef) {
+ if (value_def) {
+ abort = false;
+ return node;
+ }
+ 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: "=",
+ left: make_node(AST_SymbolRef, candidate.name, candidate.name),
+ right: value
+ });
+ }
+ candidate.write_only = false;
+ 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
+ || node instanceof AST_PropAccess
+ && (side_effects || node.expression.may_throw_on_access(compressor))
+ || node instanceof AST_SymbolRef
+ && (lvalues[node.name]
+ || side_effects && !references_in_scope(node.definition()))
+ || (sym = lhs_or_def(node))
+ && (sym instanceof AST_PropAccess || sym.name in lvalues)
+ || (side_effects || !replace_all)
+ && (parent instanceof AST_Binary && lazy_op(parent.operator)
+ || parent instanceof AST_Case
+ || parent instanceof AST_Conditional
+ || parent instanceof AST_If)) {
+ if (!(node instanceof AST_Scope)) descend(node, scanner);
+ abort = true;
+ return node;
+ }
+ // Skip (non-executed) functions and (leading) default case in switch statements
+ if (node instanceof AST_Default || node instanceof AST_Scope) return node;
+ });
+ var multi_replacer = new TreeTransformer(function(node) {
+ if (abort) return node;
+ // Skip nodes before `candidate` as quickly as possible
+ if (!hit) {
+ if (node === candidate) {
+ hit = true;
+ return node;
+ }
+ return;
+ }
+ // 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());
@@ -748,90 +988,40 @@ merge(Compressor.prototype, {
extract_candidates(statements[stat_index]);
while (candidates.length > 0) {
var candidate = candidates.pop();
+ var value_def = 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);
if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false;
+ var replace_all = value_def;
+ if (!replace_all && lhs instanceof AST_SymbolRef) {
+ var def = lhs.definition();
+ replace_all = def.references.length - def.replaced == 1;
+ }
var side_effects = value_has_side_effects(candidate);
- var hit = candidate.name instanceof AST_SymbolFunarg;
- var abort = false, replaced = false;
- var tt = new TreeTransformer(function(node, descend) {
- if (abort) return node;
- // Skip nodes before `candidate` as quickly as possible
- if (!hit) {
- if (node === candidate) {
- hit = true;
- return node;
- }
- return;
+ 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);
}
- // Stop immediately if these node types are encountered
- var parent = tt.parent();
- if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left)
- || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression)
- || node instanceof AST_Debugger
- || node instanceof AST_IterationStatement && !(node instanceof AST_For)
- || node instanceof AST_SymbolRef && !node.is_declared(compressor)
- || node instanceof AST_Try
- || node instanceof AST_With
- || parent instanceof AST_For && node !== parent.init) {
- abort = true;
- return node;
- }
- // Replace variable with assignment when found
- if (!(node instanceof AST_SymbolDeclaration)
- && !is_lhs(node, parent)
- && lhs.equivalent_to(node)) {
- CHANGED = replaced = abort = true;
- compressor.info("Collapsing {name} [{file}:{line},{col}]", {
- name: node.print_to_string(),
- file: node.start.file,
- line: node.start.line,
- col: node.start.col
- });
- if (candidate instanceof AST_UnaryPostfix) {
- return make_node(AST_UnaryPrefix, candidate, candidate);
- }
- if (candidate instanceof AST_VarDef) {
- var def = candidate.name.definition();
- if (def.references.length == 1 && !compressor.exposed(def)) {
- return maintain_this_binding(parent, node, candidate.value);
- }
- return make_node(AST_Assign, candidate, {
- operator: "=",
- left: make_node(AST_SymbolRef, candidate.name, candidate.name),
- right: candidate.value
- });
+ 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 = funarg;
+ for (var i = stat_index; !abort && i < statements.length; i++) {
+ statements[i].transform(multi_replacer);
}
- candidate.write_only = false;
- 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
- || node instanceof AST_PropAccess
- || node instanceof AST_SymbolRef
- && (lvalues[node.name]
- || side_effects && !references_in_scope(node.definition()))
- || (sym = lhs_or_def(node)) && get_symbol(sym).name in lvalues
- || parent instanceof AST_Binary
- && (parent.operator == "&&" || parent.operator == "||")
- || parent instanceof AST_Case
- || parent instanceof AST_Conditional
- || parent instanceof AST_For
- || parent instanceof AST_If) {
- if (!(node instanceof AST_Scope)) descend(node, tt);
- abort = true;
- return node;
}
- // Skip (non-executed) functions and (leading) default case in switch statements
- if (node instanceof AST_Default || node instanceof AST_Scope) return node;
- });
- for (var i = stat_index; !abort && i < statements.length; i++) {
- statements[i].transform(tt);
}
if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1);
}
@@ -846,13 +1036,19 @@ merge(Compressor.prototype, {
&& (iife = compressor.parent()) instanceof AST_Call
&& iife.expression === fn) {
var fn_strict = compressor.has_directive("use strict");
- if (fn_strict && fn.body.indexOf(fn_strict) < 0) fn_strict = false;
+ if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false;
+ var len = fn.argnames.length;
+ args = iife.args.slice(len);
var names = Object.create(null);
- for (var i = fn.argnames.length; --i >= 0;) {
+ for (var i = len; --i >= 0;) {
var sym = fn.argnames[i];
+ var arg = iife.args[i];
+ args.unshift(make_node(AST_VarDef, sym, {
+ name: sym,
+ value: arg
+ }));
if (sym.name in names) continue;
names[sym.name] = true;
- var arg = iife.args[i];
if (!arg) arg = make_node(AST_Undefined, sym).transform(compressor);
else {
var tw = new TreeWalker(function(node) {
@@ -896,11 +1092,23 @@ merge(Compressor.prototype, {
}
}
+ 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_VarDef) {
var def = expr.name.definition();
- if (def.orig.length > 1 && !(expr.name instanceof AST_SymbolFunarg)
- || def.references.length == 1 && !compressor.exposed(def)) {
+ if (!member(expr.name, def.orig)) return;
+ var declared = def.orig.length - def.eliminated;
+ var referenced = def.references.length - def.replaced;
+ 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 {
@@ -908,30 +1116,21 @@ merge(Compressor.prototype, {
}
}
- function get_symbol(node) {
- while (node instanceof AST_PropAccess) node = node.expression;
- return node;
+ function get_rvalue(expr) {
+ return expr[expr instanceof AST_Assign ? "right" : "value"];
}
function get_lvalues(expr) {
var lvalues = Object.create(null);
if (expr instanceof AST_Unary) return lvalues;
- var scope;
var tw = new TreeWalker(function(node, descend) {
- if (node instanceof AST_Scope) {
- var save_scope = scope;
- descend();
- scope = save_scope;
- return true;
- }
- if (node instanceof AST_SymbolRef || node instanceof AST_PropAccess) {
- var sym = get_symbol(node);
- if (sym instanceof AST_SymbolRef) {
- lvalues[sym.name] = lvalues[sym.name] || is_lhs(node, tw.parent());
- }
+ var sym = node;
+ while (sym instanceof AST_PropAccess) sym = sym.expression;
+ if (sym instanceof AST_SymbolRef || sym instanceof AST_This) {
+ lvalues[sym.name] = lvalues[sym.name] || is_lhs(node, tw.parent());
}
});
- expr[expr instanceof AST_Assign ? "right" : "value"].walk(tw);
+ get_rvalue(expr).walk(tw);
return lvalues;
}
@@ -952,10 +1151,11 @@ merge(Compressor.prototype, {
var found = false;
return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) {
if (found) return node;
- if (node === expr) {
+ if (node === expr || node.body === expr) {
found = true;
if (node instanceof AST_VarDef) {
- remove(node.name.definition().orig, node.name);
+ node.value = null;
+ return node;
}
return in_list ? MAP.skip : null;
}
@@ -964,16 +1164,12 @@ merge(Compressor.prototype, {
case 0: return null;
case 1: return node.expressions[0];
}
- if (node instanceof AST_Definitions && node.definitions.length == 0
- || node instanceof AST_SimpleStatement && !node.body) {
- return null;
- }
}));
}
function value_has_side_effects(expr) {
if (expr instanceof AST_Unary) return false;
- return expr[expr instanceof AST_Assign ? "right" : "value"].has_side_effects(compressor);
+ return get_rvalue(expr).has_side_effects(compressor);
}
function references_in_scope(def) {
@@ -1017,10 +1213,19 @@ merge(Compressor.prototype, {
var stat = statements[i];
var next = statements[i + 1];
- if (in_lambda && stat instanceof AST_Return && !stat.value && !next) {
- CHANGED = true;
- statements.length--;
- continue;
+ if (in_lambda && !next && stat instanceof AST_Return) {
+ if (!stat.value) {
+ CHANGED = true;
+ statements.length--;
+ 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) {
@@ -1107,9 +1312,16 @@ merge(Compressor.prototype, {
&& prev instanceof AST_If && prev.body instanceof AST_Return
&& i + 2 == statements.length && next instanceof AST_SimpleStatement) {
CHANGED = true;
- statements.push(make_node(AST_Return, next, {
- value: null
- }).transform(compressor));
+ stat = stat.clone();
+ stat.alternative = make_node(AST_BlockStatement, next, {
+ body: [
+ next,
+ make_node(AST_Return, next, {
+ value: null
+ })
+ ]
+ });
+ statements.splice(i, 2, stat.transform(compressor));
continue;
}
}
@@ -1225,13 +1437,7 @@ merge(Compressor.prototype, {
function cons_seq(right) {
n--;
var left = prev.body;
- if (!(left instanceof AST_Sequence)) {
- left = make_node(AST_Sequence, left, {
- expressions: [ left ]
- });
- }
- merge_sequence(left.expressions, right);
- return left.transform(compressor);
+ return make_sequence(left, [ left, right ]).transform(compressor);
};
var n = 0, prev;
for (var i = 0, len = statements.length; i < len; i++) {
@@ -1249,7 +1455,7 @@ merge(Compressor.prototype, {
if (!abort) {
if (stat.init) stat.init = cons_seq(stat.init);
else {
- stat.init = prev.body.drop_side_effect_free(compressor);
+ stat.init = prev.body;
n--;
}
}
@@ -1380,7 +1586,7 @@ merge(Compressor.prototype, {
|| this.alternative._dot_throw(compressor);
})
def(AST_Sequence, function(compressor) {
- return this.expressions[this.expressions.length - 1]._dot_throw(compressor);
+ return this.tail_node()._dot_throw(compressor);
});
def(AST_SymbolRef, function(compressor) {
if (this.is_undefined) return true;
@@ -1405,9 +1611,10 @@ merge(Compressor.prototype, {
return member(this.operator, unary_bool);
});
def(AST_Binary, function(){
- return member(this.operator, binary_bool) ||
- ( (this.operator == "&&" || this.operator == "||") &&
- this.left.is_boolean() && this.right.is_boolean() );
+ return member(this.operator, binary_bool)
+ || lazy_op(this.operator)
+ && this.left.is_boolean()
+ && this.right.is_boolean();
});
def(AST_Conditional, function(){
return this.consequent.is_boolean() && this.alternative.is_boolean();
@@ -1416,7 +1623,7 @@ merge(Compressor.prototype, {
return this.operator == "=" && this.right.is_boolean();
});
def(AST_Sequence, function(){
- return this.expressions[this.expressions.length - 1].is_boolean();
+ return this.tail_node().is_boolean();
});
def(AST_True, return_true);
def(AST_False, return_true);
@@ -1443,7 +1650,7 @@ merge(Compressor.prototype, {
|| this.operator == "=" && this.right.is_number(compressor);
});
def(AST_Sequence, function(compressor){
- return this.expressions[this.expressions.length - 1].is_number(compressor);
+ return this.tail_node().is_number(compressor);
});
def(AST_Conditional, function(compressor){
return this.consequent.is_number(compressor) && this.alternative.is_number(compressor);
@@ -1467,7 +1674,7 @@ merge(Compressor.prototype, {
return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor);
});
def(AST_Sequence, function(compressor){
- return this.expressions[this.expressions.length - 1].is_string(compressor);
+ return this.tail_node().is_string(compressor);
});
def(AST_Conditional, function(compressor){
return this.consequent.is_string(compressor) && this.alternative.is_string(compressor);
@@ -1476,6 +1683,7 @@ merge(Compressor.prototype, {
node.DEFMETHOD("is_string", func);
});
+ var lazy_op = makePredicate("&& ||");
var unary_side_effects = makePredicate("delete ++ --");
function is_lhs(node, parent) {
@@ -1587,35 +1795,6 @@ merge(Compressor.prototype, {
&& unaryPrefix(this.operator);
}
});
- // Obtain the constant value of an expression already known to be constant.
- // Result only valid iff this.is_constant() is true.
- AST_Node.DEFMETHOD("constant_value", function(compressor){
- // Accomodate when option evaluate=false.
- if (this instanceof AST_Constant && !(this instanceof AST_RegExp)) {
- return this.value;
- }
- // Accomodate the common constant expressions !0 and -1 when option evaluate=false.
- if (this instanceof AST_UnaryPrefix
- && this.expression instanceof AST_Constant) switch (this.operator) {
- case "!":
- return !this.expression.value;
- case "~":
- return ~this.expression.value;
- case "-":
- return -this.expression.value;
- case "+":
- return +this.expression.value;
- default:
- throw new Error(string_template("Cannot evaluate unary expression {value}", {
- value: this.print_to_string()
- }));
- }
- var result = this.evaluate(compressor);
- if (result !== this) {
- return result;
- }
- throw new Error(string_template("Cannot evaluate constant [{file}:{line},{col}]", this.start));
- });
def(AST_Statement, function(){
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
});
@@ -1634,6 +1813,7 @@ merge(Compressor.prototype, {
var elements = [];
for (var i = 0, len = this.elements.length; i < len; i++) {
var element = this.elements[i];
+ if (element instanceof AST_Function) continue;
var value = ev(element, compressor);
if (element === value) return this;
elements.push(value);
@@ -1657,6 +1837,7 @@ merge(Compressor.prototype, {
if (typeof Object.prototype[key] === 'function') {
return this;
}
+ if (prop.value instanceof AST_Function) continue;
val[key] = ev(prop.value, compressor);
if (val[key] === prop.value) return this;
}
@@ -1665,12 +1846,17 @@ merge(Compressor.prototype, {
return this;
});
def(AST_UnaryPrefix, function(compressor){
+ var e = this.expression;
// Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case.
- if (this.operator == "typeof" && this.expression instanceof AST_Function) {
+ if (compressor.option("typeofs")
+ && this.operator == "typeof"
+ && (e instanceof AST_Lambda
+ || e instanceof AST_SymbolRef
+ && e.fixed_value() instanceof AST_Lambda)) {
return typeof function(){};
}
- var e = ev(this.expression, compressor);
+ e = ev(e, compressor);
if (e === this.expression) return this;
switch (this.operator) {
case "!": return !e;
@@ -1731,7 +1917,6 @@ merge(Compressor.prototype, {
return value === node ? this : value;
});
def(AST_SymbolRef, function(compressor){
- if (!compressor.option("reduce_vars")) return this;
var fixed = this.fixed_value();
if (!fixed) return this;
this._eval = return_this;
@@ -2115,6 +2300,33 @@ merge(Compressor.prototype, {
}
def(AST_Node, return_false);
def(AST_Constant, return_true);
+ def(AST_Lambda, function(scope){
+ var self = this;
+ var result = true;
+ self.walk(new TreeWalker(function(node) {
+ if (!result) return true;
+ if (node instanceof AST_SymbolRef) {
+ if (self.inlined) {
+ result = false;
+ return true;
+ }
+ var def = node.definition();
+ if (member(def, self.enclosed)
+ && !self.variables.has(def.name)) {
+ if (scope) {
+ var scope_def = scope.find_variable(node);
+ if (def.undeclared ? !scope_def : scope_def === def) {
+ result = "f";
+ return true;
+ }
+ }
+ result = false;
+ }
+ return true;
+ }
+ }));
+ return result;
+ });
def(AST_Unary, function(){
return this.expression.is_constant_expression();
});
@@ -2198,7 +2410,6 @@ merge(Compressor.prototype, {
if (self.uses_eval || self.uses_with) return;
var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
- if (!drop_funcs && !drop_vars) return;
var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node) {
if (node instanceof AST_Assign && (node.write_only || node.operator == "=")) {
return node.left;
@@ -2221,78 +2432,54 @@ merge(Compressor.prototype, {
// this scope (not in nested scopes).
var scope = this;
var tw = new TreeWalker(function(node, descend){
- if (node !== self) {
- if (node instanceof AST_Defun) {
- if (!drop_funcs && scope === self) {
- var node_def = node.name.definition();
+ if (node === self) return;
+ if (node instanceof AST_Defun) {
+ if (!drop_funcs && scope === self) {
+ var node_def = node.name.definition();
+ if (!(node_def.id in in_use_ids)) {
+ in_use_ids[node_def.id] = true;
+ in_use.push(node_def);
+ }
+ }
+ initializations.add(node.name.name, node);
+ return true; // don't go in nested scopes
+ }
+ if (node instanceof AST_SymbolFunarg && scope === self) {
+ var_defs_by_id.add(node.definition().id, node);
+ }
+ if (node instanceof AST_Definitions && scope === self) {
+ node.definitions.forEach(function(def){
+ var node_def = def.name.definition();
+ if (def.name instanceof AST_SymbolVar) {
+ var_defs_by_id.add(node_def.id, def);
+ }
+ if (!drop_vars) {
if (!(node_def.id in in_use_ids)) {
in_use_ids[node_def.id] = true;
in_use.push(node_def);
}
}
- initializations.add(node.name.name, node);
- return true; // don't go in nested scopes
- }
- if (node instanceof AST_Definitions && scope === self) {
- node.definitions.forEach(function(def){
- var node_def = def.name.definition();
- if (def.name instanceof AST_SymbolVar) {
- var_defs_by_id.add(node_def.id, def);
+ if (def.value) {
+ initializations.add(def.name.name, def.value);
+ if (def.value.has_side_effects(compressor)) {
+ def.value.walk(tw);
}
- if (!drop_vars) {
- if (!(node_def.id in in_use_ids)) {
- in_use_ids[node_def.id] = true;
- in_use.push(node_def);
- }
- }
- if (def.value) {
- initializations.add(def.name.name, def.value);
- if (def.value.has_side_effects(compressor)) {
- def.value.walk(tw);
- }
- }
- });
- return true;
- }
- if (assign_as_unused(node) instanceof AST_SymbolRef && scope === self) {
- if (node instanceof AST_Assign) node.right.walk(tw);
- return true;
- }
- if (node instanceof AST_SymbolRef) {
- var node_def = node.definition();
- if (!(node_def.id in in_use_ids)) {
- in_use_ids[node_def.id] = true;
- in_use.push(node_def);
}
- return true;
- }
- if (node instanceof AST_Scope) {
- var save_scope = scope;
- scope = node;
- descend();
- scope = save_scope;
- return true;
- }
+ });
+ return true;
}
+ return scan_ref_scoped(node, descend);
});
self.walk(tw);
// pass 2: for every used symbol we need to walk its
// initialization code to figure out if it uses other
// symbols (that may not be in_use).
+ tw = new TreeWalker(scan_ref_scoped);
for (var i = 0; i < in_use.length; ++i) {
in_use[i].orig.forEach(function(decl){
// undeclared globals will be instanceof AST_SymbolRef
var init = initializations.get(decl.name);
if (init) init.forEach(function(init){
- var tw = new TreeWalker(function(node){
- if (node instanceof AST_SymbolRef) {
- var node_def = node.definition();
- if (!(node_def.id in in_use_ids)) {
- in_use_ids[node_def.id] = true;
- in_use.push(node_def);
- }
- }
- });
init.walk(tw);
});
});
@@ -2300,6 +2487,20 @@ merge(Compressor.prototype, {
// pass 3: we should drop declarations not in_use
var tt = new TreeTransformer(
function before(node, descend, in_list) {
+ var parent = tt.parent();
+ if (drop_vars) {
+ var sym = assign_as_unused(node);
+ if (sym instanceof AST_SymbolRef
+ && !(sym.definition().id in in_use_ids)) {
+ if (node instanceof AST_Assign) {
+ return maintain_this_binding(parent, node, node.right.transform(tt));
+ }
+ return make_node(AST_Number, node, {
+ value: 0
+ });
+ }
+ }
+ if (scope !== self) return;
if (node instanceof AST_Function
&& node.name
&& !compressor.option("keep_fnames")) {
@@ -2327,13 +2528,14 @@ merge(Compressor.prototype, {
}
}
if (drop_funcs && node instanceof AST_Defun && node !== self) {
- if (!(node.name.definition().id in in_use_ids)) {
+ var def = node.name.definition();
+ if (!(def.id in in_use_ids)) {
compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", template(node.name));
+ def.eliminated++;
return make_node(AST_EmptyStatement, node);
}
- return node;
}
- if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn && tt.parent().init === node)) {
+ if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) {
// place uninitialized names at the start
var body = [], head = [], tail = [];
// for unused names whose initialization has
@@ -2343,20 +2545,27 @@ merge(Compressor.prototype, {
node.definitions.forEach(function(def) {
if (def.value) def.value = def.value.transform(tt);
var sym = def.name.definition();
- if (sym.id in in_use_ids) {
+ if (!drop_vars || sym.id in in_use_ids) {
if (def.name instanceof AST_SymbolVar) {
var var_defs = var_defs_by_id.get(sym.id);
- if (var_defs.length > 1 && !def.value) {
+ if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
+ if (def.value) {
+ side_effects.push(make_node(AST_Assign, def, {
+ operator: "=",
+ left: make_node(AST_SymbolRef, def.name, def.name),
+ right: def.value
+ }));
+ }
remove(var_defs, def);
- remove(sym.orig, def.name);
+ sym.eliminated++;
return;
}
}
if (def.value) {
if (side_effects.length > 0) {
if (tail.length > 0) {
- merge_sequence(side_effects, def.value);
+ side_effects.push(def.value);
def.value = make_sequence(def.value, side_effects);
} else {
body.push(make_node(AST_SimpleStatement, node, {
@@ -2371,34 +2580,20 @@ merge(Compressor.prototype, {
}
} else if (sym.orig[0] instanceof AST_SymbolCatch) {
var value = def.value && def.value.drop_side_effect_free(compressor);
- if (value) merge_sequence(side_effects, value);
+ if (value) side_effects.push(value);
def.value = null;
head.push(def);
} else {
var value = def.value && def.value.drop_side_effect_free(compressor);
if (value) {
compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name));
- merge_sequence(side_effects, value);
+ side_effects.push(value);
} else {
compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name));
}
- remove(sym.orig, def.name);
+ sym.eliminated++;
}
});
- if (head.length == 0 && tail.length == 1 && tail[0].name instanceof AST_SymbolVar) {
- var var_defs = var_defs_by_id.get(tail[0].name.definition().id);
- if (var_defs.length > 1) {
- var def = tail.pop();
- compressor.warn("Converting duplicated definition of variable {name} to assignment [{file}:{line},{col}]", template(def.name));
- remove(var_defs, def);
- remove(def.name.definition().orig, def.name);
- side_effects.unshift(make_node(AST_Assign, def, {
- operator: "=",
- left: make_node(AST_SymbolRef, def.name, def.name),
- right: def.value
- }));
- }
- }
if (head.length > 0 || tail.length > 0) {
node.definitions = head.concat(tail);
body.push(node);
@@ -2409,29 +2604,16 @@ merge(Compressor.prototype, {
}));
}
switch (body.length) {
- case 0:
+ case 0:
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
- case 1:
+ case 1:
return body[0];
- default:
+ default:
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
body: body
});
}
}
- if (drop_vars) {
- var def = assign_as_unused(node);
- if (def instanceof AST_SymbolRef
- && !((def = def.definition()).id in in_use_ids)
- && self.variables.get(def.name) === def) {
- if (node instanceof AST_Assign) {
- return maintain_this_binding(tt.parent(), node, node.right.transform(tt));
- }
- return make_node(AST_Number, node, {
- value: 0
- });
- }
- }
// certain combination of unused name + side effect leads to:
// https://github.com/mishoo/UglifyJS2/issues/44
// https://github.com/mishoo/UglifyJS2/issues/1830
@@ -2463,8 +2645,13 @@ merge(Compressor.prototype, {
}
return node;
}
- if (node instanceof AST_Scope && node !== self)
+ if (node instanceof AST_Scope) {
+ var save_scope = scope;
+ scope = node;
+ descend(node, this);
+ scope = save_scope;
return node;
+ }
function template(sym) {
return {
@@ -2477,6 +2664,30 @@ merge(Compressor.prototype, {
}
);
self.transform(tt);
+
+ function scan_ref_scoped(node, descend) {
+ var sym;
+ if ((sym = assign_as_unused(node)) instanceof AST_SymbolRef
+ && self.variables.get(sym.name) === sym.definition()) {
+ if (node instanceof AST_Assign) node.right.walk(tw);
+ return true;
+ }
+ if (node instanceof AST_SymbolRef) {
+ var node_def = node.definition();
+ if (!(node_def.id in in_use_ids)) {
+ in_use_ids[node_def.id] = true;
+ in_use.push(node_def);
+ }
+ return true;
+ }
+ if (node instanceof AST_Scope) {
+ var save_scope = scope;
+ scope = node;
+ descend();
+ scope = save_scope;
+ return true;
+ }
+ }
});
AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){
@@ -2609,6 +2820,81 @@ merge(Compressor.prototype, {
return self;
});
+ AST_Scope.DEFMETHOD("make_var_name", function(prefix) {
+ var var_names = this.var_names;
+ if (!var_names) {
+ this.var_names = var_names = Object.create(null);
+ this.enclosed.forEach(function(def) {
+ var_names[def.name] = true;
+ });
+ this.variables.each(function(def, name) {
+ var_names[name] = true;
+ });
+ }
+ prefix = prefix.replace(/[^a-z_$]+/ig, "_");
+ var name = prefix;
+ for (var i = 0; var_names[name]; i++) name = prefix + "$" + i;
+ var_names[name] = true;
+ return name;
+ });
+
+ AST_Scope.DEFMETHOD("hoist_properties", function(compressor){
+ var self = this;
+ if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self;
+ var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false;
+ var defs_by_id = Object.create(null);
+ return self.transform(new TreeTransformer(function(node, descend) {
+ if (node instanceof AST_VarDef) {
+ var sym = node.name, def, value;
+ if (sym.scope === self
+ && !(def = sym.definition()).escaped
+ && !def.single_use
+ && !def.direct_access
+ && !top_retain(def)
+ && (value = sym.fixed_value()) === node.value
+ && value instanceof AST_Object) {
+ descend(node, this);
+ var defs = new Dictionary();
+ var assignments = [];
+ value.properties.forEach(function(prop) {
+ assignments.push(make_node(AST_VarDef, node, {
+ name: make_sym(prop.key),
+ value: prop.value
+ }));
+ });
+ defs_by_id[def.id] = defs;
+ return MAP.splice(assignments);
+ }
+ }
+ if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) {
+ var defs = defs_by_id[node.expression.definition().id];
+ if (defs) {
+ var key = node.property;
+ if (key instanceof AST_Node) key = key.getValue();
+ var def = defs.get(key);
+ var sym = make_node(AST_SymbolRef, node, {
+ name: def.name,
+ scope: node.expression.scope,
+ thedef: def
+ });
+ sym.reference({});
+ return sym;
+ }
+ }
+
+ function make_sym(key) {
+ var new_var = make_node(sym.CTOR, sym, {
+ name: self.make_var_name(sym.name + "_" + key),
+ scope: self
+ });
+ var def = self.def_variable(new_var);
+ defs.set(key, def);
+ self.enclosed.push(def);
+ return new_var;
+ }
+ }));
+ });
+
// drop_side_effect_free()
// remove side-effect-free parts which only affects return value
(function(def){
@@ -2624,7 +2910,7 @@ merge(Compressor.prototype, {
var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
changed |= node !== nodes[i];
if (node) {
- merge_sequence(ret, node);
+ ret.push(node);
first_in_statement = false;
}
}
@@ -2656,14 +2942,12 @@ merge(Compressor.prototype, {
def(AST_Binary, function(compressor, first_in_statement){
var right = this.right.drop_side_effect_free(compressor);
if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement);
- switch (this.operator) {
- case "&&":
- case "||":
+ if (lazy_op(this.operator)) {
if (right === this.right) return this;
var node = this.clone();
node.right = right;
return node;
- default:
+ } else {
var left = this.left.drop_side_effect_free(compressor, first_in_statement);
if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement);
return make_sequence(this, [ left, right ]);
@@ -2737,11 +3021,11 @@ merge(Compressor.prototype, {
return make_sequence(this, [ expression, property ]);
});
def(AST_Sequence, function(compressor){
- var last = this.expressions[this.expressions.length - 1];
+ var last = this.tail_node();
var expr = last.drop_side_effect_free(compressor);
if (expr === last) return this;
var expressions = this.expressions.slice(0, -1);
- if (expr) merge_sequence(expressions, expr);
+ if (expr) expressions.push(expr);
return make_sequence(this, expressions);
});
})(function(node, func){
@@ -2763,34 +3047,40 @@ merge(Compressor.prototype, {
return self;
});
- OPT(AST_DWLoop, function(self, compressor){
+ OPT(AST_While, function(self, compressor){
+ return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self;
+ });
+
+ OPT(AST_Do, function(self, compressor){
if (!compressor.option("loops")) return self;
- var cond = self.condition.evaluate(compressor);
- if (cond !== self.condition) {
- if (cond) {
- return make_node(AST_For, self, {
- body: self.body
- });
- }
- if (compressor.option("dead_code") && self instanceof AST_While) {
- var a = [];
- extract_declarations_from_unreachable_code(compressor, self.body, a);
- return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
- }
- if (self instanceof AST_Do) {
- var has_loop_control = false;
- var tw = new TreeWalker(function(node) {
- if (node instanceof AST_Scope || has_loop_control) return true;
- if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self)
- return has_loop_control = true;
- });
- var parent = compressor.parent();
- (parent instanceof AST_LabeledStatement ? parent : self).walk(tw);
- if (!has_loop_control) return self.body;
- }
- }
- if (self instanceof AST_While) {
- return make_node(AST_For, self, self).optimize(compressor);
+ var cond = self.condition.tail_node().evaluate(compressor);
+ if (!(cond instanceof AST_Node)) {
+ if (cond) return make_node(AST_For, self, {
+ body: make_node(AST_BlockStatement, self.body, {
+ body: [
+ self.body,
+ make_node(AST_SimpleStatement, self.condition, {
+ body: self.condition
+ })
+ ]
+ })
+ }).optimize(compressor);
+ var has_loop_control = false;
+ var tw = new TreeWalker(function(node) {
+ if (node instanceof AST_Scope || has_loop_control) return true;
+ if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self)
+ return has_loop_control = true;
+ });
+ var parent = compressor.parent();
+ (parent instanceof AST_LabeledStatement ? parent : self).walk(tw);
+ if (!has_loop_control) return make_node(AST_BlockStatement, self.body, {
+ body: [
+ self.body,
+ make_node(AST_SimpleStatement, self.condition, {
+ body: self.condition
+ })
+ ]
+ }).optimize(compressor);
}
return self;
});
@@ -2842,24 +3132,36 @@ merge(Compressor.prototype, {
OPT(AST_For, function(self, compressor){
if (!compressor.option("loops")) return self;
+ if (compressor.option("side_effects") && self.init) {
+ self.init = self.init.drop_side_effect_free(compressor);
+ }
if (self.condition) {
var cond = self.condition.evaluate(compressor);
- if (compressor.option("dead_code") && !cond) {
- var a = [];
- if (self.init instanceof AST_Statement) {
- a.push(self.init);
+ if (!(cond instanceof AST_Node)) {
+ if (cond) self.condition = null;
+ else if (!compressor.option("dead_code")) {
+ var orig = self.condition;
+ self.condition = make_node_from_constant(cond, self.condition);
+ self.condition = best_of_expression(self.condition.transform(compressor), orig);
}
- else if (self.init) {
- a.push(make_node(AST_SimpleStatement, self.init, {
- body: self.init
+ }
+ if (compressor.option("dead_code")) {
+ if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
+ if (!cond) {
+ var body = [];
+ extract_declarations_from_unreachable_code(compressor, self.body, body);
+ if (self.init instanceof AST_Statement) {
+ body.push(self.init);
+ } else if (self.init) {
+ body.push(make_node(AST_SimpleStatement, self.init, {
+ body: self.init
+ }));
+ }
+ body.push(make_node(AST_SimpleStatement, self.condition, {
+ body: self.condition
}));
+ return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
}
- extract_declarations_from_unreachable_code(compressor, self.body, a);
- return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
- }
- if (cond !== self.condition) {
- cond = make_node_from_constant(cond, self.condition).transform(compressor);
- self.condition = best_of_expression(cond, self.condition);
}
}
if_break_in_loop(self, compressor);
@@ -2875,28 +3177,34 @@ merge(Compressor.prototype, {
// “has no side effects”; also it doesn't work for cases like
// `x && true`, though it probably should.
var cond = self.condition.evaluate(compressor);
- if (cond !== self.condition) {
- if (cond) {
- compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start);
- if (compressor.option("dead_code")) {
- var a = [];
- if (self.alternative) {
- extract_declarations_from_unreachable_code(compressor, self.alternative, a);
- }
- a.push(self.body);
- return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
- }
- } else {
+ if (!compressor.option("dead_code") && !(cond instanceof AST_Node)) {
+ var orig = self.condition;
+ self.condition = make_node_from_constant(cond, orig);
+ self.condition = best_of_expression(self.condition.transform(compressor), orig);
+ }
+ if (compressor.option("dead_code")) {
+ if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
+ if (!cond) {
compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start);
- if (compressor.option("dead_code")) {
- var a = [];
- extract_declarations_from_unreachable_code(compressor, self.body, a);
- if (self.alternative) a.push(self.alternative);
- return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
+ var body = [];
+ extract_declarations_from_unreachable_code(compressor, self.body, body);
+ body.push(make_node(AST_SimpleStatement, self.condition, {
+ body: self.condition
+ }));
+ if (self.alternative) body.push(self.alternative);
+ return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
+ } else if (!(cond instanceof AST_Node)) {
+ compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start);
+ var body = [];
+ if (self.alternative) {
+ extract_declarations_from_unreachable_code(compressor, self.alternative, body);
}
+ body.push(make_node(AST_SimpleStatement, self.condition, {
+ body: self.condition
+ }));
+ body.push(self.body);
+ return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
}
- cond = make_node_from_constant(cond, self.condition).transform(compressor);
- self.condition = best_of_expression(cond, self.condition);
}
var negated = self.condition.negate(compressor);
var self_condition_length = self.condition.print_to_string().length;
@@ -3008,11 +3316,15 @@ merge(Compressor.prototype, {
if (!compressor.option("switches")) return self;
var branch;
var value = self.expression.evaluate(compressor);
- if (value !== self.expression) {
- var expression = make_node_from_constant(value, self.expression).transform(compressor);
- self.expression = best_of_expression(expression, self.expression);
+ if (!(value instanceof AST_Node)) {
+ var orig = self.expression;
+ self.expression = make_node_from_constant(value, orig);
+ self.expression = best_of_expression(self.expression.transform(compressor), orig);
}
if (!compressor.option("dead_code")) return self;
+ if (value instanceof AST_Node) {
+ value = self.expression.tail_node().evaluate(compressor);
+ }
var decl = [];
var body = [];
var default_branch;
@@ -3025,8 +3337,13 @@ merge(Compressor.prototype, {
} else {
eliminate_branch(branch, body[body.length - 1]);
}
- } else if (value !== self.expression) {
+ } else if (!(value instanceof AST_Node)) {
var exp = branch.expression.evaluate(compressor);
+ if (!(exp instanceof AST_Node) && exp !== value) {
+ eliminate_branch(branch, body[body.length - 1]);
+ continue;
+ }
+ if (exp instanceof AST_Node) exp = branch.expression.tail_node().evaluate(compressor);
if (exp === value) {
exact_match = branch;
if (default_branch) {
@@ -3035,9 +3352,6 @@ merge(Compressor.prototype, {
eliminate_branch(default_branch, body[default_index - 1]);
default_branch = null;
}
- } else if (exp !== branch.expression) {
- eliminate_branch(branch, body[body.length - 1]);
- continue;
}
}
if (aborts(branch)) {
@@ -3080,12 +3394,16 @@ merge(Compressor.prototype, {
});
self.walk(tw);
if (!has_break) {
- body = body[0].body.slice();
- body.unshift(make_node(AST_SimpleStatement, self.expression, {
- body: self.expression
+ var statements = body[0].body.slice();
+ var exp = body[0].expression;
+ if (exp) statements.unshift(make_node(AST_SimpleStatement, exp, {
+ body: exp
+ }));
+ statements.unshift(make_node(AST_SimpleStatement, self.expression, {
+ body:self.expression
}));
return make_node(AST_BlockStatement, self, {
- body: body
+ body: statements
}).optimize(compressor);
}
}
@@ -3130,7 +3448,9 @@ merge(Compressor.prototype, {
}));
if (reduce_vars) name.definition().fixed = false;
}
- remove(def.name.definition().orig, def.name);
+ def = def.name.definition();
+ def.eliminated++;
+ def.replaced--;
return a;
}, []);
if (assignments.length == 0) return null;
@@ -3146,11 +3466,11 @@ merge(Compressor.prototype, {
OPT(AST_Call, function(self, compressor){
var exp = self.expression;
var fn = exp;
+ if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) {
+ fn = fn.fixed_value();
+ }
if (compressor.option("unused")
- && (fn instanceof AST_Function
- || compressor.option("reduce_vars")
- && fn instanceof AST_SymbolRef
- && (fn = fn.fixed_value()) instanceof AST_Function)
+ && fn instanceof AST_Function
&& !fn.uses_arguments
&& !fn.uses_eval) {
var pos = 0, last = 0;
@@ -3174,130 +3494,133 @@ merge(Compressor.prototype, {
self.args.length = last;
}
if (compressor.option("unsafe")) {
- if (is_undeclared_ref(exp)) {
- switch (exp.name) {
- case "Array":
- if (self.args.length != 1) {
- return make_node(AST_Array, self, {
- elements: self.args
- }).optimize(compressor);
- }
- break;
- case "Object":
- if (self.args.length == 0) {
- return make_node(AST_Object, self, {
- properties: []
- });
- }
- break;
- case "String":
- if (self.args.length == 0) return make_node(AST_String, self, {
- value: ""
- });
- if (self.args.length <= 1) return make_node(AST_Binary, self, {
- left: self.args[0],
- operator: "+",
- right: make_node(AST_String, self, { value: "" })
+ if (is_undeclared_ref(exp)) switch (exp.name) {
+ case "Array":
+ if (self.args.length != 1) {
+ return make_node(AST_Array, self, {
+ elements: self.args
}).optimize(compressor);
- break;
- case "Number":
- if (self.args.length == 0) return make_node(AST_Number, self, {
- value: 0
+ }
+ break;
+ case "Object":
+ if (self.args.length == 0) {
+ return make_node(AST_Object, self, {
+ properties: []
});
- if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
+ }
+ break;
+ case "String":
+ if (self.args.length == 0) return make_node(AST_String, self, {
+ value: ""
+ });
+ if (self.args.length <= 1) return make_node(AST_Binary, self, {
+ left: self.args[0],
+ operator: "+",
+ right: make_node(AST_String, self, { value: "" })
+ }).optimize(compressor);
+ break;
+ case "Number":
+ if (self.args.length == 0) return make_node(AST_Number, self, {
+ value: 0
+ });
+ if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
+ expression: self.args[0],
+ operator: "+"
+ }).optimize(compressor);
+ case "Boolean":
+ if (self.args.length == 0) return make_node(AST_False, self);
+ if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
+ expression: make_node(AST_UnaryPrefix, self, {
expression: self.args[0],
- operator: "+"
- }).optimize(compressor);
- case "Boolean":
- if (self.args.length == 0) return make_node(AST_False, self);
- if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
- expression: make_node(AST_UnaryPrefix, self, {
- expression: self.args[0],
- operator: "!"
- }),
operator: "!"
- }).optimize(compressor);
- break;
- }
- }
- else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {
- return make_node(AST_Binary, self, {
+ }),
+ operator: "!"
+ }).optimize(compressor);
+ break;
+ } else if (exp instanceof AST_Dot) switch(exp.property) {
+ case "toString":
+ if (self.args.length == 0) return make_node(AST_Binary, self, {
left: make_node(AST_String, self, { value: "" }),
operator: "+",
right: exp.expression
}).optimize(compressor);
- }
- else if (exp instanceof AST_Dot && exp.expression instanceof AST_Array && exp.property == "join") EXIT: {
- var separator;
- if (self.args.length > 0) {
- separator = self.args[0].evaluate(compressor);
- if (separator === self.args[0]) break EXIT; // not a constant
- }
- var elements = [];
- var consts = [];
- exp.expression.elements.forEach(function(el) {
- var value = el.evaluate(compressor);
- if (value !== el) {
- consts.push(value);
- } else {
- if (consts.length > 0) {
- elements.push(make_node(AST_String, self, {
- value: consts.join(separator)
- }));
- consts.length = 0;
- }
- elements.push(el);
- }
- });
- if (consts.length > 0) {
- elements.push(make_node(AST_String, self, {
- value: consts.join(separator)
- }));
- }
- if (elements.length == 0) return make_node(AST_String, self, { value: "" });
- if (elements.length == 1) {
- if (elements[0].is_string(compressor)) {
- return elements[0];
+ break;
+ case "join":
+ if (exp.expression instanceof AST_Array) EXIT: {
+ var separator;
+ if (self.args.length > 0) {
+ separator = self.args[0].evaluate(compressor);
+ if (separator === self.args[0]) break EXIT; // not a constant
}
- return make_node(AST_Binary, elements[0], {
- operator : "+",
- left : make_node(AST_String, self, { value: "" }),
- right : elements[0]
+ var elements = [];
+ var consts = [];
+ exp.expression.elements.forEach(function(el) {
+ var value = el.evaluate(compressor);
+ if (value !== el) {
+ consts.push(value);
+ } else {
+ if (consts.length > 0) {
+ elements.push(make_node(AST_String, self, {
+ value: consts.join(separator)
+ }));
+ consts.length = 0;
+ }
+ elements.push(el);
+ }
});
- }
- if (separator == "") {
- var first;
- if (elements[0].is_string(compressor)
- || elements[1].is_string(compressor)) {
- first = elements.shift();
- } else {
- first = make_node(AST_String, self, { value: "" });
+ if (consts.length > 0) {
+ elements.push(make_node(AST_String, self, {
+ value: consts.join(separator)
+ }));
}
- return elements.reduce(function(prev, el){
- return make_node(AST_Binary, el, {
+ if (elements.length == 0) return make_node(AST_String, self, { value: "" });
+ if (elements.length == 1) {
+ if (elements[0].is_string(compressor)) {
+ return elements[0];
+ }
+ return make_node(AST_Binary, elements[0], {
operator : "+",
- left : prev,
- right : el
+ left : make_node(AST_String, self, { value: "" }),
+ right : elements[0]
});
- }, first).optimize(compressor);
+ }
+ if (separator == "") {
+ var first;
+ if (elements[0].is_string(compressor)
+ || elements[1].is_string(compressor)) {
+ first = elements.shift();
+ } else {
+ first = make_node(AST_String, self, { value: "" });
+ }
+ return elements.reduce(function(prev, el){
+ return make_node(AST_Binary, el, {
+ operator : "+",
+ left : prev,
+ right : el
+ });
+ }, first).optimize(compressor);
+ }
+ // need this awkward cloning to not affect original element
+ // best_of will decide which one to get through.
+ var node = self.clone();
+ node.expression = node.expression.clone();
+ node.expression.expression = node.expression.expression.clone();
+ node.expression.expression.elements = elements;
+ return best_of(compressor, self, node);
}
- // need this awkward cloning to not affect original element
- // best_of will decide which one to get through.
- var node = self.clone();
- node.expression = node.expression.clone();
- node.expression.expression = node.expression.expression.clone();
- node.expression.expression.elements = elements;
- return best_of(compressor, self, node);
- }
- else if (exp instanceof AST_Dot && exp.expression.is_string(compressor) && exp.property == "charAt") {
- var arg = self.args[0];
- var index = arg ? arg.evaluate(compressor) : 0;
- if (index !== arg) {
- return make_node(AST_Sub, exp, {
- expression: exp.expression,
- property: make_node_from_constant(index | 0, arg || exp)
- }).optimize(compressor);
+ break;
+ case "charAt":
+ if (exp.expression.is_string(compressor)) {
+ var arg = self.args[0];
+ var index = arg ? arg.evaluate(compressor) : 0;
+ if (index !== arg) {
+ return make_node(AST_Sub, exp, {
+ expression: exp.expression,
+ property: make_node_from_constant(index | 0, arg || exp)
+ }).optimize(compressor);
+ }
}
+ break;
}
}
if (compressor.option("unsafe_Func")
@@ -3366,13 +3689,15 @@ merge(Compressor.prototype, {
return make_sequence(self, args).optimize(compressor);
}
}
- if (exp instanceof AST_Function) {
+ if (fn instanceof AST_Function) {
if (compressor.option("inline")
- && !exp.name
- && !exp.uses_arguments
- && !exp.uses_eval
- && exp.body.length == 1
- && all(exp.argnames, function(arg) {
+ && exp === fn
+ && !fn.name
+ && !fn.uses_arguments
+ && !fn.uses_eval
+ && fn.body.length == 1
+ && !fn.contains_this()
+ && all(fn.argnames, function(arg) {
return arg.__unused;
})
&& !self.has_pure_annotation(compressor)) {
@@ -3386,28 +3711,11 @@ merge(Compressor.prototype, {
});
}
if (value) {
- var tw = new TreeWalker(function(node) {
- if (!value) return true;
- if (node instanceof AST_SymbolRef) {
- var ref = node.scope.find_variable(node);
- if (ref && ref.scope.parent_scope === fn.parent_scope) {
- value = null;
- return true;
- }
- }
- if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
- value = null;
- return true;
- }
- });
- value.walk(tw);
- }
- if (value) {
var args = self.args.concat(value);
return make_sequence(self, args).optimize(compressor);
}
}
- if (compressor.option("side_effects") && all(exp.body, is_empty)) {
+ if (compressor.option("side_effects") && all(fn.body, is_empty)) {
var args = self.args.concat(make_node(AST_Undefined, self));
return make_sequence(self, args).optimize(compressor);
}
@@ -3461,7 +3769,7 @@ merge(Compressor.prototype, {
trim_right_for_undefined();
if (end > 0 && compressor.option("cascade")) trim_left_for_assignment();
if (end == 0) {
- self = maintain_this_binding(compressor.parent(), self, expressions[0]);
+ self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]);
if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
return self;
}
@@ -3529,7 +3837,7 @@ merge(Compressor.prototype, {
}
if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) {
if (cdr.left.is_constant()) {
- if (cdr.operator == "||" || cdr.operator == "&&") {
+ if (lazy_op(cdr.operator)) {
expressions[++i] = expressions[j];
break;
}
@@ -3579,9 +3887,7 @@ merge(Compressor.prototype, {
if (self.operator == "delete"
&& !(e instanceof AST_SymbolRef
|| e instanceof AST_PropAccess
- || e instanceof AST_NaN
- || e instanceof AST_Infinity
- || e instanceof AST_Undefined)) {
+ || is_identifier_atom(e))) {
if (e instanceof AST_Sequence) {
e = e.expressions.slice();
e.push(make_node(AST_True, self));
@@ -3602,7 +3908,7 @@ merge(Compressor.prototype, {
return make_node(AST_Undefined, self).optimize(compressor);
}
}
- if (compressor.option("booleans") && compressor.in_boolean_context()) {
+ if (compressor.in_boolean_context()) {
switch (self.operator) {
case "!":
if (e instanceof AST_UnaryPrefix && e.operator == "!") {
@@ -3685,6 +3991,11 @@ merge(Compressor.prototype, {
});
var commutativeOperators = makePredicate("== === != !== * & | ^");
+ function is_object(node) {
+ return node instanceof AST_Array
+ || node instanceof AST_Lambda
+ || node instanceof AST_Object;
+ }
OPT(AST_Binary, function(self, compressor){
function reversible() {
@@ -3720,7 +4031,8 @@ merge(Compressor.prototype, {
case "!==":
if ((self.left.is_string(compressor) && self.right.is_string(compressor)) ||
(self.left.is_number(compressor) && self.right.is_number(compressor)) ||
- (self.left.is_boolean() && self.right.is_boolean())) {
+ (self.left.is_boolean() && self.right.is_boolean()) ||
+ self.left.equivalent_to(self.right)) {
self.operator = self.operator.substr(0, 2);
}
// XXX: intentionally falling down to the next case
@@ -3740,9 +4052,16 @@ merge(Compressor.prototype, {
if (self.operator.length == 2) self.operator += "=";
}
}
+ // obj !== obj => false
+ else if (self.left instanceof AST_SymbolRef
+ && self.right instanceof AST_SymbolRef
+ && self.left.definition() === self.right.definition()
+ && is_object(self.left.fixed_value())) {
+ return make_node(self.operator[0] == "=" ? AST_True : AST_False, self);
+ }
break;
}
- if (compressor.option("booleans") && self.operator == "+" && compressor.in_boolean_context()) {
+ if (self.operator == "+" && compressor.in_boolean_context()) {
var ll = self.left.evaluate(compressor);
var rr = self.right.evaluate(compressor);
if (ll && typeof ll == "string") {
@@ -3799,49 +4118,72 @@ merge(Compressor.prototype, {
if (compressor.option("evaluate")) {
switch (self.operator) {
case "&&":
- var ll = self.left.evaluate(compressor);
+ var ll = self.left.truthy ? true : self.left.falsy ? false : self.left.evaluate(compressor);
if (!ll) {
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
- return maintain_this_binding(compressor.parent(), self, self.left).optimize(compressor);
- } else if (ll !== self.left) {
+ return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
+ } else if (!(ll instanceof AST_Node)) {
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
- return maintain_this_binding(compressor.parent(), self, self.right).optimize(compressor);
+ return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
}
- if (compressor.option("booleans") && compressor.in_boolean_context()) {
- var rr = self.right.evaluate(compressor);
- if (!rr) {
+ var rr = self.right.evaluate(compressor);
+ if (!rr) {
+ if (compressor.in_boolean_context()) {
compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start);
return make_sequence(self, [
self.left,
make_node(AST_False, self)
]).optimize(compressor);
- } else if (rr !== self.right) {
- compressor.warn("Dropping side-effect-free && in boolean context [{file}:{line},{col}]", self.start);
+ } else self.falsy = true;
+ } else if (!(rr instanceof AST_Node)) {
+ var parent = compressor.parent();
+ if (parent.operator == "&&" && parent.left === compressor.self() || compressor.in_boolean_context()) {
+ compressor.warn("Dropping side-effect-free && [{file}:{line},{col}]", self.start);
return self.left.optimize(compressor);
}
}
+ // x || false && y ---> x ? y : false
+ if (self.left.operator == "||") {
+ var lr = self.left.right.evaluate(compressor);
+ if (!lr) return make_node(AST_Conditional, self, {
+ condition: self.left.left,
+ consequent: self.right,
+ alternative: self.left.right
+ }).optimize(compressor);
+ }
break;
case "||":
- var ll = self.left.evaluate(compressor);
+ var ll = self.left.truthy ? true : self.left.falsy ? false : self.left.evaluate(compressor);
if (!ll) {
compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
- return maintain_this_binding(compressor.parent(), self, self.right).optimize(compressor);
- } else if (ll !== self.left) {
+ return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
+ } else if (!(ll instanceof AST_Node)) {
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
- return maintain_this_binding(compressor.parent(), self, self.left).optimize(compressor);
+ return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
}
- if (compressor.option("booleans") && compressor.in_boolean_context()) {
- var rr = self.right.evaluate(compressor);
- if (!rr) {
- compressor.warn("Dropping side-effect-free || in boolean context [{file}:{line},{col}]", self.start);
+ var rr = self.right.evaluate(compressor);
+ if (!rr) {
+ var parent = compressor.parent();
+ if (parent.operator == "||" && parent.left === compressor.self() || compressor.in_boolean_context()) {
+ compressor.warn("Dropping side-effect-free || [{file}:{line},{col}]", self.start);
return self.left.optimize(compressor);
- } else if (rr !== self.right) {
+ }
+ } else if (!(rr instanceof AST_Node)) {
+ if (compressor.in_boolean_context()) {
compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start);
return make_sequence(self, [
self.left,
make_node(AST_True, self)
]).optimize(compressor);
- }
+ } else self.truthy = true;
+ }
+ if (self.left.operator == "&&") {
+ var lr = self.left.right.evaluate(compressor);
+ if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
+ condition: self.left.left,
+ consequent: self.left.right,
+ alternative: self.right
+ }).optimize(compressor);
}
break;
}
@@ -4029,8 +4371,7 @@ merge(Compressor.prototype, {
// "x" + (y + "z")==> "x" + y + "z"
if (self.right instanceof AST_Binary
&& self.right.operator == self.operator
- && (self.operator == "&&"
- || self.operator == "||"
+ && (lazy_op(self.operator)
|| (self.operator == "+"
&& (self.right.left.is_string(compressor)
|| (self.left.is_string(compressor)
@@ -4052,6 +4393,17 @@ merge(Compressor.prototype, {
return self;
});
+ function recursive_ref(compressor, def) {
+ var node;
+ for (var i = 0; node = compressor.parent(i); i++) {
+ if (node instanceof AST_Lambda) {
+ var name = node.name;
+ if (name && name.definition() === def) break;
+ }
+ }
+ return node;
+ }
+
OPT(AST_SymbolRef, function(self, compressor){
var def = self.resolve_defines(compressor);
if (def) {
@@ -4077,46 +4429,72 @@ merge(Compressor.prototype, {
if (fixed instanceof AST_Defun) {
d.fixed = fixed = make_node(AST_Function, fixed, fixed);
}
- if (compressor.option("unused")
- && fixed instanceof AST_Function
- && d.references.length == 1
- && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg)
- && !d.scope.uses_eval
- && compressor.find_parent(AST_Scope) === fixed.parent_scope) {
- return fixed.clone(true);
- }
- if (compressor.option("evaluate") && fixed) {
- if (d.should_replace === undefined) {
- var init = fixed.evaluate(compressor);
- if (init !== fixed && (compressor.option("unsafe_regexp") || !(init instanceof RegExp))) {
- init = make_node_from_constant(init, fixed);
- var value_length = init.optimize(compressor).print_to_string().length;
- var fn;
- if (has_symbol_ref(fixed)) {
- fn = function() {
- var result = init.optimize(compressor);
- return result === init ? result.clone(true) : result;
- };
- } else {
- value_length = Math.min(value_length, fixed.print_to_string().length);
- fn = function() {
- var result = best_of_expression(init.optimize(compressor), fixed);
- return result === init || result === fixed ? result.clone(true) : result;
- };
- }
- var name_length = d.name.length;
- var overhead = 0;
- if (compressor.option("unused") && !compressor.exposed(d)) {
- overhead = (name_length + 2 + value_length) / d.references.length;
- }
- d.should_replace = value_length <= name_length + overhead ? fn : false;
- } else {
- d.should_replace = false;
+ if (d.single_use && fixed instanceof AST_Function) {
+ if (d.scope !== self.scope
+ && (!compressor.option("reduce_funcs")
+ || d.escaped
+ || fixed.inlined)) {
+ d.single_use = false;
+ } else if (recursive_ref(compressor, d)) {
+ d.single_use = false;
+ } else if (d.scope !== self.scope || d.orig[0] instanceof AST_SymbolFunarg) {
+ d.single_use = fixed.is_constant_expression(self.scope);
+ if (d.single_use == "f") {
+ var scope = self.scope;
+ do {
+ if (scope instanceof AST_Defun || scope instanceof AST_Function) {
+ scope.inlined = true;
+ }
+ } while (scope = scope.parent_scope);
}
}
- if (d.should_replace) {
- return d.should_replace();
+ }
+ if (d.single_use && fixed) {
+ var value = fixed.optimize(compressor);
+ return value === fixed ? fixed.clone(true) : value;
+ }
+ if (fixed && d.should_replace === undefined) {
+ var init;
+ if (fixed instanceof AST_This) {
+ if (!(d.orig[0] instanceof AST_SymbolFunarg)
+ && all(d.references, function(ref) {
+ return d.scope === ref.scope;
+ })) {
+ init = fixed;
+ }
+ } else {
+ var ev = fixed.evaluate(compressor);
+ if (ev !== fixed && (compressor.option("unsafe_regexp") || !(ev instanceof RegExp))) {
+ init = make_node_from_constant(ev, fixed);
+ }
}
+ if (init) {
+ var value_length = init.optimize(compressor).print_to_string().length;
+ var fn;
+ if (has_symbol_ref(fixed)) {
+ fn = function() {
+ var result = init.optimize(compressor);
+ return result === init ? result.clone(true) : result;
+ };
+ } else {
+ value_length = Math.min(value_length, fixed.print_to_string().length);
+ fn = function() {
+ var result = best_of_expression(init.optimize(compressor), fixed);
+ return result === init || result === fixed ? result.clone(true) : result;
+ };
+ }
+ var name_length = d.name.length;
+ var overhead = 0;
+ if (compressor.option("unused") && !compressor.exposed(d)) {
+ overhead = (name_length + 2 + value_length) / d.references.length;
+ }
+ d.should_replace = value_length <= name_length + overhead ? fn : false;
+ } else {
+ d.should_replace = false;
+ }
+ }
+ if (d.should_replace) {
+ return d.should_replace();
}
}
return self;
@@ -4231,10 +4609,10 @@ merge(Compressor.prototype, {
if (cond !== self.condition) {
if (cond) {
compressor.warn("Condition always true [{file}:{line},{col}]", self.start);
- return maintain_this_binding(compressor.parent(), self, self.consequent);
+ return maintain_this_binding(compressor.parent(), compressor.self(), self.consequent);
} else {
compressor.warn("Condition always false [{file}:{line},{col}]", self.start);
- return maintain_this_binding(compressor.parent(), self, self.alternative);
+ return maintain_this_binding(compressor.parent(), compressor.self(), self.alternative);
}
}
var negated = cond.negate(compressor, first_in_statement(compressor));
@@ -4280,18 +4658,22 @@ merge(Compressor.prototype, {
});
}
// x ? y(a) : y(b) --> y(x ? a : b)
+ var arg_index;
if (consequent instanceof AST_Call
&& alternative.TYPE === consequent.TYPE
- && consequent.args.length == 1
- && alternative.args.length == 1
+ && consequent.args.length > 0
+ && consequent.args.length == alternative.args.length
&& consequent.expression.equivalent_to(alternative.expression)
- && !consequent.expression.has_side_effects(compressor)) {
- consequent.args[0] = make_node(AST_Conditional, self, {
+ && !self.condition.has_side_effects(compressor)
+ && !consequent.expression.has_side_effects(compressor)
+ && typeof (arg_index = single_arg_diff()) == "number") {
+ var node = consequent.clone();
+ node.args[arg_index] = make_node(AST_Conditional, self, {
condition: self.condition,
- consequent: consequent.args[0],
- alternative: alternative.args[0]
+ consequent: consequent.args[arg_index],
+ alternative: alternative.args[arg_index]
});
- return consequent;
+ return node;
}
// x?y?z:a:a --> x&&y?z:a
if (consequent instanceof AST_Conditional
@@ -4313,7 +4695,7 @@ merge(Compressor.prototype, {
consequent
]).optimize(compressor);
}
-
+ var in_bool = compressor.in_boolean_context();
if (is_true(self.consequent)) {
if (is_false(self.alternative)) {
// c ? true : false ---> !!c
@@ -4369,22 +4751,44 @@ merge(Compressor.prototype, {
// AST_True or !0
function is_true(node) {
return node instanceof AST_True
+ || in_bool
+ && node instanceof AST_Constant
+ && node.getValue()
|| (node instanceof AST_UnaryPrefix
&& node.operator == "!"
&& node.expression instanceof AST_Constant
- && !node.expression.value);
+ && !node.expression.getValue());
}
// AST_False or !1
function is_false(node) {
return node instanceof AST_False
+ || in_bool
+ && node instanceof AST_Constant
+ && !node.getValue()
|| (node instanceof AST_UnaryPrefix
&& node.operator == "!"
&& node.expression instanceof AST_Constant
- && !!node.expression.value);
+ && node.expression.getValue());
+ }
+
+ function single_arg_diff() {
+ var a = consequent.args;
+ var b = alternative.args;
+ for (var i = 0, len = a.length; i < len; i++) {
+ if (!a[i].equivalent_to(b[i])) {
+ for (var j = i + 1; j < len; j++) {
+ if (!a[j].equivalent_to(b[j])) return;
+ }
+ return i;
+ }
+ }
}
});
OPT(AST_Boolean, function(self, compressor){
+ if (compressor.in_boolean_context()) return make_node(AST_Number, self, {
+ value: +self.value
+ });
if (compressor.option("booleans")) {
var p = compressor.parent();
if (p instanceof AST_Binary && (p.operator == "=="
@@ -4411,19 +4815,72 @@ merge(Compressor.prototype, {
});
OPT(AST_Sub, function(self, compressor){
+ var expr = self.expression;
var prop = self.property;
- if (prop instanceof AST_String && compressor.option("properties")) {
- prop = prop.getValue();
- if (is_identifier_string(prop)) {
- return make_node(AST_Dot, self, {
- expression : self.expression,
- property : prop
- }).optimize(compressor);
+ if (compressor.option("properties")) {
+ var key = prop.evaluate(compressor);
+ if (key !== prop) {
+ if (typeof key == "string") {
+ if (key == "undefined") {
+ key = undefined;
+ } else {
+ var value = parseFloat(key);
+ if (value.toString() == key) {
+ key = value;
+ }
+ }
+ }
+ prop = self.property = best_of_expression(prop, make_node_from_constant(key, prop).transform(compressor));
+ var property = "" + key;
+ if (is_identifier_string(property)
+ && property.length <= prop.print_to_string().length + 1) {
+ return make_node(AST_Dot, self, {
+ expression: expr,
+ property: property
+ }).optimize(compressor);
+ }
}
- var v = parseFloat(prop);
- if (!isNaN(v) && v.toString() == prop) {
- self.property = make_node(AST_Number, self.property, {
- value: v
+ }
+ if (is_lhs(self, compressor.parent())) return self;
+ if (key !== prop) {
+ var sub = self.flatten_object(property, compressor);
+ if (sub) {
+ expr = self.expression = sub.expression;
+ prop = self.property = sub.property;
+ }
+ }
+ if (compressor.option("properties") && compressor.option("side_effects")
+ && prop instanceof AST_Number && expr instanceof AST_Array) {
+ var index = prop.getValue();
+ var elements = expr.elements;
+ if (index in elements) {
+ var flatten = true;
+ var values = [];
+ for (var i = elements.length; --i > index;) {
+ var value = elements[i].drop_side_effect_free(compressor);
+ if (value) {
+ values.unshift(value);
+ if (flatten && value.has_side_effects(compressor)) flatten = false;
+ }
+ }
+ var retValue = elements[index];
+ retValue = retValue instanceof AST_Hole ? make_node(AST_Undefined, retValue) : retValue;
+ if (!flatten) values.unshift(retValue);
+ while (--i >= 0) {
+ var value = elements[i].drop_side_effect_free(compressor);
+ if (value) values.unshift(value);
+ else index--;
+ }
+ if (flatten) {
+ values.push(retValue);
+ return make_sequence(self, values).optimize(compressor);
+ } else return make_node(AST_Sub, self, {
+ expression: make_node(AST_Array, expr, {
+ elements: values
+ }),
+ property: make_node(AST_Number, prop, {
+ value: index
+ })
});
}
}
@@ -4446,25 +4903,41 @@ merge(Compressor.prototype, {
return result;
});
+ AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) {
+ if (!compressor.option("properties")) return;
+ var expr = this.expression;
+ if (expr instanceof AST_Object) {
+ var props = expr.properties;
+ for (var i = props.length; --i >= 0;) {
+ var prop = props[i];
+ if ("" + prop.key == key) {
+ if (!all(props, function(prop) {
+ return prop instanceof AST_ObjectKeyVal;
+ })) break;
+ var value = prop.value;
+ if (value instanceof AST_Function
+ && !(compressor.parent() instanceof AST_New)
+ && value.contains_this()) break;
+ return make_node(AST_Sub, this, {
+ expression: make_node(AST_Array, expr, {
+ elements: props.map(function(prop) {
+ return prop.value;
+ })
+ }),
+ property: make_node(AST_Number, this, {
+ value: i
+ })
+ });
+ }
+ }
+ }
+ });
+
OPT(AST_Dot, function(self, compressor){
var def = self.resolve_defines(compressor);
if (def) {
return def.optimize(compressor);
}
- if (compressor.option("unsafe") && self.expression instanceof AST_Object) {
- var values = self.expression.properties;
- for (var i = values.length; --i >= 0;) {
- if (values[i].key === self.property) {
- var value = values[i].value;
- if (value instanceof AST_Function ? !value.contains_this() : !value.has_side_effects(compressor)) {
- var obj = self.expression.clone();
- obj.properties = obj.properties.slice();
- obj.properties.splice(i, 1);
- return make_sequence(self, [ obj, value ]).optimize(compressor);
- }
- }
- }
- }
if (compressor.option("unsafe_proto")
&& self.expression instanceof AST_Dot
&& self.expression.property == "prototype") {
@@ -4487,6 +4960,9 @@ merge(Compressor.prototype, {
break;
}
}
+ if (is_lhs(self, compressor.parent())) return self;
+ var sub = self.flatten_object(self.property, compressor);
+ if (sub) return sub.optimize(compressor);
var ev = self.evaluate(compressor);
if (ev !== self) {
ev = make_node_from_constant(ev, self).optimize(compressor);
@@ -4496,7 +4972,7 @@ merge(Compressor.prototype, {
});
function literals_in_boolean_context(self, compressor) {
- if (compressor.option("booleans") && compressor.in_boolean_context()) {
+ if (compressor.in_boolean_context()) {
return best_of(compressor, self, make_sequence(self, [
self,
make_node(AST_True, self)