From 2390fae5c4b008aa1028ffdddaa071e4084ef8ac Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 23 Dec 2020 22:22:55 +0000 Subject: [PATCH] support default values (#4442) --- lib/ast.js | 68 ++- lib/compress.js | 260 ++++++--- lib/output.js | 11 + lib/parse.js | 109 +++- lib/transform.js | 4 + test/compress/default-values.js | 952 ++++++++++++++++++++++++++++++++ test/compress/destructured.js | 22 + test/mocha/getter-setter.js | 59 +- test/reduce.js | 5 + test/ufuzz/index.js | 150 ++--- 10 files changed, 1431 insertions(+), 209 deletions(-) create mode 100644 test/compress/default-values.js diff --git a/lib/ast.js b/lib/ast.js index f319a42d..60e57385 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -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"); }, }); diff --git a/lib/compress.js b/lib/compress.js index 13b7306b..23ce712d 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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") diff --git a/lib/output.js b/lib/output.js index 3d8d74c9..cd36f74a 100644 --- a/lib/output.js +++ b/lib/output.js @@ -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) { diff --git a/lib/parse.js b/lib/parse.js index 87293cab..c8ad531f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -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, diff --git a/lib/transform.js b/lib/transform.js index 5372cc59..0b92f8b5 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -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 index 00000000..ffc351c8 --- /dev/null +++ b/test/compress/default-values.js @@ -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" +} diff --git a/test/compress/destructured.js b/test/compress/destructured.js index 17aa786a..66aebc1c 100644 --- a/test/compress/destructured.js +++ b/test/compress/destructured.js @@ -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, diff --git a/test/mocha/getter-setter.js b/test/mocha/getter-setter.js index ec811aa1..fc78a25f 100644 --- a/test/mocha/getter-setter.js +++ b/test/mocha/getter-setter.js @@ -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); + }); }); }); diff --git a/test/reduce.js b/test/reduce.js index bcb30fe8..d0797f8a 100644 --- a/test/reduce.js +++ b/test/reduce.js @@ -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: diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index d554d644..1d247984 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -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); -- 2.34.1