always keep declarations found in unreachable code
authorMihai Bazon <mihai@bazon.net>
Fri, 7 Sep 2012 12:18:32 +0000 (15:18 +0300)
committerMihai Bazon <mihai@bazon.net>
Fri, 7 Sep 2012 12:18:32 +0000 (15:18 +0300)
a few more tests and some cleanups.

lib/compress.js
test/compress/conditionals.js [new file with mode: 0644]
test/compress/dead-code.js
test/run-tests.js
tools/node.js

index a7563b9..ea0218e 100644 (file)
@@ -63,7 +63,7 @@ function Compressor(options, false_by_default) {
         comparations  : !false_by_default,
         evaluate      : !false_by_default,
         booleans      : !false_by_default,
-        dwloops       : !false_by_default,
+        loops         : !false_by_default,
         hoist_funs    : !false_by_default,
         hoist_vars    : !false_by_default,
 
@@ -129,15 +129,33 @@ function Compressor(options, false_by_default) {
         });
     };
 
-    function do_list(array, compressor) {
+    function do_list(array, compressor, splice_blocks) {
         return MAP(array, function(node){
-            return node.squeeze(compressor);
+            node = node.squeeze(compressor);
+            if (splice_blocks) {
+                if (node instanceof AST_BlockStatement) {
+                    return MAP.splice(eliminate_spurious_blocks(node.body));
+                }
+                if (node instanceof AST_EmptyStatement)
+                    return MAP.skip;
+            }
+            return node;
         });
     };
 
+    function eliminate_spurious_blocks(statements) {
+        return statements.reduce(function(a, stat){
+            if (stat instanceof AST_BlockStatement) {
+                a.push.apply(a, stat.body);
+            } else if (!(stat instanceof AST_EmptyStatement)) {
+                a.push(stat);
+            }
+            return a;
+        }, []);
+    };
+
     function tighten_body(statements, compressor) {
-        statements = do_list(statements, compressor);
-        statements = eliminate_spurious_blocks(statements);
+        statements = do_list(statements, compressor, true);
         if (compressor.option("dead_code")) {
             statements = eliminate_dead_code(statements, compressor);
         }
@@ -147,15 +165,25 @@ function Compressor(options, false_by_default) {
         return statements;
     };
 
-    function eliminate_spurious_blocks(statements) {
-        return statements.reduce(function(a, stat){
-            if (stat instanceof AST_BlockStatement) {
-                a.push.apply(a, stat.body);
-            } else if (!(stat instanceof AST_EmptyStatement)) {
-                a.push(stat);
+    function extract_declarations_from_unreachable_code(compressor, stat, target) {
+        stat.walk(new TreeWalker(function(node){
+            if (node instanceof AST_Definitions || node instanceof AST_Defun) {
+                compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
+                if (node instanceof AST_Definitions) {
+                    node = node.clone();
+                    node.remove_initializers();
+                    target.push(node);
+                }
+                else if (node instanceof AST_Defun) {
+                    target.push(node);
+                }
+                return true; // no point to descend
             }
-            return a;
-        }, []);
+            if (node instanceof AST_Scope) {
+                // also don't descend any other nested scopes
+                return true;
+            }
+        }));
     };
 
     function eliminate_dead_code(statements, compressor) {
@@ -166,24 +194,7 @@ function Compressor(options, false_by_default) {
                     a.push(stat);
                 }
                 else {
-                    stat.walk(new TreeWalker(function(node){
-                        if (node instanceof AST_Definitions || node instanceof AST_Defun) {
-                            compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
-                            if (node instanceof AST_Definitions) {
-                                node = node.clone();
-                                node.remove_initializers();
-                                a.push(node);
-                            }
-                            else if (node instanceof AST_Defun) {
-                                a.push(node);
-                            }
-                            return true; // no point to descend
-                        }
-                        if (node instanceof AST_Scope) {
-                            // also don't descend any other nested scopes
-                            return true;
-                        }
-                    }));
+                    extract_declarations_from_unreachable_code(compressor, stat, a);
                 };
             } else {
                 a.push(stat);
@@ -552,18 +563,27 @@ function Compressor(options, false_by_default) {
         return self.optimize(compressor);
     });
 
+    function warn_dead_code(node) {
+        AST_Node.warn("Dropping unreachable code [{line},{col}]", node.start);
+    };
+
     AST_DWLoop.DEFMETHOD("optimize", function(compressor){
         var self = this;
-        if (!compressor.option("dwloops")) return self;
         var cond = self.condition.evaluate(compressor);
+        self.condition = cond[0];
+        if (!compressor.option("loops")) return self;
         if (cond.length == 2) {
             if (cond[1]) {
                 return make_node(AST_For, self, {
                     body: self.body
                 });
             } else if (self instanceof AST_While) {
-                AST_Node.warn("Unreachable code [{line},{col}]", self.start);
-                return make_node(AST_EmptyStatement, self);
+                if (compressor.option("dead_code")) {
+                    warn_dead_code(self);
+                    var a = [];
+                    extract_declarations_from_unreachable_code(compressor, self.body, a);
+                    return make_node(AST_BlockStatement, self, { body: a });
+                }
             }
         }
         return self;
@@ -575,7 +595,34 @@ function Compressor(options, false_by_default) {
         if (self.condition) self.condition = self.condition.squeeze(compressor);
         if (self.step) self.step = self.step.squeeze(compressor);
         self.body = self.body.squeeze(compressor);
-        return self;
+        return self.optimize(compressor);
+    });
+
+    AST_For.DEFMETHOD("optimize", function(compressor){
+        var cond = this.condition;
+        if (cond) {
+            cond = cond.evaluate(compressor);
+            this.condition = cond[0];
+        }
+        if (!compressor.option("loops")) return this;
+        if (this.condition) {
+            var cond = this.condition.evaluate(compressor);
+            if (cond.length == 2 && !cond[1]) {
+                if (compressor.option("dead_code")) {
+                    warn_dead_code(this.body);
+                    var a = [];
+                    if (this.init instanceof AST_Statement) a.push(this.init);
+                    else if (this.init) a.push(make_node(AST_SimpleStatement, this.init, {
+                        body: this.init
+                    }));
+                    extract_declarations_from_unreachable_code(compressor, this.body, a);
+                    return make_node(AST_BlockStatement, this, {
+                        body: a
+                    });
+                }
+            }
+        }
+        return this;
     });
 
     SQUEEZE(AST_ForIn, function(self, compressor){
@@ -625,10 +672,24 @@ function Compressor(options, false_by_default) {
         if (cond.length == 2) {
             if (cond[1]) {
                 AST_Node.warn("Condition always true [{line},{col}]", self.condition.start);
-                return self.body;
+                if (compressor.option("dead_code")) {
+                    var a = [];
+                    if (self.alternative) {
+                        warn_dead_code(self.alternative);
+                        extract_declarations_from_unreachable_code(compressor, self.alternative, a);
+                    }
+                    a.push(self.body);
+                    return make_node(AST_BlockStatement, self, { body: a });
+                }
             } else {
                 AST_Node.warn("Condition always false [{line},{col}]", self.condition.start);
-                return self.alternative || make_node(AST_EmptyStatement, self);
+                if (compressor.option("dead_code")) {
+                    warn_dead_code(self.body);
+                    var a = [];
+                    extract_declarations_from_unreachable_code(compressor, self.body, a);
+                    if (self.alternative) a.push(self.alternative);
+                    return make_node(AST_BlockStatement, self, { body: a });
+                }
             }
         }
         if (self.condition instanceof AST_UnaryPrefix
@@ -638,6 +699,12 @@ function Compressor(options, false_by_default) {
             self.body = self.alternative || make_node(AST_EmptyStatement, self);
             self.alternative = tmp;
         }
+        if (self.body instanceof AST_EmptyStatement
+            && self.alternative instanceof AST_EmptyStatement) {
+            return make_node(AST_SimpleStatement, self.condition, {
+                body: self.condition
+            });
+        }
         if (self.body instanceof AST_SimpleStatement
             && self.alternative instanceof AST_SimpleStatement) {
             return make_node(AST_SimpleStatement, self, {
@@ -814,11 +881,15 @@ function Compressor(options, false_by_default) {
 
     SQUEEZE(AST_UnaryPrefix, function(self, compressor){
         // need to determine the context before cloning the node
-        var bool = compressor.in_boolean_context();
         self = self.clone();
-        var e = self.expression = self.expression.squeeze(compressor);
-        if (compressor.option("booleans") && bool) {
-            switch (self.operator) {
+        self.expression = self.expression.squeeze(compressor);
+        return self.optimize(compressor);
+    });
+
+    AST_UnaryPrefix.DEFMETHOD("optimize", function(compressor){
+        if (compressor.option("booleans") && compressor.in_boolean_context()) {
+            var e = this.expression;
+            switch (this.operator) {
               case "!":
                 if (e instanceof AST_UnaryPrefix && e.operator == "!") {
                     // !!foo ==> foo, if we're in boolean context
@@ -828,11 +899,11 @@ function Compressor(options, false_by_default) {
               case "typeof":
                 // typeof always returns a non-empty string, thus it's
                 // always true in booleans
-                AST_Node.warn("Boolean expression always true [{line},{col}]", self.start);
-                return make_node(AST_True, self).optimize(compressor);
+                AST_Node.warn("Boolean expression always true [{line},{col}]", this.start);
+                return make_node(AST_True, this).optimize(compressor);
             }
         }
-        return self.evaluate(compressor)[0];
+        return this.evaluate(compressor)[0];
     });
 
     SQUEEZE(AST_Binary, function(self, compressor){
diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js
new file mode 100644 (file)
index 0000000..ba5e674
--- /dev/null
@@ -0,0 +1,74 @@
+ifs_1: {
+    options = {
+        conditionals: true
+    };
+    input: {
+        if (foo) bar();
+        if (!foo); else bar();
+        if (foo); else bar();
+        if (foo); else;
+    }
+    expect: {
+        foo&&bar();
+        foo&&bar();
+        foo||bar();
+        foo;
+    }
+}
+
+ifs_2: {
+    options = {
+        conditionals: true
+    };
+    input: {
+        if (foo) {
+            x();
+        } else if (bar) {
+            y();
+        } else if (baz) {
+            z();
+        }
+
+        if (foo) {
+            x();
+        } else if (bar) {
+            y();
+        } else if (baz) {
+            z();
+        } else {
+            t();
+        }
+    }
+    expect: {
+        foo ? x() : bar ? y() : baz && z();
+        foo ? x() : bar ? y() : baz ? z() : t();
+    }
+}
+
+ifs_3_should_warn: {
+    options = {
+        conditionals : true,
+        dead_code    : true,
+        evaluate     : true,
+        booleans     : true
+    };
+    input: {
+        if (x && !(x + "1") && y) { // 1
+            var qq;
+            foo();
+        } else {
+            bar();
+        }
+
+        if (x || !!(x + "1") || y) { // 2
+            foo();
+        } else {
+            var jj;
+            bar();
+        }
+    }
+    expect: {
+        var qq; bar();          // 1
+        var jj; foo();          // 2
+    }
+}
index e84b401..0fd066e 100644 (file)
@@ -57,3 +57,33 @@ dead_code_2_should_warn: {
         }
     }
 }
+
+dead_code_constant_boolean_should_warn_more: {
+    options = {
+        dead_code    : true,
+        loops        : true,
+        booleans     : true,
+        conditionals : true,
+        evaluate     : true
+    };
+    input: {
+        while (!((foo && bar) || (x + "0"))) {
+            console.log("unreachable");
+            var foo;
+            function bar() {}
+        }
+        for (var x = 10; x && (y || x) && (!typeof x); ++x) {
+            asdf();
+            foo();
+            var moo;
+        }
+    }
+    expect: {
+        var foo;
+        function bar() {}
+        // nothing for the while
+        // as for the for, it should keep:
+        var x = 10;
+        var moo;
+    }
+}
index b70e229..158c9d3 100755 (executable)
@@ -119,8 +119,10 @@ function parse_test(file) {
                     })
                 );
                 var stat = node.body;
-                if (stat instanceof U.AST_BlockStatement && stat.body.length == 1)
-                    stat = stat.body[0];
+                if (stat instanceof U.AST_BlockStatement) {
+                    if (stat.body.length == 1) stat = stat.body[0];
+                    else if (stat.body.length == 0) stat = new U.AST_EmptyStatement();
+                }
                 test[node.label.name] = stat;
                 return true;
             }
index be83d17..c8d3bd8 100644 (file)
@@ -32,7 +32,7 @@ load_global("../lib/compress.js");
 load_global("../lib/sourcemap.js");
 
 UglifyJS.AST_Node.warn_function = function(txt) {
-    sys.debug(txt);
+    sys.error("WARN: " + txt);
 };
 
 // XXX: perhaps we shouldn't export everything but heck, I'm lazy.