Minor fixes to "Convert To Async" refactor (#45536)
* Minor fixes to convertToAsync * Back out on nested return in inner continuation * Baseline update * Verify type argument for call can be used, add a few more early exit shortcuts
This commit is contained in:
parent
f9a3d85b00
commit
6f7f3b1775
|
@ -634,6 +634,8 @@ namespace ts {
|
|||
getESSymbolType: () => esSymbolType,
|
||||
getNeverType: () => neverType,
|
||||
getOptionalType: () => optionalType,
|
||||
getPromiseType: () => getGlobalPromiseType(/*reportErrors*/ false),
|
||||
getPromiseLikeType: () => getGlobalPromiseLikeType(/*reportErrors*/ false),
|
||||
isSymbolAccessible,
|
||||
isArrayType,
|
||||
isTupleType,
|
||||
|
|
|
@ -4939,14 +4939,16 @@ namespace ts {
|
|||
}
|
||||
|
||||
// @api
|
||||
function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block) {
|
||||
function createCatchClause(variableDeclaration: string | BindingName | VariableDeclaration | undefined, block: Block) {
|
||||
const node = createBaseNode<CatchClause>(SyntaxKind.CatchClause);
|
||||
variableDeclaration = !isString(variableDeclaration) ? variableDeclaration : createVariableDeclaration(
|
||||
variableDeclaration,
|
||||
/*exclamationToken*/ undefined,
|
||||
/*type*/ undefined,
|
||||
/*initializer*/ undefined
|
||||
);
|
||||
if (typeof variableDeclaration === "string" || variableDeclaration && !isVariableDeclaration(variableDeclaration)) {
|
||||
variableDeclaration = createVariableDeclaration(
|
||||
variableDeclaration,
|
||||
/*exclamationToken*/ undefined,
|
||||
/*type*/ undefined,
|
||||
/*initializer*/ undefined
|
||||
);
|
||||
}
|
||||
node.variableDeclaration = variableDeclaration;
|
||||
node.block = block;
|
||||
node.transformFlags |=
|
||||
|
|
|
@ -4269,6 +4269,8 @@ namespace ts {
|
|||
/* @internal */ createArrayType(elementType: Type): Type;
|
||||
/* @internal */ getElementTypeOfArrayType(arrayType: Type): Type | undefined;
|
||||
/* @internal */ createPromiseType(type: Type): Type;
|
||||
/* @internal */ getPromiseType(): Type;
|
||||
/* @internal */ getPromiseLikeType(): Type;
|
||||
|
||||
/* @internal */ isTypeAssignableTo(source: Type, target: Type): boolean;
|
||||
/* @internal */ createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], indexInfos: IndexInfo[]): Type;
|
||||
|
@ -7454,7 +7456,7 @@ namespace ts {
|
|||
updateDefaultClause(node: DefaultClause, statements: readonly Statement[]): DefaultClause;
|
||||
createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]): HeritageClause;
|
||||
updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]): HeritageClause;
|
||||
createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block): CatchClause;
|
||||
createCatchClause(variableDeclaration: string | BindingName | VariableDeclaration | undefined, block: Block): CatchClause;
|
||||
updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block): CatchClause;
|
||||
|
||||
//
|
||||
|
|
|
@ -1364,7 +1364,7 @@ namespace ts {
|
|||
|
||||
// Warning: This has the same semantics as the forEach family of functions,
|
||||
// in that traversal terminates in the event that 'visitor' supplies a truthy value.
|
||||
export function forEachReturnStatement<T>(body: Block, visitor: (stmt: ReturnStatement) => T): T | undefined {
|
||||
export function forEachReturnStatement<T>(body: Block | Statement, visitor: (stmt: ReturnStatement) => T): T | undefined {
|
||||
|
||||
return traverse(body);
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace ts.codefix {
|
|||
readonly types: Type[];
|
||||
/** A declaration for this identifier has already been generated */
|
||||
hasBeenDeclared: boolean;
|
||||
hasBeenReferenced: boolean;
|
||||
}
|
||||
|
||||
interface Transformer {
|
||||
|
@ -43,6 +44,12 @@ namespace ts.codefix {
|
|||
readonly isInJSFile: boolean;
|
||||
}
|
||||
|
||||
interface PromiseReturningCallExpression<Name extends string> extends CallExpression {
|
||||
readonly expression: PropertyAccessExpression & {
|
||||
readonly escapedText: Name;
|
||||
};
|
||||
}
|
||||
|
||||
function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void {
|
||||
// get the function declaration - returns a promise
|
||||
const tokenAtPosition = getTokenAtPosition(sourceFile, position);
|
||||
|
@ -84,13 +91,22 @@ namespace ts.codefix {
|
|||
for (const returnStatement of returnStatements) {
|
||||
forEachChild(returnStatement, function visit(node) {
|
||||
if (isCallExpression(node)) {
|
||||
const newNodes = transformExpression(node, transformer);
|
||||
const newNodes = transformExpression(node, node, transformer, /*hasContinuation*/ false);
|
||||
if (hasFailed()) {
|
||||
return true; // return something truthy to shortcut out of more work
|
||||
}
|
||||
changes.replaceNodeWithNodes(sourceFile, returnStatement, newNodes);
|
||||
}
|
||||
else if (!isFunctionLike(node)) {
|
||||
forEachChild(node, visit);
|
||||
if (hasFailed()) {
|
||||
return true; // return something truthy to shortcut out of more work
|
||||
}
|
||||
}
|
||||
});
|
||||
if (hasFailed()) {
|
||||
return; // shortcut out of more work
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,9 +132,10 @@ namespace ts.codefix {
|
|||
setOfExpressionsToReturn.add(getNodeId(node));
|
||||
forEach(node.arguments, visit);
|
||||
}
|
||||
else if (isPromiseReturningCallExpression(node, checker, "catch")) {
|
||||
else if (isPromiseReturningCallExpression(node, checker, "catch") ||
|
||||
isPromiseReturningCallExpression(node, checker, "finally")) {
|
||||
setOfExpressionsToReturn.add(getNodeId(node));
|
||||
// if .catch() is the last call in the chain, move leftward in the chain until we hit something else that should be returned
|
||||
// if .catch() or .finally() is the last call in the chain, move leftward in the chain until we hit something else that should be returned
|
||||
forEachChild(node, visit);
|
||||
}
|
||||
else if (isPromiseTypedExpression(node, checker)) {
|
||||
|
@ -133,13 +150,48 @@ namespace ts.codefix {
|
|||
return setOfExpressionsToReturn;
|
||||
}
|
||||
|
||||
function isPromiseReturningCallExpression(node: Node, checker: TypeChecker, name: string): node is CallExpression {
|
||||
function isPromiseReturningCallExpression<Name extends string>(node: Node, checker: TypeChecker, name: Name): node is PromiseReturningCallExpression<Name> {
|
||||
if (!isCallExpression(node)) return false;
|
||||
const isExpressionOfName = hasPropertyAccessExpressionWithName(node, name);
|
||||
const nodeType = isExpressionOfName && checker.getTypeAtLocation(node);
|
||||
return !!(nodeType && checker.getPromisedTypeOfPromise(nodeType));
|
||||
}
|
||||
|
||||
// NOTE: this is a mostly copy of `isReferenceToType` from checker.ts. While this violates DRY, it keeps
|
||||
// `isReferenceToType` in checker local to the checker to avoid the cost of a property lookup on `ts`.
|
||||
function isReferenceToType(type: Type, target: Type) {
|
||||
return (getObjectFlags(type) & ObjectFlags.Reference) !== 0
|
||||
&& (type as TypeReference).target === target;
|
||||
}
|
||||
|
||||
function getExplicitPromisedTypeOfPromiseReturningCallExpression(node: PromiseReturningCallExpression<"then" | "catch" | "finally">, callback: Expression, checker: TypeChecker) {
|
||||
if (node.expression.name.escapedText === "finally") {
|
||||
// for a `finally`, there's no type argument
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If the call to `then` or `catch` comes from the global `Promise` or `PromiseLike` type, we can safely use the
|
||||
// type argument supplied for the callback. For other promise types we would need a more complex heuristic to determine
|
||||
// which type argument is safe to use as an annotation.
|
||||
const promiseType = checker.getTypeAtLocation(node.expression.expression);
|
||||
if (isReferenceToType(promiseType, checker.getPromiseType()) ||
|
||||
isReferenceToType(promiseType, checker.getPromiseLikeType())) {
|
||||
if (node.expression.name.escapedText === "then") {
|
||||
if (callback === elementAt(node.arguments, 0)) {
|
||||
// for the `onfulfilled` callback, use the first type argument
|
||||
return elementAt(node.typeArguments, 0);
|
||||
}
|
||||
else if (callback === elementAt(node.arguments, 1)) {
|
||||
// for the `onrejected` callback, use the second type argument
|
||||
return elementAt(node.typeArguments, 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return elementAt(node.typeArguments, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPromiseTypedExpression(node: Node, checker: TypeChecker): node is Expression {
|
||||
if (!isExpression(node)) return false;
|
||||
return !!checker.getPromisedTypeOfPromise(checker.getTypeAtLocation(node));
|
||||
|
@ -229,6 +281,10 @@ namespace ts.codefix {
|
|||
return createSynthIdentifier(identifier);
|
||||
}
|
||||
|
||||
function hasFailed() {
|
||||
return !codeActionSucceeded;
|
||||
}
|
||||
|
||||
function silentFail() {
|
||||
codeActionSucceeded = false;
|
||||
return emptyArray;
|
||||
|
@ -236,77 +292,40 @@ namespace ts.codefix {
|
|||
|
||||
// dispatch function to recursively build the refactoring
|
||||
// should be kept up to date with isFixablePromiseHandler in suggestionDiagnostics.ts
|
||||
function transformExpression(node: Expression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] {
|
||||
/**
|
||||
* @param hasContinuation Whether another `then`, `catch`, or `finally` continuation follows the continuation to which this expression belongs.
|
||||
* @param continuationArgName The argument name for the continuation that follows this call.
|
||||
*/
|
||||
function transformExpression(returnContextNode: Expression, node: Expression, transformer: Transformer, hasContinuation: boolean, continuationArgName?: SynthBindingName): readonly Statement[] {
|
||||
if (isPromiseReturningCallExpression(node, transformer.checker, "then")) {
|
||||
if (node.arguments.length === 0) return silentFail();
|
||||
return transformThen(node, transformer, prevArgName);
|
||||
return transformThen(node, elementAt(node.arguments, 0), elementAt(node.arguments, 1), transformer, hasContinuation, continuationArgName);
|
||||
}
|
||||
if (isPromiseReturningCallExpression(node, transformer.checker, "catch")) {
|
||||
return transformCatch(node, transformer, prevArgName);
|
||||
return transformCatch(node, elementAt(node.arguments, 0), transformer, hasContinuation, continuationArgName);
|
||||
}
|
||||
if (isPromiseReturningCallExpression(node, transformer.checker, "finally")) {
|
||||
return transformFinally(node, elementAt(node.arguments, 0), transformer, hasContinuation, continuationArgName);
|
||||
}
|
||||
if (isPropertyAccessExpression(node)) {
|
||||
return transformExpression(node.expression, transformer, prevArgName);
|
||||
return transformExpression(returnContextNode, node.expression, transformer, hasContinuation, continuationArgName);
|
||||
}
|
||||
|
||||
const nodeType = transformer.checker.getTypeAtLocation(node);
|
||||
if (nodeType && transformer.checker.getPromisedTypeOfPromise(nodeType)) {
|
||||
Debug.assertNode(node.original!.parent, isPropertyAccessExpression);
|
||||
return transformPromiseExpressionOfPropertyAccess(node, transformer, prevArgName);
|
||||
Debug.assertNode(getOriginalNode(node).parent, isPropertyAccessExpression);
|
||||
return transformPromiseExpressionOfPropertyAccess(returnContextNode, node, transformer, hasContinuation, continuationArgName);
|
||||
}
|
||||
|
||||
return silentFail();
|
||||
}
|
||||
|
||||
function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] {
|
||||
const func = singleOrUndefined(node.arguments);
|
||||
const argName = func ? getArgBindingName(func, transformer) : undefined;
|
||||
let possibleNameForVarDecl: SynthIdentifier | undefined;
|
||||
|
||||
// If there is another call in the chain after the .catch() we are transforming, we will need to save the result of both paths (try block and catch block)
|
||||
// To do this, we will need to synthesize a variable that we were not aware of while we were adding identifiers to the synthNamesMap
|
||||
// We will use the prevArgName and then update the synthNamesMap with a new variable name for the next transformation step
|
||||
if (prevArgName && !shouldReturn(node, transformer)) {
|
||||
if (isSynthIdentifier(prevArgName)) {
|
||||
possibleNameForVarDecl = prevArgName;
|
||||
transformer.synthNamesMap.forEach((val, key) => {
|
||||
if (val.identifier.text === prevArgName.identifier.text) {
|
||||
const newSynthName = createUniqueSynthName(prevArgName);
|
||||
transformer.synthNamesMap.set(key, newSynthName);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
possibleNameForVarDecl = createSynthIdentifier(factory.createUniqueName("result", GeneratedIdentifierFlags.Optimistic), prevArgName.types);
|
||||
}
|
||||
|
||||
// We are about to write a 'let' variable declaration, but `transformExpression` for both
|
||||
// the try block and catch block will assign to this name. Setting this flag indicates
|
||||
// that future assignments should be written as `name = value` instead of `const name = value`.
|
||||
possibleNameForVarDecl.hasBeenDeclared = true;
|
||||
function isNullOrUndefined({ checker }: Transformer, node: Expression) {
|
||||
if (node.kind === SyntaxKind.NullKeyword) return true;
|
||||
if (isIdentifier(node) && !isGeneratedIdentifier(node) && idText(node) === "undefined") {
|
||||
const symbol = checker.getSymbolAtLocation(node);
|
||||
return !symbol || checker.isUndefinedSymbol(symbol);
|
||||
}
|
||||
|
||||
const tryBlock = factory.createBlock(transformExpression(node.expression, transformer, possibleNameForVarDecl));
|
||||
const transformationBody = func ? getTransformationBody(func, possibleNameForVarDecl, argName, node, transformer) : emptyArray;
|
||||
const catchArg = argName ? isSynthIdentifier(argName) ? argName.identifier.text : argName.bindingPattern : "e";
|
||||
const catchVariableDeclaration = factory.createVariableDeclaration(catchArg);
|
||||
const catchClause = factory.createCatchClause(catchVariableDeclaration, factory.createBlock(transformationBody));
|
||||
|
||||
// In order to avoid an implicit any, we will synthesize a type for the declaration using the unions of the types of both paths (try block and catch block)
|
||||
let varDeclList: VariableStatement | undefined;
|
||||
let varDeclIdentifier: Identifier | undefined;
|
||||
if (possibleNameForVarDecl && !shouldReturn(node, transformer)) {
|
||||
varDeclIdentifier = getSynthesizedDeepClone(possibleNameForVarDecl.identifier);
|
||||
const typeArray: Type[] = possibleNameForVarDecl.types;
|
||||
const unionType = transformer.checker.getUnionType(typeArray, UnionReduction.Subtype);
|
||||
const unionTypeNode = transformer.isInJSFile ? undefined : transformer.checker.typeToTypeNode(unionType, /*enclosingDeclaration*/ undefined, /*flags*/ undefined);
|
||||
const varDecl = [factory.createVariableDeclaration(varDeclIdentifier, /*exclamationToken*/ undefined, unionTypeNode)];
|
||||
varDeclList = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList(varDecl, NodeFlags.Let));
|
||||
}
|
||||
|
||||
const tryStatement = factory.createTryStatement(tryBlock, catchClause, /*finallyBlock*/ undefined);
|
||||
const destructuredResult = prevArgName && varDeclIdentifier && isSynthBindingPattern(prevArgName)
|
||||
&& factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(getSynthesizedDeepClone(prevArgName.bindingPattern), /*exclamationToken*/ undefined, /*type*/ undefined, varDeclIdentifier)], NodeFlags.Const));
|
||||
return compact([varDeclList, tryStatement, destructuredResult]);
|
||||
return false;
|
||||
}
|
||||
|
||||
function createUniqueSynthName(prevArgName: SynthIdentifier): SynthIdentifier {
|
||||
|
@ -314,31 +333,168 @@ namespace ts.codefix {
|
|||
return createSynthIdentifier(renamedPrevArg);
|
||||
}
|
||||
|
||||
function transformThen(node: CallExpression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] {
|
||||
const [onFulfilled, onRejected] = node.arguments;
|
||||
const onFulfilledArgumentName = getArgBindingName(onFulfilled, transformer);
|
||||
const transformationBody = getTransformationBody(onFulfilled, prevArgName, onFulfilledArgumentName, node, transformer);
|
||||
if (onRejected) {
|
||||
const onRejectedArgumentName = getArgBindingName(onRejected, transformer);
|
||||
const tryBlock = factory.createBlock(transformExpression(node.expression, transformer, onFulfilledArgumentName).concat(transformationBody));
|
||||
const transformationBody2 = getTransformationBody(onRejected, prevArgName, onRejectedArgumentName, node, transformer);
|
||||
const catchArg = onRejectedArgumentName ? isSynthIdentifier(onRejectedArgumentName) ? onRejectedArgumentName.identifier.text : onRejectedArgumentName.bindingPattern : "e";
|
||||
const catchVariableDeclaration = factory.createVariableDeclaration(catchArg);
|
||||
const catchClause = factory.createCatchClause(catchVariableDeclaration, factory.createBlock(transformationBody2));
|
||||
return [factory.createTryStatement(tryBlock, catchClause, /* finallyBlock */ undefined)];
|
||||
function getPossibleNameForVarDecl(node: PromiseReturningCallExpression<"then" | "catch" | "finally">, transformer: Transformer, continuationArgName?: SynthBindingName) {
|
||||
let possibleNameForVarDecl: SynthIdentifier | undefined;
|
||||
|
||||
// If there is another call in the chain after the .catch() or .finally() we are transforming, we will need to save the result of both paths
|
||||
// (try block and catch/finally block). To do this, we will need to synthesize a variable that we were not aware of while we were adding
|
||||
// identifiers to the synthNamesMap. We will use the continuationArgName and then update the synthNamesMap with a new variable name for
|
||||
// the next transformation step
|
||||
|
||||
if (continuationArgName && !shouldReturn(node, transformer)) {
|
||||
if (isSynthIdentifier(continuationArgName)) {
|
||||
possibleNameForVarDecl = continuationArgName;
|
||||
transformer.synthNamesMap.forEach((val, key) => {
|
||||
if (val.identifier.text === continuationArgName.identifier.text) {
|
||||
const newSynthName = createUniqueSynthName(continuationArgName);
|
||||
transformer.synthNamesMap.set(key, newSynthName);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
possibleNameForVarDecl = createSynthIdentifier(factory.createUniqueName("result", GeneratedIdentifierFlags.Optimistic), continuationArgName.types);
|
||||
}
|
||||
|
||||
// We are about to write a 'let' variable declaration, but `transformExpression` for both
|
||||
// the try block and catch/finally block will assign to this name. Setting this flag indicates
|
||||
// that future assignments should be written as `name = value` instead of `const name = value`.
|
||||
declareSynthIdentifier(possibleNameForVarDecl);
|
||||
}
|
||||
return transformExpression(node.expression, transformer, onFulfilledArgumentName).concat(transformationBody);
|
||||
|
||||
return possibleNameForVarDecl;
|
||||
}
|
||||
|
||||
function finishCatchOrFinallyTransform(node: PromiseReturningCallExpression<"then" | "catch" | "finally">, transformer: Transformer, tryStatement: TryStatement, possibleNameForVarDecl: SynthIdentifier | undefined, continuationArgName?: SynthBindingName) {
|
||||
const statements: Statement[] = [];
|
||||
|
||||
// In order to avoid an implicit any, we will synthesize a type for the declaration using the unions of the types of both paths (try block and catch block)
|
||||
let varDeclIdentifier: Identifier | undefined;
|
||||
|
||||
if (possibleNameForVarDecl && !shouldReturn(node, transformer)) {
|
||||
varDeclIdentifier = getSynthesizedDeepClone(declareSynthIdentifier(possibleNameForVarDecl));
|
||||
const typeArray: Type[] = possibleNameForVarDecl.types;
|
||||
const unionType = transformer.checker.getUnionType(typeArray, UnionReduction.Subtype);
|
||||
const unionTypeNode = transformer.isInJSFile ? undefined : transformer.checker.typeToTypeNode(unionType, /*enclosingDeclaration*/ undefined, /*flags*/ undefined);
|
||||
const varDecl = [factory.createVariableDeclaration(varDeclIdentifier, /*exclamationToken*/ undefined, unionTypeNode)];
|
||||
const varDeclList = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList(varDecl, NodeFlags.Let));
|
||||
statements.push(varDeclList);
|
||||
}
|
||||
|
||||
statements.push(tryStatement);
|
||||
|
||||
if (continuationArgName && varDeclIdentifier && isSynthBindingPattern(continuationArgName)) {
|
||||
statements.push(factory.createVariableStatement(
|
||||
/*modifiers*/ undefined,
|
||||
factory.createVariableDeclarationList([
|
||||
factory.createVariableDeclaration(
|
||||
getSynthesizedDeepClone(declareSynthBindingPattern(continuationArgName)),
|
||||
/*exclamationToken*/ undefined,
|
||||
/*type*/ undefined,
|
||||
varDeclIdentifier
|
||||
)],
|
||||
NodeFlags.Const)));
|
||||
}
|
||||
|
||||
return statements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hasContinuation Whether another `then`, `catch`, or `finally` continuation follows this continuation.
|
||||
* @param continuationArgName The argument name for the continuation that follows this call.
|
||||
*/
|
||||
function transformFinally(node: PromiseReturningCallExpression<"finally">, onFinally: Expression | undefined, transformer: Transformer, hasContinuation: boolean, continuationArgName?: SynthBindingName): readonly Statement[] {
|
||||
if (!onFinally || isNullOrUndefined(transformer, onFinally)) {
|
||||
// Ignore this call as it has no effect on the result
|
||||
return transformExpression(/* returnContextNode */ node, node.expression.expression, transformer, hasContinuation, continuationArgName);
|
||||
}
|
||||
|
||||
const possibleNameForVarDecl = getPossibleNameForVarDecl(node, transformer, continuationArgName);
|
||||
|
||||
// Transform the left-hand-side of `.finally` into an array of inlined statements. We pass `true` for hasContinuation as `node` is the outer continuation.
|
||||
const inlinedLeftHandSide = transformExpression(/*returnContextNode*/ node, node.expression.expression, transformer, /*hasContinuation*/ true, possibleNameForVarDecl);
|
||||
if (hasFailed()) return silentFail(); // shortcut out of more work
|
||||
|
||||
// Transform the callback argument into an array of inlined statements. We pass whether we have an outer continuation here
|
||||
// as that indicates whether `return` is valid.
|
||||
const inlinedCallback = transformCallbackArgument(onFinally, hasContinuation, /*continuationArgName*/ undefined, /*argName*/ undefined, node, transformer);
|
||||
if (hasFailed()) return silentFail(); // shortcut out of more work
|
||||
|
||||
const tryBlock = factory.createBlock(inlinedLeftHandSide);
|
||||
const finallyBlock = factory.createBlock(inlinedCallback);
|
||||
const tryStatement = factory.createTryStatement(tryBlock, /*catchClause*/ undefined, finallyBlock);
|
||||
return finishCatchOrFinallyTransform(node, transformer, tryStatement, possibleNameForVarDecl, continuationArgName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hasContinuation Whether another `then`, `catch`, or `finally` continuation follows this continuation.
|
||||
* @param continuationArgName The argument name for the continuation that follows this call.
|
||||
*/
|
||||
function transformCatch(node: PromiseReturningCallExpression<"then" | "catch">, onRejected: Expression | undefined, transformer: Transformer, hasContinuation: boolean, continuationArgName?: SynthBindingName): readonly Statement[] {
|
||||
if (!onRejected || isNullOrUndefined(transformer, onRejected)) {
|
||||
// Ignore this call as it has no effect on the result
|
||||
return transformExpression(/* returnContextNode */ node, node.expression.expression, transformer, hasContinuation, continuationArgName);
|
||||
}
|
||||
|
||||
const inputArgName = getArgBindingName(onRejected, transformer);
|
||||
const possibleNameForVarDecl = getPossibleNameForVarDecl(node, transformer, continuationArgName);
|
||||
|
||||
// Transform the left-hand-side of `.then`/`.catch` into an array of inlined statements. We pass `true` for hasContinuation as `node` is the outer continuation.
|
||||
const inlinedLeftHandSide = transformExpression(/*returnContextNode*/ node, node.expression.expression, transformer, /*hasContinuation*/ true, possibleNameForVarDecl);
|
||||
if (hasFailed()) return silentFail(); // shortcut out of more work
|
||||
|
||||
// Transform the callback argument into an array of inlined statements. We pass whether we have an outer continuation here
|
||||
// as that indicates whether `return` is valid.
|
||||
const inlinedCallback = transformCallbackArgument(onRejected, hasContinuation, possibleNameForVarDecl, inputArgName, node, transformer);
|
||||
if (hasFailed()) return silentFail(); // shortcut out of more work
|
||||
|
||||
const tryBlock = factory.createBlock(inlinedLeftHandSide);
|
||||
const catchClause = factory.createCatchClause(inputArgName && getSynthesizedDeepClone(declareSynthBindingName(inputArgName)), factory.createBlock(inlinedCallback));
|
||||
const tryStatement = factory.createTryStatement(tryBlock, catchClause, /*finallyBlock*/ undefined);
|
||||
return finishCatchOrFinallyTransform(node, transformer, tryStatement, possibleNameForVarDecl, continuationArgName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hasContinuation Whether another `then`, `catch`, or `finally` continuation follows this continuation.
|
||||
* @param continuationArgName The argument name for the continuation that follows this call.
|
||||
*/
|
||||
function transformThen(node: PromiseReturningCallExpression<"then">, onFulfilled: Expression | undefined, onRejected: Expression | undefined, transformer: Transformer, hasContinuation: boolean, continuationArgName?: SynthBindingName): readonly Statement[] {
|
||||
if (!onFulfilled || isNullOrUndefined(transformer, onFulfilled)) {
|
||||
// If we don't have an `onfulfilled` callback, try treating this as a `.catch`.
|
||||
return transformCatch(node, onRejected, transformer, hasContinuation, continuationArgName);
|
||||
}
|
||||
|
||||
// We don't currently support transforming a `.then` with both onfulfilled and onrejected handlers, per GH#38152.
|
||||
if (onRejected && !isNullOrUndefined(transformer, onRejected)) {
|
||||
return silentFail();
|
||||
}
|
||||
|
||||
const inputArgName = getArgBindingName(onFulfilled, transformer);
|
||||
|
||||
// Transform the left-hand-side of `.then` into an array of inlined statements. We pass `true` for hasContinuation as `node` is the outer continuation.
|
||||
const inlinedLeftHandSide = transformExpression(node.expression.expression, node.expression.expression, transformer, /*hasContinuation*/ true, inputArgName);
|
||||
if (hasFailed()) return silentFail(); // shortcut out of more work
|
||||
|
||||
// Transform the callback argument into an array of inlined statements. We pass whether we have an outer continuation here
|
||||
// as that indicates whether `return` is valid.
|
||||
const inlinedCallback = transformCallbackArgument(onFulfilled, hasContinuation, continuationArgName, inputArgName, node, transformer);
|
||||
if (hasFailed()) return silentFail(); // shortcut out of more work
|
||||
|
||||
return concatenate(inlinedLeftHandSide, inlinedCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the 'x' part of `x.then(...)`, or the 'y()' part of `y().catch(...)`, where 'x' and 'y()' are Promises.
|
||||
*/
|
||||
function transformPromiseExpressionOfPropertyAccess(node: Expression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] {
|
||||
if (shouldReturn(node, transformer)) {
|
||||
return [factory.createReturnStatement(getSynthesizedDeepClone(node))];
|
||||
function transformPromiseExpressionOfPropertyAccess(returnContextNode: Expression, node: Expression, transformer: Transformer, hasContinuation: boolean, continuationArgName?: SynthBindingName): readonly Statement[] {
|
||||
if (shouldReturn(returnContextNode, transformer)) {
|
||||
let returnValue = getSynthesizedDeepClone(node);
|
||||
if (hasContinuation) {
|
||||
returnValue = factory.createAwaitExpression(returnValue);
|
||||
}
|
||||
return [factory.createReturnStatement(returnValue)];
|
||||
}
|
||||
|
||||
return createVariableOrAssignmentOrExpressionStatement(prevArgName, factory.createAwaitExpression(node), /*typeAnnotation*/ undefined);
|
||||
return createVariableOrAssignmentOrExpressionStatement(continuationArgName, factory.createAwaitExpression(node), /*typeAnnotation*/ undefined);
|
||||
}
|
||||
|
||||
function createVariableOrAssignmentOrExpressionStatement(variableName: SynthBindingName | undefined, rightHandSide: Expression, typeAnnotation: TypeNode | undefined): readonly Statement[] {
|
||||
|
@ -349,7 +505,7 @@ namespace ts.codefix {
|
|||
|
||||
if (isSynthIdentifier(variableName) && variableName.hasBeenDeclared) {
|
||||
// if the variable has already been declared, we don't need "let" or "const"
|
||||
return [factory.createExpressionStatement(factory.createAssignment(getSynthesizedDeepClone(variableName.identifier), rightHandSide))];
|
||||
return [factory.createExpressionStatement(factory.createAssignment(getSynthesizedDeepClone(referenceSynthIdentifier(variableName)), rightHandSide))];
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -357,14 +513,14 @@ namespace ts.codefix {
|
|||
/*modifiers*/ undefined,
|
||||
factory.createVariableDeclarationList([
|
||||
factory.createVariableDeclaration(
|
||||
getSynthesizedDeepClone(getNode(variableName)),
|
||||
getSynthesizedDeepClone(declareSynthBindingName(variableName)),
|
||||
/*exclamationToken*/ undefined,
|
||||
typeAnnotation,
|
||||
rightHandSide)],
|
||||
NodeFlags.Const))];
|
||||
}
|
||||
|
||||
function maybeAnnotateAndReturn(expressionToReturn: Expression | undefined, typeAnnotation: TypeNode | undefined): readonly Statement[] {
|
||||
function maybeAnnotateAndReturn(expressionToReturn: Expression | undefined, typeAnnotation: TypeNode | undefined): Statement[] {
|
||||
if (typeAnnotation && expressionToReturn) {
|
||||
const name = factory.createUniqueName("result", GeneratedIdentifierFlags.Optimistic);
|
||||
return [
|
||||
|
@ -376,21 +532,27 @@ namespace ts.codefix {
|
|||
}
|
||||
|
||||
// should be kept up to date with isFixablePromiseArgument in suggestionDiagnostics.ts
|
||||
function getTransformationBody(func: Expression, prevArgName: SynthBindingName | undefined, argName: SynthBindingName | undefined, parent: CallExpression, transformer: Transformer): readonly Statement[] {
|
||||
/**
|
||||
* @param hasContinuation Whether another `then`, `catch`, or `finally` continuation follows the continuation to which this callback belongs.
|
||||
* @param continuationArgName The argument name for the continuation that follows this call.
|
||||
* @param inputArgName The argument name provided to this call
|
||||
*/
|
||||
function transformCallbackArgument(func: Expression, hasContinuation: boolean, continuationArgName: SynthBindingName | undefined, inputArgName: SynthBindingName | undefined, parent: PromiseReturningCallExpression<"then" | "catch" | "finally">, transformer: Transformer): readonly Statement[] {
|
||||
switch (func.kind) {
|
||||
case SyntaxKind.NullKeyword:
|
||||
// do not produce a transformed statement for a null argument
|
||||
break;
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
case SyntaxKind.Identifier: // identifier includes undefined
|
||||
if (!argName) {
|
||||
if (!inputArgName) {
|
||||
// undefined was argument passed to promise handler
|
||||
break;
|
||||
}
|
||||
|
||||
const synthCall = factory.createCallExpression(getSynthesizedDeepClone(func as Identifier | PropertyAccessExpression), /*typeArguments*/ undefined, isSynthIdentifier(argName) ? [argName.identifier] : []);
|
||||
const synthCall = factory.createCallExpression(getSynthesizedDeepClone(func as Identifier | PropertyAccessExpression), /*typeArguments*/ undefined, isSynthIdentifier(inputArgName) ? [referenceSynthIdentifier(inputArgName)] : []);
|
||||
|
||||
if (shouldReturn(parent, transformer)) {
|
||||
return maybeAnnotateAndReturn(synthCall, parent.typeArguments?.[0]);
|
||||
return maybeAnnotateAndReturn(synthCall, getExplicitPromisedTypeOfPromiseReturningCallExpression(parent, func, transformer.checker));
|
||||
}
|
||||
|
||||
const type = transformer.checker.getTypeAtLocation(func);
|
||||
|
@ -400,9 +562,9 @@ namespace ts.codefix {
|
|||
return silentFail();
|
||||
}
|
||||
const returnType = callSignatures[0].getReturnType();
|
||||
const varDeclOrAssignment = createVariableOrAssignmentOrExpressionStatement(prevArgName, factory.createAwaitExpression(synthCall), parent.typeArguments?.[0]);
|
||||
if (prevArgName) {
|
||||
prevArgName.types.push(returnType);
|
||||
const varDeclOrAssignment = createVariableOrAssignmentOrExpressionStatement(continuationArgName, factory.createAwaitExpression(synthCall), getExplicitPromisedTypeOfPromiseReturningCallExpression(parent, func, transformer.checker));
|
||||
if (continuationArgName) {
|
||||
continuationArgName.types.push(transformer.checker.getAwaitedType(returnType) || returnType);
|
||||
}
|
||||
return varDeclOrAssignment;
|
||||
|
||||
|
@ -419,13 +581,53 @@ namespace ts.codefix {
|
|||
if (isReturnStatement(statement)) {
|
||||
seenReturnStatement = true;
|
||||
if (isReturnStatementWithFixablePromiseHandler(statement, transformer.checker)) {
|
||||
refactoredStmts = refactoredStmts.concat(getInnerTransformationBody(transformer, [statement], prevArgName));
|
||||
refactoredStmts = refactoredStmts.concat(transformReturnStatementWithFixablePromiseHandler(transformer, statement, hasContinuation, continuationArgName));
|
||||
}
|
||||
else {
|
||||
const possiblyAwaitedRightHandSide = returnType && statement.expression ? getPossiblyAwaitedRightHandSide(transformer.checker, returnType, statement.expression) : statement.expression;
|
||||
refactoredStmts.push(...maybeAnnotateAndReturn(possiblyAwaitedRightHandSide, parent.typeArguments?.[0]));
|
||||
refactoredStmts.push(...maybeAnnotateAndReturn(possiblyAwaitedRightHandSide, getExplicitPromisedTypeOfPromiseReturningCallExpression(parent, func, transformer.checker)));
|
||||
}
|
||||
}
|
||||
else if (hasContinuation && forEachReturnStatement(statement, returnTrue)) {
|
||||
// If there is a nested `return` in a callback that has a trailing continuation, we don't transform it as the resulting complexity is too great. For example:
|
||||
//
|
||||
// source | result
|
||||
// -------------------------------------| ---------------------------------------
|
||||
// function f(): Promise<number> { | async function f9(): Promise<number> {
|
||||
// return foo().then(() => { | await foo();
|
||||
// if (Math.random()) { | if (Math.random()) {
|
||||
// return 1; | return 1; // incorrect early return
|
||||
// } | }
|
||||
// return 2; | return 2; // incorrect early return
|
||||
// }).then(a => { | const a = undefined;
|
||||
// return a + 1; | return a + 1;
|
||||
// }); | }
|
||||
// } |
|
||||
//
|
||||
// However, branching returns in the outermost continuation are acceptable as no other continuation follows it:
|
||||
//
|
||||
// source | result
|
||||
//--------------------------------------|---------------------------------------
|
||||
// function f() { | async function f() {
|
||||
// return foo().then(res => { | const res = await foo();
|
||||
// if (res.ok) { | if (res.ok) {
|
||||
// return 1; | return 1;
|
||||
// } | }
|
||||
// else { | else {
|
||||
// if (res.buffer.length > 5) { | if (res.buffer.length > 5) {
|
||||
// return 2; | return 2;
|
||||
// } | }
|
||||
// else { | else {
|
||||
// return 3; | return 3;
|
||||
// } | }
|
||||
// } | }
|
||||
// }); | }
|
||||
// } |
|
||||
//
|
||||
// We may improve this in the future, but for now the heuristics are too complex
|
||||
|
||||
return silentFail();
|
||||
}
|
||||
else {
|
||||
refactoredStmts.push(statement);
|
||||
}
|
||||
|
@ -435,29 +637,31 @@ namespace ts.codefix {
|
|||
? refactoredStmts.map(s => getSynthesizedDeepClone(s))
|
||||
: removeReturns(
|
||||
refactoredStmts,
|
||||
prevArgName,
|
||||
continuationArgName,
|
||||
transformer,
|
||||
seenReturnStatement);
|
||||
}
|
||||
else {
|
||||
const innerRetStmts = isFixablePromiseHandler(funcBody, transformer.checker) ? [factory.createReturnStatement(funcBody)] : emptyArray;
|
||||
const innerCbBody = getInnerTransformationBody(transformer, innerRetStmts, prevArgName);
|
||||
const inlinedStatements = isFixablePromiseHandler(funcBody, transformer.checker) ?
|
||||
transformReturnStatementWithFixablePromiseHandler(transformer, factory.createReturnStatement(funcBody), hasContinuation, continuationArgName) :
|
||||
emptyArray;
|
||||
|
||||
if (innerCbBody.length > 0) {
|
||||
return innerCbBody;
|
||||
if (inlinedStatements.length > 0) {
|
||||
return inlinedStatements;
|
||||
}
|
||||
|
||||
if (returnType) {
|
||||
const possiblyAwaitedRightHandSide = getPossiblyAwaitedRightHandSide(transformer.checker, returnType, funcBody);
|
||||
|
||||
if (!shouldReturn(parent, transformer)) {
|
||||
const transformedStatement = createVariableOrAssignmentOrExpressionStatement(prevArgName, possiblyAwaitedRightHandSide, /*typeAnnotation*/ undefined);
|
||||
if (prevArgName) {
|
||||
prevArgName.types.push(returnType);
|
||||
const transformedStatement = createVariableOrAssignmentOrExpressionStatement(continuationArgName, possiblyAwaitedRightHandSide, /*typeAnnotation*/ undefined);
|
||||
if (continuationArgName) {
|
||||
continuationArgName.types.push(transformer.checker.getAwaitedType(returnType) || returnType);
|
||||
}
|
||||
return transformedStatement;
|
||||
}
|
||||
else {
|
||||
return maybeAnnotateAndReturn(possiblyAwaitedRightHandSide, parent.typeArguments?.[0]);
|
||||
return maybeAnnotateAndReturn(possiblyAwaitedRightHandSide, getExplicitPromisedTypeOfPromiseReturningCallExpression(parent, func, transformer.checker));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -491,9 +695,12 @@ namespace ts.codefix {
|
|||
if (prevArgName === undefined) {
|
||||
ret.push(factory.createExpressionStatement(possiblyAwaitedExpression));
|
||||
}
|
||||
else if (isSynthIdentifier(prevArgName) && prevArgName.hasBeenDeclared) {
|
||||
ret.push(factory.createExpressionStatement(factory.createAssignment(referenceSynthIdentifier(prevArgName), possiblyAwaitedExpression)));
|
||||
}
|
||||
else {
|
||||
ret.push(factory.createVariableStatement(/*modifiers*/ undefined,
|
||||
(factory.createVariableDeclarationList([factory.createVariableDeclaration(getNode(prevArgName), /*exclamationToken*/ undefined, /*type*/ undefined, possiblyAwaitedExpression)], NodeFlags.Const))));
|
||||
(factory.createVariableDeclarationList([factory.createVariableDeclaration(declareSynthBindingName(prevArgName), /*exclamationToken*/ undefined, /*type*/ undefined, possiblyAwaitedExpression)], NodeFlags.Const))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -505,29 +712,30 @@ namespace ts.codefix {
|
|||
// if block has no return statement, need to define prevArgName as undefined to prevent undeclared variables
|
||||
if (!seenReturnStatement && prevArgName !== undefined) {
|
||||
ret.push(factory.createVariableStatement(/*modifiers*/ undefined,
|
||||
(factory.createVariableDeclarationList([factory.createVariableDeclaration(getNode(prevArgName), /*exclamationToken*/ undefined, /*type*/ undefined, factory.createIdentifier("undefined"))], NodeFlags.Const))));
|
||||
(factory.createVariableDeclarationList([factory.createVariableDeclaration(declareSynthBindingName(prevArgName), /*exclamationToken*/ undefined, /*type*/ undefined, factory.createIdentifier("undefined"))], NodeFlags.Const))));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
function getInnerTransformationBody(transformer: Transformer, innerRetStmts: readonly Node[], prevArgName?: SynthBindingName) {
|
||||
/**
|
||||
* @param hasContinuation Whether another `then`, `catch`, or `finally` continuation follows the continuation to which this statement belongs.
|
||||
* @param continuationArgName The argument name for the continuation that follows this call.
|
||||
*/
|
||||
function transformReturnStatementWithFixablePromiseHandler(transformer: Transformer, innerRetStmt: ReturnStatement, hasContinuation: boolean, continuationArgName?: SynthBindingName) {
|
||||
let innerCbBody: Statement[] = [];
|
||||
for (const stmt of innerRetStmts) {
|
||||
forEachChild(stmt, function visit(node) {
|
||||
if (isCallExpression(node)) {
|
||||
const temp = transformExpression(node, transformer, prevArgName);
|
||||
innerCbBody = innerCbBody.concat(temp);
|
||||
if (innerCbBody.length > 0) {
|
||||
return;
|
||||
}
|
||||
forEachChild(innerRetStmt, function visit(node) {
|
||||
if (isCallExpression(node)) {
|
||||
const temp = transformExpression(node, node, transformer, hasContinuation, continuationArgName);
|
||||
innerCbBody = innerCbBody.concat(temp);
|
||||
if (innerCbBody.length > 0) {
|
||||
return;
|
||||
}
|
||||
else if (!isFunctionLike(node)) {
|
||||
forEachChild(node, visit);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (!isFunctionLike(node)) {
|
||||
forEachChild(node, visit);
|
||||
}
|
||||
});
|
||||
return innerCbBody;
|
||||
}
|
||||
|
||||
|
@ -597,18 +805,35 @@ namespace ts.codefix {
|
|||
return every(bindingName.elements, isEmptyBindingName);
|
||||
}
|
||||
|
||||
function getNode(bindingName: SynthBindingName) {
|
||||
return isSynthIdentifier(bindingName) ? bindingName.identifier : bindingName.bindingPattern;
|
||||
}
|
||||
|
||||
function createSynthIdentifier(identifier: Identifier, types: Type[] = []): SynthIdentifier {
|
||||
return { kind: SynthBindingNameKind.Identifier, identifier, types, hasBeenDeclared: false };
|
||||
return { kind: SynthBindingNameKind.Identifier, identifier, types, hasBeenDeclared: false, hasBeenReferenced: false };
|
||||
}
|
||||
|
||||
function createSynthBindingPattern(bindingPattern: BindingPattern, elements: readonly SynthBindingName[] = emptyArray, types: Type[] = []): SynthBindingPattern {
|
||||
return { kind: SynthBindingNameKind.BindingPattern, bindingPattern, elements, types };
|
||||
}
|
||||
|
||||
function referenceSynthIdentifier(synthId: SynthIdentifier) {
|
||||
synthId.hasBeenReferenced = true;
|
||||
return synthId.identifier;
|
||||
}
|
||||
|
||||
function declareSynthBindingName(synthName: SynthBindingName) {
|
||||
return isSynthIdentifier(synthName) ? declareSynthIdentifier(synthName) : declareSynthBindingPattern(synthName);
|
||||
}
|
||||
|
||||
function declareSynthBindingPattern(synthPattern: SynthBindingPattern) {
|
||||
for (const element of synthPattern.elements) {
|
||||
declareSynthBindingName(element);
|
||||
}
|
||||
return synthPattern.bindingPattern;
|
||||
}
|
||||
|
||||
function declareSynthIdentifier(synthId: SynthIdentifier) {
|
||||
synthId.hasBeenDeclared = true;
|
||||
return synthId.identifier;
|
||||
}
|
||||
|
||||
function isSynthIdentifier(bindingName: SynthBindingName): bindingName is SynthIdentifier {
|
||||
return bindingName.kind === SynthBindingNameKind.Identifier;
|
||||
}
|
||||
|
|
|
@ -139,33 +139,40 @@ namespace ts {
|
|||
// Should be kept up to date with transformExpression in convertToAsyncFunction.ts
|
||||
export function isFixablePromiseHandler(node: Node, checker: TypeChecker): boolean {
|
||||
// ensure outermost call exists and is a promise handler
|
||||
if (!isPromiseHandler(node) || !node.arguments.every(arg => isFixablePromiseArgument(arg, checker))) {
|
||||
if (!isPromiseHandler(node) || !hasSupportedNumberOfArguments(node) || !node.arguments.every(arg => isFixablePromiseArgument(arg, checker))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ensure all chained calls are valid
|
||||
let currentNode = node.expression;
|
||||
let currentNode = node.expression.expression;
|
||||
while (isPromiseHandler(currentNode) || isPropertyAccessExpression(currentNode)) {
|
||||
if (isCallExpression(currentNode) && !currentNode.arguments.every(arg => isFixablePromiseArgument(arg, checker))) {
|
||||
return false;
|
||||
if (isCallExpression(currentNode)) {
|
||||
if (!hasSupportedNumberOfArguments(currentNode) || !currentNode.arguments.every(arg => isFixablePromiseArgument(arg, checker))) {
|
||||
return false;
|
||||
}
|
||||
currentNode = currentNode.expression.expression;
|
||||
}
|
||||
else {
|
||||
currentNode = currentNode.expression;
|
||||
}
|
||||
currentNode = currentNode.expression;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isPromiseHandler(node: Node): node is CallExpression {
|
||||
function isPromiseHandler(node: Node): node is CallExpression & { readonly expression: PropertyAccessExpression } {
|
||||
return isCallExpression(node) && (
|
||||
hasPropertyAccessExpressionWithName(node, "then") && hasSupportedNumberOfArguments(node) ||
|
||||
hasPropertyAccessExpressionWithName(node, "catch"));
|
||||
hasPropertyAccessExpressionWithName(node, "then") ||
|
||||
hasPropertyAccessExpressionWithName(node, "catch") ||
|
||||
hasPropertyAccessExpressionWithName(node, "finally"));
|
||||
}
|
||||
|
||||
function hasSupportedNumberOfArguments(node: CallExpression) {
|
||||
if (node.arguments.length > 2) return false;
|
||||
if (node.arguments.length < 2) return true;
|
||||
return some(node.arguments, arg => {
|
||||
return arg.kind === SyntaxKind.NullKeyword ||
|
||||
isIdentifier(arg) && arg.text === "undefined";
|
||||
function hasSupportedNumberOfArguments(node: CallExpression & { readonly expression: PropertyAccessExpression }) {
|
||||
const name = node.expression.name.text;
|
||||
const maxArguments = name === "then" ? 2 : name === "catch" ? 1 : name === "finally" ? 1 : 0;
|
||||
if (node.arguments.length > maxArguments) return false;
|
||||
if (node.arguments.length < maxArguments) return true;
|
||||
return maxArguments === 1 || some(node.arguments, arg => {
|
||||
return arg.kind === SyntaxKind.NullKeyword || isIdentifier(arg) && arg.text === "undefined";
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,13 @@ interface Promise<T> {
|
|||
* @returns A Promise for the completion of the callback.
|
||||
*/
|
||||
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
|
||||
/**
|
||||
* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
|
||||
* resolved value cannot be modified from the callback.
|
||||
* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
|
||||
* @returns A Promise for the completion of the callback.
|
||||
*/
|
||||
finally(onfinally?: (() => void) | undefined | null): Promise<T>
|
||||
}
|
||||
interface PromiseConstructor {
|
||||
/**
|
||||
|
@ -277,7 +284,30 @@ interface Array<T> {}`
|
|||
}
|
||||
}
|
||||
|
||||
function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: string, text: string, baselineFolder: string, includeLib?: boolean, includeModule?: boolean, expectFailure = false, onlyProvideAction = false) {
|
||||
const enum ConvertToAsyncTestFlags {
|
||||
None,
|
||||
IncludeLib = 1 << 0,
|
||||
IncludeModule = 1 << 1,
|
||||
ExpectSuggestionDiagnostic = 1 << 2,
|
||||
ExpectNoSuggestionDiagnostic = 1 << 3,
|
||||
ExpectAction = 1 << 4,
|
||||
ExpectNoAction = 1 << 5,
|
||||
|
||||
ExpectSuccess = ExpectSuggestionDiagnostic | ExpectAction,
|
||||
ExpectFailed = ExpectNoSuggestionDiagnostic | ExpectNoAction,
|
||||
}
|
||||
|
||||
function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: string, text: string, baselineFolder: string, flags: ConvertToAsyncTestFlags) {
|
||||
const includeLib = !!(flags & ConvertToAsyncTestFlags.IncludeLib);
|
||||
const includeModule = !!(flags & ConvertToAsyncTestFlags.IncludeModule);
|
||||
const expectSuggestionDiagnostic = !!(flags & ConvertToAsyncTestFlags.ExpectSuggestionDiagnostic);
|
||||
const expectNoSuggestionDiagnostic = !!(flags & ConvertToAsyncTestFlags.ExpectNoSuggestionDiagnostic);
|
||||
const expectAction = !!(flags & ConvertToAsyncTestFlags.ExpectAction);
|
||||
const expectNoAction = !!(flags & ConvertToAsyncTestFlags.ExpectNoAction);
|
||||
const expectFailure = expectNoSuggestionDiagnostic || expectNoAction;
|
||||
Debug.assert(!(expectSuggestionDiagnostic && expectNoSuggestionDiagnostic), "Cannot combine both 'ExpectSuggestionDiagnostic' and 'ExpectNoSuggestionDiagnostic'");
|
||||
Debug.assert(!(expectAction && expectNoAction), "Cannot combine both 'ExpectAction' and 'ExpectNoAction'");
|
||||
|
||||
const t = extractTest(text);
|
||||
const selectionRange = t.ranges.get("selection")!;
|
||||
if (!selectionRange) {
|
||||
|
@ -320,35 +350,47 @@ interface Array<T> {}`
|
|||
const diagnostics = languageService.getSuggestionDiagnostics(f.path);
|
||||
const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === Diagnostics.This_may_be_converted_to_an_async_function.message &&
|
||||
diagnostic.start === context.span.start && diagnostic.length === context.span.length);
|
||||
if (expectFailure) {
|
||||
assert.isUndefined(diagnostic);
|
||||
}
|
||||
else {
|
||||
assert.exists(diagnostic);
|
||||
}
|
||||
|
||||
const actions = codefix.getFixes(context);
|
||||
const action = find(actions, action => action.description === Diagnostics.Convert_to_async_function.message);
|
||||
if (expectFailure && !onlyProvideAction) {
|
||||
assert.isNotTrue(action && action.changes.length > 0);
|
||||
return;
|
||||
|
||||
let outputText: string | null;
|
||||
if (action?.changes.length) {
|
||||
const data: string[] = [];
|
||||
data.push(`// ==ORIGINAL==`);
|
||||
data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/"));
|
||||
const changes = action.changes;
|
||||
assert.lengthOf(changes, 1);
|
||||
|
||||
data.push(`// ==ASYNC FUNCTION::${action.description}==`);
|
||||
const newText = textChanges.applyChanges(sourceFile.text, changes[0].textChanges);
|
||||
data.push(newText);
|
||||
|
||||
const diagProgram = makeLanguageService({ path, content: newText }, includeLib, includeModule).getProgram()!;
|
||||
assert.isFalse(hasSyntacticDiagnostics(diagProgram));
|
||||
outputText = data.join(newLineCharacter);
|
||||
}
|
||||
else {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
outputText = null;
|
||||
}
|
||||
|
||||
assert.isTrue(action && action.changes.length > 0);
|
||||
Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, outputText);
|
||||
|
||||
const data: string[] = [];
|
||||
data.push(`// ==ORIGINAL==`);
|
||||
data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/"));
|
||||
const changes = action!.changes;
|
||||
assert.lengthOf(changes, 1);
|
||||
if (expectNoSuggestionDiagnostic) {
|
||||
assert.isUndefined(diagnostic, "Expected code fix to not provide a suggestion diagnostic");
|
||||
}
|
||||
else if (expectSuggestionDiagnostic) {
|
||||
assert.exists(diagnostic, "Expected code fix to provide a suggestion diagnostic");
|
||||
}
|
||||
|
||||
data.push(`// ==ASYNC FUNCTION::${action!.description}==`);
|
||||
const newText = textChanges.applyChanges(sourceFile.text, changes[0].textChanges);
|
||||
data.push(newText);
|
||||
|
||||
const diagProgram = makeLanguageService({ path, content: newText }, includeLib, includeModule).getProgram()!;
|
||||
assert.isFalse(hasSyntacticDiagnostics(diagProgram));
|
||||
Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, data.join(newLineCharacter));
|
||||
if (expectNoAction) {
|
||||
assert.isNotTrue(!!action?.changes.length, "Expected code fix to not provide an action");
|
||||
assert.isNotTrue(typeof outputText === "string", "Expected code fix to not apply changes");
|
||||
}
|
||||
else if (expectAction) {
|
||||
assert.isTrue(!!action?.changes.length, "Expected code fix to provide an action");
|
||||
assert.isTrue(typeof outputText === "string", "Expected code fix to apply changes");
|
||||
}
|
||||
}
|
||||
|
||||
function makeLanguageService(file: TestFSWithWatch.File, includeLib?: boolean, includeModule?: boolean) {
|
||||
|
@ -372,19 +414,23 @@ interface Array<T> {}`
|
|||
}
|
||||
|
||||
const _testConvertToAsyncFunction = createTestWrapper((it, caption: string, text: string) => {
|
||||
testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", /*includeLib*/ true);
|
||||
testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectSuccess);
|
||||
});
|
||||
|
||||
const _testConvertToAsyncFunctionFailed = createTestWrapper((it, caption: string, text: string) => {
|
||||
testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", /*includeLib*/ true, /*includeModule*/ false, /*expectFailure*/ true);
|
||||
testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectFailed);
|
||||
});
|
||||
|
||||
const _testConvertToAsyncFunctionFailedSuggestion = createTestWrapper((it, caption: string, text: string) => {
|
||||
testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", /*includeLib*/ true, /*includeModule*/ false, /*expectFailure*/ true, /*onlyProvideAction*/ true);
|
||||
testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectNoSuggestionDiagnostic | ConvertToAsyncTestFlags.ExpectAction);
|
||||
});
|
||||
|
||||
const _testConvertToAsyncFunctionFailedAction = createTestWrapper((it, caption: string, text: string) => {
|
||||
testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectSuggestionDiagnostic | ConvertToAsyncTestFlags.ExpectNoAction);
|
||||
});
|
||||
|
||||
const _testConvertToAsyncFunctionWithModule = createTestWrapper((it, caption: string, text: string) => {
|
||||
testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", /*includeLib*/ true, /*includeModule*/ true);
|
||||
testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.IncludeModule | ConvertToAsyncTestFlags.ExpectSuccess);
|
||||
});
|
||||
|
||||
describe("unittests:: services:: convertToAsyncFunction", () => {
|
||||
|
@ -435,11 +481,11 @@ function [#|f|](): Promise<void>{
|
|||
function [#|f|]():Promise<void> {
|
||||
return fetch('https://typescriptlang.org').then(result => { console.log(result); }).catch(err => { console.log(err); });
|
||||
}`);
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_CatchAndRej", `
|
||||
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchAndRej", `
|
||||
function [#|f|]():Promise<void> {
|
||||
return fetch('https://typescriptlang.org').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) });
|
||||
}`);
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_CatchAndRejRef", `
|
||||
_testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchAndRejRef", `
|
||||
function [#|f|]():Promise<void> {
|
||||
return fetch('https://typescriptlang.org').then(res, rej).catch(catch_err)
|
||||
}
|
||||
|
@ -1718,5 +1764,76 @@ function [#|foo|](p: Promise<string[]>) {
|
|||
}
|
||||
`);
|
||||
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_thenNoArguments", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().then();
|
||||
}`);
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_catchNoArguments", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().catch();
|
||||
}`);
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_chainedThenCatchThen", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().then(x => Promise.resolve(x + 1)).catch(() => 1).then(y => y + 2);
|
||||
}`);
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_finally", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().finally(() => console.log("done"));
|
||||
}`);
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_finallyNoArguments", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().finally();
|
||||
}`);
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_finallyNull", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().finally(null);
|
||||
}`);
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_finallyUndefined", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().finally(undefined);
|
||||
}`);
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_thenFinally", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().then(x => x + 1).finally(() => console.log("done"));
|
||||
}`);
|
||||
_testConvertToAsyncFunction("convertToAsyncFunction_thenFinallyThen", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().then(x => Promise.resolve(x + 1)).finally(() => console.log("done")).then(y => y + 2);
|
||||
}`);
|
||||
_testConvertToAsyncFunctionFailedAction("convertToAsyncFunction_returnInBranch", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().then(() => {
|
||||
if (Math.random()) {
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
}).then(a => {
|
||||
return a + 1;
|
||||
});
|
||||
}
|
||||
`);
|
||||
_testConvertToAsyncFunctionFailedAction("convertToAsyncFunction_partialReturnInBranch", `
|
||||
declare function foo(): Promise<number>;
|
||||
function [#|f|](): Promise<number> {
|
||||
return foo().then(() => {
|
||||
if (Math.random()) {
|
||||
return 1;
|
||||
}
|
||||
console.log("foo");
|
||||
}).then(a => {
|
||||
return a + 1;
|
||||
});
|
||||
}
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3622,7 +3622,7 @@ declare namespace ts {
|
|||
updateDefaultClause(node: DefaultClause, statements: readonly Statement[]): DefaultClause;
|
||||
createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]): HeritageClause;
|
||||
updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]): HeritageClause;
|
||||
createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block): CatchClause;
|
||||
createCatchClause(variableDeclaration: string | BindingName | VariableDeclaration | undefined, block: Block): CatchClause;
|
||||
updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block): CatchClause;
|
||||
createPropertyAssignment(name: string | PropertyName, initializer: Expression): PropertyAssignment;
|
||||
updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression): PropertyAssignment;
|
||||
|
@ -11074,7 +11074,7 @@ declare namespace ts {
|
|||
/** @deprecated Use `factory.updateHeritageClause` or the factory supplied by your transformation context instead. */
|
||||
const updateHeritageClause: (node: HeritageClause, types: readonly ExpressionWithTypeArguments[]) => HeritageClause;
|
||||
/** @deprecated Use `factory.createCatchClause` or the factory supplied by your transformation context instead. */
|
||||
const createCatchClause: (variableDeclaration: string | VariableDeclaration | undefined, block: Block) => CatchClause;
|
||||
const createCatchClause: (variableDeclaration: string | VariableDeclaration | BindingName | undefined, block: Block) => CatchClause;
|
||||
/** @deprecated Use `factory.updateCatchClause` or the factory supplied by your transformation context instead. */
|
||||
const updateCatchClause: (node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) => CatchClause;
|
||||
/** @deprecated Use `factory.createPropertyAssignment` or the factory supplied by your transformation context instead. */
|
||||
|
|
|
@ -3622,7 +3622,7 @@ declare namespace ts {
|
|||
updateDefaultClause(node: DefaultClause, statements: readonly Statement[]): DefaultClause;
|
||||
createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]): HeritageClause;
|
||||
updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]): HeritageClause;
|
||||
createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block): CatchClause;
|
||||
createCatchClause(variableDeclaration: string | BindingName | VariableDeclaration | undefined, block: Block): CatchClause;
|
||||
updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block): CatchClause;
|
||||
createPropertyAssignment(name: string | PropertyName, initializer: Expression): PropertyAssignment;
|
||||
updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression): PropertyAssignment;
|
||||
|
@ -7274,7 +7274,7 @@ declare namespace ts {
|
|||
/** @deprecated Use `factory.updateHeritageClause` or the factory supplied by your transformation context instead. */
|
||||
const updateHeritageClause: (node: HeritageClause, types: readonly ExpressionWithTypeArguments[]) => HeritageClause;
|
||||
/** @deprecated Use `factory.createCatchClause` or the factory supplied by your transformation context instead. */
|
||||
const createCatchClause: (variableDeclaration: string | VariableDeclaration | undefined, block: Block) => CatchClause;
|
||||
const createCatchClause: (variableDeclaration: string | VariableDeclaration | BindingName | undefined, block: Block) => CatchClause;
|
||||
/** @deprecated Use `factory.updateCatchClause` or the factory supplied by your transformation context instead. */
|
||||
const updateCatchClause: (node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) => CatchClause;
|
||||
/** @deprecated Use `factory.createPropertyAssignment` or the factory supplied by your transformation context instead. */
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
function /*[#|*/f/*|]*/():Promise<void> {
|
||||
return fetch('https://typescriptlang.org').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) });
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
async function f():Promise<void> {
|
||||
try {
|
||||
try {
|
||||
const result = await fetch('https://typescriptlang.org');
|
||||
console.log(result);
|
||||
} catch (rejection) {
|
||||
console.log("rejected:", rejection);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
function /*[#|*/f/*|]*/():Promise<void> {
|
||||
return fetch('https://typescriptlang.org').then(res, rej).catch(catch_err)
|
||||
}
|
||||
function res(result){
|
||||
console.log(result);
|
||||
}
|
||||
function rej(rejection){
|
||||
return rejection.ok;
|
||||
}
|
||||
function catch_err(err){
|
||||
console.log(err);
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
async function f():Promise<void> {
|
||||
try {
|
||||
try {
|
||||
const result = await fetch('https://typescriptlang.org');
|
||||
return res(result);
|
||||
} catch (rejection) {
|
||||
return rej(rejection);
|
||||
}
|
||||
} catch (err) {
|
||||
return catch_err(err);
|
||||
}
|
||||
}
|
||||
function res(result){
|
||||
console.log(result);
|
||||
}
|
||||
function rej(rejection){
|
||||
return rejection.ok;
|
||||
}
|
||||
function catch_err(err){
|
||||
console.log(err);
|
||||
}
|
|
@ -15,7 +15,7 @@ function rej(reject){
|
|||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
async function f(){
|
||||
let result: number | Promise<number>;
|
||||
let result: number;
|
||||
try {
|
||||
const result_1 = await fetch("https://typescriptlang.org");
|
||||
result = await res(result_1);
|
||||
|
|
|
@ -11,7 +11,7 @@ function /*[#|*/f/*|]*/() {
|
|||
async function f() {
|
||||
let x = fetch("https://typescriptlang.org").then(res => console.log(res));
|
||||
try {
|
||||
return x;
|
||||
return await x;
|
||||
} catch (err) {
|
||||
return console.log("Error!", err);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ function /*[#|*/f/*|]*/() {
|
|||
async function f() {
|
||||
let x = fetch("https://typescriptlang.org").then(res => console.log(res));
|
||||
try {
|
||||
return x;
|
||||
return await x;
|
||||
} catch (err) {
|
||||
return console.log("Error!", err);
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ function /*[#|*/f/*|]*/() {
|
|||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
async function f() {
|
||||
try {
|
||||
const x = await fetch('https://typescriptlang.org');
|
||||
return x.statusText;
|
||||
} catch (e) { }
|
||||
const x = await fetch('https://typescriptlang.org');
|
||||
return x.statusText;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ function /*[#|*/f/*|]*/() {
|
|||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
async function f() {
|
||||
try {
|
||||
const x = await fetch('https://typescriptlang.org');
|
||||
return x.statusText;
|
||||
} catch (e) { }
|
||||
const x = await fetch('https://typescriptlang.org');
|
||||
return x.statusText;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ function /*[#|*/f/*|]*/():Promise<void | Response> {
|
|||
|
||||
async function f():Promise<void | Response> {
|
||||
try {
|
||||
await fetch('https://typescriptlang.org');
|
||||
return await fetch('https://typescriptlang.org');
|
||||
} catch (rejection) {
|
||||
return console.log("rejected:", rejection);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ function /*[#|*/f/*|]*/():Promise<void | Response> {
|
|||
|
||||
async function f():Promise<void | Response> {
|
||||
try {
|
||||
await fetch('https://typescriptlang.org');
|
||||
return await fetch('https://typescriptlang.org');
|
||||
} catch (rej) {
|
||||
return console.log(rej);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ function /*[#|*/f/*|]*/():Promise<void | Response> {
|
|||
|
||||
async function f():Promise<void | Response> {
|
||||
try {
|
||||
return fetch('https://typescriptlang.org');
|
||||
return await fetch('https://typescriptlang.org');
|
||||
} catch (rej) {
|
||||
return console.log(rej);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ function /*[#|*/f/*|]*/() {
|
|||
|
||||
async function f() {
|
||||
try {
|
||||
await fetch('https://typescriptlang.org');
|
||||
return await fetch('https://typescriptlang.org');
|
||||
} catch (rejection) {
|
||||
return console.log("rejected:", rejection);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ function /*[#|*/f/*|]*/() {
|
|||
|
||||
async function f() {
|
||||
try {
|
||||
await fetch('https://typescriptlang.org');
|
||||
return await fetch('https://typescriptlang.org');
|
||||
} catch (rejection) {
|
||||
return console.log("rejected:", rejection);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ function my_print (resp): Promise<void> {
|
|||
|
||||
async function f() {
|
||||
try {
|
||||
return my_print(fetch("https://typescriptlang.org").then(res => console.log(res)));
|
||||
return await my_print(fetch("https://typescriptlang.org").then(res => console.log(res)));
|
||||
} catch (err) {
|
||||
return console.log("Error!", err);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ function /*[#|*/f/*|]*/() {
|
|||
|
||||
async function f() {
|
||||
try {
|
||||
return fetch(Promise.resolve(1).then(res => "https://typescriptlang.org"));
|
||||
return await fetch(Promise.resolve(1).then(res => "https://typescriptlang.org"));
|
||||
} catch (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ function /*[#|*/f/*|]*/() {
|
|||
|
||||
async function f() {
|
||||
try {
|
||||
return fetch(Promise.resolve(1).then(res => "https://typescriptlang.org"));
|
||||
return await fetch(Promise.resolve(1).then(res => "https://typescriptlang.org"));
|
||||
} catch (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ function /*[#|*/f/*|]*/(p: Promise<unknown>) {
|
|||
|
||||
async function f(p: Promise<unknown>) {
|
||||
try {
|
||||
return p;
|
||||
return await p;
|
||||
} catch (error) {
|
||||
return await Promise.reject(error);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ function /*[#|*/f/*|]*/(p: Promise<unknown>) {
|
|||
|
||||
async function f(p: Promise<unknown>) {
|
||||
try {
|
||||
return p;
|
||||
return await p;
|
||||
} catch (error) {
|
||||
return await Promise.reject(error);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ function /*[#|*/f/*|]*/(p: Promise<unknown>) {
|
|||
|
||||
async function f(p: Promise<unknown>) {
|
||||
try {
|
||||
return p;
|
||||
return await p;
|
||||
} catch (error) {
|
||||
return await Promise.reject(error);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ async function f() {
|
|||
try {
|
||||
await Promise.resolve();
|
||||
result = ({ x: 3 });
|
||||
} catch (e) {
|
||||
} catch {
|
||||
result = ({ x: "a" });
|
||||
}
|
||||
const { x } = result;
|
||||
|
|
|
@ -11,7 +11,7 @@ async function f() {
|
|||
try {
|
||||
await Promise.resolve();
|
||||
result = ({ x: 3 });
|
||||
} catch (e) {
|
||||
} catch {
|
||||
result = ({ x: "a" });
|
||||
}
|
||||
const { x } = result;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
function /*[#|*/f/*|]*/(): Promise<number> {
|
||||
return foo().catch();
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
async function f(): Promise<number> {
|
||||
return foo();
|
||||
}
|
|
@ -14,9 +14,9 @@ type APIResponse<T> = { success: true, data: T } | { success: false };
|
|||
|
||||
async function get() {
|
||||
try {
|
||||
return Promise
|
||||
return await Promise
|
||||
.resolve<APIResponse<{ email: string; }>>({ success: true, data: { email: "" } });
|
||||
} catch (e) {
|
||||
} catch {
|
||||
const result: APIResponse<{ email: string; }> = ({ success: false });
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
function /*[#|*/f/*|]*/(): Promise<number> {
|
||||
return foo().then(x => Promise.resolve(x + 1)).catch(() => 1).then(y => y + 2);
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
async function f(): Promise<number> {
|
||||
let y: number;
|
||||
try {
|
||||
const x = await foo();
|
||||
y = await Promise.resolve(x + 1);
|
||||
} catch {
|
||||
y = 1;
|
||||
}
|
||||
return y + 2;
|
||||
}
|
|
@ -7,7 +7,5 @@ function /*[#|*/f/*|]*/() {
|
|||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
async function f() {
|
||||
try {
|
||||
return Promise.resolve();
|
||||
} catch (e) { }
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
@ -7,7 +7,5 @@ function /*[#|*/f/*|]*/() {
|
|||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
async function f() {
|
||||
try {
|
||||
return Promise.resolve();
|
||||
} catch (e) { }
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ function /*[#|*/f/*|]*/() {
|
|||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
async function f() {
|
||||
try {
|
||||
const x = await Promise.resolve(0);
|
||||
return x;
|
||||
} catch (e) { }
|
||||
const x = await Promise.resolve(0);
|
||||
return x;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ function /*[#|*/f/*|]*/() {
|
|||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
async function f() {
|
||||
try {
|
||||
const x = await Promise.resolve(0);
|
||||
return x;
|
||||
} catch (e) { }
|
||||
const x = await Promise.resolve(0);
|
||||
return x;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
function /*[#|*/f/*|]*/(): Promise<number> {
|
||||
return foo().finally(() => console.log("done"));
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
async function f(): Promise<number> {
|
||||
try {
|
||||
return await foo();
|
||||
} finally {
|
||||
return console.log("done");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
function /*[#|*/f/*|]*/(): Promise<number> {
|
||||
return foo().finally();
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
async function f(): Promise<number> {
|
||||
return foo();
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
function /*[#|*/f/*|]*/(): Promise<number> {
|
||||
return foo().finally(null);
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
async function f(): Promise<number> {
|
||||
return foo();
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
function /*[#|*/f/*|]*/(): Promise<number> {
|
||||
return foo().finally(undefined);
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
async function f(): Promise<number> {
|
||||
return foo();
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
function /*[#|*/f/*|]*/(): Promise<number> {
|
||||
return foo().then(x => x + 1).finally(() => console.log("done"));
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
async function f(): Promise<number> {
|
||||
try {
|
||||
const x = await foo();
|
||||
return x + 1;
|
||||
} finally {
|
||||
return console.log("done");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
function /*[#|*/f/*|]*/(): Promise<number> {
|
||||
return foo().then(x => Promise.resolve(x + 1)).finally(() => console.log("done")).then(y => y + 2);
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
async function f(): Promise<number> {
|
||||
let y: number;
|
||||
try {
|
||||
const x = await foo();
|
||||
y = await Promise.resolve(x + 1);
|
||||
} finally {
|
||||
console.log("done");
|
||||
}
|
||||
return y + 2;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// ==ORIGINAL==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
function /*[#|*/f/*|]*/(): Promise<number> {
|
||||
return foo().then();
|
||||
}
|
||||
// ==ASYNC FUNCTION::Convert to async function==
|
||||
|
||||
declare function foo(): Promise<number>;
|
||||
async function f(): Promise<number> {
|
||||
return foo();
|
||||
}
|
Loading…
Reference in a new issue