diff options
author | Alex Lam S.L <alexlamsl@gmail.com> | 2021-01-07 02:04:09 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-07 10:04:09 +0800 |
commit | c3d358a5b8e00c51ba7a2e9d8c25b7771f5f5a46 (patch) | |
tree | d255e5d319d1e872a966578b9377d31e87caaed3 | |
parent | 68497d025877a4f364187c9ce771d863c73fe7f0 (diff) | |
download | tracifyjs-c3d358a5b8e00c51ba7a2e9d8c25b7771f5f5a46.tar.gz tracifyjs-c3d358a5b8e00c51ba7a2e9d8c25b7771f5f5a46.zip |
support rest parameters (#4515)
-rw-r--r-- | README.md | 26 | ||||
-rw-r--r-- | lib/ast.js | 37 | ||||
-rw-r--r-- | lib/compress.js | 161 | ||||
-rw-r--r-- | lib/output.js | 55 | ||||
-rw-r--r-- | lib/parse.js | 109 | ||||
-rw-r--r-- | lib/scope.js | 2 | ||||
-rw-r--r-- | lib/transform.js | 4 | ||||
-rw-r--r-- | test/compress/awaits.js (renamed from test/compress/async.js) | 0 | ||||
-rw-r--r-- | test/compress/rests.js | 487 | ||||
-rw-r--r-- | test/compress/spreads.js (renamed from test/compress/spread.js) | 28 | ||||
-rw-r--r-- | test/reduce.js | 4 | ||||
-rw-r--r-- | test/ufuzz/index.js | 41 |
12 files changed, 819 insertions, 135 deletions
@@ -627,7 +627,11 @@ to be `false` and all symbol names will be omitted. - `arguments` (default: `true`) -- replace `arguments[index]` with function parameter name whenever possible. -- `assignments` (default: `true`) -- apply optimizations to assignment expressions. +- `arrows` (default: `true`) -- apply optimizations to arrow functions + +- `assignments` (default: `true`) -- apply optimizations to assignment expressions + +- `awaits` (default: `true`) -- apply optimizations to `await` expressions - `booleans` (default: `true`) -- various optimizations for boolean context, for example `!!a ? b : c → a ? b : c` @@ -695,10 +699,6 @@ to be `false` and all symbol names will be omitted. when unsafe to do so, e.g. code which relies on `Function.prototype.length`. Pass `true` to always retain function arguments. -- `keep_fnames` (default: `false`) -- Pass `true` to prevent the - compressor from discarding function names. Useful for code relying on - `Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle-options). - - `keep_infinity` (default: `false`) -- Pass `true` to prevent `Infinity` from being compressed into `1/0`, which may cause performance issues on Chrome. @@ -747,6 +747,8 @@ to be `false` and all symbol names will be omitted. - `reduce_vars` (default: `true`) -- Improve optimization on variables assigned with and used as constant values. +- `rests` (default: `true`) -- apply optimizations to rest parameters + - `sequences` (default: `true`) -- join consecutive simple statements using the comma operator. May be set to a positive integer to specify the maximum number of consecutive comma sequences that will be generated. If this option is set to @@ -761,20 +763,20 @@ to be `false` and all symbol names will be omitted. annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For example: `/*@__PURE__*/foo();` -- `spread` (default: `true`) -- flatten spread expressions. +- `spreads` (default: `true`) -- flatten spread expressions. - `strings` (default: `true`) -- compact string concatenations. - `switches` (default: `true`) -- de-duplicate and remove unreachable `switch` branches -- `toplevel` (default: `false`) -- drop unreferenced functions (`"funcs"`) and/or - variables (`"vars"`) in the top level scope (`false` by default, `true` to drop - both unreferenced functions and variables) - - `top_retain` (default: `null`) -- prevent specific toplevel functions and variables from `unused` removal (can be array, comma-separated, RegExp or function. Implies `toplevel`) +- `toplevel` (default: `false`) -- drop unreferenced functions (`"funcs"`) and/or + variables (`"vars"`) in the top level scope (`false` by default, `true` to drop + both unreferenced functions and variables) + - `typeofs` (default: `true`) -- Transforms `typeof foo == "undefined"` into `foo === void 0`. Note: recommend to set this value to `false` for IE10 and earlier versions due to known issues. @@ -811,10 +813,6 @@ to be `false` and all symbol names will be omitted. - `eval` (default `false`) -- Pass `true` to mangle names visible in scopes where `eval` or `with` are used. -- `keep_fnames` (default `false`) -- Pass `true` to not mangle function names. - Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames` - [compress option](#compress-options). - - `reserved` (default `[]`) -- Pass an array of identifiers that should be excluded from mangling. Example: `["foo", "bar"]`. @@ -513,10 +513,11 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", { } }, AST_Scope); -var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", { +var AST_Lambda = DEFNODE("Lambda", "argnames length_read rest uses_arguments", { $documentation: "Base class for functions", $propdoc: { - argnames: "[(AST_Destructured|AST_SymbolFunarg)*] array of function arguments and/or destructured literals", + argnames: "[(AST_DefaultValue|AST_Destructured|AST_SymbolFunarg)*] array of function arguments and/or destructured literals", + rest: "[(AST_Destructured|AST_SymbolFunarg)?] rest parameter, or null if absent", uses_arguments: "[boolean/S] tells whether this function accesses the arguments array", }, each_argname: function(visit) { @@ -534,6 +535,7 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", { this.argnames.forEach(function(argname) { argname.walk(tw); }); + if (this.rest) this.rest.walk(tw); }, walk: function(visitor) { var node = this; @@ -542,6 +544,7 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", { node.argnames.forEach(function(argname) { argname.walk(visitor); }); + if (node.rest) node.rest.walk(visitor); walk_body(node, visitor); }); }, @@ -551,6 +554,9 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", { if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]"); }, true); }); + if (this.rest != null) validate_destructured(this.rest, function(node) { + if (!(node instanceof AST_SymbolFunarg)) throw new Error("rest must be AST_SymbolFunarg"); + }); }, }, AST_Scope); @@ -576,6 +582,7 @@ var AST_Arrow = DEFNODE("Arrow", "inlined value", { node.argnames.forEach(function(argname) { argname.walk(visitor); }); + if (node.rest) node.rest.walk(visitor); if (node.value) { node.value.walk(visitor); } else { @@ -1155,25 +1162,31 @@ var AST_Array = DEFNODE("Array", "elements", { }, }); -var AST_Destructured = DEFNODE("Destructured", null, { +var AST_Destructured = DEFNODE("Destructured", "rest", { $documentation: "Base class for destructured literal", + $propdoc: { + rest: "[(AST_Destructured|AST_SymbolDeclaration|AST_SymbolRef)?] rest parameter, or null if absent", + }, }); 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, true); - }); - if (node instanceof AST_DestructuredObject) return node.properties.forEach(function(prop) { - validate_destructured(prop.value, check, true); - }); + if (node instanceof AST_Destructured) { + if (node.rest != null) validate_destructured(node.rest, check); + if (node instanceof AST_DestructuredArray) return node.elements.forEach(function(node) { + 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, true); + }); + } check(node); } var AST_DestructuredArray = DEFNODE("DestructuredArray", "elements", { $documentation: "A destructured array literal", $propdoc: { - elements: "[AST_Node*] array of elements", + elements: "[(AST_DefaultValue|AST_Destructured|AST_SymbolDeclaration|AST_SymbolRef)*] array of elements", }, walk: function(visitor) { var node = this; @@ -1181,6 +1194,7 @@ var AST_DestructuredArray = DEFNODE("DestructuredArray", "elements", { node.elements.forEach(function(element) { element.walk(visitor); }); + if (node.rest) node.rest.walk(visitor); }); }, }, AST_Destructured); @@ -1189,7 +1203,7 @@ var AST_DestructuredKeyVal = DEFNODE("DestructuredKeyVal", "key value", { $documentation: "A key: value destructured property", $propdoc: { key: "[string|AST_Node] property name. For computed property this is an AST_Node.", - value: "[AST_Node] property value", + value: "[AST_DefaultValue|AST_Destructured|AST_SymbolDeclaration|AST_SymbolRef] property value", }, walk: function(visitor) { var node = this; @@ -1218,6 +1232,7 @@ var AST_DestructuredObject = DEFNODE("DestructuredObject", "properties", { node.properties.forEach(function(prop) { prop.walk(visitor); }); + if (node.rest) node.rest.walk(visitor); }); }, _validate: function() { diff --git a/lib/compress.js b/lib/compress.js index b1389253..4bc57012 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -81,13 +81,14 @@ function Compressor(options, false_by_default) { objects : !false_by_default, passes : 1, properties : !false_by_default, - pure_getters : !false_by_default && "strict", pure_funcs : null, + pure_getters : !false_by_default && "strict", reduce_funcs : !false_by_default, reduce_vars : !false_by_default, + rests : !false_by_default, sequences : !false_by_default, side_effects : !false_by_default, - spread : !false_by_default, + spreads : !false_by_default, strings : !false_by_default, switches : !false_by_default, top_retain : null, @@ -609,7 +610,7 @@ merge(Compressor.prototype, { }); } - function scan_declaration(tw, lhs, fixed, visit) { + function scan_declaration(tw, compressor, lhs, fixed, visit) { var scanner = new TreeWalker(function(node) { if (node instanceof AST_DefaultValue) { reset_flags(node); @@ -640,6 +641,15 @@ merge(Compressor.prototype, { }; node.walk(scanner); }); + if (node.rest) { + fixed = compressor.option("rests") && function() { + var value = save(); + return value instanceof AST_Array ? make_node(AST_Array, node, { + elements: value.elements.slice(node.elements.length), + }) : node.rest; + }; + node.rest.walk(scanner); + } fixed = save; return true; } @@ -670,6 +680,10 @@ merge(Compressor.prototype, { }; node.value.walk(scanner); }); + if (node.rest) { + fixed = false; + node.rest.walk(scanner); + } fixed = save; return true; } @@ -719,12 +733,12 @@ merge(Compressor.prototype, { var safe = !fn.uses_arguments || tw.has_directive("use strict"); fn.argnames.forEach(function(arg, i) { var value = iife.args[i]; - scan_declaration(tw, arg, function() { + scan_declaration(tw, compressor, arg, function() { var j = fn.argnames.indexOf(arg); return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife); }, function(node, fixed) { var d = node.definition(); - if (safe && d.fixed === undefined) { + if (fixed && safe && d.fixed === undefined) { mark(tw, d); tw.loop_ids[d.id] = tw.in_loop; var value = iife.args[i]; @@ -759,7 +773,7 @@ merge(Compressor.prototype, { return; } else if (node.operator == "=") { node.right.walk(tw); - scan_declaration(tw, left, function() { + scan_declaration(tw, compressor, left, function() { return node.right; }, function(sym, fixed, walk) { if (!(sym instanceof AST_SymbolRef)) { @@ -769,7 +783,7 @@ merge(Compressor.prototype, { } var d = sym.definition(); d.assignments++; - if (!is_modified(compressor, tw, node, node.right, 0) && safe_to_assign(tw, d)) { + if (fixed && !is_modified(compressor, tw, node, node.right, 0) && safe_to_assign(tw, d)) { push_ref(d, sym); mark(tw, d); if (d.single_use && left instanceof AST_Destructured) d.single_use = false; @@ -1135,15 +1149,15 @@ merge(Compressor.prototype, { } return true; }); - def(AST_VarDef, function(tw) { + def(AST_VarDef, function(tw, descend, compressor) { var node = this; if (!node.value) return; node.value.walk(tw); - scan_declaration(tw, node.name, function() { + scan_declaration(tw, compressor, node.name, function() { return node.value; }, function(name, fixed) { var d = name.definition(); - if (safe_to_assign(tw, d, true)) { + if (fixed && safe_to_assign(tw, d, true)) { mark(tw, d); tw.loop_ids[d.id] = tw.in_loop; d.fixed = fixed; @@ -5097,6 +5111,7 @@ merge(Compressor.prototype, { node.argnames.forEach(function(argname) { argname.mark_symbol(marker, scanner); }); + if (node.rest) node.rest.mark_symbol(marker, scanner); } if (node instanceof AST_Arrow && node.value) { node.value.walk(tw); @@ -5513,8 +5528,9 @@ merge(Compressor.prototype, { var fns_with_marked_args = []; var trimmer = new TreeTransformer(function(node) { if (node instanceof AST_DefaultValue) return trim_default(trimmer, node); + if (node instanceof AST_Destructured && node.rest) node.rest = node.rest.transform(trimmer); if (node instanceof AST_DestructuredArray) { - var trim = true; + var trim = !node.rest; for (var i = node.elements.length; --i >= 0;) { var element = node.elements[i].transform(trimmer); if (element) { @@ -5528,19 +5544,33 @@ merge(Compressor.prototype, { } return node; } - if (node instanceof AST_DestructuredKeyVal) { - 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)) { - node.value = node.value.transform(tt); - } else { - var value = node.value.transform(trimmer); - if (!value) return List.skip; - node.value = value; - } + if (node instanceof AST_DestructuredObject) { + var properties = []; + node.properties.forEach(function(prop) { + var retain = false; + if (prop.key instanceof AST_Node) { + prop.key = prop.key.transform(tt); + retain = prop.key.has_side_effects(compressor); + } + if ((retain || node.rest) && is_decl(prop.value)) { + prop.value = prop.value.transform(tt); + properties.push(prop); + } else { + var value = prop.value.transform(trimmer); + if (!value && node.rest) { + if (prop.value instanceof AST_DestructuredArray) { + value = make_node(AST_DestructuredArray, prop.value, { elements: [] }); + } else { + value = make_node(AST_DestructuredObject, prop.value, { properties: [] }); + } + } + if (value) { + prop.value = value; + properties.push(prop); + } + } + }); + node.properties = properties; return node; } if (node instanceof AST_SymbolDeclaration) return node.definition().id in in_use_ids ? node : null; @@ -5607,7 +5637,7 @@ merge(Compressor.prototype, { unused_fn_names.push(node); } if (!(node instanceof AST_Accessor)) { - var trim = compressor.drop_fargs(node, parent); + var trim = compressor.drop_fargs(node, parent) && !node.rest; for (var a = node.argnames, i = a.length; --i >= 0;) { var sym = a[i]; if (!(sym instanceof AST_SymbolFunarg)) { @@ -5634,6 +5664,13 @@ merge(Compressor.prototype, { sym.__unused = true; } } + if (node.rest) { + node.rest = node.rest.transform(trimmer); + if (node.rest instanceof AST_DestructuredArray && node.rest.elements.length == 0 + || node.rest instanceof AST_DestructuredObject && node.rest.properties.length == 0) { + node.rest = null; + } + } fns_with_marked_args.push(node); } } @@ -6149,8 +6186,22 @@ merge(Compressor.prototype, { element = element.transform(trimmer); if (element) elements[index] = element; }); + if (node.rest) { + if (compressor.option("rests")) { + value = values && make_node(AST_Array, save, { + elements: values.slice(node.elements.length), + }); + node.rest = node.rest.transform(trimmer); + } else { + node.rest = node.rest.transform(tt); + } + } value = save; - if (values && elements.length == 0) return null; + if (node.rest) { + elements.length = node.elements.length; + } else if (values && elements.length == 0) { + return null; + } fill_holes(node, elements); node.elements = elements; return node; @@ -6181,19 +6232,36 @@ merge(Compressor.prototype, { value = values && values[prop.key]; retain = false; } - if (retain && is_decl(prop.value)) { + if ((retain || node.rest) && is_decl(prop.value)) { prop.value = prop.value.transform(tt); properties.push(prop); } else { var newValue = prop.value.transform(trimmer); + if (!newValue && node.rest) { + if (prop.value instanceof AST_DestructuredArray) { + newValue = make_node(AST_DestructuredArray, prop.value, { elements: [] }); + } else { + newValue = make_node(AST_DestructuredObject, prop.value, { properties: [] }); + } + } if (newValue) { prop.value = newValue; properties.push(prop); } } }); + if (node.rest) { + if (compressor.option("rests")) { + value = values && make_node(AST_Object, save, { + properties: [], + }); + node.rest = node.rest.transform(trimmer); + } else { + node.rest = node.rest.transform(tt); + } + } value = save; - if (properties.length == 0 && value && !value.may_throw_on_access(compressor)) { + if (properties.length == 0 && !node.rest && value && !value.may_throw_on_access(compressor)) { return null; } node.properties = properties; @@ -7704,16 +7772,26 @@ merge(Compressor.prototype, { if (!all(args, function(arg) { return !(arg instanceof AST_Spread); })) return; + if (fn.rest) { + if (!compressor.option("rests")) return; + var insert = fn.argnames.length; + for (var i = args.length; i < insert; i++) { + args[i] = make_node(AST_Undefined, call).optimize(compressor); + } + args[insert] = make_node(AST_Array, call, { elements: args.splice(insert) }); + fn.argnames.push(fn.rest); + fn.rest = null; + } var pos = 0, last = 0; var is_iife = fn === exp && !fn.name; var drop_defaults = is_iife && compressor.option("default_values"); var drop_fargs = is_iife && compressor.drop_fargs(fn, call) ? function(argname, arg) { if (!argname) return true; if (argname instanceof AST_DestructuredArray) { - return argname.elements.length == 0 && arg instanceof AST_Array; + return argname.elements.length == 0 && !argname.rest && arg instanceof AST_Array; } if (argname instanceof AST_DestructuredObject) { - return argname.properties.length == 0 && arg && !arg.may_throw_on_access(compressor); + return argname.properties.length == 0 && !argname.rest && arg && !arg.may_throw_on_access(compressor); } return argname.__unused; } : return_false; @@ -8311,7 +8389,7 @@ merge(Compressor.prototype, { } function can_substitute_directly() { - if (has_default || has_destructured || var_assigned) return; + if (has_default || has_destructured || var_assigned || fn.rest) return; if (compressor.option("inline") < 2 && fn.argnames.length) return; if (!fn.variables.all(function(def) { return def.references.length - def.replaced < 2 && def.orig[0] instanceof AST_SymbolFunarg; @@ -8526,15 +8604,18 @@ merge(Compressor.prototype, { operator: "=", left: make_node(AST_DestructuredArray, self, { elements: fn.argnames.map(function(argname) { - return argname.convert_symbol(AST_SymbolRef, function(ref, name) { - var symbol = make_node(AST_SymbolVar, name, name); - name.definition().orig.push(symbol); - append_var(decls, expressions, symbol); - }); + return argname.convert_symbol(AST_SymbolRef, process); }), + rest: fn.rest && fn.rest.convert_symbol(AST_SymbolRef, process), }), right: make_node(AST_Array, self, { elements: self.args.slice() }), })); + + function process(ref, name) { + var symbol = make_node(AST_SymbolVar, name, name); + name.definition().orig.push(symbol); + append_var(decls, expressions, symbol); + } } function flatten_vars(decls, expressions) { @@ -8568,7 +8649,7 @@ merge(Compressor.prototype, { function flatten_fn() { var decls = []; var expressions = []; - if (has_default > 1 || has_destructured) { + if (has_default > 1 || has_destructured || fn.rest) { flatten_destructured(decls, expressions); } else { flatten_args(decls, expressions); @@ -10308,7 +10389,7 @@ merge(Compressor.prototype, { OPT(AST_Spread, function(self, compressor) { var exp = self.expression; - if (compressor.option("spread") && exp instanceof AST_Array && !(compressor.parent() instanceof AST_Object)) { + if (compressor.option("spreads") && exp instanceof AST_Array && !(compressor.parent() instanceof AST_Object)) { return List.splice(exp.elements.map(function(node) { return node instanceof AST_Hole ? make_node(AST_Undefined, node).optimize(compressor) : node; })); @@ -10392,7 +10473,7 @@ merge(Compressor.prototype, { argname = null; } } - } else if (index < fn.argnames.length + 5 && compressor.drop_fargs(fn, fn_parent)) { + } else if (index < fn.argnames.length + 5 && compressor.drop_fargs(fn, fn_parent) && !fn.rest) { while (index >= fn.argnames.length) { argname = fn.make_var(AST_SymbolFunarg, fn, "argument_" + fn.argnames.length); fn.argnames.push(argname); @@ -10603,7 +10684,7 @@ merge(Compressor.prototype, { if (!(prop instanceof AST_Spread)) return process(prop); found = true; var exp = prop.expression; - if (compressor.option("spread") && exp instanceof AST_Object && all(exp.properties, function(prop) { + if (compressor.option("spreads") && exp instanceof AST_Object && all(exp.properties, function(prop) { return !(prop instanceof AST_ObjectGetter || prop instanceof AST_Spread); })) { changed = true; diff --git a/lib/output.js b/lib/output.js index cd36f74a..9e704e8c 100644 --- a/lib/output.js +++ b/lib/output.js @@ -992,16 +992,26 @@ function OutputStream(options) { }); /* -----[ functions ]----- */ - DEFPRINT(AST_Arrow, function(output) { - var self = this; - if (self.argnames.length == 1 && self.argnames[0] instanceof AST_SymbolFunarg) { - self.argnames[0].print(output); - } else output.with_parens(function() { + function print_funargs(self, output) { + output.with_parens(function() { self.argnames.forEach(function(arg, i) { if (i) output.comma(); arg.print(output); }); + if (self.rest) { + if (self.argnames.length) output.comma(); + output.print("..."); + self.rest.print(output); + } }); + } + DEFPRINT(AST_Arrow, function(output) { + var self = this; + if (self.argnames.length == 1 && self.argnames[0] instanceof AST_SymbolFunarg && !self.rest) { + self.argnames[0].print(output); + } else { + print_funargs(self, output); + } output.space(); output.print("=>"); output.space(); @@ -1016,12 +1026,7 @@ function OutputStream(options) { output.space(); self.name.print(output); } - output.with_parens(function() { - self.argnames.forEach(function(arg, i) { - if (i) output.comma(); - arg.print(output); - }); - }); + print_funargs(self, output); output.space(); print_braced(self, output, true); } @@ -1355,25 +1360,30 @@ function OutputStream(options) { } : noop); }); DEFPRINT(AST_DestructuredArray, function(output) { - var a = this.elements, len = a.length; - output.with_square(len > 0 ? function() { + var a = this.elements, len = a.length, rest = this.rest; + output.with_square(len || rest ? function() { output.space(); a.forEach(function(exp, i) { if (i) output.comma(); exp.print(output); + }); + if (rest) { + if (len) output.comma(); + output.print("..."); + rest.print(output); + } else if (a[len - 1] instanceof AST_Hole) { // If the final element is a hole, we need to make sure it // doesn't look like a trailing comma, by inserting an actual // trailing comma. - if (i === len - 1 && exp instanceof AST_Hole) - output.comma(); - }); + output.comma(); + } output.space(); } : noop); }); DEFPRINT(AST_DestructuredKeyVal, print_key_value); DEFPRINT(AST_DestructuredObject, function(output) { - var props = this.properties; - if (props.length > 0) output.with_block(function() { + var props = this.properties, len = props.length, rest = this.rest; + if (len || rest) output.with_block(function() { props.forEach(function(prop, i) { if (i) { output.print(","); @@ -1382,6 +1392,15 @@ function OutputStream(options) { output.indent(); prop.print(output); }); + if (rest) { + if (len) { + output.print(","); + output.newline(); + } + output.indent(); + output.print("..."); + rest.print(output); + } output.newline(); }); else print_braced_empty(this, output); diff --git a/lib/parse.js b/lib/parse.js index c8ad531f..e44c2090 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1039,11 +1039,18 @@ function parse($TEXT, options) { } function to_funarg(node) { - if (node instanceof AST_Array) return new AST_DestructuredArray({ - start: node.start, - elements: node.elements.map(to_funarg), - end: node.end, - }); + if (node instanceof AST_Array) { + var rest = null; + if (node.elements[node.elements.length - 1] instanceof AST_Spread) { + rest = to_funarg(node.elements.pop().expression); + } + return new AST_DestructuredArray({ + start: node.start, + elements: node.elements.map(to_funarg), + rest: rest, + end: node.end, + }); + } if (node instanceof AST_Assign) return new AST_DefaultValue({ start: node.start, name: to_funarg(node.left), @@ -1056,28 +1063,37 @@ function parse($TEXT, options) { } if (node instanceof AST_DestructuredArray) { node.elements = node.elements.map(to_funarg); + if (node.rest) node.rest = to_funarg(node.rest); return node; } if (node instanceof AST_DestructuredObject) { node.properties.forEach(function(prop) { prop.value = to_funarg(prop.value); }); + if (node.rest) node.rest = to_funarg(node.rest); 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) { - if (!(prop instanceof AST_ObjectKeyVal)) token_error(prop.start, "Invalid destructuring assignment"); - return new AST_DestructuredKeyVal({ - start: prop.start, - key: prop.key, - value: to_funarg(prop.value), - end: prop.end, - }); - }), - end: node.end, - }); + if (node instanceof AST_Object) { + var rest = null; + if (node.properties[node.properties.length - 1] instanceof AST_Spread) { + rest = to_funarg(node.properties.pop().expression); + } + return new AST_DestructuredObject({ + start: node.start, + properties: node.properties.map(function(prop) { + if (!(prop instanceof AST_ObjectKeyVal)) token_error(prop.start, "Invalid destructuring assignment"); + return new AST_DestructuredKeyVal({ + start: prop.start, + key: prop.key, + value: to_funarg(prop.value), + end: prop.end, + }); + }), + rest: rest, + end: node.end, + }); + } if (node instanceof AST_SymbolRef) return new AST_SymbolFunarg(node); token_error(node.start, "Invalid arrow parameter"); } @@ -1116,6 +1132,7 @@ function parse($TEXT, options) { return new AST_Arrow({ start: start, argnames: argnames, + rest: exprs.rest || null, body: body, value: value, end: prev(), @@ -1155,6 +1172,7 @@ function parse($TEXT, options) { if (S.input.has_directive("use strict")) { if (name) strict_verify_symbol(name); argnames.forEach(strict_verify_symbol); + if (argnames.rest) strict_verify_symbol(argnames.rest); } S.input.pop_directives_stack(); --S.in_function; @@ -1164,6 +1182,7 @@ function parse($TEXT, options) { return new ctor({ name: name, argnames: argnames, + rest: argnames.rest || null, body: body }); }; @@ -1445,17 +1464,22 @@ function parse($TEXT, options) { if (allow_trailing_comma && is("punc", closing)) break; if (allow_empty && is("punc", ",")) { a.push(new AST_Hole({ start: S.token, end: S.token })); - } else if (parser === maybe_assign && is("operator", "...")) { + } else if (!is("operator", "...")) { + a.push(parser()); + } else if (parser === maybe_assign) { a.push(new AST_Spread({ start: S.token, expression: (next(), parser()), end: prev(), })); } else { - a.push(parser()); + next(); + a.rest = parser(); + if (a.rest instanceof AST_DefaultValue) token_error(a.rest.start, "Invalid rest parameter"); + break; } } - next(); + expect(closing); return a; } @@ -1634,17 +1658,19 @@ function parse($TEXT, options) { var start = S.token; if (is("punc", "[")) { next(); + var elements = expr_list("]", !options.strict, true, function() { + return maybe_default(type); + }); return new AST_DestructuredArray({ start: start, - elements: expr_list("]", !options.strict, true, function() { - return maybe_default(type); - }), + elements: elements, + rest: elements.rest || null, end: prev(), }); } if (is("punc", "{")) { next(); - var first = true, a = []; + var first = true, a = [], rest = null; while (!is("punc", "}")) { if (first) first = false; else expect(","); // allow trailing comma @@ -1661,6 +1687,11 @@ function parse($TEXT, options) { })); continue; } + if (is("operator", "...")) { + next(); + rest = maybe_destructured(type); + break; + } var name = as_symbol(type); if (is("operator", "=")) { next(); @@ -1678,10 +1709,11 @@ function parse($TEXT, options) { end: prev(), })); } - next(); + expect("}"); return new AST_DestructuredObject({ start: start, properties: a, + rest: rest, end: prev(), }); } @@ -1845,6 +1877,11 @@ function parse($TEXT, options) { function to_destructured(node) { if (node instanceof AST_Array) { + var rest = null; + if (node.elements[node.elements.length - 1] instanceof AST_Spread) { + rest = to_destructured(node.elements.pop().expression); + if (!(rest instanceof AST_Destructured || is_assignable(rest))) return node; + } var elements = node.elements.map(to_destructured); return all(elements, function(node) { return node instanceof AST_DefaultValue @@ -1854,6 +1891,7 @@ function parse($TEXT, options) { }) ? new AST_DestructuredArray({ start: node.start, elements: elements, + rest: rest, end: node.end, }) : node; } @@ -1867,6 +1905,11 @@ function parse($TEXT, options) { }) : node; } if (!(node instanceof AST_Object)) return node; + var rest = null; + if (node.properties[node.properties.length - 1] instanceof AST_Spread) { + rest = to_destructured(node.properties.pop().expression); + if (!(rest instanceof AST_Destructured || is_assignable(rest))) return node; + } var props = []; for (var i = 0; i < node.properties.length; i++) { var prop = node.properties[i]; @@ -1885,6 +1928,7 @@ function parse($TEXT, options) { return new AST_DestructuredObject({ start: node.start, properties: props, + rest: rest, end: node.end, }); } @@ -1912,15 +1956,20 @@ function parse($TEXT, options) { var start = S.token; var exprs = []; while (true) { + if (maybe_arrow && is("operator", "...")) { + next(); + exprs.rest = maybe_destructured(AST_SymbolFunarg); + break; + } exprs.push(maybe_assign(no_in)); if (!is("punc", ",")) break; next(); if (maybe_arrow && is("punc", ")") && is_token(peek(), "punc", "=>")) break; } - return exprs.length == 1 ? exprs[0] : new AST_Sequence({ - start : start, - expressions : exprs, - end : prev() + return exprs.length == 1 && !exprs.rest ? exprs[0] : new AST_Sequence({ + start: start, + expressions: exprs, + end: prev(), }); } diff --git a/lib/scope.js b/lib/scope.js index 036df118..77000de3 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -119,6 +119,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { node.argnames.forEach(function(argname) { argname.walk(tw); }); + if (node.rest) node.rest.walk(tw); walk_body(node, tw); }); return true; @@ -220,6 +221,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { node.argnames.forEach(function(argname) { argname.walk(tw); }); + if (node.rest) node.rest.walk(tw); in_arg.pop(); if (node instanceof AST_Arrow && node.value) { node.value.walk(tw); diff --git a/lib/transform.js b/lib/transform.js index 0b92f8b5..9b3eba86 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -133,10 +133,12 @@ TreeTransformer.prototype = new TreeWalker; DEF(AST_Lambda, function(self, tw) { if (self.name) self.name = self.name.transform(tw); self.argnames = do_list(self.argnames, tw); + if (self.rest) self.rest = self.rest.transform(tw); self.body = do_list(self.body, tw); }); DEF(AST_Arrow, function(self, tw) { self.argnames = do_list(self.argnames, tw); + if (self.rest) self.rest = self.rest.transform(tw); if (self.value) { self.value = self.value.transform(tw); } else { @@ -180,6 +182,7 @@ TreeTransformer.prototype = new TreeWalker; }); DEF(AST_DestructuredArray, function(self, tw) { self.elements = do_list(self.elements, tw); + if (self.rest) self.rest = self.rest.transform(tw); }); DEF(AST_DestructuredKeyVal, function(self, tw) { if (self.key instanceof AST_Node) self.key = self.key.transform(tw); @@ -187,6 +190,7 @@ TreeTransformer.prototype = new TreeWalker; }); DEF(AST_DestructuredObject, function(self, tw) { self.properties = do_list(self.properties, tw); + if (self.rest) self.rest = self.rest.transform(tw); }); DEF(AST_Object, function(self, tw) { self.properties = do_list(self.properties, tw); diff --git a/test/compress/async.js b/test/compress/awaits.js index 7ae753fb..7ae753fb 100644 --- a/test/compress/async.js +++ b/test/compress/awaits.js diff --git a/test/compress/rests.js b/test/compress/rests.js new file mode 100644 index 00000000..1a27860d --- /dev/null +++ b/test/compress/rests.js @@ -0,0 +1,487 @@ +arrow_1: { + input: { + console.log.apply(console, ((...a) => a)("PASS", 42)); + } + expect_exact: 'console.log.apply(console,((...a)=>a)("PASS",42));' + expect_stdout: "PASS 42" + node_version: ">=6" +} + +arrow_2: { + input: { + console.log.apply(console, ((a, ...b) => b)("FAIL", "PASS", 42)); + } + expect_exact: 'console.log.apply(console,((a,...b)=>b)("FAIL","PASS",42));' + expect_stdout: "PASS 42" + node_version: ">=6" +} + +arrow_destructured_array_1: { + input: { + console.log.apply(console, (([ ...a ]) => a)("PASS")); + } + expect_exact: 'console.log.apply(console,(([...a])=>a)("PASS"));' + expect_stdout: "P A S S" + node_version: ">=6" +} + +arrow_destructured_array_2: { + input: { + console.log.apply(console, (([ a, ...b ]) => b)([ "FAIL", "PASS", 42 ])); + } + expect_exact: 'console.log.apply(console,(([a,...b])=>b)(["FAIL","PASS",42]));' + expect_stdout: "PASS 42" + node_version: ">=6" +} + +arrow_destructured_array_3: { + input: { + console.log((([ [ ...a ] = "FAIL" ]) => a)([ "PASS" ]).join("|")); + } + expect_exact: 'console.log((([[...a]="FAIL"])=>a)(["PASS"]).join("|"));' + expect_stdout: "P|A|S|S" + node_version: ">=6" +} + +arrow_destructured_object_1: { + input: { + var f = ({ ...a }) => a, o = f({ PASS: 42 }); + for (var k in o) + console.log(k, o[k]); + } + expect_exact: "var f=({...a})=>a,o=f({PASS:42});for(var k in o)console.log(k,o[k]);" + expect_stdout: "PASS 42" + node_version: ">=8" +} + +arrow_destructured_object_2: { + input: { + var f = ({ FAIL: a, ...b }) => b, o = f({ PASS: 42, FAIL: null }); + for (var k in o) + console.log(k, o[k]); + } + expect_exact: "var f=({FAIL:a,...b})=>b,o=f({PASS:42,FAIL:null});for(var k in o)console.log(k,o[k]);" + expect_stdout: "PASS 42" + node_version: ">=8" +} + +arrow_destructured_object_3: { + input: { + var f = ([ { ...a } = [ "FAIL" ] ]) => a; + var o = f([ "PASS" ]); + for (var k in o) + console.log(k, o[k]); + } + expect_exact: 'var f=([{...a}=["FAIL"]])=>a;var o=f(["PASS"]);for(var k in o)console.log(k,o[k]);' + expect_stdout: [ + "0 P", + "1 A", + "2 S", + "3 S", + ] + node_version: ">=8" +} + +funarg_1: { + input: { + console.log.apply(console, function(...a) { + return a; + }("PASS", 42)); + } + expect_exact: 'console.log.apply(console,function(...a){return a}("PASS",42));' + expect_stdout: "PASS 42" + node_version: ">=6" +} + +funarg_2: { + input: { + console.log.apply(console, function(a, ...b) { + return b; + }("FAIL", "PASS", 42)); + } + expect_exact: 'console.log.apply(console,function(a,...b){return b}("FAIL","PASS",42));' + expect_stdout: "PASS 42" + node_version: ">=6" +} + +destructured_array_1: { + input: { + var [ ...a ] = [ "PASS", 42 ]; + console.log.apply(console, a); + } + expect_exact: 'var[...a]=["PASS",42];console.log.apply(console,a);' + expect_stdout: "PASS 42" + node_version: ">=6" +} + +destructured_array_2: { + input: { + var [ a, ...b ] = [ "FAIL", "PASS", 42 ]; + console.log.apply(console, b); + } + expect_exact: 'var[a,...b]=["FAIL","PASS",42];console.log.apply(console,b);' + expect_stdout: "PASS 42" + node_version: ">=6" +} + +destructured_object_1: { + input: { + var { ...a } = [ "FAIL", "PASS", 42 ]; + console.log(a[1], a[2]); + } + expect_exact: 'var{...a}=["FAIL","PASS",42];console.log(a[1],a[2]);' + expect_stdout: "PASS 42" + node_version: ">=8" +} + +destructured_object_2: { + input: { + var { 0: a, ...b } = [ "FAIL", "PASS", 42 ]; + console.log(b[1], b[2]); + } + expect_exact: 'var{0:a,...b}=["FAIL","PASS",42];console.log(b[1],b[2]);' + expect_stdout: "PASS 42" + node_version: ">=8" +} + +drop_fargs: { + options = { + keep_fargs: false, + rests: true, + unused: true, + } + input: { + console.log(function(a, ...b) { + return b[0]; + }("FAIL", "PASS")); + } + expect: { + console.log(function(b) { + return b[0]; + }([ "PASS" ])); + } + expect_stdout: "PASS" + node_version: ">=6" +} + +inline: { + options = { + inline: true, + toplevel: true, + } + input: { + console.log(function(a, ...[ b, c ]) { + return c + b + a; + }("SS", "A", "P")); + } + expect: { + console.log(([ a, ...[ b, c ] ] = [ "SS", "A", "P" ], c + b + a)); + var a, b, c; + } + expect_stdout: "PASS" + node_version: ">=6" +} + +retain_var: { + options = { + unused: true, + } + input: { + var [ ...a ] = [ "PASS" ]; + console.log(a[0]); + } + expect: { + var [ ...a ] = [ "PASS" ]; + console.log(a[0]); + } + expect_stdout: "PASS" + node_version: ">=6" +} + +reduce_destructured_array: { + options = { + reduce_vars: true, + rests: true, + toplevel: true, + unused: true, + } + input: { + var [ ...a ] = [ "PASS" ]; + console.log(a[0]); + } + expect: { + console.log([ "PASS" ][0]); + } + expect_stdout: "PASS" + node_version: ">=6" +} + +reduce_destructured_object: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var { ...a } = [ "PASS" ]; + console.log(a[0]); + } + expect: { + var { ...a } = [ "PASS" ]; + console.log(a[0]); + } + expect_stdout: "PASS" + node_version: ">=8" +} + +retain_destructured_array: { + options = { + toplevel: true, + unused: true, + } + input: { + var [ a, ...b ] = [ "FAIL", "PASS", 42 ]; + console.log.apply(console, b); + } + expect: { + var [ , ...b ] = [ "FAIL", "PASS", 42 ]; + console.log.apply(console, b); + } + expect_stdout: "PASS 42" + node_version: ">=6" +} + +retain_destructured_object_1: { + options = { + toplevel: true, + unused: true, + } + input: { + var { 0: a, ...b } = [ "FAIL", "PASS", 42 ]; + for (var k in b) + console.log(k, b[k]); + } + expect: { + var { 0: a, ...b } = [ "FAIL", "PASS", 42 ]; + for (var k in b) + console.log(k, b[k]); + } + expect_stdout: [ + "1 PASS", + "2 42", + ] + node_version: ">=8" +} + +retain_destructured_object_2: { + options = { + toplevel: true, + unused: true, + } + input: { + var { foo: [ a ], ...b } = { foo: [ "FAIL" ], bar: "PASS", baz: 42 }; + for (var k in b) + console.log(k, b[k]); + } + expect: { + var { foo: [], ...b } = { foo: [ "FAIL" ], bar: "PASS", baz: 42 }; + for (var k in b) + console.log(k, b[k]); + } + expect_stdout: [ + "bar PASS", + "baz 42", + ] + node_version: ">=8" +} + +retain_funarg_destructured_array_1: { + options = { + inline: true, + keep_fargs: false, + pure_getters: "strict", + unused: true, + } + input: { + console.log((([ ...a ]) => a)([ "PASS" ])[0]); + } + expect: { + console.log((([ ...a ]) => a)([ "PASS" ])[0]); + } + expect_stdout: "PASS" + node_version: ">=6" +} + +retain_funarg_destructured_array_2: { + options = { + unused: true, + } + input: { + console.log(function([ a, ...b ]) { + return b; + }("bar")[1]); + } + expect: { + console.log(function([ , ...b ]) { + return b; + }("bar")[1]); + } + expect_stdout: "r" + node_version: ">=6" +} + +retain_funarg_destructured_object_1: { + options = { + inline: true, + keep_fargs: false, + pure_getters: "strict", + unused: true, + } + input: { + console.log((({ ...a }) => a)([ "PASS" ])[0]); + } + expect: { + console.log((({ ...a }) => a)([ "PASS" ])[0]); + } + expect_stdout: "PASS" + node_version: ">=8" +} + +retain_funarg_destructured_object_2: { + options = { + unused: true, + } + input: { + console.log(function({ p: a, ... b }) { + return b; + }({ p: "FAIL" }).p || "PASS"); + } + expect: { + console.log(function({ p: a, ... b }) { + return b; + }({ p: "FAIL" }).p || "PASS"); + } + expect_stdout: "PASS" + node_version: ">=8" +} + +drop_unused_call_args_1: { + options = { + rests: true, + unused: true, + } + input: { + (function(...a) { + console.log(a[0]); + })(42, console.log("PASS")); + } + expect: { + (function(a) { + console.log(a[0]); + })([ 42, console.log("PASS") ]); + } + expect_stdout: [ + "PASS", + "42", + ] + node_version: ">=6" +} + +drop_unused_call_args_2: { + options = { + keep_fargs: false, + rests: true, + unused: true, + } + input: { + console.log(function(a, ...b) { + return b; + }(console).length); + } + expect: { + console.log(function(b) { + return b; + }((console, [])).length); + } + expect_stdout: "0" + node_version: ">=6" +} + +merge_funarg: { + options = { + merge_vars: true, + } + input: { + (function(...a) { + var b = a.length; + console.log(b); + })(); + } + expect: { + (function(...b) { + var b = b.length; + console.log(b); + })(); + } + expect_stdout: "0" + node_version: ">=6" +} + +merge_funarg_destructured_array: { + options = { + merge_vars: true, + } + input: { + (function([ ...a ]) { + var b = a.length; + console.log(b); + })([]); + } + expect: { + (function([ ...b ]) { + var b = b.length; + console.log(b); + })([]); + } + expect_stdout: "0" + node_version: ">=6" +} + +merge_funarg_destructured_object: { + options = { + merge_vars: true, + } + input: { + (function({ ...a }) { + var b = a[0]; + console.log(b); + })([ "PASS" ]); + } + expect: { + (function({ ...b }) { + var b = b[0]; + console.log(b); + })([ "PASS" ]); + } + expect_stdout: "PASS" + node_version: ">=8" +} + +keep_arguments: { + options = { + arguments: true, + keep_fargs: false, + } + input: { + (function(...[ {} ]) { + console.log(arguments[0]); + })("PASS"); + } + expect: { + (function(...[ {} ]) { + console.log(arguments[0]); + })("PASS"); + } + expect_stdout: "PASS" + node_version: ">=6" +} diff --git a/test/compress/spread.js b/test/compress/spreads.js index 944915fe..1e4dcbf6 100644 --- a/test/compress/spread.js +++ b/test/compress/spreads.js @@ -128,7 +128,7 @@ dont_inline: { do_inline: { options = { inline: true, - spread: true, + spreads: true, } input: { console.log(function(a) { @@ -167,7 +167,7 @@ drop_empty_call_1: { drop_empty_call_2: { options = { side_effects: true, - spread: true, + spreads: true, } input: { (function() {})(...[ console.log("PASS") ]); @@ -181,7 +181,7 @@ drop_empty_call_2: { convert_hole: { options = { - spread: true, + spreads: true, } input: { console.log(...[ "PASS", , 42 ]); @@ -275,7 +275,7 @@ reduce_vars_2: { convert_setter: { options = { objects: true, - spread: true, + spreads: true, } input: { var o = { @@ -419,7 +419,7 @@ keep_getter_4: { keep_accessor: { options = { objects: true, - spread: true, + spreads: true, } input: { var o = { @@ -467,7 +467,7 @@ keep_accessor: { object_key_order_1: { options = { objects: true, - spread: true, + spreads: true, } input: { var o = { @@ -497,7 +497,7 @@ object_key_order_1: { object_key_order_2: { options = { objects: true, - spread: true, + spreads: true, } input: { var o = { @@ -527,7 +527,7 @@ object_key_order_2: { object_key_order_3: { options = { objects: true, - spread: true, + spreads: true, } input: { var o = { @@ -557,7 +557,7 @@ object_key_order_3: { object_key_order_4: { options = { objects: true, - spread: true, + spreads: true, } input: { var o = { @@ -587,7 +587,7 @@ object_key_order_4: { object_spread_array: { options = { objects: true, - spread: true, + spreads: true, } input: { var o = { @@ -613,7 +613,7 @@ object_spread_array: { object_spread_string: { options = { objects: true, - spread: true, + spreads: true, } input: { var o = { @@ -670,7 +670,7 @@ unused_var_side_effects: { issue_4329: { options = { objects: true, - spread: true, + spreads: true, } input: { console.log({ @@ -749,7 +749,7 @@ issue_4342: { issue_4345: { options = { objects: true, - spread: true, + spreads: true, } input: { console.log({ @@ -809,7 +809,7 @@ issue_4361: { issue_4363: { options = { objects: true, - spread: true, + spreads: true, } input: { ({ diff --git a/test/reduce.js b/test/reduce.js index 54494c56..cd00d5e9 100644 --- a/test/reduce.js +++ b/test/reduce.js @@ -452,6 +452,10 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) CHANGED = true; return List.skip; } + } else if (parent.rest === node) { + node.start._permute++; + CHANGED = true; + return null; } // replace this node diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index 23b3a144..7f2a7d72 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -140,6 +140,8 @@ var SUPPORT = function(matrix) { default_value: "[ a = 0 ] = [];", destructuring: "[] = [];", let: "let a;", + rest: "var [...a] = [];", + rest_object: "var {...a} = {};", spread: "[...[]];", spread_object: "({...0});", trailing_comma: "function f(a,) {}", @@ -427,9 +429,22 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was var avoid = []; var len = unique_vars.length; var pairs = createPairs(recurmax, !nameLenBefore); + pairs.has_rest = nameLenBefore && convertToRest(pairs.names); unique_vars.length = len; return pairs; + function convertToRest(names) { + var last = names.length - 1; + if (last >= 0 && SUPPORT.rest && rng(20) == 0) { + var name = names[last]; + if (name && name.indexOf("=") < 0) { + if (/^[[{]/.test(name)) name = "[ " + name + " ]"; + names[last] = "..." + name; + return true; + } + } + } + function fill(nameFn, valueFn) { var save_async = async; if (was_async != null) { @@ -519,12 +534,13 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was if (index < pairs.values.length) { pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : ""); } else switch (rng(5)) { - case 0: + case 0: pairs.values[index] = createAssignmentValue(recurmax); - case 1: + case 1: pairs.values.length = index + 1; } } + convertToRest(pairs.names); names.unshift("[ " + pairs.names.join(", ") + " ]" + default_value); values.unshift("[ " + pairs.values.join(", ") + " ]"); }); @@ -547,10 +563,17 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was } }); 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)); + var last = pairs.names.length - 1, has_rest = false; + var s = pairs.names.map(function(name, index) { + if (index in keys) return keys[index] + ": " + name; + if (index == last && SUPPORT.rest_object && rng(20) == 0 && name.indexOf("=") < 0) { + has_rest = true; + return "..." + name; + } + return rng(10) == 0 ? name : createKey(recurmax, keys) + ": " + name; + }).join(", "); + if (!has_rest) s = addTrailingComma(s); + names.unshift("{ " + s + " }" + createDefaultValue(recurmax, noDefault)); }, function() { values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) { var key = index in keys ? keys[index] : createKey(recurmax, keys); @@ -677,7 +700,8 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { if (SUPPORT.destructuring && (!allowDefun || !(name in called)) && rng(2)) { called[name] = false; var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, save_async); - params = addTrailingComma(pairs.names.join(", ")); + params = pairs.names.join(", "); + if (!pairs.has_rest) params = addTrailingComma(params); args = addTrailingComma(pairs.values.join(", ")); } else { params = createParams(save_async); @@ -1037,7 +1061,8 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { var params; if (SUPPORT.destructuring && rng(2)) { var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, save_async); - params = addTrailingComma(pairs.names.join(", ")); + params = pairs.names.join(", "); + if (!pairs.has_rest) params = addTrailingComma(params); args = addTrailingComma(pairs.values.join(", ")); } else { params = createParams(save_async, NO_DUPLICATE); |