diff --git a/packages/babel-plugin-transform-object-rest-spread/src/index.js b/packages/babel-plugin-transform-object-rest-spread/src/index.js index ec78fd61a8..45bcd3fdf6 100644 --- a/packages/babel-plugin-transform-object-rest-spread/src/index.js +++ b/packages/babel-plugin-transform-object-rest-spread/src/index.js @@ -1,6 +1,25 @@ export default function ({ types: t }) { + function hasRestProperty(node) { + for (let property of (node.properties)) { + if (t.isRestProperty(property)) { + return true; + } + } + + return false; + } + + function variableDeclarationHasRestProperty(node) { + for (let declar of (node.declarations)) { + if (t.isObjectPattern(declar.id)) { + return hasRestProperty(declar.id); + } + } + return false; + } + function hasSpread(node) { - for (let prop of (node.properties: Array)) { + for (let prop of (node.properties)) { if (t.isSpreadProperty(prop)) { return true; } @@ -8,10 +27,208 @@ export default function ({ types: t }) { return false; } + function createObjectSpread(file, props, objRef) { + const restProperty = props.pop(); + + let keys = []; + for (let prop of props) { + let key = prop.key; + if (t.isIdentifier(key) && !prop.computed) { + key = t.stringLiteral(prop.key.name); + } + keys.push(key); + } + + return [ + restProperty.argument, + t.callExpression( + file.addHelper("objectWithoutProperties"), [ + objRef, + t.arrayExpression(keys) + ] + ) + ]; + } + + function replaceRestProperty(paramsPath, i, numParams) { + if (paramsPath.isObjectPattern() && hasRestProperty(paramsPath.node)) { + let parentPath = paramsPath.parentPath; + let uid = parentPath.scope.generateUidIdentifier("ref"); + + let declar = t.variableDeclaration("let", [ + t.variableDeclarator(paramsPath.node, uid) + ]); + declar._blockHoist = i ? numParams - i : 1; + + parentPath.ensureBlock(); + parentPath.get("body").unshiftContainer("body", declar); + paramsPath.replaceWith(uid); + } + } + return { inherits: require("babel-plugin-syntax-object-rest-spread"), visitor: { + // taken from transform-es2015-parameters/src/destructuring.js + // function a({ b, ...c }) {} + Function(path) { + let params = path.get("params"); + for (let i = 0; i < params.length; i++) { + replaceRestProperty(params[i], i, params.length); + } + }, + // adapted from transform-es2015-destructuring/src/index.js#pushObjectRest + // const { a, ...b } = c; + VariableDeclarator(path, file) { + if (!path.get("id").isObjectPattern()) { return; } + const kind = path.parentPath.node.kind; + let nodes = []; + + path.traverse({ + RestProperty(path) { + let ref = this.originalPath.node.init; + + path.findParent((path) => { + if (path.isObjectProperty()) { + ref = t.memberExpression(ref, t.identifier(path.node.key.name)); + } else if (path.isVariableDeclarator()) { + return true; + } + }); + + let [ argument, callExpression ] = createObjectSpread( + file, + path.parentPath.node.properties, + ref + ); + + nodes.push( + t.variableDeclarator( + argument, + callExpression + ) + ); + + if (path.parentPath.node.properties.length === 0) { + path.findParent( + (path) => path.isObjectProperty() || path.isVariableDeclaration() + ).remove(); + } + } + },{ + originalPath: path + }); + + if (nodes.length > 0) { + path.parentPath.getSibling(path.parentPath.key + 1) + .insertBefore( + t.variableDeclaration(kind, nodes) + ); + } + }, + // taken from transform-es2015-destructuring/src/index.js#visitor + // export var { a, ...b } = c; + ExportNamedDeclaration(path) { + let declaration = path.get("declaration"); + if (!declaration.isVariableDeclaration()) return; + if (!variableDeclarationHasRestProperty(declaration.node)) return; + + let specifiers = []; + + for (let name in path.getOuterBindingIdentifiers(path)) { + let id = t.identifier(name); + specifiers.push(t.exportSpecifier(id, id)); + } + + // Split the declaration and export list into two declarations so that the variable + // declaration can be split up later without needing to worry about not being a + // top-level statement. + path.replaceWith(declaration.node); + path.insertAfter(t.exportNamedDeclaration(null, specifiers)); + }, + // try {} catch ({a, ...b}) {} + CatchClause(path) { + replaceRestProperty(path.get("param")); + }, + // ({a, ...b} = c); + AssignmentExpression(path, file) { + let leftPath = path.get("left"); + if (leftPath.isObjectPattern() && hasRestProperty(leftPath.node)) { + let nodes = []; + + let ref; + if (path.isCompletionRecord() || path.parentPath.isExpressionStatement()) { + ref = path.scope.generateUidIdentifierBasedOnNode(path.node.right, "ref"); + + nodes.push(t.variableDeclaration("var", [ + t.variableDeclarator(ref, path.node.right) + ])); + } + + let [ argument, callExpression ] = createObjectSpread( + file, + path.node.left.properties, + ref + ); + + let nodeWithoutSpread = t.clone(path.node); + nodeWithoutSpread.right = ref; + nodes.push(t.expressionStatement(nodeWithoutSpread)); + nodes.push(t.assignmentExpression( + "=", + argument, + callExpression + )); + + if (ref) { + nodes.push(t.expressionStatement(ref)); + } + + path.replaceWithMultiple(nodes); + } + }, + // taken from transform-es2015-destructuring/src/index.js#visitor + ForXStatement(path) { + let { node, scope } = path; + let left = node.left; + + // for ({a, ...b} of []) {} + if (t.isObjectPattern(left) && hasRestProperty(left)) { + let temp = scope.generateUidIdentifier("ref"); + + node.left = t.variableDeclaration("var", [ + t.variableDeclarator(temp) + ]); + + path.ensureBlock(); + + node.body.body.unshift(t.variableDeclaration("var", [ + t.variableDeclarator(left, temp) + ])); + + return; + } + + if (!t.isVariableDeclaration(left)) return; + + let pattern = left.declarations[0].id; + if (!t.isObjectPattern(pattern)) return; + + let key = scope.generateUidIdentifier("ref"); + node.left = t.variableDeclaration(left.kind, [ + t.variableDeclarator(key, null) + ]); + + path.ensureBlock(); + + node.body.body.unshift( + t.variableDeclaration(node.left.kind, [ + t.variableDeclarator(pattern, key) + ]) + ); + }, + // var a = { ...b, ...c } ObjectExpression(path, file) { if (!hasSpread(path.node)) return; diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/actual.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/actual.js new file mode 100644 index 0000000000..39f56c7a5a --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/actual.js @@ -0,0 +1,2 @@ +({ a } = c); +({ a, ...b } = c); diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/expected.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/expected.js new file mode 100644 index 0000000000..98f2cf1583 --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/assignment-expression/expected.js @@ -0,0 +1,5 @@ +({ a } = c); +var _c = c; +({ a } = _c); +b = babelHelpers.objectWithoutProperties(_c, ["a"]) +_c; diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/catch-clause/actual.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/catch-clause/actual.js new file mode 100644 index 0000000000..51beb20457 --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/catch-clause/actual.js @@ -0,0 +1,7 @@ +try {} catch({ ...a34 }) {} +try {} catch({a1, ...b1}) {} +try {} catch({a2, b2, ...c2}) {} + +// Unchanged +try {} catch(a) {} +try {} catch({ b }) {} diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/catch-clause/expected.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/catch-clause/expected.js new file mode 100644 index 0000000000..4578be0946 --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/catch-clause/expected.js @@ -0,0 +1,15 @@ +try {} catch (_ref) { + let a34 = babelHelpers.objectWithoutProperties(_ref, []); +} +try {} catch (_ref2) { + let { a1 } = _ref2; + let b1 = babelHelpers.objectWithoutProperties(_ref2, ["a1"]); +} +try {} catch (_ref3) { + let { a2, b2 } = _ref3; + let c2 = babelHelpers.objectWithoutProperties(_ref3, ["a2", "b2"]); +} + +// Unchanged +try {} catch (a) {} +try {} catch ({ b }) {} diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/export/actual.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/export/actual.js new file mode 100644 index 0000000000..5f0d8f6067 --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/export/actual.js @@ -0,0 +1,5 @@ +// ExportNamedDeclaration +export var { b, ...c } = asdf2; +// Skip +export var { bb, cc } = ads; +export var [ dd, ee ] = ads; diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/export/expected.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/export/expected.js new file mode 100644 index 0000000000..d3d2967d6e --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/export/expected.js @@ -0,0 +1,7 @@ +// ExportNamedDeclaration +var { b } = asdf2; +// Skip +var c = babelHelpers.objectWithoutProperties(asdf2, ["b"]); +export { b, c }; +export var { bb, cc } = ads; +export var [dd, ee] = ads; \ No newline at end of file diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/for-x/actual.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/for-x/actual.js new file mode 100644 index 0000000000..dd954fa0d2 --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/for-x/actual.js @@ -0,0 +1,19 @@ +// ForXStatement +for (var {a, ...b} of []) {} +for ({a, ...b} of []) {} +async function a() { + for await ({a, ...b} of []) {} +} + +// skip +for ({a} in {}) {} +for ({a} of []) {} +async function a() { + for await ({a} of []) {} +} + +for (a in {}) {} +for (a of []) {} +async function a() { + for await (a of []) {} +} diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/for-x/expected.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/for-x/expected.js new file mode 100644 index 0000000000..59b38ec57f --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/for-x/expected.js @@ -0,0 +1,28 @@ +// ForXStatement +for (var _ref of []) { + var { a } = _ref; + var b = babelHelpers.objectWithoutProperties(_ref, ["a"]); +} +for (var _ref2 of []) { + var { a } = _ref2; + var b = babelHelpers.objectWithoutProperties(_ref2, ["a"]); +} +async function a() { + for await (var _ref3 of []) { + var { a } = _ref3; + var b = babelHelpers.objectWithoutProperties(_ref3, ["a"]); + } +} + +// skip +for ({ a } in {}) {} +for ({ a } of []) {} +async function a() { + for ({ a } of []) {} +} + +for (a in {}) {} +for (a of []) {} +async function a() { + for (a of []) {} +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/options.json b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/options.json new file mode 100644 index 0000000000..cd791fa2bc --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + "syntax-async-generators", + "transform-object-rest-spread", + "external-helpers" + ] +} diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/parameters/actual.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/parameters/actual.js new file mode 100644 index 0000000000..42c4e739b6 --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/parameters/actual.js @@ -0,0 +1,8 @@ +function a({ ...a34 }) {} +function a2({a1, ...b1}) {} +function a3({a2, b2, ...c2}) {} +function a4({a3, ...c3}, {a5, ...c5}) {} +// Unchanged +function b(a) {} +function b2(a, ...b) {} +function b3({ b }) {} diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/parameters/expected.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/parameters/expected.js new file mode 100644 index 0000000000..0bb439b984 --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/parameters/expected.js @@ -0,0 +1,21 @@ +function a(_ref) { + let a34 = babelHelpers.objectWithoutProperties(_ref, []); +} +function a2(_ref2) { + let { a1 } = _ref2; + let b1 = babelHelpers.objectWithoutProperties(_ref2, ["a1"]); +} +function a3(_ref3) { + let { a2, b2 } = _ref3; + let c2 = babelHelpers.objectWithoutProperties(_ref3, ["a2", "b2"]); +} +function a4(_ref4, _ref5) { + let { a5 } = _ref5; + let c5 = babelHelpers.objectWithoutProperties(_ref5, ["a5"]); + let { a3 } = _ref4; + let c3 = babelHelpers.objectWithoutProperties(_ref4, ["a3"]); +} +// Unchanged +function b(a) {} +function b2(a, ...b) {} +function b3({ b }) {} diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-destructuring/actual.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-destructuring/actual.js new file mode 100644 index 0000000000..a6dd0ce6d4 --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-destructuring/actual.js @@ -0,0 +1,17 @@ +var z = {}; +var { ...x } = z; +var { ...a } = { a: 1 }; +var { ...x } = a.b; +var { ...x } = a(); +var {x1, ...y1} = z; +x1++; +var { [a]: b, ...c } = z; +var {x1, ...y1} = z; +let {x2, y2, ...z2} = z; +const {w3, x3, y3, ...z4} = z; + +let { + x: { a: xa, [d]: f, ...asdf }, + y: { ...d }, + ...g +} = complex; diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-destructuring/expected.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-destructuring/expected.js new file mode 100644 index 0000000000..3fbd6e33c9 --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-destructuring/expected.js @@ -0,0 +1,24 @@ +var z = {}; +var x = babelHelpers.objectWithoutProperties(z, []); +var a = babelHelpers.objectWithoutProperties({ a: 1 }, []); +var x = babelHelpers.objectWithoutProperties(a.b, []); +var x = babelHelpers.objectWithoutProperties(a(), []); + +var { x1 } = z; +var y1 = babelHelpers.objectWithoutProperties(z, ["x1"]); +x1++; +var { [a]: b } = z; +var c = babelHelpers.objectWithoutProperties(z, [a]); +var { x1 } = z; +var y1 = babelHelpers.objectWithoutProperties(z, ["x1"]); +let { x2, y2 } = z; +let z2 = babelHelpers.objectWithoutProperties(z, ["x2", "y2"]); +const { w3, x3, y3 } = z; + +const z4 = babelHelpers.objectWithoutProperties(z, ["w3", "x3", "y3"]); +let { + x: { a: xa, [d]: f } +} = complex; +let asdf = babelHelpers.objectWithoutProperties(complex.x, ["a", d]), + d = babelHelpers.objectWithoutProperties(complex.y, []), + g = babelHelpers.objectWithoutProperties(complex, ["x"]); diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-exec/exec.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-exec/exec.js new file mode 100644 index 0000000000..7f58d8f454 --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-exec/exec.js @@ -0,0 +1,22 @@ +// var { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; + +// assert.equal(x, 1); +// assert.equal(y, 2); +// assert.deepEqual(z, { a: 3, b: 4 }); + +// var complex = { +// x: { a: 1, b: 2, c: 3 }, +// }; + +// var { +// x: { a: xa, ...xbc } +// } = complex; + +// assert.equal(xa, 1); +// assert.deepEqual(xbc, { b: 2, c: 3}); + +// // own properties +// function ownX({ ...properties }) { +// return properties.x; +// } +// assert.equal(ownX(Object.create({ x: 1 })), undefined); diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/options.json b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-exec/options.json similarity index 100% rename from packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/options.json rename to packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest/variable-exec/options.json diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/assignment/actual.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/assignment/actual.js similarity index 100% rename from packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/assignment/actual.js rename to packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/assignment/actual.js diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/assignment/expected.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/assignment/expected.js similarity index 100% rename from packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/assignment/expected.js rename to packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/assignment/expected.js diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/expression/actual.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/expression/actual.js similarity index 100% rename from packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/expression/actual.js rename to packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/expression/actual.js diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/expression/expected.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/expression/expected.js similarity index 100% rename from packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/expression/expected.js rename to packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/expression/expected.js diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/options.json b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/options.json new file mode 100644 index 0000000000..85af630bdd --- /dev/null +++ b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["transform-object-rest-spread"] +} diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/variable-declaration/actual.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/variable-declaration/actual.js similarity index 100% rename from packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/variable-declaration/actual.js rename to packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/variable-declaration/actual.js diff --git a/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/variable-declaration/expected.js b/packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/variable-declaration/expected.js similarity index 100% rename from packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-rest-spread/variable-declaration/expected.js rename to packages/babel-plugin-transform-object-rest-spread/test/fixtures/object-spread/variable-declaration/expected.js