enhance `unsafe` (#3382)
authorAlex Lam S.L <alexlamsl@gmail.com>
Wed, 24 Apr 2019 18:42:54 +0000 (02:42 +0800)
committerGitHub <noreply@github.com>
Wed, 24 Apr 2019 18:42:54 +0000 (02:42 +0800)
lib/compress.js
test/compress/evaluate.js

index 7108135..0a19594 100644 (file)
@@ -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),
index 5b16824..89bfe2a 100644 (file)
@@ -820,8 +820,8 @@ unsafe_charAt_noop: {
     }
     expect: {
         console.log(
-            s.charAt(0),
-            "string".charAt(x),
+            s[0],
+            "string"[0 | x],
             (typeof x)[0]
         );
     }