From c56d89f804e852f8678fa7557f781de893c138bf Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 25 Apr 2019 02:42:54 +0800 Subject: [PATCH] enhance `unsafe` (#3382) --- lib/compress.js | 116 +++++++++++++++++++++++++++----------- test/compress/evaluate.js | 4 +- 2 files changed, 86 insertions(+), 34 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 71081350..0a195940 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2532,6 +2532,20 @@ merge(Compressor.prototype, { return this.operator == "+" && (this.left.is_string(compressor) || this.right.is_string(compressor)); }); + var fn = makePredicate([ + "charAt", + "substr", + "substring", + "toLowerCase", + "toString", + "toUpperCase", + "trim", + ]); + def(AST_Call, function(compressor) { + if (!compressor.option("unsafe")) return false; + var exp = this.expression; + return exp instanceof AST_Dot && fn[exp.property]; + }); def(AST_Conditional, function(compressor) { return this.consequent.is_string(compressor) && this.alternative.is_string(compressor); }); @@ -2539,6 +2553,9 @@ merge(Compressor.prototype, { return this.tail_node().is_string(compressor); }); def(AST_String, return_true); + def(AST_Sub, function(compressor) { + return this.expression.is_string(compressor) && this.property instanceof AST_Number; + }); def(AST_SymbolRef, function(compressor) { var fixed = this.fixed_value(); if (!fixed) return false; @@ -3127,25 +3144,31 @@ merge(Compressor.prototype, { return this.pure || !compressor.pure_funcs(this); }); AST_Node.DEFMETHOD("is_call_pure", return_false); - AST_Dot.DEFMETHOD("is_call_pure", function(compressor) { - if (!compressor.option("unsafe")) return; - var expr = this.expression; + AST_Call.DEFMETHOD("is_call_pure", function(compressor) { + if (!compressor.option("unsafe")) return false; + var dot = this.expression; + if (!(dot instanceof AST_Dot)) return false; + var exp = dot.expression; var map; - if (expr instanceof AST_Array) { + var prop = dot.property; + if (exp instanceof AST_Array) { map = native_fns.Array; - } else if (expr.is_boolean(compressor)) { + } else if (exp.is_boolean(compressor)) { map = native_fns.Boolean; - } else if (expr.is_number(compressor)) { + } else if (exp.is_number(compressor)) { map = native_fns.Number; - } else if (expr instanceof AST_RegExp) { + } else if (exp instanceof AST_RegExp) { map = native_fns.RegExp; - } else if (expr.is_string(compressor)) { - if (this.property == "replace") return false; + } else if (exp.is_string(compressor)) { map = native_fns.String; - } else if (!this.may_throw_on_access(compressor)) { + if (prop == "replace") { + var arg = this.args[1]; + if (arg && !arg.is_string(compressor)) return false; + } + } else if (!dot.may_throw_on_access(compressor)) { map = native_fns.Object; } - return map && map[this.property]; + return map && map[prop]; }); // determine if expression has side effects @@ -3170,8 +3193,7 @@ merge(Compressor.prototype, { }); def(AST_Call, function(compressor) { if (!this.is_expr_pure(compressor) - && (!this.expression.is_call_pure(compressor) - || this.expression.has_side_effects(compressor))) { + && (!this.is_call_pure(compressor) || this.expression.has_side_effects(compressor))) { return true; } return any(this.args, compressor); @@ -4162,16 +4184,15 @@ merge(Compressor.prototype, { }); def(AST_Call, function(compressor, first_in_statement) { if (!this.is_expr_pure(compressor)) { - if (this.expression.is_call_pure(compressor)) { + var exp = this.expression; + if (this.is_call_pure(compressor)) { var exprs = this.args.slice(); - exprs.unshift(this.expression.expression); + exprs.unshift(exp.expression); exprs = trim(exprs, compressor, first_in_statement); return exprs && make_sequence(this, exprs); } - if (this.expression instanceof AST_Function - && (!this.expression.name || !this.expression.name.definition().references.length)) { + if (exp instanceof AST_Function && (!exp.name || !exp.name.definition().references.length)) { var node = this.clone(); - var exp = node.expression; exp.process_expression(false, compressor); exp.walk(new TreeWalker(function(node) { if (node instanceof AST_Return && node.value) { @@ -4963,15 +4984,21 @@ merge(Compressor.prototype, { } 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); - } + if (self.args.length < 2) { + var node = make_node(AST_Sub, self, { + expression: exp.expression, + property: self.args.length ? make_node(AST_Binary, self.args[0], { + operator: "|", + left: make_node(AST_Number, self, { + value: 0 + }), + right: self.args[0] + }) : make_node(AST_Number, self, { + value: 0 + }) + }); + node.is_string = return_true; + return node.optimize(compressor); } break; case "apply": @@ -5610,7 +5637,8 @@ merge(Compressor.prototype, { } break; } - if (compressor.option("booleans") && self.operator == "+" && compressor.in_boolean_context()) { + if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) { + case "+": var ll = self.left.evaluate(compressor); var rr = self.right.evaluate(compressor); if (ll && typeof ll == "string") { @@ -5627,6 +5655,20 @@ merge(Compressor.prototype, { make_node(AST_True, self) ]).optimize(compressor); } + break; + case "==": + if (self.left instanceof AST_String && self.left.getValue() == "" && self.right.is_string(compressor)) { + return make_node(AST_UnaryPrefix, self, { + operator: "!", + expression: self.right + }).optimize(compressor); + } + break; + case "!=": + if (self.left instanceof AST_String && self.left.getValue() == "" && self.right.is_string(compressor)) { + return self.right.optimize(compressor); + } + break; } if (compressor.option("comparisons") && self.is_boolean(compressor)) { if (!(compressor.parent() instanceof AST_Binary) @@ -5646,12 +5688,12 @@ merge(Compressor.prototype, { if (self.right instanceof AST_String && self.right.getValue() == "" && self.left.is_string(compressor)) { - return self.left; + return self.left.optimize(compressor); } if (self.left instanceof AST_String && self.left.getValue() == "" && self.right.is_string(compressor)) { - return self.right; + return self.right.optimize(compressor); } if (self.left instanceof AST_Binary && self.left.operator == "+" @@ -5659,7 +5701,7 @@ merge(Compressor.prototype, { && self.left.left.getValue() == "" && self.right.is_string(compressor)) { self.left = self.left.right; - return self.transform(compressor); + return self.optimize(compressor); } } if (compressor.option("evaluate")) { @@ -5918,6 +5960,16 @@ merge(Compressor.prototype, { && self.right instanceof AST_Call && self.right.expression instanceof AST_Dot && indexFns[self.right.expression.property]) { + if (compressor.option("booleans") + && (self.operator == "==" || self.operator == "!=") + && self.left instanceof AST_Number + && self.left.getValue() == 0 + && compressor.in_boolean_context()) { + return (self.operator == "==" ? make_node(AST_UnaryPrefix, self, { + operator: "!", + expression: self.right + }) : self.right).optimize(compressor); + } if (compressor.option("comparisons") && is_indexOf_match_pattern()) { var node = make_node(AST_UnaryPrefix, self, { operator: "!", @@ -6229,7 +6281,7 @@ merge(Compressor.prototype, { if (parent instanceof AST_Exit) { if (in_try(level, parent)) break; if (is_reachable(def.scope, [ def ])) break; - if (self.operator == "=") return self.right; + if (self.operator == "=") return self.right.optimize(compressor); def.fixed = false; return make_node(AST_Binary, self, { operator: self.operator.slice(0, -1), diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 5b16824b..89bfe2a8 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -820,8 +820,8 @@ unsafe_charAt_noop: { } expect: { console.log( - s.charAt(0), - "string".charAt(x), + s[0], + "string"[0 | x], (typeof x)[0] ); } -- 2.34.1