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) {
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 = [];

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, []);