diff --git a/lib/ast.js b/lib/ast.js index 5c78a99f..0569133e 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -387,6 +387,11 @@ var AST_ArrowParametersOrSeq = DEFNODE("ArrowParametersOrSeq", "expressions", { start: ex.start, end: ex.end }); + } else if (ex instanceof AST_Destructuring) { + if (ex.names.length == 0) + croak("Invalid destructuring function parameter", ex.start.line, ex.start.col); + ex.names = ex.names.map(to_fun_args); + return ex; } else if (ex instanceof AST_SymbolRef) { return new AST_SymbolFunarg({ name: ex.name, diff --git a/lib/parse.js b/lib/parse.js index 96d3b4c3..913da4bc 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -989,35 +989,17 @@ function parse($TEXT, options) { var arrow_function = function(args) { expect_token("arrow", "=>"); - if (args instanceof AST_SymbolRef) { - args = [args]; - } else if (args instanceof AST_Seq) { - args = args.to_array(); - } else if (args instanceof AST_Node) { - croak("Invalid syntax", args.start.line, args.start.col); - } + var argnames = args.as_params(croak); - for (var i = 0; i < args.length; i++) { - if (!(args[i] instanceof AST_SymbolRef)) { - croak("Invalid parameter for an arrow function", args[i].start.line, args[i].start.col); - } - - args[i] = new AST_SymbolFunarg({ - name: args[i].name, - start: args[i].start, - end: args[i].end - }) - } + var body = is("punc", "{") ? + _function_body(true) : + _function_body(false); return new AST_Arrow({ - argnames: args, - body: (function(){ - if (is("punc", "{")) { - return _function_body(); - } else { - return expression(true); - } - })() + start : args.start, + end : body.end, + argnames : argnames, + body : body }); }; @@ -1030,7 +1012,7 @@ function parse($TEXT, options) { unexpected(); var args = params_or_seq_().as_params(croak); - var body = _function_body(); + var body = _function_body(true); return new ctor({ start : args.start, end : body.end, @@ -1060,14 +1042,18 @@ function parse($TEXT, options) { }); } - function _function_body() { + function _function_body(block) { var loop = S.in_loop; var labels = S.labels; ++S.in_function; - S.in_directives = true; + if (block) + S.in_directives = true; S.in_loop = 0; S.labels = []; - var a = block_(); + if (block) + var a = block_(); + else + var a = expression(false); --S.in_function; S.in_loop = loop; S.labels = labels; @@ -1263,16 +1249,17 @@ function parse($TEXT, options) { if (is("punc")) { switch (start.value) { case "(": - next(); - var ex = expression(true); + var ex = params_or_seq_(); ex.start = start; ex.end = S.token; - expect(")"); - return subscripts(ex, allow_calls); + if (is("arrow", "=>")) { + return arrow_function(ex); + } + return subscripts(ex.as_expr(croak), allow_calls); case "[": return subscripts(array_(), allow_calls); case "{": - return subscripts(object_(), allow_calls); + return subscripts(object_or_object_destructuring_(), allow_calls); } unexpected(); } @@ -1311,68 +1298,101 @@ function parse($TEXT, options) { }); }); - var object_ = embed_tokens(function() { + var object_or_object_destructuring_ = embed_tokens(function() { var start = S.token; expect("{"); - var first = true, a = []; - while (!is("punc", "}")) { - if (first) first = false; else expect(","); - if (!options.strict && is("punc", "}")) - // allow trailing comma - break; - var start = S.token; - var type = start.type; - var name = as_property_name(); - if (type == "name" && !is("punc", ":")) { - if (name == "get") { - a.push(new AST_ObjectGetter({ - start : start, - key : as_atom_node(), - value : function_(AST_Accessor), - end : prev() - })); - continue; + function try_an_object() { + var first = true, a = []; + while (!is("punc", "}")) { + if (first) first = false; else expect(","); + if (!options.strict && is("punc", "}")) + // allow trailing comma + break; + var start = S.token; + var type = start.type; + var name = as_property_name(); + if (type == "name" && !is("punc", ":")) { + if (name == "get") { + a.push(new AST_ObjectGetter({ + start : start, + key : as_atom_node(), + value : function_(AST_Accessor), + end : prev() + })); + continue; + } + if (name == "set") { + a.push(new AST_ObjectSetter({ + start : start, + key : as_atom_node(), + value : function_(AST_Accessor), + end : prev() + })); + continue; + } } - if (name == "set") { - a.push(new AST_ObjectSetter({ - start : start, - key : as_atom_node(), - value : function_(AST_Accessor), - end : prev() - })); - continue; - } - } - - if (!is("punc", ":")) { - // It's one of those object destructurings, the value is its own name - if (!S.in_parameters) { - croak("Invalid syntax", S.token.line, S.token.col); - } - a.push(new AST_ObjectSymbol({ - start: start, - end: start, - symbol: new AST_SymbolRef({ + + if (!is("punc", ":")) { + // It's one of those object destructurings, the value is its own name + if (!S.in_parameters) { + croak("Invalid syntax", S.token.line, S.token.col); + } + a.push(new AST_ObjectSymbol({ start: start, end: start, - name: name - }) - })); - } else { - if (S.in_parameters) { - croak("Cannot destructure", S.token.line, S.token.col); + symbol: new AST_SymbolRef({ + start: start, + end: start, + name: name + }) + })); + } else { + if (S.in_parameters) { + croak("Cannot destructure", S.token.line, S.token.col); + } + expect(":"); + a.push(new AST_ObjectKeyVal({ + start : start, + key : name, + value : expression(false), + end : prev() + })); } - expect(":"); - a.push(new AST_ObjectKeyVal({ - start : start, - key : name, - value : expression(false), - end : prev() - })); } + next(); + return new AST_Object({ properties: a }) } - next(); - return new AST_Object({ properties: a }); + + var obj = try_an_object(); + if (obj instanceof AST_Object) { return obj; } + + if (!S.in_parameters) { + croak("Cannot destructure", S.token.line, S.token.col); + } + + var firstName = obj; + + var namesInDestructuring = []; + + namesInDestructuring.push( new AST_SymbolRef({ + start : prev(), + end : prev(), + name : firstName + })); + + while (!is("punc", "}")) { + expect(","); + namesInDestructuring.push(as_symbol(AST_SymbolRef)) + } + + expect('}'); + + return new AST_Destructuring({ + start : start, + end : S.token, + names : namesInDestructuring, + is_array : false + }) }); function as_property_name() { @@ -1530,16 +1550,13 @@ function parse($TEXT, options) { return (expr instanceof AST_PropAccess || expr instanceof AST_Symbol); }; + // In ES6, AssignmentExpression can also be an ArrowFunction var maybe_assign = function(no_in) { var start = S.token; - if (start.value == "(" && peek().value == ")") { - next(); // ( - next(); // ) - return arrow_function([]); - } + var left = maybe_conditional(no_in); + var val = S.token.value; - var left = maybe_conditional(no_in), val = S.token.value; if (is("operator") && ASSIGNMENT(val)) { if (is_assignable(left)) { next(); @@ -1553,15 +1570,20 @@ function parse($TEXT, options) { } croak("Invalid assignment"); } - if (is("arrow")) { - return arrow_function(left) - } return left; }; var expression = function(commas, no_in) { var start = S.token; var expr = maybe_assign(no_in); + if (expr instanceof AST_SymbolRef && is("arrow", "=>")) { + expr = new AST_ArrowParametersOrSeq({ + start: expr.start, + end: expr.end, + expressions: [expr] + }); + return arrow_function(expr); + } if (commas && is("punc", ",")) { next(); return new AST_Seq({ diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 3c07c49e..02d2b299 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -35,3 +35,65 @@ regression_arrow_functions_and_hoist: { } expect_exact: "a=>b;" } + +destructuring_arguments: { + input: { + (function ( a ) { }); + (function ( [ a ] ) { }); + (function ( [ a, b ] ) { }); + (function ( [ [ a ] ] ) { }); + (function ( [ [ a, b ] ] ) { }); + (function ( [ a, [ b ] ] ) { }); + (function ( [ [ b ], a ] ) { }); + + (function ( { a } ) { }); + (function ( { a, b } ) { }); + + (function ( [ { a } ] ) { }); + (function ( [ { a, b } ] ) { }); + (function ( [ a, { b } ] ) { }); + (function ( [ { b }, a ] ) { }); + + ( [ a ] ) => { }; + ( [ a, b ] ) => { }; + + ( { a } ) => { }; + ( { a, b, c, d, e } ) => { }; + + ( [ a ] ) => b; + ( [ a, b ] ) => c; + + ( { a } ) => b; + ( { a, b } ) => c; + } + expect: { + (function(a){}); + (function([a]){}); + (function([a,b]){}); + (function([[a]]){}); + (function([[a,b]]){}); + (function([a,[b]]){}); + (function([[b],a]){}); + + (function({a}){}); + (function({a,b}){}); + + (function([{a}]){}); + (function([{a,b}]){}); + (function([a,{b}]){}); + (function([{b},a]){}); + + ([a])=>{}; + ([a,b])=>{}; + + ({a})=>{}; + ({a,b,c,d,e})=>{}; + + ([a])=>b; + ([a,b])=>c; + + ({a})=>b; + ({a,b})=>c; + } +} + diff --git a/test/parser.js b/test/parser.js index b512f1ca..e661907a 100644 --- a/test/parser.js +++ b/test/parser.js @@ -21,6 +21,12 @@ module.exports = function () { ok.equal(destr_fun1.argnames.length, 1); ok.equal(destr_fun2.argnames.length, 1); + var destr_fun1 = UglifyJS.parse('({a, b}) => null').body[0].body; + var destr_fun2 = UglifyJS.parse('([a, [b]]) => null').body[0].body; + + ok.equal(destr_fun1.argnames.length, 1); + ok.equal(destr_fun2.argnames.length, 1); + var destruct1 = destr_fun1.argnames[0]; var destruct2 = destr_fun2.argnames[0];