aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2020-11-17 00:01:24 +0000
committerGitHub <noreply@github.com>2020-11-17 08:01:24 +0800
commite5f80afc53e7df3b0b57931a03dc3481ddd9c30e (patch)
tree2c3686f36caccfd0a642a344eb6bcde728d80f6a
parent42e34c870ad5979135ab7407ab62abce9507fc28 (diff)
downloadtracifyjs-e5f80afc53e7df3b0b57931a03dc3481ddd9c30e.tar.gz
tracifyjs-e5f80afc53e7df3b0b57931a03dc3481ddd9c30e.zip
support destructured literals (#4278)
-rw-r--r--README.md12
-rw-r--r--lib/ast.js125
-rw-r--r--lib/compress.js798
-rw-r--r--lib/output.js90
-rw-r--r--lib/parse.js103
-rw-r--r--lib/scope.js20
-rw-r--r--lib/transform.js10
-rw-r--r--test/compress/destructured.js1297
-rw-r--r--test/reduce.js32
-rw-r--r--test/ufuzz/index.js293
10 files changed, 2476 insertions, 304 deletions
diff --git a/README.md b/README.md
index ecc844b5..c0b1e8b9 100644
--- a/README.md
+++ b/README.md
@@ -906,6 +906,8 @@ can pass additional arguments that control the code output:
- `shebang` (default `true`) -- preserve shebang `#!` in preamble (bash scripts)
+- `v8` (default `false`) -- enable workarounds for Chrome & Node.js bugs
+
- `webkit` (default `false`) -- enable workarounds for WebKit bugs.
PhantomJS users should set this option to `true`.
@@ -1138,7 +1140,7 @@ To enable fast minify mode with the API use:
UglifyJS.minify(code, { compress: false, mangle: true });
```
-#### Source maps and debugging
+### Source maps and debugging
Various `compress` transforms that simplify, rearrange, inline and remove code
are known to have an adverse effect on debugging with source maps. This is
@@ -1150,6 +1152,10 @@ disable the Uglify `compress` option and just use `mangle`.
To allow for better optimizations, the compiler makes various assumptions:
+- The code does not rely on preserving its runtime performance characteristics.
+ Typically uglified code will run faster due to less instructions and easier
+ inlining, but may be slower on rare occasions for a specific platform, e.g.
+ see [`reduce_funcs`](#compress-options).
- `.toString()` and `.valueOf()` don't have side effects, and for built-in
objects they have not been overridden.
- `undefined`, `NaN` and `Infinity` have not been externally redefined.
@@ -1177,3 +1183,7 @@ To allow for better optimizations, the compiler makes various assumptions:
top.B = "PASS";
console.log(B);
```
+- Use of `arguments` alongside destructuring as function parameters, e.g.
+ `function({}, arguments) {}` will result in `SyntaxError` in earlier versions
+ of Chrome and Node.js - UglifyJS may modify the input which in turn may
+ suppress those errors.
diff --git a/lib/ast.js b/lib/ast.js
index 02a38b9e..dc12a3f1 100644
--- a/lib/ast.js
+++ b/lib/ast.js
@@ -414,8 +414,12 @@ var AST_ForIn = DEFNODE("ForIn", "init object", {
_validate: function() {
if (this.init instanceof AST_Definitions) {
if (this.init.definitions.length != 1) throw new Error("init must have single declaration");
- } else if (!(this.init instanceof AST_PropAccess || this.init instanceof AST_SymbolRef)) {
- throw new Error("init must be assignable");
+ } else {
+ validate_destructured(this.init, function(node) {
+ if (!(node instanceof AST_PropAccess || node instanceof AST_SymbolRef)) {
+ throw new Error("init must be assignable: " + node.TYPE);
+ }
+ });
}
must_be_expression(this, "object");
},
@@ -496,12 +500,26 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
}
}, AST_Scope);
-var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments length_read", {
+var AST_Lambda = DEFNODE("Lambda", "name argnames length_read uses_arguments", {
$documentation: "Base class for functions",
$propdoc: {
name: "[AST_SymbolDeclaration?] the name of this function",
- argnames: "[AST_SymbolFunarg*] array of function arguments",
- uses_arguments: "[boolean/S] tells whether this function accesses the arguments array"
+ argnames: "[(AST_Destructured|AST_SymbolFunarg)*] array of function arguments and/or destructured literals",
+ uses_arguments: "[boolean/S] tells whether this function accesses the arguments array",
+ },
+ each_argname: function(visit) {
+ var tw = new TreeWalker(function(node) {
+ if (node instanceof AST_DestructuredObject) {
+ node.properties.forEach(function(prop) {
+ prop.value.walk(tw);
+ });
+ return true;
+ }
+ if (node instanceof AST_SymbolFunarg) visit(node);
+ });
+ this.argnames.forEach(function(argname) {
+ argname.walk(tw);
+ });
},
walk: function(visitor) {
var node = this;
@@ -515,7 +533,9 @@ var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments length_read", {
},
_validate: function() {
this.argnames.forEach(function(node) {
- if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
+ validate_destructured(node, function(node) {
+ if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
+ });
});
},
}, AST_Scope);
@@ -748,8 +768,10 @@ var AST_Const = DEFNODE("Const", null, {
_validate: function() {
this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
- if (!(node.name instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
- must_be_expression(node, "value");
+ validate_destructured(node.name, function(node) {
+ if (!(node instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
+ });
+ if (node.value != null) must_be_expression(node, "value");
});
},
}, AST_Definitions);
@@ -759,7 +781,9 @@ var AST_Let = DEFNODE("Let", null, {
_validate: function() {
this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
- if (!(node.name instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet");
+ validate_destructured(node.name, function(node) {
+ if (!(node instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet");
+ });
if (node.value != null) must_be_expression(node, "value");
});
},
@@ -770,7 +794,9 @@ var AST_Var = DEFNODE("Var", null, {
_validate: function() {
this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
- if (!(node.name instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
+ validate_destructured(node.name, function(node) {
+ if (!(node instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
+ });
if (node.value != null) must_be_expression(node, "value");
});
},
@@ -969,6 +995,14 @@ var AST_Assign = DEFNODE("Assign", null, {
$documentation: "An assignment expression — `a = b + 5`",
_validate: function() {
if (this.operator.indexOf("=") < 0) throw new Error('operator must contain "="');
+ if (this.left instanceof AST_Destructured) {
+ if (this.operator != "=") throw new Error("invalid destructuring operator: " + this.operator);
+ validate_destructured(this.left, function(node) {
+ if (!(node instanceof AST_PropAccess || node instanceof AST_SymbolRef)) {
+ throw new Error("left must be assignable: " + node.TYPE);
+ }
+ });
+ }
},
}, AST_Binary);
@@ -992,6 +1026,77 @@ var AST_Array = DEFNODE("Array", "elements", {
},
});
+var AST_Destructured = DEFNODE("Destructured", null, {
+ $documentation: "Base class for destructured literal",
+});
+
+function validate_destructured(node, check) {
+ if (node instanceof AST_DestructuredArray) return node.elements.forEach(function(node) {
+ if (!(node instanceof AST_Hole)) validate_destructured(node, check);
+ });
+ if (node instanceof AST_DestructuredObject) return node.properties.forEach(function(prop) {
+ validate_destructured(prop.value, check);
+ });
+ check(node);
+}
+
+var AST_DestructuredArray = DEFNODE("DestructuredArray", "elements", {
+ $documentation: "A destructured array literal",
+ $propdoc: {
+ elements: "[AST_Node*] array of elements",
+ },
+ walk: function(visitor) {
+ var node = this;
+ visitor.visit(node, function() {
+ node.elements.forEach(function(element) {
+ element.walk(visitor);
+ });
+ });
+ },
+}, AST_Destructured);
+
+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",
+ },
+ walk: function(visitor) {
+ var node = this;
+ visitor.visit(node, function() {
+ if (node.key instanceof AST_Node) node.key.walk(visitor);
+ node.value.walk(visitor);
+ });
+ },
+ _validate: function() {
+ if (typeof this.key != "string") {
+ if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
+ must_be_expression(this, "key");
+ }
+ must_be_expression(this, "value");
+ },
+});
+
+var AST_DestructuredObject = DEFNODE("DestructuredObject", "properties", {
+ $documentation: "A destructured object literal",
+ $propdoc: {
+ properties: "[AST_DestructuredKeyVal*] array of properties",
+ },
+ walk: function(visitor) {
+ var node = this;
+ visitor.visit(node, function() {
+ node.properties.forEach(function(prop) {
+ prop.walk(visitor);
+ });
+ });
+ },
+ _validate: function() {
+ this.properties.forEach(function(node) {
+ if (!(node instanceof AST_DestructuredKeyVal)) throw new Error("properties must be AST_DestructuredKeyVal[]");
+ });
+ },
+}, AST_Destructured);
+
var AST_Object = DEFNODE("Object", "properties", {
$documentation: "An object literal",
$propdoc: {
diff --git a/lib/compress.js b/lib/compress.js
index 9eb4a9e8..d9446e87 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -368,6 +368,15 @@ merge(Compressor.prototype, {
} while (sym = sym.parent_scope);
}
+ function can_drop_symbol(tw, ref, keep_lambda) {
+ var orig = ref.definition().orig;
+ if (ref.in_arg && (orig[0] instanceof AST_SymbolFunarg || orig[1] instanceof AST_SymbolFunarg)) return false;
+ return all(orig, function(sym) {
+ return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet
+ || keep_lambda && sym instanceof AST_SymbolLambda);
+ });
+ }
+
(function(def) {
def(AST_Node, noop);
@@ -585,6 +594,47 @@ merge(Compressor.prototype, {
if (is_arguments(def) && node.property instanceof AST_Number) def.reassigned = true;
}
+ function scan_declaration(tw, lhs, fixed, visit) {
+ var scanner = new TreeWalker(function(node) {
+ if (node instanceof AST_DestructuredArray) {
+ var save = fixed;
+ node.elements.forEach(function(node, index) {
+ if (node instanceof AST_Hole) return;
+ fixed = function() {
+ return make_node(AST_Sub, node, {
+ expression: save(),
+ property: make_node(AST_Number, node, {
+ value: index
+ })
+ });
+ };
+ node.walk(scanner);
+ });
+ fixed = save;
+ return true;
+ }
+ if (node instanceof AST_DestructuredObject) {
+ var save = fixed;
+ node.properties.forEach(function(node) {
+ if (node.key instanceof AST_Node) node.key.walk(tw);
+ fixed = function() {
+ var key = node.key;
+ return make_node(typeof key == "string" ? AST_Dot : AST_Sub, node, {
+ expression: save(),
+ property: key
+ });
+ };
+ node.value.walk(scanner);
+ });
+ fixed = save;
+ return true;
+ }
+ visit(node, fixed);
+ return true;
+ });
+ lhs.walk(scanner);
+ }
+
def(AST_Accessor, function(tw, descend, compressor) {
push(tw);
reset_variables(tw, compressor, this);
@@ -595,52 +645,66 @@ merge(Compressor.prototype, {
});
def(AST_Assign, function(tw, descend, compressor) {
var node = this;
- var eq = node.operator == "=";
- var sym = node.left;
- if (eq && sym.equivalent_to(node.right) && !sym.has_side_effects(compressor)) {
+ var left = node.left;
+ if (node.operator == "=" && left.equivalent_to(node.right) && !left.has_side_effects(compressor)) {
node.right.walk(tw);
- walk_prop(sym);
+ walk_prop(left);
node.__drop = true;
- return true;
- }
- if (!(sym instanceof AST_SymbolRef)) {
- mark_assignment_to_arguments(sym);
- return;
- }
- var d = sym.definition();
- d.assignments++;
- var fixed = d.fixed;
- var value = eq ? node.right : node;
- if (is_modified(compressor, tw, node, value, 0)) {
- d.fixed = false;
+ } else if (!(left instanceof AST_Destructured || left instanceof AST_SymbolRef)) {
+ mark_assignment_to_arguments(left);
return;
- }
- var safe = eq || safe_to_read(tw, d);
- node.right.walk(tw);
- if (safe && safe_to_assign(tw, d)) {
- push_ref(d, sym);
- mark(tw, d);
- if (eq) {
- tw.loop_ids[d.id] = tw.in_loop;
- mark_escaped(tw, d, sym.scope, node, value, 0, 1);
- sym.fixed = d.fixed = function() {
- return node.right;
- };
- } else {
+ } else if (node.operator == "=") {
+ node.right.walk(tw);
+ scan_declaration(tw, left, function() {
+ return node.right;
+ }, function(sym, fixed) {
+ if (!(sym instanceof AST_SymbolRef)) {
+ mark_assignment_to_arguments(sym);
+ sym.walk(tw);
+ return;
+ }
+ var d = sym.definition();
+ d.assignments++;
+ if (!is_modified(compressor, tw, node, node.right, 0)
+ && can_drop_symbol(tw, sym) && safe_to_assign(tw, d)) {
+ push_ref(d, sym);
+ mark(tw, d);
+ tw.loop_ids[d.id] = tw.in_loop;
+ mark_escaped(tw, d, sym.scope, node, node.right, 0, 1);
+ sym.fixed = d.fixed = fixed;
+ sym.fixed.assigns = [ node ];
+ } else {
+ sym.walk(tw);
+ d.fixed = false;
+ }
+ });
+ } else {
+ var d = left.definition();
+ d.assignments++;
+ var fixed = d.fixed;
+ if (is_modified(compressor, tw, node, node, 0)) {
+ d.fixed = false;
+ return;
+ }
+ var safe = safe_to_read(tw, d);
+ node.right.walk(tw);
+ if (safe && safe_to_assign(tw, d)) {
+ push_ref(d, left);
+ mark(tw, d);
if (d.single_use) d.single_use = false;
- sym.fixed = d.fixed = function() {
+ left.fixed = d.fixed = function() {
return make_node(AST_Binary, node, {
operator: node.operator.slice(0, -1),
- left: make_ref(sym, fixed),
+ left: make_ref(left, fixed),
right: node.right
});
};
+ left.fixed.assigns = !fixed || !fixed.assigns ? [] : fixed.assigns.slice();
+ left.fixed.assigns.push(node);
+ } else {
+ left.walk(tw);
+ d.fixed = false;
}
- sym.fixed.assigns = eq || !fixed || !fixed.assigns ? [] : fixed.assigns.slice();
- sym.fixed.assigns.push(node);
- } else {
- sym.walk(tw);
- d.fixed = false;
}
return true;
@@ -766,10 +830,12 @@ merge(Compressor.prototype, {
push(tw);
var init = this.init;
init.walk(tw);
- if (init instanceof AST_SymbolRef) {
+ if (init instanceof AST_Definitions) {
+ init.definitions[0].name.match_symbol(function(node) {
+ if (node instanceof AST_SymbolDeclaration) node.definition().fixed = false;
+ });
+ } else if (init instanceof AST_SymbolRef) {
init.definition().fixed = false;
- } else if (init instanceof AST_Var) {
- init.definitions[0].name.definition().fixed = false;
}
this.body.walk(tw);
pop(tw);
@@ -793,22 +859,26 @@ merge(Compressor.prototype, {
// Virtually turn IIFE parameters into variable definitions:
// (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
// So existing transformation rules can work on them.
+ var safe = !fn.uses_arguments || tw.has_directive("use strict");
fn.argnames.forEach(function(arg, i) {
- var d = arg.definition();
- if (d.fixed === undefined && (!fn.uses_arguments || tw.has_directive("use strict"))) {
- mark(tw, d);
- tw.loop_ids[d.id] = tw.in_loop;
- var value = iife.args[i];
- d.fixed = function() {
- var j = fn.argnames.indexOf(arg);
- return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife);
- };
- d.fixed.assigns = [ arg ];
- } else {
- d.fixed = false;
- }
+ var value = iife.args[i];
+ scan_declaration(tw, 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) {
+ mark(tw, d);
+ tw.loop_ids[d.id] = tw.in_loop;
+ var value = iife.args[i];
+ d.fixed = fixed;
+ d.fixed.assigns = [ arg ];
+ } else {
+ d.fixed = false;
+ }
+ });
});
- descend();
+ walk_body(fn, tw);
var safe_ids = tw.safe_ids;
pop(tw);
walk_defuns(tw, fn);
@@ -881,6 +951,7 @@ merge(Compressor.prototype, {
} else if (d.fixed === undefined || !safe_to_read(tw, d)) {
d.fixed = false;
} else if (d.fixed) {
+ if (this.in_arg && d.orig[0] instanceof AST_SymbolLambda) this.fixed = d.scope;
var value = this.fixed_value();
var recursive = recursive_ref(tw, d);
if (recursive) {
@@ -908,7 +979,7 @@ merge(Compressor.prototype, {
}
mark_escaped(tw, d, this.scope, this, value, 0, 1);
}
- this.fixed = d.fixed;
+ if (!this.fixed) this.fixed = d.fixed;
var parent;
if (d.fixed instanceof AST_Defun
&& !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) {
@@ -988,22 +1059,24 @@ merge(Compressor.prototype, {
}
return true;
});
- def(AST_VarDef, function(tw, descend) {
+ def(AST_VarDef, function(tw) {
var node = this;
if (!node.value) return;
- descend();
- var d = node.name.definition();
- if (safe_to_assign(tw, d, true)) {
- mark(tw, d);
- tw.loop_ids[d.id] = tw.in_loop;
- d.fixed = function() {
- return node.value;
- };
- d.fixed.assigns = [ node ];
- if (node.name instanceof AST_SymbolConst && d.redefined()) d.single_use = false;
- } else {
- d.fixed = false;
- }
+ node.value.walk(tw);
+ scan_declaration(tw, node.name, function() {
+ return node.value;
+ }, function(name, fixed) {
+ var d = name.definition();
+ if (safe_to_assign(tw, d, true)) {
+ mark(tw, d);
+ tw.loop_ids[d.id] = tw.in_loop;
+ d.fixed = fixed;
+ d.fixed.assigns = [ node ];
+ if (name instanceof AST_SymbolConst && d.redefined()) d.single_use = false;
+ } else {
+ d.fixed = false;
+ }
+ });
return true;
});
def(AST_While, function(tw, descend) {
@@ -1059,6 +1132,64 @@ merge(Compressor.prototype, {
return sym instanceof AST_SymbolLambda && def.scope.name === sym;
});
+ AST_Node.DEFMETHOD("convert_symbol", noop);
+ AST_Destructured.DEFMETHOD("convert_symbol", function(type, process) {
+ return this.transform(new TreeTransformer(function(node, descend) {
+ if (node instanceof AST_Destructured) {
+ node = node.clone();
+ descend(node, this);
+ return node;
+ }
+ if (node instanceof AST_DestructuredKeyVal) {
+ node = node.clone();
+ node.value = node.value.transform(this);
+ return node;
+ }
+ return node.convert_symbol(type, process);
+ }));
+ });
+ function convert_symbol(type, process) {
+ var node = make_node(type, this, this);
+ process(node, this);
+ return node;
+ }
+ AST_SymbolDeclaration.DEFMETHOD("convert_symbol", convert_symbol);
+ AST_SymbolRef.DEFMETHOD("convert_symbol", convert_symbol);
+
+ AST_Destructured.DEFMETHOD("mark_symbol", function(process, tw) {
+ var marker = new TreeWalker(function(node) {
+ if (node instanceof AST_DestructuredKeyVal) {
+ if (node.key instanceof AST_Node) node.key.walk(tw);
+ node.value.walk(marker);
+ return true;
+ }
+ return process(node);
+ });
+ this.walk(marker);
+ });
+ function mark_symbol(process) {
+ return process(this);
+ }
+ AST_SymbolDeclaration.DEFMETHOD("mark_symbol", mark_symbol);
+ AST_SymbolRef.DEFMETHOD("mark_symbol", mark_symbol);
+
+ AST_Node.DEFMETHOD("match_symbol", function(predicate) {
+ return predicate(this);
+ });
+ AST_Destructured.DEFMETHOD("match_symbol", function(predicate) {
+ var found = false;
+ var tw = new TreeWalker(function(node) {
+ if (found) return true;
+ if (node instanceof AST_DestructuredKeyVal) {
+ node.value.walk(tw);
+ return true;
+ }
+ if (predicate(node)) return found = true;
+ });
+ this.walk(tw);
+ return found;
+ });
+
function find_scope(compressor) {
var level = 0, node;
while (node = compressor.parent(level++)) {
@@ -1557,6 +1688,8 @@ merge(Compressor.prototype, {
}
if (node instanceof AST_Debugger) return true;
if (node instanceof AST_Defun) return funarg && lhs.name === node.name.name;
+ if (node instanceof AST_Destructured) return parent instanceof AST_Assign;
+ if (node instanceof AST_DestructuredKeyVal) return node.key instanceof AST_Node;
if (node instanceof AST_DWLoop) return true;
if (node instanceof AST_LoopControl) return true;
if (node instanceof AST_SymbolRef) {
@@ -1589,6 +1722,9 @@ merge(Compressor.prototype, {
}
if (!(fn instanceof AST_Lambda)) return true;
if (def && recursive_ref(compressor, def)) return true;
+ if (!all(fn.argnames, function(argname) {
+ return !(argname instanceof AST_Destructured);
+ })) return true;
if (fn.collapse_scanning) return false;
fn.collapse_scanning = true;
var replace = can_replace;
@@ -1636,13 +1772,20 @@ merge(Compressor.prototype, {
}
if (node instanceof AST_This) return symbol_in_lvalues(node, parent);
if (node instanceof AST_VarDef) {
- if (!node.value) return false;
- return lvalues.has(node.name.name) || side_effects && may_modify(node.name);
+ if ((in_try || !lhs_local) && node.name instanceof AST_Destructured) return true;
+ return node.value && node.name.match_symbol(function(node) {
+ return node instanceof AST_SymbolDeclaration
+ && (lvalues.has(node.name) || side_effects && may_modify(node));
+ });
}
var sym = is_lhs(node.left, node);
+ if (!sym) return false;
if (sym instanceof AST_PropAccess) return true;
- if (!(sym instanceof AST_SymbolRef)) return false;
- return lvalues.has(sym.name) || read_toplevel && compressor.exposed(sym.definition());
+ if ((in_try || !lhs_local) && sym instanceof AST_Destructured) return true;
+ return sym.match_symbol(function(node) {
+ return node instanceof AST_SymbolRef
+ && (lvalues.has(node.name) || read_toplevel && compressor.exposed(node.definition()));
+ });
}
function extract_args() {
@@ -1665,6 +1808,7 @@ merge(Compressor.prototype, {
name: sym,
value: arg
}));
+ if (sym instanceof AST_Destructured) continue;
if (sym.name in names) continue;
names[sym.name] = true;
if (!arg) {
@@ -1700,7 +1844,7 @@ merge(Compressor.prototype, {
if (expr instanceof AST_Array) {
expr.elements.forEach(extract_candidates);
} else if (expr instanceof AST_Assign) {
- candidates.push(hit_stack.slice());
+ if (!(expr.left instanceof AST_Destructured)) candidates.push(hit_stack.slice());
extract_candidates(expr.left);
extract_candidates(expr.right);
if (expr.left instanceof AST_SymbolRef) {
@@ -2057,7 +2201,7 @@ merge(Compressor.prototype, {
}
if (value) lvalues.add(node.name, is_modified(compressor, tw, node, value, 0));
if (find_arguments && node instanceof AST_Sub) {
- scope.argnames.forEach(function(argname) {
+ scope.each_argname(function(argname) {
if (!compressor.option("reduce_vars") || argname.definition().assignments) {
lvalues.add(argname.name, true);
}
@@ -2642,6 +2786,7 @@ merge(Compressor.prototype, {
function merge_conditional_assignments(var_def, exprs, keep) {
if (!compressor.option("conditionals")) return;
+ if (var_def.name instanceof AST_Destructured) return;
var trimmed = false;
var def = var_def.name.definition();
while (exprs.length > keep) {
@@ -2759,11 +2904,15 @@ merge(Compressor.prototype, {
}
} else if (stat instanceof AST_ForIn) {
if (defs && defs.TYPE == stat.init.TYPE) {
- defs.definitions = defs.definitions.concat(stat.init.definitions);
- var name = stat.init.definitions[0].name;
- var ref = make_node(AST_SymbolRef, name, name);
- name.definition().references.push(ref);
- stat.init = ref;
+ var defns = defs.definitions.slice();
+ stat.init = stat.init.definitions[0].name.convert_symbol(AST_SymbolRef, function(ref, name) {
+ defns.push(make_node(AST_VarDef, name, {
+ name: name,
+ value: null
+ }));
+ name.definition().references.push(ref);
+ });
+ defs.definitions = defns;
CHANGED = true;
}
stat.object = join_assigns_expr(stat.object);
@@ -2821,10 +2970,14 @@ merge(Compressor.prototype, {
var block;
stat.walk(new TreeWalker(function(node, descend) {
if (node instanceof AST_Definitions) {
- if (node.remove_initializers(compressor)) {
+ var defns = [];
+ if (node.remove_initializers(compressor, defns)) {
AST_Node.warn("Dropping initialization in unreachable code [{file}:{line},{col}]", node.start);
}
- push(node);
+ if (defns.length > 0) {
+ node.definitions = defns;
+ push(node);
+ }
return true;
}
if (node instanceof AST_Defun) {
@@ -3261,8 +3414,10 @@ merge(Compressor.prototype, {
var unary_side_effects = makePredicate("delete ++ --");
function is_lhs(node, parent) {
- if (parent instanceof AST_Unary && unary_side_effects[parent.operator]) return parent.expression;
- if (parent instanceof AST_Assign && parent.left === node) return node;
+ if (parent instanceof AST_Assign) return parent.left === node && node;
+ if (parent instanceof AST_Destructured) return node;
+ if (parent instanceof AST_DestructuredKeyVal) return node;
+ if (parent instanceof AST_Unary) return unary_side_effects[parent.operator] && parent.expression;
}
(function(def) {
@@ -3831,6 +3986,9 @@ merge(Compressor.prototype, {
if (fn.evaluating) return this;
if (fn.name && fn.name.definition().recursive_refs > 0) return this;
if (this.is_expr_pure(compressor)) return this;
+ if (!all(fn.argnames, function(sym) {
+ return !(sym instanceof AST_Destructured);
+ })) return this;
var args = eval_args(this.args);
if (!args && !ignore_side_effects) return this;
var stat = fn.first_statement();
@@ -4094,6 +4252,16 @@ merge(Compressor.prototype, {
def(AST_Definitions, function(compressor) {
return any(this.definitions, compressor);
});
+ def(AST_DestructuredArray, function(compressor) {
+ return any(this.elements, compressor);
+ });
+ def(AST_DestructuredKeyVal, function(compressor) {
+ return this.key instanceof AST_Node && this.key.has_side_effects(compressor)
+ || this.value.has_side_effects(compressor);
+ });
+ def(AST_DestructuredObject, function(compressor) {
+ return any(this.properties, compressor);
+ });
def(AST_Dot, function(compressor) {
return this.expression.may_throw_on_access(compressor)
|| this.expression.has_side_effects(compressor);
@@ -4132,9 +4300,7 @@ merge(Compressor.prototype, {
});
def(AST_SymbolDeclaration, return_false);
def(AST_SymbolRef, function(compressor) {
- return !(this.is_declared(compressor) && all(this.definition().orig, function(sym) {
- return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet);
- }));
+ return !this.is_declared(compressor) || !can_drop_symbol(compressor, this);
});
def(AST_This, return_false);
def(AST_Try, function(compressor) {
@@ -4461,12 +4627,26 @@ merge(Compressor.prototype, {
var prev = Object.create(null);
var tw = new TreeWalker(function(node, descend) {
if (node instanceof AST_Assign) {
- var sym = node.left;
- if (!(sym instanceof AST_SymbolRef)) return;
- if (node.operator != "=") mark(sym, true, false);
- node.right.walk(tw);
- mark(sym, false, true);
- return true;
+ var lhs = node.left;
+ if (lhs instanceof AST_Destructured) {
+ node.right.walk(tw);
+ lhs.mark_symbol(function(node) {
+ if (node instanceof AST_SymbolRef) {
+ mark(node, false, true);
+ } else {
+ node.walk(tw);
+ }
+ return true;
+ }, tw);
+ return true;
+ }
+ if (lhs instanceof AST_SymbolRef) {
+ if (node.operator != "=") mark(lhs, true, false);
+ node.right.walk(tw);
+ mark(lhs, false, true);
+ return true;
+ }
+ return;
}
if (node instanceof AST_Binary) {
if (!lazy_op[node.operator]) return;
@@ -4491,13 +4671,6 @@ merge(Compressor.prototype, {
pop();
return true;
}
- if (node instanceof AST_Const) {
- node.definitions.forEach(function(defn) {
- references[defn.name.definition().id] = false;
- defn.value.walk(tw);
- });
- return true;
- }
if (node instanceof AST_Continue) {
var target = tw.loopcontrol_target(node);
if (target instanceof AST_Do) insert(target);
@@ -4556,21 +4729,16 @@ merge(Compressor.prototype, {
pop();
return true;
}
- if (node instanceof AST_Let) {
- node.definitions.forEach(function(defn) {
- references[defn.name.definition().id] = false;
- if (defn.value) defn.value.walk(tw);
- });
- return true;
- }
if (node instanceof AST_Scope) {
push();
segment.block = node;
if (node === self) root = segment;
if (node instanceof AST_Lambda) {
if (node.name) references[node.name.definition().id] = false;
- if (node.uses_arguments && !tw.has_directive("use strict")) node.argnames.forEach(function(node) {
+ node.each_argname(node.uses_arguments && !tw.has_directive("use strict") ? function(node) {
references[node.definition().id] = false;
+ } : function(node) {
+ mark(node, false, true);
});
}
descend();
@@ -4596,10 +4764,6 @@ merge(Compressor.prototype, {
});
return true;
}
- if (node instanceof AST_SymbolFunarg) {
- if (!node.__unused) mark(node, false, true);
- return true;
- }
if (node instanceof AST_SymbolRef) {
mark(node, true, false);
return true;
@@ -4631,17 +4795,27 @@ merge(Compressor.prototype, {
return true;
}
if (node instanceof AST_VarDef) {
- if (node.value) {
- node.value.walk(tw);
- mark(node.name, false, true);
- } else {
- var id = node.name.definition().id;
- if (!(id in references)) {
- declarations.add(id, node.name);
+ if (node.value) node.value.walk(tw);
+ node.name.mark_symbol(node.value ? function(node) {
+ if (!(node instanceof AST_SymbolDeclaration)) return;
+ if (node instanceof AST_SymbolVar) {
+ mark(node, false, true);
+ } else {
+ references[node.definition().id] = false;
+ }
+ return true;
+ } : function(node) {
+ if (!(node instanceof AST_SymbolDeclaration)) return;
+ var id = node.definition().id;
+ if (!(node instanceof AST_SymbolVar)) {
+ references[id] = false;
+ } else if (!(id in references)) {
+ declarations.add(id, node);
} else if (references[id]) {
- references[id].push(node.name);
+ references[id].push(node);
}
- }
+ return true;
+ }, tw);
return true;
}
if (node instanceof AST_While) {
@@ -4711,7 +4885,7 @@ merge(Compressor.prototype, {
}
function mark(sym, read, write) {
- var def = sym.definition();
+ var def = sym.definition(), ldef;
if (def.id in references) {
var refs = references[def.id];
if (!refs) return;
@@ -4723,7 +4897,10 @@ merge(Compressor.prototype, {
} else if (!read) {
return;
}
- } else if (self.variables.get(def.name) !== def || compressor.exposed(def) || sym.name == "arguments") {
+ } else if ((ldef = self.variables.get(def.name)) !== def) {
+ if (ldef && root === segment) references[ldef.id] = false;
+ return references[def.id] = false;
+ } else if (compressor.exposed(def) || sym.name == "arguments") {
return references[def.id] = false;
} else {
var refs = declarations.get(def.id) || [];
@@ -4781,8 +4958,8 @@ merge(Compressor.prototype, {
var self = this;
var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
- var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node, props) {
- var sym;
+ var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(tw, node, props) {
+ var sym, nested = false;
if (node instanceof AST_Assign) {
if (node.write_only || node.operator == "=") sym = node.left;
} else if (node instanceof AST_Unary) {
@@ -4792,13 +4969,12 @@ merge(Compressor.prototype, {
while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) {
if (sym instanceof AST_Sub) props.unshift(sym.property);
sym = sym.expression;
+ nested = true;
}
}
if (!(sym instanceof AST_SymbolRef)) return;
if (compressor.exposed(sym.definition())) return;
- if (!all(sym.definition().orig, function(sym) {
- return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLambda || sym instanceof AST_SymbolLet);
- })) return;
+ if (!can_drop_symbol(tw, sym, nested)) return;
return sym;
};
var assign_in_use = Object.create(null);
@@ -4823,7 +4999,7 @@ merge(Compressor.prototype, {
var scope = this;
var tw = new TreeWalker(function(node, descend) {
if (node instanceof AST_Lambda && node.uses_arguments && !tw.has_directive("use strict")) {
- node.argnames.forEach(function(argname) {
+ node.each_argname(function(argname) {
var def = argname.definition();
if (!(def.id in in_use_ids)) {
in_use_ids[def.id] = true;
@@ -4844,24 +5020,28 @@ merge(Compressor.prototype, {
}
if (node instanceof AST_Definitions) {
node.definitions.forEach(function(defn) {
- var def = defn.name.definition();
- var_defs_by_id.add(def.id, defn);
- if (node instanceof AST_Var && def.orig[0] instanceof AST_SymbolCatch) {
- var redef = def.redefined();
- if (redef) var_defs_by_id.add(redef.id, defn);
- }
- if ((!drop_vars || (node instanceof AST_Const ? def.redefined() : def.const_redefs))
- && !(def.id in in_use_ids)) {
- in_use_ids[def.id] = true;
- in_use.push(def);
- }
- if (!defn.value) return;
- if (defn.value.has_side_effects(compressor)) {
- defn.value.walk(tw);
- } else {
- initializations.add(def.id, defn.value);
- }
- assignments.add(def.id, defn);
+ var side_effects = defn.value
+ && (defn.name instanceof AST_Destructured || defn.value.has_side_effects(compressor));
+ defn.name.mark_symbol(function(name) {
+ if (!(name instanceof AST_SymbolDeclaration)) return;
+ var def = name.definition();
+ var_defs_by_id.add(def.id, defn);
+ if (node instanceof AST_Var && def.orig[0] instanceof AST_SymbolCatch) {
+ var redef = def.redefined();
+ if (redef) var_defs_by_id.add(redef.id, defn);
+ }
+ if ((!drop_vars || (node instanceof AST_Const ? def.redefined() : def.const_redefs))
+ && !(def.id in in_use_ids)) {
+ in_use_ids[def.id] = true;
+ in_use.push(def);
+ }
+ if (defn.value) {
+ if (!side_effects) initializations.add(def.id, defn.value);
+ assignments.add(def.id, defn);
+ }
+ return true;
+ }, tw);
+ if (side_effects) defn.value.walk(tw);
});
return true;
}
@@ -4916,10 +5096,14 @@ merge(Compressor.prototype, {
var drop_fn_name = compressor.option("keep_fnames") ? return_false : compressor.option("ie8") ? function(def) {
return !compressor.exposed(def) && def.references.length == def.replaced;
} : function(def) {
- // any declarations with same name will overshadow
- // name of this anonymous function and can therefore
- // never be used anywhere
- return !(def.id in in_use_ids) || def.orig.length > 1;
+ if (!(def.id in in_use_ids)) return true;
+ if (def.orig.length < 2) return false;
+ // function argument will always overshadow its name
+ if (def.orig[1] instanceof AST_SymbolFunarg) return true;
+ // retain if referenced within destructured object of argument
+ return all(def.references, function(ref) {
+ return !ref.in_arg;
+ });
};
// pass 3: we should drop declarations not in_use
var unused_fn_names = [];
@@ -4928,7 +5112,7 @@ merge(Compressor.prototype, {
var tt = new TreeTransformer(function(node, descend, in_list) {
var parent = tt.parent();
if (drop_vars) {
- var props = [], sym = assign_as_unused(node, props);
+ var props = [], sym = assign_as_unused(tt, node, props);
if (sym) {
var def = sym.definition();
var in_use = def.id in in_use_ids;
@@ -4990,6 +5174,38 @@ merge(Compressor.prototype, {
var trim = compressor.drop_fargs(node, parent);
for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i];
+ if (sym instanceof AST_Destructured) {
+ sym.transform(new TreeTransformer(function(node) {
+ if (node instanceof AST_DestructuredArray) {
+ var trim = true;
+ for (var i = node.elements.length; --i >= 0;) {
+ var sym = node.elements[i];
+ if (!(sym instanceof AST_SymbolFunarg)) {
+ node.elements[i] = sym.transform(this);
+ trim = false;
+ } else if (sym.definition().id in in_use_ids) {
+ trim = false;
+ } else if (trim) {
+ node.elements.pop();
+ } else {
+ node.elements[i] = make_node(AST_Hole, sym);
+ }
+ }
+ return node;
+ }
+ if (node instanceof AST_DestructuredKeyVal) {
+ if (!(node.value instanceof AST_SymbolFunarg)) {
+ node.value = node.value.transform(this);
+ return node;
+ }
+ if (typeof node.key != "string") return node;
+ if (node.value.definition().id in in_use_ids) return node;
+ return List.skip;
+ }
+ }));
+ trim = false;
+ continue;
+ }
var def = sym.definition();
if (def.id in in_use_ids) {
trim = false;
@@ -5015,6 +5231,84 @@ merge(Compressor.prototype, {
var duplicated = 0;
node.definitions.forEach(function(def) {
if (def.value) def.value = def.value.transform(tt);
+ if (def.name instanceof AST_Destructured) {
+ var value = def.value;
+ var trimmer = new TreeTransformer(function(node) {
+ if (node instanceof AST_DestructuredArray) {
+ var save = value;
+ if (value instanceof AST_SymbolRef) value = value.fixed_value();
+ var values = value instanceof AST_Array && value.elements;
+ var elements = [];
+ node.elements.forEach(function(element, index) {
+ if (element instanceof AST_Hole) return;
+ value = values && values[index];
+ element = element.transform(trimmer);
+ if (element) elements[index] = element;
+ });
+ value = save;
+ if (values && elements.length == 0) return null;
+ for (var i = elements.length; --i >= 0;) {
+ if (!elements[i]) elements[i] = make_node(AST_Hole, node.elements[i] || node);
+ }
+ node.elements = elements;
+ return node;
+ }
+ if (node instanceof AST_DestructuredObject) {
+ var save = value;
+ if (value instanceof AST_SymbolRef) value = value.fixed_value();
+ var values;
+ if (value instanceof AST_Object) {
+ values = Object.create(null);
+ for (var i = 0; i < value.properties.length; i++) {
+ var prop = value.properties[i];
+ if (typeof prop.key != "string") {
+ values = null;
+ break;
+ }
+ values[prop.key] = prop.value;
+ }
+ }
+ var properties = [];
+ node.properties.forEach(function(prop) {
+ var retain;
+ if (prop.key instanceof AST_Node) {
+ prop.key = prop.key.transform(tt);
+ value = null;
+ retain = prop.key.has_side_effects(compressor);
+ } else {
+ value = values && values[prop.key];
+ retain = false;
+ }
+ if (retain && prop.value instanceof AST_SymbolDeclaration) {
+ properties.push(prop);
+ } else {
+ var newValue = prop.value.transform(trimmer);
+ if (newValue) {
+ prop.value = newValue;
+ properties.push(prop);
+ }
+ }
+ });
+ value = save;
+ if (properties.length == 0 && value && !value.may_throw_on_access(compressor)) {
+ return null;
+ }
+ node.properties = properties;
+ return node;
+ }
+ if (node instanceof AST_SymbolDeclaration) {
+ return !drop_vars || node.definition().id in in_use_ids || is_catch(node) ? node : null;
+ }
+ });
+ var name = def.name.transform(trimmer);
+ if (name) {
+ flush();
+ } else {
+ value = value.drop_side_effect_free(compressor);
+ if (value) side_effects.push(value);
+ }
+ return;
+ }
var sym = def.name.definition();
if (!drop_vars || sym.id in in_use_ids) {
if (def.value && indexOf_assign(sym, def) < 0) {
@@ -5070,26 +5364,9 @@ merge(Compressor.prototype, {
remove(var_defs, def);
duplicated++;
}
- if (side_effects.length > 0) {
- if (tail.length == 0) {
- body.push(make_node(AST_SimpleStatement, node, {
- body: make_sequence(node, side_effects)
- }));
- } else if (def.value) {
- side_effects.push(def.value);
- def.value = make_sequence(def.value, side_effects);
- } else {
- def.value = make_node(AST_UnaryPrefix, def, {
- operator: "void",
- expression: make_sequence(def, side_effects)
- });
- }
- side_effects = [];
- }
- tail.push(def);
+ flush();
}
- } else if (sym.orig[0] instanceof AST_SymbolCatch
- && sym.scope.resolve() === def.name.scope.resolve()) {
+ } else if (is_catch(def.name)) {
var value = def.value && def.value.drop_side_effect_free(compressor);
if (value) side_effects.push(value);
var var_defs = var_defs_by_id.get(sym.id);
@@ -5112,6 +5389,11 @@ merge(Compressor.prototype, {
sym.eliminated++;
}
+ function is_catch(node) {
+ var sym = node.definition();
+ return sym.orig[0] instanceof AST_SymbolCatch && sym.scope.resolve() === node.scope.resolve();
+ }
+
function can_rename(fn, name) {
var def = fn.variables.get(name);
return !def || fn.name && def === fn.name.definition();
@@ -5123,6 +5405,26 @@ merge(Compressor.prototype, {
|| parent instanceof AST_For && parent.init === node
|| parent instanceof AST_If;
}
+
+ function flush() {
+ if (side_effects.length > 0) {
+ if (tail.length == 0) {
+ body.push(make_node(AST_SimpleStatement, node, {
+ body: make_sequence(node, side_effects)
+ }));
+ } else if (def.value) {
+ side_effects.push(def.value);
+ def.value = make_sequence(def.value, side_effects);
+ } else {
+ def.value = make_node(AST_UnaryPrefix, def, {
+ operator: "void",
+ expression: make_sequence(def, side_effects)
+ });
+ }
+ side_effects = [];
+ }
+ tail.push(def);
+ }
});
switch (head.length) {
case 0:
@@ -5227,6 +5529,7 @@ merge(Compressor.prototype, {
} else while (sym instanceof AST_PropAccess) {
sym = sym.expression.tail_node();
}
+ if (sym instanceof AST_Destructured) return;
var def = sym.definition();
if (!def) return;
if (def.id in in_use_ids) return;
@@ -5342,7 +5645,7 @@ merge(Compressor.prototype, {
var def = node.expression.definition();
if (def.scope === self) assignments.add(def.id, node);
}
- var node_def, props = [], sym = assign_as_unused(node, props);
+ var node_def, props = [], sym = assign_as_unused(tw, node, props);
if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) {
props.forEach(function(prop) {
prop.walk(tw);
@@ -5376,6 +5679,7 @@ merge(Compressor.prototype, {
}
if (!drop_vars || !compressor.option("loops")) return;
if (!is_empty(node.body)) return;
+ if (node.init instanceof AST_Destructured) return;
if (node.init.has_side_effects(compressor)) return;
node.object.walk(tw);
return true;
@@ -5742,6 +6046,7 @@ merge(Compressor.prototype, {
}));
function can_hoist(sym, right, count) {
+ if (!(sym instanceof AST_Symbol)) return;
var def = sym.definition();
if (def.assignments != count) return;
if (def.direct_access) return;
@@ -5764,7 +6069,7 @@ merge(Compressor.prototype, {
}
});
- function safe_to_drop(fn, compressor) {
+ function fn_name_unused(fn, compressor) {
if (!fn.name || !compressor.option("ie8")) return true;
var def = fn.name.definition();
if (compressor.exposed(def)) return false;
@@ -5940,7 +6245,7 @@ merge(Compressor.prototype, {
return expr.drop_side_effect_free(compressor, first_in_statement);
});
def(AST_Function, function(compressor) {
- return safe_to_drop(this, compressor) ? null : this;
+ return fn_name_unused(this, compressor) ? null : this;
});
def(AST_Object, function(compressor, first_in_statement) {
var exprs = [];
@@ -5977,13 +6282,8 @@ merge(Compressor.prototype, {
if (!property) return expression;
return make_sequence(this, [ expression, property ]);
});
- function drop_symbol(ref) {
- return all(ref.definition().orig, function(sym) {
- return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet);
- });
- }
def(AST_SymbolRef, function(compressor) {
- return this.is_declared(compressor) && drop_symbol(this) ? null : this;
+ return this.is_declared(compressor) && can_drop_symbol(compressor, this) ? null : this;
});
def(AST_This, return_null);
def(AST_Unary, function(compressor, first_in_statement) {
@@ -5992,7 +6292,9 @@ merge(Compressor.prototype, {
this.write_only = !exp.has_side_effects(compressor);
return this;
}
- if (this.operator == "typeof" && exp instanceof AST_SymbolRef && drop_symbol(exp)) return null;
+ if (this.operator == "typeof" && exp instanceof AST_SymbolRef && can_drop_symbol(compressor, exp)) {
+ return null;
+ }
var node = exp.drop_side_effect_free(compressor, first_in_statement);
if (first_in_statement && node && is_iife_call(node)) {
if (node === exp && this.operator == "!") return this;
@@ -6477,6 +6779,7 @@ merge(Compressor.prototype, {
exprs.push(line.body);
} else if (line instanceof AST_Var) {
if (!compressor.option("sequences") && exprs.length > 0) return;
+ line.remove_initializers(compressor, var_defs);
line.definitions.forEach(process_var_def);
} else {
return;
@@ -6492,23 +6795,20 @@ merge(Compressor.prototype, {
if (stat instanceof AST_SimpleStatement) return [ stat.body ];
if (stat instanceof AST_Var) {
var exprs = [];
+ stat.remove_initializers(compressor, var_defs);
stat.definitions.forEach(process_var_def);
return exprs;
}
function process_var_def(var_def) {
- var_defs.push(make_node(AST_VarDef, var_def, {
- name: var_def.name,
- value: null
- }));
if (!var_def.value) return;
- var ref = make_node(AST_SymbolRef, var_def.name, var_def.name);
exprs.push(make_node(AST_Assign, var_def, {
operator: "=",
- left: ref,
+ left: var_def.name.convert_symbol(AST_SymbolRef, function(ref) {
+ refs.push(ref);
+ }),
right: var_def.value
}));
- refs.push(ref);
}
}
});
@@ -6710,25 +7010,27 @@ merge(Compressor.prototype, {
return self;
});
- AST_Const.DEFMETHOD("remove_initializers", function(compressor) {
- this.definitions.forEach(function(def) {
- def.value = make_node(AST_Undefined, def).optimize(compressor);
- });
- return true;
- });
-
- function remove_initializers() {
- var CHANGED = false;
- this.definitions.forEach(function(def) {
- if (!def.value) return;
- def.value = null;
- CHANGED = true;
- });
- return CHANGED;
+ function remove_initializers(make_value) {
+ return function(compressor, defns) {
+ var dropped = false;
+ this.definitions.forEach(function(defn) {
+ if (defn.value) dropped = true;
+ defn.name.match_symbol(function(node) {
+ if (node instanceof AST_SymbolDeclaration) defns.push(make_node(AST_VarDef, node, {
+ name: node,
+ value: make_value(compressor, node)
+ }));
+ });
+ });
+ return dropped;
+ };
}
- AST_Let.DEFMETHOD("remove_initializers", remove_initializers);
- AST_Var.DEFMETHOD("remove_initializers", remove_initializers);
+ AST_Const.DEFMETHOD("remove_initializers", remove_initializers(function(compressor, node) {
+ return make_node(AST_Undefined, node).optimize(compressor);
+ }));
+ AST_Let.DEFMETHOD("remove_initializers", remove_initializers(return_null));
+ AST_Var.DEFMETHOD("remove_initializers", remove_initializers(return_null));
AST_Definitions.DEFMETHOD("to_assignments", function() {
var assignments = this.definitions.reduce(function(a, defn) {
@@ -6751,25 +7053,26 @@ merge(Compressor.prototype, {
function varify(self, compressor) {
return compressor.option("varify") && all(self.definitions, function(defn) {
- var node = defn.name;
- if (!node.fixed_value()) return false;
- var def = node.definition();
- if (compressor.exposed(def)) return false;
- var scope = def.scope.resolve();
- for (var s = def.scope; s !== scope;) {
- s = s.parent_scope;
- if (s.var_names()[node.name]) return false;
- }
- return true;
+ return !defn.name.match_symbol(function(node) {
+ if (!(node instanceof AST_SymbolDeclaration)) return;
+ if (!node.fixed_value()) return true;
+ var def = node.definition();
+ if (compressor.exposed(def)) return true;
+ var scope = def.scope.resolve();
+ for (var s = def.scope; s !== scope;) {
+ s = s.parent_scope;
+ if (s.var_names()[node.name]) return true;
+ }
+ });
}) ? make_node(AST_Var, self, {
definitions: self.definitions.map(function(defn) {
- var name = make_node(AST_SymbolVar, defn.name, defn.name);
- var def = name.definition();
- def.orig[def.orig.indexOf(defn.name)] = name;
- var scope = def.scope.resolve();
- if (def.scope !== scope) scope.variables.set(def.name, def);
return make_node(AST_VarDef, defn, {
- name: name,
+ name: defn.name.convert_symbol(AST_SymbolVar, function(name, node) {
+ var def = name.definition();
+ def.orig[def.orig.indexOf(node)] = name;
+ var scope = def.scope.resolve();
+ if (def.scope !== scope) scope.variables.set(def.name, def);
+ }),
value: defn.value
});
})
@@ -6798,14 +7101,23 @@ merge(Compressor.prototype, {
if (fns_with_marked_args && fns_with_marked_args.indexOf(fn) < 0) return;
var args = call.args;
var pos = 0, last = 0;
- var drop_fargs = fn === exp && !fn.name && compressor.drop_fargs(fn, call);
+ var drop_fargs = fn === exp && !fn.name && 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;
+ }
+ if (argname instanceof AST_DestructuredObject) {
+ return argname.properties.length == 0 && arg && !arg.may_throw_on_access(compressor);
+ }
+ return argname.__unused;
+ } : return_false;
var side_effects = [];
for (var i = 0; i < args.length; i++) {
- var trim = i >= fn.argnames.length;
- if (trim || "__unused" in fn.argnames[i]) {
+ var argname = fn.argnames[i];
+ if (!argname || "__unused" in argname) {
var node = args[i].drop_side_effect_free(compressor);
- if (drop_fargs && (trim || fn.argnames[i].__unused)) {
- if (!trim) fn.argnames.splice(i, 1);
+ if (drop_fargs(argname)) {
+ if (argname) fn.argnames.splice(i, 1);
args.splice(i, 1);
if (node) side_effects.push(node);
i--;
@@ -6814,7 +7126,7 @@ merge(Compressor.prototype, {
side_effects.push(node);
args[pos++] = make_sequence(call, side_effects);
side_effects = [];
- } else if (!trim) {
+ } else if (argname) {
if (side_effects.length) {
args[pos++] = make_sequence(call, side_effects);
side_effects = [];
@@ -6825,6 +7137,13 @@ merge(Compressor.prototype, {
continue;
}
}
+ } else if (argname && drop_fargs(argname, args[i])) {
+ var node = args[i].drop_side_effect_free(compressor);
+ fn.argnames.splice(i, 1);
+ args.splice(i, 1);
+ if (node) side_effects.push(node);
+ i--;
+ continue;
} else {
side_effects.push(args[i]);
args[pos++] = make_sequence(call, side_effects);
@@ -6832,8 +7151,8 @@ merge(Compressor.prototype, {
}
last = pos;
}
- if (drop_fargs) for (; i < fn.argnames.length; i++) {
- if (fn.argnames[i].__unused) fn.argnames.splice(i--, 1);
+ for (; i < fn.argnames.length; i++) {
+ if (drop_fargs(fn.argnames[i])) fn.argnames.splice(i--, 1);
}
args.length = last;
if (!side_effects.length) return;
@@ -7129,7 +7448,12 @@ merge(Compressor.prototype, {
var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
var is_func = fn instanceof AST_Lambda;
var stat = is_func && fn.first_statement();
- var can_inline = compressor.option("inline") && !self.is_expr_pure(compressor);
+ var can_inline = is_func
+ && compressor.option("inline")
+ && !self.is_expr_pure(compressor)
+ && all(fn.argnames, function(argname) {
+ return !(argname instanceof AST_Destructured);
+ });
if (can_inline && stat instanceof AST_Return) {
var value = stat.value;
if (exp === fn && (!value || value.is_constant_expression())) {
@@ -7189,7 +7513,10 @@ merge(Compressor.prototype, {
}
if (compressor.option("side_effects")
&& all(fn.body, is_empty)
- && (fn !== exp || safe_to_drop(fn, compressor))) {
+ && all(fn.argnames, function(argname) {
+ return !(argname instanceof AST_Destructured);
+ })
+ && (fn !== exp || fn_name_unused(fn, compressor))) {
var args = self.args.concat(make_node(AST_Undefined, self));
return make_sequence(self, args).optimize(compressor);
}
@@ -9409,6 +9736,17 @@ merge(Compressor.prototype, {
return try_evaluate(compressor, self);
});
+ OPT(AST_DestructuredKeyVal, function(self, compressor) {
+ if (compressor.option("objects")) {
+ var key = self.key;
+ if (key instanceof AST_Node) {
+ key = key.evaluate(compressor);
+ if (key !== self.key) self.key = "" + key;
+ }
+ }
+ return self;
+ });
+
OPT(AST_Object, function(self, compressor) {
if (!compressor.option("objects") || compressor.has_directive("use strict")) return self;
for (var i = self.properties.length; --i >= 0;) {
diff --git a/lib/output.js b/lib/output.js
index 884cafc3..12358ffc 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -69,6 +69,7 @@ function OutputStream(options) {
semicolons : true,
shebang : true,
source_map : null,
+ v8 : false,
webkit : false,
width : 80,
wrap_iife : false,
@@ -499,11 +500,11 @@ function OutputStream(options) {
}
}
if (/comment[134]/.test(c.type)) {
- print("//" + c.value.replace(/[@#]__PURE__/g, ' ') + "\n");
+ print("//" + c.value.replace(/[@#]__PURE__/g, " ") + "\n");
indent();
last_nlb = true;
} else if (c.type == "comment2") {
- print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/");
+ print("/*" + c.value.replace(/[@#]__PURE__/g, " ") + "*/");
last_nlb = false;
}
});
@@ -557,10 +558,10 @@ function OutputStream(options) {
space();
}
if (/comment[134]/.test(c.type)) {
- print("//" + c.value.replace(/[@#]__PURE__/g, ' '));
+ print("//" + c.value.replace(/[@#]__PURE__/g, " "));
need_newline_indented = true;
} else if (c.type == "comment2") {
- print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/");
+ print("/*" + c.value.replace(/[@#]__PURE__/g, " ") + "*/");
need_space = true;
}
});
@@ -610,7 +611,7 @@ function OutputStream(options) {
},
parent : function(n) {
return stack[stack.length - 2 - (n || 0)];
- }
+ },
};
}
@@ -652,13 +653,7 @@ function OutputStream(options) {
/* -----[ PARENTHESES ]----- */
function PARENS(nodetype, func) {
- if (Array.isArray(nodetype)) {
- nodetype.forEach(function(nodetype) {
- PARENS(nodetype, func);
- });
- } else {
- nodetype.DEFMETHOD("needs_parens", func);
- }
+ nodetype.DEFMETHOD("needs_parens", func);
}
PARENS(AST_Node, return_false);
@@ -667,11 +662,11 @@ function OutputStream(options) {
// the first token to appear in a statement.
PARENS(AST_Function, function(output) {
if (!output.has_parens() && first_in_statement(output)) return true;
- if (output.option('webkit')) {
+ if (output.option("webkit")) {
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) return true;
}
- if (output.option('wrap_iife')) {
+ if (output.option("wrap_iife")) {
var p = output.parent();
if (p instanceof AST_Call && p.expression === this) return true;
}
@@ -679,9 +674,10 @@ function OutputStream(options) {
// same goes for an object literal, because otherwise it would be
// interpreted as a block of code.
- PARENS(AST_Object, function(output) {
+ function needs_parens_obj(output) {
return !output.has_parens() && first_in_statement(output);
- });
+ }
+ PARENS(AST_Object, needs_parens_obj);
PARENS(AST_Unary, function(output) {
var p = output.parent();
@@ -701,6 +697,7 @@ function OutputStream(options) {
|| p instanceof AST_Conditional
// { [(1, 2)]: 3 }[2] ==> 3
// { foo: (1, 2) }.foo ==> 2
+ || p instanceof AST_DestructuredKeyVal
|| p instanceof AST_ObjectProperty
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|| p instanceof AST_PropAccess && p.expression === this
@@ -747,7 +744,7 @@ function OutputStream(options) {
var p = output.parent();
if (p instanceof AST_New) return p.expression === this;
// https://bugs.webkit.org/show_bug.cgi?id=123506
- if (output.option('webkit')) {
+ if (output.option("webkit")) {
var g = output.parent(1);
return this.expression instanceof AST_Function
&& p instanceof AST_PropAccess
@@ -776,18 +773,29 @@ function OutputStream(options) {
}
});
- PARENS([ AST_Assign, AST_Conditional ], function(output) {
+ function needs_parens_assign_cond(self, output) {
var p = output.parent();
// 1 + (a = 2) + 3 → 6, side effect setting a = 2
if (p instanceof AST_Binary) return !(p instanceof AST_Assign);
// (a = func)() —or— new (a = Object)()
- if (p instanceof AST_Call) return p.expression === this;
+ if (p instanceof AST_Call) return p.expression === self;
// (a = foo) ? bar : baz
- if (p instanceof AST_Conditional) return p.condition === this;
+ if (p instanceof AST_Conditional) return p.condition === self;
// (a = foo)["prop"] —or— (a = foo).prop
- if (p instanceof AST_PropAccess) return p.expression === this;
+ if (p instanceof AST_PropAccess) return p.expression === self;
// !(a = false) → true
if (p instanceof AST_Unary) return true;
+ }
+ PARENS(AST_Assign, function(output) {
+ if (needs_parens_assign_cond(this, output)) return true;
+ // v8 parser bug => workaround
+ // f([1], [a] = []) => f([1], ([a] = []))
+ if (output.option("v8")) return this.left instanceof AST_Destructured;
+ // ({ p: a } = o);
+ if (this.left instanceof AST_DestructuredObject) return needs_parens_obj(output);
+ });
+ PARENS(AST_Conditional, function(output) {
+ return needs_parens_assign_cond(this, output);
});
/* -----[ PRINTERS ]----- */
@@ -1274,6 +1282,38 @@ function OutputStream(options) {
output.space();
} : noop);
});
+ DEFPRINT(AST_DestructuredArray, function(output) {
+ var a = this.elements, len = a.length;
+ output.with_square(len > 0 ? function() {
+ output.space();
+ a.forEach(function(exp, i) {
+ if (i) output.comma();
+ exp.print(output);
+ // 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.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() {
+ props.forEach(function(prop, i) {
+ if (i) {
+ output.print(",");
+ output.newline();
+ }
+ output.indent();
+ prop.print(output);
+ });
+ output.newline();
+ });
+ else print_braced_empty(this, output);
+ });
DEFPRINT(AST_Object, function(output) {
var props = this.properties;
if (props.length > 0) output.with_block(function() {
@@ -1314,12 +1354,13 @@ function OutputStream(options) {
}
}
- DEFPRINT(AST_ObjectKeyVal, function(output) {
+ function print_key_value(output) {
var self = this;
print_property_key(self, output);
output.colon();
self.value.print(output);
- });
+ }
+ DEFPRINT(AST_ObjectKeyVal, print_key_value);
function print_accessor(type) {
return function(output) {
var self = this;
@@ -1483,6 +1524,7 @@ function OutputStream(options) {
AST_Constant,
AST_Debugger,
AST_Definitions,
+ AST_Destructured,
AST_Finally,
AST_Jump,
AST_Lambda,
@@ -1497,7 +1539,7 @@ function OutputStream(options) {
output.add_mapping(this.start);
});
- DEFMAP([ AST_ObjectProperty ], function(output) {
+ DEFMAP([ AST_DestructuredKeyVal, AST_ObjectProperty ], function(output) {
if (typeof this.key == "string") output.add_mapping(this.start, this.key);
});
})();
diff --git a/lib/parse.js b/lib/parse.js
index c91fb73f..a422a5a6 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -973,14 +973,16 @@ function parse($TEXT, options) {
if (!is("punc", ";")) {
init = is("keyword", "const")
? (next(), const_(true))
+ : is("keyword", "let")
+ ? (next(), let_(true))
: is("keyword", "var")
? (next(), var_(true))
: expression(true, true);
if (is("operator", "in")) {
- if (init instanceof AST_Var) {
+ if (init instanceof AST_Definitions) {
if (init.definitions.length > 1)
croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos);
- } else if (!is_assignable(init)) {
+ } else if (!(is_assignable(init) || (init = to_destructured(init)) instanceof AST_Destructured)) {
croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos);
}
next();
@@ -1025,7 +1027,7 @@ function parse($TEXT, options) {
var argnames = [];
for (var first = true; !is("punc", ")");) {
if (first) first = false; else expect(",");
- argnames.push(as_symbol(AST_SymbolFunarg));
+ argnames.push(maybe_destructured(AST_SymbolFunarg));
}
next();
var loop = S.in_loop;
@@ -1146,16 +1148,16 @@ function parse($TEXT, options) {
});
}
- function vardefs(type, no_in, must_init) {
+ function vardefs(type, no_in) {
var a = [];
for (;;) {
var start = S.token;
- var name = as_symbol(type);
+ var name = maybe_destructured(type);
var value = null;
if (is("operator", "=")) {
next();
value = expression(false, no_in);
- } else if (must_init) {
+ } else if (!no_in && (type === AST_SymbolConst || name instanceof AST_Destructured)) {
croak("Missing initializer in declaration");
}
a.push(new AST_VarDef({
@@ -1174,7 +1176,7 @@ function parse($TEXT, options) {
var const_ = function(no_in) {
return new AST_Const({
start : prev(),
- definitions : vardefs(AST_SymbolConst, no_in, true),
+ definitions : vardefs(AST_SymbolConst, no_in),
end : prev()
});
};
@@ -1306,7 +1308,8 @@ function parse($TEXT, options) {
unexpected();
};
- function expr_list(closing, allow_trailing_comma, allow_empty) {
+ function expr_list(closing, allow_trailing_comma, allow_empty, parser) {
+ if (!parser) parser = expression;
var first = true, a = [];
while (!is("punc", closing)) {
if (first) first = false; else expect(",");
@@ -1314,7 +1317,7 @@ function parse($TEXT, options) {
if (is("punc", ",") && allow_empty) {
a.push(new AST_Hole({ start: S.token, end: S.token }));
} else {
- a.push(expression(false));
+ a.push(parser());
}
}
next();
@@ -1449,6 +1452,54 @@ function parse($TEXT, options) {
return sym;
}
+ function maybe_destructured(type) {
+ var start = S.token;
+ if (is("punc", "[")) {
+ next();
+ return new AST_DestructuredArray({
+ start: start,
+ elements: expr_list("]", !options.strict, true, function() {
+ return maybe_destructured(type);
+ }),
+ end: prev(),
+ });
+ }
+ if (is("punc", "{")) {
+ next();
+ var first = true, a = [];
+ while (!is("punc", "}")) {
+ if (first) first = false; else expect(",");
+ // allow trailing comma
+ if (!options.strict && is("punc", "}")) break;
+ var key_start = S.token;
+ var key = as_property_key();
+ if (!is("punc", ":") && key_start.type == "name") {
+ a.push(new AST_DestructuredKeyVal({
+ start: key_start,
+ key: key,
+ value: _make_symbol(type, key_start),
+ end: prev(),
+ }));
+ continue;
+ }
+ expect(":");
+ a.push(new AST_DestructuredKeyVal({
+ start: key_start,
+ key: key,
+ value: maybe_destructured(type),
+ end: prev(),
+ }));
+ }
+ next();
+ return new AST_DestructuredObject({
+ start: start,
+ properties: a,
+ end: prev(),
+ });
+ }
+ return as_symbol(type);
+ }
+
function mark_pure(call) {
var start = call.start;
var comments = start.comments_before;
@@ -1578,11 +1629,43 @@ function parse($TEXT, options) {
return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef;
}
+ function to_destructured(node) {
+ if (node instanceof AST_Array) {
+ var elements = node.elements.map(to_destructured);
+ return all(elements, function(node) {
+ return node instanceof AST_Destructured || node instanceof AST_Hole || is_assignable(node);
+ }) ? new AST_DestructuredArray({
+ start: node.start,
+ elements: elements,
+ end: node.end,
+ }) : node;
+ }
+ if (!(node instanceof AST_Object)) return node;
+ var props = [];
+ for (var i = 0; i < node.properties.length; i++) {
+ var prop = node.properties[i];
+ if (!(prop instanceof AST_ObjectKeyVal)) return node;
+ var value = to_destructured(prop.value);
+ if (!(value instanceof AST_Destructured || is_assignable(value))) return node;
+ props.push(new AST_DestructuredKeyVal({
+ start: prop.start,
+ key: prop.key,
+ value: value,
+ end: prop.end,
+ }));
+ }
+ return new AST_DestructuredObject({
+ start: node.start,
+ properties: props,
+ end: node.end,
+ });
+ }
+
var maybe_assign = function(no_in) {
var start = S.token;
var left = maybe_conditional(no_in), val = S.token.value;
if (is("operator") && ASSIGNMENT[val]) {
- if (is_assignable(left)) {
+ if (is_assignable(left) || val == "=" && (left = to_destructured(left)) instanceof AST_Destructured) {
next();
return new AST_Assign({
start : start,
diff --git a/lib/scope.js b/lib/scope.js
index 80034a54..bffd493d 100644
--- a/lib/scope.js
+++ b/lib/scope.js
@@ -204,7 +204,17 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
// pass 2: find back references and eval
self.globals = new Dictionary();
+ var in_arg = [];
var tw = new TreeWalker(function(node) {
+ if (node instanceof AST_Lambda) {
+ in_arg.push(node);
+ node.argnames.forEach(function(argname) {
+ argname.walk(tw);
+ });
+ in_arg.pop();
+ walk_body(node, tw);
+ return true;
+ }
if (node instanceof AST_LoopControl) {
if (node.label) node.label.thedef.references.push(node);
return true;
@@ -212,6 +222,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
if (node instanceof AST_SymbolRef) {
var name = node.name;
var sym = node.scope.find_variable(name);
+ for (var i = in_arg.length; i > 0 && sym;) {
+ i = in_arg.lastIndexOf(sym.scope, i - 1);
+ if (i < 0) break;
+ var decl = sym.orig[0];
+ if (decl instanceof AST_SymbolFunarg || decl instanceof AST_SymbolLambda) {
+ node.in_arg = true;
+ break;
+ }
+ sym = sym.scope.parent_scope.find_variable(name);
+ }
if (!sym) {
sym = self.def_global(node);
} else if (name == "arguments" && sym.scope instanceof AST_Lambda) {
diff --git a/lib/transform.js b/lib/transform.js
index 4aeb0f02..3831504d 100644
--- a/lib/transform.js
+++ b/lib/transform.js
@@ -160,6 +160,16 @@ TreeTransformer.prototype = new TreeWalker;
DEF(AST_Array, function(self, tw) {
self.elements = do_list(self.elements, tw);
});
+ DEF(AST_DestructuredArray, function(self, tw) {
+ self.elements = do_list(self.elements, tw);
+ });
+ DEF(AST_DestructuredKeyVal, function(self, tw) {
+ if (self.key instanceof AST_Node) self.key = self.key.transform(tw);
+ self.value = self.value.transform(tw);
+ });
+ DEF(AST_DestructuredObject, function(self, tw) {
+ self.properties = do_list(self.properties, tw);
+ });
DEF(AST_Object, function(self, tw) {
self.properties = do_list(self.properties, tw);
});
diff --git a/test/compress/destructured.js b/test/compress/destructured.js
new file mode 100644
index 00000000..01b24563
--- /dev/null
+++ b/test/compress/destructured.js
@@ -0,0 +1,1297 @@
+redefine_arguments_1: {
+ options = {
+ toplevel: false,
+ unused: true,
+ }
+ input: {
+ function f([ arguments ]) {}
+ }
+ expect: {
+ function f([]) {}
+ }
+ expect_stdout: true
+ node_version: ">=8"
+}
+
+redefine_arguments_1_toplevel: {
+ options = {
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ function f([ arguments ]) {}
+ }
+ expect: {}
+ expect_stdout: true
+ node_version: ">=8"
+}
+
+redefine_arguments_2: {
+ options = {
+ side_effects: true,
+ }
+ input: {
+ (function([], arguments) {});
+ }
+ expect: {}
+ expect_stdout: true
+ node_version: ">=8"
+}
+
+redefine_arguments_3: {
+ options = {
+ keep_fargs: "strict",
+ unused: true,
+ }
+ input: {
+ (function([], arguments) {})([]);
+ }
+ expect: {
+ (function() {})();
+ }
+ expect_stdout: true
+ node_version: ">=8"
+}
+
+redefine_arguments_4: {
+ options = {
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ function f() {
+ (function({}, arguments) {});
+ }
+ }
+ expect: {}
+ expect_stdout: true
+ node_version: ">=8"
+}
+
+uses_arguments_1_merge_vars: {
+ options = {
+ merge_vars: true,
+ }
+ input: {
+ console.log(typeof function({}) {
+ return arguments;
+ }(42));
+ }
+ expect: {
+ console.log(typeof function({}) {
+ return arguments;
+ }(42));
+ }
+ expect_stdout: "object"
+ node_version: ">=6"
+}
+
+uses_arguments_1_unused: {
+ options = {
+ unused: true,
+ }
+ input: {
+ console.log(typeof function({}) {
+ return arguments;
+ }(42));
+ }
+ expect: {
+ console.log(typeof function({}) {
+ return arguments;
+ }(42));
+ }
+ expect_stdout: "object"
+ node_version: ">=6"
+}
+
+uses_arguments_2: {
+ options = {
+ collapse_vars: true,
+ reduce_vars: true,
+ }
+ input: {
+ console.log(typeof function({ a }) {
+ a[1] = 2;
+ return arguments;
+ }({ a: 42 }));
+ }
+ expect: {
+ console.log(typeof function({ a }) {
+ a[1] = 2;
+ return arguments;
+ }({ a: 42 }));
+ }
+ expect_stdout: "object"
+ node_version: ">=6"
+}
+
+funarg_merge_vars_1: {
+ options = {
+ merge_vars: true,
+ }
+ input: {
+ function f(a, {
+ [a]: b
+ }) {
+ console.log(b);
+ }
+ f(0, [ "PASS" ]);
+ }
+ expect: {
+ function f(a, {
+ [a]: b
+ }) {
+ console.log(b);
+ }
+ f(0, [ "PASS" ]);
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_merge_vars_2: {
+ options = {
+ merge_vars: true,
+ }
+ input: {
+ var a = 0;
+ (function f({
+ [a]: b,
+ }) {
+ var a = typeof b;
+ console.log(a);
+ })([ 42 ]);
+ }
+ expect: {
+ var a = 0;
+ (function f({
+ [a]: b,
+ }) {
+ var a = typeof b;
+ console.log(a);
+ })([ 42 ]);
+ }
+ expect_stdout: "number"
+ node_version: ">=6"
+}
+
+funarg_side_effects_1: {
+ options = {
+ side_effects: true,
+ }
+ input: {
+ try {
+ (function({}) {})();
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect: {
+ try {
+ (function({}) {})();
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_side_effects_2: {
+ options = {
+ side_effects: true,
+ }
+ input: {
+ try {
+ (function({
+ [(a, 0)]: a,
+ }) {})(1);
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect: {
+ try {
+ (function({
+ [(a, 0)]: a,
+ }) {})(1);
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_side_effects_3: {
+ options = {
+ side_effects: true,
+ }
+ input: {
+ try {
+ (function({
+ p: {
+ [(a, 0)]: a,
+ },
+ }) {})({
+ p: 1,
+ });
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect: {
+ try {
+ (function({
+ p: {
+ [(a, 0)]: a,
+ },
+ }) {})({
+ p: 1,
+ });
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_unused_1: {
+ options = {
+ keep_fargs: "strict",
+ unused: true,
+ }
+ input: {
+ (function([]) {})([ console.log("PASS") ]);
+ }
+ expect: {
+ (function() {})(console.log("PASS"));
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_unused_2: {
+ options = {
+ unused: true,
+ }
+ input: {
+ function f([ a, b, c ]) {
+ console.log(b);
+ }
+ f([ "FAIL", "PASS" ]);
+ }
+ expect: {
+ function f([ , b ]) {
+ console.log(b);
+ }
+ f([ "FAIL", "PASS" ]);
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_unused_3: {
+ options = {
+ objects: true,
+ evaluate: true,
+ unused: true,
+ }
+ input: {
+ function f({
+ [0]: a,
+ }) {
+ return "PASS";
+ }
+ console.log(f([ "FAIL" ]));
+ }
+ expect: {
+ function f({}) {
+ return "PASS";
+ }
+ console.log(f([ "FAIL" ]));
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_unused_4: {
+ options = {
+ keep_fargs: "strict",
+ pure_getters: "strict",
+ unused: true,
+ }
+ input: {
+ console.log(function([ a ], { b }, c) {
+ return "PASS";
+ }([ 1 ], { b: 2 }, 3));
+ }
+ expect: {
+ console.log(function() {
+ return "PASS";
+ }());
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_unused_5: {
+ options = {
+ unused: true,
+ }
+ input: {
+ try {
+ (function({
+ [c = 0]: c
+ }) {})(1);
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect: {
+ try {
+ (function({
+ [c = 0]: c
+ }) {})(1);
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_unused_6_inline: {
+ options = {
+ inline: true,
+ pure_getters: "strict",
+ unused: true,
+ }
+ input: {
+ (function(a) {
+ var {} = (a = console, 42);
+ })();
+ console.log(typeof a);
+ }
+ expect: {
+ void console;
+ console.log(typeof a);
+ }
+ expect_stdout: "undefined"
+ node_version: ">=6"
+}
+
+funarg_unused_6_keep_fargs: {
+ options = {
+ keep_fargs: "strict",
+ unused: true,
+ }
+ input: {
+ (function(a) {
+ var {} = (a = console, 42);
+ })();
+ console.log(typeof a);
+ }
+ expect: {
+ (function() {
+ var {} = (console, 42);
+ })();
+ console.log(typeof a);
+ }
+ expect_stdout: "undefined"
+ node_version: ">=6"
+}
+
+funarg_collapse_vars_1: {
+ options = {
+ collapse_vars: true,
+ unused: true,
+ }
+ input: {
+ console.log(function(a, {}) {
+ return typeof a;
+ var b;
+ }(console, {}));
+ }
+ expect: {
+ console.log(function(a, {}) {
+ return typeof (0, console);
+ }(0, {}));
+ }
+ expect_stdout: "object"
+ node_version: ">=6"
+}
+
+funarg_collapse_vars_2: {
+ options = {
+ collapse_vars: true,
+ keep_fargs: "strict",
+ unused: true,
+ }
+ input: {
+ console.log(function([ a ], { b }, c) {
+ return a + b + c;
+ }([ "P" ], { b: "A" }, "SS"));
+ }
+ expect: {
+ console.log(function([ a ], { b }) {
+ return a + b + "SS";
+ }([ "P" ], { b: "A" }));
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_collapse_vars_3: {
+ options = {
+ collapse_vars: true,
+ }
+ input: {
+ var a = "FAIL";
+ try {
+ a = "PASS";
+ (function({}) {})();
+ throw "PASS";
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect: {
+ var a = "FAIL";
+ try {
+ a = "PASS";
+ (function({}) {})();
+ throw "PASS";
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_reduce_vars_1: {
+ options = {
+ reduce_vars: true,
+ unused: true,
+ }
+ input: {
+ try {
+ (function({
+ [a]: b,
+ }, a) {
+ console.log("FAIL");
+ })({});
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect: {
+ try {
+ (function({
+ [a]: b,
+ }, a) {
+ console.log("FAIL");
+ })({});
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_reduce_vars_2: {
+ options = {
+ evaluate: true,
+ keep_fargs: "strict",
+ pure_getters: "strict",
+ reduce_vars: true,
+ unsafe: true,
+ unused: true,
+ }
+ input: {
+ console.log(function([ a ], { b }, c) {
+ return a + b + c;
+ }([ "P" ], { b: "A" }, "SS"));
+ }
+ expect: {
+ console.log("PASS");
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_reduce_vars_3: {
+ options = {
+ evaluate: true,
+ reduce_vars: true,
+ toplevel: true,
+ }
+ input: {
+ var a = 0;
+ (function({
+ [a++]: b
+ }) {})(0);
+ console.log(a);
+ }
+ expect: {
+ var a = 0;
+ (function({
+ [a++]: b
+ }) {})(0);
+ console.log(1);
+ }
+ expect_stdout: "1"
+ node_version: ">=6"
+}
+
+funarg_reduce_vars_4: {
+ options = {
+ evaluate: true,
+ reduce_vars: true,
+ }
+ input: {
+ try {
+ (function f({
+ [a = 1]: a,
+ }) {})(2);
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect: {
+ try {
+ (function f({
+ [a = 1]: a,
+ }) {})(2);
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+funarg_computed_key_scope_1: {
+ rename = true
+ input: {
+ var b = 0;
+ function f({
+ [b]: a
+ }) {
+ var b = 42;
+ console.log(a, b);
+ }
+ f([ "PASS" ]);
+ }
+ expect: {
+ var b = 0;
+ function f({
+ [b]: a
+ }) {
+ var c = 42;
+ console.log(a, c);
+ }
+ f([ "PASS" ]);
+ }
+ expect_stdout: "PASS 42"
+ node_version: ">=6"
+}
+
+funarg_computed_key_scope_2: {
+ options = {
+ reduce_funcs: true,
+ reduce_vars: true,
+ unused: true,
+ }
+ input: {
+ (function({
+ [function() {
+ console.log(typeof f);
+ }()]: a
+ }) {
+ function f() {}
+ })(0);
+ }
+ expect: {
+ (function({
+ [function() {
+ console.log(typeof f);
+ }()]: a
+ }) {})(0);
+ }
+ expect_stdout: "undefined"
+ node_version: ">=6"
+}
+
+funarg_computed_key_scope_3: {
+ options = {
+ reduce_funcs: true,
+ reduce_vars: true,
+ unused: true,
+ }
+ input: {
+ (function({
+ [function() {
+ (function({
+ [function() {
+ console.log(typeof f, typeof g, typeof h);
+ }()]: a
+ }) {
+ function f() {}
+ })(1);
+ function g() {}
+ }()]: b
+ }) {
+ function h() {}
+ })(2);
+ }
+ expect: {
+ (function({
+ [function() {
+ (function({
+ [function() {
+ console.log(typeof f, typeof function() {}, typeof h);
+ }()]: a
+ }) {})(1);
+ }()]: b
+ }) {})(2);
+ }
+ expect_stdout: "undefined function undefined"
+ node_version: ">=6"
+}
+
+funarg_inline: {
+ options = {
+ inline: true,
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ try {
+ function f({}) {
+ return 42;
+ }
+ var a = f();
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect: {
+ try {
+ (function({}) {})();
+ } catch (e) {
+ console.log("PASS");
+ }
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+simple_const: {
+ options = {
+ evaluate: true,
+ reduce_vars: true,
+ toplevel: true,
+ unsafe: true,
+ unused: true,
+ varify: true,
+ }
+ input: {
+ const [ a ] = [ "PASS" ];
+ console.log(a);
+ }
+ expect: {
+ console.log("PASS");
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+simple_let: {
+ options = {
+ evaluate: true,
+ reduce_vars: true,
+ toplevel: true,
+ unsafe: true,
+ unused: true,
+ varify: true,
+ }
+ input: {
+ let [ a ] = [ "PASS" ];
+ console.log(a);
+ }
+ expect: {
+ console.log("PASS");
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+simple_var: {
+ options = {
+ evaluate: true,
+ reduce_vars: true,
+ toplevel: true,
+ unsafe: true,
+ unused: true,
+ }
+ input: {
+ var [ a ] = [ "PASS" ];
+ console.log(a);
+ }
+ expect: {
+ console.log("PASS");
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+collapse_vars_1: {
+ options = {
+ collapse_vars: true,
+ }
+ input: {
+ var a = "PASS";
+ var {
+ [a.p]: a,
+ } = !console.log(a);
+ }
+ expect: {
+ var a = "PASS";
+ var {
+ [a.p]: a,
+ } = !console.log(a);
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+collapse_vars_2: {
+ options = {
+ collapse_vars: true,
+ }
+ input: {
+ console.log(function() {
+ [] = [];
+ return "PASS";
+ }());
+ }
+ expect: {
+ console.log(function() {
+ [] = [];
+ return "PASS";
+ }());
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+collapse_vars_3: {
+ options = {
+ collapse_vars: true,
+ }
+ input: {
+ console.log(function(a) {
+ [ a ] = (a = "FAIL", [ "PASS" ]);
+ return a;
+ }());
+ }
+ expect: {
+ console.log(function(a) {
+ [ a ] = (a = "FAIL", [ "PASS" ]);
+ return a;
+ }());
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+collapse_vars_4: {
+ options = {
+ collapse_vars: true,
+ }
+ input: {
+ var a;
+ try {
+ a = 42;
+ [ 42..p ] = null;
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect: {
+ var a;
+ try {
+ a = 42;
+ [ 42..p ] = null;
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect_stdout: "42"
+ node_version: ">=6"
+}
+
+collapse_vars_5: {
+ options = {
+ collapse_vars: true,
+ }
+ input: {
+ var a;
+ try {
+ [] = (a = 42, null);
+ a = 42;
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect: {
+ var a;
+ try {
+ [] = (a = 42, null);
+ a = 42;
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect_stdout: "42"
+ node_version: ">=6"
+}
+
+collapse_vars_6: {
+ options = {
+ collapse_vars: true,
+ }
+ input: {
+ var a;
+ try {
+ var [] = (a = 42, null);
+ a = 42;
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect: {
+ var a;
+ try {
+ var [] = (a = 42, null);
+ a = 42;
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect_stdout: "42"
+ node_version: ">=6"
+}
+
+collapse_vars_7: {
+ options = {
+ collapse_vars: true,
+ }
+ input: {
+ var a = "FAIL";
+ try {
+ (function() {
+ [] = (a = "PASS", null);
+ return "PASS";
+ })();
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect: {
+ var a = "FAIL";
+ try {
+ (function() {
+ [] = (a = "PASS", null);
+ return "PASS";
+ })();
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+collapse_vars_8: {
+ options = {
+ collapse_vars: true,
+ }
+ input: {
+ var a = "FAIL";
+ try {
+ (function() {
+ var {} = (a = "PASS", null);
+ return "PASS";
+ })();
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect: {
+ var a = "FAIL";
+ try {
+ (function() {
+ var {} = (a = "PASS", null);
+ return "PASS";
+ })();
+ } catch (e) {
+ console.log(a);
+ }
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+conditionals: {
+ options = {
+ conditionals: true,
+ }
+ input: {
+ if (console.log("PASS")) {
+ var [] = 0;
+ }
+ }
+ expect: {
+ console.log("PASS") && ([] = 0);
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+dead_code: {
+ options = {
+ conditionals: true,
+ dead_code: true,
+ evaluate: true,
+ }
+ input: {
+ if (0) {
+ let [] = 42;
+ var { a, b: [ c ] } = null;
+ }
+ console.log("PASS");
+ }
+ expect: {
+ 0;
+ var a, c;
+ console.log("PASS");
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+drop_unused_1: {
+ options = {
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ switch (0) {
+ case console.log(a, a):
+ try {
+ throw 42;
+ } catch (a) {
+ var [ a ] = [];
+ }
+ }
+ }
+ expect: {
+ switch (0) {
+ case console.log(a, a):
+ try {
+ throw 42;
+ } catch (a) {
+ var [ a ] = [];
+ }
+ }
+ }
+ expect_stdout: "undefined undefined"
+ node_version: ">=6"
+}
+
+drop_unused_2: {
+ options = {
+ merge_vars: true,
+ pure_getters: "strict",
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ function f(a) {
+ var b = [ console.log("PASS"), a ], {
+ p: a,
+ } = 0;
+ }
+ f();
+ }
+ expect:{
+ (function(a) {
+ console.log("PASS");
+ })();
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+join_vars: {
+ options = {
+ conditionals: true,
+ join_vars: true,
+ }
+ input: {
+ const [ a ] = [ "PASS" ];
+ a,
+ console.log(a);
+ }
+ expect: {
+ const [ a ] = [ "PASS" ];
+ a,
+ console.log(a);
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+keep_fargs: {
+ options = {
+ keep_fargs: "strict",
+ unused: true,
+ }
+ input: {
+ console.log(function f(a) {
+ var {} = a;
+ }(0));
+ }
+ expect: {
+ console.log(function(a) {
+ var {} = a;
+ }(0));
+ }
+ expect_stdout: "undefined"
+ node_version: ">=6"
+}
+
+reduce_vars_1: {
+ options = {
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var a;
+ console.log("PASS") && ([ a ] = 0);
+ }
+ expect: {
+ var a;
+ console.log("PASS") && ([ a ] = 0);
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+reduce_vars_2: {
+ options = {
+ evaluate: true,
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var a = "FAIL", b = 42;
+ ({
+ [console.log(a, b)]: b.p
+ } = a = "PASS");
+ }
+ expect: {
+ ({
+ [console.log("PASS", 42)]: 42..p
+ } = "PASS");
+ }
+ expect_stdout: "PASS 42"
+ node_version: ">=6"
+}
+
+computed_key_evaluate: {
+ options = {
+ evaluate: true,
+ reduce_vars: true,
+ toplevel: true,
+ }
+ input: {
+ var a = 0, {
+ [++a]: b,
+ } = [ "FAIL 1", a ? "FAIL 2" : "PASS" ];
+ console.log(b);
+ }
+ expect: {
+ var a = 0, {
+ [1]: b,
+ } = [ "FAIL 1", 0 ? "FAIL 2" : "PASS" ];
+ console.log(b);
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+computed_key_unused: {
+ options = {
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var {
+ [console.log("bar")]: a,
+ [console.log("baz")]: { b },
+ [console.log("moo")]: [
+ c,
+ {
+ [console.log("moz")]: d,
+ e,
+ },
+ ],
+ } = {
+ [console.log("foo")]: [ null, 42 ],
+ };
+ }
+ expect: {
+ var {
+ [console.log("bar")]: a,
+ [console.log("baz")]: {},
+ [console.log("moo")]: [
+ ,
+ {
+ [console.log("moz")]: d,
+ },
+ ],
+ } = {
+ [console.log("foo")]: [ null, 42 ],
+ };
+ }
+ expect_stdout: [
+ "foo",
+ "bar",
+ "baz",
+ "moo",
+ "moz",
+ ]
+ node_version: ">=6"
+}
+
+for_in_1: {
+ options = {
+ loops: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ for (var { a } in console.log("PASS"));
+ }
+ expect: {
+ for (var { a } in console.log("PASS"));
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+for_in_2: {
+ options = {
+ join_vars: true,
+ }
+ input: {
+ var a;
+ for (var { b } in console.log("PASS"));
+ }
+ expect: {
+ var a, b;
+ for ({ b } in console.log("PASS"));
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+for_in_3: {
+ options = {
+ merge_vars: true,
+ reduce_vars: true,
+ }
+ input: {
+ for (var { length: a } in [ 42 ])
+ console.log(a);
+ }
+ expect: {
+ for (var { length: a } in [ 42 ])
+ console.log(a);
+ }
+ expect_stdout: "1"
+ node_version: ">=6"
+}
+
+fn_name_evaluate: {
+ options = {
+ evaluate: true,
+ objects: true,
+ reduce_vars: true,
+ typeofs: true,
+ }
+ input: {
+ console.log(function f({
+ [typeof f]: a,
+ }) {
+ var f;
+ return a;
+ }({
+ function: "PASS",
+ undefined: "FAIL",
+ }));
+ }
+ expect: {
+ console.log(function f({
+ function: a,
+ }) {
+ var f;
+ return a;
+ }({
+ function: "PASS",
+ undefined: "FAIL",
+ }));
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
+
+fn_name_unused: {
+ options = {
+ unused: true,
+ }
+ input: {
+ console.log(function f({
+ [typeof f]: a,
+ }) {
+ var f;
+ return a;
+ }({
+ function: "PASS",
+ undefined: "FAIL",
+ }));
+ }
+ expect: {
+ console.log(function f({
+ [typeof f]: a,
+ }) {
+ var f;
+ return a;
+ }({
+ function: "PASS",
+ undefined: "FAIL",
+ }));
+ }
+ expect_stdout: "PASS"
+ node_version: ">=6"
+}
diff --git a/test/reduce.js b/test/reduce.js
index ab742af4..f126573e 100644
--- a/test/reduce.js
+++ b/test/reduce.js
@@ -95,6 +95,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
// quick ignores
if (node instanceof U.AST_Accessor) return;
+ if (node instanceof U.AST_Destructured) return;
if (node instanceof U.AST_Directive) return;
if (!in_list && node instanceof U.AST_EmptyStatement) return;
if (node instanceof U.AST_Label) return;
@@ -114,6 +115,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
// ignore lvalues
if (parent instanceof U.AST_Assign && parent.left === node) return;
+ if (parent instanceof U.AST_Destructured) return;
+ if (parent instanceof U.AST_DestructuredKeyVal && parent.value === node) return;
if (parent instanceof U.AST_Unary && parent.expression === node) switch (parent.operator) {
case "++":
case "--":
@@ -250,13 +253,23 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
}
else if (node instanceof U.AST_ForIn) {
- var expr = [
- node.init,
- node.object,
- node.body,
- ][ (node.start._permute * steps | 0) % 3 ];
+ var expr;
+ switch ((node.start._permute * steps | 0) % 3) {
+ case 0:
+ if (!(node.init instanceof U.AST_Definitions
+ && node.init.definitions[0].name instanceof U.AST_Destructured)) {
+ expr = node.init;
+ }
+ break;
+ case 1:
+ expr = node.object;
+ break;
+ case 2:
+ if (!has_loopcontrol(node.body, node, parent)) expr = node.body;
+ break;
+ }
node.start._permute += step;
- if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
+ if (expr) {
CHANGED = true;
return to_statement(expr);
}
@@ -389,6 +402,13 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
CHANGED = true;
return List.skip;
}
+
+ // skip element/property from (destructured) array/object
+ if (parent instanceof U.AST_Array || parent instanceof U.AST_Destructured || parent instanceof AST_Object) {
+ node.start._permute++;
+ CHANGED = true;
+ return List.skip;
+ }
}
// replace this node
diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js
index 82fdc37a..1c4af3c3 100644
--- a/test/ufuzz/index.js
+++ b/test/ufuzz/index.js
@@ -377,6 +377,118 @@ function createArgs(recurmax, stmtDepth, canThrow) {
return args.join(", ");
}
+function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames, maybe, dontStore) {
+ var avoid = [];
+ var len = unique_vars.length;
+ var pairs = createPairs(recurmax);
+ unique_vars.length = len;
+ return pairs;
+
+ function createAssignmentValue(recurmax) {
+ var current = VAR_NAMES;
+ VAR_NAMES = (varNames || VAR_NAMES).slice();
+ var value = varNames && rng(2) ? createValue() : createExpression(recurmax, noComma, stmtDepth, canThrow);
+ VAR_NAMES = current;
+ return value;
+ }
+
+ function createKey(recurmax, keys) {
+ var save = VAR_NAMES;
+ VAR_NAMES = VAR_NAMES.filter(function(name) {
+ return avoid.indexOf(name) < 0;
+ });
+ var len = VAR_NAMES.length;
+ var key;
+ do {
+ key = createObjectKey(recurmax, stmtDepth, canThrow);
+ } while (keys.indexOf(key) >= 0);
+ VAR_NAMES = save.concat(VAR_NAMES.slice(len));
+ return key;
+ }
+
+ function createPairs(recurmax) {
+ var names = [], values = [];
+ var m = rng(4), n = rng(4);
+ if (!varNames) m = Math.max(m, n, 1);
+ for (var i = Math.max(m, n); --i >= 0;) {
+ if (i < m && i < n) {
+ createDestructured(recurmax, names, values);
+ continue;
+ }
+ if (i < m) {
+ unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
+ var name = createVarName(maybe, dontStore);
+ unique_vars.length -= 6;
+ avoid.push(name);
+ unique_vars.push(name);
+ names.unshift(name);
+ }
+ if (i < n) {
+ values.unshift(createAssignmentValue(recurmax));
+ }
+ }
+ return {
+ names: names,
+ values: values,
+ };
+ }
+
+ function createDestructured(recurmax, names, values) {
+ switch (rng(20)) {
+ case 0:
+ if (--recurmax < 0) {
+ names.unshift("[]");
+ values.unshift('""');
+ } else {
+ var pairs = createPairs(recurmax);
+ while (!rng(10)) {
+ var index = rng(pairs.names.length + 1);
+ pairs.names.splice(index, 0, "");
+ pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : "");
+ }
+ names.unshift("[ " + pairs.names.join(", ") + " ]");
+ values.unshift("[ " + pairs.values.join(", ") + " ]");
+ }
+ break;
+ case 1:
+ if (--recurmax < 0) {
+ names.unshift("{}");
+ values.unshift('""');
+ } else {
+ var pairs = createPairs(recurmax);
+ var keys = [];
+ pairs.names.forEach(function(name, index) {
+ if (/^[[{]/.test(name)) {
+ var key;
+ do {
+ key = KEYS[rng(KEYS.length)];
+ } while (keys.indexOf(key) >= 0);
+ keys[index] = key;
+ }
+ });
+ names.unshift("{ " + pairs.names.map(function(name, index) {
+ var key = index in keys ? keys[index] : rng(10) && createKey(recurmax, keys);
+ return key ? key + ": " + name : name;
+ }).join(", ") + " }");
+ values.unshift("{ " + pairs.values.map(function(value, index) {
+ var key = index in keys ? keys[index] : createKey(recurmax, keys);
+ return key + ": " + value;
+ }).join(", ") + " }");
+ }
+ break;
+ default:
+ unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
+ var name = createVarName(maybe, dontStore);
+ unique_vars.length -= 6;
+ avoid.push(name);
+ unique_vars.push(name);
+ names.unshift(name);
+ values.unshift(createAssignmentValue(recurmax));
+ break;
+ }
+ }
+}
+
function filterDirective(s) {
if (!generate_directive && !s[1] && /\("/.test(s[2])) s[2] = ";" + s[2];
return s;
@@ -415,11 +527,37 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
return names.indexOf(name) < 0;
});
var len = VAR_NAMES.length;
- var s = type + " " + names.map(function(name) {
- var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
- VAR_NAMES.push(name);
- return name + " = " + value;
- }).join(", ") + ";";
+ var s = type + " ";
+ switch (rng(10)) {
+ case 0:
+ while (!rng(10)) names.splice(rng(names.length + 1), 0, "");
+ s += "[ " + names.join(", ") + " ] = [ " + names.map(function() {
+ return rng(10) ? createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) : "";
+ }).join(", ") + " ];";
+ break;
+ case 1:
+ var keys = [];
+ s += "{ " + names.map(function(name, i) {
+ var key = createObjectKey(recurmax, stmtDepth, canThrow);
+ if (!/\[/.test(key)) keys[i] = key;
+ return key + ": " + name;
+ }).join(", ") + "} = { " + names.map(function(name, i) {
+ var key = i in keys ? keys[i] : createObjectKey(recurmax, stmtDepth, canThrow);
+ return key + ": " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
+ }).join(", ") + "};";
+ break;
+ default:
+ s += names.map(function(name, i) {
+ if (type == "let" && !rng(10)) {
+ VAR_NAMES.push(name);
+ return name;
+ }
+ var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
+ VAR_NAMES.push(name);
+ return name + " = " + value;
+ }).join(", ") + ";";
+ break;
+ }
VAR_NAMES = save.concat(VAR_NAMES.slice(len));
return s;
}
@@ -429,9 +567,9 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
if (--recurmax < 0) { return ";"; }
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
var s = [];
- var name;
+ var name, args;
+ var varNames = VAR_NAMES.slice();
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
- var namesLenBefore = VAR_NAMES.length;
if (allowDefun || rng(5) > 0) {
name = "f" + funcs++;
} else {
@@ -439,7 +577,16 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
name = createVarName(MANDATORY, !allowDefun);
unique_vars.length -= 3;
}
- s.push("function " + name + "(" + createParams() + "){", strictMode());
+ var params;
+ if ((!allowDefun || !(name in called)) && rng(2)) {
+ called[name] = false;
+ var pairs = createAssignmentPairs(recurmax, COMMA_OK, stmtDepth, canThrow, varNames, MANDATORY);
+ params = pairs.names.join(", ");
+ args = pairs.values.join(", ");
+ } else {
+ params = createParams();
+ }
+ s.push("function " + name + "(" + params + "){", strictMode());
s.push(defns());
if (rng(5) === 0) {
// functions with functions. lower the recursion to prevent a mess.
@@ -450,17 +597,16 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
}
s.push("}", "");
s = filterDirective(s).join("\n");
-
- VAR_NAMES.length = namesLenBefore;
});
+ VAR_NAMES = varNames;
if (!allowDefun) {
// avoid "function statements" (decl inside statements)
s = "var " + createVarName(MANDATORY) + " = " + s;
- s += "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
- } else if (!(name in called) || rng(3) > 0) {
+ s += "(" + (args || createArgs(recurmax, stmtDepth, canThrow)) + ")";
+ } else if (!(name in called) || args || rng(3)) {
s += "var " + createVarName(MANDATORY) + " = " + name;
- s += "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
+ s += "(" + (args || createArgs(recurmax, stmtDepth, canThrow)) + ")";
}
return s + ";";
@@ -561,8 +707,9 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
return [
"{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ",
label.target + " for (",
- /^key/.test(key) ? "var " : "",
- key + " in expr" + loop + ") {",
+ !/^key/.test(key) ? rng(10) ? "" : "var " : rng(10) ? "var " : rng(2) ? "let " : "const ",
+ rng(20) ? key : "{ length: " + key + " }",
+ " in expr" + loop + ") {",
rng(5) > 1 ? "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[" + key + "]; " : "",
createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
"}}",
@@ -576,7 +723,12 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
// note: default does not _need_ to be last
return "switch (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") { " + createSwitchParts(recurmax, 4, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}";
case STMT_VAR:
- switch (rng(3)) {
+ if (!rng(20)) {
+ var pairs = createAssignmentPairs(recurmax, NO_COMMA, stmtDepth, canThrow, null, MANDATORY);
+ return "var " + pairs.names.map(function(name, index) {
+ return index in pairs.values ? name + " = " + pairs.values[index] : name;
+ }).join(", ") + ";";
+ } else switch (rng(3)) {
case 0:
unique_vars.push("c");
var name = createVarName(MANDATORY);
@@ -719,7 +871,28 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++:
return getVarName();
case p++:
- return getVarName(NO_CONST) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
+ switch (rng(20)) {
+ case 0:
+ return [
+ "[ ",
+ new Array(rng(3)).join(","),
+ getVarName(NO_CONST),
+ new Array(rng(3)).join(","),
+ " ] = ",
+ createArrayLiteral(recurmax, stmtDepth, canThrow),
+ ].join("");
+ case 1:
+ return [
+ "{ ",
+ rng(2) ? "" : createObjectKey(recurmax, stmtDepth, canThrow) + ": ",
+ getVarName(NO_CONST),
+ " } = ",
+ createExpression(recurmax, COMMA_OK, stmtDepth, canThrow),
+ " || {}",
+ ].join("");
+ default:
+ return getVarName(NO_CONST) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
+ }
case p++:
return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
@@ -864,7 +1037,10 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++:
case p++:
case p++:
- var name = rng(3) == 0 ? getVarName() : "f" + rng(funcs + 2);
+ var name;
+ do {
+ name = rng(3) == 0 ? getVarName() : "f" + rng(funcs + 2);
+ } while (name in called && !called[name]);
called[name] = true;
return "typeof " + name + ' == "function" && --_calls_ >= 0 && ' + name + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
}
@@ -1002,13 +1178,77 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
case 3:
assignee = getVarName();
- expr = "(" + assignee + "[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow)
- + "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
+ switch (rng(20)) {
+ case 0:
+ expr = [
+ "([ ",
+ assignee,
+ "[", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), "]",
+ " ] = [ ",
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+ " ])",
+ ].join("");
+ break;
+ case 1:
+ var key1 = createObjectKey(recurmax, stmtDepth, canThrow);
+ var key2 = /^\[/.test(key1) ? createObjectKey(recurmax, stmtDepth, canThrow) : key1;
+ expr = [
+ "({ ",
+ key1, ": ", assignee,
+ "[", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), "]",
+ " } = { ",
+ key2, ": ", _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+ " })",
+ ].join("");
+ break;
+ default:
+ expr = [
+ "(",
+ assignee,
+ "[", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), "]",
+ createAssignment(),
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+ ")",
+ ].join("");
+ break;
+ }
return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")";
case 4:
assignee = getVarName();
- expr = "(" + assignee + "." + getDotKey(true) + createAssignment()
- + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
+ switch (rng(20)) {
+ case 0:
+ expr = [
+ "([ ",
+ assignee,
+ ".", getDotKey(true),
+ " ] = [ ",
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+ " ])",
+ ].join("");
+ break;
+ case 1:
+ var key1 = createObjectKey(recurmax, stmtDepth, canThrow);
+ var key2 = /^\[/.test(key1) ? createObjectKey(recurmax, stmtDepth, canThrow) : key1;
+ expr = [
+ "({ ",
+ key1, ": ", assignee,
+ ".", getDotKey(true),
+ " } = { ",
+ key2, ": ", _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+ " })",
+ ].join("");
+ break;
+ default:
+ expr = [
+ "(",
+ assignee,
+ ".", getDotKey(true),
+ createAssignment(),
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
+ ")",
+ ].join("");
+ break;
+ }
return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")";
default:
return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
@@ -1351,7 +1591,14 @@ function patch_try_catch(orig, toplevel) {
}
}
-var minify_options = require("./options.json").map(JSON.stringify);
+var minify_options = require("./options.json");
+if (typeof sandbox.run_code("console.log([ 1 ], {} = 2);") != "string") {
+ minify_options.forEach(function(o) {
+ if (!("output" in o)) o.output = {};
+ o.output.v8 = true;
+ });
+}
+minify_options = minify_options.map(JSON.stringify);
var original_code, original_result, errored;
var uglify_code, uglify_result, ok;
for (var round = 1; round <= num_iterations; round++) {