Skip to content

Commit

Permalink
Implement newableArrowFunctions assumption
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Jan 12, 2021
1 parent caa4fda commit bd90382
Show file tree
Hide file tree
Showing 35 changed files with 157 additions and 25 deletions.
1 change: 1 addition & 0 deletions packages/babel-core/src/config/validation/options.js
Expand Up @@ -337,6 +337,7 @@ export const assumptionsNames = new Set<string>([
"ignoreToPrimitiveHint",
"iterableIsArray",
"mutableTemplateObject",
"newableArrowFunctions",
"noDocumentAll",
"pureGetters",
"setClassMethods",
Expand Down
3 changes: 2 additions & 1 deletion packages/babel-helper-remap-async-to-generator/src/index.js
Expand Up @@ -31,6 +31,7 @@ const awaitVisitor = {
export default function (
path: NodePath,
helpers: { wrapAsync: Object, wrapAwait: Object },
newableArrowFunctions?: boolean,
) {
path.traverse(awaitVisitor, {
wrapAwait: helpers.wrapAwait,
Expand All @@ -41,7 +42,7 @@ export default function (
path.node.async = false;
path.node.generator = true;

wrapFunction(path, t.cloneNode(helpers.wrapAsync));
wrapFunction(path, t.cloneNode(helpers.wrapAsync), newableArrowFunctions);

const isProperty =
path.isObjectMethod() ||
Expand Down
17 changes: 13 additions & 4 deletions packages/babel-helper-wrap-function/src/index.js
Expand Up @@ -57,7 +57,11 @@ function classOrObjectMethod(path: NodePath, callId: Object) {
.unwrapFunctionEnvironment();
}

function plainFunction(path: NodePath, callId: Object) {
function plainFunction(
path: NodePath,
callId: Object,
newableArrowFunctions: boolean,
) {
const node = path.node;
const isDeclaration = path.isFunctionDeclaration();
const functionId = node.id;
Expand All @@ -68,7 +72,7 @@ function plainFunction(path: NodePath, callId: Object) {
: buildAnonymousExpressionWrapper;

if (path.isArrowFunctionExpression()) {
path.arrowFunctionToExpression();
path.arrowFunctionToExpression({ newableArrowFunctions });
}

node.id = null;
Expand Down Expand Up @@ -123,10 +127,15 @@ function plainFunction(path: NodePath, callId: Object) {
}
}

export default function wrapFunction(path: NodePath, callId: Object) {
export default function wrapFunction(
path: NodePath,
callId: Object,
// TODO(Babel 8): Consider defaulting to false for spec compliancy
newableArrowFunctions: boolean = true,
) {
if (path.isMethod()) {
classOrObjectMethod(path, callId);
} else {
plainFunction(path, callId);
plainFunction(path, callId, newableArrowFunctions);
}
}
Expand Up @@ -70,6 +70,8 @@ export default declare(api => {

path.traverse(yieldStarVisitor, state);

// We don't need to pass the newableArrowFunctions assumption, since
// async generators are never arrow functions.
remapAsyncToGenerator(path, {
wrapAsync: state.addHelper("wrapAsyncGenerator"),
wrapAwait: state.addHelper("awaitAsyncGenerator"),
Expand Down
9 changes: 7 additions & 2 deletions packages/babel-plugin-transform-arrow-functions/src/index.js
Expand Up @@ -4,7 +4,9 @@ import type NodePath from "@babel/traverse";
export default declare((api, options) => {
api.assertVersion(7);

const { spec } = options;
const newableArrowFunctions =
api.assumption("newableArrowFunctions") ?? !options.spec;

return {
name: "transform-arrow-functions",

Expand All @@ -20,7 +22,10 @@ export default declare((api, options) => {
// While other utils may be fine inserting other arrows to make more transforms possible,
// the arrow transform itself absolutely cannot insert new arrow functions.
allowInsertArrow: false,
specCompliant: !!spec,
newableArrowFunctions,

// TODO(Babel 8): This is only needed for backward compat with @babel/traverse <7.13.0
specCompliant: !newableArrowFunctions,
});
},
},
Expand Down

This file was deleted.

@@ -0,0 +1,10 @@
function foo() {
arr.map(x => x * x);
var f = (x, y) => x * y;
(function () {
return () => this;
})();
return {
g: () => this
}
}
@@ -0,0 +1,29 @@
function foo() {
var _this = this;

arr.map(function (x) {
babelHelpers.newArrowCheck(this, _this);
return x * x;
}.bind(this));

var f = function f(x, y) {
babelHelpers.newArrowCheck(this, _this);
return x * y;
}.bind(this);

(function () {
var _this2 = this;

return function () {
babelHelpers.newArrowCheck(this, _this2);
return this;
}.bind(this);
})();

return {
g: function g() {
babelHelpers.newArrowCheck(this, _this);
return this;
}.bind(this)
};
}
@@ -0,0 +1,6 @@
{
"plugins": ["external-helpers", "transform-arrow-functions"],
"assumptions": {
"newableArrowFunctions": false
}
}
@@ -0,0 +1 @@
let a = () => 1;
@@ -0,0 +1,6 @@
var _this = this;

let a = function a() {
babelHelpers.newArrowCheck(this, _this);
return 1;
}.bind(this);
@@ -0,0 +1 @@
let a = () => 1;
@@ -0,0 +1,9 @@
{
"plugins": [
"external-helpers",
["transform-arrow-functions", { "spec": false }]
],
"assumptions": {
"newableArrowFunctions": false
}
}
@@ -0,0 +1,6 @@
var _this = this;

let a = function a() {
babelHelpers.newArrowCheck(this, _this);
return 1;
}.bind(this);
@@ -0,0 +1 @@
let a = () => 1;
@@ -0,0 +1,9 @@
{
"plugins": [
"external-helpers",
["transform-arrow-functions", { "spec": true }]
],
"assumptions": {
"newableArrowFunctions": true
}
}
@@ -0,0 +1,3 @@
let a = function () {
return 1;
};
11 changes: 7 additions & 4 deletions packages/babel-plugin-transform-async-to-generator/src/index.js
Expand Up @@ -7,6 +7,7 @@ export default declare((api, options) => {
api.assertVersion(7);

const { method, module } = options;
const newableArrowFunctions = api.assumption("newableArrowFunctions");

if (method && module) {
return {
Expand All @@ -23,7 +24,7 @@ export default declare((api, options) => {
wrapAsync = state.methodWrapper = addNamed(path, method, module);
}

remapAsyncToGenerator(path, { wrapAsync });
remapAsyncToGenerator(path, { wrapAsync }, newableArrowFunctions);
},
},
};
Expand All @@ -36,9 +37,11 @@ export default declare((api, options) => {
Function(path, state) {
if (!path.node.async || path.node.generator) return;

remapAsyncToGenerator(path, {
wrapAsync: state.addHelper("asyncToGenerator"),
});
remapAsyncToGenerator(
path,
{ wrapAsync: state.addHelper("asyncToGenerator") },
newableArrowFunctions,
);
},
},
};
Expand Down
@@ -0,0 +1 @@
async () => 2;
@@ -0,0 +1,6 @@
{
"plugins": ["external-helpers", "transform-async-to-generator"],
"assumptions": {
"newableArrowFunctions": false
}
}
@@ -0,0 +1,7 @@
var _this = this;

/*#__PURE__*/
babelHelpers.asyncToGenerator(function* () {
babelHelpers.newArrowCheck(this, _this);
return 2;
});
@@ -0,0 +1,12 @@
{
"plugins": [
"external-helpers",
[
"transform-async-to-generator",
{ "module": "bluebird", "method": "coroutine" }
]
],
"assumptions": {
"newableArrowFunctions": true
}
}
1 change: 1 addition & 0 deletions packages/babel-plugin-transform-classes/src/index.js
Expand Up @@ -68,6 +68,7 @@ export default declare((api, options) => {
if (path.isCallExpression()) {
annotateAsPure(path);
if (path.get("callee").isArrowFunctionExpression()) {
// This is an IIFE, so we don't need to worry about the newableArrowFunctions assumption
path.get("callee").arrowFunctionToExpression();
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/babel-plugin-transform-parameters/src/index.js
Expand Up @@ -8,6 +8,7 @@ export default declare((api, options) => {

const ignoreFunctionLength =
api.assumption("ignoreFunctionLength") ?? options.loose;
const newableArrowFunctions = api.assumption("newableArrowFunctions");

return {
name: "transform-parameters",
Expand All @@ -21,7 +22,7 @@ export default declare((api, options) => {
.some(param => param.isRestElement() || param.isAssignmentPattern())
) {
// default/rest visitors require access to `arguments`, so it cannot be an arrow
path.arrowFunctionToExpression();
path.arrowFunctionToExpression({ newableArrowFunctions });
}

const convertedRest = convertFunctionRest(path);
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-plugin-transform-parameters/src/params.js
Expand Up @@ -206,6 +206,8 @@ export default function convertFunctionParams(
// sure that we correctly handle this and arguments.
const bodyPath = path.get("body.body");
const arrowPath = bodyPath[bodyPath.length - 1].get("argument.callee");

// This is an IIFE, so we don't need to worry about the newableArrowFunctions assumption
arrowPath.arrowFunctionToExpression();

arrowPath.node.generator = path.node.generator;
Expand Down
17 changes: 11 additions & 6 deletions packages/babel-traverse/src/path/conversion.js
Expand Up @@ -72,6 +72,7 @@ export function ensureBlock() {
/**
* Keeping this for backward-compatibility. You should use arrowFunctionToExpression() for >=7.x.
*/
// TODO(Babel 8): Remove this
export function arrowFunctionToShadowed() {
if (!this.isArrowFunctionExpression()) return;

Expand Down Expand Up @@ -103,7 +104,10 @@ export function unwrapFunctionEnvironment() {
*/
export function arrowFunctionToExpression({
allowInsertArrow = true,
/** @deprecated Use `newableArrowFunctions` instead */
specCompliant = false,
// TODO(Babel 8): Consider defaulting to `false` for spec compliancy
newableArrowFunctions = !specCompliant,
} = {}) {
if (!this.isArrowFunctionExpression()) {
throw this.buildCodeFrameError(
Expand All @@ -113,13 +117,13 @@ export function arrowFunctionToExpression({

const thisBinding = hoistFunctionEnvironment(
this,
specCompliant,
newableArrowFunctions,
allowInsertArrow,
);

this.ensureBlock();
this.node.type = "FunctionExpression";
if (specCompliant) {
if (!newableArrowFunctions) {
const checkBinding = thisBinding
? null
: this.parentPath.scope.generateUidIdentifier("arrowCheckId");
Expand Down Expand Up @@ -160,7 +164,8 @@ export function arrowFunctionToExpression({
*/
function hoistFunctionEnvironment(
fnPath,
specCompliant = false,
// TODO(Babel 8): Consider defaulting to `false` for spec compliancy
newableArrowFunctions = true,
allowInsertArrow = true,
) {
const thisEnvFn = fnPath.findParent(p => {
Expand Down Expand Up @@ -298,11 +303,11 @@ function hoistFunctionEnvironment(

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

if (
!specCompliant ||
newableArrowFunctions ||
// 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))
Expand All @@ -316,7 +321,7 @@ function hoistFunctionEnvironment(
thisChild.replaceWith(thisRef);
});

if (specCompliant) thisBinding = null;
if (!newableArrowFunctions) thisBinding = null;
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/babel-traverse/src/path/replacement.js
Expand Up @@ -260,6 +260,8 @@ export function replaceExpressionWithStatements(nodes: Array<Object>) {
}

const callee = this.get("callee");

// This is an IIFE, so we don't need to worry about the newableArrowFunctions assumption
callee.arrowFunctionToExpression();

// (() => await xxx)() -> await (async () => await xxx)();
Expand Down

0 comments on commit bd90382

Please sign in to comment.