diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-12-10 21:51:33 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-12-10 21:51:33 +0100 |
commit | 0469abd4a9c9270a1fdc962969e36e63699af8b4 (patch) | |
tree | f9864d4a4148621378958794cbbfdc2393733283 /node_modules/uglify-js/lib/compress.js | |
parent | 6947e79bbc258f7bc96af424ddb71a511f0c15a3 (diff) |
upgrade dependencies
Diffstat (limited to 'node_modules/uglify-js/lib/compress.js')
-rw-r--r-- | node_modules/uglify-js/lib/compress.js | 1662 |
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) |