Skip to content

Commit 421730a

Browse files
Refactoring promises returning functions to use async and await (#26373)
1 parent 08f5edb commit 421730a

File tree

82 files changed

+3589
-6
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+3589
-6
lines changed

src/compiler/checker.ts

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ namespace ts {
120120
return node ? getTypeFromTypeNode(node) : errorType;
121121
},
122122
getParameterType: getTypeAtPosition,
123+
getPromisedTypeOfPromise,
123124
getReturnTypeOfSignature,
124125
getNullableType,
125126
getNonNullableType,

src/compiler/diagnosticMessages.json

+14-2
Original file line numberDiff line numberDiff line change
@@ -4183,7 +4183,10 @@
41834183
"category": "Suggestion",
41844184
"code": 80005
41854185
},
4186-
4186+
"This may be converted to an async function.": {
4187+
"category": "Suggestion",
4188+
"code": 80006
4189+
},
41874190
"Add missing 'super()' call": {
41884191
"category": "Message",
41894192
"code": 90001
@@ -4556,12 +4559,21 @@
45564559
"category": "Message",
45574560
"code": 95062
45584561
},
4562+
45594563
"Add missing enum member '{0}'": {
45604564
"category": "Message",
45614565
"code": 95063
45624566
},
45634567
"Add all missing imports": {
45644568
"category": "Message",
45654569
"code": 95064
4570+
},
4571+
"Convert to async function":{
4572+
"category": "Message",
4573+
"code": 95065
4574+
},
4575+
"Convert all to async functions": {
4576+
"category": "Message",
4577+
"code": 95066
45664578
}
4567-
}
4579+
}

src/compiler/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2911,6 +2911,8 @@ namespace ts {
29112911
getBaseTypes(type: InterfaceType): BaseType[];
29122912
getBaseTypeOfLiteralType(type: Type): Type;
29132913
getWidenedType(type: Type): Type;
2914+
/* @internal */
2915+
getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined;
29142916
getReturnTypeOfSignature(signature: Signature): Type;
29152917
/**
29162918
* Gets the type of a parameter at a given position in a signature.

src/services/codefixes/convertToAsyncFunction.ts

+538
Large diffs are not rendered by default.

src/services/suggestionDiagnostics.ts

+61
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace ts {
33
export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] {
44
program.getSemanticDiagnostics(sourceFile, cancellationToken);
55
const diags: DiagnosticWithLocation[] = [];
6+
const checker = program.getDiagnosticsProducingTypeChecker();
67

78
if (sourceFile.commonJsModuleIndicator &&
89
(programContainsEs6Modules(program) || compilerOptionsIndicateEs6Modules(program.getCompilerOptions())) &&
@@ -68,6 +69,9 @@ namespace ts {
6869
}
6970
}
7071

72+
if (isFunctionLikeDeclaration(node)) {
73+
addConvertToAsyncFunctionDiagnostics(node, checker, diags);
74+
}
7175
node.forEachChild(check);
7276
}
7377
}
@@ -109,7 +113,64 @@ namespace ts {
109113
}
110114
}
111115

116+
function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]): void {
117+
118+
const functionType = node.type ? checker.getTypeFromTypeNode(node.type) : undefined;
119+
if (isAsyncFunction(node) || !node.body || !functionType) {
120+
return;
121+
}
122+
123+
const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call);
124+
const returnType = callSignatures.length ? checker.getReturnTypeOfSignature(callSignatures[0]) : undefined;
125+
126+
if (!returnType || !checker.getPromisedTypeOfPromise(returnType)) {
127+
return;
128+
}
129+
130+
// collect all the return statements
131+
// check that a property access expression exists in there and that it is a handler
132+
const returnStatements = getReturnStatementsWithPromiseHandlers(node);
133+
if (returnStatements.length > 0) {
134+
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function));
135+
}
136+
}
137+
112138
function getErrorNodeFromCommonJsIndicator(commonJsModuleIndicator: Node): Node {
113139
return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator;
114140
}
141+
142+
/** @internal */
143+
export function getReturnStatementsWithPromiseHandlers(node: Node): Node[] {
144+
const returnStatements: Node[] = [];
145+
if (isFunctionLike(node)) {
146+
forEachChild(node, visit);
147+
}
148+
else {
149+
visit(node);
150+
}
151+
152+
function visit(child: Node) {
153+
if (isFunctionLike(child)) {
154+
return;
155+
}
156+
157+
if (isReturnStatement(child)) {
158+
forEachChild(child, addHandlers);
159+
}
160+
161+
function addHandlers(returnChild: Node) {
162+
if (isPromiseHandler(returnChild)) {
163+
returnStatements.push(child as ReturnStatement);
164+
}
165+
}
166+
167+
forEachChild(child, visit);
168+
}
169+
return returnStatements;
170+
}
171+
172+
function isPromiseHandler(node: Node): boolean {
173+
return (isCallExpression(node) && isPropertyAccessExpression(node.expression) &&
174+
(node.expression.name.text === "then" || node.expression.name.text === "catch"));
175+
}
115176
}

src/services/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"codefixes/addMissingInvocationForDecorator.ts",
4747
"codefixes/annotateWithTypeFromJSDoc.ts",
4848
"codefixes/convertFunctionToEs6Class.ts",
49+
"codefixes/convertToAsyncFunction.ts",
4950
"codefixes/convertToEs6Module.ts",
5051
"codefixes/correctQualifiedNameToIndexedAccessType.ts",
5152
"codefixes/fixClassIncorrectlyImplementsInterface.ts",

src/services/utilities.ts

+34-4
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ namespace ts {
226226

227227
export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } {
228228
return node.kind === SyntaxKind.Identifier && isBreakOrContinueStatement(node.parent) && node.parent.label === node;
229-
}
229+
}
230230

231231
export function isLabelOfLabeledStatement(node: Node): node is Identifier {
232232
return node.kind === SyntaxKind.Identifier && isLabeledStatement(node.parent) && node.parent.label === node;
@@ -396,7 +396,7 @@ namespace ts {
396396
export function isThis(node: Node): boolean {
397397
switch (node.kind) {
398398
case SyntaxKind.ThisKeyword:
399-
// case SyntaxKind.ThisType: TODO: GH#9267
399+
// case SyntaxKind.ThisType: TODO: GH#9267
400400
return true;
401401
case SyntaxKind.Identifier:
402402
// 'this' as a parameter
@@ -1656,8 +1656,34 @@ namespace ts {
16561656
return clone;
16571657
}
16581658

1659-
function getSynthesizedDeepCloneWorker<T extends Node>(node: T): T {
1660-
const visited = visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext);
1659+
export function getSynthesizedDeepCloneWithRenames<T extends Node | undefined>(node: T, includeTrivia = true, renameMap?: Map<Identifier>, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T {
1660+
1661+
let clone;
1662+
if (node && isIdentifier(node!) && renameMap && checker) {
1663+
const symbol = checker.getSymbolAtLocation(node!);
1664+
const renameInfo = symbol && renameMap.get(String(getSymbolId(symbol)));
1665+
1666+
if (renameInfo) {
1667+
clone = createIdentifier(renameInfo.text);
1668+
}
1669+
}
1670+
1671+
if (!clone) {
1672+
clone = node && getSynthesizedDeepCloneWorker(node as NonNullable<T>, renameMap, checker, callback);
1673+
}
1674+
1675+
if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone);
1676+
if (callback && node) callback(node!, clone);
1677+
1678+
return clone as T;
1679+
}
1680+
1681+
1682+
function getSynthesizedDeepCloneWorker<T extends Node>(node: T, renameMap?: Map<Identifier>, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T {
1683+
const visited = (renameMap || checker || callback) ?
1684+
visitEachChild(node, wrapper, nullTransformationContext) :
1685+
visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext);
1686+
16611687
if (visited === node) {
16621688
// This only happens for leaf nodes - internal nodes always see their children change.
16631689
const clone = getSynthesizedClone(node);
@@ -1675,6 +1701,10 @@ namespace ts {
16751701
// would have made.
16761702
visited.parent = undefined!;
16771703
return visited;
1704+
1705+
function wrapper(node: T) {
1706+
return getSynthesizedDeepCloneWithRenames(node, /*includeTrivia*/ true, renameMap, checker, callback);
1707+
}
16781708
}
16791709

16801710
export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T>, includeTrivia?: boolean): NodeArray<T>;

src/testRunner/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"unittests/compileOnSave.ts",
4949
"unittests/configurationExtension.ts",
5050
"unittests/convertCompilerOptionsFromJson.ts",
51+
"unittests/convertToAsyncFunction.ts",
5152
"unittests/convertToBase64.ts",
5253
"unittests/convertTypeAcquisitionFromJson.ts",
5354
"unittests/customTransforms.ts",

0 commit comments

Comments
 (0)