From 482e1baea3672fc1bc39594a5615487d05a15f5f Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 29 May 2019 01:21:08 +0800 Subject: [PATCH] enhance `assignments` & `unused` (#3428) closes #3427 --- lib/compress.js | 77 ++++++++++++++++++++++++----------- test/compress/assignment.js | 16 ++++++++ test/compress/drop-unused.js | 34 ++++++++++++++++ test/compress/pure_getters.js | 20 +++++++++ 4 files changed, 123 insertions(+), 24 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index e99ce50888..7bbb5a2005 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2282,8 +2282,7 @@ merge(Compressor.prototype, { // returns true if this node may be null, undefined or contain `AST_Accessor` (function(def) { AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) { - return !compressor.option("pure_getters") - || this._dot_throw(compressor); + return !compressor.option("pure_getters") || this._dot_throw(compressor); }); function is_strict(compressor) { return /strict/.test(compressor.option("pure_getters")); @@ -2291,7 +2290,15 @@ merge(Compressor.prototype, { def(AST_Node, is_strict); def(AST_Array, return_false); def(AST_Assign, function(compressor) { - return this.operator == "=" && this.right._dot_throw(compressor); + if (this.operator != "=") return false; + var rhs = this.right; + if (!rhs._dot_throw(compressor)) return false; + var sym = this.left; + if (!(sym instanceof AST_SymbolRef)) return true; + if (rhs instanceof AST_Binary && rhs.operator == "||" && sym.name == rhs.left.name) { + return rhs.right._dot_throw(compressor); + } + return true; }); def(AST_Binary, function(compressor) { return lazy_op[this.operator] && (this.left._dot_throw(compressor) || this.right._dot_throw(compressor)); @@ -3618,7 +3625,7 @@ merge(Compressor.prototype, { var value = null; if (node instanceof AST_Assign) { if (!in_use || node.left === sym && def.id in fixed_ids && fixed_ids[def.id] !== node) { - value = node.right; + value = get_rhs(node); } } else if (!in_use) { value = make_node(AST_Number, node, { @@ -3843,6 +3850,15 @@ merge(Compressor.prototype, { } } + function get_rhs(assign) { + var rhs = assign.right; + if (!assign.write_only) return rhs; + if (!(rhs instanceof AST_Binary && lazy_op[rhs.operator])) return rhs; + var sym = assign.left; + if (!(sym instanceof AST_SymbolRef) || sym.name != rhs.left.name) return rhs; + return rhs.right; + } + function scan_ref_scoped(node, descend) { var node_def, props = [], sym = assign_as_unused(node, props); if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) { @@ -3850,9 +3866,10 @@ merge(Compressor.prototype, { prop.walk(tw); }); if (node instanceof AST_Assign) { - node.right.walk(tw); + var right = get_rhs(node); + right.walk(tw); if (node.left === sym) { - if (!node_def.chained && sym.fixed_value(true) === node.right) { + if (!node_def.chained && sym.fixed_value(true) === right) { fixed_ids[node_def.id] = node; } if (!node.write_only) { @@ -4163,12 +4180,14 @@ merge(Compressor.prototype, { }); def(AST_Assign, function(compressor) { var left = this.left; - if (left.has_side_effects(compressor) - || compressor.has_directive("use strict") - && left instanceof AST_PropAccess - && left.expression.is_constant()) { - return this; + if (left instanceof AST_PropAccess) { + var expr = left.expression; + if (expr instanceof AST_Assign && !expr.may_throw_on_access(compressor)) { + expr.write_only = true; + } + if (compressor.has_directive("use strict") && expr.is_constant()) return this; } + if (left.has_side_effects(compressor)) return this; this.write_only = true; if (root_expr(left).is_constant_expression(compressor.find_parent(AST_Scope))) { return this.right.drop_side_effect_free(compressor); @@ -4243,8 +4262,9 @@ merge(Compressor.prototype, { }); def(AST_Constant, return_null); def(AST_Dot, function(compressor, first_in_statement) { - if (this.expression.may_throw_on_access(compressor)) return this; - return this.expression.drop_side_effect_free(compressor, first_in_statement); + var expr = this.expression; + if (expr.may_throw_on_access(compressor)) return this; + return expr.drop_side_effect_free(compressor, first_in_statement); }); def(AST_Function, function(compressor) { return this.name && compressor.option("ie8") ? this : null; @@ -5556,20 +5576,29 @@ merge(Compressor.prototype, { self.right = tmp; } } - if (commutativeOperators[self.operator]) { - if (self.right.is_constant() - && !self.left.is_constant()) { - // if right is a constant, whatever side effects the - // left side might have could not influence the - // result. hence, force switch. - - if (!(self.left instanceof AST_Binary - && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) { - reverse(); - } + if (commutativeOperators[self.operator] && self.right.is_constant() && !self.left.is_constant()) { + // if right is a constant, whatever side effects the + // left side might have could not influence the + // result. hence, force switch. + if (!(self.left instanceof AST_Binary + && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) { + reverse(); } } self = self.lift_sequences(compressor); + if (compressor.option("assignments") && lazy_op[self.operator]) { + var assign = self.right; + // a || (a = x) => a = a || x + // a && (a = x) => a = a && x + if (self.left instanceof AST_SymbolRef + && assign instanceof AST_Assign + && assign.operator == "=" + && self.left.equivalent_to(assign.left)) { + self.right = assign.right; + assign.right = self; + return assign; + } + } if (compressor.option("comparisons")) switch (self.operator) { case "===": case "!==": diff --git a/test/compress/assignment.js b/test/compress/assignment.js index ece0185d98..ecd0ac4a41 100644 --- a/test/compress/assignment.js +++ b/test/compress/assignment.js @@ -311,3 +311,19 @@ issue_3375: { } expect_stdout: "string" } + +issue_3427: { + options = { + assignments: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + (function() { + var a; + a || (a = {}); + })(); + } + expect: {} +} diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index d7f3cf76d3..6ed193e9f0 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -2028,3 +2028,37 @@ issue_3375: { } expect_stdout: "0 0" } + +issue_3427_1: { + options = { + sequences: true, + side_effects: true, + unused: true, + } + input: { + (function() { + var a; + a = a || {}; + })(); + } + expect: {} +} + +issue_3427_2: { + options = { + unused: true, + } + input: { + (function() { + var s = "PASS"; + console.log(s = s || "FAIL"); + })(); + } + expect: { + (function() { + var s = "PASS"; + console.log(s = s || "FAIL"); + })(); + } + expect_stdout: "PASS" +} diff --git a/test/compress/pure_getters.js b/test/compress/pure_getters.js index b6e42f9da4..54c89c6d6f 100644 --- a/test/compress/pure_getters.js +++ b/test/compress/pure_getters.js @@ -1187,3 +1187,23 @@ drop_arguments: { } expect_stdout: "PASS" } + +issue_3427: { + options = { + assignments: true, + collapse_vars: true, + inline: true, + pure_getters: "strict", + sequences: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a; + (function(b) { + b.p = 42; + })(a || (a = {})); + } + expect: {} +}