- `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`.
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
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.
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.
_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");
},
}
}, 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;
},
_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);
_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);
_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");
});
},
_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");
});
},
$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);
},
});
+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: {
} 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);
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);
});
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;
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);
// 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);
} 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) {
}
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)) {
}
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) {
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++)) {
}
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) {
}
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;
}
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() {
name: sym,
value: arg
}));
+ if (sym instanceof AST_Destructured) continue;
if (sym.name in names) continue;
names[sym.name] = true;
if (!arg) {
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) {
}
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);
}
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) {
}
} 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);
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) {
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) {
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();
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);
});
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) {
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;
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);
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();
});
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;
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) {
}
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;
} 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) || [];
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) {
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);
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;
}
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;
}
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 = [];
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;
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;
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) {
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);
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();
|| 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:
} 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;
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);
}
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;
}));
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;
}
});
- 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;
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 = [];
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) {
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;
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;
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);
}
}
});
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) {
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
});
})
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--;
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 = [];
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);
}
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;
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())) {
}
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);
}
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;) {
semicolons : true,
shebang : true,
source_map : null,
+ v8 : false,
webkit : false,
width : 80,
wrap_iife : false,
}
}
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;
}
});
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;
}
});
},
parent : function(n) {
return stack[stack.length - 2 - (n || 0)];
- }
+ },
};
}
/* -----[ 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);
// 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;
}
// 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();
|| 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
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
}
});
- 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 ]----- */
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() {
}
}
- 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;
AST_Constant,
AST_Debugger,
AST_Definitions,
+ AST_Destructured,
AST_Finally,
AST_Jump,
AST_Lambda,
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);
});
})();
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();
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;
});
}
- 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({
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()
});
};
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(",");
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();
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;
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,
// 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;
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) {
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);
});
--- /dev/null
+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"
+}
// 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;
// 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 "--":
}
}
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);
}
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
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;
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;
}
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 {
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.
}
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 + ";";
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),
"}}",
// 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);
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++:
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) + ")";
}
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);
}
}
-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++) {