support rest parameters (#4515)

This commit is contained in:
Alex Lam S.L
2021-01-07 02:04:09 +00:00
committed by GitHub
parent 68497d0258
commit c3d358a5b8
12 changed files with 819 additions and 135 deletions

View File

@@ -627,7 +627,11 @@ to be `false` and all symbol names will be omitted.
- `arguments` (default: `true`) -- replace `arguments[index]` with function - `arguments` (default: `true`) -- replace `arguments[index]` with function
parameter name whenever possible. 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, - `booleans` (default: `true`) -- various optimizations for boolean context,
for example `!!a ? b : c → a ? b : c` 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`. when unsafe to do so, e.g. code which relies on `Function.prototype.length`.
Pass `true` to always retain function arguments. 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 - `keep_infinity` (default: `false`) -- Pass `true` to prevent `Infinity` from
being compressed into `1/0`, which may cause performance issues on Chrome. 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 - `reduce_vars` (default: `true`) -- Improve optimization on variables assigned with and
used as constant values. used as constant values.
- `rests` (default: `true`) -- apply optimizations to rest parameters
- `sequences` (default: `true`) -- join consecutive simple statements using the - `sequences` (default: `true`) -- join consecutive simple statements using the
comma operator. May be set to a positive integer to specify the maximum number 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 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 annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For
example: `/*@__PURE__*/foo();` example: `/*@__PURE__*/foo();`
- `spread` (default: `true`) -- flatten spread expressions. - `spreads` (default: `true`) -- flatten spread expressions.
- `strings` (default: `true`) -- compact string concatenations. - `strings` (default: `true`) -- compact string concatenations.
- `switches` (default: `true`) -- de-duplicate and remove unreachable `switch` branches - `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 - `top_retain` (default: `null`) -- prevent specific toplevel functions and
variables from `unused` removal (can be array, comma-separated, RegExp or variables from `unused` removal (can be array, comma-separated, RegExp or
function. Implies `toplevel`) 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 - `typeofs` (default: `true`) -- Transforms `typeof foo == "undefined"` into
`foo === void 0`. Note: recommend to set this value to `false` for IE10 and `foo === void 0`. Note: recommend to set this value to `false` for IE10 and
earlier versions due to known issues. 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 - `eval` (default `false`) -- Pass `true` to mangle names visible in scopes
where `eval` or `with` are used. 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 - `reserved` (default `[]`) -- Pass an array of identifiers that should be
excluded from mangling. Example: `["foo", "bar"]`. excluded from mangling. Example: `["foo", "bar"]`.

View File

@@ -513,10 +513,11 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
} }
}, AST_Scope); }, 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", $documentation: "Base class for functions",
$propdoc: { $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", uses_arguments: "[boolean/S] tells whether this function accesses the arguments array",
}, },
each_argname: function(visit) { each_argname: function(visit) {
@@ -534,6 +535,7 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", {
this.argnames.forEach(function(argname) { this.argnames.forEach(function(argname) {
argname.walk(tw); argname.walk(tw);
}); });
if (this.rest) this.rest.walk(tw);
}, },
walk: function(visitor) { walk: function(visitor) {
var node = this; var node = this;
@@ -542,6 +544,7 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", {
node.argnames.forEach(function(argname) { node.argnames.forEach(function(argname) {
argname.walk(visitor); argname.walk(visitor);
}); });
if (node.rest) node.rest.walk(visitor);
walk_body(node, 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[]"); if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
}, true); }, 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); }, AST_Scope);
@@ -576,6 +582,7 @@ var AST_Arrow = DEFNODE("Arrow", "inlined value", {
node.argnames.forEach(function(argname) { node.argnames.forEach(function(argname) {
argname.walk(visitor); argname.walk(visitor);
}); });
if (node.rest) node.rest.walk(visitor);
if (node.value) { if (node.value) {
node.value.walk(visitor); node.value.walk(visitor);
} else { } 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", $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) { function validate_destructured(node, check, allow_default) {
if (node instanceof AST_DefaultValue && allow_default) return validate_destructured(node.name, check); 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_Destructured) {
if (!(node instanceof AST_Hole)) validate_destructured(node, check, true); if (node.rest != null) validate_destructured(node.rest, check);
}); if (node instanceof AST_DestructuredArray) return node.elements.forEach(function(node) {
if (node instanceof AST_DestructuredObject) return node.properties.forEach(function(prop) { if (!(node instanceof AST_Hole)) validate_destructured(node, check, true);
validate_destructured(prop.value, check, true); });
}); if (node instanceof AST_DestructuredObject) return node.properties.forEach(function(prop) {
validate_destructured(prop.value, check, true);
});
}
check(node); check(node);
} }
var AST_DestructuredArray = DEFNODE("DestructuredArray", "elements", { var AST_DestructuredArray = DEFNODE("DestructuredArray", "elements", {
$documentation: "A destructured array literal", $documentation: "A destructured array literal",
$propdoc: { $propdoc: {
elements: "[AST_Node*] array of elements", elements: "[(AST_DefaultValue|AST_Destructured|AST_SymbolDeclaration|AST_SymbolRef)*] array of elements",
}, },
walk: function(visitor) { walk: function(visitor) {
var node = this; var node = this;
@@ -1181,6 +1194,7 @@ var AST_DestructuredArray = DEFNODE("DestructuredArray", "elements", {
node.elements.forEach(function(element) { node.elements.forEach(function(element) {
element.walk(visitor); element.walk(visitor);
}); });
if (node.rest) node.rest.walk(visitor);
}); });
}, },
}, AST_Destructured); }, AST_Destructured);
@@ -1189,7 +1203,7 @@ var AST_DestructuredKeyVal = DEFNODE("DestructuredKeyVal", "key value", {
$documentation: "A key: value destructured property", $documentation: "A key: value destructured property",
$propdoc: { $propdoc: {
key: "[string|AST_Node] property name. For computed property this is an AST_Node.", 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) { walk: function(visitor) {
var node = this; var node = this;
@@ -1218,6 +1232,7 @@ var AST_DestructuredObject = DEFNODE("DestructuredObject", "properties", {
node.properties.forEach(function(prop) { node.properties.forEach(function(prop) {
prop.walk(visitor); prop.walk(visitor);
}); });
if (node.rest) node.rest.walk(visitor);
}); });
}, },
_validate: function() { _validate: function() {

View File

@@ -81,13 +81,14 @@ function Compressor(options, false_by_default) {
objects : !false_by_default, objects : !false_by_default,
passes : 1, passes : 1,
properties : !false_by_default, properties : !false_by_default,
pure_getters : !false_by_default && "strict",
pure_funcs : null, pure_funcs : null,
pure_getters : !false_by_default && "strict",
reduce_funcs : !false_by_default, reduce_funcs : !false_by_default,
reduce_vars : !false_by_default, reduce_vars : !false_by_default,
rests : !false_by_default,
sequences : !false_by_default, sequences : !false_by_default,
side_effects : !false_by_default, side_effects : !false_by_default,
spread : !false_by_default, spreads : !false_by_default,
strings : !false_by_default, strings : !false_by_default,
switches : !false_by_default, switches : !false_by_default,
top_retain : null, 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) { var scanner = new TreeWalker(function(node) {
if (node instanceof AST_DefaultValue) { if (node instanceof AST_DefaultValue) {
reset_flags(node); reset_flags(node);
@@ -640,6 +641,15 @@ merge(Compressor.prototype, {
}; };
node.walk(scanner); 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; fixed = save;
return true; return true;
} }
@@ -670,6 +680,10 @@ merge(Compressor.prototype, {
}; };
node.value.walk(scanner); node.value.walk(scanner);
}); });
if (node.rest) {
fixed = false;
node.rest.walk(scanner);
}
fixed = save; fixed = save;
return true; return true;
} }
@@ -719,12 +733,12 @@ merge(Compressor.prototype, {
var safe = !fn.uses_arguments || tw.has_directive("use strict"); var safe = !fn.uses_arguments || tw.has_directive("use strict");
fn.argnames.forEach(function(arg, i) { fn.argnames.forEach(function(arg, i) {
var value = iife.args[i]; var value = iife.args[i];
scan_declaration(tw, arg, function() { scan_declaration(tw, compressor, arg, function() {
var j = fn.argnames.indexOf(arg); var j = fn.argnames.indexOf(arg);
return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife); return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife);
}, function(node, fixed) { }, function(node, fixed) {
var d = node.definition(); var d = node.definition();
if (safe && d.fixed === undefined) { if (fixed && safe && d.fixed === undefined) {
mark(tw, d); mark(tw, d);
tw.loop_ids[d.id] = tw.in_loop; tw.loop_ids[d.id] = tw.in_loop;
var value = iife.args[i]; var value = iife.args[i];
@@ -759,7 +773,7 @@ merge(Compressor.prototype, {
return; return;
} else if (node.operator == "=") { } else if (node.operator == "=") {
node.right.walk(tw); node.right.walk(tw);
scan_declaration(tw, left, function() { scan_declaration(tw, compressor, left, function() {
return node.right; return node.right;
}, function(sym, fixed, walk) { }, function(sym, fixed, walk) {
if (!(sym instanceof AST_SymbolRef)) { if (!(sym instanceof AST_SymbolRef)) {
@@ -769,7 +783,7 @@ merge(Compressor.prototype, {
} }
var d = sym.definition(); var d = sym.definition();
d.assignments++; 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); push_ref(d, sym);
mark(tw, d); mark(tw, d);
if (d.single_use && left instanceof AST_Destructured) d.single_use = false; if (d.single_use && left instanceof AST_Destructured) d.single_use = false;
@@ -1135,15 +1149,15 @@ merge(Compressor.prototype, {
} }
return true; return true;
}); });
def(AST_VarDef, function(tw) { def(AST_VarDef, function(tw, descend, compressor) {
var node = this; var node = this;
if (!node.value) return; if (!node.value) return;
node.value.walk(tw); node.value.walk(tw);
scan_declaration(tw, node.name, function() { scan_declaration(tw, compressor, node.name, function() {
return node.value; return node.value;
}, function(name, fixed) { }, function(name, fixed) {
var d = name.definition(); var d = name.definition();
if (safe_to_assign(tw, d, true)) { if (fixed && safe_to_assign(tw, d, true)) {
mark(tw, d); mark(tw, d);
tw.loop_ids[d.id] = tw.in_loop; tw.loop_ids[d.id] = tw.in_loop;
d.fixed = fixed; d.fixed = fixed;
@@ -5097,6 +5111,7 @@ merge(Compressor.prototype, {
node.argnames.forEach(function(argname) { node.argnames.forEach(function(argname) {
argname.mark_symbol(marker, scanner); argname.mark_symbol(marker, scanner);
}); });
if (node.rest) node.rest.mark_symbol(marker, scanner);
} }
if (node instanceof AST_Arrow && node.value) { if (node instanceof AST_Arrow && node.value) {
node.value.walk(tw); node.value.walk(tw);
@@ -5513,8 +5528,9 @@ merge(Compressor.prototype, {
var fns_with_marked_args = []; var fns_with_marked_args = [];
var trimmer = new TreeTransformer(function(node) { var trimmer = new TreeTransformer(function(node) {
if (node instanceof AST_DefaultValue) return trim_default(trimmer, 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) { if (node instanceof AST_DestructuredArray) {
var trim = true; var trim = !node.rest;
for (var i = node.elements.length; --i >= 0;) { for (var i = node.elements.length; --i >= 0;) {
var element = node.elements[i].transform(trimmer); var element = node.elements[i].transform(trimmer);
if (element) { if (element) {
@@ -5528,19 +5544,33 @@ merge(Compressor.prototype, {
} }
return node; return node;
} }
if (node instanceof AST_DestructuredKeyVal) { if (node instanceof AST_DestructuredObject) {
var retain = false; var properties = [];
if (node.key instanceof AST_Node) { node.properties.forEach(function(prop) {
node.key = node.key.transform(tt); var retain = false;
retain = node.key.has_side_effects(compressor); if (prop.key instanceof AST_Node) {
} prop.key = prop.key.transform(tt);
if (retain && is_decl(node.value)) { retain = prop.key.has_side_effects(compressor);
node.value = node.value.transform(tt); }
} else { if ((retain || node.rest) && is_decl(prop.value)) {
var value = node.value.transform(trimmer); prop.value = prop.value.transform(tt);
if (!value) return List.skip; properties.push(prop);
node.value = value; } 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; return node;
} }
if (node instanceof AST_SymbolDeclaration) return node.definition().id in in_use_ids ? node : null; 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); unused_fn_names.push(node);
} }
if (!(node instanceof AST_Accessor)) { 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;) { for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i]; var sym = a[i];
if (!(sym instanceof AST_SymbolFunarg)) { if (!(sym instanceof AST_SymbolFunarg)) {
@@ -5634,6 +5664,13 @@ merge(Compressor.prototype, {
sym.__unused = true; 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); fns_with_marked_args.push(node);
} }
} }
@@ -6149,8 +6186,22 @@ merge(Compressor.prototype, {
element = element.transform(trimmer); element = element.transform(trimmer);
if (element) elements[index] = element; 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; 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); fill_holes(node, elements);
node.elements = elements; node.elements = elements;
return node; return node;
@@ -6181,19 +6232,36 @@ merge(Compressor.prototype, {
value = values && values[prop.key]; value = values && values[prop.key];
retain = false; retain = false;
} }
if (retain && is_decl(prop.value)) { if ((retain || node.rest) && is_decl(prop.value)) {
prop.value = prop.value.transform(tt); prop.value = prop.value.transform(tt);
properties.push(prop); properties.push(prop);
} else { } else {
var newValue = prop.value.transform(trimmer); 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) { if (newValue) {
prop.value = newValue; prop.value = newValue;
properties.push(prop); 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; 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; return null;
} }
node.properties = properties; node.properties = properties;
@@ -7704,16 +7772,26 @@ merge(Compressor.prototype, {
if (!all(args, function(arg) { if (!all(args, function(arg) {
return !(arg instanceof AST_Spread); return !(arg instanceof AST_Spread);
})) return; })) 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 pos = 0, last = 0;
var is_iife = fn === exp && !fn.name; var is_iife = fn === exp && !fn.name;
var drop_defaults = is_iife && compressor.option("default_values"); var drop_defaults = is_iife && compressor.option("default_values");
var drop_fargs = is_iife && compressor.drop_fargs(fn, call) ? function(argname, arg) { var drop_fargs = is_iife && compressor.drop_fargs(fn, call) ? function(argname, arg) {
if (!argname) return true; if (!argname) return true;
if (argname instanceof AST_DestructuredArray) { 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) { 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 argname.__unused;
} : return_false; } : return_false;
@@ -8311,7 +8389,7 @@ merge(Compressor.prototype, {
} }
function can_substitute_directly() { 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 (compressor.option("inline") < 2 && fn.argnames.length) return;
if (!fn.variables.all(function(def) { if (!fn.variables.all(function(def) {
return def.references.length - def.replaced < 2 && def.orig[0] instanceof AST_SymbolFunarg; return def.references.length - def.replaced < 2 && def.orig[0] instanceof AST_SymbolFunarg;
@@ -8526,15 +8604,18 @@ merge(Compressor.prototype, {
operator: "=", operator: "=",
left: make_node(AST_DestructuredArray, self, { left: make_node(AST_DestructuredArray, self, {
elements: fn.argnames.map(function(argname) { elements: fn.argnames.map(function(argname) {
return argname.convert_symbol(AST_SymbolRef, function(ref, name) { return argname.convert_symbol(AST_SymbolRef, process);
var symbol = make_node(AST_SymbolVar, name, name);
name.definition().orig.push(symbol);
append_var(decls, expressions, symbol);
});
}), }),
rest: fn.rest && fn.rest.convert_symbol(AST_SymbolRef, process),
}), }),
right: make_node(AST_Array, self, { elements: self.args.slice() }), 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) { function flatten_vars(decls, expressions) {
@@ -8568,7 +8649,7 @@ merge(Compressor.prototype, {
function flatten_fn() { function flatten_fn() {
var decls = []; var decls = [];
var expressions = []; var expressions = [];
if (has_default > 1 || has_destructured) { if (has_default > 1 || has_destructured || fn.rest) {
flatten_destructured(decls, expressions); flatten_destructured(decls, expressions);
} else { } else {
flatten_args(decls, expressions); flatten_args(decls, expressions);
@@ -10308,7 +10389,7 @@ merge(Compressor.prototype, {
OPT(AST_Spread, function(self, compressor) { OPT(AST_Spread, function(self, compressor) {
var exp = self.expression; 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 List.splice(exp.elements.map(function(node) {
return node instanceof AST_Hole ? make_node(AST_Undefined, node).optimize(compressor) : node; return node instanceof AST_Hole ? make_node(AST_Undefined, node).optimize(compressor) : node;
})); }));
@@ -10392,7 +10473,7 @@ merge(Compressor.prototype, {
argname = null; 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) { while (index >= fn.argnames.length) {
argname = fn.make_var(AST_SymbolFunarg, fn, "argument_" + fn.argnames.length); argname = fn.make_var(AST_SymbolFunarg, fn, "argument_" + fn.argnames.length);
fn.argnames.push(argname); fn.argnames.push(argname);
@@ -10603,7 +10684,7 @@ merge(Compressor.prototype, {
if (!(prop instanceof AST_Spread)) return process(prop); if (!(prop instanceof AST_Spread)) return process(prop);
found = true; found = true;
var exp = prop.expression; 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); return !(prop instanceof AST_ObjectGetter || prop instanceof AST_Spread);
})) { })) {
changed = true; changed = true;

View File

@@ -992,16 +992,26 @@ function OutputStream(options) {
}); });
/* -----[ functions ]----- */ /* -----[ functions ]----- */
DEFPRINT(AST_Arrow, function(output) { function print_funargs(self, output) {
var self = this; output.with_parens(function() {
if (self.argnames.length == 1 && self.argnames[0] instanceof AST_SymbolFunarg) {
self.argnames[0].print(output);
} else output.with_parens(function() {
self.argnames.forEach(function(arg, i) { self.argnames.forEach(function(arg, i) {
if (i) output.comma(); if (i) output.comma();
arg.print(output); 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.space();
output.print("=>"); output.print("=>");
output.space(); output.space();
@@ -1016,12 +1026,7 @@ function OutputStream(options) {
output.space(); output.space();
self.name.print(output); self.name.print(output);
} }
output.with_parens(function() { print_funargs(self, output);
self.argnames.forEach(function(arg, i) {
if (i) output.comma();
arg.print(output);
});
});
output.space(); output.space();
print_braced(self, output, true); print_braced(self, output, true);
} }
@@ -1355,25 +1360,30 @@ function OutputStream(options) {
} : noop); } : noop);
}); });
DEFPRINT(AST_DestructuredArray, function(output) { DEFPRINT(AST_DestructuredArray, function(output) {
var a = this.elements, len = a.length; var a = this.elements, len = a.length, rest = this.rest;
output.with_square(len > 0 ? function() { output.with_square(len || rest ? function() {
output.space(); output.space();
a.forEach(function(exp, i) { a.forEach(function(exp, i) {
if (i) output.comma(); if (i) output.comma();
exp.print(output); 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 // If the final element is a hole, we need to make sure it
// doesn't look like a trailing comma, by inserting an actual // doesn't look like a trailing comma, by inserting an actual
// trailing comma. // trailing comma.
if (i === len - 1 && exp instanceof AST_Hole) output.comma();
output.comma(); }
});
output.space(); output.space();
} : noop); } : noop);
}); });
DEFPRINT(AST_DestructuredKeyVal, print_key_value); DEFPRINT(AST_DestructuredKeyVal, print_key_value);
DEFPRINT(AST_DestructuredObject, function(output) { DEFPRINT(AST_DestructuredObject, function(output) {
var props = this.properties; var props = this.properties, len = props.length, rest = this.rest;
if (props.length > 0) output.with_block(function() { if (len || rest) output.with_block(function() {
props.forEach(function(prop, i) { props.forEach(function(prop, i) {
if (i) { if (i) {
output.print(","); output.print(",");
@@ -1382,6 +1392,15 @@ function OutputStream(options) {
output.indent(); output.indent();
prop.print(output); prop.print(output);
}); });
if (rest) {
if (len) {
output.print(",");
output.newline();
}
output.indent();
output.print("...");
rest.print(output);
}
output.newline(); output.newline();
}); });
else print_braced_empty(this, output); else print_braced_empty(this, output);

View File

@@ -1039,11 +1039,18 @@ function parse($TEXT, options) {
} }
function to_funarg(node) { function to_funarg(node) {
if (node instanceof AST_Array) return new AST_DestructuredArray({ if (node instanceof AST_Array) {
start: node.start, var rest = null;
elements: node.elements.map(to_funarg), if (node.elements[node.elements.length - 1] instanceof AST_Spread) {
end: node.end, 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({ if (node instanceof AST_Assign) return new AST_DefaultValue({
start: node.start, start: node.start,
name: to_funarg(node.left), name: to_funarg(node.left),
@@ -1056,28 +1063,37 @@ function parse($TEXT, options) {
} }
if (node instanceof AST_DestructuredArray) { if (node instanceof AST_DestructuredArray) {
node.elements = node.elements.map(to_funarg); node.elements = node.elements.map(to_funarg);
if (node.rest) node.rest = to_funarg(node.rest);
return node; return node;
} }
if (node instanceof AST_DestructuredObject) { if (node instanceof AST_DestructuredObject) {
node.properties.forEach(function(prop) { node.properties.forEach(function(prop) {
prop.value = to_funarg(prop.value); prop.value = to_funarg(prop.value);
}); });
if (node.rest) node.rest = to_funarg(node.rest);
return node; return node;
} }
if (node instanceof AST_Hole) return node; if (node instanceof AST_Hole) return node;
if (node instanceof AST_Object) return new AST_DestructuredObject({ if (node instanceof AST_Object) {
start: node.start, var rest = null;
properties: node.properties.map(function(prop) { if (node.properties[node.properties.length - 1] instanceof AST_Spread) {
if (!(prop instanceof AST_ObjectKeyVal)) token_error(prop.start, "Invalid destructuring assignment"); rest = to_funarg(node.properties.pop().expression);
return new AST_DestructuredKeyVal({ }
start: prop.start, return new AST_DestructuredObject({
key: prop.key, start: node.start,
value: to_funarg(prop.value), properties: node.properties.map(function(prop) {
end: prop.end, if (!(prop instanceof AST_ObjectKeyVal)) token_error(prop.start, "Invalid destructuring assignment");
}); return new AST_DestructuredKeyVal({
}), start: prop.start,
end: node.end, 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); if (node instanceof AST_SymbolRef) return new AST_SymbolFunarg(node);
token_error(node.start, "Invalid arrow parameter"); token_error(node.start, "Invalid arrow parameter");
} }
@@ -1116,6 +1132,7 @@ function parse($TEXT, options) {
return new AST_Arrow({ return new AST_Arrow({
start: start, start: start,
argnames: argnames, argnames: argnames,
rest: exprs.rest || null,
body: body, body: body,
value: value, value: value,
end: prev(), end: prev(),
@@ -1155,6 +1172,7 @@ function parse($TEXT, options) {
if (S.input.has_directive("use strict")) { if (S.input.has_directive("use strict")) {
if (name) strict_verify_symbol(name); if (name) strict_verify_symbol(name);
argnames.forEach(strict_verify_symbol); argnames.forEach(strict_verify_symbol);
if (argnames.rest) strict_verify_symbol(argnames.rest);
} }
S.input.pop_directives_stack(); S.input.pop_directives_stack();
--S.in_function; --S.in_function;
@@ -1164,6 +1182,7 @@ function parse($TEXT, options) {
return new ctor({ return new ctor({
name: name, name: name,
argnames: argnames, argnames: argnames,
rest: argnames.rest || null,
body: body body: body
}); });
}; };
@@ -1445,17 +1464,22 @@ function parse($TEXT, options) {
if (allow_trailing_comma && is("punc", closing)) break; if (allow_trailing_comma && is("punc", closing)) break;
if (allow_empty && is("punc", ",")) { if (allow_empty && is("punc", ",")) {
a.push(new AST_Hole({ start: S.token, end: S.token })); 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({ a.push(new AST_Spread({
start: S.token, start: S.token,
expression: (next(), parser()), expression: (next(), parser()),
end: prev(), end: prev(),
})); }));
} else { } 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; return a;
} }
@@ -1634,17 +1658,19 @@ function parse($TEXT, options) {
var start = S.token; var start = S.token;
if (is("punc", "[")) { if (is("punc", "[")) {
next(); next();
var elements = expr_list("]", !options.strict, true, function() {
return maybe_default(type);
});
return new AST_DestructuredArray({ return new AST_DestructuredArray({
start: start, start: start,
elements: expr_list("]", !options.strict, true, function() { elements: elements,
return maybe_default(type); rest: elements.rest || null,
}),
end: prev(), end: prev(),
}); });
} }
if (is("punc", "{")) { if (is("punc", "{")) {
next(); next();
var first = true, a = []; var first = true, a = [], rest = null;
while (!is("punc", "}")) { while (!is("punc", "}")) {
if (first) first = false; else expect(","); if (first) first = false; else expect(",");
// allow trailing comma // allow trailing comma
@@ -1661,6 +1687,11 @@ function parse($TEXT, options) {
})); }));
continue; continue;
} }
if (is("operator", "...")) {
next();
rest = maybe_destructured(type);
break;
}
var name = as_symbol(type); var name = as_symbol(type);
if (is("operator", "=")) { if (is("operator", "=")) {
next(); next();
@@ -1678,10 +1709,11 @@ function parse($TEXT, options) {
end: prev(), end: prev(),
})); }));
} }
next(); expect("}");
return new AST_DestructuredObject({ return new AST_DestructuredObject({
start: start, start: start,
properties: a, properties: a,
rest: rest,
end: prev(), end: prev(),
}); });
} }
@@ -1845,6 +1877,11 @@ function parse($TEXT, options) {
function to_destructured(node) { function to_destructured(node) {
if (node instanceof AST_Array) { 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); var elements = node.elements.map(to_destructured);
return all(elements, function(node) { return all(elements, function(node) {
return node instanceof AST_DefaultValue return node instanceof AST_DefaultValue
@@ -1854,6 +1891,7 @@ function parse($TEXT, options) {
}) ? new AST_DestructuredArray({ }) ? new AST_DestructuredArray({
start: node.start, start: node.start,
elements: elements, elements: elements,
rest: rest,
end: node.end, end: node.end,
}) : node; }) : node;
} }
@@ -1867,6 +1905,11 @@ function parse($TEXT, options) {
}) : node; }) : node;
} }
if (!(node instanceof AST_Object)) return 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 = []; var props = [];
for (var i = 0; i < node.properties.length; i++) { for (var i = 0; i < node.properties.length; i++) {
var prop = node.properties[i]; var prop = node.properties[i];
@@ -1885,6 +1928,7 @@ function parse($TEXT, options) {
return new AST_DestructuredObject({ return new AST_DestructuredObject({
start: node.start, start: node.start,
properties: props, properties: props,
rest: rest,
end: node.end, end: node.end,
}); });
} }
@@ -1912,15 +1956,20 @@ function parse($TEXT, options) {
var start = S.token; var start = S.token;
var exprs = []; var exprs = [];
while (true) { while (true) {
if (maybe_arrow && is("operator", "...")) {
next();
exprs.rest = maybe_destructured(AST_SymbolFunarg);
break;
}
exprs.push(maybe_assign(no_in)); exprs.push(maybe_assign(no_in));
if (!is("punc", ",")) break; if (!is("punc", ",")) break;
next(); next();
if (maybe_arrow && is("punc", ")") && is_token(peek(), "punc", "=>")) break; if (maybe_arrow && is("punc", ")") && is_token(peek(), "punc", "=>")) break;
} }
return exprs.length == 1 ? exprs[0] : new AST_Sequence({ return exprs.length == 1 && !exprs.rest ? exprs[0] : new AST_Sequence({
start : start, start: start,
expressions : exprs, expressions: exprs,
end : prev() end: prev(),
}); });
} }

View File

@@ -119,6 +119,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
node.argnames.forEach(function(argname) { node.argnames.forEach(function(argname) {
argname.walk(tw); argname.walk(tw);
}); });
if (node.rest) node.rest.walk(tw);
walk_body(node, tw); walk_body(node, tw);
}); });
return true; return true;
@@ -220,6 +221,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
node.argnames.forEach(function(argname) { node.argnames.forEach(function(argname) {
argname.walk(tw); argname.walk(tw);
}); });
if (node.rest) node.rest.walk(tw);
in_arg.pop(); in_arg.pop();
if (node instanceof AST_Arrow && node.value) { if (node instanceof AST_Arrow && node.value) {
node.value.walk(tw); node.value.walk(tw);

View File

@@ -133,10 +133,12 @@ TreeTransformer.prototype = new TreeWalker;
DEF(AST_Lambda, function(self, tw) { DEF(AST_Lambda, function(self, tw) {
if (self.name) self.name = self.name.transform(tw); if (self.name) self.name = self.name.transform(tw);
self.argnames = do_list(self.argnames, tw); self.argnames = do_list(self.argnames, tw);
if (self.rest) self.rest = self.rest.transform(tw);
self.body = do_list(self.body, tw); self.body = do_list(self.body, tw);
}); });
DEF(AST_Arrow, function(self, tw) { DEF(AST_Arrow, function(self, tw) {
self.argnames = do_list(self.argnames, tw); self.argnames = do_list(self.argnames, tw);
if (self.rest) self.rest = self.rest.transform(tw);
if (self.value) { if (self.value) {
self.value = self.value.transform(tw); self.value = self.value.transform(tw);
} else { } else {
@@ -180,6 +182,7 @@ TreeTransformer.prototype = new TreeWalker;
}); });
DEF(AST_DestructuredArray, function(self, tw) { DEF(AST_DestructuredArray, function(self, tw) {
self.elements = do_list(self.elements, tw); self.elements = do_list(self.elements, tw);
if (self.rest) self.rest = self.rest.transform(tw);
}); });
DEF(AST_DestructuredKeyVal, function(self, tw) { DEF(AST_DestructuredKeyVal, function(self, tw) {
if (self.key instanceof AST_Node) self.key = self.key.transform(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) { DEF(AST_DestructuredObject, function(self, tw) {
self.properties = do_list(self.properties, tw); self.properties = do_list(self.properties, tw);
if (self.rest) self.rest = self.rest.transform(tw);
}); });
DEF(AST_Object, function(self, tw) { DEF(AST_Object, function(self, tw) {
self.properties = do_list(self.properties, tw); self.properties = do_list(self.properties, tw);

487
test/compress/rests.js Normal file
View File

@@ -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"
}

View File

@@ -128,7 +128,7 @@ dont_inline: {
do_inline: { do_inline: {
options = { options = {
inline: true, inline: true,
spread: true, spreads: true,
} }
input: { input: {
console.log(function(a) { console.log(function(a) {
@@ -167,7 +167,7 @@ drop_empty_call_1: {
drop_empty_call_2: { drop_empty_call_2: {
options = { options = {
side_effects: true, side_effects: true,
spread: true, spreads: true,
} }
input: { input: {
(function() {})(...[ console.log("PASS") ]); (function() {})(...[ console.log("PASS") ]);
@@ -181,7 +181,7 @@ drop_empty_call_2: {
convert_hole: { convert_hole: {
options = { options = {
spread: true, spreads: true,
} }
input: { input: {
console.log(...[ "PASS", , 42 ]); console.log(...[ "PASS", , 42 ]);
@@ -275,7 +275,7 @@ reduce_vars_2: {
convert_setter: { convert_setter: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
var o = { var o = {
@@ -419,7 +419,7 @@ keep_getter_4: {
keep_accessor: { keep_accessor: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
var o = { var o = {
@@ -467,7 +467,7 @@ keep_accessor: {
object_key_order_1: { object_key_order_1: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
var o = { var o = {
@@ -497,7 +497,7 @@ object_key_order_1: {
object_key_order_2: { object_key_order_2: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
var o = { var o = {
@@ -527,7 +527,7 @@ object_key_order_2: {
object_key_order_3: { object_key_order_3: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
var o = { var o = {
@@ -557,7 +557,7 @@ object_key_order_3: {
object_key_order_4: { object_key_order_4: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
var o = { var o = {
@@ -587,7 +587,7 @@ object_key_order_4: {
object_spread_array: { object_spread_array: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
var o = { var o = {
@@ -613,7 +613,7 @@ object_spread_array: {
object_spread_string: { object_spread_string: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
var o = { var o = {
@@ -670,7 +670,7 @@ unused_var_side_effects: {
issue_4329: { issue_4329: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
console.log({ console.log({
@@ -749,7 +749,7 @@ issue_4342: {
issue_4345: { issue_4345: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
console.log({ console.log({
@@ -809,7 +809,7 @@ issue_4361: {
issue_4363: { issue_4363: {
options = { options = {
objects: true, objects: true,
spread: true, spreads: true,
} }
input: { input: {
({ ({

View File

@@ -452,6 +452,10 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
CHANGED = true; CHANGED = true;
return List.skip; return List.skip;
} }
} else if (parent.rest === node) {
node.start._permute++;
CHANGED = true;
return null;
} }
// replace this node // replace this node

View File

@@ -140,6 +140,8 @@ var SUPPORT = function(matrix) {
default_value: "[ a = 0 ] = [];", default_value: "[ a = 0 ] = [];",
destructuring: "[] = [];", destructuring: "[] = [];",
let: "let a;", let: "let a;",
rest: "var [...a] = [];",
rest_object: "var {...a} = {};",
spread: "[...[]];", spread: "[...[]];",
spread_object: "({...0});", spread_object: "({...0});",
trailing_comma: "function f(a,) {}", trailing_comma: "function f(a,) {}",
@@ -427,9 +429,22 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
var avoid = []; var avoid = [];
var len = unique_vars.length; var len = unique_vars.length;
var pairs = createPairs(recurmax, !nameLenBefore); var pairs = createPairs(recurmax, !nameLenBefore);
pairs.has_rest = nameLenBefore && convertToRest(pairs.names);
unique_vars.length = len; unique_vars.length = len;
return pairs; 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) { function fill(nameFn, valueFn) {
var save_async = async; var save_async = async;
if (was_async != null) { if (was_async != null) {
@@ -519,12 +534,13 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
if (index < pairs.values.length) { if (index < pairs.values.length) {
pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : ""); pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : "");
} else switch (rng(5)) { } else switch (rng(5)) {
case 0: case 0:
pairs.values[index] = createAssignmentValue(recurmax); pairs.values[index] = createAssignmentValue(recurmax);
case 1: case 1:
pairs.values.length = index + 1; pairs.values.length = index + 1;
} }
} }
convertToRest(pairs.names);
names.unshift("[ " + pairs.names.join(", ") + " ]" + default_value); names.unshift("[ " + pairs.names.join(", ") + " ]" + default_value);
values.unshift("[ " + pairs.values.join(", ") + " ]"); values.unshift("[ " + pairs.values.join(", ") + " ]");
}); });
@@ -547,10 +563,17 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
} }
}); });
fill(function() { fill(function() {
names.unshift("{ " + addTrailingComma(pairs.names.map(function(name, index) { var last = pairs.names.length - 1, has_rest = false;
var key = index in keys ? keys[index] : rng(10) && createKey(recurmax, keys); var s = pairs.names.map(function(name, index) {
return key ? key + ": " + name : name; if (index in keys) return keys[index] + ": " + name;
}).join(", ")) + " }" + createDefaultValue(recurmax, noDefault)); 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() { }, function() {
values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) { values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) {
var key = index in keys ? keys[index] : createKey(recurmax, keys); 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)) { if (SUPPORT.destructuring && (!allowDefun || !(name in called)) && rng(2)) {
called[name] = false; called[name] = false;
var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, save_async); 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(", ")); args = addTrailingComma(pairs.values.join(", "));
} else { } else {
params = createParams(save_async); params = createParams(save_async);
@@ -1037,7 +1061,8 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
var params; var params;
if (SUPPORT.destructuring && rng(2)) { if (SUPPORT.destructuring && rng(2)) {
var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, save_async); 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(", ")); args = addTrailingComma(pairs.values.join(", "));
} else { } else {
params = createParams(save_async, NO_DUPLICATE); params = createParams(save_async, NO_DUPLICATE);