Don't extract rest elements from nested expressions (#7364)

* Don't extract rest elements from nested expressions

* Node 4
This commit is contained in:
Nicolò Ribaudo 2018-02-17 16:22:38 +01:00 committed by Mateusz Burzyński
parent 4d17a96d50
commit 3d49766f6b
8 changed files with 152 additions and 94 deletions

View File

@ -10,15 +10,30 @@ export default function(api, opts) {
function hasRestElement(path) { function hasRestElement(path) {
let foundRestElement = false; let foundRestElement = false;
path.traverse({ visitRestElements(path, () => {
RestElement() {
foundRestElement = true; foundRestElement = true;
path.stop(); path.stop();
},
}); });
return foundRestElement; 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) { function hasSpread(node) {
for (const prop of node.properties) { for (const prop of node.properties) {
if (t.isSpreadElement(prop)) { if (t.isSpreadElement(prop)) {
@ -147,16 +162,9 @@ export default function(api, opts) {
} }
let insertionPath = path; let insertionPath = path;
const originalPath = path;
path.get("id").traverse( visitRestElements(path.get("id"), path => {
{
// 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()) { if (!path.parentPath.isObjectPattern()) {
// Return early if the parent is not an ObjectPattern, but // Return early if the parent is not an ObjectPattern, but
// (for example) an ArrayPattern or Function, because that // (for example) an ArrayPattern or Function, because that
@ -168,33 +176,30 @@ export default function(api, opts) {
// skip single-property case, e.g. // skip single-property case, e.g.
// const { ...x } = foo(); // const { ...x } = foo();
// since the RHS will not be duplicated // since the RHS will not be duplicated
this.originalPath.node.id.properties.length > 1 && originalPath.node.id.properties.length > 1 &&
!t.isIdentifier(this.originalPath.node.init) !t.isIdentifier(originalPath.node.init)
) { ) {
// const { a, ...b } = foo(); // const { a, ...b } = foo();
// to avoid calling foo() twice, as a first step convert it to: // to avoid calling foo() twice, as a first step convert it to:
// const _foo = foo(), // const _foo = foo(),
// { a, ...b } = _foo; // { a, ...b } = _foo;
const initRef = path.scope.generateUidIdentifierBasedOnNode( const initRef = path.scope.generateUidIdentifierBasedOnNode(
this.originalPath.node.init, originalPath.node.init,
"ref", "ref",
); );
// insert _foo = foo() // insert _foo = foo()
this.originalPath.insertBefore( originalPath.insertBefore(
t.variableDeclarator(initRef, this.originalPath.node.init), t.variableDeclarator(initRef, originalPath.node.init),
); );
// replace foo() with _foo // replace foo() with _foo
this.originalPath.replaceWith( originalPath.replaceWith(
t.variableDeclarator( t.variableDeclarator(originalPath.node.id, t.cloneNode(initRef)),
this.originalPath.node.id,
t.cloneNode(initRef),
),
); );
return; return;
} }
let ref = this.originalPath.node.init; let ref = originalPath.node.init;
const refPropertyPath = []; const refPropertyPath = [];
let kind; let kind;
@ -237,24 +242,22 @@ export default function(api, opts) {
if (objectPatternPath.node.properties.length === 0) { if (objectPatternPath.node.properties.length === 0) {
objectPatternPath objectPatternPath
.findParent( .findParent(
path => path => path.isObjectProperty() || path.isVariableDeclarator(),
path.isObjectProperty() || path.isVariableDeclarator(),
) )
.remove(); .remove();
} }
}, });
},
{
originalPath: path,
},
);
}, },
// taken from transform-destructuring/src/index.js#visitor // taken from transform-destructuring/src/index.js#visitor
// export var { a, ...b } = c; // export var { a, ...b } = c;
ExportNamedDeclaration(path) { ExportNamedDeclaration(path) {
const declaration = path.get("declaration"); const declaration = path.get("declaration");
if (!declaration.isVariableDeclaration()) return; if (!declaration.isVariableDeclaration()) return;
if (!hasRestElement(declaration)) return;
const hasRest = declaration
.get("declarations")
.some(path => hasRestElement(path.get("id")));
if (!hasRest) return;
const specifiers = []; const specifiers = [];

View File

@ -0,0 +1,6 @@
const {
[({ ...rest }) => {
let { ...b } = {};
}]: a,
[({ ...d } = {})]: c,
} = {};

View File

@ -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
} = {};

View File

@ -0,0 +1,6 @@
const {
a = ({ ...rest }) => {
let { ...b } = {};
},
c = ({ ...d } = {}),
} = {};

View File

@ -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)
} = {};

View File

@ -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");

View File

@ -0,0 +1 @@
const { a: { ...bar }, b: { ...baz }, ...foo } = obj;

View File

@ -0,0 +1,3 @@
const bar = babelHelpers.objectWithoutProperties(obj.a, []),
baz = babelHelpers.objectWithoutProperties(obj.b, []),
foo = babelHelpers.objectWithoutProperties(obj, []);