diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 6474c84df9b08..50e00740c680d 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -302,7 +302,6 @@ namespace ts.codefix { 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)); @@ -310,10 +309,8 @@ namespace ts.codefix { 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)]; } - return transformExpression(node.expression, transformer, onFulfilledArgumentName).concat(transformationBody); } @@ -395,11 +392,12 @@ namespace ts.codefix { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: { const funcBody = (func as FunctionExpression | ArrowFunction).body; + const returnType = getLastCallSignature(transformer.checker.getTypeAtLocation(func), transformer.checker)?.getReturnType(); + // Arrow functions with block bodies { } will enter this control flow if (isBlock(funcBody)) { let refactoredStmts: Statement[] = []; let seenReturnStatement = false; - for (const statement of funcBody.statements) { if (isReturnStatement(statement)) { seenReturnStatement = true; @@ -407,7 +405,8 @@ namespace ts.codefix { refactoredStmts = refactoredStmts.concat(getInnerTransformationBody(transformer, [statement], prevArgName)); } else { - refactoredStmts.push(...maybeAnnotateAndReturn(statement.expression, parent.typeArguments?.[0])); + const possiblyAwaitedRightHandSide = returnType && statement.expression ? getPossiblyAwaitedRightHandSide(transformer.checker, returnType, statement.expression) : statement.expression; + refactoredStmts.push(...maybeAnnotateAndReturn(possiblyAwaitedRightHandSide, parent.typeArguments?.[0])); } } else { @@ -431,19 +430,21 @@ namespace ts.codefix { return innerCbBody; } - const type = transformer.checker.getTypeAtLocation(func); - const returnType = getLastCallSignature(type, transformer.checker)!.getReturnType(); - const rightHandSide = getSynthesizedDeepClone(funcBody); - const possiblyAwaitedRightHandSide = !!transformer.checker.getPromisedTypeOfPromise(returnType) ? factory.createAwaitExpression(rightHandSide) : rightHandSide; - if (!shouldReturn(parent, transformer)) { - const transformedStatement = createVariableOrAssignmentOrExpressionStatement(prevArgName, possiblyAwaitedRightHandSide, /*typeAnnotation*/ undefined); - if (prevArgName) { - prevArgName.types.push(returnType); + 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); + } + return transformedStatement; + } + else { + return maybeAnnotateAndReturn(possiblyAwaitedRightHandSide, parent.typeArguments?.[0]); } - return transformedStatement; } else { - return maybeAnnotateAndReturn(possiblyAwaitedRightHandSide, parent.typeArguments?.[0]); + return silentFail(); } } } @@ -454,12 +455,16 @@ namespace ts.codefix { return emptyArray; } + function getPossiblyAwaitedRightHandSide(checker: TypeChecker, type: Type, expr: Expression): AwaitExpression | Expression { + const rightHandSide = getSynthesizedDeepClone(expr); + return !!checker.getPromisedTypeOfPromise(type) ? factory.createAwaitExpression(rightHandSide) : rightHandSide; + } + function getLastCallSignature(type: Type, checker: TypeChecker): Signature | undefined { const callSignatures = checker.getSignaturesOfType(type, SignatureKind.Call); return lastOrUndefined(callSignatures); } - function removeReturns(stmts: readonly Statement[], prevArgName: SynthBindingName | undefined, transformer: Transformer, seenReturnStatement: boolean): readonly Statement[] { const ret: Statement[] = []; for (const stmt of stmts) { diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index 952f6bf4d68e7..183f7bccb4237 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -902,7 +902,7 @@ function [#|f|](): Promise { } ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen", ` + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen1", ` function [#|f|]() { return Promise.resolve().then(function () { return Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { @@ -921,6 +921,26 @@ function [#|f|]() { })]).then(res => res.toString()); }); } +` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen3", ` +function [#|f|]() { + return Promise.resolve().then(() => + Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { + return fetch("https://github.com"); + }).then(res => res.toString())])); +} +` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen4", ` +function [#|f|]() { + return Promise.resolve().then(() => + Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { + return fetch("https://github.com"); + })]).then(res => res.toString())); +} ` ); _testConvertToAsyncFunction("convertToAsyncFunction_Scope1", ` @@ -1124,6 +1144,27 @@ function [#|bar|](x: T): Promise { ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_Return1", ` +function [#|f|](p: Promise) { + return p.catch((error: Error) => { + return Promise.reject(error); + }); +}` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_Return2", ` +function [#|f|](p: Promise) { + return p.catch((error: Error) => Promise.reject(error)); +}` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_Return3", ` +function [#|f|](p: Promise) { + return p.catch(function (error: Error) { + return Promise.reject(error); + }); +}` + ); _testConvertToAsyncFunction("convertToAsyncFunction_LocalReturn", ` function [#|f|]() { @@ -1352,7 +1393,7 @@ function [#|f|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_noArgs", ` + _testConvertToAsyncFunction("convertToAsyncFunction_noArgs1", ` function delay(millis: number): Promise { throw "no" } @@ -1364,7 +1405,21 @@ function [#|main2|]() { .then(() => { console.log("."); return delay(500); }) .then(() => { console.log("."); return delay(500); }) } -`); + `); + + _testConvertToAsyncFunction("convertToAsyncFunction_noArgs2", ` +function delay(millis: number): Promise { + throw "no" +} + +function [#|main2|]() { + console.log("Please wait. Loading."); + return delay(500) + .then(() => delay(500)) + .then(() => delay(500)) + .then(() => delay(500)) +} + `); _testConvertToAsyncFunction("convertToAsyncFunction_exportModifier", ` export function [#|foo|]() { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen1.js similarity index 79% rename from tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.js rename to tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen1.js index 8b4b5afd88ca0..3dea02ec3b1dd 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen1.js @@ -12,7 +12,7 @@ function /*[#|*/f/*|]*/() { async function f() { await Promise.resolve(); - return Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function() { + return await Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function() { return fetch("https://github.com"); }).then(res => res.toString())]); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen1.ts similarity index 79% rename from tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.ts rename to tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen1.ts index 8b4b5afd88ca0..3dea02ec3b1dd 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen1.ts @@ -12,7 +12,7 @@ function /*[#|*/f/*|]*/() { async function f() { await Promise.resolve(); - return Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function() { + return await Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function() { return fetch("https://github.com"); }).then(res => res.toString())]); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen3.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen3.js new file mode 100644 index 0000000000000..5219dc7358c7f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen3.js @@ -0,0 +1,17 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(() => + Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { + return fetch("https://github.com"); + }).then(res => res.toString())])); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + await Promise.resolve(); + return await Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function() { + return fetch("https://github.com"); + }).then(res => res.toString())]); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen3.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen3.ts new file mode 100644 index 0000000000000..5219dc7358c7f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen3.ts @@ -0,0 +1,17 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(() => + Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { + return fetch("https://github.com"); + }).then(res => res.toString())])); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + await Promise.resolve(); + return await Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function() { + return fetch("https://github.com"); + }).then(res => res.toString())]); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen4.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen4.js new file mode 100644 index 0000000000000..f3be8d3479c71 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen4.js @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(() => + Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { + return fetch("https://github.com"); + })]).then(res => res.toString())); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + await Promise.resolve(); + const res = await Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function() { + return fetch("https://github.com"); + })]); + return res.toString(); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen4.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen4.ts new file mode 100644 index 0000000000000..f3be8d3479c71 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen4.ts @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(() => + Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { + return fetch("https://github.com"); + })]).then(res => res.toString())); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + await Promise.resolve(); + const res = await Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function() { + return fetch("https://github.com"); + })]); + return res.toString(); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Return1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Return1.ts new file mode 100644 index 0000000000000..75e1d23902e86 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Return1.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(p: Promise) { + return p.catch((error: Error) => { + return Promise.reject(error); + }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(p: Promise) { + try { + return p; + } catch (error) { + return await Promise.reject(error); + } +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Return2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Return2.ts new file mode 100644 index 0000000000000..01fea0275c6f8 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Return2.ts @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(p: Promise) { + return p.catch((error: Error) => Promise.reject(error)); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(p: Promise) { + try { + return p; + } catch (error) { + return await Promise.reject(error); + } +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Return3.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Return3.ts new file mode 100644 index 0000000000000..43cbe7e36b4e9 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Return3.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(p: Promise) { + return p.catch(function (error: Error) { + return Promise.reject(error); + }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(p: Promise) { + try { + return p; + } catch (error) { + return await Promise.reject(error); + } +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_noArgs.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_noArgs1.ts similarity index 93% rename from tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_noArgs.ts rename to tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_noArgs1.ts index 088bf9f828e76..d5be5a9f05488 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_noArgs.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_noArgs1.ts @@ -11,7 +11,7 @@ function /*[#|*/main2/*|]*/() { .then(() => { console.log("."); return delay(500); }) .then(() => { console.log("."); return delay(500); }) } - + // ==ASYNC FUNCTION::Convert to async function== function delay(millis: number): Promise { @@ -26,5 +26,6 @@ async function main2() { console.log("."); await delay(500); console.log("."); - return delay(500); + return await delay(500); } + \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_noArgs2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_noArgs2.ts new file mode 100644 index 0000000000000..04f75d9d78f84 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_noArgs2.ts @@ -0,0 +1,28 @@ +// ==ORIGINAL== + +function delay(millis: number): Promise { + throw "no" +} + +function /*[#|*/main2/*|]*/() { + console.log("Please wait. Loading."); + return delay(500) + .then(() => delay(500)) + .then(() => delay(500)) + .then(() => delay(500)) +} + +// ==ASYNC FUNCTION::Convert to async function== + +function delay(millis: number): Promise { + throw "no" +} + +async function main2() { + console.log("Please wait. Loading."); + await delay(500); + await delay(500); + await delay(500); + return await delay(500); +} + \ No newline at end of file