support default values (#4442)
authorAlex Lam S.L <alexlamsl@gmail.com>
Wed, 23 Dec 2020 22:22:55 +0000 (22:22 +0000)
committerGitHub <noreply@github.com>
Wed, 23 Dec 2020 22:22:55 +0000 (06:22 +0800)
lib/ast.js
lib/compress.js
lib/output.js
lib/parse.js
lib/transform.js
test/compress/default-values.js [new file with mode: 0644]
test/compress/destructured.js
test/mocha/getter-setter.js
test/reduce.js
test/ufuzz/index.js

index f319a42..60e5738 100644 (file)
@@ -207,15 +207,25 @@ var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
     $documentation: "The empty statement (empty block or simply a semicolon)"
 }, AST_Statement);
 
-function must_be_expression(node, prop) {
-    if (!(node[prop] instanceof AST_Node)) throw new Error(prop + " must be AST_Node");
-    if (node[prop] instanceof AST_Hole) throw new Error(prop + " cannot be AST_Hole");
-    if (node[prop] instanceof AST_Spread) throw new Error(prop + " cannot be AST_Spread");
-    if (node[prop] instanceof AST_Statement && !is_function(node[prop])) {
-        throw new Error(prop + " cannot be AST_Statement");
+function validate_expression(value, prop, multiple, allow_spread, allow_hole) {
+    multiple = multiple ? "contain" : "be";
+    if (!(value instanceof AST_Node)) throw new Error(prop + " must " + multiple + " AST_Node");
+    if (value instanceof AST_DefaultValue) throw new Error(prop + " cannot " + multiple + " AST_DefaultValue");
+    if (value instanceof AST_Destructured) throw new Error(prop + " cannot " + multiple + " AST_Destructured");
+    if (value instanceof AST_Hole && !allow_hole) throw new Error(prop + " cannot " + multiple + " AST_Hole");
+    if (value instanceof AST_Spread && !allow_spread) throw new Error(prop + " cannot " + multiple + " AST_Spread");
+    if (value instanceof AST_Statement && !is_function(value)) {
+        throw new Error(prop + " cannot " + multiple + " AST_Statement");
+    }
+    if (value instanceof AST_SymbolDeclaration) {
+        throw new Error(prop + " cannot " + multiple + " AST_SymbolDeclaration");
     }
 }
 
+function must_be_expression(node, prop) {
+    validate_expression(node[prop], prop);
+}
+
 var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
     $documentation: "A statement consisting of an expression, i.e. a = 1 + 2",
     $propdoc: {
@@ -534,7 +544,7 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", {
         this.argnames.forEach(function(node) {
             validate_destructured(node, function(node) {
                 if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
-            });
+            }, true);
         });
     },
 }, AST_Scope);
@@ -838,7 +848,6 @@ var AST_Const = DEFNODE("Const", null, {
             validate_destructured(node.name, function(node) {
                 if (!(node instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
             });
-            if (node.value != null) must_be_expression(node, "value");
         });
     },
 }, AST_Definitions);
@@ -851,7 +860,6 @@ var AST_Let = DEFNODE("Let", null, {
             validate_destructured(node.name, function(node) {
                 if (!(node instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet");
             });
-            if (node.value != null) must_be_expression(node, "value");
         });
     },
 }, AST_Definitions);
@@ -864,7 +872,6 @@ var AST_Var = DEFNODE("Var", null, {
             validate_destructured(node.name, function(node) {
                 if (!(node instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
             });
-            if (node.value != null) must_be_expression(node, "value");
         });
     },
 }, AST_Definitions);
@@ -873,7 +880,7 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
     $documentation: "A variable declaration; only appears in a AST_Definitions node",
     $propdoc: {
         name: "[AST_Destructured|AST_SymbolVar] name of the variable",
-        value: "[AST_Node?] initializer, or null of there's no initializer"
+        value: "[AST_Node?] initializer, or null of there's no initializer",
     },
     walk: function(visitor) {
         var node = this;
@@ -882,18 +889,34 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
             if (node.value) node.value.walk(visitor);
         });
     },
+    _validate: function() {
+        if (this.value != null) must_be_expression(this, "value");
+    },
 });
 
 /* -----[ OTHER ]----- */
 
+var AST_DefaultValue = DEFNODE("DefaultValue", "name value", {
+    $documentation: "A default value declaration",
+    $propdoc: {
+        name: "[AST_Destructured|AST_SymbolDeclaration] name of the variable",
+        value: "[AST_Node] value to assign if variable is `undefined`",
+    },
+    walk: function(visitor) {
+        var node = this;
+        visitor.visit(node, function() {
+            node.name.walk(visitor);
+            node.value.walk(visitor);
+        });
+    },
+    _validate: function() {
+        must_be_expression(this, "value");
+    },
+});
+
 function must_be_expressions(node, prop, allow_spread, allow_hole) {
     node[prop].forEach(function(node) {
-        if (!(node instanceof AST_Node)) throw new Error(prop + " must be AST_Node[]");
-        if (!allow_hole && node instanceof AST_Hole) throw new Error(prop + " cannot be AST_Hole");
-        if (!allow_spread && node instanceof AST_Spread) throw new Error(prop + " cannot be AST_Spread");
-        if (node instanceof AST_Statement && !is_function(node)) {
-            throw new Error(prop + " cannot contain AST_Statement");
-        }
+        validate_expression(node, prop, true, allow_spread, allow_hole);
     });
 }
 
@@ -1048,7 +1071,7 @@ var AST_Binary = DEFNODE("Binary", "operator left right", {
         });
     },
     _validate: function() {
-        must_be_expression(this, "left");
+        if (!(this instanceof AST_Assign)) must_be_expression(this, "left");
         if (typeof this.operator != "string") throw new Error("operator must be string");
         must_be_expression(this, "right");
     },
@@ -1131,12 +1154,13 @@ var AST_Destructured = DEFNODE("Destructured", null, {
     $documentation: "Base class for destructured literal",
 });
 
-function validate_destructured(node, check) {
+function validate_destructured(node, check, allow_default) {
+    if (node instanceof AST_DefaultValue && allow_default) return validate_destructured(node.name, check);
     if (node instanceof AST_DestructuredArray) return node.elements.forEach(function(node) {
-        if (!(node instanceof AST_Hole)) validate_destructured(node, check);
+        if (!(node instanceof AST_Hole)) validate_destructured(node, check, true);
     });
     if (node instanceof AST_DestructuredObject) return node.properties.forEach(function(prop) {
-        validate_destructured(prop.value, check);
+        validate_destructured(prop.value, check, true);
     });
     check(node);
 }
@@ -1174,7 +1198,7 @@ var AST_DestructuredKeyVal = DEFNODE("DestructuredKeyVal", "key value", {
             if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
             must_be_expression(this, "key");
         }
-        must_be_expression(this, "value");
+        if (!(this.value instanceof AST_Node)) throw new Error("value must be AST_Node");
     },
 });
 
index 13b7306..23ce712 100644 (file)
@@ -56,6 +56,7 @@ function Compressor(options, false_by_default) {
         comparisons     : !false_by_default,
         conditionals    : !false_by_default,
         dead_code       : !false_by_default,
+        default_values  : !false_by_default,
         directives      : !false_by_default,
         drop_console    : false,
         drop_debugger   : !false_by_default,
@@ -607,6 +608,20 @@ merge(Compressor.prototype, {
 
         function scan_declaration(tw, lhs, fixed, visit) {
             var scanner = new TreeWalker(function(node) {
+                if (node instanceof AST_DefaultValue) {
+                    reset_flags(node);
+                    push(tw);
+                    node.value.walk(tw);
+                    pop(tw);
+                    var save = fixed;
+                    fixed = function() {
+                        var value = save();
+                        return is_undefined(value) ? make_sequence(node, [ value, node.value ]) : node.name;
+                    };
+                    node.name.walk(scanner);
+                    fixed = save;
+                    return true;
+                }
                 if (node instanceof AST_DestructuredArray) {
                     reset_flags(node);
                     var save = fixed;
@@ -1184,6 +1199,11 @@ merge(Compressor.prototype, {
     AST_Node.DEFMETHOD("convert_symbol", noop);
     AST_Destructured.DEFMETHOD("convert_symbol", function(type, process) {
         return this.transform(new TreeTransformer(function(node, descend) {
+            if (node instanceof AST_DefaultValue) {
+                node = node.clone();
+                node.name = node.name.transform(this);
+                return node;
+            }
             if (node instanceof AST_Destructured) {
                 node = node.clone();
                 descend(node, this);
@@ -1205,8 +1225,13 @@ merge(Compressor.prototype, {
     AST_SymbolDeclaration.DEFMETHOD("convert_symbol", convert_symbol);
     AST_SymbolRef.DEFMETHOD("convert_symbol", convert_symbol);
 
-    AST_Destructured.DEFMETHOD("mark_symbol", function(process, tw) {
+    function mark_destructured(process, tw) {
         var marker = new TreeWalker(function(node) {
+            if (node instanceof AST_DefaultValue) {
+                node.value.walk(tw);
+                node.name.walk(marker);
+                return true;
+            }
             if (node instanceof AST_DestructuredKeyVal) {
                 if (node.key instanceof AST_Node) node.key.walk(tw);
                 node.value.walk(marker);
@@ -1215,7 +1240,9 @@ merge(Compressor.prototype, {
             return process(node);
         });
         this.walk(marker);
-    });
+    }
+    AST_DefaultValue.DEFMETHOD("mark_symbol", mark_destructured);
+    AST_Destructured.DEFMETHOD("mark_symbol", mark_destructured);
     function mark_symbol(process) {
         return process(this);
     }
@@ -1229,6 +1256,10 @@ merge(Compressor.prototype, {
         var found = false;
         var tw = new TreeWalker(function(node) {
             if (found) return true;
+            if (node instanceof AST_DefaultValue) {
+                node.name.walk(tw);
+                return true;
+            }
             if (node instanceof AST_DestructuredKeyVal) {
                 if (!allow_computed_keys && node.key instanceof AST_Node) return found = true;
                 node.value.walk(tw);
@@ -1658,7 +1689,7 @@ merge(Compressor.prototype, {
                     var assign_used = false;
                     var can_replace = !args || !hit;
                     if (!can_replace) {
-                        for (var j = scope.argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) {
+                        for (var j = candidate.index + 1; !abort && j < args.length; j++) {
                             args[j].transform(scanner);
                         }
                         can_replace = true;
@@ -1895,9 +1926,14 @@ merge(Compressor.prototype, {
                     for (var i = len; --i >= 0;) {
                         var sym = fn.argnames[i];
                         var arg = iife.args[i];
+                        var value;
+                        if (sym instanceof AST_DefaultValue) {
+                            value = sym.value;
+                            sym = sym.name;
+                        }
                         args.unshift(make_node(AST_VarDef, sym, {
                             name: sym,
-                            value: arg
+                            value: value ? arg ? make_sequence(iife, [ arg, value ]) : value : arg,
                         }));
                         if (sym instanceof AST_Destructured) {
                             if (!sym.match_symbol(return_false)) continue;
@@ -1906,17 +1942,21 @@ merge(Compressor.prototype, {
                         }
                         if (sym.name in names) continue;
                         names[sym.name] = true;
-                        if (!arg) {
+                        if (value) arg = !arg || is_undefined(arg) ? value : null;
+                        if (!arg && !value) {
                             arg = make_node(AST_Undefined, sym).transform(compressor);
                         } else if (arg instanceof AST_Lambda && arg.pinned()) {
                             arg = null;
-                        } else {
+                        } else if (arg) {
                             arg.walk(tw);
                         }
-                        if (arg) candidates.unshift([ make_node(AST_VarDef, sym, {
+                        if (!arg) continue;
+                        var candidate = make_node(AST_VarDef, sym, {
                             name: sym,
                             value: arg
-                        }) ]);
+                        });
+                        candidate.index = i;
+                        candidates.unshift([ candidate ]);
                     }
                 }
             }
@@ -2310,14 +2350,22 @@ merge(Compressor.prototype, {
             }
 
             function remove_candidate(expr) {
-                if (expr.name instanceof AST_SymbolFunarg) {
-                    var index = compressor.self().argnames.indexOf(expr.name);
-                    var args = compressor.parent().args;
-                    if (args[index]) {
-                        args[index] = make_node(AST_Number, args[index], {
+                var index = expr.index;
+                if (index >= 0) {
+                    var argname = scope.argnames[index];
+                    if (argname instanceof AST_DefaultValue) {
+                        argname.value = make_node(AST_Number, argname, {
                             value: 0
                         });
-                        expr.name.definition().fixed = false;
+                        argname.name.definition().fixed = false;
+                    } else {
+                        var args = compressor.parent().args;
+                        if (args[index]) {
+                            args[index] = make_node(AST_Number, args[index], {
+                                value: 0
+                            });
+                            argname.definition().fixed = false;
+                        }
                     }
                     return true;
                 }
@@ -3097,7 +3145,7 @@ merge(Compressor.prototype, {
             || node instanceof AST_Undefined
             || node instanceof AST_UnaryPrefix
                 && node.operator == "void"
-                && !node.expression.has_side_effects(compressor);
+                && !(compressor && node.expression.has_side_effects(compressor));
     }
 
     // is_truthy()
@@ -4077,10 +4125,18 @@ merge(Compressor.prototype, {
                 if (fn.evaluating) return this;
                 if (fn.name && fn.name.definition().recursive_refs > 0) return this;
                 if (this.is_expr_pure(compressor)) return this;
-                if (!all(fn.argnames, function(sym) {
+                var args = eval_args(this.args);
+                if (!all(fn.argnames, function(sym, index) {
+                    if (sym instanceof AST_DefaultValue) {
+                        if (!args) return false;
+                        if (args[index] !== undefined) return false;
+                        var value = sym.value._eval(compressor, ignore_side_effects, cached, depth);
+                        if (value === sym.value) return false;
+                        args[index] = value;
+                        sym = sym.name;
+                    }
                     return !(sym instanceof AST_Destructured);
                 })) return this;
-                var args = eval_args(this.args);
                 if (!args && !ignore_side_effects) return this;
                 var stat = fn.first_statement();
                 if (!(stat instanceof AST_Return)) {
@@ -4104,9 +4160,10 @@ merge(Compressor.prototype, {
                 if (!val) return;
                 var cached_args = [];
                 if (!args || all(fn.argnames, function(sym, i) {
-                    var value = args[i];
+                    if (sym instanceof AST_DefaultValue) sym = sym.name;
                     var def = sym.definition();
                     if (def.orig[def.orig.length - 1] !== sym) return false;
+                    var value = args[i];
                     def.references.forEach(function(node) {
                         node._eval = function() {
                             return value;
@@ -5340,32 +5397,35 @@ merge(Compressor.prototype, {
         var calls_to_drop_args = [];
         var fns_with_marked_args = [];
         var trimmer = new TreeTransformer(function(node) {
+            if (node instanceof AST_DefaultValue) return trim_default(tt, trimmer, node);
             if (node instanceof AST_DestructuredArray) {
                 var trim = true;
                 for (var i = node.elements.length; --i >= 0;) {
-                    var sym = node.elements[i];
-                    if (!(sym instanceof AST_SymbolDeclaration)) {
-                        node.elements[i] = sym.transform(trimmer);
-                        trim = false;
-                    } else if (sym.definition().id in in_use_ids) {
+                    var element = node.elements[i].transform(trimmer);
+                    if (element) {
+                        node.elements[i] = element;
                         trim = false;
                     } else if (trim) {
                         node.elements.pop();
                     } else {
-                        node.elements[i] = make_node(AST_Hole, sym);
+                        node.elements[i] = make_node(AST_Hole, node.elements[i]);
                     }
                 }
                 return node;
             }
             if (node instanceof AST_DestructuredKeyVal) {
-                if (!(node.value instanceof AST_SymbolDeclaration)) {
-                    node.value = node.value.transform(trimmer);
-                    return node;
-                }
-                if (typeof node.key != "string") return node;
-                if (node.value.definition().id in in_use_ids) return node;
-                return List.skip;
+                var retain = false;
+                if (node.key instanceof AST_Node) {
+                    node.key = node.key.transform(tt);
+                    retain = node.key.has_side_effects(compressor);
+                }
+                if (retain && is_decl(node.value)) return node;
+                var value = node.value.transform(trimmer);
+                if (!value) return List.skip;
+                node.value = value;
+                return node;
             }
+            if (node instanceof AST_SymbolDeclaration) return node.definition().id in in_use_ids ? node : null;
         });
         var tt = new TreeTransformer(function(node, descend, in_list) {
             var parent = tt.parent();
@@ -5432,21 +5492,28 @@ merge(Compressor.prototype, {
                     var trim = compressor.drop_fargs(node, parent);
                     for (var a = node.argnames, i = a.length; --i >= 0;) {
                         var sym = a[i];
-                        if (sym instanceof AST_Destructured) {
-                            sym.transform(trimmer);
-                            trim = false;
+                        if (!(sym instanceof AST_SymbolFunarg)) {
+                            var arg = sym.transform(trimmer);
+                            if (arg) {
+                                trim = false;
+                            } else if (trim) {
+                                log(sym.name, "Dropping unused function argument {name}");
+                                a.pop();
+                            } else {
+                                sym.name.__unused = true;
+                                a[i] = sym.name;
+                            }
                             continue;
                         }
                         var def = sym.definition();
                         if (def.id in in_use_ids) {
                             trim = false;
                             if (indexOf_assign(def, sym) < 0) sym.__unused = null;
+                        } else if (trim) {
+                            log(sym, "Dropping unused function argument {name}");
+                            a.pop();
                         } else {
                             sym.__unused = true;
-                            if (trim) {
-                                log(sym, "Dropping unused function argument {name}");
-                                a.pop();
-                            }
                         }
                     }
                     fns_with_marked_args.push(node);
@@ -5469,6 +5536,7 @@ merge(Compressor.prototype, {
                     if (def.name instanceof AST_Destructured) {
                         var value = def.value;
                         var trimmer = new TreeTransformer(function(node) {
+                            if (node instanceof AST_DefaultValue) return trim_default(tt, trimmer, node);
                             if (node instanceof AST_DestructuredArray) {
                                 var save = value;
                                 if (value instanceof AST_SymbolRef) value = value.fixed_value();
@@ -5514,7 +5582,7 @@ merge(Compressor.prototype, {
                                         value = values && values[prop.key];
                                         retain = false;
                                     }
-                                    if (retain && prop.value instanceof AST_SymbolDeclaration) {
+                                    if (retain && is_decl(prop.value)) {
                                         properties.push(prop);
                                     } else {
                                         var newValue = prop.value.transform(trimmer);
@@ -5962,6 +6030,38 @@ merge(Compressor.prototype, {
                 return true;
             }
         }
+
+        function is_decl(node) {
+            return (node instanceof AST_DefaultValue ? node.name : node) instanceof AST_SymbolDeclaration;
+        }
+
+        function trim_default(tt, trimmer, node) {
+            node.value = node.value.transform(tt);
+            var name = node.name.transform(trimmer);
+            if (!name) {
+                var value = node.value.drop_side_effect_free(compressor);
+                if (!value) return null;
+                name = node.name;
+                if (name instanceof AST_Destructured) {
+                    name = name.clone();
+                    name[name instanceof AST_DestructuredArray ? "elements" : "properties"] = [];
+                    if (!(value instanceof AST_Array || value.is_string(compressor)
+                        || name instanceof AST_DestructuredObject
+                            && (value instanceof AST_Object
+                                || value.is_boolean(compressor)
+                                || value.is_number(compressor)))) {
+                        value = make_node(AST_Array, value, {
+                            elements: [ value ],
+                        });
+                    }
+                    node.name = name;
+                } else {
+                    log(name, "Side effects in default value of unused variable {name}");
+                }
+                node.value = value;
+            }
+            return node;
+        }
     });
 
     AST_Scope.DEFMETHOD("hoist_declarations", function(compressor) {
@@ -6168,7 +6268,8 @@ merge(Compressor.prototype, {
             if (!(exp instanceof AST_Lambda)) return;
             if (exp.uses_arguments || exp.pinned()) return;
             var sym = exp.argnames[parent.args.indexOf(this)];
-            if (sym && !all_bool(sym.definition(), bool_returns, compressor)) return;
+            if (sym instanceof AST_DefaultValue) sym = sym.name;
+            if (sym instanceof AST_SymbolFunarg && !all_bool(sym.definition(), bool_returns, compressor)) return;
         } else if (parent.TYPE == "Call") {
             compressor.pop();
             var in_bool = compressor.in_boolean_context();
@@ -7447,6 +7548,11 @@ merge(Compressor.prototype, {
         var side_effects = [];
         for (var i = 0; i < args.length; i++) {
             var argname = fn.argnames[i];
+            if (compressor.option("default_values")
+                && argname instanceof AST_DefaultValue
+                && args[i].is_defined(compressor)) {
+                fn.argnames[i] = argname = argname.name;
+            }
             if (!argname || "__unused" in argname) {
                 var node = args[i].drop_side_effect_free(compressor);
                 if (drop_fargs(argname)) {
@@ -7779,22 +7885,31 @@ merge(Compressor.prototype, {
             }
         }
         var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
-        var is_func = fn instanceof  AST_Arrow || fn instanceof AST_Defun || fn instanceof AST_Function;
+        var is_func = fn instanceof AST_Arrow || fn instanceof AST_Defun || fn instanceof AST_Function;
         var stat = is_func && fn.first_statement();
-        var can_inline = is_func
-            && compressor.option("inline")
-            && !self.is_expr_pure(compressor)
-            && all(fn.argnames, function(argname) {
-                return !(argname instanceof AST_Destructured);
-            })
-            && all(self.args, function(arg) {
-                return !(arg instanceof AST_Spread);
-            });
+        var has_default = false;
+        var can_drop = is_func && all(fn.argnames, function(argname, index) {
+            if (argname instanceof AST_DefaultValue) {
+                has_default = true;
+                var arg = self.args[index];
+                if (arg && !is_undefined(arg)) return false;
+                var abort = false;
+                argname.value.walk(new TreeWalker(function(node) {
+                    if (abort) return true;
+                    if (node instanceof AST_SymbolRef && fn.find_variable(node.name) === node.definition()) {
+                        return abort = true;
+                    }
+                }));
+                if (abort) return false;
+                argname = argname.name;
+            }
+            return !(argname instanceof AST_Destructured);
+        });
+        var can_inline = can_drop && compressor.option("inline") && !self.is_expr_pure(compressor);
         if (can_inline && stat instanceof AST_Return) {
             var value = stat.value;
             if (exp === fn && (!value || value.is_constant_expression() && safe_from_await(value))) {
-                var args = self.args.concat(value || make_node(AST_Undefined, self));
-                return make_sequence(self, args).optimize(compressor);
+                return make_sequence(self, convert_args(value)).optimize(compressor);
             }
         }
         if (is_func) {
@@ -7805,6 +7920,9 @@ merge(Compressor.prototype, {
                 && !(fn.name && fn instanceof AST_Function)
                 && (exp === fn || !recursive_ref(compressor, def = exp.definition())
                     && fn.is_constant_expression(find_scope(compressor)))
+                && all(self.args, function(arg) {
+                    return !(arg instanceof AST_Spread);
+                })
                 && (value = can_flatten_body(stat))
                 && !fn.contains_this()) {
                 var replacing = exp === fn || def.single_use && def.references.length - def.replaced == 1;
@@ -7848,19 +7966,11 @@ merge(Compressor.prototype, {
                 }
             }
             if (compressor.option("side_effects")
+                && can_drop
                 && all(fn.body, is_empty)
                 && (fn !== exp || fn_name_unused(fn, compressor))
-                && !(fn instanceof AST_Arrow && fn.value)
-                && all(fn.argnames, function(argname) {
-                    return !(argname instanceof AST_Destructured);
-                })) {
-                var args = self.args.map(function(arg) {
-                    return arg instanceof AST_Spread ? make_node(AST_Array, arg, {
-                        elements: [ arg ],
-                    }) : arg;
-                });
-                args.push(make_node(AST_Undefined, self));
-                return make_sequence(self, args).optimize(compressor);
+                && !(fn instanceof AST_Arrow && fn.value)) {
+                return make_sequence(self, convert_args()).optimize(compressor);
             }
         }
         if (compressor.option("drop_console")) {
@@ -7881,6 +7991,19 @@ merge(Compressor.prototype, {
         }
         return try_evaluate(compressor, self);
 
+        function convert_args(value) {
+            var args = self.args.map(function(arg) {
+                return arg instanceof AST_Spread ? make_node(AST_Array, arg, {
+                    elements: [ arg ],
+                }) : arg;
+            });
+            fn.argnames.forEach(function(argname, index) {
+                if (argname instanceof AST_DefaultValue) args.push(argname.value);
+            });
+            args.push(value || make_node(AST_Undefined, self));
+            return args;
+        }
+
         function safe_from_await(node) {
             if (!is_async(scope || compressor.find_parent(AST_Scope))) return true;
             var safe = true;
@@ -7948,6 +8071,7 @@ merge(Compressor.prototype, {
         }
 
         function can_substitute_directly() {
+            if (has_default) return;
             if (var_assigned) return;
             if (compressor.option("inline") < 2 && fn.argnames.length) return;
             if (!fn.variables.all(function(def) {
@@ -8017,6 +8141,7 @@ merge(Compressor.prototype, {
             for (var i = 0; i < fn.argnames.length; i++) {
                 var arg = fn.argnames[i];
                 if (arg.__unused) continue;
+                if (arg instanceof AST_DefaultValue) arg = arg.name;
                 if (!safe_to_inject || var_exists(defined, arg.name)) return false;
                 used[arg.name] = true;
                 if (in_loop) in_loop.push(arg.definition());
@@ -8115,6 +8240,10 @@ merge(Compressor.prototype, {
             for (i = len; --i >= 0;) {
                 var name = fn.argnames[i];
                 var value = self.args[i];
+                if (name instanceof AST_DefaultValue) {
+                    value = value ? make_sequence(self, [ value, name.value ]) : name.value;
+                    name = name.name;
+                }
                 if (name.__unused || scope.var_names()[name.name]) {
                     if (value) expressions.push(value);
                 } else {
@@ -8148,6 +8277,7 @@ merge(Compressor.prototype, {
                     }
                     append_var(decls, expressions, name, var_def.value);
                     if (in_loop && all(fn.argnames, function(argname) {
+                        if (argname instanceof AST_DefaultValue) argname = argname.name;
                         return argname.name != name.name;
                     })) {
                         var def = fn.variables.get(name.name);
@@ -9936,13 +10066,13 @@ merge(Compressor.prototype, {
             var argname = fn.argnames[index];
             if (def.deleted && def.deleted[index]) {
                 argname = null;
-            } else if (argname instanceof AST_Destructured) {
+            } else if (argname && !(argname instanceof AST_SymbolFunarg)) {
                 argname = null;
             } else if (argname && (compressor.has_directive("use strict")
                 || fn.name
                 || !(fn_parent instanceof AST_Call && index < fn_parent.args.length)
                 || !all(fn.argnames, function(argname) {
-                    return !(argname instanceof AST_Destructured);
+                    return argname instanceof AST_SymbolFunarg;
                 }))) {
                 var arg_def = argname.definition();
                 if (!compressor.option("reduce_vars")
index 3d8d74c..cd36f74 100644 (file)
@@ -702,6 +702,8 @@ function OutputStream(options) {
             // (false, true) ? (a = 10, b = 20) : (c = 30)
             // ==> 20 (side effect, set a := 10 and b := 20)
             || p instanceof AST_Conditional
+            // [ a = (1, 2) ] = [] ==> a == 2
+            || p instanceof AST_DefaultValue
             // { [(1, 2)]: 3 }[2] ==> 3
             // { foo: (1, 2) }.foo ==> 2
             || p instanceof AST_DestructuredKeyVal
@@ -1218,6 +1220,15 @@ function OutputStream(options) {
         }
     });
 
+    DEFPRINT(AST_DefaultValue, function(output) {
+        var self = this;
+        self.name.print(output);
+        output.space();
+        output.print("=");
+        output.space();
+        self.value.print(output);
+    });
+
     /* -----[ other expressions ]----- */
     function print_call_args(self, output) {
         if (self.expression instanceof AST_Call || self.expression instanceof AST_Lambda) {
index 87293ca..c8ad531 100644 (file)
@@ -1041,11 +1041,30 @@ function parse($TEXT, options) {
     function to_funarg(node) {
         if (node instanceof AST_Array) return new AST_DestructuredArray({
             start: node.start,
-            elements: node.elements.map(function(node) {
-                return node instanceof AST_Hole ? node : to_funarg(node);
-            }),
+            elements: node.elements.map(to_funarg),
+            end: node.end,
+        });
+        if (node instanceof AST_Assign) return new AST_DefaultValue({
+            start: node.start,
+            name: to_funarg(node.left),
+            value: node.right,
             end: node.end,
         });
+        if (node instanceof AST_DefaultValue) {
+            node.name = to_funarg(node.name);
+            return node;
+        }
+        if (node instanceof AST_DestructuredArray) {
+            node.elements = node.elements.map(to_funarg);
+            return node;
+        }
+        if (node instanceof AST_DestructuredObject) {
+            node.properties.forEach(function(prop) {
+                prop.value = to_funarg(prop.value);
+            });
+            return node;
+        }
+        if (node instanceof AST_Hole) return node;
         if (node instanceof AST_Object) return new AST_DestructuredObject({
             start: node.start,
             properties: node.properties.map(function(prop) {
@@ -1122,7 +1141,7 @@ function parse($TEXT, options) {
         var was_funarg = S.in_funarg;
         S.in_funarg = S.in_function;
         var argnames = expr_list(")", !options.strict, false, function() {
-            return maybe_destructured(AST_SymbolFunarg);
+            return maybe_default(AST_SymbolFunarg);
         });
         S.in_funarg = was_funarg;
         var loop = S.in_loop;
@@ -1468,6 +1487,32 @@ function parse($TEXT, options) {
                 }));
                 continue;
             }
+            if (is_token(peek(), "operator", "=")) {
+                var name = as_symbol(AST_SymbolRef);
+                next();
+                a.push(new AST_ObjectKeyVal({
+                    start: start,
+                    key: start.value,
+                    value: new AST_Assign({
+                        start: start,
+                        left: name,
+                        operator: "=",
+                        right: maybe_assign(),
+                        end: prev(),
+                    }),
+                    end: prev(),
+                }));
+                continue;
+            }
+            if (is_token(peek(), "punc", ",") || is_token(peek(), "punc", "}")) {
+                a.push(new AST_ObjectKeyVal({
+                    start: start,
+                    key: start.value,
+                    value: as_symbol(AST_SymbolRef),
+                    end: prev(),
+                }));
+                continue;
+            }
             var key = as_property_key();
             if (is("punc", "(")) {
                 var func_start = S.token;
@@ -1492,15 +1537,6 @@ function parse($TEXT, options) {
                 }));
                 continue;
             }
-            if (is("punc", ",") || is("punc", "}")) {
-                a.push(new AST_ObjectKeyVal({
-                    start: start,
-                    key: key,
-                    value: _make_symbol(AST_SymbolRef, start),
-                    end: prev(),
-                }));
-                continue;
-            }
             if (start.type == "name") switch (key) {
               case "async":
                 key = as_property_key();
@@ -1601,7 +1637,7 @@ function parse($TEXT, options) {
             return new AST_DestructuredArray({
                 start: start,
                 elements: expr_list("]", !options.strict, true, function() {
-                    return maybe_destructured(type);
+                    return maybe_default(type);
                 }),
                 end: prev(),
             });
@@ -1620,15 +1656,25 @@ function parse($TEXT, options) {
                     a.push(new AST_DestructuredKeyVal({
                         start: key_start,
                         key: key,
-                        value: maybe_destructured(type),
+                        value: maybe_default(type),
                         end: prev(),
                     }));
                     continue;
                 }
+                var name = as_symbol(type);
+                if (is("operator", "=")) {
+                    next();
+                    name = new AST_DefaultValue({
+                        start: name.start,
+                        name: name,
+                        value: maybe_assign(),
+                        end: prev(),
+                    });
+                }
                 a.push(new AST_DestructuredKeyVal({
                     start: key_start,
                     key: key_start.value,
-                    value: as_symbol(type),
+                    value: name,
                     end: prev(),
                 }));
             }
@@ -1642,6 +1688,19 @@ function parse($TEXT, options) {
         return as_symbol(type);
     }
 
+    function maybe_default(type) {
+        var start = S.token;
+        var name = maybe_destructured(type);
+        if (!is("operator", "=")) return name;
+        next();
+        return new AST_DefaultValue({
+            start: start,
+            name: name,
+            value: maybe_assign(),
+            end: prev(),
+        });
+    }
+
     function mark_pure(call) {
         var start = call.start;
         var comments = start.comments_before;
@@ -1788,20 +1847,34 @@ function parse($TEXT, options) {
         if (node instanceof AST_Array) {
             var elements = node.elements.map(to_destructured);
             return all(elements, function(node) {
-                return node instanceof AST_Destructured || node instanceof AST_Hole || is_assignable(node);
+                return node instanceof AST_DefaultValue
+                    || node instanceof AST_Destructured
+                    || node instanceof AST_Hole
+                    || is_assignable(node);
             }) ? new AST_DestructuredArray({
                 start: node.start,
                 elements: elements,
                 end: node.end,
             }) : node;
         }
+        if (node instanceof AST_Assign) {
+            var name = to_destructured(node.left);
+            return name instanceof AST_Destructured || is_assignable(name) ? new AST_DefaultValue({
+                start: node.start,
+                name: name,
+                value: node.right,
+                end: node.end,
+            }) : node;
+        }
         if (!(node instanceof AST_Object)) return node;
         var props = [];
         for (var i = 0; i < node.properties.length; i++) {
             var prop = node.properties[i];
             if (!(prop instanceof AST_ObjectKeyVal)) return node;
             var value = to_destructured(prop.value);
-            if (!(value instanceof AST_Destructured || is_assignable(value))) return node;
+            if (!(value instanceof AST_DefaultValue || value instanceof AST_Destructured || is_assignable(value))) {
+                return node;
+            }
             props.push(new AST_DestructuredKeyVal({
                 start: prop.start,
                 key: prop.key,
index 5372cc5..0b92f8b 100644 (file)
@@ -126,6 +126,10 @@ TreeTransformer.prototype = new TreeWalker;
         self.name = self.name.transform(tw);
         if (self.value) self.value = self.value.transform(tw);
     });
+    DEF(AST_DefaultValue, function(self, tw) {
+        self.name = self.name.transform(tw);
+        self.value = self.value.transform(tw);
+    });
     DEF(AST_Lambda, function(self, tw) {
         if (self.name) self.name = self.name.transform(tw);
         self.argnames = do_list(self.argnames, tw);
diff --git a/test/compress/default-values.js b/test/compress/default-values.js
new file mode 100644 (file)
index 0000000..ffc351c
--- /dev/null
@@ -0,0 +1,952 @@
+arrow_1: {
+    input: {
+        console.log(((a = "PASS") => a)());
+    }
+    expect_exact: 'console.log(((a="PASS")=>a)());'
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+arrow_2: {
+    input: {
+        console.log((([ a = "FAIL" ]) => a)([ "PASS" ]));
+    }
+    expect_exact: 'console.log((([a="FAIL"])=>a)(["PASS"]));'
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+arrow_3: {
+    input: {
+        (([ a = console ] = null) => a.log("PASS"))("");
+    }
+    expect_exact: '(([a=console]=null)=>a.log("PASS"))("");'
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+assign: {
+    input: {
+        [ a = "PASS" ] = [];
+        console.log(a);
+    }
+    expect_exact: '[a="PASS"]=[];console.log(a);'
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+declaration_var: {
+    input: {
+        var [ a = "PASS" ] = [ , ];
+        console.log(a);
+    }
+    expect_exact: 'var[a="PASS"]=[,];console.log(a);'
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+declaration_const: {
+    input: {
+        const [ a = "FAIL" ] = [ "PASS" ];
+        console.log(a);
+    }
+    expect_exact: 'const[a="FAIL"]=["PASS"];console.log(a);'
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+declaration_let: {
+    input: {
+        let [ a = "PASS" ] = [ void 42 ];
+        console.log(a);
+    }
+    expect_exact: 'let[a="PASS"]=[void 42];console.log(a);'
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+object_shorthand_assign: {
+    input: {
+        ({ a = "PASS" } = 42);
+        console.log(a);
+    }
+    expect_exact: '({a:a="PASS"}=42);console.log(a);'
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+object_shorthand_declaration: {
+    input: {
+        var { a = "PASS" } = 42;
+        console.log(a);
+    }
+    expect_exact: 'var{a:a="PASS"}=42;console.log(a);'
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+object_shorthand_function: {
+    input: {
+        (function({ a = "PASS" }) {
+            console.log(a);
+        })(42);
+    }
+    expect_exact: '(function({a:a="PASS"}){console.log(a)})(42);'
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+retain_arguments_1: {
+    options = {
+        arguments: true,
+    }
+    input: {
+        console.log(function(a = "FAIL") {
+            return arguments[0];
+        }() || "PASS");
+    }
+    expect: {
+        console.log(function(a = "FAIL") {
+            return arguments[0];
+        }() || "PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+retain_arguments_2: {
+    options = {
+        arguments: true,
+    }
+    input: {
+        console.log(function(a, b = null) {
+            a = "FAIL";
+            return arguments[0];
+        }("PASS", 42));
+    }
+    expect: {
+        console.log(function(a, b = null) {
+            a = "FAIL";
+            return arguments[0];
+        }("PASS", 42));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+process_boolean_returns: {
+    options = {
+        booleans: true,
+        evaluate: true,
+        reduce_vars: true,
+    }
+    input: {
+        console.log(function(a = console.log("FAIL 1")) {
+            return a() ? "PASS" : "FAIL 2";
+        }(function() {
+            return 42;
+        }));
+    }
+    expect: {
+        console.log(function(a = console.log("FAIL 1")) {
+            return a() ? "PASS" : "FAIL 2";
+        }(function() {
+            return 1;
+        }));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+collapse_value_1: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        console.log(function(a = "PASS") {
+            return a;
+        }());
+    }
+    expect: {
+        console.log(function(a) {
+            return "PASS";
+        }());
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+collapse_value_2: {
+    options = {
+        collapse_vars: true,
+        unused: true,
+    }
+    input: {
+        (function(a = console) {
+            return a;
+        })().log("PASS");
+    }
+    expect: {
+        (function(a) {
+            return console;
+        })().log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+flatten_if: {
+    options = {
+        conditionals: true,
+    }
+    input: {
+        if (console.log("PASS")) {
+            var [
+                a = function b() {
+                    for (c in b);
+                },
+            ] = 0;
+        }
+    }
+    expect: {
+        var a;
+        console.log("PASS") && ([
+            a = function b() {
+                for (c in b);
+            },
+        ] = 0);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+maintain_if: {
+    options = {
+        conditionals: true,
+    }
+    input: {
+        if (a)
+            for (;;);
+        else
+            var [ a = "PASS" ] = [];
+        console.log(a);
+    }
+    expect: {
+        if (a)
+            for (;;);
+        else
+            var [ a = "PASS" ] = [];
+        console.log(a);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+reduce_value: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        console.log(function(a = "PASS") {
+            return a;
+        }());
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+evaluate_iife: {
+    options = {
+        evaluate: true,
+    }
+    input: {
+        console.log(function(a = "PASS") {
+            return a;
+        }());
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+unsafe_evaluate_iife_1: {
+    options = {
+        evaluate: true,
+        unsafe: true,
+    }
+    input: {
+        console.log(function([ a ] = []) {
+            return "PASS";
+        }());
+    }
+    expect: {
+        console.log(function([ a ] = []) {
+            return "PASS";
+        }());
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+unsafe_evaluate_iife_2: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        unsafe: true,
+    }
+    input: {
+        console.log(function([ a ] = []) {
+            return a[0];
+        }([ [ "PASS" ] ]));
+    }
+    expect: {
+        console.log(function([ a ] = []) {
+            return a[0];
+        }([ [ "PASS" ] ]));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+inline_direct: {
+    options = {
+        default_values: true,
+        inline: true,
+        unused: true,
+    }
+    input: {
+        console.log(function(a = "FAIL") {
+            return a;
+        }("PASS"));
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+inline_constant: {
+    options = {
+        inline: true,
+    }
+    input: {
+        console.log(function(a = console.log("foo")) {
+            return "bar";
+        }(void console.log("baz")));
+    }
+    expect: {
+        console.log((void console.log("baz"), console.log("foo"), "bar"));
+    }
+    expect_stdout: [
+        "baz",
+        "foo",
+        "bar",
+    ]
+    node_version: ">=6"
+}
+
+inline_function: {
+    options = {
+        default_values: true,
+        inline: true,
+        sequences: true,
+        side_effects: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        (function(a = console.log("foo"), b = console.log("bar")) {
+            console.log("baz");
+        }(void console.log("moo"), 42));
+    }
+    expect: {
+        console.log("moo"),
+        console.log("foo"),
+        console.log("baz");
+    }
+    expect_stdout: [
+        "moo",
+        "foo",
+        "baz",
+    ]
+    node_version: ">=6"
+}
+
+inline_loop_1: {
+    options = {
+        inline: true,
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        while (function f(a = "PASS") {
+            console.log(a);
+        }());
+    }
+    expect: {
+        while (a = "PASS", void console.log(a));
+        var a;
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+inline_loop_2: {
+    options = {
+        inline: true,
+        toplevel: true,
+    }
+    input: {
+        while (function(a = [ "PASS" ]) {
+            var a = function f(b) {
+                console.log(a[b]);
+            }(0);
+        }());
+    }
+    expect: {
+        while (a = [ "PASS" ], a = function f(b) {
+            console.log(a[b]);
+        }(0), void 0) ;
+        var a;
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+drop_empty_iife: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        console.log(function(a = console.log("foo")) {}(void console.log("baz")));
+    }
+    expect: {
+        console.log((console.log("baz"), void console.log("foo")));
+    }
+    expect_stdout: [
+        "baz",
+        "foo",
+        "undefined",
+    ]
+    node_version: ">=6"
+}
+
+retain_empty_iife: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        var a;
+        try {
+            (function(a = a) {})();
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        var a;
+        try {
+            (function(a = a) {})();
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+retain_fargs: {
+    options = {
+        unused: true,
+    }
+    input: {
+        (function([ a = console.log("PASS") ]) {})([]);
+    }
+    expect: {
+        (function([ a = console.log("PASS") ]) {})([]);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+drop_fargs: {
+    options = {
+        keep_fargs: "strict",
+        unused: true,
+    }
+    input: {
+        console.log(function(a = 42, b = console.log("foo"), c = true) {
+            return "bar";
+        }(console.log("baz"), "moo", false));
+    }
+    expect: {
+        console.log(function(b = console.log("foo")) {
+            return "bar";
+        }((console.log("baz"), "moo")));
+    }
+    expect_stdout: [
+        "baz",
+        "bar",
+    ]
+    expect_warnings: [
+        "WARN: Dropping unused function argument c [test/compress/default-values.js:1,61]",
+        "WARN: Side effects in default value of unused variable b [test/compress/default-values.js:1,37]",
+    ]
+    node_version: ">=6"
+}
+
+unused_var_1: {
+    options = {
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var [ a = 42 ] = [ console.log("PASS") ];
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+unused_var_2: {
+    options = {
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var {
+            p: [ a ] = "" + console.log("FAIL"),
+        } = {
+            p: [ console.log("PASS") ],
+        };
+    }
+    expect: {
+        var {
+            p: [] = [ console.log("FAIL") ],
+        } = {
+            p: [ console.log("PASS") ],
+        };
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_var_1: {
+    mangle = {
+        toplevel: false,
+    }
+    input: {
+        var N = 1, [ {
+            pname: p = "x",
+            i: n = N,
+        }, {
+            [p + n]: v,
+        } ] = [ {}, {
+            x1: "PASS",
+        } ];
+        console.log(v);
+    }
+    expect: {
+        var N = 1, [ {
+            pname: p = "x",
+            i: n = N,
+        }, {
+            [p + n]: v,
+        } ] = [ {}, {
+            x1: "PASS",
+        } ];
+        console.log(v);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_var_1_toplevel: {
+    mangle = {
+        toplevel: true,
+    }
+    input: {
+        var N = 1, [ {
+            pname: p = "x",
+            i: n = N,
+        }, {
+            [p + n]: v,
+        } ] = [ {}, {
+            x1: "PASS",
+        } ];
+        console.log(v);
+    }
+    expect: {
+        var o = 1, [ {
+            pname: a = "x",
+            i: e = o,
+        }, {
+            [a + e]: l,
+        } ] = [ {}, {
+            x1: "PASS",
+        } ];
+        console.log(l);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_var_2: {
+    mangle = {
+        toplevel: false,
+    }
+    input: {
+        var N = 1, [ {
+            pname: p = "x",
+            i: n = N,
+        } = {}, {
+            [p + n]: v,
+        } ] = [ , {
+            x1: "PASS",
+        } ];
+        console.log(v);
+    }
+    expect: {
+        var N = 1, [ {
+            pname: p = "x",
+            i: n = N,
+        } = {}, {
+            [p + n]: v,
+        } ] = [ , {
+            x1: "PASS",
+        } ];
+        console.log(v);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_var_2_toplevel: {
+    mangle = {
+        toplevel: true,
+    }
+    input: {
+        var N = 1, [ {
+            pname: p = "x",
+            i: n = N,
+        } = {}, {
+            [p + n]: v,
+        } ] = [ , {
+            x1: "PASS",
+        } ];
+        console.log(v);
+    }
+    expect: {
+        var o = 1, [ {
+            pname: a = "x",
+            i: e = o,
+        } = {}, {
+            [a + e]: l,
+        } ] = [ , {
+            x1: "PASS",
+        } ];
+        console.log(l);
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_function_1: {
+    mangle = {
+        toplevel: false,
+    }
+    input: {
+        var N = 1;
+        (function(o, {
+            pname: p,
+        } = o, {
+            [p + N]: v,
+        } = o) {
+            let N;
+            console.log(v);
+        })({
+            pname: "x",
+            x1: "PASS",
+        });
+    }
+    expect: {
+        var N = 1;
+        (function(n, {
+            pname: e,
+        } = n, {
+            [e + N]: o,
+        } = n) {
+            let a;
+            console.log(o);
+        })({
+            pname: "x",
+            x1: "PASS",
+        });
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_function_1_toplevel: {
+    mangle = {
+        toplevel: true,
+    }
+    input: {
+        var N = 1;
+        (function(o, {
+            pname: p,
+        } = o, {
+            [p + N]: v,
+        } = o) {
+            let N;
+            console.log(v);
+        })({
+            pname: "x",
+            x1: "PASS",
+        });
+    }
+    expect: {
+        var l = 1;
+        (function(n, {
+            pname: e,
+        } = n, {
+            [e + l]: o,
+        } = n) {
+            let a;
+            console.log(o);
+        })({
+            pname: "x",
+            x1: "PASS",
+        });
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_function_2: {
+    mangle = {
+        toplevel: false,
+    }
+    input: {
+        var N = 1;
+        (function({
+            pname: p = "x",
+            i: n = N,
+        }, {
+            [p + n]: v,
+        }) {
+            let N;
+            console.log(v);
+        })({}, {
+            x1: "PASS",
+        });
+    }
+    expect: {
+        var N = 1;
+        (function({
+            pname: n = "x",
+            i: o = N,
+        }, {
+            [n + o]: e,
+        }) {
+            let l;
+            console.log(e);
+        })({}, {
+            x1: "PASS",
+        });
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_function_2_toplevel: {
+    mangle = {
+        toplevel: true,
+    }
+    input: {
+        var N = 1;
+        (function({
+            pname: p = "x",
+            i: n = N,
+        }, {
+            [p + n]: v,
+        }) {
+            let N;
+            console.log(v);
+        })({}, {
+            x1: "PASS",
+        });
+    }
+    expect: {
+        var a = 1;
+        (function({
+            pname: n = "x",
+            i: o = a,
+        }, {
+            [n + o]: e,
+        }) {
+            let l;
+            console.log(e);
+        })({}, {
+            x1: "PASS",
+        });
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_arrow_1: {
+    mangle = {
+        toplevel: false,
+    }
+    input: {
+        var N = 1;
+        ((o, {
+            pname: p,
+        } = o, {
+            [p + N]: v,
+        } = o) => {
+            let N;
+            console.log(v);
+        })({
+            pname: "x",
+            x1: "PASS",
+        });
+    }
+    expect: {
+        var N = 1;
+        ((e, {
+            pname: a,
+        } = e, {
+            [a + N]: l,
+        } = e) => {
+            let n;
+            console.log(l);
+        })({
+            pname: "x",
+            x1: "PASS",
+        });
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_arrow_1_toplevel: {
+    mangle = {
+        toplevel: true,
+    }
+    input: {
+        var N = 1;
+        ((o, {
+            pname: p,
+        } = o, {
+            [p + N]: v,
+        } = o) => {
+            let N;
+            console.log(v);
+        })({
+            pname: "x",
+            x1: "PASS",
+        });
+    }
+    expect: {
+        var o = 1;
+        ((e, {
+            pname: a,
+        } = e, {
+            [a + o]: l,
+        } = e) => {
+            let n;
+            console.log(l);
+        })({
+            pname: "x",
+            x1: "PASS",
+        });
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_arrow_2: {
+    mangle = {
+        toplevel: false,
+    }
+    input: {
+        var N = 1;
+        (({
+            pname: p = "x",
+            i: n = N,
+        }, {
+            [p + n]: v,
+        }) => {
+            let N;
+            console.log(v);
+        })({}, {
+            x1: "PASS",
+        });
+    }
+    expect: {
+        var N = 1;
+        (({
+            pname: e = "x",
+            i: l = N,
+        }, {
+            [e + l]: o,
+        }) => {
+            let a;
+            console.log(o);
+        })({}, {
+            x1: "PASS",
+        });
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
+mangle_arrow_2_toplevel: {
+    mangle = {
+        toplevel: true,
+    }
+    input: {
+        var N = 1;
+        (({
+            pname: p = "x",
+            i: n = N,
+        }, {
+            [p + n]: v,
+        }) => {
+            let N;
+            console.log(v);
+        })({}, {
+            x1: "PASS",
+        });
+    }
+    expect: {
+        var n = 1;
+        (({
+            pname: e = "x",
+            i: l = n,
+        }, {
+            [e + l]: o,
+        }) => {
+            let a;
+            console.log(o);
+        })({}, {
+            x1: "PASS",
+        });
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
index 17aa786..66aebc1 100644 (file)
@@ -691,6 +691,28 @@ funarg_inline: {
     node_version: ">=6"
 }
 
+process_boolean_returns: {
+    options = {
+        booleans: true,
+    }
+    input: {
+        console.log(function({ length }) {
+            return length ? "FAIL" : "PASS";
+        }(function() {
+            return 42;
+        }));
+    }
+    expect: {
+        console.log(function({ length }) {
+            return length ? "FAIL" : "PASS";
+        }(function() {
+            return 42;
+        }));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
+
 simple_const: {
     options = {
         evaluate: true,
index ec811aa..fc78a25 100644 (file)
@@ -3,7 +3,7 @@ var UglifyJS = require("../node");
 
 describe("Getters and setters", function() {
     it("Should not accept operator symbols as getter/setter name", function() {
-        var illegalOperators = [
+        [
             "++",
             "--",
             "+",
@@ -42,43 +42,26 @@ describe("Getters and setters", function() {
             "&=",
             "&&",
             "||"
-        ];
-        var generator = function() {
-            var results = [];
-
-            for (var i in illegalOperators) {
-                results.push({
-                    code: "var obj = { get " + illegalOperators[i] + "() { return test; }};",
-                    operator: illegalOperators[i],
-                    method: "get"
-                });
-                results.push({
-                    code: "var obj = { set " + illegalOperators[i] + "(value) { test = value}};",
-                    operator: illegalOperators[i],
-                    method: "set"
-                });
-            }
-
-            return results;
-        };
-        var testCase = function(data) {
-            return function() {
-                UglifyJS.parse(data.code);
-            };
-        };
-        var fail = function(data) {
-            return function(e) {
+        ].reduce(function(tests, illegalOperator) {
+            tests.push({
+                code: "var obj = { get " + illegalOperator + "() { return test; }};",
+                operator: illegalOperator,
+            });
+            tests.push({
+                code: "var obj = { set " + illegalOperator + "(value) { test = value; }};",
+                operator: illegalOperator,
+            });
+            return tests;
+        }, []).forEach(function(test) {
+            assert.throws(function() {
+                UglifyJS.parse(test.code);
+            }, test.operator == "=" ? function(e) {
+                return e instanceof UglifyJS.JS_Parse_Error
+                    && /^Unexpected token: punc «{», expected: punc «.*?»$/.test(e.message);
+            } : function(e) {
                 return e instanceof UglifyJS.JS_Parse_Error
-                    && e.message === "Unexpected token: operator «" + data.operator + "»";
-            };
-        };
-        var errorMessage = function(data) {
-            return "Expected but didn't get a syntax error while parsing following line:\n" + data.code;
-        };
-        var tests = generator();
-        for (var i = 0; i < tests.length; i++) {
-            var test = tests[i];
-            assert.throws(testCase(test), fail(test), errorMessage(test));
-        }
+                    && e.message === "Unexpected token: operator «" + test.operator + "»";
+            }, "Expected but didn't get a syntax error while parsing following line:\n" + test.code);
+        });
     });
 });
index bcb30fe..d0797f8 100644 (file)
@@ -211,6 +211,11 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
                     node.alternative,
                 ][ ((node.start._permute += step) * steps | 0) % 3 ];
             }
+            else if (node instanceof U.AST_DefaultValue) {
+                node.start._permute++;
+                CHANGED = true;
+                return node.name;
+            }
             else if (node instanceof U.AST_Defun) {
                 switch (((node.start._permute += step) * steps | 0) % 2) {
                   case 0:
index d554d64..1d24798 100644 (file)
@@ -137,6 +137,7 @@ var SUPPORT = function(matrix) {
     catch_omit_var: "try {} catch {}",
     computed_key: "({[0]: 0});",
     const_block: "var a; { const a = 0; }",
+    default_value: "[ a = 0 ] = [];",
     destructuring: "[] = [];",
     let: "let a;",
     spread: "[...[]];",
@@ -425,18 +426,35 @@ function createArgs(recurmax, stmtDepth, canThrow) {
 function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was_async) {
     var avoid = [];
     var len = unique_vars.length;
-    var pairs = createPairs(recurmax);
+    var pairs = createPairs(recurmax, !nameLenBefore);
     unique_vars.length = len;
     return pairs;
 
-    function createAssignmentValue(recurmax) {
+    function fill(nameFn, valueFn) {
         var save_async = async;
-        if (was_async != null) async = was_async;
+        if (was_async != null) {
+            async = false;
+            if (save_async || was_async) addAvoidVar("await");
+        }
+        avoid.forEach(addAvoidVar);
         var save_vars = nameLenBefore && VAR_NAMES.splice(nameLenBefore);
-        var value = nameLenBefore && rng(2) ? createValue() : createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
+        if (nameFn) nameFn();
+        if (was_async != null) {
+            async = was_async;
+            if (save_async || was_async) removeAvoidVar("await");
+        }
+        if (valueFn) valueFn();
         if (save_vars) [].push.apply(VAR_NAMES, save_vars);
+        avoid.forEach(removeAvoidVar);
         async = save_async;
-        return value;
+    }
+
+    function createAssignmentValue(recurmax) {
+        return nameLenBefore && rng(2) ? createValue() : createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
+    }
+
+    function createDefaultValue(recurmax, noDefault) {
+        return !noDefault && SUPPORT.default_value && rng(20) == 0 ? " = " +  createAssignmentValue(recurmax) : "";
     }
 
     function createKey(recurmax, keys) {
@@ -459,20 +477,22 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
         return name;
     }
 
-    function createPairs(recurmax) {
+    function createPairs(recurmax, noDefault) {
         var names = [], values = [];
         var m = rng(4), n = rng(4);
         if (!nameLenBefore) m = Math.max(m, n, 1);
         for (var i = Math.max(m, n); --i >= 0;) {
             if (i < m && i < n) {
-                createDestructured(recurmax, names, values);
-                continue;
-            }
-            if (i < m) {
-                names.unshift(createName());
-            }
-            if (i < n) {
-                values.unshift(createAssignmentValue(recurmax));
+                createDestructured(recurmax, noDefault, names, values);
+            } else if (i < m) {
+                var name = createName();
+                fill(function() {
+                    names.unshift(name + createDefaultValue(recurmax, noDefault));
+                });
+            } else {
+                fill(null, function() {
+                    values.unshift(createAssignmentValue(recurmax));
+                });
             }
         }
         return {
@@ -481,7 +501,7 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
         };
     }
 
-    function createDestructured(recurmax, names, values) {
+    function createDestructured(recurmax, noDefault, names, values) {
         switch (rng(20)) {
           case 0:
             if (--recurmax < 0) {
@@ -489,20 +509,25 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
                 values.unshift('""');
             } else {
                 var pairs = createPairs(recurmax);
-                while (!rng(10)) {
-                    var index = rng(pairs.names.length + 1);
-                    pairs.names.splice(index, 0, "");
-                    if (index < pairs.values.length) {
-                        pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : "");
-                    } else switch (rng(5)) {
-                      case 0:
-                        pairs.values[index] = createAssignmentValue(recurmax);
-                      case 1:
-                        pairs.values.length = index + 1;
+                var default_value;
+                fill(function() {
+                    default_value = createDefaultValue(recurmax, noDefault);
+                }, function() {
+                    while (!rng(10)) {
+                        var index = rng(pairs.names.length + 1);
+                        pairs.names.splice(index, 0, "");
+                        if (index < pairs.values.length) {
+                            pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : "");
+                        } else switch (rng(5)) {
+                        case 0:
+                            pairs.values[index] = createAssignmentValue(recurmax);
+                        case 1:
+                            pairs.values.length = index + 1;
+                        }
                     }
-                }
-                names.unshift("[ " + pairs.names.join(", ") + " ]");
-                values.unshift("[ " + pairs.values.join(", ") + " ]");
+                    names.unshift("[ " + pairs.names.join(", ") + " ]" + default_value);
+                    values.unshift("[ " + pairs.values.join(", ") + " ]");
+                });
             }
             break;
           case 1:
@@ -521,33 +546,26 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
                         keys[index] = key;
                     }
                 });
-                var save_async = async;
-                if (was_async != null) {
-                    async = false;
-                    if (save_async || was_async) avoid.push("await");
-                }
-                addAvoidVars(avoid);
-                var save_vars = nameLenBefore && VAR_NAMES.splice(nameLenBefore);
-                names.unshift("{ " + addTrailingComma(pairs.names.map(function(name, index) {
-                    var key = index in keys ? keys[index] : rng(10) && createKey(recurmax, keys);
-                    return key ? key + ": " + name : name;
-                }).join(", ")) + " }");
-                if (was_async != null) {
-                    async = was_async;
-                    if (save_async || was_async) removeAvoidVars([ avoid.pop() ]);
-                }
-                values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) {
-                    var key = index in keys ? keys[index] : createKey(recurmax, keys);
-                    return key + ": " + value;
-                }).join(", ")) + " }");
-                if (save_vars) [].push.apply(VAR_NAMES, save_vars);
-                removeAvoidVars(avoid);
-                async = save_async;
+                fill(function() {
+                    names.unshift("{ " + addTrailingComma(pairs.names.map(function(name, index) {
+                        var key = index in keys ? keys[index] : rng(10) && createKey(recurmax, keys);
+                        return key ? key + ": " + name : name;
+                    }).join(", ")) + " }" + createDefaultValue(recurmax, noDefault));
+                }, function() {
+                    values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) {
+                        var key = index in keys ? keys[index] : createKey(recurmax, keys);
+                        return key + ": " + value;
+                    }).join(", ")) + " }");
+                });
             }
             break;
           default:
-            names.unshift(createName());
-            values.unshift(createAssignmentValue(recurmax));
+            var name = createName();
+            fill(function() {
+                names.unshift(name + createDefaultValue(recurmax, noDefault));
+            }, function() {
+                values.unshift(createAssignmentValue(recurmax));
+            });
             break;
         }
     }
@@ -575,8 +593,8 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
     }
     unique_vars.length -= 6;
     fn(function() {
-        addAvoidVars(consts);
-        addAvoidVars(lets);
+        consts.forEach(addAvoidVar);
+        lets.forEach(addAvoidVar);
         if (rng(2)) {
             return createDefinitions("const", consts) + "\n" + createDefinitions("let", lets) + "\n";
         } else {
@@ -610,17 +628,17 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
           default:
             s += names.map(function(name) {
                 if (type == "let" && !rng(10)) {
-                    removeAvoidVars([ name ]);
+                    removeAvoidVar(name);
                     return name;
                 }
                 var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
-                removeAvoidVars([ name ]);
+                removeAvoidVar(name);
                 return name + " = " + value;
             }).join(", ") + ";";
             names.length = 0;
             break;
         }
-        removeAvoidVars(names);
+        names.forEach(removeAvoidVar);
         return s;
     }
 }
@@ -877,9 +895,9 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
                     unique_vars.length -= 6;
                     if (SUPPORT.computed_key && rng(10) == 0) {
                         s += " catch ({  message: " + message + ", ";
-                        addAvoidVars([ name ]);
+                        addAvoidVar(name);
                         s += "[" + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + "]: " + name;
-                        removeAvoidVars([ name ]);
+                        removeAvoidVar(name);
                         s += " }) { ";
                     } else {
                         s += " catch ({ name: " + name + ", message: " + message + " }) { ";
@@ -1483,15 +1501,13 @@ function createUnaryPostfix() {
     return UNARY_POSTFIX[rng(UNARY_POSTFIX.length)];
 }
 
-function addAvoidVars(names) {
-    avoid_vars = avoid_vars.concat(names);
+function addAvoidVar(name) {
+    avoid_vars.push(name);
 }
 
-function removeAvoidVars(names) {
-    names.forEach(function(name) {
-        var index = avoid_vars.lastIndexOf(name);
-        if (index >= 0) avoid_vars.splice(index, 1);
-    });
+function removeAvoidVar(name) {
+    var index = avoid_vars.lastIndexOf(name);
+    if (index >= 0) avoid_vars.splice(index, 1);
 }
 
 function getVarName(noConst) {
@@ -1799,6 +1815,8 @@ for (var round = 1; round <= num_iterations; round++) {
     var orig_result = [ sandbox.run_code(original_code), sandbox.run_code(original_code, true) ];
     errored = typeof orig_result[0] != "string";
     if (errored) {
+        println();
+        println();
         println("//=============================================================");
         println("// original code");
         try_beautify(original_code, false, orig_result[0], println);