diff --git a/lib/compress.js b/lib/compress.js index ab4c3c2f..7454cbe8 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -121,8 +121,8 @@ merge(Compressor.prototype, { node = node.process_expression(true); } var passes = +this.options.passes || 1; - for (var pass = 0; pass < passes && pass < 3; ++pass) { - if (pass > 0 || this.option("reduce_vars")) + for (var pass = 0; pass < passes; ++pass) { + if (this.option("reduce_vars")) node.reset_opt_flags(this, true); node = node.transform(this); } @@ -145,7 +145,6 @@ merge(Compressor.prototype, { this.warnings_produced = {}; }, before: function(node, descend, in_list) { - if (node._squeezed) return node; var was_scope = false; if (node instanceof AST_Scope) { node = node.hoist_declarations(this); @@ -157,7 +156,6 @@ merge(Compressor.prototype, { node.drop_unused(this); descend(node, this); } - node._squeezed = true; return node; } }); @@ -167,12 +165,9 @@ merge(Compressor.prototype, { function OPT(node, optimizer) { node.DEFMETHOD("optimize", function(compressor){ var self = this; - if (self._optimized) return self; if (compressor.has_directive("use asm")) return self; var opt = optimizer(self, compressor); - opt._optimized = true; - if (opt === self) return opt; - return opt.transform(compressor); + return opt; }); }; @@ -235,10 +230,6 @@ merge(Compressor.prototype, { } }); var tw = new TreeWalker(function(node, descend){ - if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { - node._squeezed = false; - node._optimized = false; - } if (reduce_vars) { if (node instanceof AST_Toplevel) node.globals.each(reset_def); if (node instanceof AST_Scope) node.variables.each(reset_def); @@ -384,7 +375,7 @@ merge(Compressor.prototype, { return new ctor(props); }; - function make_node_from_constant(compressor, val, orig) { + function make_node_from_constant(val, orig) { switch (typeof val) { case "string": return make_node(AST_String, orig, { @@ -404,9 +395,9 @@ merge(Compressor.prototype, { return make_node(AST_Number, orig, { value: val }); case "boolean": - return make_node(val ? AST_True : AST_False, orig).optimize(compressor); + return make_node(val ? AST_True : AST_False, orig); case "undefined": - return make_node(AST_Undefined, orig).transform(compressor); + return make_node(AST_Undefined, orig); default: if (val === null) { return make_node(AST_Null, orig, { value: null }); @@ -424,15 +415,17 @@ merge(Compressor.prototype, { // func(something) because that changes the meaning of // the func (becomes lexical instead of global). function maintain_this_binding(parent, orig, val) { - if (parent instanceof AST_Call && parent.expression === orig) { - if (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name === "eval") { - return make_node(AST_Seq, orig, { - car: make_node(AST_Number, orig, { - value: 0 - }), - cdr: val - }); - } + if (parent instanceof AST_Call && parent.expression === orig + && (val instanceof AST_PropAccess + || val instanceof AST_SymbolRef && val.name === "eval")) { + return orig instanceof AST_Seq + && orig.car instanceof AST_Number + && orig.car.value == 0 ? orig : make_node(AST_Seq, orig, { + car: make_node(AST_Number, orig, { + value: 0 + }), + cdr: val + }); } return val; } @@ -649,8 +642,6 @@ merge(Compressor.prototype, { statements[prev_stat_index] = make_node(AST_EmptyStatement, self); var_defs_removed = true; } - // Further optimize statement after substitution. - stat.reset_opt_flags(compressor); compressor.warn("Collapsing " + (is_constant ? "constant" : "variable") + " " + var_name + " [{file}:{line},{col}]", node.start); @@ -817,7 +808,7 @@ merge(Compressor.prototype, { body: body }); stat.alternative = null; - ret = funs.concat([ stat.transform(compressor) ]); + ret = funs.concat([ stat ]); continue loop; } @@ -834,7 +825,7 @@ merge(Compressor.prototype, { CHANGED = true; ret.push(make_node(AST_Return, ret[0], { value: null - }).transform(compressor)); + })); ret.unshift(stat); continue loop; } @@ -858,7 +849,7 @@ merge(Compressor.prototype, { stat.alternative = make_node(AST_BlockStatement, stat, { body: body }); - ret = [ stat.transform(compressor) ]; + ret = [ stat ]; continue loop; } @@ -878,7 +869,7 @@ merge(Compressor.prototype, { stat.alternative = make_node(AST_BlockStatement, stat.alternative, { body: as_statement_array(stat.alternative).slice(0, -1) }); - ret = [ stat.transform(compressor) ]; + ret = [ stat ]; continue loop; } @@ -982,7 +973,7 @@ merge(Compressor.prototype, { } else { left = AST_Seq.cons(left, right); } - return left.transform(compressor); + return left; }; var ret = [], prev = null; statements.forEach(function(stat){ @@ -1217,7 +1208,7 @@ merge(Compressor.prototype, { properties: props }); } - return make_node_from_constant(compressor, value, orig); + return make_node_from_constant(value, orig); } def(AST_Node, noop); def(AST_Dot, function(compressor, suffix){ @@ -1243,20 +1234,32 @@ merge(Compressor.prototype, { node.DEFMETHOD("_find_defs", func); }); - function best_of(ast1, ast2) { + function best_of_expression(ast1, ast2) { return ast1.print_to_string().length > ast2.print_to_string().length ? ast2 : ast1; } function best_of_statement(ast1, ast2) { - return best_of(make_node(AST_SimpleStatement, ast1, { + return best_of_expression(make_node(AST_SimpleStatement, ast1, { body: ast1 }), make_node(AST_SimpleStatement, ast2, { body: ast2 })).body; } + function best_of(compressor, ast1, ast2) { + return (first_in_statement(compressor) ? best_of_statement : best_of_expression)(ast1, ast2); + } + + function best_of_constant(compressor, ev, ast) { + var self = compressor.self(); + compressor.pop(); + ev = ev.transform(compressor); + compressor.push(self); + return best_of(compressor, ev, ast); + } + // methods to evaluate a constant expression (function (def){ // The evaluate method returns an array with one or two @@ -1277,11 +1280,18 @@ merge(Compressor.prototype, { } var node; try { - node = make_node_from_constant(compressor, val, this); + node = make_node_from_constant(val, this); } catch(ex) { return [ this ]; } - return [ best_of(node, this), val ]; + return [ node, val ]; + }); + AST_Node.DEFMETHOD("evaluate_self", function(compressor){ + var ev = this.evaluate(compressor); + if (ev.length > 1) { + return best_of_constant(compressor, ev[0], this); + } + return this; }); var unaryPrefix = makePredicate("! ~ - +"); AST_Node.DEFMETHOD("is_constant", function(){ @@ -1480,9 +1490,9 @@ merge(Compressor.prototype, { var stat = make_node(AST_SimpleStatement, alt, { body: alt }); - return best_of(negated, stat) === stat ? alt : negated; + return best_of_expression(negated, stat) === stat ? alt : negated; } - return best_of(negated, alt); + return best_of_expression(negated, alt); } def(AST_Node, function(){ return basic_negation(this); @@ -2109,9 +2119,12 @@ merge(Compressor.prototype, { if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) { if (this.expression instanceof AST_Function && (!this.expression.name || !this.expression.name.definition().references.length)) { - var node = this.clone(); - node.expression = node.expression.process_expression(false); - return node; + var expression = this.expression.process_expression(false); + if (!this.expression.equivalent_to(expression)) { + var node = this.clone(); + node.expression = expression; + return node; + } } return this; } @@ -2129,6 +2142,7 @@ merge(Compressor.prototype, { switch (this.operator) { case "&&": case "||": + if (right === this.right) return this; var node = this.clone(); node.right = right; return node; @@ -2242,7 +2256,9 @@ merge(Compressor.prototype, { OPT(AST_DWLoop, function(self, compressor){ var cond = self.condition.evaluate(compressor); - self.condition = cond[0]; + if (cond.length > 1) { + self.condition = best_of_constant(compressor, cond[0], self.condition); + } if (!compressor.option("loops")) return self; if (cond.length > 1) { if (cond[1]) { @@ -2261,7 +2277,7 @@ merge(Compressor.prototype, { } } if (self instanceof AST_While) { - return make_node(AST_For, self, self).transform(compressor); + return make_node(AST_For, self, self); } return self; }); @@ -2272,11 +2288,10 @@ merge(Compressor.prototype, { if (self.body instanceof AST_BlockStatement) { self.body = self.body.clone(); self.body.body = rest.concat(self.body.body.slice(1)); - self.body = self.body.transform(compressor); } else { self.body = make_node(AST_BlockStatement, self.body, { body: rest - }).transform(compressor); + }); } if_break_in_loop(self, compressor); } @@ -2315,7 +2330,9 @@ merge(Compressor.prototype, { var cond = self.condition; if (cond) { cond = cond.evaluate(compressor); - self.condition = cond[0]; + if (cond.length > 1) { + self.condition = best_of_constant(compressor, cond[0], self.condition); + } } if (!compressor.option("loops")) return self; if (cond) { @@ -2348,7 +2365,6 @@ 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); - self.condition = cond[0]; if (cond.length > 1) { if (cond[1]) { compressor.warn("Condition always true [{file}:{line},{col}]", self.condition.start); @@ -2358,7 +2374,7 @@ merge(Compressor.prototype, { extract_declarations_from_unreachable_code(compressor, self.alternative, a); } a.push(self.body); - return make_node(AST_BlockStatement, self, { body: a }).transform(compressor); + return make_node(AST_BlockStatement, self, { body: a }); } } else { compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start); @@ -2366,9 +2382,10 @@ merge(Compressor.prototype, { 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 }).transform(compressor); + return make_node(AST_BlockStatement, self, { body: a }); } } + self.condition = best_of_constant(compressor, cond[0], self.condition); } var negated = self.condition.negate(compressor); var self_condition_length = self.condition.print_to_string().length; @@ -2386,7 +2403,7 @@ merge(Compressor.prototype, { if (is_empty(self.body) && is_empty(self.alternative)) { return make_node(AST_SimpleStatement, self.condition, { body: self.condition - }).transform(compressor); + }); } if (self.body instanceof AST_SimpleStatement && self.alternative instanceof AST_SimpleStatement) { @@ -2396,7 +2413,7 @@ merge(Compressor.prototype, { consequent : statement_to_expression(self.body), alternative : statement_to_expression(self.alternative) }) - }).transform(compressor); + }); } if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) { if (self_condition_length === negated_length && !negated_is_best @@ -2412,14 +2429,14 @@ merge(Compressor.prototype, { left : negated, right : statement_to_expression(self.body) }) - }).transform(compressor); + }); return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "&&", left : self.condition, right : statement_to_expression(self.body) }) - }).transform(compressor); + }); } if (self.body instanceof AST_EmptyStatement && self.alternative @@ -2430,7 +2447,7 @@ merge(Compressor.prototype, { left : self.condition, right : statement_to_expression(self.alternative) }) - }).transform(compressor); + }); } if (self.body instanceof AST_Exit && self.alternative instanceof AST_Exit @@ -2441,7 +2458,7 @@ merge(Compressor.prototype, { consequent : self.body.value || make_node(AST_Undefined, self.body), alternative : self.alternative.value || make_node(AST_Undefined, self.alternative) }) - }).transform(compressor); + }); } if (self.body instanceof AST_If && !self.body.alternative @@ -2450,7 +2467,7 @@ merge(Compressor.prototype, { operator: "&&", left: self.condition, right: self.body.condition - }).transform(compressor); + }); self.body = self.body.body; } if (aborts(self.body)) { @@ -2459,7 +2476,7 @@ merge(Compressor.prototype, { self.alternative = null; return make_node(AST_BlockStatement, self, { body: [ self, alt ] - }).transform(compressor); + }); } } if (aborts(self.alternative)) { @@ -2469,7 +2486,7 @@ merge(Compressor.prototype, { self.alternative = null; return make_node(AST_BlockStatement, self, { body: [ self, body ] - }).transform(compressor); + }); } return self; }); @@ -2478,7 +2495,7 @@ merge(Compressor.prototype, { if (self.body.length == 0 && compressor.option("conditionals")) { return make_node(AST_SimpleStatement, self, { body: self.expression - }).transform(compressor); + }); } for(;;) { var last_branch = self.body[self.body.length - 1]; @@ -2496,7 +2513,7 @@ merge(Compressor.prototype, { var exp = self.expression.evaluate(compressor); out: if (exp.length == 2) try { // constant expression - self.expression = exp[0]; + self.expression = best_of_constant(compressor, exp[0], self.expression); if (!compressor.option("dead_code")) break out; var value = exp[1]; var in_if = false; @@ -2509,14 +2526,14 @@ merge(Compressor.prototype, { // no need to descend these node types return node; } - else if (node instanceof AST_Switch && node === self) { + else if (node === self) { node = node.clone(); descend(node, this); return ruined ? node : make_node(AST_BlockStatement, node, { body: node.body.reduce(function(a, branch){ return a.concat(branch.body); }, []) - }).transform(compressor); + }); } else if (node instanceof AST_If || node instanceof AST_Try) { var save = in_if; @@ -2656,7 +2673,7 @@ merge(Compressor.prototype, { if (self.args.length != 1) { return make_node(AST_Array, self, { elements: self.args - }).transform(compressor); + }); } break; case "Object": @@ -2674,7 +2691,7 @@ merge(Compressor.prototype, { left: self.args[0], operator: "+", right: make_node(AST_String, self, { value: "" }) - }).transform(compressor); + }); break; case "Number": if (self.args.length == 0) return make_node(AST_Number, self, { @@ -2683,7 +2700,7 @@ merge(Compressor.prototype, { if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { expression: self.args[0], operator: "+" - }).transform(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, { @@ -2692,7 +2709,7 @@ merge(Compressor.prototype, { operator: "!" }), operator: "!" - }).transform(compressor); + }); break; case "Function": // new Function() => function(){} @@ -2757,7 +2774,7 @@ merge(Compressor.prototype, { left: make_node(AST_String, self, { value: "" }), operator: "+", right: exp.expression - }).transform(compressor); + }); } else if (exp instanceof AST_Dot && exp.expression instanceof AST_Array && exp.property == "join") EXIT: { var separator; @@ -2769,9 +2786,9 @@ merge(Compressor.prototype, { var elements = []; var consts = []; exp.expression.elements.forEach(function(el) { - el = el.evaluate(compressor); - if (el.length > 1) { - consts.push(el[1]); + var ev = el.evaluate(compressor); + if (ev.length > 1) { + consts.push(ev[1]); } else { if (consts.length > 0) { elements.push(make_node(AST_String, self, { @@ -2779,7 +2796,7 @@ merge(Compressor.prototype, { })); consts.length = 0; } - elements.push(el[0]); + elements.push(el); } }); if (consts.length > 0) { @@ -2812,15 +2829,15 @@ merge(Compressor.prototype, { left : prev, right : el }); - }, first).transform(compressor); + }, first); } // need this awkward cloning to not affect original element - // best_of will decide which one to get through. + // best_of_expression 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(self, node); + return best_of_expression(self, node); } } if (exp instanceof AST_Function) { @@ -2828,13 +2845,13 @@ merge(Compressor.prototype, { var value = exp.body[0].value; if (!value || value.is_constant()) { var args = self.args.concat(value || make_node(AST_Undefined, self)); - return AST_Seq.from_array(args).transform(compressor); + return AST_Seq.from_array(args); } } if (compressor.option("side_effects")) { if (!AST_Block.prototype.has_side_effects.call(exp, compressor)) { var args = self.args.concat(make_node(AST_Undefined, self)); - return AST_Seq.from_array(args).transform(compressor); + return AST_Seq.from_array(args); } } } @@ -2847,7 +2864,7 @@ merge(Compressor.prototype, { if (name instanceof AST_SymbolRef && name.name == "console" && name.undeclared()) { - return make_node(AST_Undefined, self).transform(compressor); + return make_node(AST_Undefined, self); } } } @@ -2869,7 +2886,7 @@ merge(Compressor.prototype, { case "Function": case "Error": case "Array": - return make_node(AST_Call, self, self).transform(compressor); + return make_node(AST_Call, self, self); } } } @@ -2879,8 +2896,12 @@ merge(Compressor.prototype, { OPT(AST_Seq, function(self, compressor){ if (!compressor.option("side_effects")) return self; - self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor)); - if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr); + var car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor)); + if (car) { + self.car = car; + } else { + return maintain_this_binding(compressor.parent(), self, self.cdr); + } if (compressor.option("cascade")) { var left; if (self.car instanceof AST_Assign @@ -2932,7 +2953,7 @@ merge(Compressor.prototype, { var x = seq.to_array(); this.expression = x.pop(); x.push(this); - seq = AST_Seq.from_array(x).transform(compressor); + seq = AST_Seq.from_array(x); return seq; } } @@ -2945,9 +2966,7 @@ merge(Compressor.prototype, { OPT(AST_UnaryPrefix, function(self, compressor){ var seq = self.lift_sequences(compressor); - if (seq !== self) { - return seq; - } + if (seq !== self) return seq; var e = self.expression; if (compressor.option("side_effects") && self.operator == "void") { e = e.drop_side_effect_free(compressor); @@ -2955,7 +2974,7 @@ merge(Compressor.prototype, { self.expression = e; return self; } else { - return make_node(AST_Undefined, self).transform(compressor); + return make_node(AST_Undefined, self); } } if (compressor.option("booleans") && compressor.in_boolean_context()) { @@ -2966,8 +2985,7 @@ merge(Compressor.prototype, { return e.expression; } if (e instanceof AST_Binary) { - var statement = first_in_statement(compressor); - self = (statement ? best_of_statement : best_of)(self, e.negate(compressor, statement)); + self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor))); } break; case "typeof": @@ -2977,10 +2995,19 @@ merge(Compressor.prototype, { return make_node(AST_Seq, self, { car: e, cdr: make_node(AST_True, self) - }).optimize(compressor); + }); } } - return self.evaluate(compressor)[0]; + if (e instanceof AST_Infinity && self.operator == "-") { + self.expression = e.transform(compressor); + return self; + } else if (e instanceof AST_Number + && (self.operator == "-" + || self.operator == "!" + && (e.value == 0 || e.value == 1))) { + return self; + } + return self.evaluate_self(compressor); }); function has_side_effects_or_prop_access(node, compressor) { @@ -2998,7 +3025,7 @@ merge(Compressor.prototype, { var x = seq.to_array(); this.left = x.pop(); x.push(this); - seq = AST_Seq.from_array(x).transform(compressor); + seq = AST_Seq.from_array(x); return seq; } if (this.right instanceof AST_Seq @@ -3008,7 +3035,7 @@ merge(Compressor.prototype, { var x = seq.to_array(); this.right = x.pop(); x.push(this); - seq = AST_Seq.from_array(x).transform(compressor); + seq = AST_Seq.from_array(x); return seq; } } @@ -3071,7 +3098,8 @@ merge(Compressor.prototype, { } } } - self = self.lift_sequences(compressor); + var seq = self.lift_sequences(compressor); + if (seq !== self) return seq; if (compressor.option("comparisons")) switch (self.operator) { case "===": case "!==": @@ -3092,7 +3120,7 @@ merge(Compressor.prototype, { if (expr instanceof AST_SymbolRef ? !expr.undeclared() : !(expr instanceof AST_PropAccess) || compressor.option("screw_ie8")) { self.right = expr; - self.left = make_node(AST_Undefined, self.left).optimize(compressor); + self.left = make_node(AST_Undefined, self.left); if (self.operator.length == 2) self.operator += "="; } } @@ -3107,13 +3135,13 @@ merge(Compressor.prototype, { return make_node(AST_Seq, self, { car: self.left, cdr: make_node(AST_False, self) - }).optimize(compressor); + }); } if (ll.length > 1 && ll[1]) { - return rr[0]; + return best_of_constant(compressor, rr[0], self.right); } if (rr.length > 1 && rr[1]) { - return ll[0]; + return best_of_constant(compressor, ll[0], self.left); } break; case "||": @@ -3124,13 +3152,13 @@ merge(Compressor.prototype, { return make_node(AST_Seq, self, { car: self.left, cdr: make_node(AST_True, self) - }).optimize(compressor); + }); } if (ll.length > 1 && !ll[1]) { - return rr[0]; + return best_of_constant(compressor, rr[0], self.right); } if (rr.length > 1 && !rr[1]) { - return ll[0]; + return best_of_constant(compressor, ll[0], self.left); } break; case "+": @@ -3141,26 +3169,25 @@ merge(Compressor.prototype, { return make_node(AST_Seq, self, { car: self.right, cdr: make_node(AST_True, self) - }).optimize(compressor); + }); } if (rr.length > 1 && rr[0] instanceof AST_String && rr[1]) { compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start); return make_node(AST_Seq, self, { car: self.left, cdr: make_node(AST_True, self) - }).optimize(compressor); + }); } break; } if (compressor.option("comparisons") && self.is_boolean()) { if (!(compressor.parent() instanceof AST_Binary) || compressor.parent() instanceof AST_Assign) { - var statement = first_in_statement(compressor); var negated = make_node(AST_UnaryPrefix, self, { operator: "!", - expression: self.negate(compressor, statement) + expression: self.negate(compressor, first_in_statement(compressor)) }); - self = (statement ? best_of_statement : best_of)(self, negated); + self = best_of(compressor, self, negated); } if (compressor.option("unsafe_comps")) { switch (self.operator) { @@ -3186,7 +3213,7 @@ merge(Compressor.prototype, { && self.left.left.getValue() == "" && self.right.is_string(compressor)) { self.left = self.left.right; - return self.transform(compressor); + return self; } } if (compressor.option("evaluate")) { @@ -3312,9 +3339,9 @@ merge(Compressor.prototype, { }); if (self.right instanceof AST_Constant && !(self.left instanceof AST_Constant)) { - self = best_of(reversed, self); + self = best_of(compressor, reversed, self); } else { - self = best_of(self, reversed); + self = best_of(compressor, self, reversed); } } if (associative && self.is_number(compressor)) { @@ -3409,9 +3436,8 @@ merge(Compressor.prototype, { right : self.right.left }); self.right = self.right.right; - return self.transform(compressor); } - return self.evaluate(compressor)[0]; + return self.evaluate_self(compressor); }); OPT(AST_SymbolRef, function(self, compressor){ @@ -3426,11 +3452,11 @@ merge(Compressor.prototype, { && (!self.scope.uses_with || !compressor.find_parent(AST_With))) { switch (self.name) { case "undefined": - return make_node(AST_Undefined, self).transform(compressor); + return make_node(AST_Undefined, self); case "NaN": - return make_node(AST_NaN, self).transform(compressor); + return make_node(AST_NaN, self); case "Infinity": - return make_node(AST_Infinity, self).transform(compressor); + return make_node(AST_Infinity, self); } } if (compressor.option("evaluate") && compressor.option("reduce_vars")) { @@ -3439,11 +3465,12 @@ merge(Compressor.prototype, { if (d.should_replace === undefined) { var init = d.fixed.evaluate(compressor); if (init.length > 1) { - var value = init[0].print_to_string().length; + init = best_of_constant(compressor, init[0], d.fixed); + var value = init.print_to_string().length; var name = d.name.length; var freq = d.references.length; var overhead = d.global || !freq ? 0 : (name + 2 + value) / freq; - d.should_replace = value <= name + overhead ? init[0] : false; + d.should_replace = value <= name + overhead ? init : false; } else { d.should_replace = false; } @@ -3484,7 +3511,8 @@ merge(Compressor.prototype, { var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ]; var ASSIGN_OPS_COMMUTATIVE = [ '*', '|', '^', '&' ]; OPT(AST_Assign, function(self, compressor){ - self = self.lift_sequences(compressor); + var seq = self.lift_sequences(compressor); + if (seq !== self) return seq; if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) { // x = expr1 OP expr2 if (self.right.left instanceof AST_SymbolRef @@ -3523,9 +3551,8 @@ merge(Compressor.prototype, { return maintain_this_binding(compressor.parent(), self, self.alternative); } } - var statement = first_in_statement(compressor); - var negated = cond[0].negate(compressor, statement); - if ((statement ? best_of_statement : best_of)(cond[0], negated) === negated) { + var negated = self.condition.negate(compressor, first_in_statement(compressor)); + if (best_of(compressor, self.condition, negated) === negated) { self = make_node(AST_Conditional, self, { condition: negated, consequent: self.alternative, @@ -3589,7 +3616,7 @@ merge(Compressor.prototype, { return make_node(AST_Seq, self, { car: self.condition, cdr: consequent - }).optimize(compressor); + }); } if (is_true(self.consequent)) { @@ -3696,7 +3723,7 @@ merge(Compressor.prototype, { return make_node(AST_Dot, self, { expression : self.expression, property : prop - }).optimize(compressor); + }); } var v = parseFloat(prop); if (!isNaN(v) && v.toString() == prop) { @@ -3705,7 +3732,7 @@ merge(Compressor.prototype, { }); } } - return self.evaluate(compressor)[0]; + return self.evaluate_self(compressor); }); OPT(AST_Dot, function(self, compressor){ @@ -3720,7 +3747,7 @@ merge(Compressor.prototype, { property : make_node(AST_String, self, { value: prop }) - }).optimize(compressor); + }); } if (compressor.option("unsafe_proto") && self.expression instanceof AST_Dot @@ -3744,16 +3771,15 @@ merge(Compressor.prototype, { break; } } - return self.evaluate(compressor)[0]; + return self.evaluate_self(compressor); }); function literals_in_boolean_context(self, compressor) { if (compressor.option("booleans") && compressor.in_boolean_context()) { - var best = first_in_statement(compressor) ? best_of_statement : best_of; - return best(self, make_node(AST_Seq, self, { + return best_of_constant(compressor, make_node(AST_Seq, self, { car: self, cdr: make_node(AST_True, self) - }).optimize(compressor)); + }), self); } return self; }; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 6d7e2d9f..34feaa7c 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -270,19 +270,14 @@ collapse_vars_if: { } expect: { function f1() { - sideeffect(); - return "x" != "Bar" + (g1 + g2) / 4 ? g9 : g5; + return sideeffect(), "x" != "Bar" + (g1 + g2) / 4 ? g9 : g5; } function f2() { var x = g1 + g2; - sideeffect(); - return "x" != "Bar" + x / 4 ? g9 : g5; + return sideeffect(), "x" != "Bar" + x / 4 ? g9 : g5; } function f3(x) { - if (x) { - return 1; - } - return 2; + return x ? 1 : 2; } } } @@ -551,13 +546,11 @@ collapse_vars_switch: { } expect: { function f1() { - sideeffect(); - switch ("Bar" + (g1 + g2) / 4) { case 0: return g9 } + switch (sideeffect(), "Bar" + (g1 + g2) / 4) { case 0: return g9 } } function f2() { var x = g1 + g2; - sideeffect(); - switch ("Bar" + x / 4) { case 0: return g9 } + switch (sideeffect(), "Bar" + x / 4) { case 0: return g9 } } function f3(x) { // verify no extraneous semicolon in case block before return @@ -698,10 +691,10 @@ collapse_vars_lvalues_drop_assign: { function f3(x) { var a = (x -= 3); return x + a; } function f4(x) { var a = (x -= 3); return x + a; } function f5(x) { var v = (e1(), e2()), c = v = --x; return x - c; } - function f6(x) { e1(), e2(); return --x - x; } - function f7(x) { var v = (e1(), e2()), c = v - x; return x - c; } - function f8(x) { var v = (e1(), e2()); return x - (v - x); } - function f9(x) { e1(); return e2() - x - x; } + function f6(x) { return e1(), e2(), --x - x; } + function f7(x) { return x - (e1(), e2() - x); } + function f8(x) { return x - (e1(), e2() - x); } + function f9(x) { return e1(), e2() - x - x; } } } @@ -1142,8 +1135,7 @@ collapse_vars_constants: { } function f3(x) { var b = x.prop; - sideeffect1(); - return b + -9; + return sideeffect1(), b + -9; } } } @@ -1193,10 +1185,10 @@ collapse_vars_short_circuit: { function f14(x,y) { var a = foo(), b = bar(); return (b - a) || (x - y); } } expect: { - function f0(x) { foo(); return bar() || x; } - function f1(x) { foo(); return bar() && x; } + function f0(x) { return foo(), bar() || x; } + function f1(x) { return foo(), bar() && x; } function f2(x) { var a = foo(), b = bar(); return x && a && b; } - function f3(x) { var a = foo(); bar(); return a && x; } + function f3(x) { var a = foo(); return bar(), a && x; } function f4(x) { var a = foo(), b = bar(); return a && x && b; } function f5(x) { var a = foo(), b = bar(); return x || a || b; } function f6(x) { var a = foo(), b = bar(); return a || x || b; } diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 074d2a65..0acc11e3 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -52,6 +52,7 @@ ifs_3_should_warn: { evaluate : true, booleans : true, side_effects : true, + passes : 3, }; input: { var x, y; @@ -98,6 +99,7 @@ ifs_5: { if_return: true, conditionals: true, comparisons: true, + passes: 2, }; input: { function f() { @@ -187,7 +189,8 @@ cond_1: { cond_2: { options = { - conditionals: true + conditionals: true, + passes: 2, }; input: { var x, FooBar; @@ -276,6 +279,7 @@ cond_7: { options = { conditionals: true, evaluate : true, + passes : 3, side_effects: true, }; input: { @@ -338,7 +342,8 @@ cond_7: { cond_7_1: { options = { conditionals: true, - evaluate : true + evaluate : true, + passes : 2, }; input: { var x; @@ -359,7 +364,8 @@ cond_8: { options = { conditionals: true, evaluate : true, - booleans : false + booleans : false, + passes : 2, }; input: { var a; @@ -443,7 +449,8 @@ cond_8b: { options = { conditionals: true, evaluate : true, - booleans : true + booleans : true, + passes : 2, }; input: { var a; @@ -526,7 +533,8 @@ cond_8c: { options = { conditionals: true, evaluate : false, - booleans : false + booleans : false, + passes : 2, }; input: { var a; diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 45b7af6e..982f466c 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -66,6 +66,7 @@ dead_code_constant_boolean_should_warn_more: { conditionals : true, evaluate : true, side_effects : true, + passes : 2, }; input: { while (!((foo && bar) || (x + "0"))) { diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 68739503..842588c4 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -696,7 +696,7 @@ in_boolean_context: { !b("foo"), !b([1, 2]), !b(/foo/), - ![1, foo()], + (foo(), !1), (foo(), !1) ); } diff --git a/test/compress/global_defs.js b/test/compress/global_defs.js index a69d031e..38bb976f 100644 --- a/test/compress/global_defs.js +++ b/test/compress/global_defs.js @@ -141,7 +141,7 @@ mixed: { } expect_warnings: [ 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:126,22]', - 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:127,22]', 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]', + 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:127,22]', ] } diff --git a/test/compress/issue-1261.js b/test/compress/issue-1261.js index a872c578..5bcad18f 100644 --- a/test/compress/issue-1261.js +++ b/test/compress/issue-1261.js @@ -152,25 +152,25 @@ should_warn: { "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:128,23]", "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:128,23]", "WARN: Boolean || always true [test/compress/issue-1261.js:129,23]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:129,23]", - "WARN: Condition always true [test/compress/issue-1261.js:129,23]", "WARN: Boolean || always true [test/compress/issue-1261.js:130,8]", - "WARN: Condition always true [test/compress/issue-1261.js:130,8]", "WARN: Boolean && always false [test/compress/issue-1261.js:131,23]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:131,23]", - "WARN: Condition always false [test/compress/issue-1261.js:131,23]", "WARN: Boolean && always false [test/compress/issue-1261.js:132,8]", - "WARN: Condition always false [test/compress/issue-1261.js:132,8]", "WARN: + in boolean context always true [test/compress/issue-1261.js:133,23]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:133,23]", - "WARN: Condition always true [test/compress/issue-1261.js:133,23]", "WARN: + in boolean context always true [test/compress/issue-1261.js:134,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:134,31]", - "WARN: Condition always true [test/compress/issue-1261.js:134,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:135,23]", "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:136,24]", "WARN: Condition always true [test/compress/issue-1261.js:136,8]", "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:137,31]", "WARN: Condition always false [test/compress/issue-1261.js:137,8]", + "WARN: Condition always true [test/compress/issue-1261.js:129,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:129,23]", + "WARN: Condition always true [test/compress/issue-1261.js:130,8]", + "WARN: Condition always false [test/compress/issue-1261.js:131,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:131,23]", + "WARN: Condition always false [test/compress/issue-1261.js:132,8]", + "WARN: Condition always true [test/compress/issue-1261.js:133,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:133,23]", + "WARN: Condition always true [test/compress/issue-1261.js:134,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:134,31]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:135,23]", ] } diff --git a/test/compress/issue-1569.js b/test/compress/issue-1569.js index 5f0bca34..697f8d00 100644 --- a/test/compress/issue-1569.js +++ b/test/compress/issue-1569.js @@ -1,5 +1,6 @@ inner_reference: { options = { + passes: 2, side_effects: true, } input: { @@ -14,6 +15,5 @@ inner_reference: { !function f(a) { return a && f(a - 1) + a; }(42); - !void 0; } } diff --git a/test/compress/issue-637.js b/test/compress/issue-637.js index 45fd2481..54c1d64c 100644 --- a/test/compress/issue-637.js +++ b/test/compress/issue-637.js @@ -2,7 +2,9 @@ wrongly_optimized: { options = { conditionals: true, booleans: true, - evaluate: true + evaluate: true, + sequences: true, + passes: 2, }; input: { function func() { @@ -16,7 +18,6 @@ wrongly_optimized: { function func() { foo(); } - // TODO: optimize to `func(), bar()` - (func(), 0) || bar(); + func(), bar(); } } diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js index 9a0b5a46..500978f4 100644 --- a/test/compress/negate-iife.js +++ b/test/compress/negate-iife.js @@ -381,6 +381,7 @@ issue_1288_side_effects: { conditionals: true, negate_iife: true, side_effects: true, + passes: 2, } input: { if (w) ; diff --git a/test/compress/numbers.js b/test/compress/numbers.js index 0b40bb9c..20868093 100644 --- a/test/compress/numbers.js +++ b/test/compress/numbers.js @@ -39,6 +39,7 @@ comparisons: { evaluate_1: { options = { evaluate: true, + passes: 2, unsafe_math: false, } input: { @@ -78,6 +79,7 @@ evaluate_1: { evaluate_2: { options = { evaluate: true, + passes: 2, unsafe_math: true, } input: { @@ -98,7 +100,7 @@ evaluate_2: { console.log( x + 1 + 2, 2 * x, - 3 + +x, + +x + 3, 1 + x + 2 + 3, 3 | x, 6 + x--, @@ -120,7 +122,7 @@ evaluate_3: { console.log(1 + Number(x) + 2); } expect: { - console.log(3 + +x); + console.log(+x + 3); } } diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index a5ab59f9..17438e58 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -53,9 +53,7 @@ reduce_vars: { console.log(-3); eval("console.log(a);"); })(eval); - (function() { - return "yes"; - })(); + "yes"; console.log(2); } } diff --git a/test/compress/transform-optimize.js b/test/compress/transform-optimize.js new file mode 100644 index 00000000..7693d219 --- /dev/null +++ b/test/compress/transform-optimize.js @@ -0,0 +1,129 @@ +booleans_evaluate: { + options = { + booleans: true, + evaluate: true, + } + input: { + console.log(typeof void 0 != "undefined"); + console.log(1 == 1, 1 === 1) + console.log(1 != 1, 1 !== 1) + } + expect: { + console.log(!1); + console.log(!0, !0); + console.log(!1, !1); + } +} + +booleans_global_defs: { + options = { + booleans: true, + evaluate: true, + global_defs: { + A: true, + }, + } + input: { + console.log(A == 1); + } + expect: { + console.log(!0); + } +} + +condition_evaluate: { + options = { + booleans: true, + dead_code: false, + evaluate: true, + loops: false, + } + input: { + while (1 === 2); + for (; 1 == true;); + if (void 0 == null); + } + expect: { + while (!1); + for (; !0;); + if (!0); + } +} + +if_else_empty_1: { + options = { + conditionals: true, + } + input: { + if ({} ? a : b); else {} + } + expect: { + !{} ? b : a; + } +} + +if_else_empty_2: { + options = { + conditionals: true, + passes: 2, + } + input: { + if ({} ? a : b); else {} + } + expect: { + !{} ? b : a; + } +} + +label_if_break_1: { + options = { + conditionals: true, + dead_code: true, + evaluate: true, + } + input: { + L: if (true) { + a; + break L; + } + } + expect: { + a; + } +} + +label_if_break_2: { + options = { + conditionals: true, + dead_code: true, + evaluate: true, + passes: 2 + } + input: { + L: if (true) { + a; + break L; + } + } + expect: { + a; + } +} + +while_if_break: { + options = { + conditionals: true, + loops: true, + passes: 2, + sequences: true, + } + input: { + while (a) { + if (b) if(c) d; + if (e) break; + } + } + expect: { + for(; a && (b && c && d, !e);); + } +} diff --git a/test/mocha/spidermonkey.js b/test/mocha/spidermonkey.js index c1294525..ad777499 100644 --- a/test/mocha/spidermonkey.js +++ b/test/mocha/spidermonkey.js @@ -4,7 +4,7 @@ var uglify = require("../../"); describe("spidermonkey export/import sanity test", function() { it("should produce a functional build when using --self with spidermonkey", function (done) { - this.timeout(20000); + this.timeout(60000); var uglifyjs = '"' + process.argv[0] + '" bin/uglifyjs'; var command = uglifyjs + " --self -cm --wrap SpiderUglify --dump-spidermonkey-ast | " +