From 3dc412580a396936bede8b316e054129543f4893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 13 Jul 2022 15:12:14 +0200 Subject: [PATCH 1/2] Refactor helper-wrap-function for better type inference --- .../babel-helper-wrap-function/src/index.ts | 85 +++++++++++-------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/packages/babel-helper-wrap-function/src/index.ts b/packages/babel-helper-wrap-function/src/index.ts index d9f84c2fc6d1..00b0f1dd27bf 100644 --- a/packages/babel-helper-wrap-function/src/index.ts +++ b/packages/babel-helper-wrap-function/src/index.ts @@ -6,11 +6,30 @@ import { callExpression, functionExpression, isAssignmentPattern, + isArrowFunctionExpression, + isFunctionDeclaration, isRestElement, returnStatement, } from "@babel/types"; import type * as t from "@babel/types"; +type ExpressionWrapperBuilder = ( + replacements?: Parameters>[0], +) => t.CallExpression & { + callee: t.FunctionExpression & { + body: { + body: [ + t.VariableDeclaration & { + declarations: [ + { init: t.FunctionExpression | t.ArrowFunctionExpression }, + ]; + }, + ...ExtraBody, + ]; + }; + }; +}; + const buildAnonymousExpressionWrapper = template.expression(` (function () { var REF = FUNCTION; @@ -18,7 +37,9 @@ const buildAnonymousExpressionWrapper = template.expression(` return REF.apply(this, arguments); }; })() -`); +`) as ExpressionWrapperBuilder< + [t.ReturnStatement & { argument: t.FunctionExpression }] +>; const buildNamedExpressionWrapper = template.expression(` (function () { @@ -28,7 +49,9 @@ const buildNamedExpressionWrapper = template.expression(` } return NAME; })() -`); +`) as ExpressionWrapperBuilder< + [t.FunctionDeclaration, t.ReturnStatement & { argument: t.Identifier }] +>; const buildDeclarationWrapper = template.statements(` function NAME(PARAMS) { return REF.apply(this, arguments); } @@ -72,28 +95,21 @@ function plainFunction( noNewArrows: boolean, ignoreFunctionLength: boolean, ) { - const node = path.node; - const isDeclaration = path.isFunctionDeclaration(); - // @ts-expect-error id is not in ArrowFunctionExpression - const functionId = node.id; - const wrapper = isDeclaration - ? buildDeclarationWrapper - : functionId - ? buildNamedExpressionWrapper - : buildAnonymousExpressionWrapper; - - if (path.isArrowFunctionExpression()) { - path.arrowFunctionToExpression({ noNewArrows }); - } - // @ts-expect-error node is FunctionDeclaration|FunctionExpression - node.id = null; + const { node } = path; + const isDeclaration = isFunctionDeclaration(node); + const isArrow = isArrowFunctionExpression(node); - if (isDeclaration) { + let functionId = null; + if (isArrow) { + (path as NodePath).arrowFunctionToExpression({ noNewArrows }); + } else { + functionId = node.id; + node.id = null; node.type = "FunctionExpression"; } const built = callExpression(callId, [ - node as Exclude, + node as Exclude, ]); const params: t.Identifier[] = []; @@ -104,34 +120,35 @@ function plainFunction( params.push(path.scope.generateUidIdentifier("x")); } - const container = wrapper({ + const wrapperArgs = { NAME: functionId || null, REF: path.scope.generateUidIdentifier(functionId ? functionId.name : "ref"), FUNCTION: built, PARAMS: params, - }); + }; if (isDeclaration) { - path.replaceWith((container as t.Statement[])[0]); - path.insertAfter((container as t.Statement[])[1]); + const container = buildDeclarationWrapper(wrapperArgs); + path.replaceWith(container[0]); + path.insertAfter(container[1]); } else { - // @ts-expect-error todo(flow->ts) separate `wrapper` for `isDeclaration` and `else` branches - const retFunction = container.callee.body.body[1].argument; - if (!functionId) { + let container; + + if (functionId) { + container = buildNamedExpressionWrapper(wrapperArgs); + } else { + container = buildAnonymousExpressionWrapper(wrapperArgs); + + const returnFn = container.callee.body.body[1].argument; nameFunction({ - node: retFunction, + node: returnFn, parent: path.parent, scope: path.scope, }); + functionId = returnFn.id; } - if ( - !retFunction || - retFunction.id || - (!ignoreFunctionLength && params.length) - ) { - // we have an inferred function id or params so we need this wrapper - // @ts-expect-error todo(flow->ts) separate `wrapper` for `isDeclaration` and `else` branches + if (functionId || (!ignoreFunctionLength && params.length)) { path.replaceWith(container); } else { // we can omit this wrapper as the conditions it protects for do not apply From 592e27f043315fead8c1b1c4a39d1acae0897ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 13 Jul 2022 15:38:51 +0200 Subject: [PATCH 2/2] Fix compiling async arrows in uncompiled class fields --- .../babel-helper-wrap-function/src/index.ts | 21 ++++++++-------- .../basic/exec.js | 0 .../basic/input.js | 0 .../basic/options.json | 0 .../basic/output.js | 2 +- .../bluebird/input.js | 0 .../bluebird/options.json | 0 .../in-uncompiled-class-fields/input.js | 8 ++++++ .../in-uncompiled-class-fields/output.js | 25 +++++++++++++++++++ .../babel-traverse/src/path/conversion.ts | 18 ++++++++++--- 10 files changed, 60 insertions(+), 14 deletions(-) rename packages/babel-plugin-transform-async-to-generator/test/fixtures/{assumption-newableArrowFunctions-false => assumption-noNewArrows-false}/basic/exec.js (100%) rename packages/babel-plugin-transform-async-to-generator/test/fixtures/{assumption-newableArrowFunctions-false => assumption-noNewArrows-false}/basic/input.js (100%) rename packages/babel-plugin-transform-async-to-generator/test/fixtures/{assumption-newableArrowFunctions-false => assumption-noNewArrows-false}/basic/options.json (100%) rename packages/babel-plugin-transform-async-to-generator/test/fixtures/{assumption-newableArrowFunctions-false => assumption-noNewArrows-false}/basic/output.js (89%) rename packages/babel-plugin-transform-async-to-generator/test/fixtures/{assumption-newableArrowFunctions-false => assumption-noNewArrows-false}/bluebird/input.js (100%) rename packages/babel-plugin-transform-async-to-generator/test/fixtures/{assumption-newableArrowFunctions-false => assumption-noNewArrows-false}/bluebird/options.json (100%) create mode 100644 packages/babel-plugin-transform-async-to-generator/test/fixtures/regression/in-uncompiled-class-fields/input.js create mode 100644 packages/babel-plugin-transform-async-to-generator/test/fixtures/regression/in-uncompiled-class-fields/output.js diff --git a/packages/babel-helper-wrap-function/src/index.ts b/packages/babel-helper-wrap-function/src/index.ts index 00b0f1dd27bf..744e238457bd 100644 --- a/packages/babel-helper-wrap-function/src/index.ts +++ b/packages/babel-helper-wrap-function/src/index.ts @@ -6,7 +6,6 @@ import { callExpression, functionExpression, isAssignmentPattern, - isArrowFunctionExpression, isFunctionDeclaration, isRestElement, returnStatement, @@ -95,19 +94,21 @@ function plainFunction( noNewArrows: boolean, ignoreFunctionLength: boolean, ) { - const { node } = path; - const isDeclaration = isFunctionDeclaration(node); - const isArrow = isArrowFunctionExpression(node); - let functionId = null; - if (isArrow) { - (path as NodePath).arrowFunctionToExpression({ noNewArrows }); + let node; + if (path.isArrowFunctionExpression()) { + path = path.arrowFunctionToExpression({ noNewArrows }); + node = path.node as t.FunctionDeclaration | t.FunctionExpression; } else { - functionId = node.id; - node.id = null; - node.type = "FunctionExpression"; + node = path.node as t.FunctionDeclaration | t.FunctionExpression; } + const isDeclaration = isFunctionDeclaration(node); + + functionId = node.id; + node.id = null; + node.type = "FunctionExpression"; + const built = callExpression(callId, [ node as Exclude, ]); diff --git a/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/basic/exec.js b/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/basic/exec.js similarity index 100% rename from packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/basic/exec.js rename to packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/basic/exec.js diff --git a/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/basic/input.js b/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/basic/input.js similarity index 100% rename from packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/basic/input.js rename to packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/basic/input.js diff --git a/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/basic/options.json b/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/basic/options.json similarity index 100% rename from packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/basic/options.json rename to packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/basic/options.json diff --git a/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/basic/output.js b/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/basic/output.js similarity index 89% rename from packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/basic/output.js rename to packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/basic/output.js index c0d2e6069da5..6c6182babb06 100644 --- a/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/basic/output.js +++ b/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/basic/output.js @@ -4,4 +4,4 @@ var _this = this; babelHelpers.asyncToGenerator(function* () { babelHelpers.newArrowCheck(this, _this); return 2; -}); +}).bind(this); diff --git a/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/bluebird/input.js b/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/bluebird/input.js similarity index 100% rename from packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/bluebird/input.js rename to packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/bluebird/input.js diff --git a/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/bluebird/options.json b/packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/bluebird/options.json similarity index 100% rename from packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-newableArrowFunctions-false/bluebird/options.json rename to packages/babel-plugin-transform-async-to-generator/test/fixtures/assumption-noNewArrows-false/bluebird/options.json diff --git a/packages/babel-plugin-transform-async-to-generator/test/fixtures/regression/in-uncompiled-class-fields/input.js b/packages/babel-plugin-transform-async-to-generator/test/fixtures/regression/in-uncompiled-class-fields/input.js new file mode 100644 index 000000000000..e62d139684bb --- /dev/null +++ b/packages/babel-plugin-transform-async-to-generator/test/fixtures/regression/in-uncompiled-class-fields/input.js @@ -0,0 +1,8 @@ +// Angular needs to alwys compile async functions, even if +// the targets support class fields. +// https://github.com/babel/babel/issues/14749 + +class A { + a = async () => this; + b = async (x, y, z) => this; +} diff --git a/packages/babel-plugin-transform-async-to-generator/test/fixtures/regression/in-uncompiled-class-fields/output.js b/packages/babel-plugin-transform-async-to-generator/test/fixtures/regression/in-uncompiled-class-fields/output.js new file mode 100644 index 000000000000..64bff6ab2dc4 --- /dev/null +++ b/packages/babel-plugin-transform-async-to-generator/test/fixtures/regression/in-uncompiled-class-fields/output.js @@ -0,0 +1,25 @@ +// Angular needs to alwys compile async functions, even if +// the targets support class fields. +// https://github.com/babel/babel/issues/14749 +class A { + a = (() => { + var _this = this; + + return babelHelpers.asyncToGenerator(function* () { + return _this; + }); + })(); + b = (() => { + var _this2 = this; + + return function () { + var _ref2 = babelHelpers.asyncToGenerator(function* (x, y, z) { + return _this2; + }); + + return function (_x, _x2, _x3) { + return _ref2.apply(this, arguments); + }; + }(); + })(); +} diff --git a/packages/babel-traverse/src/path/conversion.ts b/packages/babel-traverse/src/path/conversion.ts index d5156950d232..1352f6a04c73 100644 --- a/packages/babel-traverse/src/path/conversion.ts +++ b/packages/babel-traverse/src/path/conversion.ts @@ -135,6 +135,13 @@ export function unwrapFunctionEnvironment(this: NodePath) { hoistFunctionEnvironment(this); } +function setType( + path: NodePath, + type: T, +): asserts path is NodePath> { + path.node.type = type; +} + /** * Convert a given arrow function into a normal ES5 function expression. */ @@ -151,7 +158,7 @@ export function arrowFunctionToExpression( specCompliant?: boolean | void; noNewArrows?: boolean; } = {}, -) { +): NodePath> { if (!this.isArrowFunctionExpression()) { throw (this as NodePath).buildCodeFrameError( "Cannot convert non-arrow function to a function expression.", @@ -166,7 +173,8 @@ export function arrowFunctionToExpression( // @ts-expect-error TS requires explicit fn type annotation fn.ensureBlock(); - fn.node.type = "FunctionExpression"; + setType(fn, "FunctionExpression"); + if (!noNewArrows) { const checkBinding = thisBinding ? null @@ -178,7 +186,7 @@ export function arrowFunctionToExpression( }); } - (fn.get("body") as NodePath).unshiftContainer( + fn.get("body").unshiftContainer( "body", expressionStatement( callExpression(this.hub.addHelper("newArrowCheck"), [ @@ -200,7 +208,11 @@ export function arrowFunctionToExpression( [checkBinding ? identifier(checkBinding.name) : thisExpression()], ), ); + + return fn.get("callee.object"); } + + return fn; } const getSuperCallsVisitor = mergeVisitors<{