Correctly transpile when cross-param refs with obj rest (#11326)

* Transform initializers with ids in rest elements

Fix issue 11281. Transform parameters with default initializers that
have ids that are also in a parameter with a rest element.
Before, these parameters were not transformed.

* Add plugin-transform-parameters as dependency

* Remove outdated comment

* Use set instead of array for paramsWithRestElement

* Skip when encounter "Scope"

Previously, f({...R}, f = R => R) would be incorrectly transformed.

* Pass in loose mode option instead of false

* Address review and re-organize tests

Checking the RHS of an assignment pattern/checking the parent of
an identifier node fails on cases like "({...R}, a = f(R))" or
"({...R}, {[R.key]: a = 42})".

Also refactor tests by removing unecessary tests and
separating "should transform" from "should not transform" tests.
This commit is contained in:
Vedant Roy
2020-04-07 15:06:43 -04:00
committed by GitHub
parent 70cc111b35
commit aea0fcd599
7 changed files with 172 additions and 16 deletions

View File

@@ -1,6 +1,7 @@
import { declare } from "@babel/helper-plugin-utils";
import syntaxObjectRestSpread from "@babel/plugin-syntax-object-rest-spread";
import { types as t } from "@babel/core";
import { convertFunctionParams } from "@babel/plugin-transform-parameters";
// TODO: Remove in Babel 8
// @babel/types <=7.3.3 counts FOO as referenced in var { x: FOO }.
@@ -177,9 +178,9 @@ export default declare((api, opts) => {
];
}
function replaceRestElement(parentPath, paramPath) {
function replaceRestElement(parentPath, paramPath, container) {
if (paramPath.isAssignmentPattern()) {
replaceRestElement(parentPath, paramPath.get("left"));
replaceRestElement(parentPath, paramPath.get("left"), container);
return;
}
@@ -187,7 +188,7 @@ export default declare((api, opts) => {
const elements = paramPath.get("elements");
for (let i = 0; i < elements.length; i++) {
replaceRestElement(parentPath, elements[i]);
replaceRestElement(parentPath, elements[i], container);
}
}
@@ -198,8 +199,12 @@ export default declare((api, opts) => {
t.variableDeclarator(paramPath.node, uid),
]);
parentPath.ensureBlock();
parentPath.get("body").unshiftContainer("body", declar);
if (container) {
container.push(declar);
} else {
parentPath.ensureBlock();
parentPath.get("body").unshiftContainer("body", declar);
}
paramPath.replaceWith(t.cloneNode(uid));
}
}
@@ -209,12 +214,71 @@ export default declare((api, opts) => {
inherits: syntaxObjectRestSpread,
visitor: {
// taken from transform-parameters/src/destructuring.js
// function a({ b, ...c }) {}
Function(path) {
const params = path.get("params");
for (let i = params.length - 1; i >= 0; i--) {
replaceRestElement(params[i].parentPath, params[i]);
const paramsWithRestElement = new Set();
const idsInRestParams = new Set();
for (let i = 0; i < params.length; ++i) {
const param = params[i];
if (hasRestElement(param)) {
paramsWithRestElement.add(i);
for (const name of Object.keys(param.getBindingIdentifiers())) {
idsInRestParams.add(name);
}
}
}
// if true, a parameter exists that has an id in its initializer
// that is also an id bound in a rest parameter
// example: f({...R}, a = R)
let idInRest = false;
const IdentifierHandler = function(path, functionScope) {
const name = path.node.name;
if (
path.scope.getBinding(name) === functionScope.getBinding(name) &&
idsInRestParams.has(name)
) {
idInRest = true;
path.stop();
}
};
let i;
for (i = 0; i < params.length && !idInRest; ++i) {
const param = params[i];
if (!paramsWithRestElement.has(i)) {
if (param.isReferencedIdentifier() || param.isBindingIdentifier()) {
IdentifierHandler(path, path.scope);
} else {
param.traverse(
{
"Scope|TypeAnnotation|TSTypeAnnotation": path => path.skip(),
"ReferencedIdentifier|BindingIdentifier": IdentifierHandler,
},
path.scope,
);
}
}
}
if (!idInRest) {
for (let i = 0; i < params.length; ++i) {
const param = params[i];
if (paramsWithRestElement.has(i)) {
replaceRestElement(param.parentPath, param);
}
}
} else {
const shouldTransformParam = idx =>
idx >= i - 1 || paramsWithRestElement.has(idx);
convertFunctionParams(
path,
loose,
shouldTransformParam,
replaceRestElement,
);
}
},
// adapted from transform-destructuring/src/index.js#pushObjectRest