From 4f278dc05a85da3ce39687f59691e23c1c66877b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Mon, 18 Jul 2022 16:27:53 +0200 Subject: [PATCH] Fix compiling async arrows in uncompiled class fields (#14752) * Refactor helper-wrap-function for better type inference * Fix compiling async arrows in uncompiled class fields --- .../babel-helper-wrap-function/src/index.ts | 86 +++++++++++-------- .../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, 101 insertions(+), 38 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 d9f84c2fc6d1..744e238457bd 100644 --- a/packages/babel-helper-wrap-function/src/index.ts +++ b/packages/babel-helper-wrap-function/src/index.ts @@ -6,11 +6,29 @@ import { callExpression, functionExpression, isAssignmentPattern, + 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 +36,9 @@ const buildAnonymousExpressionWrapper = template.expression(` return REF.apply(this, arguments); }; })() -`); +`) as ExpressionWrapperBuilder< + [t.ReturnStatement & { argument: t.FunctionExpression }] +>; const buildNamedExpressionWrapper = template.expression(` (function () { @@ -28,7 +48,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 +94,23 @@ 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; - + let functionId = null; + let node; if (path.isArrowFunctionExpression()) { - path.arrowFunctionToExpression({ noNewArrows }); + path = path.arrowFunctionToExpression({ noNewArrows }); + node = path.node as t.FunctionDeclaration | t.FunctionExpression; + } else { + node = path.node as t.FunctionDeclaration | t.FunctionExpression; } - // @ts-expect-error node is FunctionDeclaration|FunctionExpression - node.id = null; - if (isDeclaration) { - node.type = "FunctionExpression"; - } + const isDeclaration = isFunctionDeclaration(node); + + 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 +121,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 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<{