From: Alex Lam S.L Date: Tue, 17 Nov 2020 00:01:24 +0000 (+0000) Subject: support destructured literals (#4278) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=e5f80afc53e7df3b0b57931a03dc3481ddd9c30e;p=UglifyJS.git support destructured literals (#4278) --- 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++) {