Skip to content

Commit

Permalink
Don't use args rest/spread to hoist super method calls (#9939)
Browse files Browse the repository at this point in the history
* Don't use args rest/spread to hoist super method calls

* Hoist this in hoisted super method calls

* Make the code more readable

Review by edoardocavazza
  • Loading branch information
nicolo-ribaudo committed Oct 11, 2019
1 parent c455d2a commit 99035ca
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ function s(_x) {

function _s() {
_s = babelHelpers.asyncToGenerator(function* (x) {
var _this = this,
_arguments = arguments;
var _arguments = arguments,
_this = this;

for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
class Foo extends class {} {
method() {
var _superprop_callMethod = (..._args) => super.method(..._args);
var _superprop_getMethod = () => super.method,
_this = this;

return babelHelpers.asyncToGenerator(function* () {
_superprop_callMethod();
_superprop_getMethod().call(_this);

var arrow = () => _superprop_callMethod();
var arrow = () => _superprop_getMethod().call(_this);
})();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class MyClass extends BaseClass {
async loadEntity() {
this.website = await this.loadWebsite();
this.report.setCompany(this.website.company);
super.loadEntity();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"presets": [
[
"env",
{
"targets": [
"Chrome >= 60",
"Safari >= 11",
"iOS >= 10.3",
"Firefox >= 55",
"Edge >= 16"
]
}
]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

class MyClass extends BaseClass {
loadEntity() {
var _superprop_getLoadEntity = () => super.loadEntity,
_this = this;

return _asyncToGenerator(function* () {
_this.website = yield _this.loadWebsite();

_this.report.setCompany(_this.website.company);

_superprop_getLoadEntity().call(_this);
})();
}

}
137 changes: 52 additions & 85 deletions packages/babel-traverse/src/path/conversion.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,30 +216,6 @@ function hoistFunctionEnvironment(
});
}

// Convert all "this" references in the arrow to point at the alias.
let thisBinding;
if (thisPaths.length > 0 || specCompliant) {
thisBinding = getThisBinding(thisEnvFn, inConstructor);

if (
!specCompliant ||
// In subclass constructors, still need to rewrite because "this" can't be bound in spec mode
// because it might not have been initialized yet.
(inConstructor && hasSuperClass(thisEnvFn))
) {
thisPaths.forEach(thisChild => {
const thisRef = thisChild.isJSX()
? t.jsxIdentifier(thisBinding)
: t.identifier(thisBinding);

thisRef.loc = thisChild.node.loc;
thisChild.replaceWith(thisRef);
});

if (specCompliant) thisBinding = null;
}
}

// Convert all "arguments" references in the arrow to point at the alias.
if (argumentsPaths.length > 0) {
const argumentsBinding = getBinding(thisEnvFn, "arguments", () =>
Expand Down Expand Up @@ -286,42 +262,64 @@ function hoistFunctionEnvironment(
? ""
: superProp.get("property").node.name;

if (superProp.parentPath.isCallExpression({ callee: superProp.node })) {
const superBinding = getSuperPropCallBinding(thisEnvFn, key);
const isAssignment = superProp.parentPath.isAssignmentExpression({
left: superProp.node,
});
const isCall = superProp.parentPath.isCallExpression({
callee: superProp.node,
});
const superBinding = getSuperPropBinding(thisEnvFn, isAssignment, key);

const args = [];
if (superProp.node.computed) {
args.push(superProp.get("property").node);
}

if (isAssignment) {
const value = superProp.parentPath.node.right;
args.push(value);
}

const call = t.callExpression(t.identifier(superBinding), args);

if (superProp.node.computed) {
const prop = superProp.get("property").node;
superProp.replaceWith(t.identifier(superBinding));
superProp.parentPath.node.arguments.unshift(prop);
} else {
superProp.replaceWith(t.identifier(superBinding));
}
if (isCall) {
superProp.parentPath.unshiftContainer("arguments", t.thisExpression());
superProp.replaceWith(t.memberExpression(call, t.identifier("call")));

thisPaths.push(superProp.parentPath.get("arguments.0"));
} else if (isAssignment) {
// Replace not only the super.prop, but the whole assignment
superProp.parentPath.replaceWith(call);
} else {
const isAssignment = superProp.parentPath.isAssignmentExpression({
left: superProp.node,
});
const superBinding = getSuperPropBinding(thisEnvFn, isAssignment, key);

const args = [];
if (superProp.node.computed) {
args.push(superProp.get("property").node);
}

if (isAssignment) {
const value = superProp.parentPath.node.right;
args.push(value);
superProp.parentPath.replaceWith(
t.callExpression(t.identifier(superBinding), args),
);
} else {
superProp.replaceWith(
t.callExpression(t.identifier(superBinding), args),
);
}
superProp.replaceWith(call);
}
});
}

// Convert all "this" references in the arrow to point at the alias.
let thisBinding;
if (thisPaths.length > 0 || specCompliant) {
thisBinding = getThisBinding(thisEnvFn, inConstructor);

if (
!specCompliant ||
// In subclass constructors, still need to rewrite because "this" can't be bound in spec mode
// because it might not have been initialized yet.
(inConstructor && hasSuperClass(thisEnvFn))
) {
thisPaths.forEach(thisChild => {
const thisRef = thisChild.isJSX()
? t.jsxIdentifier(thisBinding)
: t.identifier(thisBinding);

thisRef.loc = thisChild.node.loc;
thisChild.replaceWith(thisRef);
});

if (specCompliant) thisBinding = null;
}
}

return thisBinding;
}

Expand Down Expand Up @@ -485,37 +483,6 @@ function getSuperBinding(thisEnvFn) {
});
}

// Create a binding for a function that will call "super.foo()" or "super[foo]()".
function getSuperPropCallBinding(thisEnvFn, propName) {
return getBinding(thisEnvFn, `superprop_call:${propName || ""}`, () => {
const argsBinding = thisEnvFn.scope.generateUidIdentifier("args");
const argsList = [t.restElement(argsBinding)];

let fnBody;
if (propName) {
// (...args) => super.foo(...args)
fnBody = t.callExpression(
t.memberExpression(t.super(), t.identifier(propName)),
[t.spreadElement(t.identifier(argsBinding.name))],
);
} else {
const method = thisEnvFn.scope.generateUidIdentifier("prop");
// (method, ...args) => super[method](...args)
argsList.unshift(method);
fnBody = t.callExpression(
t.memberExpression(
t.super(),
t.identifier(method.name),
true /* computed */,
),
[t.spreadElement(t.identifier(argsBinding.name))],
);
}

return t.arrowFunctionExpression(argsList, fnBody);
});
}

// Create a binding for a function that will call "super.foo" or "super[foo]".
function getSuperPropBinding(thisEnvFn, isAssignment, propName) {
const op = isAssignment ? "set" : "get";
Expand Down
34 changes: 29 additions & 5 deletions packages/babel-traverse/test/arrow-transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ describe("arrow function conversion", () => {
);
});

it("should convert super.prop() calls", () => {
it("should convert super.prop() calls without params", () => {
assertConversion(
`
() => {
Expand All @@ -549,17 +549,40 @@ describe("arrow function conversion", () => {
() => super.foo();
`,
`
var _superprop_callFoo = (..._args) => super.foo(..._args);
var _superprop_getFoo = () => super.foo,
_this = this;
(function () {
_superprop_callFoo();
_superprop_getFoo().call(_this);
});
super.foo();
() => super.foo();
`,
);
});

it("should convert super.prop() calls with params", () => {
assertConversion(
`
() => {
super.foo(a, b, ...c);
};
super.foo(a, b, ...c);
() => super.foo(a, b, ...c);
`,
`
var _superprop_getFoo = () => super.foo,
_this = this;
(function () {
_superprop_getFoo().call(_this, a, b, ...c);
});
super.foo(a, b, ...c);
() => super.foo(a, b, ...c);
`,
);
});

it("should convert super[prop]() calls", () => {
assertConversion(
`
Expand All @@ -570,10 +593,11 @@ describe("arrow function conversion", () => {
() => super[foo]();
`,
`
var _superprop_call = (_prop, ..._args) => super[_prop](..._args);
var _superprop_get = _prop => super[_prop],
_this = this;
(function () {
_superprop_call(foo);
_superprop_get(foo).call(_this);
});
super[foo]();
() => super[foo]();
Expand Down

0 comments on commit 99035ca

Please sign in to comment.