Avoid duplicating impure expressions in object rest destructuring (#5151)

* avoid duplicating impure initializers in object rest destructuring

* reuse existing VariableDeclarations in object rest destructuring, to fix two issues:

1. inserting an additional VariableDeclaration after the current one may change order of operations, which is unsafe if a future VariableDeclarator refers to a destructured variable.

2. The entire VariableDeclaration is removed when all properties are rest properties, indiscriminately removing other variables
This commit is contained in:
Erik Desjardins
2017-01-18 21:32:44 -05:00
committed by Henry Zhu
parent 3871236723
commit bca170ad79
11 changed files with 124 additions and 58 deletions

View File

@@ -78,11 +78,31 @@ export default function ({ types: t }) {
// const { a, ...b } = c;
VariableDeclarator(path, file) {
if (!path.get("id").isObjectPattern()) { return; }
const kind = path.parentPath.node.kind;
const nodes = [];
path.traverse({
let insertionPath = path;
path.get("id").traverse({
RestProperty(path) {
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, initRef));
return;
}
let ref = this.originalPath.node.init;
path.findParent((path) => {
@@ -99,29 +119,24 @@ export default function ({ types: t }) {
ref
);
nodes.push(
insertionPath.insertAfter(
t.variableDeclarator(
argument,
callExpression
)
);
insertionPath = insertionPath.getSibling(insertionPath.key + 1);
if (path.parentPath.node.properties.length === 0) {
path.findParent(
(path) => path.isObjectProperty() || path.isVariableDeclaration()
(path) => path.isObjectProperty() || path.isVariableDeclarator()
).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;