From dd22eda888380be41f47bba1f6740b283fb099cc Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 12 Feb 2020 01:01:17 +0000 Subject: [PATCH] enhance `evaluate` (#3714) --- lib/compress.js | 93 ++++++++++++++++++++---------------- test/compress/reduce_vars.js | 9 +--- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 0fcd199e..55e40ef2 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -3073,10 +3073,10 @@ merge(Compressor.prototype, { // is returned. // They can be distinguished as constant value is never a // descendant of AST_Node. - AST_Node.DEFMETHOD("evaluate", function(compressor) { + AST_Node.DEFMETHOD("evaluate", function(compressor, ignore_side_effects) { if (!compressor.option("evaluate")) return this; var cached = []; - var val = this._eval(compressor, cached, 1); + var val = this._eval(compressor, ignore_side_effects, cached, 1); cached.forEach(function(node) { delete node._eval; }); @@ -3104,6 +3104,19 @@ merge(Compressor.prototype, { def(AST_Constant, function() { return this.value; }); + def(AST_Assign, function(compressor, ignore_side_effects, cached, depth) { + if (!ignore_side_effects) return this; + if (this.operator != "=") return this; + var node = this.right; + var value = node._eval(compressor, ignore_side_effects, cached, depth); + return value === node ? this : value; + }); + def(AST_Sequence, function(compressor, ignore_side_effects, cached, depth) { + if (!ignore_side_effects) return this; + var node = this.tail_node(); + var value = node._eval(compressor, ignore_side_effects, cached, depth); + return value === node ? this : value; + }); def(AST_Function, function(compressor) { if (compressor.option("unsafe")) { var fn = function() {}; @@ -3115,12 +3128,12 @@ merge(Compressor.prototype, { } return this; }); - def(AST_Array, function(compressor, cached, depth) { + def(AST_Array, function(compressor, ignore_side_effects, cached, depth) { if (compressor.option("unsafe")) { var elements = []; for (var i = 0; i < this.elements.length; i++) { var element = this.elements[i]; - var value = element._eval(compressor, cached, depth); + var value = element._eval(compressor, ignore_side_effects, cached, depth); if (element === value) return this; elements.push(value); } @@ -3128,7 +3141,7 @@ merge(Compressor.prototype, { } return this; }); - def(AST_Object, function(compressor, cached, depth) { + def(AST_Object, function(compressor, ignore_side_effects, cached, depth) { if (compressor.option("unsafe")) { var val = {}; for (var i = 0; i < this.properties.length; i++) { @@ -3137,14 +3150,14 @@ merge(Compressor.prototype, { if (key instanceof AST_Symbol) { key = key.name; } else if (key instanceof AST_Node) { - key = key._eval(compressor, cached, depth); + key = key._eval(compressor, ignore_side_effects, cached, depth); if (key === prop.key) return this; } if (typeof Object.prototype[key] === 'function') { return this; } if (prop.value instanceof AST_Function) continue; - val[key] = prop.value._eval(compressor, cached, depth); + val[key] = prop.value._eval(compressor, ignore_side_effects, cached, depth); if (val[key] === prop.value) return this; } return val; @@ -3152,7 +3165,7 @@ merge(Compressor.prototype, { return this; }); var non_converting_unary = makePredicate("! typeof void"); - def(AST_UnaryPrefix, function(compressor, cached, depth) { + def(AST_UnaryPrefix, function(compressor, ignore_side_effects, cached, depth) { var e = this.expression; // Function would be evaluated to an array and so typeof would // incorrectly return 'object'. Hence making is a special case. @@ -3164,7 +3177,7 @@ merge(Compressor.prototype, { return typeof function(){}; } if (!non_converting_unary[this.operator]) depth++; - var v = e._eval(compressor, cached, depth); + var v = e._eval(compressor, ignore_side_effects, cached, depth); if (v === this.expression) return this; switch (this.operator) { case "!": return !v; @@ -3187,12 +3200,12 @@ merge(Compressor.prototype, { return this; }); var non_converting_binary = makePredicate("&& || === !=="); - def(AST_Binary, function(compressor, cached, depth) { + def(AST_Binary, function(compressor, ignore_side_effects, cached, depth) { if (!non_converting_binary[this.operator]) depth++; - var left = this.left._eval(compressor, cached, depth); + var left = this.left._eval(compressor, ignore_side_effects, cached, depth); if (left === this.left) return this; if (this.operator == (left ? "||" : "&&")) return left; - var right = this.right._eval(compressor, cached, depth); + var right = this.right._eval(compressor, ignore_side_effects, cached, depth); if (right === this.right) return this; var result; switch (this.operator) { @@ -3235,14 +3248,14 @@ merge(Compressor.prototype, { return (match[1] || ".").length - 1 - (match[2] || "").slice(1); } }); - def(AST_Conditional, function(compressor, cached, depth) { - var condition = this.condition._eval(compressor, cached, depth); + def(AST_Conditional, function(compressor, ignore_side_effects, cached, depth) { + var condition = this.condition._eval(compressor, ignore_side_effects, cached, depth); if (condition === this.condition) return this; var node = condition ? this.consequent : this.alternative; - var value = node._eval(compressor, cached, depth); + var value = node._eval(compressor, ignore_side_effects, cached, depth); return value === node ? this : value; }); - def(AST_SymbolRef, function(compressor, cached, depth) { + def(AST_SymbolRef, function(compressor, ignore_side_effects, cached, depth) { var fixed = this.fixed_value(); if (!fixed) return this; var value; @@ -3250,7 +3263,7 @@ merge(Compressor.prototype, { value = fixed._eval(); } else { this._eval = return_this; - value = fixed._eval(compressor, cached, depth); + value = fixed._eval(compressor, ignore_side_effects, cached, depth); delete this._eval; if (value === fixed) return this; fixed._eval = function() { @@ -3307,11 +3320,11 @@ merge(Compressor.prototype, { ], }); var regexp_props = makePredicate("global ignoreCase multiline source"); - def(AST_PropAccess, function(compressor, cached, depth) { + def(AST_PropAccess, function(compressor, ignore_side_effects, cached, depth) { if (compressor.option("unsafe")) { var key = this.property; if (key instanceof AST_Node) { - key = key._eval(compressor, cached, depth); + key = key._eval(compressor, ignore_side_effects, cached, depth); if (key === this.property) return this; } var exp = this.expression; @@ -3321,7 +3334,7 @@ merge(Compressor.prototype, { if (!static_value || !static_value[key]) return this; val = global_objs[exp.name]; } else { - val = exp._eval(compressor, cached, depth + 1); + val = exp._eval(compressor, ignore_side_effects, cached, depth + 1); if (val == null || val === exp) return this; if (val instanceof RegExp) { if (!regexp_props[key]) return this; @@ -3340,7 +3353,7 @@ merge(Compressor.prototype, { } return this; }); - def(AST_Call, function(compressor, cached, depth) { + def(AST_Call, function(compressor, ignore_side_effects, cached, depth) { var exp = this.expression; var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp; if (fn instanceof AST_Lambda) { @@ -3361,14 +3374,14 @@ merge(Compressor.prototype, { }); }); fn.evaluating = true; - var val = stat.value._eval(compressor, cached, depth); + var val = stat.value._eval(compressor, ignore_side_effects, cached, depth); delete fn.evaluating; if (val === stat.value) return this; return val; } else if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { var key = exp.property; if (key instanceof AST_Node) { - key = key._eval(compressor, cached, depth); + key = key._eval(compressor, ignore_side_effects, cached, depth); if (key === exp.property) return this; } var val; @@ -3378,7 +3391,7 @@ merge(Compressor.prototype, { if (!static_fn || !static_fn[key]) return this; val = global_objs[e.name]; } else { - val = e._eval(compressor, cached, depth + 1); + val = e._eval(compressor, ignore_side_effects, cached, depth + 1); if (val == null || val === e) return this; var native_fn = native_fns[val.constructor.name]; if (!native_fn || !native_fn[key]) return this; @@ -3403,7 +3416,7 @@ merge(Compressor.prototype, { var values = []; for (var i = 0; i < args.length; i++) { var arg = args[i]; - var value = arg._eval(compressor, cached, depth); + var value = arg._eval(compressor, ignore_side_effects, cached, depth); if (arg === value) return; values.push(value); } @@ -4478,7 +4491,7 @@ merge(Compressor.prototype, { node.in_bool = true; var value = node.value; if (value) { - var ev = value.is_truthy() || value.tail_node().evaluate(compressor); + var ev = value.is_truthy() || value.evaluate(compressor, true); if (!ev) { value = value.drop_side_effect_free(compressor); node.value = value ? make_sequence(node.value, [ @@ -4878,7 +4891,7 @@ merge(Compressor.prototype, { OPT(AST_Do, function(self, compressor) { if (!compressor.option("loops")) return self; - var cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); + var cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true); if (!(cond instanceof AST_Node)) { if (cond) return make_node(AST_For, self, { body: make_node(AST_BlockStatement, self.body, { @@ -5008,16 +5021,14 @@ merge(Compressor.prototype, { } if (self.condition) { var cond = self.condition.evaluate(compressor); - 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); - } - } if (cond instanceof AST_Node) { - cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); + cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true); + } else 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); } if (!cond) { if (compressor.option("dead_code")) { @@ -5109,7 +5120,7 @@ merge(Compressor.prototype, { } if (compressor.option("dead_code")) { if (cond instanceof AST_Node) { - cond = self.condition.is_truthy() || self.condition.tail_node().evaluate(compressor); + cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true); } if (!cond) { AST_Node.warn("Condition always false [{file}:{line},{col}]", self.condition.start); @@ -5255,7 +5266,7 @@ merge(Compressor.prototype, { } if (!compressor.option("dead_code")) return self; if (value instanceof AST_Node) { - value = self.expression.tail_node().evaluate(compressor); + value = self.expression.evaluate(compressor, true); } var decl = []; var body = []; @@ -5277,7 +5288,7 @@ merge(Compressor.prototype, { eliminate_branch(branch, body[body.length - 1]); continue; } - if (exp instanceof AST_Node) exp = branch.expression.tail_node().evaluate(compressor); + if (exp instanceof AST_Node) exp = branch.expression.evaluate(compressor, true); if (exp === value) { exact_match = branch; if (default_branch) { @@ -6461,7 +6472,7 @@ merge(Compressor.prototype, { } // (x || false) && y => x ? y : false if (self.left.operator == "||") { - var lr = self.left.right.tail_node().evaluate(compressor); + var lr = self.left.right.evaluate(compressor, true); if (!lr) return make_node(AST_Conditional, self, { condition: self.left.left, consequent: self.right, @@ -6495,7 +6506,7 @@ merge(Compressor.prototype, { } // x && true || y => x ? true : y if (self.left.operator == "&&") { - var lr = self.left.right.is_truthy() || self.left.right.tail_node().evaluate(compressor); + var lr = self.left.right.is_truthy() || self.left.right.evaluate(compressor, true); if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, { condition: self.left.left, consequent: self.left.right, diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 3331732a..6db0a477 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2071,13 +2071,8 @@ issue_1670_6: { } expect: { (function(a) { - switch (1) { - case a = 1: - console.log(a); - break; - default: - console.log(2); - } + a = 1; + console.log(a); })(1); } expect_stdout: "1" -- 2.34.1