Support transforming params of arrow functions in class fields (#13941)

This commit is contained in:
Nicolò Ribaudo 2021-11-09 12:12:23 +01:00 committed by GitHub
parent a6a526968d
commit 43f989941b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 167 additions and 20 deletions

View File

@ -23,6 +23,10 @@ export default declare((api, options) => {
) {
// default/rest visitors require access to `arguments`, so it cannot be an arrow
path.arrowFunctionToExpression({ noNewArrows });
// In some cases arrowFunctionToExpression replaces the function with a wrapper.
// Return early; the wrapped function will be visited later in the AST traversal.
if (!path.isFunctionExpression()) return;
}
const convertedRest = convertFunctionRest(path);

View File

@ -0,0 +1 @@
let f = (x = 0) => x + 1;

View File

@ -0,0 +1,7 @@
var _this = this;
let f = function f() {
let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
babelHelpers.newArrowCheck(this, _this);
return x + 1;
}.bind(this);

View File

@ -0,0 +1,6 @@
{
"plugins": ["transform-parameters"],
"assumptions": {
"noNewArrows": false
}
}

View File

@ -0,0 +1,8 @@
class A extends B {
handle = ((x = 0) => {
console.log(x, this, new.target, super.y);
})(() => {
let y = 0;
return (x = y) => x + this;
})((x = 1) => {})(this);
}

View File

@ -0,0 +1,5 @@
{
"plugins": [
"transform-parameters"
]
}

View File

@ -0,0 +1,22 @@
class A extends B {
handle = (() => {
var _newtarget = new.target,
_superprop_getY = () => super.y,
_this = this;
return function () {
let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
console.log(x, _this, _newtarget, _superprop_getY());
};
})()(() => {
var _this2 = this;
let y = 0;
return function () {
let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : y;
return x + _this2;
};
})((() => function () {
let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
})())(this);
}

View File

@ -0,0 +1,8 @@
class A extends B {
#handle = ((x = 0) => {
console.log(x, this, new.target, super.y);
})(() => {
let y = 0;
return (x = y) => x + this;
})((x = 1) => {})(this);
}

View File

@ -0,0 +1,5 @@
{
"plugins": [
"transform-parameters"
]
}

View File

@ -0,0 +1,22 @@
class A extends B {
#handle = (() => {
var _newtarget = new.target,
_superprop_getY = () => super.y,
_this = this;
return function () {
let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
console.log(x, _this, _newtarget, _superprop_getY());
};
})()(() => {
var _this2 = this;
let y = 0;
return function () {
let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : y;
return x + _this2;
};
})((() => function () {
let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
})())(this);
}

View File

@ -0,0 +1,5 @@
class A {
#handle = (x = 0) => {
console.log(x);
};
}

View File

@ -0,0 +1,5 @@
{
"plugins": [
"transform-parameters"
]
}

View File

@ -0,0 +1,6 @@
class A {
#handle = (() => function () {
let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
console.log(x);
})();
}

View File

@ -0,0 +1,5 @@
class A {
handle = (x = 0) => {
console.log(x);
};
}

View File

@ -0,0 +1,5 @@
{
"plugins": [
"transform-parameters"
]
}

View File

@ -0,0 +1,6 @@
class A {
handle = (() => function () {
let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
console.log(x);
})();
}

View File

@ -22,6 +22,7 @@ import {
stringLiteral,
super as _super,
thisExpression,
toExpression,
unaryExpression,
} from "@babel/types";
import type * as t from "@babel/types";
@ -146,27 +147,26 @@ export function arrowFunctionToExpression(
);
}
const thisBinding = hoistFunctionEnvironment(
const { thisBinding, fnPath: fn } = hoistFunctionEnvironment(
this,
noNewArrows,
allowInsertArrow,
);
this.ensureBlock();
// @ts-expect-error todo(flow->ts): avoid mutating nodes
this.node.type = "FunctionExpression";
fn.ensureBlock();
fn.node.type = "FunctionExpression";
if (!noNewArrows) {
const checkBinding = thisBinding
? null
: this.parentPath.scope.generateUidIdentifier("arrowCheckId");
: fn.scope.generateUidIdentifier("arrowCheckId");
if (checkBinding) {
this.parentPath.scope.push({
fn.parentPath.scope.push({
id: checkBinding,
init: objectExpression([]),
});
}
this.get("body").unshiftContainer(
fn.get("body").unshiftContainer(
"body",
expressionStatement(
callExpression(this.hub.addHelper("newArrowCheck"), [
@ -178,10 +178,10 @@ export function arrowFunctionToExpression(
),
);
this.replaceWith(
fn.replaceWith(
callExpression(
memberExpression(
nameFunction(this, true) || this.node,
nameFunction(this, true) || fn.node,
identifier("bind"),
),
[checkBinding ? identifier(checkBinding.name) : thisExpression()],
@ -193,27 +193,54 @@ export function arrowFunctionToExpression(
/**
* Given a function, traverse its contents, and if there are references to "this", "arguments", "super",
* or "new.target", ensure that these references reference the parent environment around this function.
*
* @returns `thisBinding`: the name of the injected reference to `this`; for example "_this"
* @returns `fnPath`: the new path to the function node. This is different from the fnPath
* parameter when the function node is wrapped in another node.
*/
function hoistFunctionEnvironment(
fnPath,
fnPath: NodePath<t.Function>,
// TODO(Babel 8): Consider defaulting to `false` for spec compliancy
noNewArrows = true,
allowInsertArrow = true,
) {
const thisEnvFn = fnPath.findParent(p => {
): { thisBinding: string; fnPath: NodePath<t.Function> } {
let arrowParent;
let thisEnvFn = fnPath.findParent(p => {
if (p.isArrowFunctionExpression()) {
arrowParent ??= p;
return false;
}
return (
(p.isFunction() && !p.isArrowFunctionExpression()) ||
p.isFunction() ||
p.isProgram() ||
p.isClassProperty({ static: false })
p.isClassProperty({ static: false }) ||
p.isClassPrivateProperty({ static: false })
);
});
const inConstructor = thisEnvFn?.node.kind === "constructor";
const inConstructor = thisEnvFn.isClassMethod({ kind: "constructor" });
if (thisEnvFn.isClassProperty()) {
if (thisEnvFn.isClassProperty() || thisEnvFn.isClassPrivateProperty()) {
if (arrowParent) {
thisEnvFn = arrowParent;
} else if (allowInsertArrow) {
// It's safe to wrap this function in another and not hoist to the
// top level because the 'this' binding is constant in class
// properties (since 'super()' has already been called), so we don't
// need to capture/reassign it at the top level.
fnPath.replaceWith(
callExpression(
arrowFunctionExpression([], toExpression(fnPath.node)),
[],
),
);
thisEnvFn = fnPath.get("callee");
fnPath = thisEnvFn.get("body");
} else {
throw fnPath.buildCodeFrameError(
"Unable to transform arrow inside class property",
);
}
}
const { thisPaths, argumentsPaths, newTargetPaths, superProps, superCalls } =
getScopeInformation(fnPath);
@ -365,7 +392,7 @@ function hoistFunctionEnvironment(
}
}
return thisBinding;
return { thisBinding, fnPath };
}
function standardizeSuperProperty(superProp) {