diff --git a/lib/compress.js b/lib/compress.js index db26dfd7..275503c0 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1731,6 +1731,7 @@ merge(Compressor.prototype, { var val = {}; for (var i = 0, len = this.properties.length; i < len; i++) { var prop = this.properties[i]; + if (prop instanceof AST_Expansion) return this; var key = prop.key; if (key instanceof AST_Symbol) { key = key.name; diff --git a/lib/parse.js b/lib/parse.js index bb9bdf1f..0cd5671f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1522,7 +1522,7 @@ function parse($TEXT, options) { } if (is_expand) { if (!is("punc", "]")) { - unexpected(); // Must be last element + croak("Rest element must be last element"); } elements[elements.length - 1] = new AST_Expansion({ start: expand_token, @@ -1547,18 +1547,33 @@ function parse($TEXT, options) { } else { expect(","); } + if (is("expand", "...")) { + is_expand = true; + expand_token = S.token; + used_parameters.mark_spread(S.token); + next(); + } if (is("name") && (is_token(peek(), "punc") || is_token(peek(), "operator")) && [",", "}", "="].indexOf(peek().value) !== -1) { used_parameters.add_parameter(S.token); - elements.push(new AST_ObjectKeyVal({ - start: prev(), - key: S.token.value, - value: new symbol_type({ - start: S.token, - name: S.token.value, - end: S.token - }), - end: prev() - })); + var value = new symbol_type({ + start: S.token, + name: S.token.value, + end: S.token, + }); + if (is_expand) { + elements.push(new AST_Expansion({ + start: expand_token, + expression: value, + end: value.end, + })); + } else { + elements.push(new AST_ObjectKeyVal({ + start: prev(), + key: S.token.value, + value: value, + end: value.end, + })); + } next(); } else if (is("punc", "}")) { continue; // Allow trailing hole @@ -1589,7 +1604,12 @@ function parse($TEXT, options) { })); } } - if (is("operator", "=")) { + if (is_expand) { + if (!is("punc", "}")) { + croak("Rest element must be last element"); + } + } + else if (is("operator", "=")) { used_parameters.mark_default_assignment(S.token); next(); elements[elements.length - 1].value = new AST_DefaultAssign({ @@ -2143,7 +2163,18 @@ function parse($TEXT, options) { if (!options.strict && is("punc", "}")) // allow trailing comma break; + start = S.token; + if (start.type == "expand") { + next(); + a.push(new AST_Expansion({ + start: start, + expression: expression(false), + end: prev(), + })); + continue; + } + var name = as_property_name(); var value; diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 9b7e6aa0..2f095383 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -695,3 +695,65 @@ export_default_class_decl: { } expect_exact: "export default class Car{};export class Cab{};" } + +object_rest_spread: { + mangle = { + toplevel: true, + } + input: { + var { w: w1, ...V } = { w: 7, x: 1, y: 2 }; console.log(w1, V); + let { w: w2, ...L } = { w: 8, x: 3, y: 4 }; console.log(w2, L); + const { w: w3, ...C } = { w: 9, x: 5, y: 6 }; console.log(w3, C); + + let b; + ({ b, ...V } = { a: 1, b: 2, c: 3 }); console.log(V); + ({ b, ...L } = { a: 4, b: 5, c: 6 }); console.log(L); + + (function({ y, ...p }){ console.log(p); })({ x: 1, y: 2, z: 3 }); + (({ y, ...p }) => { console.log(p); })({ x: 4, y: 5, z: 6 }); + + const T = { a: 1, b: 2 }; console.log({ ...T, w: 0, ...{}, ...L, ...{K: 9} }); + } + expect: { + var { w: o, ...l } = { w: 7, x: 1, y: 2 }; console.log(o, l); + let { w: c, ...n } = { w: 8, x: 3, y: 4 }; console.log(c, n); + const { w: e, ...s } = { w: 9, x: 5, y: 6 }; console.log(e, s); + + let g; + ({ b: g, ...l } = { a: 1, b: 2, c: 3 }); console.log(l); + ({ b: g, ...n } = { a: 4, b: 5, c: 6 }); console.log(n); + + (function({ y: o, ...l }) { console.log(l); })({ x: 1, y: 2, z: 3 }); + (({ y: o, ...l }) => { console.log(l); })({ x: 4, y: 5, z: 6 }); + + const w = { a: 1, b: 2 }; console.log({ ...w, w: 0, ...{}, ...n, ...{ K: 9 } }); + } +} + +object_spread_unsafe: { + options = { + collapse_vars: true, + evaluate: true, + join_vars: true, + passes: 3, + reduce_vars: true, + side_effects: true, + toplevel: true, + unsafe: true, + unused: true, + } + mangle = { + toplevel: true, + } + input: { + var o1 = { x: 1, y: 2 }; + var o2 = { x: 3, z: 4 }; + var cloned = { ...o1 }; + var merged = { ...o1, ...o2 }; + console.log(cloned, merged); + } + expect: { + var o = { x: 1, y: 2 }, l = { ...o }, x = { ...o, ...{ x: 3, z: 4 } }; + console.log(l, x); + } +} diff --git a/test/mocha/lhs-expressions.js b/test/mocha/lhs-expressions.js index d3dcf6c1..78a15c7e 100644 --- a/test/mocha/lhs-expressions.js +++ b/test/mocha/lhs-expressions.js @@ -253,13 +253,19 @@ describe("Left-hand side expressions", function () { // Multiple spreads are not allowed in destructuring array expect("[...a, ...b] = [1, 2, 3, 4]", "Spread must the be last element in destructuring array"); - // Spread in obvious object pattern - expect("({...a} = foo)", "Unexpected token: expand (...)"); + // Array spread must be last in destructuring declaration + expect("let [ ...x, a ] = o;", "Rest element must be last element"); + + // Only one spread per destructuring array declaration + expect("let [ a, ...x, ...y ] = o;", "Rest element must be last element"); // Spread in block should not be allowed expect("{...a} = foo", "Unexpected token: expand (...)"); - // Not in standard yet - expect("let foo = {bar: 42}, bar; bar = {...foo}", "Unexpected token: expand (...)"); + // Object spread must be last in destructuring declaration + expect("let { ...x, a } = o;", "Rest element must be last element"); + + // Only one spread per destructuring declaration + expect("let { a, ...x, ...y } = o;", "Rest element must be last element"); }); }); diff --git a/test/mocha/unicode.js b/test/mocha/unicode.js index a89b5031..2ad48ac2 100644 --- a/test/mocha/unicode.js +++ b/test/mocha/unicode.js @@ -146,6 +146,7 @@ describe("Unicode", function() { if (semver.satisfies(process.version, ">=4")) { it("Should not unescape unpaired surrogates", function() { + this.timeout(5000); var code = []; for (var i = 0; i <= 0x20001; i++) { code.push("\\u{" + i.toString(16) + "}");