* Optimize `transform-async-to-generator` output - remove wrapper if the function length is zero. - remove wrapper if the `assumptions.ignoreFunctionLength` is `true`. * chore: update test case and code style * chore: add test
153 lines
3.8 KiB
TypeScript
153 lines
3.8 KiB
TypeScript
import type { NodePath } from "@babel/traverse";
|
|
import nameFunction from "@babel/helper-function-name";
|
|
import template from "@babel/template";
|
|
import {
|
|
blockStatement,
|
|
callExpression,
|
|
functionExpression,
|
|
isAssignmentPattern,
|
|
isRestElement,
|
|
returnStatement,
|
|
} from "@babel/types";
|
|
import type * as t from "@babel/types";
|
|
|
|
const buildAnonymousExpressionWrapper = template.expression(`
|
|
(function () {
|
|
var REF = FUNCTION;
|
|
return function NAME(PARAMS) {
|
|
return REF.apply(this, arguments);
|
|
};
|
|
})()
|
|
`);
|
|
|
|
const buildNamedExpressionWrapper = template.expression(`
|
|
(function () {
|
|
var REF = FUNCTION;
|
|
function NAME(PARAMS) {
|
|
return REF.apply(this, arguments);
|
|
}
|
|
return NAME;
|
|
})()
|
|
`);
|
|
|
|
const buildDeclarationWrapper = template(`
|
|
function NAME(PARAMS) { return REF.apply(this, arguments); }
|
|
function REF() {
|
|
REF = FUNCTION;
|
|
return REF.apply(this, arguments);
|
|
}
|
|
`);
|
|
|
|
function classOrObjectMethod(
|
|
path: NodePath<t.ClassMethod | t.ClassPrivateMethod | t.ObjectMethod>,
|
|
callId: any,
|
|
) {
|
|
const node = path.node;
|
|
const body = node.body;
|
|
|
|
const container = functionExpression(
|
|
null,
|
|
[],
|
|
blockStatement(body.body),
|
|
true,
|
|
);
|
|
body.body = [
|
|
returnStatement(callExpression(callExpression(callId, [container]), [])),
|
|
];
|
|
|
|
// Regardless of whether or not the wrapped function is a an async method
|
|
// or generator the outer function should not be
|
|
node.async = false;
|
|
node.generator = false;
|
|
|
|
// Unwrap the wrapper IIFE's environment so super and this and such still work.
|
|
(
|
|
path.get("body.body.0.argument.callee.arguments.0") as NodePath
|
|
).unwrapFunctionEnvironment();
|
|
}
|
|
|
|
function plainFunction(
|
|
path: NodePath<any>,
|
|
callId: any,
|
|
noNewArrows: boolean,
|
|
ignoreFunctionLength: boolean,
|
|
) {
|
|
const node = path.node;
|
|
const isDeclaration = path.isFunctionDeclaration();
|
|
const functionId = node.id;
|
|
const wrapper = isDeclaration
|
|
? buildDeclarationWrapper
|
|
: functionId
|
|
? buildNamedExpressionWrapper
|
|
: buildAnonymousExpressionWrapper;
|
|
|
|
if (path.isArrowFunctionExpression()) {
|
|
path.arrowFunctionToExpression({ noNewArrows });
|
|
}
|
|
|
|
node.id = null;
|
|
|
|
if (isDeclaration) {
|
|
node.type = "FunctionExpression";
|
|
}
|
|
|
|
const built = callExpression(callId, [node]);
|
|
|
|
const params: t.Identifier[] = [];
|
|
for (const param of node.params) {
|
|
if (isAssignmentPattern(param) || isRestElement(param)) {
|
|
break;
|
|
}
|
|
params.push(path.scope.generateUidIdentifier("x"));
|
|
}
|
|
|
|
const container = wrapper({
|
|
NAME: functionId || null,
|
|
REF: path.scope.generateUidIdentifier(functionId ? functionId.name : "ref"),
|
|
FUNCTION: built,
|
|
PARAMS: params,
|
|
});
|
|
|
|
if (isDeclaration) {
|
|
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) {
|
|
nameFunction({
|
|
node: retFunction,
|
|
parent: path.parent,
|
|
scope: path.scope,
|
|
});
|
|
}
|
|
|
|
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
|
|
path.replaceWith(container);
|
|
} else {
|
|
// we can omit this wrapper as the conditions it protects for do not apply
|
|
path.replaceWith(built);
|
|
}
|
|
}
|
|
}
|
|
|
|
export default function wrapFunction(
|
|
path: NodePath,
|
|
callId: any,
|
|
// TODO(Babel 8): Consider defaulting to false for spec compliancy
|
|
noNewArrows: boolean = true,
|
|
ignoreFunctionLength: boolean = false,
|
|
) {
|
|
if (path.isMethod()) {
|
|
classOrObjectMethod(path, callId);
|
|
} else {
|
|
plainFunction(path, callId, noNewArrows, ignoreFunctionLength);
|
|
}
|
|
}
|