[Pkg-javascript-commits] [uglifyjs] 36/228: fix `pure_funcs` & improve `side_effects` - only drops side-effect-free arguments - drop side-effect-free parts with discarded value from `AST_Seq` & `AST_SimpleStatement`

Jonas Smedegaard dr at jones.dk
Sat Apr 15 14:25:14 UTC 2017


This is an automated email from the git hooks/post-receive script.

js pushed a commit to branch master
in repository uglifyjs.

commit 26fbeece1c385a0e63efe3a6683af8459f4e495a
Author: alexlamsl <alexlamsl at gmail.com>
Date:   Mon Feb 20 01:46:59 2017 +0800

    fix `pure_funcs` & improve `side_effects`
    - only drops side-effect-free arguments
    - drop side-effect-free parts with discarded value from `AST_Seq` & `AST_SimpleStatement`
    
    closes #1494
---
 lib/compress.js              | 178 +++++++++++++++++++++++---
 test/compress/drop-unused.js |  58 +++++++++
 test/compress/pure_funcs.js  | 295 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 516 insertions(+), 15 deletions(-)

diff --git a/lib/compress.js b/lib/compress.js
index 237af72..4dfcdcf 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -83,6 +83,14 @@ function Compressor(options, false_by_default) {
         global_defs   : {},
         passes        : 1,
     }, true);
+    var pure_funcs = this.options["pure_funcs"];
+    if (typeof pure_funcs == "function") {
+        this.pure_funcs = pure_funcs;
+    } else {
+        this.pure_funcs = pure_funcs ? function(node) {
+            return pure_funcs.indexOf(node.expression.print_to_string()) < 0;
+        } : return_true;
+    }
     var top_retain = this.options["top_retain"];
     if (top_retain instanceof RegExp) {
         this.top_retain = function(def) {
@@ -304,6 +312,13 @@ merge(Compressor.prototype, {
         }
     }
 
+    function is_iife_call(node) {
+        if (node instanceof AST_Call && !(node instanceof AST_New)) {
+            return node.expression instanceof AST_Function || is_iife_call(node.expression);
+        }
+        return false;
+    }
+
     function tighten_body(statements, compressor) {
         var CHANGED, max_iter = 10;
         do {
@@ -1354,10 +1369,12 @@ merge(Compressor.prototype, {
         def(AST_This, return_false);
 
         def(AST_Call, function(compressor){
-            var pure = compressor.option("pure_funcs");
-            if (!pure) return true;
-            if (typeof pure == "function") return pure(this);
-            return pure.indexOf(this.expression.print_to_string()) < 0;
+            if (compressor.pure_funcs(this)) return true;
+            for (var i = this.args.length; --i >= 0;) {
+                if (this.args[i].has_side_effects(compressor))
+                    return true;
+            }
+            return false;
         });
 
         def(AST_Block, function(compressor){
@@ -1855,12 +1872,151 @@ merge(Compressor.prototype, {
         return self;
     });
 
+    // drop_side_effect_free()
+    // remove side-effect-free parts which only affects return value
+    (function(def){
+        function return_this() {
+            return this;
+        }
+
+        function return_null() {
+            return null;
+        }
+
+        // Drop side-effect-free elements from an array of expressions.
+        // Returns an array of expressions with side-effects or null
+        // if all elements were dropped. Note: original array may be
+        // returned if nothing changed.
+        function trim(nodes, compressor, first_in_statement) {
+            var ret = [], changed = false;
+            for (var i = 0, ii = nodes.length; i < ii; i++) {
+                var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
+                changed |= node !== nodes[i];
+                if (node) {
+                    ret.push(node);
+                    first_in_statement = false;
+                }
+            }
+            return changed ? ret.length ? ret : null : nodes;
+        }
+
+        def(AST_Node, return_this);
+        def(AST_Constant, return_null);
+        def(AST_This, return_null);
+        def(AST_Call, function(compressor, first_in_statement){
+            if (compressor.pure_funcs(this)) return this;
+            var args = trim(this.args, compressor, first_in_statement);
+            return args && AST_Seq.from_array(args);
+        });
+        def(AST_Function, return_null);
+        def(AST_Binary, function(compressor, first_in_statement){
+            var right = this.right.drop_side_effect_free(compressor);
+            if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement);
+            switch (this.operator) {
+              case "&&":
+              case "||":
+                var node = this.clone();
+                node.right = right;
+                return node;
+              default:
+                var left = this.left.drop_side_effect_free(compressor, first_in_statement);
+                if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement);
+                return make_node(AST_Seq, this, {
+                    car: left,
+                    cdr: right
+                });
+            }
+        });
+        def(AST_Assign, return_this);
+        def(AST_Conditional, function(compressor){
+            var consequent = this.consequent.drop_side_effect_free(compressor);
+            var alternative = this.alternative.drop_side_effect_free(compressor);
+            if (consequent === this.consequent && alternative === this.alternative) return this;
+            if (!consequent) return alternative ? make_node(AST_Binary, this, {
+                operator: "||",
+                left: this.condition,
+                right: alternative
+            }) : this.condition.drop_side_effect_free(compressor);
+            if (!alternative) return make_node(AST_Binary, this, {
+                operator: "&&",
+                left: this.condition,
+                right: consequent
+            });
+            var node = this.clone();
+            node.consequent = consequent;
+            node.alternative = alternative;
+            return node;
+        });
+        def(AST_Unary, function(compressor, first_in_statement){
+            switch (this.operator) {
+              case "delete":
+              case "++":
+              case "--":
+                return this;
+              case "typeof":
+                if (this.expression instanceof AST_SymbolRef) return null;
+              default:
+                if (first_in_statement && is_iife_call(this.expression)) return this;
+                return this.expression.drop_side_effect_free(compressor, first_in_statement);
+            }
+        });
+        def(AST_SymbolRef, function() {
+            return this.undeclared() ? this : null;
+        });
+        def(AST_Object, function(compressor, first_in_statement){
+            var values = trim(this.properties, compressor, first_in_statement);
+            return values && AST_Seq.from_array(values);
+        });
+        def(AST_ObjectProperty, function(compressor, first_in_statement){
+            return this.value.drop_side_effect_free(compressor, first_in_statement);
+        });
+        def(AST_Array, function(compressor, first_in_statement){
+            var values = trim(this.elements, compressor, first_in_statement);
+            return values && AST_Seq.from_array(values);
+        });
+        def(AST_Dot, function(compressor, first_in_statement){
+            if (!compressor.option("pure_getters")) return this;
+            return this.expression.drop_side_effect_free(compressor, first_in_statement);
+        });
+        def(AST_Sub, function(compressor, first_in_statement){
+            if (!compressor.option("pure_getters")) return this;
+            var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
+            if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
+            var property = this.property.drop_side_effect_free(compressor);
+            if (!property) return expression;
+            return make_node(AST_Seq, this, {
+                car: expression,
+                cdr: property
+            });
+        });
+        def(AST_Seq, function(compressor){
+            var cdr = this.cdr.drop_side_effect_free(compressor);
+            if (cdr === this.cdr) return this;
+            if (!cdr) return this.car;
+            return make_node(AST_Seq, this, {
+                car: this.car,
+                cdr: cdr
+            });
+        });
+    })(function(node, func){
+        node.DEFMETHOD("drop_side_effect_free", func);
+    });
+
     OPT(AST_SimpleStatement, function(self, compressor){
         if (compressor.option("side_effects")) {
-            if (!self.body.has_side_effects(compressor)) {
+            var body = self.body;
+            if (!body.has_side_effects(compressor)) {
                 compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
                 return make_node(AST_EmptyStatement, self);
             }
+            var node = body.drop_side_effect_free(compressor, true);
+            if (!node) {
+                compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start);
+                return make_node(AST_EmptyStatement, self);
+            }
+            if (node !== body) {
+                return make_node(AST_SimpleStatement, self, { body: node });
+            }
         }
         return self;
     });
@@ -2435,13 +2591,6 @@ merge(Compressor.prototype, {
             return self.negate(compressor, true);
         }
         return self;
-
-        function is_iife_call(node) {
-            if (node instanceof AST_Call && !(node instanceof AST_New)) {
-                return node.expression instanceof AST_Function || is_iife_call(node.expression);
-            }
-            return false;
-        }
     });
 
     OPT(AST_New, function(self, compressor){
@@ -2464,9 +2613,8 @@ merge(Compressor.prototype, {
     OPT(AST_Seq, function(self, compressor){
         if (!compressor.option("side_effects"))
             return self;
-        if (!self.car.has_side_effects(compressor)) {
-            return maintain_this_binding(compressor.parent(), self, self.cdr);
-        }
+        self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor));
+        if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr);
         if (compressor.option("cascade")) {
             if (self.car instanceof AST_Assign
                 && !self.car.left.has_side_effects(compressor)) {
diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js
index 5a09c6c..f5a88f2 100644
--- a/test/compress/drop-unused.js
+++ b/test/compress/drop-unused.js
@@ -590,3 +590,61 @@ drop_fnames: {
         }
     }
 }
+
+global_var: {
+    options = {
+        side_effects: true,
+        unused: true,
+    }
+    input: {
+        var a;
+        function foo(b) {
+            a;
+            b;
+            c;
+            typeof c === "undefined";
+            c + b + a;
+            b && b.ar();
+            return b;
+        }
+    }
+    expect: {
+        var a;
+        function foo(b) {
+            c;
+            c;
+            b && b.ar();
+            return b;
+        }
+    }
+}
+
+iife: {
+    options = {
+        side_effects: true,
+        unused: true,
+    }
+    input: {
+        function f() {
+            var a;
+            ~function() {}(b);
+        }
+    }
+    expect: {
+        function f() {
+            ~function() {}(b);
+        }
+    }
+}
+
+drop_value: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        (1, [2, foo()], 3, {a:1, b:bar()});
+    }
+    expect: {
+        foo(), bar();
+    }
+}
diff --git a/test/compress/pure_funcs.js b/test/compress/pure_funcs.js
new file mode 100644
index 0000000..3cc529a
--- /dev/null
+++ b/test/compress/pure_funcs.js
@@ -0,0 +1,295 @@
+array: {
+    options = {
+        pure_funcs: [ "Math.floor" ],
+        side_effects: true,
+    }
+    input: {
+        var a;
+        function f(b) {
+            Math.floor(a / b);
+            Math.floor(c / b);
+        }
+    }
+    expect: {
+        var a;
+        function f(b) {
+            c;
+        }
+    }
+}
+
+func: {
+    options = {
+        pure_funcs: function(node) {
+            return !~node.args[0].print_to_string().indexOf("a");
+        },
+        side_effects: true,
+    }
+    input: {
+        function f(a, b) {
+            Math.floor(a / b);
+            Math.floor(c / b);
+        }
+    }
+    expect: {
+        function f(a, b) {
+            Math.floor(c / b);
+        }
+    }
+}
+
+side_effects: {
+    options = {
+        pure_funcs: [ "console.log" ],
+        side_effects: true,
+    }
+    input: {
+        function f(a, b) {
+            console.log(a());
+            console.log(b);
+        }
+    }
+    expect: {
+        function f(a, b) {
+            a();
+        }
+    }
+}
+
+unused: {
+    options = {
+        pure_funcs: [ "pure" ],
+        side_effects: true,
+        unused: true,
+    }
+    input: {
+        function foo() {
+            var u = pure(1);
+            var x = pure(2);
+            var y = pure(x);
+            var z = pure(pure(side_effects()));
+            return pure(3);
+        }
+    }
+    expect: {
+        function foo() {
+            side_effects();
+            return pure(3);
+        }
+    }
+}
+
+babel: {
+    options = {
+        pure_funcs: [ "_classCallCheck" ],
+        side_effects: true,
+        unused: true,
+    }
+    input: {
+        function _classCallCheck(instance, Constructor) {
+            if (!(instance instanceof Constructor))
+                throw new TypeError("Cannot call a class as a function");
+        }
+        var Foo = function Foo() {
+            _classCallCheck(this, Foo);
+        };
+    }
+    expect: {
+        function _classCallCheck(instance, Constructor) {
+            if (!(instance instanceof Constructor))
+                throw new TypeError("Cannot call a class as a function");
+        }
+        var Foo = function() {
+        };
+    }
+}
+
+conditional: {
+    options = {
+        pure_funcs: [ "pure" ],
+        side_effects: true,
+    }
+    input: {
+        pure(1 | a() ? 2 & b() : 7 ^ c());
+        pure(1 | a() ? 2 & b() : 5);
+        pure(1 | a() ? 4 : 7 ^ c());
+        pure(1 | a() ? 4 : 5);
+        pure(3 ? 2 & b() : 7 ^ c());
+        pure(3 ? 2 & b() : 5);
+        pure(3 ? 4 : 7 ^ c());
+        pure(3 ? 4 : 5);
+    }
+    expect: {
+        1 | a() ? b() : c();
+        1 | a() && b();
+        1 | a() || c();
+        a();
+        3 ? b() : c();
+        3 && b();
+        3 || c();
+    }
+}
+
+relational: {
+    options = {
+        pure_funcs: [ "foo" ],
+        side_effects :true,
+    }
+    input: {
+        foo() in foo();
+        foo() instanceof bar();
+        foo() < "bar";
+        bar() > foo();
+        bar() != bar();
+        bar() !== "bar";
+        "bar" == foo();
+        "bar" === bar();
+        "bar" >= "bar";
+    }
+    expect: {
+        bar();
+        bar();
+        bar(), bar();
+        bar();
+        bar();
+    }
+}
+
+arithmetic: {
+    options = {
+        pure_funcs: [ "foo" ],
+        side_effects :true,
+    }
+    input: {
+        foo() + foo();
+        foo() - bar();
+        foo() * "bar";
+        bar() / foo();
+        bar() & bar();
+        bar() | "bar";
+        "bar" >> foo();
+        "bar" << bar();
+        "bar" >>> "bar";
+    }
+    expect: {
+        bar();
+        bar();
+        bar(), bar();
+        bar();
+        bar();
+    }
+}
+
+boolean_and: {
+    options = {
+        pure_funcs: [ "foo" ],
+        side_effects :true,
+    }
+    input: {
+        foo() && foo();
+        foo() && bar();
+        foo() && "bar";
+        bar() && foo();
+        bar() && bar();
+        bar() && "bar";
+        "bar" && foo();
+        "bar" && bar();
+        "bar" && "bar";
+    }
+    expect: {
+        foo() && bar();
+        bar();
+        bar() && bar();
+        bar();
+        "bar" && bar();
+    }
+}
+
+boolean_or: {
+    options = {
+        pure_funcs: [ "foo" ],
+        side_effects :true,
+    }
+    input: {
+        foo() || foo();
+        foo() || bar();
+        foo() || "bar";
+        bar() || foo();
+        bar() || bar();
+        bar() || "bar";
+        "bar" || foo();
+        "bar" || bar();
+        "bar" || "bar";
+    }
+    expect: {
+        foo() || bar();
+        bar();
+        bar() || bar();
+        bar();
+        "bar" || bar();
+    }
+}
+
+assign: {
+    options = {
+        pure_funcs: [ "foo" ],
+        side_effects :true,
+    }
+    input: {
+        var a;
+        function f(b) {
+            a = foo();
+            b *= 4 + foo();
+            c >>= 0 | foo();
+        }
+    }
+    expect: {
+        var a;
+        function f(b) {
+            a = foo();
+            b *= 4 + foo();
+            c >>= 0 | foo();
+        }
+    }
+}
+
+unary: {
+    options = {
+        pure_funcs: [ "foo" ],
+        side_effects :true,
+    }
+    input: {
+        typeof foo();
+        typeof bar();
+        typeof "bar";
+        void foo();
+        void bar();
+        void "bar";
+        delete a[foo()];
+        delete a[bar()];
+        delete a["bar"];
+        a[foo()]++;
+        a[bar()]++;
+        a["bar"]++;
+        --a[foo()];
+        --a[bar()];
+        --a["bar"];
+        ~foo();
+        ~bar();
+        ~"bar";
+    }
+    expect: {
+        bar();
+        bar();
+        delete a[foo()];
+        delete a[bar()];
+        delete a["bar"];
+        a[foo()]++;
+        a[bar()]++;
+        a["bar"]++;
+        --a[foo()];
+        --a[bar()];
+        --a["bar"];
+        bar();
+    }
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/uglifyjs.git



More information about the Pkg-javascript-commits mailing list