diff --git a/packages/babel-plugin-proposal-object-rest-spread/src/index.js b/packages/babel-plugin-proposal-object-rest-spread/src/index.js index 2bf068e21e..f4941e5f5f 100644 --- a/packages/babel-plugin-proposal-object-rest-spread/src/index.js +++ b/packages/babel-plugin-proposal-object-rest-spread/src/index.js @@ -10,15 +10,30 @@ export default function(api, opts) { function hasRestElement(path) { let foundRestElement = false; - path.traverse({ - RestElement() { - foundRestElement = true; - path.stop(); - }, + visitRestElements(path, () => { + foundRestElement = true; + path.stop(); }); return foundRestElement; } + function visitRestElements(path, visitor) { + path.traverse({ + Expression(path) { + const parentType = path.parent.type; + if ( + (parentType == "AssignmentPattern" && path.key === "right") || + (parentType == "ObjectProperty" && + path.parent.computed && + path.key === "key") + ) { + path.skip(); + } + }, + RestElement: visitor, + }); + } + function hasSpread(node) { for (const prop of node.properties) { if (t.isSpreadElement(prop)) { @@ -147,114 +162,102 @@ export default function(api, opts) { } let insertionPath = path; + const originalPath = path; - path.get("id").traverse( - { - // If there's a default-value AssignmentPattern within the ObjectPattern, - // we should not traverse into it, lest we end up in another function body. - // (The parent traversal will handle it.) - AssignmentPattern(path) { - path.skip(); - }, - RestElement(path) { - if (!path.parentPath.isObjectPattern()) { - // Return early if the parent is not an ObjectPattern, but - // (for example) an ArrayPattern or Function, because that - // means this RestElement is an not an object property. - return; - } + visitRestElements(path.get("id"), path => { + if (!path.parentPath.isObjectPattern()) { + // Return early if the parent is not an ObjectPattern, but + // (for example) an ArrayPattern or Function, because that + // means this RestElement is an not an object property. + return; + } - if ( - // skip single-property case, e.g. - // const { ...x } = foo(); - // since the RHS will not be duplicated - this.originalPath.node.id.properties.length > 1 && - !t.isIdentifier(this.originalPath.node.init) - ) { - // const { a, ...b } = foo(); - // to avoid calling foo() twice, as a first step convert it to: - // const _foo = foo(), - // { a, ...b } = _foo; - const initRef = path.scope.generateUidIdentifierBasedOnNode( - this.originalPath.node.init, - "ref", - ); - // insert _foo = foo() - this.originalPath.insertBefore( - t.variableDeclarator(initRef, this.originalPath.node.init), - ); - // replace foo() with _foo - this.originalPath.replaceWith( - t.variableDeclarator( - this.originalPath.node.id, - t.cloneNode(initRef), - ), - ); + if ( + // skip single-property case, e.g. + // const { ...x } = foo(); + // since the RHS will not be duplicated + originalPath.node.id.properties.length > 1 && + !t.isIdentifier(originalPath.node.init) + ) { + // const { a, ...b } = foo(); + // to avoid calling foo() twice, as a first step convert it to: + // const _foo = foo(), + // { a, ...b } = _foo; + const initRef = path.scope.generateUidIdentifierBasedOnNode( + originalPath.node.init, + "ref", + ); + // insert _foo = foo() + originalPath.insertBefore( + t.variableDeclarator(initRef, originalPath.node.init), + ); + // replace foo() with _foo + originalPath.replaceWith( + t.variableDeclarator(originalPath.node.id, t.cloneNode(initRef)), + ); - return; - } + return; + } - let ref = this.originalPath.node.init; - const refPropertyPath = []; - let kind; + let ref = originalPath.node.init; + const refPropertyPath = []; + let kind; - path.findParent(path => { - if (path.isObjectProperty()) { - refPropertyPath.unshift(path.node.key.name); - } else if (path.isVariableDeclarator()) { - kind = path.parentPath.node.kind; - return true; - } - }); + path.findParent(path => { + if (path.isObjectProperty()) { + refPropertyPath.unshift(path.node.key.name); + } else if (path.isVariableDeclarator()) { + kind = path.parentPath.node.kind; + return true; + } + }); - if (refPropertyPath.length) { - refPropertyPath.forEach(prop => { - ref = t.memberExpression(ref, t.identifier(prop)); - }); - } + if (refPropertyPath.length) { + refPropertyPath.forEach(prop => { + ref = t.memberExpression(ref, t.identifier(prop)); + }); + } - const objectPatternPath = path.findParent(path => - path.isObjectPattern(), - ); - const [ - impureComputedPropertyDeclarators, - argument, - callExpression, - ] = createObjectSpread(objectPatternPath, file, ref); + const objectPatternPath = path.findParent(path => + path.isObjectPattern(), + ); + const [ + impureComputedPropertyDeclarators, + argument, + callExpression, + ] = createObjectSpread(objectPatternPath, file, ref); - t.assertIdentifier(argument); + t.assertIdentifier(argument); - insertionPath.insertBefore(impureComputedPropertyDeclarators); + insertionPath.insertBefore(impureComputedPropertyDeclarators); - insertionPath.insertAfter( - t.variableDeclarator(argument, callExpression), - ); + insertionPath.insertAfter( + t.variableDeclarator(argument, callExpression), + ); - insertionPath = insertionPath.getSibling(insertionPath.key + 1); + insertionPath = insertionPath.getSibling(insertionPath.key + 1); - path.scope.registerBinding(kind, insertionPath); + path.scope.registerBinding(kind, insertionPath); - if (objectPatternPath.node.properties.length === 0) { - objectPatternPath - .findParent( - path => - path.isObjectProperty() || path.isVariableDeclarator(), - ) - .remove(); - } - }, - }, - { - originalPath: path, - }, - ); + if (objectPatternPath.node.properties.length === 0) { + objectPatternPath + .findParent( + path => path.isObjectProperty() || path.isVariableDeclarator(), + ) + .remove(); + } + }); }, // taken from transform-destructuring/src/index.js#visitor // export var { a, ...b } = c; ExportNamedDeclaration(path) { const declaration = path.get("declaration"); if (!declaration.isVariableDeclaration()) return; - if (!hasRestElement(declaration)) return; + + const hasRest = declaration + .get("declarations") + .some(path => hasRestElement(path.get("id"))); + if (!hasRest) return; const specifiers = []; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-computed-key/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-computed-key/input.js new file mode 100644 index 0000000000..74d87a1495 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-computed-key/input.js @@ -0,0 +1,6 @@ +const { + [({ ...rest }) => { + let { ...b } = {}; + }]: a, + [({ ...d } = {})]: c, +} = {}; \ No newline at end of file diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-computed-key/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-computed-key/output.js new file mode 100644 index 0000000000..dbc67adecf --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-computed-key/output.js @@ -0,0 +1,9 @@ +var _ref2; + +const { + [(_ref) => { + let rest = babelHelpers.objectWithoutProperties(_ref, []); + let b = babelHelpers.objectWithoutProperties({}, []); + }]: a, + [(_ref2 = {}, ({} = _ref2), d = babelHelpers.objectWithoutProperties(_ref2, []), _ref2)]: c +} = {}; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-default-value/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-default-value/input.js new file mode 100644 index 0000000000..7537d685b3 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-default-value/input.js @@ -0,0 +1,6 @@ +const { + a = ({ ...rest }) => { + let { ...b } = {}; + }, + c = ({ ...d } = {}), +} = {}; \ No newline at end of file diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-default-value/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-default-value/output.js new file mode 100644 index 0000000000..b166fdc5d9 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-default-value/output.js @@ -0,0 +1,9 @@ +var _ref2; + +const { + a = (_ref) => { + let rest = babelHelpers.objectWithoutProperties(_ref, []); + let b = babelHelpers.objectWithoutProperties({}, []); + }, + c = (_ref2 = {}, ({} = _ref2), d = babelHelpers.objectWithoutProperties(_ref2, []), _ref2) +} = {}; diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-order/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-order/exec.js new file mode 100644 index 0000000000..bdccb5b708 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-order/exec.js @@ -0,0 +1,21 @@ +var result = ""; + +var obj = { + get foo() { + result += "foo" + }, + a: { + get bar() { + result += "bar"; + } + }, + b: { + get baz() { + result += "baz"; + } + } +}; + +var { a: { ...bar }, b: { ...baz }, ...foo } = obj; + +assert.strictEqual(result, "barbazfoo"); \ No newline at end of file diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-order/input.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-order/input.js new file mode 100644 index 0000000000..76876286cd --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-order/input.js @@ -0,0 +1 @@ +const { a: { ...bar }, b: { ...baz }, ...foo } = obj; \ No newline at end of file diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-order/output.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-order/output.js new file mode 100644 index 0000000000..96a7088a39 --- /dev/null +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-rest/nested-order/output.js @@ -0,0 +1,3 @@ +const bar = babelHelpers.objectWithoutProperties(obj.a, []), + baz = babelHelpers.objectWithoutProperties(obj.b, []), + foo = babelHelpers.objectWithoutProperties(obj, []);