[Pkg-javascript-commits] [uglifyjs] 31/491: enhance `collapse_vars` (#1862)

Jonas Smedegaard dr at jones.dk
Wed Feb 14 19:51:19 UTC 2018


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

js pushed a commit to annotated tag debian/3.3.10-1
in repository uglifyjs.

commit dee5a27516cb574dda5fc3d23a64344f0ea654b6
Author: Alex Lam S.L <alexlamsl at gmail.com>
Date:   Sat May 6 16:15:43 2017 +0800

    enhance `collapse_vars` (#1862)
    
    - extend expression types
      - `a++`
      - `a=x;`
    - extend scan range
      - `for(init;;);`
      - `switch(expr){case expr:}`
      - `a = x; a = a || y;`
    - terminate upon `debugger;`
    
    closes #1821
    fixes #27
    fixes #315
    fixes #1858
---
 lib/compress.js                | 341 ++++++++++++++------------
 test/compress/collapse_vars.js | 527 ++++++++++++++++++++++++++++++++++++++++-
 test/compress/drop-unused.js   |  33 +++
 test/compress/issue-1609.js    |   9 +-
 4 files changed, 745 insertions(+), 165 deletions(-)

diff --git a/lib/compress.js b/lib/compress.js
index 931b8d6..bf6a40d 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -512,6 +512,10 @@ merge(Compressor.prototype, {
         return fixed();
     });
 
+    function is_lhs_read_only(lhs) {
+        return lhs instanceof AST_SymbolRef && lhs.definition().orig[0] instanceof AST_SymbolLambda;
+    }
+
     function find_variable(compressor, name) {
         var scope, i = 0;
         while (scope = compressor.parent(i++)) {
@@ -643,174 +647,210 @@ merge(Compressor.prototype, {
                 statements = join_consecutive_vars(statements, compressor);
             }
             if (compressor.option("collapse_vars")) {
-                statements = collapse_single_use_vars(statements, compressor);
+                statements = collapse(statements, compressor);
             }
         } while (CHANGED && max_iter-- > 0);
 
         return statements;
 
-        function collapse_single_use_vars(statements, compressor) {
-            // Iterate statements backwards looking for a statement with a var/const
-            // declaration immediately preceding it. Grab the rightmost var definition
-            // and if it has exactly one reference then attempt to replace its reference
-            // in the statement with the var value and then erase the var definition.
-
+        // Search from right to left for assignment-like expressions:
+        // - `var a = x;`
+        // - `a = x;`
+        // - `++a`
+        // For each candidate, scan from left to right for first usage, then try
+        // to fold assignment into the site for compression.
+        // Will not attempt to collapse assignments into or past code blocks
+        // which are not sequentially executed, e.g. loops and conditionals.
+        function collapse(statements, compressor) {
             var scope = compressor.find_parent(AST_Scope);
-            var stat_index;
-            var prev_stat_index;
-            var def_stat_index;
-            var stat;
-            var var_defs;
-            var var_defs_index;
-            for (stat_index = statements.length; --stat_index >= 0;) {
-                stat = statements[stat_index];
-                // Scan variable definitions from right to left.
-                if (stat instanceof AST_Definitions) {
-                    prev_stat_index = stat_index;
-                    var_defs = stat.definitions;
-                    for (def_stat_index = var_defs.length; --def_stat_index >= 1;) {
-                        stat = var_defs[def_stat_index];
-                        scan_var_defs(def_stat_index);
+            if (scope.uses_eval || scope.uses_with) return statements;
+            var candidates = [];
+            var stat_index = statements.length;
+            while (--stat_index >= 0) {
+                extract_candidates(statements[stat_index]);
+                while (candidates.length > 0) {
+                    var candidate = candidates.pop();
+                    var lhs = get_lhs(candidate);
+                    if (!lhs || is_lhs_read_only(lhs)) continue;
+                    var lvalues = get_lvalues(candidate);
+                    if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false;
+                    var side_effects = value_has_side_effects(candidate);
+                    var hit = false, abort = false, replaced = false;
+                    var tt = new TreeTransformer(function(node, descend) {
+                        if (abort) return node;
+                        // Skip nodes before `candidate` as quickly as possible
+                        if (!hit) {
+                            if (node === candidate) {
+                                hit = true;
+                                return node;
+                            }
+                            return;
+                        }
+                        // Stop immediately if these node types are encountered
+                        var parent = tt.parent();
+                        if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left)
+                            || node instanceof AST_Debugger
+                            || node instanceof AST_IterationStatement && !(node instanceof AST_For)
+                            || node instanceof AST_SymbolRef && node.undeclared()
+                            || node instanceof AST_Try
+                            || node instanceof AST_With
+                            || parent instanceof AST_For && node !== parent.init) {
+                            abort = true;
+                            return node;
+                        }
+                        // Replace variable with assignment when found
+                        if (!(node instanceof AST_SymbolDeclaration)
+                            && !is_lhs(node, parent)
+                            && lhs.equivalent_to(node)) {
+                            CHANGED = replaced = abort = true;
+                            compressor.info("Collapsing {name} [{file}:{line},{col}]", {
+                                name: node.print_to_string(),
+                                file: node.start.file,
+                                line: node.start.line,
+                                col: node.start.col
+                            });
+                            if (candidate instanceof AST_UnaryPostfix) {
+                                return make_node(AST_UnaryPrefix, candidate, candidate);
+                            }
+                            if (candidate instanceof AST_VarDef) {
+                                var def = candidate.name.definition();
+                                if (def.references.length == 1 && (!def.global || compressor.toplevel(def))) {
+                                    return maintain_this_binding(parent, node, candidate.value);
+                                }
+                                return make_node(AST_Assign, candidate, {
+                                    operator: "=",
+                                    left: make_node(AST_SymbolRef, candidate.name, candidate.name),
+                                    right: candidate.value
+                                });
+                            }
+                            return candidate;
+                        }
+                        // These node types have child nodes that execute sequentially,
+                        // but are otherwise not safe to scan into or beyond them.
+                        var sym;
+                        if (node instanceof AST_Call
+                            || node instanceof AST_Exit
+                            || node instanceof AST_PropAccess
+                            || node instanceof AST_SymbolRef
+                               && (lvalues[node.name]
+                                   || side_effects && !references_in_scope(node.definition()))
+                            || (sym = lhs_or_def(node)) && get_symbol(sym).name in lvalues
+                            || parent instanceof AST_Binary
+                                && (parent.operator == "&&" || parent.operator == "||")
+                            || parent instanceof AST_Case
+                            || parent instanceof AST_Conditional
+                            || parent instanceof AST_For
+                            || parent instanceof AST_If) {
+                            if (!(node instanceof AST_Scope)) descend(node, tt);
+                            abort = true;
+                            return node;
+                        }
+                        // Skip (non-executed) functions and (leading) default case in switch statements
+                        if (node instanceof AST_Default || node instanceof AST_Scope) return node;
+                    });
+                    for (var i = stat_index; !abort && i < statements.length; i++) {
+                        statements[i].transform(tt);
                     }
-                } else if (stat_index > 0) {
-                    // The variable definition must precede a statement.
-                    prev_stat_index = stat_index - 1;
-                    var prev_stat = statements[prev_stat_index];
-                    if (!(prev_stat instanceof AST_Definitions)) continue;
-                    var_defs = prev_stat.definitions;
-                    scan_var_defs(var_defs.length);
+                    if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1);
                 }
             }
-
             return statements;
 
-            function scan_var_defs(end_pos) {
-                var var_names_seen = Object.create(null);
-                var side_effects_encountered = false;
-                var lvalues_encountered = false;
-                var lvalues = Object.create(null);
-                for (var_defs_index = end_pos; --var_defs_index >= 0;) {
-                    var var_decl = var_defs[var_defs_index];
-                    // `drop_unused()` shuffles variables without values to the top,
-                    // so we can terminate upon first sighting as an optimization.
-                    if (var_decl.value == null) break;
-                    var var_name = var_decl.name.name;
-
-                    // Bail if we've seen a var definition of same name before.
-                    if (var_name in var_names_seen) break;
-                    var_names_seen[var_name] = true;
-
-                    // Only interested in non-constant values.
-                    if (var_decl.value.is_constant()) continue;
-
-                    // Only interested in cases with just one reference to the variable.
-                    var def = var_decl.name.definition();
-                    if (def.references.length !== 1
-                        || var_name == "arguments"
-                        || def.global && !compressor.toplevel(def)) {
-                        side_effects_encountered = true;
-                        continue;
-                    }
-                    var ref = def.references[0];
+            function extract_candidates(expr) {
+                if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor)
+                    || expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) {
+                    candidates.push(expr);
+                } else if (expr instanceof AST_Sequence) {
+                    expr.expressions.forEach(extract_candidates);
+                } else if (expr instanceof AST_Definitions) {
+                    expr.definitions.forEach(function(var_def) {
+                        if (var_def.value) candidates.push(var_def);
+                    });
+                } else if (expr instanceof AST_SimpleStatement) {
+                    extract_candidates(expr.body);
+                } else if (expr instanceof AST_For && expr.init) {
+                    extract_candidates(expr.init);
+                }
+            }
 
-                    // Don't replace ref if eval() or with statement in scope.
-                    if (ref.scope.uses_eval || ref.scope.uses_with) break;
+            function get_lhs(expr) {
+                if (expr instanceof AST_VarDef) {
+                    var def = expr.name.definition();
+                    if (def.orig.length > 1
+                        || def.references.length == 1 && (!def.global || compressor.toplevel(def))) {
+                        return make_node(AST_SymbolRef, expr.name, expr.name);
+                    }
+                } else {
+                    return expr[expr instanceof AST_Assign ? "left" : "expression"];
+                }
+            }
 
-                    // Restrict var replacement to constants if side effects encountered.
-                    if (side_effects_encountered |= lvalues_encountered) continue;
+            function get_symbol(node) {
+                while (node instanceof AST_PropAccess) node = node.expression;
+                return node;
+            }
 
-                    var value_has_side_effects = var_decl.value.has_side_effects(compressor);
-                    // Non-constant single use vars can only be replaced in same scope.
-                    if (ref.scope !== scope) {
-                        side_effects_encountered |= value_has_side_effects;
-                        continue;
+            function get_lvalues(expr) {
+                var lvalues = Object.create(null);
+                if (expr instanceof AST_Unary) return lvalues;
+                var scope;
+                var tw = new TreeWalker(function(node, descend) {
+                    if (node instanceof AST_Scope) {
+                        var save_scope = scope;
+                        descend();
+                        scope = save_scope;
+                        return true;
                     }
-
-                    // Detect lvalues in var value.
-                    var tw = new TreeWalker(function(node){
-                        if (node instanceof AST_SymbolRef && is_lvalue(node, tw.parent())) {
-                            lvalues[node.name] = lvalues_encountered = true;
-                        }
-                    });
-                    var_decl.value.walk(tw);
-
-                    // Replace the non-constant single use var in statement if side effect free.
-                    var unwind = false;
-                    var tt = new TreeTransformer(
-                        function preorder(node) {
-                            if (unwind || node instanceof AST_Scope && node !== scope) return node;
-                            var parent = tt.parent();
-                            if (node instanceof AST_Try
-                                || node instanceof AST_With
-                                || node instanceof AST_Case
-                                || node instanceof AST_IterationStatement
-                                || (parent instanceof AST_If          && node !== parent.condition)
-                                || (parent instanceof AST_Conditional && node !== parent.condition)
-                                || (node instanceof AST_SymbolRef
-                                    && value_has_side_effects
-                                    && !are_references_in_scope(node.definition(), scope))
-                                || (parent instanceof AST_Binary
-                                    && (parent.operator == "&&" || parent.operator == "||")
-                                    && node === parent.right)
-                                || (parent instanceof AST_Switch && node !== parent.expression)) {
-                                return side_effects_encountered = unwind = true, node;
-                            }
-                            function are_references_in_scope(def, scope) {
-                                if (def.orig.length === 1
-                                    && def.orig[0] instanceof AST_SymbolDefun) return true;
-                                if (def.scope !== scope) return false;
-                                var refs = def.references;
-                                for (var i = 0, len = refs.length; i < len; i++) {
-                                    if (refs[i].scope !== scope) return false;
-                                }
-                                return true;
-                            }
-                        },
-                        function postorder(node) {
-                            if (unwind) return node;
-                            if (node === ref)
-                                return unwind = true, replace_var(var_decl, node, tt.parent(), false);
-                            if (side_effects_encountered |= node.has_side_effects(compressor))
-                                return unwind = true, node;
-                            if (lvalues_encountered && node instanceof AST_SymbolRef && node.name in lvalues) {
-                                side_effects_encountered = true;
-                                return unwind = true, node;
-                            }
+                    if (node instanceof AST_SymbolRef || node instanceof AST_PropAccess) {
+                        var sym = get_symbol(node);
+                        if (sym instanceof AST_SymbolRef) {
+                            lvalues[sym.name] = lvalues[sym.name] || is_lhs(node, tw.parent());
                         }
-                    );
-                    stat.transform(tt);
-                }
+                    }
+                });
+                expr[expr instanceof AST_Assign ? "right" : "value"].walk(tw);
+                return lvalues;
             }
 
-            function is_lvalue(node, parent) {
-                return node instanceof AST_SymbolRef && is_lhs(node, parent);
+            function lhs_or_def(node) {
+                if (node instanceof AST_VarDef) return node.value && node.name;
+                return is_lhs(node.left, node);
             }
 
-            function replace_var(var_decl, node, parent, is_constant) {
-                if (is_lvalue(node, parent)) return node;
+            function remove_candidate(expr) {
+                var found = false;
+                return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) {
+                    if (found) return node;
+                    if (node === expr) {
+                        found = true;
+                        if (node instanceof AST_VarDef) {
+                            remove(node.name.definition().orig, node.name);
+                        }
+                        return in_list ? MAP.skip : null;
+                    }
+                }, function(node) {
+                    if (node instanceof AST_Sequence) switch (node.expressions.length) {
+                      case 0: return null;
+                      case 1: return node.expressions[0];
+                    }
+                    if (node instanceof AST_Definitions && node.definitions.length == 0
+                        || node instanceof AST_SimpleStatement && !node.body) {
+                        return null;
+                    }
+                }));
+            }
 
-                // Remove var definition and return its value to the TreeTransformer to replace.
-                var value = maintain_this_binding(parent, node, var_decl.value);
-                var_decl.value = null;
+            function value_has_side_effects(expr) {
+                if (expr instanceof AST_Unary) return false;
+                return expr[expr instanceof AST_Assign ? "right" : "value"].has_side_effects(compressor);
+            }
 
-                var_defs.splice(var_defs_index, 1);
-                def_stat_index--;
-                if (var_defs.length === 0) {
-                    statements.splice(prev_stat_index, 1);
-                    stat_index--;
-                }
-                // Further optimize statement after substitution.
-                stat.reset_opt_flags(compressor);
-
-                compressor.info("Collapsing {type} {name} [{file}:{line},{col}]", {
-                    type: is_constant ? "constant" : "variable",
-                    name: var_decl.name.name,
-                    file: node.start.file,
-                    line: node.start.line,
-                    col: node.start.col
+            function references_in_scope(def) {
+                if (def.orig.length == 1 && def.orig[0] instanceof AST_SymbolDefun) return true;
+                if (def.scope !== scope) return false;
+                return def.references.every(function(ref) {
+                    return ref.scope === scope;
                 });
-                CHANGED = true;
-                return value;
             }
         }
 
@@ -2022,7 +2062,8 @@ merge(Compressor.prototype, {
                                 var var_defs = var_defs_by_id.get(sym.id);
                                 if (var_defs.length > 1 && !def.value) {
                                     compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
-                                    var_defs.splice(var_defs.indexOf(def), 1);
+                                    remove(var_defs, def);
+                                    remove(sym.orig, def.name);
                                     return;
                                 }
                             }
@@ -2055,6 +2096,7 @@ merge(Compressor.prototype, {
                             } else {
                                 compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name));
                             }
+                            remove(sym.orig, def.name);
                         }
                     });
                     if (head.length == 0 && tail.length == 1 && tail[0].name instanceof AST_SymbolVar) {
@@ -2062,7 +2104,8 @@ merge(Compressor.prototype, {
                         if (var_defs.length > 1) {
                             var def = tail.pop();
                             compressor.warn("Converting duplicated definition of variable {name} to assignment [{file}:{line},{col}]", template(def.name));
-                            var_defs.splice(var_defs.indexOf(def), 1);
+                            remove(var_defs, def);
+                            remove(def.name.definition().orig, def.name);
                             side_effects.unshift(make_node(AST_Assign, def, {
                                 operator: "=",
                                 left: make_node(AST_SymbolRef, def.name, def.name),
@@ -3130,9 +3173,7 @@ merge(Compressor.prototype, {
                     && (left.operator == "++" || left.operator == "--")) {
                     left = left.expression;
                 } else left = null;
-                if (!left ||
-                    left instanceof AST_SymbolRef
-                        && left.definition().orig[0] instanceof AST_SymbolLambda) {
+                if (!left || is_lhs_read_only(left)) {
                     expressions[++i] = cdr;
                     continue;
                 }
diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js
index a4c1f9e..4215ceb 100644
--- a/test/compress/collapse_vars.js
+++ b/test/compress/collapse_vars.js
@@ -68,11 +68,10 @@ collapse_vars_side_effects_1: {
             log(x, s.charAt(i++), y, 7);
         }
         function f4() {
-            var log = console.log.bind(console),
-                i = 10,
+            var i = 10,
                 x = i += 2,
                 y = i += 3;
-            log(x, i += 4, y, i);
+            console.log.bind(console)(x, i += 4, y, i);
         }
         f1(), f2(), f3(), f4();
     }
@@ -671,8 +670,8 @@ collapse_vars_lvalues: {
         function f4(x) { var a = (x -= 3); return x + a; }
         function f5(x) { var w = e1(), v = e2(), c = v = --x; return (w = x) - c; }
         function f6(x) { var w = e1(), v = e2(); return (v = --x) - (w = x); }
-        function f7(x) { var w = e1(), c = e2() - x; return (w = x) - c; }
-        function f8(x) { var w = e1(), v = e2(); return (w = x) - (v - x); }
+        function f7(x) { var w = e1(); return (w = x) - (e2() - x); }
+        function f8(x) { var w = e1(); return (w = x) - (e2() - x); }
         function f9(x) { var w = e1(); return e2() - x - (w = x); }
     }
 }
@@ -703,8 +702,8 @@ collapse_vars_lvalues_drop_assign: {
         function f4(x) { var a = (x -= 3); return x + a; }
         function f5(x) { e1(); var v = e2(), c = v = --x; return x - c; }
         function f6(x) { e1(), e2(); return --x - x; }
-        function f7(x) { e1(); var c = e2() - x; return x - c; }
-        function f8(x) { e1(); var v = e2(); return x - (v - x); }
+        function f7(x) { e1(); return x - (e2() - x); }
+        function f8(x) { e1(); return x - (e2() - x); }
         function f9(x) { e1(); return e2() - x - x; }
     }
 }
@@ -1047,10 +1046,9 @@ collapse_vars_object: {
     }
     expect: {
         function f0(x, y) {
-            var z = x + y;
             return {
                 get b() { return 7; },
-                r: z
+                r: x + y
             };
         }
         function f1(x, y) {
@@ -1677,3 +1675,514 @@ var_defs: {
     }
     expect_stdout: "97"
 }
+
+assignment: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        function f() {
+            var a;
+            a = x;
+            return a;
+        }
+    }
+    expect: {
+        function f() {
+            return x;
+        }
+    }
+}
+
+for_init: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        function f(x, y) {
+            var a = x;
+            var b = y;
+            for (a; b;);
+        }
+    }
+    expect: {
+        function f(x, y) {
+            var b = y;
+            for (x; b;);
+        }
+    }
+}
+
+switch_case: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        function f(x, y, z) {
+            var a = x();
+            var b = y();
+            var c = z;
+            switch (a) {
+              default: d();
+              case b: e();
+              case c: f();
+            }
+        }
+    }
+    expect: {
+        function f(x, y, z) {
+            var c = z;
+            switch (x()) {
+              default: d();
+              case y(): e();
+              case c: f();
+            }
+        }
+    }
+}
+
+issue_27: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        (function(jQuery) {
+            var $;
+            $ = jQuery;
+            $("body").addClass("foo");
+        })(jQuery);
+    }
+    expect: {
+        (function(jQuery) {
+            jQuery("body").addClass("foo");
+        })(jQuery);
+    }
+}
+
+modified: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        function f1(b) {
+            var a = b;
+            return b + a;
+        }
+        function f2(b) {
+            var a = b;
+            return b++ + a;
+        }
+        function f3(b) {
+            var a = b++;
+            return b + a;
+        }
+        function f4(b) {
+            var a = b++;
+            return b++ + a;
+        }
+        function f5(b) {
+            var a = function() {
+                return b;
+            }();
+            return b++ + a;
+        }
+        console.log(f1(1), f2(1), f3(1), f4(1), f5(1));
+    }
+    expect: {
+        function f1(b) {
+            return b + b;
+        }
+        function f2(b) {
+            var a = b;
+            return b++ + a;
+        }
+        function f3(b) {
+            var a = b++;
+            return b + a;
+        }
+        function f4(b) {
+            var a = b++;
+            return b++ + a;
+        }
+        function f5(b) {
+            var a = function() {
+                return b;
+            }();
+            return b++ + a;
+        }
+        console.log(f1(1), f2(1), f3(1), f4(1), f5(1));
+    }
+    expect_stdout: "2 2 3 3 2"
+}
+
+issue_1858: {
+    options = {
+        collapse_vars: true,
+        pure_getters: true,
+        unused: true,
+    }
+    input: {
+        console.log(function(x) {
+            var a = {}, b = a.b = x;
+            return a.b + b;
+        }(1));
+    }
+    expect: {
+        console.log(function(x) {
+            var a = {}, b = a.b = x;
+            return a.b + b;
+        }(1));
+    }
+    expect_stdout: "2"
+}
+
+anonymous_function: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        console.log(function f(a) {
+            f ^= 0;
+            return f * a;
+        }(1));
+    }
+    expect: {
+        console.log(function f(a) {
+            f ^= 0;
+            return f * a;
+        }(1));
+    }
+    expect_stdout: true
+}
+
+side_effects_property: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = [];
+        var b = 0;
+        a[b++] = function() { return 42;};
+        var c = a[b++]();
+        console.log(c);
+    }
+    expect: {
+        var a = [];
+        var b = 0;
+        a[b++] = function() { return 42;};
+        var c = a[b++]();
+        console.log(c);
+    }
+    expect_stdout: true
+}
+
+undeclared: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        function f(x, y) {
+            var a;
+            a = x;
+            b = y;
+            return b + a;
+        }
+    }
+    expect: {
+        function f(x, y) {
+            var a;
+            a = x;
+            b = y;
+            return b + a;
+        }
+    }
+}
+
+ref_scope: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        console.log(function() {
+            var a = 1, b = 2, c = 3;
+            var a = c++, b = b /= a;
+            return function() {
+                return a;
+            }() + b;
+        }());
+    }
+    expect: {
+        console.log(function() {
+            var a = 1, b = 2, c = 3;
+            b = b /= a = c++;
+            return function() {
+                return a;
+            }() + b;
+        }());
+    }
+    expect_stdout: true
+}
+
+chained_1: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = 2;
+        var a = 3 / a;
+        console.log(a);
+    }
+    expect: {
+        var a = 3 / (a = 2);
+        console.log(a);
+    }
+    expect_stdout: true
+}
+
+chained_2: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a;
+        var a = 2;
+        a = 3 / a;
+        console.log(a);
+    }
+    expect: {
+        var a;
+        a = 3 / (a = 2);
+        console.log(a);
+    }
+    expect_stdout: true
+}
+
+chained_3: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        console.log(function(a, b) {
+            var c = a, c = b;
+            b++;
+            return c;
+        }(1, 2));
+    }
+    expect: {
+        console.log(function(a, b) {
+            var c = a, c = b;
+            b++;
+            return c;
+        }(1, 2));
+    }
+    expect_stdout: "2"
+}
+
+boolean_binary_1: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = 1;
+        a++;
+        (function() {} || a || 3).toString();
+        console.log(a);
+    }
+    expect: {
+        var a = 1;
+        a++;
+        (function() {} || a || 3).toString();
+        console.log(a);
+    }
+    expect_stdout: true
+}
+
+boolean_binary_2: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var c = 0;
+        c += 1;
+        (function() {
+            c = 1 + c;
+        } || 9).toString();
+        console.log(c);
+    }
+    expect: {
+        var c = 0;
+        c += 1;
+        (function() {
+            c = 1 + c;
+        } || 9).toString();
+        console.log(c);
+    }
+    expect_stdout: true
+}
+
+inner_lvalues: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        var a, b = 10;
+        var a = (--b || a || 3).toString(), c = --b + -a;
+        console.log(null, a, b);
+    }
+    expect: {
+        var a, b = 10;
+        var a = (--b || a || 3).toString(), c = --b + -a;
+        console.log(null, a, b);
+    }
+    expect_stdout: true
+}
+
+double_def: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = x, a = a && y;
+        a();
+    }
+    expect: {
+        var a = x;
+        (a = a && y)();
+    }
+}
+
+toplevel_single_reference: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a;
+        for (var b in x) {
+            var a = b;
+            b(a);
+        }
+    }
+    expect: {
+        var a;
+        for (var b in x)
+            b(a = b);
+    }
+}
+
+unused_orig: {
+    options = {
+        collapse_vars: true,
+        passes: 2,
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        var a = 1;
+        console.log(function(b) {
+            var a;
+            var c = b;
+            for (var d in c) {
+                var a = c[0];
+                return --b + a;
+            }
+            try {
+            } catch (e) {
+                --b + a;
+            }
+            a && a.NaN;
+        }([2]), a);
+    }
+    expect: {
+        var a = 1;
+        console.log(function(b) {
+            var c = b;
+            for (var d in c) {
+                var a = c[0];
+                return --b + a;
+            }
+            a && a.NaN;
+        }([2]), a);
+    }
+    expect_stdout: "3 1"
+}
+
+issue_315: {
+    options = {
+        collapse_vars: true,
+        evaluate: true,
+        keep_fargs: false,
+        reduce_vars: true,
+        sequences: true,
+        unused: true,
+    }
+    input: {
+        console.log(function(s) {
+            var w, _i, _len, _ref, _results;
+            _ref = s.trim().split(" ");
+            _results = [];
+            for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+                w = _ref[_i];
+                _results.push(w.toLowerCase());
+            }
+            return _results;
+        }("test"));
+    }
+    expect: {
+        console.log(function() {
+            var w, _i, _len, _ref, _results;
+            for (_results = [], _i = 0, _len = (_ref = "test".trim().split(" ")).length; _i < _len ; _i++)
+                w = _ref[_i], _results.push(w.toLowerCase());
+            return _results;
+        }());
+    }
+    expect_stdout: true
+}
+
+lvalues_def: {
+    options = {
+        collapse_vars: true,
+        side_effects: true,
+        unused: true,
+    }
+    input: {
+        var a = 0, b = 1;
+        var a = b++, b = +function() {}();
+        a && a[a++];
+        console.log(a, b);
+    }
+    expect: {
+        var a = 0, b = 1;
+        var a = b++, b = +void 0;
+        a && a[a++];
+        console.log(a, b);
+    }
+    expect_stdout: true
+}
+
+compound_assignment: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a;
+        a = 1;
+        a += a + 2;
+        console.log(a);
+    }
+    expect: {
+        var a;
+        a = 1;
+        a += a + 2;
+        console.log(a);
+    }
+    expect_stdout: "4"
+}
diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js
index c702cfa..ddf90bf 100644
--- a/test/compress/drop-unused.js
+++ b/test/compress/drop-unused.js
@@ -1114,3 +1114,36 @@ issue_1838: {
         "}",
     ]
 }
+
+var_catch_toplevel: {
+    options = {
+        conditionals: true,
+        negate_iife: true,
+        reduce_vars: true,
+        side_effects: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        function f() {
+            a--;
+            try {
+                a++;
+            } catch(a) {
+                if (a) var a;
+                var a = 10;
+            }
+        }
+        f();
+    }
+    expect: {
+        !function() {
+            a--;
+            try {
+                a++;
+            } catch(a) {
+                var a;
+            }
+        }();
+    }
+}
diff --git a/test/compress/issue-1609.js b/test/compress/issue-1609.js
index da4b54a..dffa54a 100644
--- a/test/compress/issue-1609.js
+++ b/test/compress/issue-1609.js
@@ -18,9 +18,7 @@ chained_evaluation_1: {
     expect: {
         (function() {
             (function() {
-                var c;
-                c = f(1);
-                c.bar = 1;
+                f(1).bar = 1;
             })();
         })();
     }
@@ -46,9 +44,8 @@ chained_evaluation_2: {
     expect: {
         (function() {
             (function() {
-                var c, b = "long piece of string";
-                c = f(b);
-                c.bar = b;
+                var b = "long piece of string";
+                f(b).bar = b;
             })();
         })();
     }

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