[Pkg-javascript-commits] [uglifyjs] 18/228: drop unused: toplevel, assign-only - assign statement does not count towards variable usage by default - only works with assignments on the same scope level as declaration - can be disabled with `unused` set to "keep_assign" - `toplevel` to drop unused top-level variables and/or functions - `top_retain` to whitelist top-level exceptions

Jonas Smedegaard dr at jones.dk
Sat Apr 15 14:25:12 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 148047fbbf1951a52e69170edf510c59b3899e6c
Author: alexlamsl <alexlamsl at gmail.com>
Date:   Sat Feb 18 19:03:53 2017 +0800

    drop unused: toplevel, assign-only
    - assign statement does not count towards variable usage by default
    - only works with assignments on the same scope level as declaration
    - can be disabled with `unused` set to "keep_assign"
    - `toplevel` to drop unused top-level variables and/or functions
    - `top_retain` to whitelist top-level exceptions
    
    closes #1450
---
 README.md                      |  10 +-
 lib/compress.js                |  72 +++++++-
 test/compress/collapse_vars.js | 114 ++++++++++++-
 test/compress/drop-unused.js   | 379 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 565 insertions(+), 10 deletions(-)

diff --git a/README.md b/README.md
index a8b5584..a2eaeae 100644
--- a/README.md
+++ b/README.md
@@ -361,7 +361,15 @@ to set `true`; it's effectively a shortcut for `foo=true`).
 - `loops` -- optimizations for `do`, `while` and `for` loops when we can
   statically determine the condition
 
-- `unused` -- drop unreferenced functions and variables
+- `unused` -- drop unreferenced functions and variables (simple direct variable
+  assignments do not count as references unless set to `"keep_assign"`)
+
+- `toplevel` -- drop unreferenced functions (`"funcs"`) and/or variables (`"vars"`)
+  in the toplevel scope (`false` by default, `true` to drop both unreferenced
+  functions and variables)
+
+- `top_retain` -- prevent specific toplevel functions and variables from `unused`
+  removal (can be array, comma-separated, RegExp or function. Implies `toplevel`)
 
 - `hoist_funs` -- hoist function declarations
 
diff --git a/lib/compress.js b/lib/compress.js
index 459256f..0dcfb2b 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -60,6 +60,8 @@ function Compressor(options, false_by_default) {
         booleans      : !false_by_default,
         loops         : !false_by_default,
         unused        : !false_by_default,
+        toplevel      : !!options["top_retain"],
+        top_retain    : null,
         hoist_funs    : !false_by_default,
         keep_fargs    : true,
         keep_fnames   : false,
@@ -80,6 +82,21 @@ function Compressor(options, false_by_default) {
         global_defs   : {},
         passes        : 1,
     }, true);
+    var top_retain = this.options["top_retain"];
+    if (top_retain instanceof RegExp) {
+        this.top_retain = function(def) {
+            return top_retain.test(def.name);
+        };
+    } else if (typeof top_retain === "function") {
+        this.top_retain = top_retain;
+    } else if (top_retain) {
+        if (typeof top_retain === "string") {
+            top_retain = top_retain.split(/,/);
+        }
+        this.top_retain = function(def) {
+            return top_retain.indexOf(def.name) >= 0;
+        };
+    }
     var sequences = this.options["sequences"];
     this.sequences_limit = sequences == 1 ? 200 : sequences | 0;
     this.warnings_produced = {};
@@ -1409,13 +1426,27 @@ merge(Compressor.prototype, {
     AST_Scope.DEFMETHOD("drop_unused", function(compressor){
         var self = this;
         if (compressor.has_directive("use asm")) return self;
+        var toplevel = compressor.option("toplevel");
         if (compressor.option("unused")
-            && !(self instanceof AST_Toplevel)
+            && (!(self instanceof AST_Toplevel) || toplevel)
             && !self.uses_eval
-            && !self.uses_with
-           ) {
+            && !self.uses_with) {
+            var assign_as_unused = !/keep_assign/.test(compressor.option("unused"));
+            var drop_funcs = /funcs/.test(toplevel);
+            var drop_vars = /vars/.test(toplevel);
+            if (!(self instanceof AST_Toplevel) || toplevel == true) {
+                drop_funcs = drop_vars = true;
+            }
             var in_use = [];
             var in_use_ids = {}; // avoid expensive linear scans of in_use
+            if (self instanceof AST_Toplevel && compressor.top_retain) {
+                self.variables.each(function(def) {
+                    if (compressor.top_retain(def) && !(def.id in in_use_ids)) {
+                        in_use_ids[def.id] = true;
+                        in_use.push(def);
+                    }
+                });
+            }
             var initializations = new Dictionary();
             // pass 1: find out which symbols are directly used in
             // this scope (not in nested scopes).
@@ -1423,11 +1454,25 @@ merge(Compressor.prototype, {
             var tw = new TreeWalker(function(node, descend){
                 if (node !== self) {
                     if (node instanceof AST_Defun) {
+                        if (!drop_funcs && scope === self) {
+                            var node_def = node.name.definition();
+                            if (!(node_def.id in in_use_ids)) {
+                                in_use_ids[node_def.id] = true;
+                                in_use.push(node_def);
+                            }
+                        }
                         initializations.add(node.name.name, node);
                         return true; // don't go in nested scopes
                     }
                     if (node instanceof AST_Definitions && scope === self) {
                         node.definitions.forEach(function(def){
+                            if (!drop_vars) {
+                                var node_def = def.name.definition();
+                                if (!(node_def.id in in_use_ids)) {
+                                    in_use_ids[node_def.id] = true;
+                                    in_use.push(node_def);
+                                }
+                            }
                             if (def.value) {
                                 initializations.add(def.name.name, def.value);
                                 if (def.value.has_side_effects(compressor)) {
@@ -1437,6 +1482,14 @@ merge(Compressor.prototype, {
                         });
                         return true;
                     }
+                    if (assign_as_unused
+                        && node instanceof AST_Assign
+                        && node.operator == "="
+                        && node.left instanceof AST_SymbolRef
+                        && scope === self) {
+                        node.right.walk(tw);
+                        return true;
+                    }
                     if (node instanceof AST_SymbolRef) {
                         var node_def = node.definition();
                         if (!(node_def.id in in_use_ids)) {
@@ -1496,7 +1549,7 @@ merge(Compressor.prototype, {
                             }
                         }
                     }
-                    if (node instanceof AST_Defun && node !== self) {
+                    if (drop_funcs && node instanceof AST_Defun && node !== self) {
                         if (!(node.name.definition().id in in_use_ids)) {
                             compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", {
                                 name : node.name.name,
@@ -1508,7 +1561,7 @@ merge(Compressor.prototype, {
                         }
                         return node;
                     }
-                    if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
+                    if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {
                         var def = node.definitions.filter(function(def){
                             if (def.name.definition().id in in_use_ids) return true;
                             var w = {
@@ -1571,6 +1624,15 @@ merge(Compressor.prototype, {
                         }
                         return node;
                     }
+                    if (drop_vars && assign_as_unused
+                        && node instanceof AST_Assign
+                        && node.operator == "="
+                        && node.left instanceof AST_SymbolRef) {
+                        var def = node.left.definition();
+                        if (!(def.id in in_use_ids) && self.variables.get(def.name) === def) {
+                            return node.right;
+                        }
+                    }
                     if (node instanceof AST_For) {
                         descend(node, this);
 
diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js
index ef7af9e..d7432f3 100644
--- a/test/compress/collapse_vars.js
+++ b/test/compress/collapse_vars.js
@@ -338,8 +338,9 @@ collapse_vars_while: {
 collapse_vars_do_while: {
     options = {
         collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
-        comparisons:true, evaluate:true, booleans:false, loops:false, unused:true, hoist_funs:true,
-        keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
+        comparisons:true, evaluate:true, booleans:false, loops:false, unused:"keep_assign",
+        hoist_funs:true, keep_fargs:true, if_return:true, join_vars:true, cascade:true,
+        side_effects:true
     }
     input: {
         function f1(y) {
@@ -409,6 +410,79 @@ collapse_vars_do_while: {
     }
 }
 
+collapse_vars_do_while_drop_assign: {
+    options = {
+        collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
+        comparisons:true, evaluate:true, booleans:false, loops:false, unused:true, hoist_funs:true,
+        keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
+    }
+    input: {
+        function f1(y) {
+            // The constant do-while condition `c` will be replaced.
+            var c = 9;
+            do { } while (c === 77);
+        }
+        function f2(y) {
+            // The non-constant do-while condition `c` will not be replaced.
+            var c = 5 - y;
+            do { } while (c);
+        }
+        function f3(y) {
+            // The constant `x` will be replaced in the do loop body.
+            function fn(n) { console.log(n); }
+            var a = 2, x = 7;
+            do {
+                fn(a = x);
+                break;
+            } while (y);
+        }
+        function f4(y) {
+            // The non-constant `a` will not be replaced in the do loop body.
+            var a = y / 4;
+            do {
+                return a;
+            } while (y);
+        }
+        function f5(y) {
+            function p(x) { console.log(x); }
+            do {
+                // The non-constant `a` will be replaced in p(a)
+                // because it is declared in same block.
+                var a = y - 3;
+                p(a);
+            } while (--y);
+        }
+    }
+    expect: {
+        function f1(y) {
+            do ; while (false);
+        }
+        function f2(y) {
+            var c = 5 - y;
+            do ; while (c);
+        }
+        function f3(y) {
+            function fn(n) { console.log(n); }
+            do {
+                fn(7);
+                break;
+            } while (y);
+        }
+        function f4(y) {
+            var a = y / 4;
+            do
+                return a;
+            while (y);
+        }
+        function f5(y) {
+            function p(x) { console.log(x); }
+            do {
+                p(y - 3);
+            } while (--y);
+        }
+    }
+}
+
 collapse_vars_seq: {
     options = {
         collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
@@ -567,8 +641,9 @@ collapse_vars_assignment: {
 collapse_vars_lvalues: {
     options = {
         collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
-        comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
-        keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
+        comparisons:true, evaluate:true, booleans:true, loops:true, unused:"keep_assign",
+        hoist_funs:true, keep_fargs:true, if_return:true, join_vars:true, cascade:true,
+        side_effects:true
     }
     input: {
         function f0(x) { var i = ++x; return x += i; }
@@ -593,7 +668,38 @@ collapse_vars_lvalues: {
         function f7(x) { var w = e1(), v = e2(), c = v - x; return (w = x) - c; }
         function f8(x) { var w = e1(), v = e2(); return (w = x) - (v - x); }
         function f9(x) { var w = e1(); return e2() - x - (w = x); }
+    }
+}
 
+collapse_vars_lvalues_drop_assign: {
+    options = {
+        collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
+        comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
+        keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
+    }
+    input: {
+        function f0(x) { var i = ++x; return x += i; }
+        function f1(x) { var a = (x -= 3); return x += a; }
+        function f2(x) { var z = x, a = ++z; return z += a; }
+        function f3(x) { var a = (x -= 3), b = x + a; return b; }
+        function f4(x) { var a = (x -= 3); return x + a; }
+        function f5(x) { var w = e1(), v = e2(), c = v = --x, b = w = x; return b - c; }
+        function f6(x) { var w = e1(), v = e2(), c = v = --x, b = w = x; return c - b; }
+        function f7(x) { var w = e1(), v = e2(), c = v - x, b = w = x; return b - c; }
+        function f8(x) { var w = e1(), v = e2(), b = w = x, c = v - x; return b - c; }
+        function f9(x) { var w = e1(), v = e2(), b = w = x, c = v - x; return c - b; }
+    }
+    expect: {
+        function f0(x) { var i = ++x; return x += i; }
+        function f1(x) { var a = (x -= 3); return x += a; }
+        function f2(x) { var z = x, a = ++z; return z += a; }
+        function f3(x) { var a = (x -= 3); return x + a; }
+        function f4(x) { var a = (x -= 3); return x + a; }
+        function f5(x) { var v = (e1(), e2()), c = v = --x; return x - c; }
+        function f6(x) { e1(), e2(); return --x - x; }
+        function f7(x) { var v = (e1(), e2()), c = v - x; return x - c; }
+        function f8(x) { var v = (e1(), e2()); return x - (v - x); }
+        function f9(x) { e1(); return e2() - x - x; }
     }
 }
 
diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js
index 035a428..5620cf4 100644
--- a/test/compress/drop-unused.js
+++ b/test/compress/drop-unused.js
@@ -177,3 +177,382 @@ keep_fnames: {
         }
     }
 }
+
+drop_assign: {
+    options = { unused: true };
+    input: {
+        function f1() {
+            var a;
+            a = 1;
+        }
+        function f2() {
+            var a = 1;
+            a = 2;
+        }
+        function f3(a) {
+            a = 1;
+        }
+        function f4() {
+            var a;
+            return a = 1;
+        }
+        function f5() {
+            var a;
+            return function() {
+                a = 1;
+            }
+        }
+    }
+    expect: {
+        function f1() {
+            1;
+        }
+        function f2() {
+            2;
+        }
+        function f3(a) {
+            1;
+        }
+        function f4() {
+            return 1;
+        }
+        function f5() {
+            var a;
+            return function() {
+                a = 1;
+            }
+        }
+    }
+}
+
+keep_assign: {
+    options = { unused: "keep_assign" };
+    input: {
+        function f1() {
+            var a;
+            a = 1;
+        }
+        function f2() {
+            var a = 1;
+            a = 2;
+        }
+        function f3(a) {
+            a = 1;
+        }
+        function f4() {
+            var a;
+            return a = 1;
+        }
+        function f5() {
+            var a;
+            return function() {
+                a = 1;
+            }
+        }
+    }
+    expect: {
+        function f1() {
+            var a;
+            a = 1;
+        }
+        function f2() {
+            var a = 1;
+            a = 2;
+        }
+        function f3(a) {
+            a = 1;
+        }
+        function f4() {
+            var a;
+            return a = 1;
+        }
+        function f5() {
+            var a;
+            return function() {
+                a = 1;
+            }
+        }
+    }
+}
+
+drop_toplevel_funcs: {
+    options = { toplevel: "funcs", unused: true };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        var a, b = 1, c = g;
+        a = 2;
+        function g() {}
+        console.log(b = 3);
+    }
+}
+
+drop_toplevel_vars: {
+    options = { toplevel: "vars", unused: true };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        var c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        2;
+        function g() {}
+        function h() {}
+        console.log(3);
+    }
+}
+
+drop_toplevel_vars_fargs: {
+    options = { keep_fargs: false, toplevel: "vars", unused: true };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        var c = g;
+        function f() {
+            return function() {
+                c = 2;
+            }
+        }
+        2;
+        function g() {}
+        function h() {}
+        console.log(3);
+    }
+}
+
+drop_toplevel_all: {
+    options = { toplevel: true, unused: true };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        2;
+        console.log(3);
+    }
+}
+
+drop_toplevel_retain: {
+    options = { top_retain: "f,a,o", unused: true };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        var a, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        console.log(3);
+    }
+}
+
+drop_toplevel_retain_array: {
+    options = { top_retain: [ "f", "a", "o" ], unused: true };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        var a, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        console.log(3);
+    }
+}
+
+drop_toplevel_retain_regex: {
+    options = { top_retain: /^[fao]$/, unused: true };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        var a, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        console.log(3);
+    }
+}
+
+drop_toplevel_all_retain: {
+    options = { toplevel: true, top_retain: "f,a,o", unused: true };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        var a, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        console.log(3);
+    }
+}
+
+drop_toplevel_funcs_retain: {
+    options = { toplevel: "funcs", top_retain: "f,a,o", unused: true };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        console.log(b = 3);
+    }
+}
+
+drop_toplevel_vars_retain: {
+    options = { toplevel: "vars", top_retain: "f,a,o", unused: true };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        var a, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(3);
+    }
+}
+
+drop_toplevel_keep_assign: {
+    options = { toplevel: true, unused: "keep_assign" };
+    input: {
+        var a, b = 1, c = g;
+        function f(d) {
+            return function() {
+                c = 2;
+            }
+        }
+        a = 2;
+        function g() {}
+        function h() {}
+        console.log(b = 3);
+    }
+    expect: {
+        var a, b = 1;
+        a = 2;
+        console.log(b = 3);
+    }
+}

-- 
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