1/* @internal */ 2namespace ts.codefix { 3 const fixId = "convertToAsyncFunction"; 4 const errorCodes = [Diagnostics.This_may_be_converted_to_an_async_function.code]; 5 let codeActionSucceeded = true; 6 registerCodeFix({ 7 errorCodes, 8 getCodeActions(context: CodeFixContext) { 9 codeActionSucceeded = true; 10 const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncFunction(t, context.sourceFile, context.span.start, context.program.getTypeChecker())); 11 return codeActionSucceeded ? [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_async_function, fixId, Diagnostics.Convert_all_to_async_functions)] : []; 12 }, 13 fixIds: [fixId], 14 getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file, err.start, context.program.getTypeChecker())), 15 }); 16 17 const enum SynthBindingNameKind { 18 Identifier, 19 BindingPattern, 20 } 21 22 type SynthBindingName = SynthBindingPattern | SynthIdentifier; 23 24 interface SynthBindingPattern { 25 readonly kind: SynthBindingNameKind.BindingPattern; 26 readonly elements: readonly SynthBindingName[]; 27 readonly bindingPattern: BindingPattern; 28 readonly types: Type[]; 29 } 30 31 interface SynthIdentifier { 32 readonly kind: SynthBindingNameKind.Identifier; 33 readonly identifier: Identifier; 34 readonly types: Type[]; 35 /** A declaration for this identifier has already been generated */ 36 hasBeenDeclared: boolean; 37 } 38 39 interface Transformer { 40 readonly checker: TypeChecker; 41 readonly synthNamesMap: ESMap<string, SynthIdentifier>; // keys are the symbol id of the identifier 42 readonly setOfExpressionsToReturn: ReadonlySet<number>; // keys are the node ids of the expressions 43 readonly isInJSFile: boolean; 44 } 45 46 function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { 47 // get the function declaration - returns a promise 48 const tokenAtPosition = getTokenAtPosition(sourceFile, position); 49 let functionToConvert: FunctionLikeDeclaration | undefined; 50 51 // if the parent of a FunctionLikeDeclaration is a variable declaration, the convertToAsync diagnostic will be reported on the variable name 52 if (isIdentifier(tokenAtPosition) && isVariableDeclaration(tokenAtPosition.parent) && 53 tokenAtPosition.parent.initializer && isFunctionLikeDeclaration(tokenAtPosition.parent.initializer)) { 54 functionToConvert = tokenAtPosition.parent.initializer; 55 } 56 else { 57 functionToConvert = tryCast(getContainingFunction(getTokenAtPosition(sourceFile, position)), isFunctionLikeDeclaration); 58 } 59 60 if (!functionToConvert) { 61 return; 62 } 63 64 const synthNamesMap = new Map<string, SynthIdentifier>(); 65 const isInJavascript = isInJSFile(functionToConvert); 66 const setOfExpressionsToReturn = getAllPromiseExpressionsToReturn(functionToConvert, checker); 67 const functionToConvertRenamed = renameCollidingVarNames(functionToConvert, checker, synthNamesMap); 68 const returnStatements = functionToConvertRenamed.body && isBlock(functionToConvertRenamed.body) ? getReturnStatementsWithPromiseHandlers(functionToConvertRenamed.body, checker) : emptyArray; 69 const transformer: Transformer = { checker, synthNamesMap, setOfExpressionsToReturn, isInJSFile: isInJavascript }; 70 if (!returnStatements.length) { 71 return; 72 } 73 74 const pos = functionToConvert.modifiers ? functionToConvert.modifiers.end : 75 functionToConvert.decorators ? skipTrivia(sourceFile.text, functionToConvert.decorators.end) : 76 functionToConvert.getStart(sourceFile); 77 const options = functionToConvert.modifiers ? { prefix: " " } : { suffix: " " }; 78 changes.insertModifierAt(sourceFile, pos, SyntaxKind.AsyncKeyword, options); 79 80 for (const returnStatement of returnStatements) { 81 forEachChild(returnStatement, function visit(node) { 82 if (isCallExpression(node)) { 83 const newNodes = transformExpression(node, transformer); 84 changes.replaceNodeWithNodes(sourceFile, returnStatement, newNodes); 85 } 86 else if (!isFunctionLike(node)) { 87 forEachChild(node, visit); 88 } 89 }); 90 } 91 } 92 93 function getReturnStatementsWithPromiseHandlers(body: Block, checker: TypeChecker): readonly ReturnStatement[] { 94 const res: ReturnStatement[] = []; 95 forEachReturnStatement(body, ret => { 96 if (isReturnStatementWithFixablePromiseHandler(ret, checker)) res.push(ret); 97 }); 98 return res; 99 } 100 101 /* 102 Finds all of the expressions of promise type that should not be saved in a variable during the refactor 103 */ 104 function getAllPromiseExpressionsToReturn(func: FunctionLikeDeclaration, checker: TypeChecker): Set<number> { 105 if (!func.body) { 106 return new Set(); 107 } 108 109 const setOfExpressionsToReturn = new Set<number>(); 110 forEachChild(func.body, function visit(node: Node) { 111 if (isPromiseReturningCallExpression(node, checker, "then")) { 112 setOfExpressionsToReturn.add(getNodeId(node)); 113 forEach(node.arguments, visit); 114 } 115 else if (isPromiseReturningCallExpression(node, checker, "catch")) { 116 setOfExpressionsToReturn.add(getNodeId(node)); 117 // if .catch() is the last call in the chain, move leftward in the chain until we hit something else that should be returned 118 forEachChild(node, visit); 119 } 120 else if (isPromiseTypedExpression(node, checker)) { 121 setOfExpressionsToReturn.add(getNodeId(node)); 122 // don't recurse here, since we won't refactor any children or arguments of the expression 123 } 124 else { 125 forEachChild(node, visit); 126 } 127 }); 128 129 return setOfExpressionsToReturn; 130 } 131 132 function isPromiseReturningCallExpression(node: Node, checker: TypeChecker, name: string): node is CallExpression { 133 if (!isCallExpression(node)) return false; 134 const isExpressionOfName = hasPropertyAccessExpressionWithName(node, name); 135 const nodeType = isExpressionOfName && checker.getTypeAtLocation(node); 136 return !!(nodeType && checker.getPromisedTypeOfPromise(nodeType)); 137 } 138 139 function isPromiseTypedExpression(node: Node, checker: TypeChecker): node is Expression { 140 if (!isExpression(node)) return false; 141 return !!checker.getPromisedTypeOfPromise(checker.getTypeAtLocation(node)); 142 } 143 144 /* 145 Renaming of identifiers may be necessary as the refactor changes scopes - 146 This function collects all existing identifier names and names of identifiers that will be created in the refactor. 147 It then checks for any collisions and renames them through getSynthesizedDeepClone 148 */ 149 function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: ESMap<string, SynthIdentifier>): FunctionLikeDeclaration { 150 const identsToRenameMap = new Map<string, Identifier>(); // key is the symbol id 151 const collidingSymbolMap = createMultiMap<Symbol>(); 152 forEachChild(nodeToRename, function visit(node: Node) { 153 if (!isIdentifier(node)) { 154 forEachChild(node, visit); 155 return; 156 } 157 const symbol = checker.getSymbolAtLocation(node); 158 if (symbol) { 159 const type = checker.getTypeAtLocation(node); 160 // Note - the choice of the last call signature is arbitrary 161 const lastCallSignature = getLastCallSignature(type, checker); 162 const symbolIdString = getSymbolId(symbol).toString(); 163 164 // If the identifier refers to a function, we want to add the new synthesized variable for the declaration. Example: 165 // fetch('...').then(response => { ... }) 166 // will eventually become 167 // const response = await fetch('...') 168 // so we push an entry for 'response'. 169 if (lastCallSignature && !isParameter(node.parent) && !isFunctionLikeDeclaration(node.parent) && !synthNamesMap.has(symbolIdString)) { 170 const firstParameter = firstOrUndefined(lastCallSignature.parameters); 171 const ident = firstParameter && isParameter(firstParameter.valueDeclaration) && tryCast(firstParameter.valueDeclaration.name, isIdentifier) || factory.createUniqueName("result", GeneratedIdentifierFlags.Optimistic); 172 const synthName = getNewNameIfConflict(ident, collidingSymbolMap); 173 synthNamesMap.set(symbolIdString, synthName); 174 collidingSymbolMap.add(ident.text, symbol); 175 } 176 // We only care about identifiers that are parameters, variable declarations, or binding elements 177 else if (node.parent && (isParameter(node.parent) || isVariableDeclaration(node.parent) || isBindingElement(node.parent))) { 178 const originalName = node.text; 179 const collidingSymbols = collidingSymbolMap.get(originalName); 180 181 // if the identifier name conflicts with a different identifier that we've already seen 182 if (collidingSymbols && collidingSymbols.some(prevSymbol => prevSymbol !== symbol)) { 183 const newName = getNewNameIfConflict(node, collidingSymbolMap); 184 identsToRenameMap.set(symbolIdString, newName.identifier); 185 synthNamesMap.set(symbolIdString, newName); 186 collidingSymbolMap.add(originalName, symbol); 187 } 188 else { 189 const identifier = getSynthesizedDeepClone(node); 190 synthNamesMap.set(symbolIdString, createSynthIdentifier(identifier)); 191 collidingSymbolMap.add(originalName, symbol); 192 } 193 } 194 } 195 }); 196 197 return getSynthesizedDeepCloneWithReplacements(nodeToRename, /*includeTrivia*/ true, original => { 198 if (isBindingElement(original) && isIdentifier(original.name) && isObjectBindingPattern(original.parent)) { 199 const symbol = checker.getSymbolAtLocation(original.name); 200 const renameInfo = symbol && identsToRenameMap.get(String(getSymbolId(symbol))); 201 if (renameInfo && renameInfo.text !== (original.name || original.propertyName).getText()) { 202 return factory.createBindingElement( 203 original.dotDotDotToken, 204 original.propertyName || original.name, 205 renameInfo, 206 original.initializer); 207 } 208 } 209 else if (isIdentifier(original)) { 210 const symbol = checker.getSymbolAtLocation(original); 211 const renameInfo = symbol && identsToRenameMap.get(String(getSymbolId(symbol))); 212 if (renameInfo) { 213 return factory.createIdentifier(renameInfo.text); 214 } 215 } 216 }); 217 } 218 219 function getNewNameIfConflict(name: Identifier, originalNames: ReadonlyESMap<string, Symbol[]>): SynthIdentifier { 220 const numVarsSameName = (originalNames.get(name.text) || emptyArray).length; 221 const identifier = numVarsSameName === 0 ? name : factory.createIdentifier(name.text + "_" + numVarsSameName); 222 return createSynthIdentifier(identifier); 223 } 224 225 function silentFail() { 226 codeActionSucceeded = false; 227 return emptyArray; 228 } 229 230 // dispatch function to recursively build the refactoring 231 // should be kept up to date with isFixablePromiseHandler in suggestionDiagnostics.ts 232 function transformExpression(node: Expression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] { 233 if (isPromiseReturningCallExpression(node, transformer.checker, "then")) { 234 if (node.arguments.length === 0) return silentFail(); 235 return transformThen(node, transformer, prevArgName); 236 } 237 if (isPromiseReturningCallExpression(node, transformer.checker, "catch")) { 238 return transformCatch(node, transformer, prevArgName); 239 } 240 if (isPropertyAccessExpression(node)) { 241 return transformExpression(node.expression, transformer, prevArgName); 242 } 243 244 const nodeType = transformer.checker.getTypeAtLocation(node); 245 if (nodeType && transformer.checker.getPromisedTypeOfPromise(nodeType)) { 246 Debug.assertNode(node.original!.parent, isPropertyAccessExpression); 247 return transformPromiseExpressionOfPropertyAccess(node, transformer, prevArgName); 248 } 249 250 return silentFail(); 251 } 252 253 function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] { 254 const func = singleOrUndefined(node.arguments); 255 const argName = func ? getArgBindingName(func, transformer) : undefined; 256 let possibleNameForVarDecl: SynthIdentifier | undefined; 257 258 // 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) 259 // To do this, we will need to synthesize a variable that we were not aware of while we were adding identifiers to the synthNamesMap 260 // We will use the prevArgName and then update the synthNamesMap with a new variable name for the next transformation step 261 if (prevArgName && !shouldReturn(node, transformer)) { 262 if (isSynthIdentifier(prevArgName)) { 263 possibleNameForVarDecl = prevArgName; 264 transformer.synthNamesMap.forEach((val, key) => { 265 if (val.identifier.text === prevArgName.identifier.text) { 266 const newSynthName = createUniqueSynthName(prevArgName); 267 transformer.synthNamesMap.set(key, newSynthName); 268 } 269 }); 270 } 271 else { 272 possibleNameForVarDecl = createSynthIdentifier(factory.createUniqueName("result", GeneratedIdentifierFlags.Optimistic), prevArgName.types); 273 } 274 275 // We are about to write a 'let' variable declaration, but `transformExpression` for both 276 // the try block and catch block will assign to this name. Setting this flag indicates 277 // that future assignments should be written as `name = value` instead of `const name = value`. 278 possibleNameForVarDecl.hasBeenDeclared = true; 279 } 280 281 const tryBlock = factory.createBlock(transformExpression(node.expression, transformer, possibleNameForVarDecl)); 282 const transformationBody = func ? getTransformationBody(func, possibleNameForVarDecl, argName, node, transformer) : emptyArray; 283 const catchArg = argName ? isSynthIdentifier(argName) ? argName.identifier.text : argName.bindingPattern : "e"; 284 const catchVariableDeclaration = factory.createVariableDeclaration(catchArg); 285 const catchClause = factory.createCatchClause(catchVariableDeclaration, factory.createBlock(transformationBody)); 286 287 // 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) 288 let varDeclList: VariableStatement | undefined; 289 let varDeclIdentifier: Identifier | undefined; 290 if (possibleNameForVarDecl && !shouldReturn(node, transformer)) { 291 varDeclIdentifier = getSynthesizedDeepClone(possibleNameForVarDecl.identifier); 292 const typeArray: Type[] = possibleNameForVarDecl.types; 293 const unionType = transformer.checker.getUnionType(typeArray, UnionReduction.Subtype); 294 const unionTypeNode = transformer.isInJSFile ? undefined : transformer.checker.typeToTypeNode(unionType, /*enclosingDeclaration*/ undefined, /*flags*/ undefined); 295 const varDecl = [factory.createVariableDeclaration(varDeclIdentifier, /*exclamationToken*/ undefined, unionTypeNode)]; 296 varDeclList = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList(varDecl, NodeFlags.Let)); 297 } 298 299 const tryStatement = factory.createTryStatement(tryBlock, catchClause, /*finallyBlock*/ undefined); 300 const destructuredResult = prevArgName && varDeclIdentifier && isSynthBindingPattern(prevArgName) 301 && factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(getSynthesizedDeepClone(prevArgName.bindingPattern), /*exclamationToken*/ undefined, /*type*/ undefined, varDeclIdentifier)], NodeFlags.Const)); 302 return compact([varDeclList, tryStatement, destructuredResult]); 303 } 304 305 function createUniqueSynthName(prevArgName: SynthIdentifier): SynthIdentifier { 306 const renamedPrevArg = factory.createUniqueName(prevArgName.identifier.text, GeneratedIdentifierFlags.Optimistic); 307 return createSynthIdentifier(renamedPrevArg); 308 } 309 310 function transformThen(node: CallExpression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] { 311 const [onFulfilled, onRejected] = node.arguments; 312 const onFulfilledArgumentName = getArgBindingName(onFulfilled, transformer); 313 const transformationBody = getTransformationBody(onFulfilled, prevArgName, onFulfilledArgumentName, node, transformer); 314 if (onRejected) { 315 const onRejectedArgumentName = getArgBindingName(onRejected, transformer); 316 const tryBlock = factory.createBlock(transformExpression(node.expression, transformer, onFulfilledArgumentName).concat(transformationBody)); 317 const transformationBody2 = getTransformationBody(onRejected, prevArgName, onRejectedArgumentName, node, transformer); 318 const catchArg = onRejectedArgumentName ? isSynthIdentifier(onRejectedArgumentName) ? onRejectedArgumentName.identifier.text : onRejectedArgumentName.bindingPattern : "e"; 319 const catchVariableDeclaration = factory.createVariableDeclaration(catchArg); 320 const catchClause = factory.createCatchClause(catchVariableDeclaration, factory.createBlock(transformationBody2)); 321 return [factory.createTryStatement(tryBlock, catchClause, /* finallyBlock */ undefined)]; 322 } 323 return transformExpression(node.expression, transformer, onFulfilledArgumentName).concat(transformationBody); 324 } 325 326 /** 327 * Transforms the 'x' part of `x.then(...)`, or the 'y()' part of `y().catch(...)`, where 'x' and 'y()' are Promises. 328 */ 329 function transformPromiseExpressionOfPropertyAccess(node: Expression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] { 330 if (shouldReturn(node, transformer)) { 331 return [factory.createReturnStatement(getSynthesizedDeepClone(node))]; 332 } 333 334 return createVariableOrAssignmentOrExpressionStatement(prevArgName, factory.createAwaitExpression(node), /*typeAnnotation*/ undefined); 335 } 336 337 function createVariableOrAssignmentOrExpressionStatement(variableName: SynthBindingName | undefined, rightHandSide: Expression, typeAnnotation: TypeNode | undefined): readonly Statement[] { 338 if (!variableName || isEmptyBindingName(variableName)) { 339 // if there's no argName to assign to, there still might be side effects 340 return [factory.createExpressionStatement(rightHandSide)]; 341 } 342 343 if (isSynthIdentifier(variableName) && variableName.hasBeenDeclared) { 344 // if the variable has already been declared, we don't need "let" or "const" 345 return [factory.createExpressionStatement(factory.createAssignment(getSynthesizedDeepClone(variableName.identifier), rightHandSide))]; 346 } 347 348 return [ 349 factory.createVariableStatement( 350 /*modifiers*/ undefined, 351 factory.createVariableDeclarationList([ 352 factory.createVariableDeclaration( 353 getSynthesizedDeepClone(getNode(variableName)), 354 /*exclamationToken*/ undefined, 355 typeAnnotation, 356 rightHandSide)], 357 NodeFlags.Const))]; 358 } 359 360 function maybeAnnotateAndReturn(expressionToReturn: Expression | undefined, typeAnnotation: TypeNode | undefined): readonly Statement[] { 361 if (typeAnnotation && expressionToReturn) { 362 const name = factory.createUniqueName("result", GeneratedIdentifierFlags.Optimistic); 363 return [ 364 ...createVariableOrAssignmentOrExpressionStatement(createSynthIdentifier(name), expressionToReturn, typeAnnotation), 365 factory.createReturnStatement(name) 366 ]; 367 } 368 return [factory.createReturnStatement(expressionToReturn)]; 369 } 370 371 // should be kept up to date with isFixablePromiseArgument in suggestionDiagnostics.ts 372 function getTransformationBody(func: Expression, prevArgName: SynthBindingName | undefined, argName: SynthBindingName | undefined, parent: CallExpression, transformer: Transformer): readonly Statement[] { 373 switch (func.kind) { 374 case SyntaxKind.NullKeyword: 375 // do not produce a transformed statement for a null argument 376 break; 377 case SyntaxKind.PropertyAccessExpression: 378 case SyntaxKind.Identifier: // identifier includes undefined 379 if (!argName) { 380 // undefined was argument passed to promise handler 381 break; 382 } 383 384 const synthCall = factory.createCallExpression(getSynthesizedDeepClone(func as Identifier | PropertyAccessExpression), /*typeArguments*/ undefined, isSynthIdentifier(argName) ? [argName.identifier] : []); 385 if (shouldReturn(parent, transformer)) { 386 return maybeAnnotateAndReturn(synthCall, parent.typeArguments?.[0]); 387 } 388 389 const type = transformer.checker.getTypeAtLocation(func); 390 const callSignatures = transformer.checker.getSignaturesOfType(type, SignatureKind.Call); 391 if (!callSignatures.length) { 392 // if identifier in handler has no call signatures, it's invalid 393 return silentFail(); 394 } 395 const returnType = callSignatures[0].getReturnType(); 396 const varDeclOrAssignment = createVariableOrAssignmentOrExpressionStatement(prevArgName, factory.createAwaitExpression(synthCall), parent.typeArguments?.[0]); 397 if (prevArgName) { 398 prevArgName.types.push(returnType); 399 } 400 return varDeclOrAssignment; 401 402 case SyntaxKind.FunctionExpression: 403 case SyntaxKind.ArrowFunction: { 404 const funcBody = (func as FunctionExpression | ArrowFunction).body; 405 const returnType = getLastCallSignature(transformer.checker.getTypeAtLocation(func), transformer.checker)?.getReturnType(); 406 407 // Arrow functions with block bodies { } will enter this control flow 408 if (isBlock(funcBody)) { 409 let refactoredStmts: Statement[] = []; 410 let seenReturnStatement = false; 411 for (const statement of funcBody.statements) { 412 if (isReturnStatement(statement)) { 413 seenReturnStatement = true; 414 if (isReturnStatementWithFixablePromiseHandler(statement, transformer.checker)) { 415 refactoredStmts = refactoredStmts.concat(getInnerTransformationBody(transformer, [statement], prevArgName)); 416 } 417 else { 418 const possiblyAwaitedRightHandSide = returnType && statement.expression ? getPossiblyAwaitedRightHandSide(transformer.checker, returnType, statement.expression) : statement.expression; 419 refactoredStmts.push(...maybeAnnotateAndReturn(possiblyAwaitedRightHandSide, parent.typeArguments?.[0])); 420 } 421 } 422 else { 423 refactoredStmts.push(statement); 424 } 425 } 426 427 return shouldReturn(parent, transformer) 428 ? refactoredStmts.map(s => getSynthesizedDeepClone(s)) 429 : removeReturns( 430 refactoredStmts, 431 prevArgName, 432 transformer, 433 seenReturnStatement); 434 } 435 else { 436 const innerRetStmts = isFixablePromiseHandler(funcBody, transformer.checker) ? [factory.createReturnStatement(funcBody)] : emptyArray; 437 const innerCbBody = getInnerTransformationBody(transformer, innerRetStmts, prevArgName); 438 439 if (innerCbBody.length > 0) { 440 return innerCbBody; 441 } 442 443 if (returnType) { 444 const possiblyAwaitedRightHandSide = getPossiblyAwaitedRightHandSide(transformer.checker, returnType, funcBody); 445 if (!shouldReturn(parent, transformer)) { 446 const transformedStatement = createVariableOrAssignmentOrExpressionStatement(prevArgName, possiblyAwaitedRightHandSide, /*typeAnnotation*/ undefined); 447 if (prevArgName) { 448 prevArgName.types.push(returnType); 449 } 450 return transformedStatement; 451 } 452 else { 453 return maybeAnnotateAndReturn(possiblyAwaitedRightHandSide, parent.typeArguments?.[0]); 454 } 455 } 456 else { 457 return silentFail(); 458 } 459 } 460 } 461 default: 462 // If no cases apply, we've found a transformation body we don't know how to handle, so the refactoring should no-op to avoid deleting code. 463 return silentFail(); 464 } 465 return emptyArray; 466 } 467 468 function getPossiblyAwaitedRightHandSide(checker: TypeChecker, type: Type, expr: Expression): AwaitExpression | Expression { 469 const rightHandSide = getSynthesizedDeepClone(expr); 470 return !!checker.getPromisedTypeOfPromise(type) ? factory.createAwaitExpression(rightHandSide) : rightHandSide; 471 } 472 473 function getLastCallSignature(type: Type, checker: TypeChecker): Signature | undefined { 474 const callSignatures = checker.getSignaturesOfType(type, SignatureKind.Call); 475 return lastOrUndefined(callSignatures); 476 } 477 478 function removeReturns(stmts: readonly Statement[], prevArgName: SynthBindingName | undefined, transformer: Transformer, seenReturnStatement: boolean): readonly Statement[] { 479 const ret: Statement[] = []; 480 for (const stmt of stmts) { 481 if (isReturnStatement(stmt)) { 482 if (stmt.expression) { 483 const possiblyAwaitedExpression = isPromiseTypedExpression(stmt.expression, transformer.checker) ? factory.createAwaitExpression(stmt.expression) : stmt.expression; 484 if (prevArgName === undefined) { 485 ret.push(factory.createExpressionStatement(possiblyAwaitedExpression)); 486 } 487 else { 488 ret.push(factory.createVariableStatement(/*modifiers*/ undefined, 489 (factory.createVariableDeclarationList([factory.createVariableDeclaration(getNode(prevArgName), /*exclamationToken*/ undefined, /*type*/ undefined, possiblyAwaitedExpression)], NodeFlags.Const)))); 490 } 491 } 492 } 493 else { 494 ret.push(getSynthesizedDeepClone(stmt)); 495 } 496 } 497 498 // if block has no return statement, need to define prevArgName as undefined to prevent undeclared variables 499 if (!seenReturnStatement && prevArgName !== undefined) { 500 ret.push(factory.createVariableStatement(/*modifiers*/ undefined, 501 (factory.createVariableDeclarationList([factory.createVariableDeclaration(getNode(prevArgName), /*exclamationToken*/ undefined, /*type*/ undefined, factory.createIdentifier("undefined"))], NodeFlags.Const)))); 502 } 503 504 return ret; 505 } 506 507 508 function getInnerTransformationBody(transformer: Transformer, innerRetStmts: readonly Node[], prevArgName?: SynthBindingName) { 509 let innerCbBody: Statement[] = []; 510 for (const stmt of innerRetStmts) { 511 forEachChild(stmt, function visit(node) { 512 if (isCallExpression(node)) { 513 const temp = transformExpression(node, transformer, prevArgName); 514 innerCbBody = innerCbBody.concat(temp); 515 if (innerCbBody.length > 0) { 516 return; 517 } 518 } 519 else if (!isFunctionLike(node)) { 520 forEachChild(node, visit); 521 } 522 }); 523 } 524 return innerCbBody; 525 } 526 527 function getArgBindingName(funcNode: Expression, transformer: Transformer): SynthBindingName | undefined { 528 const types: Type[] = []; 529 let name: SynthBindingName | undefined; 530 531 if (isFunctionLikeDeclaration(funcNode)) { 532 if (funcNode.parameters.length > 0) { 533 const param = funcNode.parameters[0].name; 534 name = getMappedBindingNameOrDefault(param); 535 } 536 } 537 else if (isIdentifier(funcNode)) { 538 name = getMapEntryOrDefault(funcNode); 539 } 540 else if (isPropertyAccessExpression(funcNode) && isIdentifier(funcNode.name)) { 541 name = getMapEntryOrDefault(funcNode.name); 542 } 543 544 // return undefined argName when arg is null or undefined 545 // eslint-disable-next-line no-in-operator 546 if (!name || "identifier" in name && name.identifier.text === "undefined") { 547 return undefined; 548 } 549 550 return name; 551 552 function getMappedBindingNameOrDefault(bindingName: BindingName): SynthBindingName { 553 if (isIdentifier(bindingName)) return getMapEntryOrDefault(bindingName); 554 const elements = flatMap(bindingName.elements, element => { 555 if (isOmittedExpression(element)) return []; 556 return [getMappedBindingNameOrDefault(element.name)]; 557 }); 558 559 return createSynthBindingPattern(bindingName, elements); 560 } 561 562 function getMapEntryOrDefault(identifier: Identifier): SynthIdentifier { 563 const originalNode = getOriginalNode(identifier); 564 const symbol = getSymbol(originalNode); 565 566 if (!symbol) { 567 return createSynthIdentifier(identifier, types); 568 } 569 570 const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); 571 return mapEntry || createSynthIdentifier(identifier, types); 572 } 573 574 function getSymbol(node: Node): Symbol | undefined { 575 return node.symbol ? node.symbol : transformer.checker.getSymbolAtLocation(node); 576 } 577 578 function getOriginalNode(node: Node): Node { 579 return node.original ? node.original : node; 580 } 581 } 582 583 function isEmptyBindingName(bindingName: SynthBindingName | undefined): boolean { 584 if (!bindingName) { 585 return true; 586 } 587 if (isSynthIdentifier(bindingName)) { 588 return !bindingName.identifier.text; 589 } 590 return every(bindingName.elements, isEmptyBindingName); 591 } 592 593 function getNode(bindingName: SynthBindingName) { 594 return isSynthIdentifier(bindingName) ? bindingName.identifier : bindingName.bindingPattern; 595 } 596 597 function createSynthIdentifier(identifier: Identifier, types: Type[] = []): SynthIdentifier { 598 return { kind: SynthBindingNameKind.Identifier, identifier, types, hasBeenDeclared: false }; 599 } 600 601 function createSynthBindingPattern(bindingPattern: BindingPattern, elements: readonly SynthBindingName[] = emptyArray, types: Type[] = []): SynthBindingPattern { 602 return { kind: SynthBindingNameKind.BindingPattern, bindingPattern, elements, types }; 603 } 604 605 function isSynthIdentifier(bindingName: SynthBindingName): bindingName is SynthIdentifier { 606 return bindingName.kind === SynthBindingNameKind.Identifier; 607 } 608 609 function isSynthBindingPattern(bindingName: SynthBindingName): bindingName is SynthBindingPattern { 610 return bindingName.kind === SynthBindingNameKind.BindingPattern; 611 } 612 613 function shouldReturn(expression: Expression, transformer: Transformer): boolean { 614 return !!expression.original && transformer.setOfExpressionsToReturn.has(getNodeId(expression.original)); 615 } 616} 617