inlining of static methods & constants (#2211)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 6 Jul 2017 21:35:32 +0000 (05:35 +0800)
committerGitHub <noreply@github.com>
Thu, 6 Jul 2017 21:35:32 +0000 (05:35 +0800)
- guard by `unsafe`
- support `Array`, `Math`, `Number`, `Object` & `String`

fixes #2207

lib/compress.js
test/compress/evaluate.js

index 76eb691..e634924 100644 (file)
@@ -1718,6 +1718,40 @@ merge(Compressor.prototype, {
             this._eval = fixed._eval;
             return value;
         });
+        var global_objs = {
+            Array: Array,
+            Boolean: Boolean,
+            Math: Math,
+            Number: Number,
+            RegExp: RegExp,
+            Object: Object,
+            String: String,
+        };
+        function convert_to_predicate(obj) {
+            for (var key in obj) {
+                obj[key] = makePredicate(obj[key]);
+            }
+        }
+        var static_values = {
+            Math: [
+                "E",
+                "LN10",
+                "LN2",
+                "LOG2E",
+                "LOG10E",
+                "PI",
+                "SQRT1_2",
+                "SQRT2",
+            ],
+            Number: [
+                "MAX_VALUE",
+                "MIN_VALUE",
+                "NaN",
+                "NEGATIVE_INFINITY",
+                "POSITIVE_INFINITY",
+            ],
+        };
+        convert_to_predicate(static_values);
         def(AST_PropAccess, function(compressor){
             if (compressor.option("unsafe")) {
                 var key = this.property;
@@ -1725,11 +1759,16 @@ merge(Compressor.prototype, {
                     key = ev(key, compressor);
                     if (key === this.property) return this;
                 }
-                var val = ev(this.expression, compressor);
-                if (val === this.expression) return this;
-                if (val && HOP(val, key)) {
-                    return val[key];
+                var exp = this.expression;
+                var val;
+                if (exp instanceof AST_SymbolRef && exp.undeclared()) {
+                    if (!(static_values[exp.name] || return_false)(key)) return this;
+                    val = global_objs[exp.name];
+                } else {
+                    val = ev(exp, compressor);
+                    if (!val || val === exp || !HOP(val, key)) return this;
                 }
+                return val[key];
             }
             return this;
         });
@@ -1739,22 +1778,22 @@ merge(Compressor.prototype, {
             "valueOf",
         ];
         var native_fns = {
-            Array: makePredicate([
+            Array: [
                 "indexOf",
                 "join",
                 "lastIndexOf",
                 "slice",
-            ].concat(object_fns)),
-            Boolean: makePredicate(object_fns),
-            Number: makePredicate([
+            ].concat(object_fns),
+            Boolean: object_fns,
+            Number: [
                 "toExponential",
                 "toFixed",
                 "toPrecision",
-            ].concat(object_fns)),
-            RegExp: makePredicate([
+            ].concat(object_fns),
+            RegExp: [
                 "test",
-            ].concat(object_fns)),
-            String: makePredicate([
+            ].concat(object_fns),
+            String: [
                 "charAt",
                 "charCodeAt",
                 "concat",
@@ -1769,8 +1808,45 @@ merge(Compressor.prototype, {
                 "substr",
                 "substring",
                 "trim",
-            ].concat(object_fns)),
+            ].concat(object_fns),
+        };
+        convert_to_predicate(native_fns);
+        var static_fns = {
+            Array: [
+                "isArray",
+            ],
+            Math: [
+                "abs",
+                "acos",
+                "asin",
+                "atan",
+                "ceil",
+                "cos",
+                "exp",
+                "floor",
+                "log",
+                "round",
+                "sin",
+                "sqrt",
+                "tan",
+                "atan2",
+                "pow",
+                "max",
+                "min"
+            ],
+            Number: [
+                "isFinite",
+                "isNaN",
+            ],
+            Object: [
+                "keys",
+                "getOwnPropertyNames",
+            ],
+            String: [
+                "fromCharCode",
+            ],
         };
+        convert_to_predicate(static_fns);
         def(AST_Call, function(compressor){
             var exp = this.expression;
             if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
@@ -1779,18 +1855,23 @@ merge(Compressor.prototype, {
                     key = ev(key, compressor);
                     if (key === exp.property) return this;
                 }
-                var val = ev(exp.expression, compressor);
-                if (val === exp.expression) return this;
-                if ((val && native_fns[val.constructor.name] || return_false)(key)) {
-                    var args = [];
-                    for (var i = 0, len = this.args.length; i < len; i++) {
-                        var arg = this.args[i];
-                        var value = ev(arg, compressor);
-                        if (arg === value) return this;
-                        args.push(value);
-                    }
-                    return val[key].apply(val, args);
+                var val;
+                var e = exp.expression;
+                if (e instanceof AST_SymbolRef && e.undeclared()) {
+                    if (!(static_fns[e.name] || return_false)(key)) return this;
+                    val = global_objs[e.name];
+                } else {
+                    val = ev(e, compressor);
+                    if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this;
+                }
+                var args = [];
+                for (var i = 0, len = this.args.length; i < len; i++) {
+                    var arg = this.args[i];
+                    var value = ev(arg, compressor);
+                    if (arg === value) return this;
+                    args.push(value);
                 }
+                return val[key].apply(val, args);
             }
             return this;
         });
index 69ea8c1..38e9cdc 100644 (file)
@@ -1085,3 +1085,75 @@ string_charCodeAt: {
     }
     expect_stdout: "NaN"
 }
+
+issue_2207_1: {
+    options = {
+        evaluate: true,
+        unsafe: true,
+    }
+    input: {
+        console.log(String.fromCharCode(65));
+        console.log(Math.max(3, 6, 2, 7, 3, 4));
+        console.log(Math.cos(1.2345));
+        console.log(Math.cos(1.2345) - Math.sin(4.321));
+        console.log(Math.pow(Math.PI, Math.E - Math.LN10));
+    }
+    expect: {
+        console.log("A");
+        console.log(7);
+        console.log(Math.cos(1.2345));
+        console.log(1.2543732512566947);
+        console.log(1.6093984514472044);
+    }
+    expect_stdout: true
+}
+
+issue_2207_2: {
+    options = {
+        evaluate: true,
+        unsafe: true,
+    }
+    input: {
+        console.log(Math.E);
+        console.log(Math.LN10);
+        console.log(Math.LN2);
+        console.log(Math.LOG2E);
+        console.log(Math.LOG10E);
+        console.log(Math.PI);
+        console.log(Math.SQRT1_2);
+        console.log(Math.SQRT2);
+    }
+    expect: {
+        console.log(Math.E);
+        console.log(Math.LN10);
+        console.log(Math.LN2);
+        console.log(Math.LOG2E);
+        console.log(Math.LOG10E);
+        console.log(Math.PI);
+        console.log(Math.SQRT1_2);
+        console.log(Math.SQRT2);
+    }
+    expect_stdout: true
+}
+
+issue_2207_3: {
+    options = {
+        evaluate: true,
+        unsafe: true,
+    }
+    input: {
+        console.log(Number.MAX_VALUE);
+        console.log(Number.MIN_VALUE);
+        console.log(Number.NaN);
+        console.log(Number.NEGATIVE_INFINITY);
+        console.log(Number.POSITIVE_INFINITY);
+    }
+    expect: {
+        console.log(Number.MAX_VALUE);
+        console.log(5e-324);
+        console.log(NaN);
+        console.log(-1/0);
+        console.log(1/0);
+    }
+    expect_stdout: true
+}