diff --git a/lib/ast.js b/lib/ast.js index fe56bed5..2c73b6d3 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -665,9 +665,12 @@ var AST_Const = DEFNODE("Const", null, { var AST_VarDef = DEFNODE("VarDef", "name value", { $documentation: "A variable declaration; only appears in a AST_Definitions node", $propdoc: { - name: "[AST_SymbolVar|AST_SymbolConst] name of the variable", + name: "[AST_SymbolVar|AST_SymbolConst|AST_Destructuring] name of the variable", value: "[AST_Node?] initializer, or null of there's no initializer" }, + is_destructuring: function() { + return this.name instanceof AST_Destructuring; + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.name._walk(visitor); diff --git a/lib/compress.js b/lib/compress.js index cbd1caee..51fd18dc 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1056,6 +1056,7 @@ merge(Compressor.prototype, { } if (node instanceof AST_Definitions && scope === self) { node.definitions.forEach(function(def){ + if (def.is_destructuring()) return; /* Destructurings are type assertions! */ if (def.value) { initializations.add(def.name.name, def.value); if (def.value.has_side_effects(compressor)) { @@ -1142,6 +1143,7 @@ merge(Compressor.prototype, { } if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { var def = node.definitions.filter(function(def){ + if (def.is_destructuring()) return true; if (member(def.name.definition(), in_use)) return true; var w = { name : def.name.name, @@ -1262,6 +1264,7 @@ merge(Compressor.prototype, { } if (node instanceof AST_Var && hoist_vars) { node.definitions.forEach(function(def){ + if (def.is_destructuring()) { return; } vars.set(def.name.name, def); ++vars_found; }); @@ -1702,13 +1705,23 @@ merge(Compressor.prototype, { AST_Definitions.DEFMETHOD("to_assignments", function(){ var assignments = this.definitions.reduce(function(a, def){ - if (def.value) { + if (def.value && !def.is_destructuring()) { var name = make_node(AST_SymbolRef, def.name, def.name); a.push(make_node(AST_Assign, def, { operator : "=", left : name, right : def.value })); + } else if (def.value) { + // Because it's a destructuring, do not turn into an assignment. + var varDef = make_node(AST_VarDef, def, { + name: def.name, + value: def.value + }); + var var_ = make_node(AST_Var, def, { + definitions: [ varDef ] + }); + a.push(var_); } return a; }, []); diff --git a/lib/parse.js b/lib/parse.js index 2351dfc1..a0717c1f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1180,13 +1180,24 @@ function parse($TEXT, options) { function vardefs(no_in, in_const) { var a = []; + var def; for (;;) { - a.push(new AST_VarDef({ - start : S.token, - name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar), - value : is("operator", "=") ? (next(), expression(false, no_in)) : null, - end : prev() - })); + if (is("punc", "{") || is("punc", "[")) { + def = new AST_VarDef({ + start: S.token, + name: destructuring_(), + value: (expect_token("operator", "="), expression(false, no_in)), + end: prev() + }); + } else { + def = new AST_VarDef({ + start : S.token, + name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar), + value : is("operator", "=") ? (next(), expression(false, no_in)) : null, + end : prev() + }) + } + a.push(def); if (!is("punc", ",")) break; next(); @@ -1194,6 +1205,16 @@ function parse($TEXT, options) { return a; }; + var destructuring_ = embed_tokens(function () { + var is_array = is("punc", "["); + var closing = is_array ? ']' : '}'; + next() + return new AST_Destructuring({ + names: expr_list(closing), + is_array: is_array + }) + }); + var var_ = function(no_in) { return new AST_Var({ start : prev(), diff --git a/test/compress/destructuring.js b/test/compress/destructuring.js new file mode 100644 index 00000000..58a77cd4 --- /dev/null +++ b/test/compress/destructuring.js @@ -0,0 +1,19 @@ + +destructuring_arrays: { + input: { + var [aa, bb] = cc; + } + expect: { + var[aa,bb]=cc; + } +} + +destructuring_objects: { + input: { + var {aa, bb} = {aa:1, bb:2}; + } + expect: { + var{aa,bb}={aa:1,bb:2}; + } +} + diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index c1cf5c3c..9bee9a2b 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -164,6 +164,21 @@ used_var_in_catch: { } } +unused_keep_harmony_destructuring: { + options = { unused: true }; + input: { + function foo() { + var {x, y} = foo; + var a = foo; + } + } + expect: { + function foo() { + var {x, y} = foo; + } + } +} + keep_fnames: { options = { unused: true, keep_fnames: true, unsafe: true }; input: { diff --git a/test/compress/hoist.js b/test/compress/hoist.js index 2b359fc5..68f5a8c3 100644 --- a/test/compress/hoist.js +++ b/test/compress/hoist.js @@ -65,3 +65,21 @@ hoist_no_destructurings: { } } } + +dont_hoist_var_destructurings: { + options = { + hoist_vars: true, + hoist_funs: true + } + input: { + function x() { + // If foo is null or undefined, this should be an exception + var {x,y} = foo; + } + } + expect: { + function x() { + var {x,y} = foo; + } + } +} diff --git a/test/parser.js b/test/parser.js index e661907a..85c2e23e 100644 --- a/test/parser.js +++ b/test/parser.js @@ -94,6 +94,14 @@ module.exports = function () { UglifyJS.parse('(function [a] { })'); }); + // Destructuring variable declaration + + var decls = UglifyJS.parse('var {a,b} = foo, { c, d } = bar'); + + ok.equal(decls.body[0].TYPE, 'Var'); + ok.equal(decls.body[0].definitions.length, 2); + ok.equal(decls.body[0].definitions[0].name.TYPE, 'Destructuring'); + ok.throws(function () { // Note: this *is* a valid destructuring, but before we implement // destructuring (right now it's only destructuring *arguments*),