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,114 +162,102 @@ export default function(api, opts) {
} }
let insertionPath = path; let insertionPath = path;
const originalPath = path;
path.get("id").traverse( visitRestElements(path.get("id"), path => {
{ if (!path.parentPath.isObjectPattern()) {
// If there's a default-value AssignmentPattern within the ObjectPattern, // Return early if the parent is not an ObjectPattern, but
// we should not traverse into it, lest we end up in another function body. // (for example) an ArrayPattern or Function, because that
// (The parent traversal will handle it.) // means this RestElement is an not an object property.
AssignmentPattern(path) { return;
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;
}
if ( if (
// 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;
path.findParent(path => { path.findParent(path => {
if (path.isObjectProperty()) { if (path.isObjectProperty()) {
refPropertyPath.unshift(path.node.key.name); refPropertyPath.unshift(path.node.key.name);
} else if (path.isVariableDeclarator()) { } else if (path.isVariableDeclarator()) {
kind = path.parentPath.node.kind; kind = path.parentPath.node.kind;
return true; return true;
} }
}); });
if (refPropertyPath.length) { if (refPropertyPath.length) {
refPropertyPath.forEach(prop => { refPropertyPath.forEach(prop => {
ref = t.memberExpression(ref, t.identifier(prop)); ref = t.memberExpression(ref, t.identifier(prop));
}); });
} }
const objectPatternPath = path.findParent(path => const objectPatternPath = path.findParent(path =>
path.isObjectPattern(), path.isObjectPattern(),
); );
const [ const [
impureComputedPropertyDeclarators, impureComputedPropertyDeclarators,
argument, argument,
callExpression, callExpression,
] = createObjectSpread(objectPatternPath, file, ref); ] = createObjectSpread(objectPatternPath, file, ref);
t.assertIdentifier(argument); t.assertIdentifier(argument);
insertionPath.insertBefore(impureComputedPropertyDeclarators); insertionPath.insertBefore(impureComputedPropertyDeclarators);
insertionPath.insertAfter( insertionPath.insertAfter(
t.variableDeclarator(argument, callExpression), 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) { 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, []);