From f3971dfcac2ff640170211d3798e6d54c435ade8 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 31 May 2018 13:11:20 -0700 Subject: [PATCH 001/196] updated build files --- src/harness/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 359701a02cb3a..5b68f42c471f3 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -93,6 +93,7 @@ "../services/codefixes/annotateWithTypeFromJSDoc.ts", "../services/codefixes/convertFunctionToEs6Class.ts", "../services/codefixes/convertToEs6Module.ts", + "../services/codefixes/convertPromisesToAwaitAndAsync.ts", "../services/codefixes/correctQualifiedNameToIndexedAccessType.ts", "../services/codefixes/fixClassIncorrectlyImplementsInterface.ts", "../services/codefixes/importFixes.ts", From 6f803f65f0ec8cdacb5ae7ee64a2ee27f0f5611e Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 31 May 2018 16:55:05 -0700 Subject: [PATCH 002/196] Initial commit of changing all functions that return a promise to async --- src/compiler/diagnosticMessages.json | 12 +++++++++ .../convertPromisesToAwaitAndAsync.ts | 22 ++++++++++++++++ src/services/suggestionDiagnostics.ts | 26 +++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 src/services/codefixes/convertPromisesToAwaitAndAsync.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 59007e610b4ad..047d4b9483d1e 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3964,6 +3964,10 @@ "category": "Suggestion", "code": 80005 }, + "This may be converted to use async and await.": { + "category": "Suggestion", + "code": 80006 + }, "Add missing 'super()' call": { "category": "Message", @@ -4280,5 +4284,13 @@ "Remove all unused labels": { "category": "Message", "code": 95054 + }, + "Convert to use async and await":{ + "category": "Message", + "code": 95055 + }, + "Convert all to use async and await": { + "category": "Message", + "code": 95056 } } diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts new file mode 100644 index 0000000000000..e48f4ee071188 --- /dev/null +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -0,0 +1,22 @@ +namespace ts.codefix { + const fixId = "convertPromisesToAwaitAndAsync"; + const errorCodes = [Diagnostics.This_may_be_converted_to_use_async_and_await.code]; + registerCodeFix({ + errorCodes, + getCodeActions(context: CodeFixContext) { + const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncAwait(t, context.sourceFile, context.span.start, context.program.getTypeChecker())); + return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_use_async_and_await, fixId, Diagnostics.Convert_all_to_use_async_and_await)]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncAwait(changes, err.file!, err.start, context.program.getTypeChecker())), + }); + function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { + const oldNode = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, false)).valueDeclaration; + const newModifier:Modifier = createModifier(SyntaxKind.AsyncKeyword); + let newNode = oldNode; + newNode.modifiers ? newNode.modifiers.concat([newModifier]) : newNode.modifiers = createNodeArray([newModifier]); + changes.replaceNode(sourceFile, oldNode, newNode) + //changes.insertNodeBefore(sourceFile, temp.valueDeclaration, newNode); + } + +} \ No newline at end of file diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 40ed7719c9fe4..e7244b53a07b1 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -17,12 +17,28 @@ namespace ts { switch (node.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: + if (isJsFile) { const symbol = node.symbol; if (symbol.members && (symbol.members.size > 0)) { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration)); } } + + if(node.modifiers && containsAsync(node.modifiers)){ + break; + } + + const returnType = checker.getReturnTypeOfSignature(checker.getSignatureFromDeclaration( node)) + if(!returnType || !returnType.symbol){ + break; + } + + debugger; + if(returnType.flags === TypeFlags.Object && returnType.symbol.name === "Promise"){ + diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_use_async_and_await)); + } + break; } @@ -104,4 +120,14 @@ namespace ts { function getErrorNodeFromCommonJsIndicator(commonJsModuleIndicator: Node): Node { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } + + function containsAsync(arr: NodeArray): boolean{ + for(let modifier of arr){ + if(modifier.kind === SyntaxKind.AsyncKeyword){ + return true; + } + } + + return false; + } } From b8d818356a0c4ff9fe3284b4e171e4099a3a1417 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 4 Jun 2018 11:19:29 -0700 Subject: [PATCH 003/196] adding await to .then() works --- .../convertPromisesToAwaitAndAsync.ts | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index e48f4ee071188..abab557f614c4 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -13,10 +13,27 @@ namespace ts.codefix { function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { const oldNode = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, false)).valueDeclaration; const newModifier:Modifier = createModifier(SyntaxKind.AsyncKeyword); - let newNode = oldNode; + let newNode = oldNode; newNode.modifiers ? newNode.modifiers.concat([newModifier]) : newNode.modifiers = createNodeArray([newModifier]); - changes.replaceNode(sourceFile, oldNode, newNode) - //changes.insertNodeBefore(sourceFile, temp.valueDeclaration, newNode); + changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, oldNode) + refactorDotThen(sourceFile, changes, newNode); + } + + function refactorDotThen(sourceFile: SourceFile, changes: textChanges.ChangeTracker, node: Node){ + switch(node.kind){ + case SyntaxKind.PropertyAccessExpression: + if((node).name.text === "then"){ + let newParNode = createAwait(node.parent); + //getChildren().concat(node.parent.getChildren()) + changes.replaceNode(sourceFile, node.parent, newParNode) + return; + } + break; + } + + for( let child of node.getChildren() ){ + refactorDotThen(sourceFile, changes, child); + } } } \ No newline at end of file From 69795c88422025defb8df46eabc14b0e14705144 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 4 Jun 2018 19:05:50 -0700 Subject: [PATCH 004/196] Try block is added --- .../convertPromisesToAwaitAndAsync.ts | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index abab557f614c4..422b41635ca4f 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -11,28 +11,35 @@ namespace ts.codefix { getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncAwait(changes, err.file!, err.start, context.program.getTypeChecker())), }); function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { - const oldNode = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, false)).valueDeclaration; - const newModifier:Modifier = createModifier(SyntaxKind.AsyncKeyword); - let newNode = oldNode; - newNode.modifiers ? newNode.modifiers.concat([newModifier]) : newNode.modifiers = createNodeArray([newModifier]); - changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, oldNode) - refactorDotThen(sourceFile, changes, newNode); + const funcToConvert = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, false)).valueDeclaration; + changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert) + + //maybe find the statement line with the promise call so we can put that in the .then + let tryBlock:FunctionBody = refactorDotThen(sourceFile, changes, funcToConvert) + if(tryBlock && (funcToConvert).body.statements.length > 0){ + changes.insertNodeBefore(sourceFile, (funcToConvert).body.statements[0], createTry(tryBlock, undefined, undefined)); + } + } - function refactorDotThen(sourceFile: SourceFile, changes: textChanges.ChangeTracker, node: Node){ + function refactorDotThen(sourceFile: SourceFile, changes: textChanges.ChangeTracker, node: Node): FunctionBody{ switch(node.kind){ case SyntaxKind.PropertyAccessExpression: - if((node).name.text === "then"){ + if((node).name.text === "then" /*and the return type of the function called is a promise!!*/){ let newParNode = createAwait(node.parent); - //getChildren().concat(node.parent.getChildren()) changes.replaceNode(sourceFile, node.parent, newParNode) - return; + //node.parent.arguments[0].body is the try block + let parNode = node.parent as CallExpression; + return parNode.arguments.length > 0 && isFunctionLikeDeclaration(parNode.arguments[0]) ? ((parNode.arguments[0]).body) : null; } break; } for( let child of node.getChildren() ){ - refactorDotThen(sourceFile, changes, child); + let ret = refactorDotThen(sourceFile, changes, child); + if(ret){ + return ret; + } } } From 6c0a71235a787e13b1727d19aa4313fc125feffe Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 5 Jun 2018 16:34:24 -0700 Subject: [PATCH 005/196] major refactor - still buggy --- .../convertPromisesToAwaitAndAsync.ts | 115 ++++++++++++++++-- src/services/suggestionDiagnostics.ts | 7 +- 2 files changed, 112 insertions(+), 10 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 422b41635ca4f..f67c69d25e711 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -14,27 +14,109 @@ namespace ts.codefix { const funcToConvert = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, false)).valueDeclaration; changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert) - //maybe find the statement line with the promise call so we can put that in the .then - let tryBlock:FunctionBody = refactorDotThen(sourceFile, changes, funcToConvert) - if(tryBlock && (funcToConvert).body.statements.length > 0){ - changes.insertNodeBefore(sourceFile, (funcToConvert).body.statements[0], createTry(tryBlock, undefined, undefined)); + + //collect what to put in the try block + let callToAwait = findCallToAwait(funcToConvert, checker) + let dotThen:FunctionBody = findDotThen(funcToConvert, checker); + let tryBlock:Block = undefined; + + if(!callToAwait){ + return; + } + + let newParNode = createAwait(callToAwait); + + if(dotThen){ + let stmts = createNodeArray([createStatement(newParNode)]).concat(createNodeArray(dotThen.statements)); + tryBlock = createBlock(stmts); + }else{ + tryBlock = createBlock(createNodeArray([createStatement(newParNode)])); + } + + changes.insertNodeBefore(sourceFile, (funcToConvert).body.statements[0], createTry(tryBlock, undefined, undefined)); + changes.deleteNode(sourceFile, callToAwait); + } + + + + function findCallToAwait(node: Node, checker:TypeChecker): Expression{ + switch(node.kind){ + case SyntaxKind.CallExpression: + //let callsig = checker.getTypeAtLocation(node).getCallSignatures()[0]; + //if(cllsig && isPromiseType(checker.getReturnTypeOfSignature(callsig))){ + if(isPromiseType(checker.getTypeAtLocation(node))){ + return node as CallExpression; + } + } + + for( let child of node.getChildren() ){ + let ret = findCallToAwait(child, checker); + if(ret){ + return ret; + } + } + + } + + function findDotThen(node:Node, checker:TypeChecker): FunctionBody{ + switch(node.kind){ + case SyntaxKind.PropertyAccessExpression: + if((node).name.text === "then" && isPromiseType(checker.getTypeAtLocation(node.parent))){ + let parNode = node.parent as CallExpression; + if(parNode.arguments.length > 0 && isFunctionLikeDeclaration(parNode.arguments[0])){ + //find the body of the func in the .then() to put in the try block + return ((parNode.arguments[0]).body as FunctionBody); + } + } + break; + } + + //recurse + for( let child of node.getChildren() ){ + let ret = findDotThen(child, checker); + if(ret){ + return ret; + } } - + } + /* function refactorDotThen(sourceFile: SourceFile, changes: textChanges.ChangeTracker, node: Node): FunctionBody{ switch(node.kind){ case SyntaxKind.PropertyAccessExpression: - if((node).name.text === "then" /*and the return type of the function called is a promise!!*/){ + if((node).name.text === "then" /*and the return type of the function called is a promise!!*///){ + //add await to the function call + /* let newParNode = createAwait(node.parent); changes.replaceNode(sourceFile, node.parent, newParNode) - //node.parent.arguments[0].body is the try block + let parNode = node.parent as CallExpression; - return parNode.arguments.length > 0 && isFunctionLikeDeclaration(parNode.arguments[0]) ? ((parNode.arguments[0]).body) : null; + if(parNode.arguments.length > 0 && isFunctionLikeDeclaration(parNode.arguments[0])){ + //find the body of the func in the .then() to put in the try block + let funcBody:FunctionBody = (parNode.arguments[0]).body as FunctionBody; + /* + let nodeStmt:ExpressionStatement = createStatement(node); + + for(let stmt of funcBody.statements){ + if(isExpressionStatement(stmt) && exprStmtEql(stmt, nodeStmt)){ + return funcBody; + } + } + + //add the function call that returns a promise + let stmts = funcBody.statements.concat(nodeStmt); //problem here - we don't want to add this node if its already there + funcBody.statements = createNodeArray(stmts); + */ + //createTryBlock(sourceFile, changes, funcBody, [node]) + /* + return funcBody; + } } break; } + //recurse for( let child of node.getChildren() ){ let ret = refactorDotThen(sourceFile, changes, child); if(ret){ @@ -42,5 +124,22 @@ namespace ts.codefix { } } } + */ + + /* + function exprStmtEql(stmt1:ExpressionStatement, stmt2:ExpressionStatement): boolean{ + if(isExpressionStatement(stmt1.expression) && isExpressionStatement(stmt2.expression)){ + return exprStmtEql((stmt1.expression), stmt2.expression) + }else if(!isExpressionStatement(stmt1.expression) && !isExpressionStatement(stmt2.expression) && + stmt1.expression.kind === stmt2.expression.kind){ + return true; + }else{ + return false; + } + } + */ + function isPromiseType(T:Type):boolean{ + return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; + } } \ No newline at end of file diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index e7244b53a07b1..694251b1ab1fe 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -34,8 +34,7 @@ namespace ts { break; } - debugger; - if(returnType.flags === TypeFlags.Object && returnType.symbol.name === "Promise"){ + if(isPromiseType(returnType)){ diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_use_async_and_await)); } @@ -130,4 +129,8 @@ namespace ts { return false; } + + function isPromiseType(T:Type):boolean{ + return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; + } } From 546ee1bc0be7004f34941b6ebf4345b12bce6819 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 6 Jun 2018 15:36:55 -0700 Subject: [PATCH 006/196] Fixed to allow for non inlined functions in a then() --- .../convertPromisesToAwaitAndAsync.ts | 103 ++++++------------ 1 file changed, 32 insertions(+), 71 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index f67c69d25e711..6747bcb702f33 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -16,25 +16,36 @@ namespace ts.codefix { //collect what to put in the try block - let callToAwait = findCallToAwait(funcToConvert, checker) - let dotThen:FunctionBody = findDotThen(funcToConvert, checker); + let callToAwait = findCallToAwait(funcToConvert, checker); + let dotThenTuple:[NodeArray, string, TextRange] = findDotThen(funcToConvert, checker); + let dotThenBody:NodeArray = dotThenTuple[0]; + let dotThenFuncName:string = dotThenTuple[1]; + let textRange:TextRange = dotThenTuple[2]; let tryBlock:Block = undefined; if(!callToAwait){ return; } - let newParNode = createAwait(callToAwait); + let awaitNode = createAwait(callToAwait); - if(dotThen){ - let stmts = createNodeArray([createStatement(newParNode)]).concat(createNodeArray(dotThen.statements)); - tryBlock = createBlock(stmts); + if(dotThenTuple && dotThenBody.length > 0){ + let awaitDecl = createVariableDeclarationList(createNodeArray([createVariableDeclaration(dotThenFuncName, undefined, awaitNode)])); + let awaitDeclStmt = createVariableStatement(undefined, awaitDecl); + //let stmts = createNodeArray([createStatement(awaitNode)]).concat(getSynthesizedDeepClones(createNodeArray(dotThenBody.statements))); + let stmts = undefined; + stmts = createNodeArray([awaitDeclStmt]).concat(getSynthesizedDeepClones(dotThenBody)); + tryBlock = createBlock(stmts.concat()); + //changes.deleteNode(sourceFile, dotThen) }else{ - tryBlock = createBlock(createNodeArray([createStatement(newParNode)])); + tryBlock = createBlock(createNodeArray([createStatement(awaitNode)])); } - changes.insertNodeBefore(sourceFile, (funcToConvert).body.statements[0], createTry(tryBlock, undefined, undefined)); - changes.deleteNode(sourceFile, callToAwait); + changes.replaceRange(sourceFile, textRange, createTry(tryBlock, undefined, undefined), {prefix: "\n"}) + //changes.replaceNode(sourceFile, callToAwait, createTry(tryBlock, undefined, undefined)); + //fix this + //changes.insertNodeBefore(sourceFile, (funcToConvert).body.statements[0], createTry(tryBlock, undefined, undefined)); + //changes.deleteNodes(sourceFile, dotThenBody); } @@ -42,9 +53,7 @@ namespace ts.codefix { function findCallToAwait(node: Node, checker:TypeChecker): Expression{ switch(node.kind){ case SyntaxKind.CallExpression: - //let callsig = checker.getTypeAtLocation(node).getCallSignatures()[0]; - //if(cllsig && isPromiseType(checker.getReturnTypeOfSignature(callsig))){ - if(isPromiseType(checker.getTypeAtLocation(node))){ + if(isPromiseType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression){ return node as CallExpression; } } @@ -58,14 +67,23 @@ namespace ts.codefix { } - function findDotThen(node:Node, checker:TypeChecker): FunctionBody{ + function findDotThen(node:Node, checker:TypeChecker): [NodeArray, string, TextRange]{ switch(node.kind){ case SyntaxKind.PropertyAccessExpression: if((node).name.text === "then" && isPromiseType(checker.getTypeAtLocation(node.parent))){ let parNode = node.parent as CallExpression; if(parNode.arguments.length > 0 && isFunctionLikeDeclaration(parNode.arguments[0])){ //find the body of the func in the .then() to put in the try block - return ((parNode.arguments[0]).body as FunctionBody); + let funcDecl:FunctionLikeDeclaration = parNode.arguments[0] as FunctionLikeDeclaration; + let funcBody:FunctionBody = funcDecl.body as FunctionBody; + return [createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.pos, parNode.end)]; + //return parNode.arguments[0] as FunctionLikeDeclaration; + //return ((parNode.arguments[0]).body as FunctionBody); + }else if(parNode.arguments.length > 0 && isIdentifier(parNode.arguments[0])){ + let argName= ((checker.getTypeAtLocation(parNode.arguments[0]).symbol.valueDeclaration).parameters[0].name) + let callExpr = createCall(parNode.arguments[0], undefined, [argName]); + return [createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(parNode.pos, parNode.end)] + //return checker.getTypeAtLocation(parNode.arguments[0]).symbol.valueDeclaration as FunctionLikeDeclaration; } } break; @@ -81,63 +99,6 @@ namespace ts.codefix { } - /* - function refactorDotThen(sourceFile: SourceFile, changes: textChanges.ChangeTracker, node: Node): FunctionBody{ - switch(node.kind){ - case SyntaxKind.PropertyAccessExpression: - if((node).name.text === "then" /*and the return type of the function called is a promise!!*///){ - //add await to the function call - /* - let newParNode = createAwait(node.parent); - changes.replaceNode(sourceFile, node.parent, newParNode) - - let parNode = node.parent as CallExpression; - if(parNode.arguments.length > 0 && isFunctionLikeDeclaration(parNode.arguments[0])){ - //find the body of the func in the .then() to put in the try block - let funcBody:FunctionBody = (parNode.arguments[0]).body as FunctionBody; - /* - let nodeStmt:ExpressionStatement = createStatement(node); - - for(let stmt of funcBody.statements){ - if(isExpressionStatement(stmt) && exprStmtEql(stmt, nodeStmt)){ - return funcBody; - } - } - - //add the function call that returns a promise - let stmts = funcBody.statements.concat(nodeStmt); //problem here - we don't want to add this node if its already there - funcBody.statements = createNodeArray(stmts); - */ - //createTryBlock(sourceFile, changes, funcBody, [node]) - /* - return funcBody; - } - } - break; - } - - //recurse - for( let child of node.getChildren() ){ - let ret = refactorDotThen(sourceFile, changes, child); - if(ret){ - return ret; - } - } - } - */ - - /* - function exprStmtEql(stmt1:ExpressionStatement, stmt2:ExpressionStatement): boolean{ - if(isExpressionStatement(stmt1.expression) && isExpressionStatement(stmt2.expression)){ - return exprStmtEql((stmt1.expression), stmt2.expression) - }else if(!isExpressionStatement(stmt1.expression) && !isExpressionStatement(stmt2.expression) && - stmt1.expression.kind === stmt2.expression.kind){ - return true; - }else{ - return false; - } - } - */ function isPromiseType(T:Type):boolean{ return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; } From 8019335e59a97ddcce0142edb858fde38507da7a Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 7 Jun 2018 10:53:38 -0700 Subject: [PATCH 007/196] Got catch blocks and onRej functions working --- .../convertPromisesToAwaitAndAsync.ts | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 6747bcb702f33..b1665b7f48c63 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -11,17 +11,25 @@ namespace ts.codefix { getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncAwait(changes, err.file!, err.start, context.program.getTypeChecker())), }); function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { + //get the function declaration - returns a promise const funcToConvert = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, false)).valueDeclaration; + //add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert) - //collect what to put in the try block let callToAwait = findCallToAwait(funcToConvert, checker); - let dotThenTuple:[NodeArray, string, TextRange] = findDotThen(funcToConvert, checker); - let dotThenBody:NodeArray = dotThenTuple[0]; - let dotThenFuncName:string = dotThenTuple[1]; - let textRange:TextRange = dotThenTuple[2]; + let onResTuple:[NodeArray, string, TextRange] = findDotThen(funcToConvert, checker, true); + let onResBody:NodeArray = onResTuple[0]; + let onResFuncName:string = onResTuple[1]; + let onResTextRange:TextRange = onResTuple[2]; + + let onRejTuple:[NodeArray, string, TextRange] = findDotThen(funcToConvert, checker, false); + let onRejBody:NodeArray = onRejTuple[0]; + //let onRejFuncName:string = onRejTuple[1]; + //let onRejTextRange:TextRange = onRejTuple[2]; + let tryBlock:Block = undefined; + let catchBlock:CatchClause = undefined; if(!callToAwait){ return; @@ -29,23 +37,24 @@ namespace ts.codefix { let awaitNode = createAwait(callToAwait); - if(dotThenTuple && dotThenBody.length > 0){ - let awaitDecl = createVariableDeclarationList(createNodeArray([createVariableDeclaration(dotThenFuncName, undefined, awaitNode)])); + if(onResTuple && onResBody.length > 0){ + let awaitDecl = createVariableDeclarationList(createNodeArray([createVariableDeclaration(onResFuncName, undefined, awaitNode)])); let awaitDeclStmt = createVariableStatement(undefined, awaitDecl); - //let stmts = createNodeArray([createStatement(awaitNode)]).concat(getSynthesizedDeepClones(createNodeArray(dotThenBody.statements))); - let stmts = undefined; - stmts = createNodeArray([awaitDeclStmt]).concat(getSynthesizedDeepClones(dotThenBody)); - tryBlock = createBlock(stmts.concat()); - //changes.deleteNode(sourceFile, dotThen) + let stmts = createNodeArray([awaitDeclStmt]).concat(getSynthesizedDeepClones(onResBody)); + tryBlock = createBlock(stmts); }else{ tryBlock = createBlock(createNodeArray([createStatement(awaitNode)])); } - changes.replaceRange(sourceFile, textRange, createTry(tryBlock, undefined, undefined), {prefix: "\n"}) - //changes.replaceNode(sourceFile, callToAwait, createTry(tryBlock, undefined, undefined)); - //fix this - //changes.insertNodeBefore(sourceFile, (funcToConvert).body.statements[0], createTry(tryBlock, undefined, undefined)); - //changes.deleteNodes(sourceFile, dotThenBody); + + if(onRejTuple && onRejBody.length > 0){ + let stmts = createNodeArray(onRejBody); + catchBlock = createCatchClause("e", createBlock(stmts)); + }else{ + catchBlock = createCatchClause("e", createBlock(createNodeArray())); + } + + changes.replaceRange(sourceFile, onResTextRange, createTry(tryBlock, catchBlock, undefined), {prefix: "\n"}) } @@ -67,23 +76,24 @@ namespace ts.codefix { } - function findDotThen(node:Node, checker:TypeChecker): [NodeArray, string, TextRange]{ + function findDotThen(node:Node, checker:TypeChecker, onRes:boolean): [NodeArray, string, TextRange]{ + + const index:number = onRes ? 0 : 1; + switch(node.kind){ case SyntaxKind.PropertyAccessExpression: if((node).name.text === "then" && isPromiseType(checker.getTypeAtLocation(node.parent))){ let parNode = node.parent as CallExpression; - if(parNode.arguments.length > 0 && isFunctionLikeDeclaration(parNode.arguments[0])){ - //find the body of the func in the .then() to put in the try block - let funcDecl:FunctionLikeDeclaration = parNode.arguments[0] as FunctionLikeDeclaration; + if(parNode.arguments.length > index && isFunctionLikeDeclaration(parNode.arguments[index])){ + //inlined function definitiion + let funcDecl:FunctionLikeDeclaration = parNode.arguments[index] as FunctionLikeDeclaration; let funcBody:FunctionBody = funcDecl.body as FunctionBody; return [createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.pos, parNode.end)]; - //return parNode.arguments[0] as FunctionLikeDeclaration; - //return ((parNode.arguments[0]).body as FunctionBody); - }else if(parNode.arguments.length > 0 && isIdentifier(parNode.arguments[0])){ - let argName= ((checker.getTypeAtLocation(parNode.arguments[0]).symbol.valueDeclaration).parameters[0].name) - let callExpr = createCall(parNode.arguments[0], undefined, [argName]); + }else if(parNode.arguments.length > index && isIdentifier(parNode.arguments[index])){ + //reference to an elsewhere declared function + let argName = ((checker.getTypeAtLocation(parNode.arguments[index]).symbol.valueDeclaration).parameters[0].name) + let callExpr = createCall(parNode.arguments[index], undefined, [argName]); return [createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(parNode.pos, parNode.end)] - //return checker.getTypeAtLocation(parNode.arguments[0]).symbol.valueDeclaration as FunctionLikeDeclaration; } } break; @@ -91,14 +101,14 @@ namespace ts.codefix { //recurse for( let child of node.getChildren() ){ - let ret = findDotThen(child, checker); + let ret = findDotThen(child, checker, onRes); if(ret){ return ret; } } } - + function isPromiseType(T:Type):boolean{ return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; } From 6e31ff9050e04311ce9efdc4e0d8232a46098baf Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 7 Jun 2018 13:46:38 -0700 Subject: [PATCH 008/196] Fixed try/catch formatting --- .../codefixes/convertPromisesToAwaitAndAsync.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index b1665b7f48c63..a1c827f456746 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -54,7 +54,7 @@ namespace ts.codefix { catchBlock = createCatchClause("e", createBlock(createNodeArray())); } - changes.replaceRange(sourceFile, onResTextRange, createTry(tryBlock, catchBlock, undefined), {prefix: "\n"}) + changes.replaceRange(sourceFile, onResTextRange, createTry(tryBlock, catchBlock, undefined)); } @@ -76,24 +76,29 @@ namespace ts.codefix { } - function findDotThen(node:Node, checker:TypeChecker, onRes:boolean): [NodeArray, string, TextRange]{ + enum PromiseMemberFunc{ + Then = "then", + Catch = "catch", + } + + function findDotThen(node:Node, checker:TypeChecker, onRes:boolean, memberFunc:PromiseMemberFunc): [NodeArray, string, TextRange]{ const index:number = onRes ? 0 : 1; switch(node.kind){ case SyntaxKind.PropertyAccessExpression: - if((node).name.text === "then" && isPromiseType(checker.getTypeAtLocation(node.parent))){ + if((node).name.text === memberFunc && isPromiseType(checker.getTypeAtLocation(node.parent))){ let parNode = node.parent as CallExpression; if(parNode.arguments.length > index && isFunctionLikeDeclaration(parNode.arguments[index])){ //inlined function definitiion let funcDecl:FunctionLikeDeclaration = parNode.arguments[index] as FunctionLikeDeclaration; let funcBody:FunctionBody = funcDecl.body as FunctionBody; - return [createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.pos, parNode.end)]; + return [createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.getStart(), parNode.end)]; }else if(parNode.arguments.length > index && isIdentifier(parNode.arguments[index])){ //reference to an elsewhere declared function let argName = ((checker.getTypeAtLocation(parNode.arguments[index]).symbol.valueDeclaration).parameters[0].name) let callExpr = createCall(parNode.arguments[index], undefined, [argName]); - return [createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(parNode.pos, parNode.end)] + return [createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(parNode.getStart(), parNode.end)] } } break; From 469da81e665c33b9c8eea41cc81f992f12b6ba7e Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 7 Jun 2018 17:05:46 -0700 Subject: [PATCH 009/196] Initial commit of recursive function to create cascading catch blocks --- .../convertPromisesToAwaitAndAsync.ts | 76 +++++++++++++++---- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index a1c827f456746..bf307f00d366f 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -16,17 +16,40 @@ namespace ts.codefix { //add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert) + let retArrayRes:[NodeArray, string, TextRange][] = []; + //let retArrayRej:[NodeArray, string, TextRange][] = []; + let retArrayCatch:[NodeArray, string, TextRange][] = []; + + //collect what to put in the try block let callToAwait = findCallToAwait(funcToConvert, checker); - let onResTuple:[NodeArray, string, TextRange] = findDotThen(funcToConvert, checker, true); - let onResBody:NodeArray = onResTuple[0]; - let onResFuncName:string = onResTuple[1]; - let onResTextRange:TextRange = onResTuple[2]; + + + findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Then, retArrayRes); + let onResTuple:[NodeArray, string, TextRange] = retArrayRes[0]; + let onResBody:NodeArray, onResFuncName:string, onResTextRange:TextRange = undefined; + + if(onResTuple){ + onResBody = onResTuple[0]; + onResFuncName = onResTuple[1]; + onResTextRange = onResTuple[2]; + } - let onRejTuple:[NodeArray, string, TextRange] = findDotThen(funcToConvert, checker, false); - let onRejBody:NodeArray = onRejTuple[0]; + /* + findDotThen(funcToConvert, checker, false, PromiseMemberFunc.Then, retArrayRej); + let onRejTuple:[NodeArray, string, TextRange] = retArrayRej[0]; + let onRejBody:NodeArray = onRejTuple[0];*/ //let onRejFuncName:string = onRejTuple[1]; //let onRejTextRange:TextRange = onRejTuple[2]; + + + findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Catch, retArrayCatch); + let onCatchTuple:[NodeArray, string, TextRange] = retArrayCatch[0]; + + let onCatchTextRange = undefined; + if(onCatchTuple){ + onCatchTextRange = onCatchTuple[2]; + } let tryBlock:Block = undefined; let catchBlock:CatchClause = undefined; @@ -37,7 +60,7 @@ namespace ts.codefix { let awaitNode = createAwait(callToAwait); - if(onResTuple && onResBody.length > 0){ + if(onResTuple && onResBody && onResBody.length > 0){ let awaitDecl = createVariableDeclarationList(createNodeArray([createVariableDeclaration(onResFuncName, undefined, awaitNode)])); let awaitDeclStmt = createVariableStatement(undefined, awaitDecl); let stmts = createNodeArray([awaitDeclStmt]).concat(getSynthesizedDeepClones(onResBody)); @@ -47,14 +70,37 @@ namespace ts.codefix { } + catchBlock = createCatchClause("e", createBlock(createCascadingCatches(retArrayCatch))); + + + /* if(onRejTuple && onRejBody.length > 0){ let stmts = createNodeArray(onRejBody); catchBlock = createCatchClause("e", createBlock(stmts)); }else{ catchBlock = createCatchClause("e", createBlock(createNodeArray())); + }*/ + + let range = onResTextRange ? onResTextRange : onCatchTextRange; + changes.replaceRange(sourceFile, range, createTry(tryBlock, catchBlock, undefined)); + } + + function createCascadingCatches(catchArray:[NodeArray, string, TextRange][]): NodeArray{ + + if(catchArray.length == 0){ + return undefined; + } + + if(catchArray.length == 1){ + return getSynthesizedDeepClones(catchArray.pop()[0]); + //just add the function call } - changes.replaceRange(sourceFile, onResTextRange, createTry(tryBlock, catchBlock, undefined)); + let catchItem = catchArray.pop(); + let cascadingCatches = createCascadingCatches(catchArray); + let catchBlock = createBlock(cascadingCatches); + + return createNodeArray([createTry(createBlock(getSynthesizedDeepClones(catchItem[0])), createCatchClause("e", catchBlock), undefined)]); } @@ -81,7 +127,7 @@ namespace ts.codefix { Catch = "catch", } - function findDotThen(node:Node, checker:TypeChecker, onRes:boolean, memberFunc:PromiseMemberFunc): [NodeArray, string, TextRange]{ + function findDotThen(node:Node, checker:TypeChecker, onRes:boolean, memberFunc:PromiseMemberFunc, retArray:[NodeArray, string, TextRange][]):void{ //[[NodeArray, string, TextRange]]{ const index:number = onRes ? 0 : 1; @@ -93,12 +139,14 @@ namespace ts.codefix { //inlined function definitiion let funcDecl:FunctionLikeDeclaration = parNode.arguments[index] as FunctionLikeDeclaration; let funcBody:FunctionBody = funcDecl.body as FunctionBody; - return [createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.getStart(), parNode.end)]; + retArray.push([createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.getStart(), parNode.end)]); + //return [createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.getStart(), parNode.end)]; }else if(parNode.arguments.length > index && isIdentifier(parNode.arguments[index])){ //reference to an elsewhere declared function let argName = ((checker.getTypeAtLocation(parNode.arguments[index]).symbol.valueDeclaration).parameters[0].name) let callExpr = createCall(parNode.arguments[index], undefined, [argName]); - return [createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(parNode.getStart(), parNode.end)] + retArray.push([createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(parNode.getStart(), parNode.end)]); + //return [createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(parNode.getStart(), parNode.end)] } } break; @@ -106,12 +154,12 @@ namespace ts.codefix { //recurse for( let child of node.getChildren() ){ - let ret = findDotThen(child, checker, onRes); + /*let ret = findDotThen(child, checker, onRes, memberFunc); if(ret){ return ret; - } + }*/ + findDotThen(child, checker, onRes, memberFunc, retArray); } - } function isPromiseType(T:Type):boolean{ From 8c8ebe98f39e73e77cefc9cf879af960368b5470 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 7 Jun 2018 17:18:27 -0700 Subject: [PATCH 010/196] Fixed catch param names --- src/services/codefixes/convertPromisesToAwaitAndAsync.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index bf307f00d366f..61513353afe9e 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -70,7 +70,9 @@ namespace ts.codefix { } - catchBlock = createCatchClause("e", createBlock(createCascadingCatches(retArrayCatch))); + let catchParam = retArrayCatch.length > 0 ? retArrayCatch[retArrayCatch.length-1][1] : "e"; + + catchBlock = createCatchClause(catchParam, createBlock(createCascadingCatches(retArrayCatch))); /* @@ -100,7 +102,7 @@ namespace ts.codefix { let cascadingCatches = createCascadingCatches(catchArray); let catchBlock = createBlock(cascadingCatches); - return createNodeArray([createTry(createBlock(getSynthesizedDeepClones(catchItem[0])), createCatchClause("e", catchBlock), undefined)]); + return createNodeArray([createTry(createBlock(getSynthesizedDeepClones(catchItem[0])), createCatchClause(catchItem[1], catchBlock), undefined)]); } From 635cc04d725873f4f9ef02bc78cee73b430f4638 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 8 Jun 2018 16:17:20 -0700 Subject: [PATCH 011/196] First commit of nested trys and cascading catches --- .../convertPromisesToAwaitAndAsync.ts | 94 +++++++++++-------- 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 61513353afe9e..81ad7dbe04768 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -16,65 +16,82 @@ namespace ts.codefix { //add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert) + //containers to hold the resolve handlers, rejection handlers, and catch handlers let retArrayRes:[NodeArray, string, TextRange][] = []; - //let retArrayRej:[NodeArray, string, TextRange][] = []; + let retArrayRej:[NodeArray, string, TextRange][] = []; let retArrayCatch:[NodeArray, string, TextRange][] = []; - - //collect what to put in the try block - let callToAwait = findCallToAwait(funcToConvert, checker); - + //find the function call that returns a promise + let callToAwait:Expression = getSynthesizedDeepClone(findCallToAwait(funcToConvert, checker)); + //get the resolve handlers findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Then, retArrayRes); let onResTuple:[NodeArray, string, TextRange] = retArrayRes[0]; let onResBody:NodeArray, onResFuncName:string, onResTextRange:TextRange = undefined; - if(onResTuple){ onResBody = onResTuple[0]; onResFuncName = onResTuple[1]; onResTextRange = onResTuple[2]; } - /* + + //get the rejection handlers findDotThen(funcToConvert, checker, false, PromiseMemberFunc.Then, retArrayRej); let onRejTuple:[NodeArray, string, TextRange] = retArrayRej[0]; - let onRejBody:NodeArray = onRejTuple[0];*/ - //let onRejFuncName:string = onRejTuple[1]; - //let onRejTextRange:TextRange = onRejTuple[2]; + let onRejBody:NodeArray = undefined; + if(onRejTuple){ + onRejBody = onRejTuple[0]; + } + //get the catch handlers findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Catch, retArrayCatch); let onCatchTuple:[NodeArray, string, TextRange] = retArrayCatch[0]; - - let onCatchTextRange = undefined; + let onCatchTextRange:TextRange = undefined; if(onCatchTuple){ onCatchTextRange = onCatchTuple[2]; } - let tryBlock:Block = undefined; - let catchBlock:CatchClause = undefined; - + //if there is no call to a function that returns a promise, just add async keyword and return if(!callToAwait){ + //TODO: ADD THE ASYNC KEYWORD return; } let awaitNode = createAwait(callToAwait); + + //get the cascading catch block + let catchParam = retArrayCatch.length > 0 ? retArrayCatch[retArrayCatch.length-1][1] : "e"; + let cascadingCatchBlock = createCatchClause(catchParam, createBlock(createCascadingCatches(retArrayCatch))); + //get the onRes handler body + let onResTryStmts:NodeArray = undefined; if(onResTuple && onResBody && onResBody.length > 0){ + onResTryStmts = onResBody; + } + + //create the top level try block + let topLevelTryStmts:NodeArray; + if(onResTryStmts){ let awaitDecl = createVariableDeclarationList(createNodeArray([createVariableDeclaration(onResFuncName, undefined, awaitNode)])); let awaitDeclStmt = createVariableStatement(undefined, awaitDecl); - let stmts = createNodeArray([awaitDeclStmt]).concat(getSynthesizedDeepClones(onResBody)); - tryBlock = createBlock(stmts); + let onResTryBlock = createBlock(onResTryStmts); + topLevelTryStmts = createNodeArray([awaitDeclStmt, createTry(onResTryBlock, getSynthesizedDeepClone(cascadingCatchBlock), undefined)]); + }else{ - tryBlock = createBlock(createNodeArray([createStatement(awaitNode)])); + topLevelTryStmts = createNodeArray([createStatement(awaitNode)]); } - - let catchParam = retArrayCatch.length > 0 ? retArrayCatch[retArrayCatch.length-1][1] : "e"; - - catchBlock = createCatchClause(catchParam, createBlock(createCascadingCatches(retArrayCatch))); + let topLevelTryBlock:Block = createBlock(topLevelTryStmts); //add the onRes try block + let topLevelCatchClause:CatchClause = undefined; //add the onRej try block + if(onRejTuple && onRejBody && onRejBody.length > 0){ + //fix this -> "e" ? + let onRejTryBlock = createTry(createBlock(onRejBody), getSynthesizedDeepClone(cascadingCatchBlock), undefined) + topLevelCatchClause = createCatchClause("e", createBlock(createNodeArray([onRejTryBlock]))); + } + /* if(onRejTuple && onRejBody.length > 0){ let stmts = createNodeArray(onRejBody); @@ -83,8 +100,18 @@ namespace ts.codefix { catchBlock = createCatchClause("e", createBlock(createNodeArray())); }*/ - let range = onResTextRange ? onResTextRange : onCatchTextRange; - changes.replaceRange(sourceFile, range, createTry(tryBlock, catchBlock, undefined)); + let range:TextRange = undefined; + if(onResTextRange && onCatchTextRange){ + range = createTextRange(onResTextRange.pos, onCatchTextRange.end); + }else if(onResTextRange){ + range = onResTextRange; + }else if(onCatchTextRange){ + range = onCatchTextRange; + } + + if(range){ + changes.replaceRange(sourceFile, range, createTry(topLevelTryBlock, topLevelCatchClause, undefined)); + } } function createCascadingCatches(catchArray:[NodeArray, string, TextRange][]): NodeArray{ @@ -105,8 +132,6 @@ namespace ts.codefix { return createNodeArray([createTry(createBlock(getSynthesizedDeepClones(catchItem[0])), createCatchClause(catchItem[1], catchBlock), undefined)]); } - - function findCallToAwait(node: Node, checker:TypeChecker): Expression{ switch(node.kind){ case SyntaxKind.CallExpression: @@ -121,7 +146,6 @@ namespace ts.codefix { return ret; } } - } enum PromiseMemberFunc{ @@ -136,19 +160,19 @@ namespace ts.codefix { switch(node.kind){ case SyntaxKind.PropertyAccessExpression: if((node).name.text === memberFunc && isPromiseType(checker.getTypeAtLocation(node.parent))){ - let parNode = node.parent as CallExpression; + let parNode = getSynthesizedDeepClone(node.parent) as CallExpression; if(parNode.arguments.length > index && isFunctionLikeDeclaration(parNode.arguments[index])){ //inlined function definitiion let funcDecl:FunctionLikeDeclaration = parNode.arguments[index] as FunctionLikeDeclaration; let funcBody:FunctionBody = funcDecl.body as FunctionBody; - retArray.push([createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.getStart(), parNode.end)]); - //return [createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.getStart(), parNode.end)]; + let name:string = funcDecl.parameters[0].symbol ? funcDecl.parameters[0].symbol.name : (funcDecl.parameters[0].name).text; + retArray.push([createNodeArray(funcBody.statements), name, createTextRange(node.parent.getStart(), node.parent.end)]); + //retArray.push([createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.getStart(), parNode.end)]); }else if(parNode.arguments.length > index && isIdentifier(parNode.arguments[index])){ //reference to an elsewhere declared function - let argName = ((checker.getTypeAtLocation(parNode.arguments[index]).symbol.valueDeclaration).parameters[0].name) + let argName = ((checker.getTypeAtLocation(parNode.arguments[index]).symbol.valueDeclaration).parameters[0].name); let callExpr = createCall(parNode.arguments[index], undefined, [argName]); - retArray.push([createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(parNode.getStart(), parNode.end)]); - //return [createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(parNode.getStart(), parNode.end)] + retArray.push([createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(node.parent.getStart(), node.parent.end)]); } } break; @@ -156,10 +180,6 @@ namespace ts.codefix { //recurse for( let child of node.getChildren() ){ - /*let ret = findDotThen(child, checker, onRes, memberFunc); - if(ret){ - return ret; - }*/ findDotThen(child, checker, onRes, memberFunc, retArray); } } From 5099bd1ed179a9807a907d93e929883e19b87c30 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 8 Jun 2018 16:24:30 -0700 Subject: [PATCH 012/196] Fixed the outermost catch to take the correct variable --- src/services/codefixes/convertPromisesToAwaitAndAsync.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 81ad7dbe04768..0d8d9f1877c2a 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -38,9 +38,10 @@ namespace ts.codefix { //get the rejection handlers findDotThen(funcToConvert, checker, false, PromiseMemberFunc.Then, retArrayRej); let onRejTuple:[NodeArray, string, TextRange] = retArrayRej[0]; - let onRejBody:NodeArray = undefined; + let onRejBody:NodeArray, onRejFuncName:string = undefined; if(onRejTuple){ onRejBody = onRejTuple[0]; + onRejFuncName = onRejTuple[1]; } @@ -89,7 +90,7 @@ namespace ts.codefix { if(onRejTuple && onRejBody && onRejBody.length > 0){ //fix this -> "e" ? let onRejTryBlock = createTry(createBlock(onRejBody), getSynthesizedDeepClone(cascadingCatchBlock), undefined) - topLevelCatchClause = createCatchClause("e", createBlock(createNodeArray([onRejTryBlock]))); + topLevelCatchClause = createCatchClause(onRejFuncName, createBlock(createNodeArray([onRejTryBlock]))); } /* From b8353dc6f0d028e8e912e391b279531d5c01bf9c Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 8 Jun 2018 16:56:14 -0700 Subject: [PATCH 013/196] Refactored to use objects instead of tuple arrays --- .../convertPromisesToAwaitAndAsync.ts | 103 ++++++++---------- 1 file changed, 44 insertions(+), 59 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 0d8d9f1877c2a..e1aaf75f3e36e 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -17,41 +17,21 @@ namespace ts.codefix { changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert) //containers to hold the resolve handlers, rejection handlers, and catch handlers - let retArrayRes:[NodeArray, string, TextRange][] = []; - let retArrayRej:[NodeArray, string, TextRange][] = []; - let retArrayCatch:[NodeArray, string, TextRange][] = []; + let resCallbacks:callbackFunction[] = []; + let rejCallbacks:callbackFunction[] = []; + let catchCallbacks:callbackFunction[] = []; //find the function call that returns a promise let callToAwait:Expression = getSynthesizedDeepClone(findCallToAwait(funcToConvert, checker)); - //get the resolve handlers - findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Then, retArrayRes); - let onResTuple:[NodeArray, string, TextRange] = retArrayRes[0]; - let onResBody:NodeArray, onResFuncName:string, onResTextRange:TextRange = undefined; - if(onResTuple){ - onResBody = onResTuple[0]; - onResFuncName = onResTuple[1]; - onResTextRange = onResTuple[2]; - } - + findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Then, resCallbacks); //get the resolve handlers + findDotThen(funcToConvert, checker, false, PromiseMemberFunc.Then, rejCallbacks); //get the rejection handlers + findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Catch, catchCallbacks); //get the catch handlers - //get the rejection handlers - findDotThen(funcToConvert, checker, false, PromiseMemberFunc.Then, retArrayRej); - let onRejTuple:[NodeArray, string, TextRange] = retArrayRej[0]; - let onRejBody:NodeArray, onRejFuncName:string = undefined; - if(onRejTuple){ - onRejBody = onRejTuple[0]; - onRejFuncName = onRejTuple[1]; - } - - - //get the catch handlers - findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Catch, retArrayCatch); - let onCatchTuple:[NodeArray, string, TextRange] = retArrayCatch[0]; - let onCatchTextRange:TextRange = undefined; - if(onCatchTuple){ - onCatchTextRange = onCatchTuple[2]; - } + //note - I will eventually fix this to handle all callbacks + let resCallback = resCallbacks.length > 0 ? resCallbacks[0] : undefined; + let rejCallback = rejCallbacks.length > 0 ? rejCallbacks[0] : undefined; + let catchCallback = rejCallbacks.length > 0 ? rejCallbacks[0] : undefined; //if there is no call to a function that returns a promise, just add async keyword and return if(!callToAwait){ @@ -62,19 +42,20 @@ namespace ts.codefix { let awaitNode = createAwait(callToAwait); //get the cascading catch block - let catchParam = retArrayCatch.length > 0 ? retArrayCatch[retArrayCatch.length-1][1] : "e"; - let cascadingCatchBlock = createCatchClause(catchParam, createBlock(createCascadingCatches(retArrayCatch))); + let catchParam = catchCallbacks.length > 0 ? catchCallbacks[catchCallbacks.length-1].argName : "e"; + let cascadingCatchBlock = createCatchClause(catchParam, createBlock(createCascadingCatches(catchCallbacks))); //get the onRes handler body let onResTryStmts:NodeArray = undefined; - if(onResTuple && onResBody && onResBody.length > 0){ - onResTryStmts = onResBody; + //if(onResTuple && onResBody && onResBody.length > 0){ + if(resCallback){ + onResTryStmts = resCallback.body; } //create the top level try block let topLevelTryStmts:NodeArray; if(onResTryStmts){ - let awaitDecl = createVariableDeclarationList(createNodeArray([createVariableDeclaration(onResFuncName, undefined, awaitNode)])); + let awaitDecl = createVariableDeclarationList(createNodeArray([createVariableDeclaration(resCallback.argName, undefined, awaitNode)])); let awaitDeclStmt = createVariableStatement(undefined, awaitDecl); let onResTryBlock = createBlock(onResTryStmts); topLevelTryStmts = createNodeArray([awaitDeclStmt, createTry(onResTryBlock, getSynthesizedDeepClone(cascadingCatchBlock), undefined)]); @@ -87,27 +68,20 @@ namespace ts.codefix { let topLevelCatchClause:CatchClause = undefined; //add the onRej try block - if(onRejTuple && onRejBody && onRejBody.length > 0){ - //fix this -> "e" ? - let onRejTryBlock = createTry(createBlock(onRejBody), getSynthesizedDeepClone(cascadingCatchBlock), undefined) - topLevelCatchClause = createCatchClause(onRejFuncName, createBlock(createNodeArray([onRejTryBlock]))); + // if(onRejTuple && onRejBody && onRejBody.length > 0){ + if(rejCallback){ + let onRejTryBlock = createTry(createBlock(rejCallback.body), getSynthesizedDeepClone(cascadingCatchBlock), undefined) + topLevelCatchClause = createCatchClause(rejCallback.argName, createBlock(createNodeArray([onRejTryBlock]))); } - /* - if(onRejTuple && onRejBody.length > 0){ - let stmts = createNodeArray(onRejBody); - catchBlock = createCatchClause("e", createBlock(stmts)); - }else{ - catchBlock = createCatchClause("e", createBlock(createNodeArray())); - }*/ let range:TextRange = undefined; - if(onResTextRange && onCatchTextRange){ - range = createTextRange(onResTextRange.pos, onCatchTextRange.end); - }else if(onResTextRange){ - range = onResTextRange; - }else if(onCatchTextRange){ - range = onCatchTextRange; + if(resCallback.range && catchCallback && catchCallback.range){ + range = createTextRange(resCallback.range.pos, catchCallback.range.end); + }else if(resCallback.range){ + range = resCallback.range; + }else if(catchCallback && catchCallback.range){ + range = catchCallback.range; } if(range){ @@ -115,14 +89,14 @@ namespace ts.codefix { } } - function createCascadingCatches(catchArray:[NodeArray, string, TextRange][]): NodeArray{ + function createCascadingCatches(catchArray:callbackFunction[]): NodeArray{ if(catchArray.length == 0){ return undefined; } if(catchArray.length == 1){ - return getSynthesizedDeepClones(catchArray.pop()[0]); + return getSynthesizedDeepClones(catchArray.pop().body); //just add the function call } @@ -130,7 +104,7 @@ namespace ts.codefix { let cascadingCatches = createCascadingCatches(catchArray); let catchBlock = createBlock(cascadingCatches); - return createNodeArray([createTry(createBlock(getSynthesizedDeepClones(catchItem[0])), createCatchClause(catchItem[1], catchBlock), undefined)]); + return createNodeArray([createTry(createBlock(getSynthesizedDeepClones(catchItem.body)), createCatchClause(catchItem.argName, catchBlock), undefined)]); } function findCallToAwait(node: Node, checker:TypeChecker): Expression{ @@ -154,7 +128,19 @@ namespace ts.codefix { Catch = "catch", } - function findDotThen(node:Node, checker:TypeChecker, onRes:boolean, memberFunc:PromiseMemberFunc, retArray:[NodeArray, string, TextRange][]):void{ //[[NodeArray, string, TextRange]]{ + class callbackFunction{ + body: NodeArray; + argName: string; + range: TextRange; + + constructor(_body:NodeArray, _argName:string, _range:TextRange){ + this.body = _body; + this.argName = _argName; + this.range = _range; + } + } + + function findDotThen(node:Node, checker:TypeChecker, onRes:boolean, memberFunc:PromiseMemberFunc, retArray:callbackFunction[]): void{ //[[NodeArray, string, TextRange]]{ const index:number = onRes ? 0 : 1; @@ -167,13 +153,12 @@ namespace ts.codefix { let funcDecl:FunctionLikeDeclaration = parNode.arguments[index] as FunctionLikeDeclaration; let funcBody:FunctionBody = funcDecl.body as FunctionBody; let name:string = funcDecl.parameters[0].symbol ? funcDecl.parameters[0].symbol.name : (funcDecl.parameters[0].name).text; - retArray.push([createNodeArray(funcBody.statements), name, createTextRange(node.parent.getStart(), node.parent.end)]); - //retArray.push([createNodeArray(funcBody.statements), funcDecl.parameters[0].symbol.name, createTextRange(parNode.getStart(), parNode.end)]); + retArray.push(new callbackFunction(createNodeArray(funcBody.statements), name, createTextRange(node.parent.getStart(), node.parent.end))); }else if(parNode.arguments.length > index && isIdentifier(parNode.arguments[index])){ //reference to an elsewhere declared function let argName = ((checker.getTypeAtLocation(parNode.arguments[index]).symbol.valueDeclaration).parameters[0].name); let callExpr = createCall(parNode.arguments[index], undefined, [argName]); - retArray.push([createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(node.parent.getStart(), node.parent.end)]); + retArray.push(new callbackFunction(createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(node.parent.getStart(), node.parent.end))); } } break; From 25efbed4a3cb5c4dd10ae9fc5face4d8d335b6e0 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 11 Jun 2018 14:32:52 -0700 Subject: [PATCH 014/196] Initial commit of recursive refactoring --- .../convertPromisesToAwaitAndAsync.ts | 75 +++++++++++++++++-- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index e1aaf75f3e36e..ded3dfac6ef27 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -15,15 +15,30 @@ namespace ts.codefix { const funcToConvert = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, false)).valueDeclaration; //add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert) - +/* //containers to hold the resolve handlers, rejection handlers, and catch handlers let resCallbacks:callbackFunction[] = []; let rejCallbacks:callbackFunction[] = []; let catchCallbacks:callbackFunction[] = []; //find the function call that returns a promise - let callToAwait:Expression = getSynthesizedDeepClone(findCallToAwait(funcToConvert, checker)); - + let callToAwait:Expression = findCallToAwait(funcToConvert, checker); + */ + + for(let child of funcToConvert.getChildren()){ + if(child.kind === SyntaxKind.Block){ + for(let stmt of (child).statements){ + if(stmt.kind === SyntaxKind.ExpressionStatement && (stmt).expression.kind === SyntaxKind.CallExpression){ + let newNode = parseCallback((stmt).expression as CallExpression, checker); + if(newNode){ + changes.replaceNode(sourceFile, child, createBlock(newNode)); + } + } + } + } + } + + /* findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Then, resCallbacks); //get the resolve handlers findDotThen(funcToConvert, checker, false, PromiseMemberFunc.Then, rejCallbacks); //get the rejection handlers findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Catch, catchCallbacks); //get the catch handlers @@ -40,6 +55,10 @@ namespace ts.codefix { } let awaitNode = createAwait(callToAwait); + let children = callToAwait.getChildren(); + if(children){ + + } //get the cascading catch block let catchParam = catchCallbacks.length > 0 ? catchCallbacks[catchCallbacks.length-1].argName : "e"; @@ -64,11 +83,12 @@ namespace ts.codefix { topLevelTryStmts = createNodeArray([createStatement(awaitNode)]); } - let topLevelTryBlock:Block = createBlock(topLevelTryStmts); //add the onRes try block - let topLevelCatchClause:CatchClause = undefined; //add the onRej try block + let topLevelTryBlock:Block = createBlock(topLevelTryStmts); //add the onRes try block + let topLevelCatchClause:CatchClause = undefined; //add the onRej try block // if(onRejTuple && onRejBody && onRejBody.length > 0){ + if(rejCallback){ let onRejTryBlock = createTry(createBlock(rejCallback.body), getSynthesizedDeepClone(cascadingCatchBlock), undefined) topLevelCatchClause = createCatchClause(rejCallback.argName, createBlock(createNodeArray([onRejTryBlock]))); @@ -168,10 +188,53 @@ namespace ts.codefix { for( let child of node.getChildren() ){ findDotThen(child, checker, onRes, memberFunc, retArray); } + */ + } + + + function parseCallback(node:Expression, checker:TypeChecker): Statement[]{ + if(node.kind === SyntaxKind.CallExpression && isPromiseType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression){ + return parsePromiseCall(node as CallExpression); + }else if(node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)){ + return parseThen(node as CallExpression, checker); + }else if(node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "catch", checker)){ + return parseCatch(node as CallExpression, checker); + }else if(node.kind === SyntaxKind.PropertyAccessExpression){ + return parseCallback((node).expression, checker) + } } + function parseCatch(node:CallExpression, checker:TypeChecker): Statement[]{ + let func:Identifier = node.arguments[0] as Identifier; + + let tryBlock = createBlock(parseCallback(node.expression, checker)); + let catchClause = createCatchClause("e", createBlock([createReturn(createCall(func, undefined, [createIdentifier("idk")]))])) + + return [createTry(tryBlock, catchClause, undefined)] + } + + function parseThen(node:CallExpression, checker:TypeChecker): Statement[]{ + let res:Identifier = node.arguments[0] as Identifier; + let rej:Identifier = node.arguments[1] as Identifier; + + let tryBlock = createBlock(parseCallback(node.expression, checker)); + let catchClause = createCatchClause("e", createBlock([createStatement(createCall(rej, undefined, [createIdentifier("e")]))])); + + return [createTry(tryBlock, catchClause, undefined) as Statement, createStatement(createCall(res, undefined, [createIdentifier("val")])) as Statement]; + } + + function parsePromiseCall(node:CallExpression): Statement[]{ + return [createVariableStatement(undefined, [createVariableDeclaration(createIdentifier("val"), undefined, createAwait(node.expression))])]; + } + + function isCallback(node:CallExpression, funcName:string, checker:TypeChecker):boolean{ + if(node.expression.kind !== SyntaxKind.PropertyAccessExpression){ + return false; + } + return (node.expression).name.text === funcName && isPromiseType(checker.getTypeAtLocation(node)); + } + function isPromiseType(T:Type):boolean{ return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; } - } \ No newline at end of file From 0b760bf6968ea96b410533a4eeb49e4ae9e4c220 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 11 Jun 2018 17:43:27 -0700 Subject: [PATCH 015/196] Got inlined arrow functions working with recursive method --- .../convertPromisesToAwaitAndAsync.ts | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index ded3dfac6ef27..c2be16333a41e 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -31,7 +31,8 @@ namespace ts.codefix { if(stmt.kind === SyntaxKind.ExpressionStatement && (stmt).expression.kind === SyntaxKind.CallExpression){ let newNode = parseCallback((stmt).expression as CallExpression, checker); if(newNode){ - changes.replaceNode(sourceFile, child, createBlock(newNode)); + changes.replaceNodeWithNodes(sourceFile, stmt, newNode); + //changes.replaceNode(sourceFile, child, createBlock(newNode)); } } } @@ -205,26 +206,35 @@ namespace ts.codefix { } function parseCatch(node:CallExpression, checker:TypeChecker): Statement[]{ - let func:Identifier = node.arguments[0] as Identifier; + let func= node.arguments[0]; let tryBlock = createBlock(parseCallback(node.expression, checker)); - let catchClause = createCatchClause("e", createBlock([createReturn(createCall(func, undefined, [createIdentifier("idk")]))])) - + let catchClause = createCatchClause("e", createBlock(getCallbackBody(func, "e"))) return [createTry(tryBlock, catchClause, undefined)] } function parseThen(node:CallExpression, checker:TypeChecker): Statement[]{ - let res:Identifier = node.arguments[0] as Identifier; - let rej:Identifier = node.arguments[1] as Identifier; + let res = node.arguments[0]; + let rej = node.arguments[1]; let tryBlock = createBlock(parseCallback(node.expression, checker)); - let catchClause = createCatchClause("e", createBlock([createStatement(createCall(rej, undefined, [createIdentifier("e")]))])); + let catchClause = createCatchClause("e", createBlock(getCallbackBody(rej, "e"))); return [createTry(tryBlock, catchClause, undefined) as Statement, createStatement(createCall(res, undefined, [createIdentifier("val")])) as Statement]; } function parsePromiseCall(node:CallExpression): Statement[]{ - return [createVariableStatement(undefined, [createVariableDeclaration(createIdentifier("val"), undefined, createAwait(node.expression))])]; + return [createVariableStatement(undefined, [createVariableDeclaration(createIdentifier("val"), undefined, createAwait(node))])]; + } + + function getCallbackBody(func: Node, argName: string): NodeArray{ + switch(func.kind){ + case SyntaxKind.Identifier: + return createNodeArray([createStatement(createCall(func as Identifier, undefined, [createIdentifier(argName)]))]); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + return (func).body.statements; + } } function isCallback(node:CallExpression, funcName:string, checker:TypeChecker):boolean{ From d3a92d937b8d2ea62340838908c35aac09eef71b Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 12 Jun 2018 13:54:43 -0700 Subject: [PATCH 016/196] Fixed if not rej function --- .../convertPromisesToAwaitAndAsync.ts | 188 ++---------------- 1 file changed, 22 insertions(+), 166 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index c2be16333a41e..82d4b8b86382c 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -15,185 +15,37 @@ namespace ts.codefix { const funcToConvert = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, false)).valueDeclaration; //add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert) -/* - //containers to hold the resolve handlers, rejection handlers, and catch handlers - let resCallbacks:callbackFunction[] = []; - let rejCallbacks:callbackFunction[] = []; - let catchCallbacks:callbackFunction[] = []; - - //find the function call that returns a promise - let callToAwait:Expression = findCallToAwait(funcToConvert, checker); - */ for(let child of funcToConvert.getChildren()){ if(child.kind === SyntaxKind.Block){ for(let stmt of (child).statements){ - if(stmt.kind === SyntaxKind.ExpressionStatement && (stmt).expression.kind === SyntaxKind.CallExpression){ - let newNode = parseCallback((stmt).expression as CallExpression, checker); - if(newNode){ - changes.replaceNodeWithNodes(sourceFile, stmt, newNode); - //changes.replaceNode(sourceFile, child, createBlock(newNode)); - } - } - } - } - } - - /* - findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Then, resCallbacks); //get the resolve handlers - findDotThen(funcToConvert, checker, false, PromiseMemberFunc.Then, rejCallbacks); //get the rejection handlers - findDotThen(funcToConvert, checker, true, PromiseMemberFunc.Catch, catchCallbacks); //get the catch handlers - - //note - I will eventually fix this to handle all callbacks - let resCallback = resCallbacks.length > 0 ? resCallbacks[0] : undefined; - let rejCallback = rejCallbacks.length > 0 ? rejCallbacks[0] : undefined; - let catchCallback = rejCallbacks.length > 0 ? rejCallbacks[0] : undefined; - - //if there is no call to a function that returns a promise, just add async keyword and return - if(!callToAwait){ - //TODO: ADD THE ASYNC KEYWORD - return; - } - - let awaitNode = createAwait(callToAwait); - let children = callToAwait.getChildren(); - if(children){ - - } - - //get the cascading catch block - let catchParam = catchCallbacks.length > 0 ? catchCallbacks[catchCallbacks.length-1].argName : "e"; - let cascadingCatchBlock = createCatchClause(catchParam, createBlock(createCascadingCatches(catchCallbacks))); - - //get the onRes handler body - let onResTryStmts:NodeArray = undefined; - //if(onResTuple && onResBody && onResBody.length > 0){ - if(resCallback){ - onResTryStmts = resCallback.body; - } - - //create the top level try block - let topLevelTryStmts:NodeArray; - if(onResTryStmts){ - let awaitDecl = createVariableDeclarationList(createNodeArray([createVariableDeclaration(resCallback.argName, undefined, awaitNode)])); - let awaitDeclStmt = createVariableStatement(undefined, awaitDecl); - let onResTryBlock = createBlock(onResTryStmts); - topLevelTryStmts = createNodeArray([awaitDeclStmt, createTry(onResTryBlock, getSynthesizedDeepClone(cascadingCatchBlock), undefined)]); - - }else{ - topLevelTryStmts = createNodeArray([createStatement(awaitNode)]); - } - - let topLevelTryBlock:Block = createBlock(topLevelTryStmts); //add the onRes try block - let topLevelCatchClause:CatchClause = undefined; //add the onRej try block - - - // if(onRejTuple && onRejBody && onRejBody.length > 0){ - - if(rejCallback){ - let onRejTryBlock = createTry(createBlock(rejCallback.body), getSynthesizedDeepClone(cascadingCatchBlock), undefined) - topLevelCatchClause = createCatchClause(rejCallback.argName, createBlock(createNodeArray([onRejTryBlock]))); - } - - - let range:TextRange = undefined; - if(resCallback.range && catchCallback && catchCallback.range){ - range = createTextRange(resCallback.range.pos, catchCallback.range.end); - }else if(resCallback.range){ - range = resCallback.range; - }else if(catchCallback && catchCallback.range){ - range = catchCallback.range; - } - - if(range){ - changes.replaceRange(sourceFile, range, createTry(topLevelTryBlock, topLevelCatchClause, undefined)); - } - } - - function createCascadingCatches(catchArray:callbackFunction[]): NodeArray{ - - if(catchArray.length == 0){ - return undefined; - } + let promiseCall:CallExpression = getPromiseCall(stmt); + let newNode = parseCallback(promiseCall, checker); - if(catchArray.length == 1){ - return getSynthesizedDeepClones(catchArray.pop().body); - //just add the function call - } - - let catchItem = catchArray.pop(); - let cascadingCatches = createCascadingCatches(catchArray); - let catchBlock = createBlock(cascadingCatches); - - return createNodeArray([createTry(createBlock(getSynthesizedDeepClones(catchItem.body)), createCatchClause(catchItem.argName, catchBlock), undefined)]); - } - - function findCallToAwait(node: Node, checker:TypeChecker): Expression{ - switch(node.kind){ - case SyntaxKind.CallExpression: - if(isPromiseType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression){ - return node as CallExpression; + if(newNode){ + changes.replaceNodeWithNodes(sourceFile, stmt, newNode); + } } - } - - for( let child of node.getChildren() ){ - let ret = findCallToAwait(child, checker); - if(ret){ - return ret; } } } - enum PromiseMemberFunc{ - Then = "then", - Catch = "catch", - } - - class callbackFunction{ - body: NodeArray; - argName: string; - range: TextRange; - - constructor(_body:NodeArray, _argName:string, _range:TextRange){ - this.body = _body; - this.argName = _argName; - this.range = _range; + function getPromiseCall(node:Node): CallExpression{ + if(node.kind == SyntaxKind.CallExpression){ + return node as CallExpression; } - } - function findDotThen(node:Node, checker:TypeChecker, onRes:boolean, memberFunc:PromiseMemberFunc, retArray:callbackFunction[]): void{ //[[NodeArray, string, TextRange]]{ - - const index:number = onRes ? 0 : 1; - - switch(node.kind){ - case SyntaxKind.PropertyAccessExpression: - if((node).name.text === memberFunc && isPromiseType(checker.getTypeAtLocation(node.parent))){ - let parNode = getSynthesizedDeepClone(node.parent) as CallExpression; - if(parNode.arguments.length > index && isFunctionLikeDeclaration(parNode.arguments[index])){ - //inlined function definitiion - let funcDecl:FunctionLikeDeclaration = parNode.arguments[index] as FunctionLikeDeclaration; - let funcBody:FunctionBody = funcDecl.body as FunctionBody; - let name:string = funcDecl.parameters[0].symbol ? funcDecl.parameters[0].symbol.name : (funcDecl.parameters[0].name).text; - retArray.push(new callbackFunction(createNodeArray(funcBody.statements), name, createTextRange(node.parent.getStart(), node.parent.end))); - }else if(parNode.arguments.length > index && isIdentifier(parNode.arguments[index])){ - //reference to an elsewhere declared function - let argName = ((checker.getTypeAtLocation(parNode.arguments[index]).symbol.valueDeclaration).parameters[0].name); - let callExpr = createCall(parNode.arguments[index], undefined, [argName]); - retArray.push(new callbackFunction(createNodeArray([createStatement(callExpr)]), argName.text, createTextRange(node.parent.getStart(), node.parent.end))); - } - } - break; + for( let child of node.getChildren().filter(node => isNode(node)) ){ + return getPromiseCall(child); } - - //recurse - for( let child of node.getChildren() ){ - findDotThen(child, checker, onRes, memberFunc, retArray); - } - */ } function parseCallback(node:Expression, checker:TypeChecker): Statement[]{ + if(!node){ + return; + } + if(node.kind === SyntaxKind.CallExpression && isPromiseType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression){ return parsePromiseCall(node as CallExpression); }else if(node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)){ @@ -216,11 +68,15 @@ namespace ts.codefix { function parseThen(node:CallExpression, checker:TypeChecker): Statement[]{ let res = node.arguments[0]; let rej = node.arguments[1]; + + if(rej){ + let tryBlock = createBlock(parseCallback(node.expression, checker)); + let catchClause = createCatchClause("e", createBlock(getCallbackBody(rej, "e"))); - let tryBlock = createBlock(parseCallback(node.expression, checker)); - let catchClause = createCatchClause("e", createBlock(getCallbackBody(rej, "e"))); - - return [createTry(tryBlock, catchClause, undefined) as Statement, createStatement(createCall(res, undefined, [createIdentifier("val")])) as Statement]; + return [createTry(tryBlock, catchClause, undefined) as Statement].concat(getCallbackBody(res, "val")); + }else{ + return parseCallback(node.expression, checker).concat(getCallbackBody(res,"val")); //hack -> fix this + } } function parsePromiseCall(node:CallExpression): Statement[]{ From 460c6dde47ccf481000f0f1cd3fc7476231bfe01 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 12 Jun 2018 14:11:06 -0700 Subject: [PATCH 017/196] Added a couple tests --- .../cases/fourslash/codeFixPromiseToAsync.ts | 21 ++++++++++++++ .../codeFixPromiseToAsyncFunctionRef.ts | 28 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsync.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncFunctionRef.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts new file mode 100644 index 0000000000000..20e645b6b623e --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsync.ts @@ -0,0 +1,21 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(result => { console.log(result); }); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + var result = await fetch('http://yahoo.com); + console.log(result); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncFunctionRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncFunctionRef.ts new file mode 100644 index 0000000000000..934b3db922564 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncFunctionRef.ts @@ -0,0 +1,28 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(res); +////} +//// +////function res(result){ +//// console.log(result); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + var result = await fetch('http://yahoo.com); + res(result); +} +function res(result){ + console.log(result); +}`, +}); From 73ed97edada9a3e4b8ab26e2f822a32c21dfbd76 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 13 Jun 2018 15:45:54 -0700 Subject: [PATCH 018/196] Added a series of test cases --- .../fourslash/codeFixPromiseToAsyncCatch.ts | 25 ++++++++++ .../codeFixPromiseToAsyncCatchAndRej.ts | 29 +++++++++++ .../codeFixPromiseToAsyncCatchAndRejRef.ts | 50 +++++++++++++++++++ .../codeFixPromiseToAsyncCatchRef.ts | 39 +++++++++++++++ .../codeFixPromiseToAsyncCatch_noBrackets.ts | 25 ++++++++++ .../fourslash/codeFixPromiseToAsyncNoRes.ts | 24 +++++++++ .../fourslash/codeFixPromiseToAsyncRej.ts | 25 ++++++++++ .../fourslash/codeFixPromiseToAsyncRejRef.ts | 39 +++++++++++++++ .../codeFixPromiseToAsyncRej_NoBrackets.ts | 25 ++++++++++ .../fourslash/codeFixPromiseToAsyncResRef.ts | 28 +++++++++++ ...codeFixPromiseToAsyncResRef_NoReturnVal.ts | 28 +++++++++++ .../codeFixPromiseToAsync_NoBrackets.ts | 21 ++++++++ 12 files changed, 358 insertions(+) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncRej.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts new file mode 100644 index 0000000000000..183184852dc67 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts @@ -0,0 +1,25 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(result => { console.log(result); }).catch(err => { console.log(err); }); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + try{ + var result = await fetch('http://yahoo.com); + console.log(result); + }catch(err){ + console.log(err); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts new file mode 100644 index 0000000000000..531a10ceb757e --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts @@ -0,0 +1,29 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) }); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + try{ + try{ + var result = await fetch('http://yahoo.com); + }catch(rejection){ + console.log("rejected", rejection); + } + console.log(result); + }catch(err){ + console.log(err); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts new file mode 100644 index 0000000000000..4aa2c67e12d90 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -0,0 +1,50 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').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); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + try{ + try{ + var result = await fetch('http://yahoo.com); + }catch(rejection){ + return rej(rejection); + } + return res(result); + }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); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts new file mode 100644 index 0000000000000..a4786e8906887 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts @@ -0,0 +1,39 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(res).catch(catch_err) +////} +//// +////function res(result){ +//// console.log(result); +////} +//// +////function catch_err(err){ +//// console.log(err); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + try{ + var result = await fetch('http://yahoo.com); + return res(result); + }catch(err){ + return catch_err(err); + } +} +function res(result){ + console.log(result); +} +function catch_err(err){ + console.log(err); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts new file mode 100644 index 0000000000000..1f1621d2c413e --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts @@ -0,0 +1,25 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(result => console.log(result); ).catch(err => console.log(err); ); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + try{ + var result = await fetch('http://yahoo.com); + console.log(result); + }catch(err){ + console.log(err); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts new file mode 100644 index 0000000000000..b7afcd3568545 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts @@ -0,0 +1,24 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(null, rejection => console.log("rejected:", rejection); ); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + try{ + var result = await fetch('http://yahoo.com); + }catch(rejection){ + console.log("rejected", rejection); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts new file mode 100644 index 0000000000000..f9c92b79b03a4 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -0,0 +1,25 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + try{ + var result = await fetch('http://yahoo.com); + }catch(rejection){ + console.log("rejected", rejection); + } + console.log(result); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts new file mode 100644 index 0000000000000..e97e0b8f5a834 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -0,0 +1,39 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(res, rej); +////} +//// +////function res(result){ +//// console.log(result); +////} +//// +////function rej(err){ +//// console.log(err); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + try{ + var result = await fetch('http://yahoo.com); + }catch(err){ + return rej(err); + } + return res(result); +} +function res(result){ + console.log(result); +} +function rej(err){ + console.log(err); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts new file mode 100644 index 0000000000000..f1da6e09e94d5 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts @@ -0,0 +1,25 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(result => console.log(result), rejection => console.log("rejected:", rejection); ); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + try{ + var result = await fetch('http://yahoo.com); + }catch(rejection){ + console.log("rejected", rejection); + } + console.log(result); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts new file mode 100644 index 0000000000000..5dd723dd75037 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts @@ -0,0 +1,28 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(res); +////} +//// +////function res(result){ +//// return result.ok; +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + var result = await fetch('http://yahoo.com); + return res(result); +} +function res(result){ + return result.ok; +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts new file mode 100644 index 0000000000000..8ee98f457c41b --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -0,0 +1,28 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(res); +////} +//// +////function res(result){ +//// console.log(result); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + var result = await fetch('http://yahoo.com); + return res(result); +} +function res(result){ + console.log(result); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts new file mode 100644 index 0000000000000..e97928173c462 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts @@ -0,0 +1,21 @@ +/// + +////function f():Promise { +//// return fetch('http://yahoo.com').then(result => console.log(result); ); +////} + +verify.getSuggestionDiagnostics([{ + message: "Convert to use async and await", + code: 95055, + reportsUnnecessary: true, +}]); + +verify.codeFix({ + description: "Remove unreachable code", + index: 0, + newFileContent: +`async function f() { + var result = await fetch('http://yahoo.com); + console.log(result); +}`, +}); From effb55907b4f82d4a59ec77f04c847526688ec0f Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 14 Jun 2018 09:12:18 -0700 Subject: [PATCH 019/196] Fixed copy paste mistakes in tests --- tests/cases/fourslash/codeFixPromiseToAsync.ts | 10 +++++----- tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts | 10 +++++----- .../fourslash/codeFixPromiseToAsyncCatchAndRej.ts | 10 +++++----- .../fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts | 9 ++++----- .../cases/fourslash/codeFixPromiseToAsyncCatchRef.ts | 9 ++++----- .../codeFixPromiseToAsyncCatch_noBrackets.ts | 12 ++++++------ tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts | 9 ++++----- tests/cases/fourslash/codeFixPromiseToAsyncRej.ts | 9 ++++----- tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts | 9 ++++----- .../fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts | 11 +++++------ tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts | 9 ++++----- .../codeFixPromiseToAsyncResRef_NoReturnVal.ts | 9 ++++----- .../fourslash/codeFixPromiseToAsync_NoBrackets.ts | 12 ++++++------ 13 files changed, 60 insertions(+), 68 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts index 20e645b6b623e..49c77c964a766 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync.ts @@ -1,17 +1,17 @@ /// -////function f():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => { console.log(result); }); ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); + verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts index 183184852dc67..4e5f302a46dbc 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts @@ -1,17 +1,17 @@ /// -////function f():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => { console.log(result); }).catch(err => { console.log(err); }); ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); + verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts index 531a10ceb757e..eee26fc7b226d 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts @@ -1,17 +1,17 @@ /// -////function f():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) }); ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); + verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index 4aa2c67e12d90..5d78ef068caec 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -1,6 +1,6 @@ /// -////function f():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res, rej).catch(catch_err) ////} //// @@ -17,13 +17,12 @@ ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts index a4786e8906887..833b57650b52d 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts @@ -1,6 +1,6 @@ /// -////function f():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res).catch(catch_err) ////} //// @@ -13,13 +13,12 @@ ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts index 1f1621d2c413e..752e42431e0df 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts @@ -1,17 +1,17 @@ /// -////function f():Promise { -//// return fetch('http://yahoo.com').then(result => console.log(result); ).catch(err => console.log(err); ); +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => console.log(result)).catch(err => console.log(err)); ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); + verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts index b7afcd3568545..557600d0d3008 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts @@ -1,17 +1,16 @@ /// -////function f():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(null, rejection => console.log("rejected:", rejection); ); ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts index f9c92b79b03a4..c82ea6b81ec4c 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -1,17 +1,16 @@ /// -////function f():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index e97e0b8f5a834..fd812c9195ae2 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -1,6 +1,6 @@ /// -////function f():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res, rej); ////} //// @@ -13,13 +13,12 @@ ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts index f1da6e09e94d5..9db7ad4edea52 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts @@ -1,17 +1,16 @@ /// -////function f():Promise { -//// return fetch('http://yahoo.com').then(result => console.log(result), rejection => console.log("rejected:", rejection); ); +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => console.log(result), rejection => console.log("rejected:", rejection)); ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts index 5dd723dd75037..b6655011ba6fc 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts @@ -1,6 +1,6 @@ /// -////function f():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res); ////} //// @@ -9,13 +9,12 @@ ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts index 8ee98f457c41b..e08ea3e2417a3 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -1,6 +1,6 @@ /// -////function f():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res); ////} //// @@ -9,13 +9,12 @@ ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { diff --git a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts index e97928173c462..e7b5c2d4620a1 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts @@ -1,17 +1,17 @@ /// -////function f():Promise { -//// return fetch('http://yahoo.com').then(result => console.log(result); ); +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => console.log(result)); ////} verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, + message: "This may be converted to use async and await.", + code: 80006, }]); + verify.codeFix({ - description: "Remove unreachable code", + description: "Convert to use async and await", index: 0, newFileContent: `async function f() { From 3b1aff1f19972a948ccdb37aa8bc082cd3cab508 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 14 Jun 2018 10:07:49 -0700 Subject: [PATCH 020/196] Fixed linting errors --- .../convertPromisesToAwaitAndAsync.ts | 113 ++++++++++-------- 1 file changed, 63 insertions(+), 50 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 82d4b8b86382c..8f10178de91d1 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -11,18 +11,17 @@ namespace ts.codefix { getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncAwait(changes, err.file!, err.start, context.program.getTypeChecker())), }); function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { - //get the function declaration - returns a promise - const funcToConvert = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, false)).valueDeclaration; - //add the async keyword - changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert) - - for(let child of funcToConvert.getChildren()){ - if(child.kind === SyntaxKind.Block){ - for(let stmt of (child).statements){ - let promiseCall:CallExpression = getPromiseCall(stmt); - let newNode = parseCallback(promiseCall, checker); + // get the function declaration - returns a promise + const funcToConvert = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)).valueDeclaration; + // add the async keyword + changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); + for (const child of funcToConvert.getChildren()) { + if (child.kind === SyntaxKind.Block) { + for (const stmt of (child).statements) { + const promiseCall: CallExpression = getPromiseCall(stmt); + const newNode = parseCallback(promiseCall, checker); - if(newNode){ + if (newNode) { changes.replaceNodeWithNodes(sourceFile, stmt, newNode); } } @@ -30,77 +29,91 @@ namespace ts.codefix { } } - function getPromiseCall(node:Node): CallExpression{ - if(node.kind == SyntaxKind.CallExpression){ + function getPromiseCall(node: Node): CallExpression { + if (node.kind === SyntaxKind.CallExpression) { return node as CallExpression; } - for( let child of node.getChildren().filter(node => isNode(node)) ){ + for (const child of node.getChildren().filter(node => isNode(node))) { return getPromiseCall(child); } } - - - function parseCallback(node:Expression, checker:TypeChecker): Statement[]{ - if(!node){ + + function parseCallback(node: Expression, checker: TypeChecker, argName?: string): Statement[] { + if (!node) { return; } - if(node.kind === SyntaxKind.CallExpression && isPromiseType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression){ - return parsePromiseCall(node as CallExpression); - }else if(node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)){ + if (node.kind === SyntaxKind.CallExpression && isPromiseType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression) { + return parsePromiseCall(node as CallExpression, argName); + } + else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { return parseThen(node as CallExpression, checker); - }else if(node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "catch", checker)){ + } + else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "catch", checker)) { return parseCatch(node as CallExpression, checker); - }else if(node.kind === SyntaxKind.PropertyAccessExpression){ - return parseCallback((node).expression, checker) + } + else if (node.kind === SyntaxKind.PropertyAccessExpression) { + return parseCallback((node).expression, checker, argName); } } - function parseCatch(node:CallExpression, checker:TypeChecker): Statement[]{ - let func= node.arguments[0]; + function parseCatch(node: CallExpression, checker: TypeChecker): Statement[] { + const func = node.arguments[0]; - let tryBlock = createBlock(parseCallback(node.expression, checker)); - let catchClause = createCatchClause("e", createBlock(getCallbackBody(func, "e"))) - return [createTry(tryBlock, catchClause, undefined)] + const tryBlock = createBlock(parseCallback(node.expression, checker)); + const catchClause = createCatchClause("e", createBlock(getCallbackBody(func, "e"))); + return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node:CallExpression, checker:TypeChecker): Statement[]{ - let res = node.arguments[0]; - let rej = node.arguments[1]; - - if(rej){ - let tryBlock = createBlock(parseCallback(node.expression, checker)); - let catchClause = createCatchClause("e", createBlock(getCallbackBody(rej, "e"))); + function parseThen(node: CallExpression, checker: TypeChecker): Statement[] { + const res = node.arguments[0]; + const rej = node.arguments[1]; + // TODO - what if this is a binding pattern and not an Identifier + let argName = "val"; + if (isFunctionLikeDeclaration(res)) { + argName = (res.parameters[0].name).text; + } + + if (rej) { + const tryBlock = createBlock(parseCallback(node.expression, checker)); + const catchClause = createCatchClause("e", createBlock(getCallbackBody(rej, "e"))); - return [createTry(tryBlock, catchClause, undefined) as Statement].concat(getCallbackBody(res, "val")); - }else{ - return parseCallback(node.expression, checker).concat(getCallbackBody(res,"val")); //hack -> fix this + return [createTry(tryBlock, catchClause, /*finalllyBlock*/ undefined) as Statement].concat(getCallbackBody(res, argName)); + } + else { + return parseCallback(node.expression, checker, argName).concat(getCallbackBody(res, argName)); } } - function parsePromiseCall(node:CallExpression): Statement[]{ - return [createVariableStatement(undefined, [createVariableDeclaration(createIdentifier("val"), undefined, createAwait(node))])]; + function parsePromiseCall(node: CallExpression, argName?: string): Statement[] { + if (!argName) { + argName = "val"; // fix this to maybe not always create a variable declaration if not necessary + } + return [createVariableStatement(/*modifiers*/ undefined, [createVariableDeclaration(createIdentifier(argName), /*type*/ undefined, createAwait(node))])]; } - function getCallbackBody(func: Node, argName: string): NodeArray{ - switch(func.kind){ + function getCallbackBody(func: Node, argName: string): NodeArray { + switch (func.kind) { case SyntaxKind.Identifier: - return createNodeArray([createStatement(createCall(func as Identifier, undefined, [createIdentifier(argName)]))]); + return createNodeArray([(createReturn(createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)])))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.ArrowFunction: - return (func).body.statements; + if ((func).body.kind === SyntaxKind.Block) { + return (func).body.statements; + } + else { + return createNodeArray([createStatement((func).body as Expression)]); + } } } - - function isCallback(node:CallExpression, funcName:string, checker:TypeChecker):boolean{ - if(node.expression.kind !== SyntaxKind.PropertyAccessExpression){ + function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { + if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; } return (node.expression).name.text === funcName && isPromiseType(checker.getTypeAtLocation(node)); } - - function isPromiseType(T:Type):boolean{ + function isPromiseType(T: Type): boolean { return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; } } \ No newline at end of file From 71b422d95a916dc4430ab35a0029317247c4d4d2 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 14 Jun 2018 11:07:07 -0700 Subject: [PATCH 021/196] Fixed suggestion diagnostics to only suggest when funciton is returning a promise with a .then()/.catch(). Added corresponding test and fixed lint errors --- src/services/suggestionDiagnostics.ts | 48 ++++++++++++++----- .../codeFixPromiseToAsyncNoSuggestion.ts | 7 +++ 2 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 694251b1ab1fe..a138b0f32a9d2 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -24,20 +24,30 @@ namespace ts { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration)); } } - - if(node.modifiers && containsAsync(node.modifiers)){ + if (node.modifiers && isAsyncFunction(node)) { break; } - const returnType = checker.getReturnTypeOfSignature(checker.getSignatureFromDeclaration( node)) - if(!returnType || !returnType.symbol){ + const returnType = checker.getReturnTypeOfSignature(checker.getSignatureFromDeclaration(node)); + if (!returnType || !returnType.symbol) { break; } - if(isPromiseType(returnType)){ + // collect all the return statements + // check that a property access expression exists in there and that it is a handler + const retStmts: ReturnStatement[] = []; + getReturnStmts(node, retStmts); + + let isCallback = false; + for (const stmt of retStmts) { + if (hasCallback(stmt)) { + isCallback = true; + break; + } + } + if (isPromiseType(returnType) && isCallback) { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_use_async_and_await)); } - break; } @@ -120,17 +130,29 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - function containsAsync(arr: NodeArray): boolean{ - for(let modifier of arr){ - if(modifier.kind === SyntaxKind.AsyncKeyword){ + function isPromiseType(T: Type): boolean { + return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; + } + + function hasCallback(stmt: Node): boolean { + if (stmt.kind === SyntaxKind.CallExpression && (stmt).expression.kind === SyntaxKind.PropertyAccessExpression && + (((stmt).expression).name.text === "then" || ((stmt).expression).name.text === "catch")) { return true; - } } - return false; + for (const child of stmt.getChildren().filter(node => isNode(node))) { + return hasCallback(child); + } } - function isPromiseType(T:Type):boolean{ - return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; + function getReturnStmts(node: Node, retStmts: ReturnStatement[]) { + for (const child of node.getChildren().filter(node => isNode(node))) { + if (child.kind === SyntaxKind.ReturnStatement) { + retStmts.push(child as ReturnStatement); + } + else { + getReturnStmts(child, retStmts); + } + } } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts new file mode 100644 index 0000000000000..d45c77ba59d8b --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts @@ -0,0 +1,7 @@ +/// + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com') +////} + +verify.getSuggestionDiagnostics([]); From 9b2aa3086e75c7eecddc575a97c21ba7655b703c Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 14 Jun 2018 15:03:36 -0700 Subject: [PATCH 022/196] Refactored to avoid using getChildren() --- .../convertPromisesToAwaitAndAsync.ts | 32 ++++++-------- src/services/suggestionDiagnostics.ts | 43 +++++++++---------- 2 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 8f10178de91d1..b169952354dd4 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -12,30 +12,24 @@ namespace ts.codefix { }); function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { // get the function declaration - returns a promise - const funcToConvert = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)).valueDeclaration; + const funcToConvert: FunctionLikeDeclaration = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)).valueDeclaration as FunctionLikeDeclaration; // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); - for (const child of funcToConvert.getChildren()) { - if (child.kind === SyntaxKind.Block) { - for (const stmt of (child).statements) { - const promiseCall: CallExpression = getPromiseCall(stmt); - const newNode = parseCallback(promiseCall, checker); + + const stmts: NodeArray = (funcToConvert.body).statements; + for (const stmt of stmts) { + forEachChild(stmt, function visit(node: Node) { + if (node.kind === SyntaxKind.CallExpression) { + const newNode = parseCallback(node as CallExpression, checker); if (newNode) { - changes.replaceNodeWithNodes(sourceFile, stmt, newNode); + changes.replaceNodeWithNodes(sourceFile, stmt, newNode); } } - } - } - } - - function getPromiseCall(node: Node): CallExpression { - if (node.kind === SyntaxKind.CallExpression) { - return node as CallExpression; - } - - for (const child of node.getChildren().filter(node => isNode(node))) { - return getPromiseCall(child); + else if (!isFunctionLike(node)) { + forEachChild(node, visit); + } + }); } } @@ -113,7 +107,7 @@ namespace ts.codefix { } return (node.expression).name.text === funcName && isPromiseType(checker.getTypeAtLocation(node)); } - function isPromiseType(T: Type): boolean { + function isPromiseType(T: Type): boolean { return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; } } \ No newline at end of file diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index a138b0f32a9d2..cf5ff1ea2f7e1 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -38,14 +38,7 @@ namespace ts { const retStmts: ReturnStatement[] = []; getReturnStmts(node, retStmts); - let isCallback = false; - for (const stmt of retStmts) { - if (hasCallback(stmt)) { - isCallback = true; - break; - } - } - if (isPromiseType(returnType) && isCallback) { + if (isPromiseType(returnType) && retStmts.length > 0) { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_use_async_and_await)); } break; @@ -134,25 +127,29 @@ namespace ts { return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; } - function hasCallback(stmt: Node): boolean { - if (stmt.kind === SyntaxKind.CallExpression && (stmt).expression.kind === SyntaxKind.PropertyAccessExpression && - (((stmt).expression).name.text === "then" || ((stmt).expression).name.text === "catch")) { - return true; - } + function getReturnStmts(node: Node, retStmts: ReturnStatement[]) { + forEachChild(node, visit); - for (const child of stmt.getChildren().filter(node => isNode(node))) { - return hasCallback(child); - } - } + function visit(node: Node) { + if (isFunctionLike(node)) { + return; + } - function getReturnStmts(node: Node, retStmts: ReturnStatement[]) { - for (const child of node.getChildren().filter(node => isNode(node))) { - if (child.kind === SyntaxKind.ReturnStatement) { - retStmts.push(child as ReturnStatement); + if (node.kind === SyntaxKind.ReturnStatement) { + forEachChild(node, hasCallback); } - else { - getReturnStmts(child, retStmts); + + function hasCallback(child: Node) { + if (child.kind === SyntaxKind.CallExpression && (child).expression.kind === SyntaxKind.PropertyAccessExpression && + (((child).expression).name.text === "then" || ((child).expression).name.text === "catch")) { + retStmts.push(node as ReturnStatement); + } + else if (!isFunctionLike(child)) { + forEachChild(child, hasCallback); + } } + + forEachChild(node, visit); } } } From 2384440d8ff72172c470d2f8e34b2b947d250fd3 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 15 Jun 2018 11:50:17 -0700 Subject: [PATCH 023/196] Created isPromiseLike function in the checker to replace my isPromise helepr function. Also fixed a small bug in the code fix to create the correct exception var names --- src/compiler/checker.ts | 8 +++++ src/compiler/types.ts | 1 + .../convertPromisesToAwaitAndAsync.ts | 30 +++++++++++-------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 653e608849902..d194fc983a664 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -289,6 +289,7 @@ namespace ts { getNeverType: () => neverType, isSymbolAccessible, isArrayLikeType, + isPromiseLikeType, getAllPossiblePropertiesOfTypes, getSuggestionForNonexistentProperty: (node, type) => getSuggestionForNonexistentProperty(node, type), getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), @@ -419,6 +420,7 @@ namespace ts { let globalObjectType: ObjectType; let globalFunctionType: ObjectType; let globalArrayType: GenericType; + let globalPromiseType: GenericType; let globalReadonlyArrayType: GenericType; let globalStringType: ObjectType; let globalNumberType: ObjectType; @@ -11810,6 +11812,11 @@ namespace ts { return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalArrayType; } + function isPromiseLikeType(type: Type): boolean { + return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalPromiseType /*|| (type).target === globalReadonlyArrayType)*/ || + !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, globalPromiseType); + } + function isArrayLikeType(type: Type): boolean { // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, // or if it is not the undefined or null type and if it is assignable to ReadonlyArray @@ -27028,6 +27035,7 @@ namespace ts { getSymbolLinks(unknownSymbol).type = unknownType; // Initialize special types + globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true); globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true); globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 23e990d913e70..952c87ff8e5d5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3046,6 +3046,7 @@ namespace ts { * Does not include properties of primitive types. */ /* @internal */ isArrayLikeType(type: Type): boolean; + /* @internal */ isPromiseLikeType(type: Type): boolean; /* @internal */ getAllPossiblePropertiesOfTypes(type: ReadonlyArray): Symbol[]; /* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags, excludeGlobals: boolean): Symbol | undefined; /* @internal */ getJsxNamespace(location?: Node): string; diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index b169952354dd4..9fc3cb4a3f374 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -16,7 +16,6 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); - const stmts: NodeArray = (funcToConvert.body).statements; for (const stmt of stmts) { forEachChild(stmt, function visit(node: Node) { @@ -38,7 +37,7 @@ namespace ts.codefix { return; } - if (node.kind === SyntaxKind.CallExpression && isPromiseType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression) { + if (node.kind === SyntaxKind.CallExpression && checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression) { return parsePromiseCall(node as CallExpression, argName); } else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { @@ -54,9 +53,14 @@ namespace ts.codefix { function parseCatch(node: CallExpression, checker: TypeChecker): Statement[] { const func = node.arguments[0]; + let argName = "e"; + if (isFunctionLikeDeclaration(func)){ + argName = (func.parameters[0].name).text; + } const tryBlock = createBlock(parseCallback(node.expression, checker)); - const catchClause = createCatchClause("e", createBlock(getCallbackBody(func, "e"))); + //instead of using e -> get the paramater of the catch function and use that + const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName))); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } @@ -64,19 +68,24 @@ namespace ts.codefix { const res = node.arguments[0]; const rej = node.arguments[1]; // TODO - what if this is a binding pattern and not an Identifier - let argName = "val"; + let argNameRes = "val"; if (isFunctionLikeDeclaration(res)) { - argName = (res.parameters[0].name).text; + argNameRes = (res.parameters[0].name).text; } if (rej) { + let argNameRej = "e"; + if (isFunctionLikeDeclaration(rej)){ + argNameRej = (rej.parameters[0].name).text; + } + const tryBlock = createBlock(parseCallback(node.expression, checker)); - const catchClause = createCatchClause("e", createBlock(getCallbackBody(rej, "e"))); + const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej))); - return [createTry(tryBlock, catchClause, /*finalllyBlock*/ undefined) as Statement].concat(getCallbackBody(res, argName)); + return [createTry(tryBlock, catchClause, /*finalllyBlock*/ undefined) as Statement].concat(getCallbackBody(res, argNameRes)); } else { - return parseCallback(node.expression, checker, argName).concat(getCallbackBody(res, argName)); + return parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes)); } } @@ -105,9 +114,6 @@ namespace ts.codefix { if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; } - return (node.expression).name.text === funcName && isPromiseType(checker.getTypeAtLocation(node)); - } - function isPromiseType(T: Type): boolean { - return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; + return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); } } \ No newline at end of file From b806922834a78082edbb5aa2efdf059d80a725f4 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 15 Jun 2018 13:53:24 -0700 Subject: [PATCH 024/196] Fixed formatting and spacing on tests and added es6 target --- .../cases/fourslash/codeFixPromiseToAsync.ts | 11 +++++----- .../fourslash/codeFixPromiseToAsyncCatch.ts | 19 ++++++++++------- .../codeFixPromiseToAsyncCatchAndRej.ts | 20 +++++++++++------- .../codeFixPromiseToAsyncCatchAndRejRef.ts | 21 ++++++++++++------- .../codeFixPromiseToAsyncCatchRef.ts | 17 ++++++++------- .../codeFixPromiseToAsyncCatch_noBrackets.ts | 20 ++++++++++-------- .../fourslash/codeFixPromiseToAsyncNoRes.ts | 4 +++- .../codeFixPromiseToAsyncNoSuggestion.ts | 2 ++ .../fourslash/codeFixPromiseToAsyncRej.ts | 6 ++++-- .../fourslash/codeFixPromiseToAsyncRejRef.ts | 11 +++++++--- .../codeFixPromiseToAsyncRej_NoBrackets.ts | 19 ++++++++++------- .../fourslash/codeFixPromiseToAsyncResRef.ts | 7 +++++-- ...codeFixPromiseToAsyncResRef_NoReturnVal.ts | 9 +++++--- .../codeFixPromiseToAsync_NoBrackets.ts | 10 +++++---- 14 files changed, 109 insertions(+), 67 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts index 49c77c964a766..61c2216805e92 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync.ts @@ -1,6 +1,8 @@ /// -////function [|f|]():Promise { +// @target: es6 + +////function [|f|](): Promise { //// return fetch('http://yahoo.com').then(result => { console.log(result); }); ////} @@ -9,13 +11,12 @@ verify.getSuggestionDiagnostics([{ code: 80006, }]); - verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - var result = await fetch('http://yahoo.com); - console.log(result); +`async function f(): Promise { + var result = await fetch('http://yahoo.com'); + console.log(result); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts index 4e5f302a46dbc..f6232a881ada6 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts @@ -1,6 +1,8 @@ /// -////function [|f|]():Promise { +// @target: es6 + +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => { console.log(result); }).catch(err => { console.log(err); }); ////} @@ -14,12 +16,13 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - try{ - var result = await fetch('http://yahoo.com); - console.log(result); - }catch(err){ - console.log(err); - } +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + console.log(result); + } + catch (err) { + console.log(err); + } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts index eee26fc7b226d..6dcc1246cf1fa 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts @@ -1,6 +1,8 @@ /// -////function [|f|]():Promise { +// @target: es6 + +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) }); ////} @@ -14,15 +16,17 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - try{ - try{ - var result = await fetch('http://yahoo.com); - }catch(rejection){ - console.log("rejected", rejection); +`async function f():Promise { + try { + try { + var result = await fetch('http://yahoo.com'); + } + catch (rejection) { + console.log("rejected:", rejection); } console.log(result); - }catch(err){ + } + catch (err) { console.log(err); } }`, diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index 5d78ef068caec..92846fbfc011c 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -1,6 +1,8 @@ /// -////function [|f|]():Promise { +// @target: es6 + +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res, rej).catch(catch_err) ////} //// @@ -25,24 +27,29 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - try{ - try{ - var result = await fetch('http://yahoo.com); - }catch(rejection){ +`async function f(): Promise { + try { + try { + var result = await fetch('http://yahoo.com'); + } + catch (rejection) { return rej(rejection); } return res(result); - }catch(err){ + } + 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); }`, diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts index 833b57650b52d..812372e2a110f 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts @@ -1,6 +1,6 @@ /// -////function [|f|]():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res).catch(catch_err) ////} //// @@ -21,17 +21,20 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - try{ - var result = await fetch('http://yahoo.com); - return res(result); - }catch(err){ - return catch_err(err); +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + return res(result); + } + catch(err) { + return catch_err(err); } } + function res(result){ console.log(result); } + function catch_err(err){ console.log(err); }`, diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts index 752e42431e0df..cd910c18629ac 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts @@ -1,6 +1,8 @@ /// -////function [|f|]():Promise { +// @target: es6 + +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => console.log(result)).catch(err => console.log(err)); ////} @@ -9,17 +11,17 @@ verify.getSuggestionDiagnostics([{ code: 80006, }]); - verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - try{ - var result = await fetch('http://yahoo.com); - console.log(result); - }catch(err){ - console.log(err); - } +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + console.log(result); + } + catch (err) { + console.log(err); + } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts index 557600d0d3008..5e93f915628b4 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts @@ -1,5 +1,7 @@ /// +// @target: es6 + ////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(null, rejection => console.log("rejected:", rejection); ); ////} @@ -15,7 +17,7 @@ verify.codeFix({ newFileContent: `async function f() { try{ - var result = await fetch('http://yahoo.com); + var result = await fetch('http://yahoo.com'); }catch(rejection){ console.log("rejected", rejection); } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts index d45c77ba59d8b..394a470f3710a 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts @@ -1,5 +1,7 @@ /// +// @target: es6 + ////function [|f|]():Promise { //// return fetch('http://yahoo.com') ////} diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts index c82ea6b81ec4c..60e9aaf414e89 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -1,5 +1,7 @@ /// +// @target: es6 + ////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); ////} @@ -15,9 +17,9 @@ verify.codeFix({ newFileContent: `async function f() { try{ - var result = await fetch('http://yahoo.com); + var result = await fetch('http://yahoo.com'); }catch(rejection){ - console.log("rejected", rejection); + console.log("rejected:", rejection); } console.log(result); }`, diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index fd812c9195ae2..83949f3b2ff07 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -1,5 +1,7 @@ /// +// @target: es6 + ////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res, rej); ////} @@ -22,16 +24,19 @@ verify.codeFix({ index: 0, newFileContent: `async function f() { - try{ - var result = await fetch('http://yahoo.com); - }catch(err){ + try { + var result = await fetch('http://yahoo.com'); + } + catch(err){ return rej(err); } return res(result); } + function res(result){ console.log(result); } + function rej(err){ console.log(err); }`, diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts index 9db7ad4edea52..04a07a91a2ee6 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts @@ -1,6 +1,8 @@ /// -////function [|f|]():Promise { +// @target: es6 + +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => console.log(result), rejection => console.log("rejected:", rejection)); ////} @@ -13,12 +15,13 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - try{ - var result = await fetch('http://yahoo.com); - }catch(rejection){ - console.log("rejected", rejection); - } - console.log(result); +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + } + catch (rejection) { + console.log("rejected:", rejection); + } + console.log(result); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts index b6655011ba6fc..6c589e62e2a41 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts @@ -1,6 +1,8 @@ /// -////function [|f|]():Promise { +// @target: es6 + +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res); ////} //// @@ -17,10 +19,11 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { +`async function f():Promise { var result = await fetch('http://yahoo.com); return res(result); } + function res(result){ return result.ok; }`, diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts index e08ea3e2417a3..a43c50742541a 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -1,6 +1,8 @@ /// -////function [|f|]():Promise { +// @target: es6 + +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res); ////} //// @@ -17,10 +19,11 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - var result = await fetch('http://yahoo.com); +`async function f():Promise { + var result = await fetch('http://yahoo.com'); return res(result); } + function res(result){ console.log(result); }`, diff --git a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts index e7b5c2d4620a1..11a573cabc7f5 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts @@ -1,6 +1,8 @@ /// -////function [|f|]():Promise { +// @target: es6 + +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => console.log(result)); ////} @@ -14,8 +16,8 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - var result = await fetch('http://yahoo.com); - console.log(result); +`async function f():Promise { + var result = await fetch('http://yahoo.com'); + console.log(result); }`, }); From 602a8a9941b456b0f8ffd43d82b1091ed9622a31 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 15 Jun 2018 14:51:52 -0700 Subject: [PATCH 025/196] Fixed spacing on tests and added a couple more tests --- .../codeFixPromiseToAsyncCatchAndRejRef.ts | 14 ++++++------ .../codeFixPromiseToAsyncCatchRef.ts | 10 ++++----- .../codeFixPromiseToAsyncIgnoreArgs1.ts | 22 +++++++++++++++++++ .../codeFixPromiseToAsyncIgnoreArgs2.ts | 22 +++++++++++++++++++ .../fourslash/codeFixPromiseToAsyncRej.ts | 17 +++++++------- .../fourslash/codeFixPromiseToAsyncRejRef.ts | 20 ++++++++--------- .../fourslash/codeFixPromiseToAsyncResRef.ts | 6 ++--- ...codeFixPromiseToAsyncResRef_NoReturnVal.ts | 6 ++--- 8 files changed, 81 insertions(+), 36 deletions(-) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index 92846fbfc011c..33cc1300c0288 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -2,16 +2,16 @@ // @target: es6 -////function [|f|]():Promise { +////function [|f|](): Promise { //// return fetch('http://yahoo.com').then(res, rej).catch(catch_err) ////} //// ////function res(result){ -//// console.log(result); +//// console.log(result); ////} //// ////function rej(rejection){ -//// return rejection.ok; +//// return rejection.ok; ////} //// ////function catch_err(err){ @@ -34,11 +34,11 @@ verify.codeFix({ } catch (rejection) { return rej(rejection); - } + } return res(result); - } - catch(err) { - return catch_err(err) + } + catch (err) { + return catch_err(err); } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts index 812372e2a110f..555b767a23e3f 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts @@ -5,7 +5,7 @@ ////} //// ////function res(result){ -//// console.log(result); +//// console.log(result); ////} //// ////function catch_err(err){ @@ -25,10 +25,10 @@ verify.codeFix({ try { var result = await fetch('http://yahoo.com'); return res(result); - } - catch(err) { - return catch_err(err); - } + } + catch (err) { + return catch_err(err); + } } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts new file mode 100644 index 0000000000000..e26fca8145988 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts @@ -0,0 +1,22 @@ +/// + +// @target: es6 + +////function [|f|](): Promise { +//// return fetch('http://yahoo.com').then( _ => { console.log(done); }); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f(): Promise { + await fetch('http://yahoo.com'); + console.log(done); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts new file mode 100644 index 0000000000000..8bbc1cbeaa1ab --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts @@ -0,0 +1,22 @@ +/// + +// @target: es6 + +////function [|f|](): Promise { +//// return fetch('http://yahoo.com').then( () => { console.log(done); }); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f(): Promise { + await fetch('http://yahoo.com'); + console.log(done); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts index 60e9aaf414e89..555a202d00a45 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -2,7 +2,7 @@ // @target: es6 -////function [|f|]():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); ////} @@ -15,12 +15,13 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - try{ - var result = await fetch('http://yahoo.com'); - }catch(rejection){ - console.log("rejected:", rejection); - } - console.log(result); +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + } + catch (rejection) { + console.log("rejected:", rejection); + } + console.log(result); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index 83949f3b2ff07..54d27ab8e0603 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -2,12 +2,12 @@ // @target: es6 -////function [|f|]():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(res, rej); ////} //// ////function res(result){ -//// console.log(result); +//// console.log(result); ////} //// ////function rej(err){ @@ -23,14 +23,14 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - try { - var result = await fetch('http://yahoo.com'); - } - catch(err){ - return rej(err); - } - return res(result); +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + } + catch (err) { + return rej(err); + } + return res(result); } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts index 6c589e62e2a41..2e01c0548b677 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts @@ -7,7 +7,7 @@ ////} //// ////function res(result){ -//// return result.ok; +//// return result.ok; ////} verify.getSuggestionDiagnostics([{ @@ -20,8 +20,8 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - var result = await fetch('http://yahoo.com); - return res(result); + var result = await fetch('http://yahoo.com'); + return res(result); } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts index a43c50742541a..ae2aa8eb2ec24 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -7,7 +7,7 @@ ////} //// ////function res(result){ -//// console.log(result); +//// console.log(result); ////} verify.getSuggestionDiagnostics([{ @@ -20,8 +20,8 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - var result = await fetch('http://yahoo.com'); - return res(result); + var result = await fetch('http://yahoo.com'); + return res(result); } function res(result){ From c1e5de648612c072c10fa4fa03c36d1ed0e0706e Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 15 Jun 2018 14:52:57 -0700 Subject: [PATCH 026/196] More variable name fixes --- .../convertPromisesToAwaitAndAsync.ts | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 9fc3cb4a3f374..dcd454379f9ac 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -53,12 +53,9 @@ namespace ts.codefix { function parseCatch(node: CallExpression, checker: TypeChecker): Statement[] { const func = node.arguments[0]; - let argName = "e"; - if (isFunctionLikeDeclaration(func)){ - argName = (func.parameters[0].name).text; - } - - const tryBlock = createBlock(parseCallback(node.expression, checker)); + let argName = getArgName(func, "arg", checker); + + const tryBlock = createBlock(parseCallback(node.expression, checker, argName)); //instead of using e -> get the paramater of the catch function and use that const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName))); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; @@ -68,18 +65,12 @@ namespace ts.codefix { const res = node.arguments[0]; const rej = node.arguments[1]; // TODO - what if this is a binding pattern and not an Identifier - let argNameRes = "val"; - if (isFunctionLikeDeclaration(res)) { - argNameRes = (res.parameters[0].name).text; - } + let argNameRes = getArgName(res, "val", checker); if (rej) { - let argNameRej = "e"; - if (isFunctionLikeDeclaration(rej)){ - argNameRej = (rej.parameters[0].name).text; - } + let argNameRej = getArgName(rej, "e", checker); - const tryBlock = createBlock(parseCallback(node.expression, checker)); + const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes)); const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej))); return [createTry(tryBlock, catchClause, /*finalllyBlock*/ undefined) as Statement].concat(getCallbackBody(res, argNameRes)); @@ -116,4 +107,21 @@ namespace ts.codefix { } return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); } + function getArgName(funcNode: Node, defaultVal: string, checker:TypeChecker): string{ + if (isFunctionLikeDeclaration(funcNode)) { + return (funcNode.parameters[0].name).text; + } + else if(checker.getTypeAtLocation(funcNode).getCallSignatures().length > 0 && checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters.length > 0) { + let name = checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters[0].name + if (name !== "_" && name !== "()"){ //do i need this? and if i do, is this correct? + return name; + } + else { + return defaultVal; + } + } + else { + return defaultVal; + } + } } \ No newline at end of file From bece3e11e81d0ee6573a77340ac3e751083f438c Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 15 Jun 2018 16:08:26 -0700 Subject: [PATCH 027/196] Fixed more variable name issues --- .../convertPromisesToAwaitAndAsync.ts | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index dcd454379f9ac..28a7ff50aecd5 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -32,13 +32,13 @@ namespace ts.codefix { } } - function parseCallback(node: Expression, checker: TypeChecker, argName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, argName?: string, argUsed?: boolean): Statement[] { if (!node) { return; } if (node.kind === SyntaxKind.CallExpression && checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression) { - return parsePromiseCall(node as CallExpression, argName); + return parsePromiseCall(node as CallExpression, argName, argUsed); } else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { return parseThen(node as CallExpression, checker); @@ -47,16 +47,15 @@ namespace ts.codefix { return parseCatch(node as CallExpression, checker); } else if (node.kind === SyntaxKind.PropertyAccessExpression) { - return parseCallback((node).expression, checker, argName); + return parseCallback((node).expression, checker, argName, argUsed); } } function parseCatch(node: CallExpression, checker: TypeChecker): Statement[] { const func = node.arguments[0]; - let argName = getArgName(func, "arg", checker); - - const tryBlock = createBlock(parseCallback(node.expression, checker, argName)); - //instead of using e -> get the paramater of the catch function and use that + const argName = getArgName(func, "arg", checker); + const tryBlock = createBlock(parseCallback(node.expression, checker, argName, argName !== "arg")); + // instead of using e -> get the paramater of the catch function and use that const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName))); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } @@ -65,26 +64,27 @@ namespace ts.codefix { const res = node.arguments[0]; const rej = node.arguments[1]; // TODO - what if this is a binding pattern and not an Identifier - let argNameRes = getArgName(res, "val", checker); + const argNameRes = getArgName(res, "val", checker); if (rej) { - let argNameRej = getArgName(rej, "e", checker); + const argNameRej = getArgName(rej, "e", checker); - const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes)); + const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes, argNameRes !== "val")); const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej))); return [createTry(tryBlock, catchClause, /*finalllyBlock*/ undefined) as Statement].concat(getCallbackBody(res, argNameRes)); } else { - return parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes)); + return parseCallback(node.expression, checker, argNameRes, argNameRes !== "val").concat(getCallbackBody(res, argNameRes)); } } - function parsePromiseCall(node: CallExpression, argName?: string): Statement[] { + function parsePromiseCall(node: CallExpression, argName?: string, argUsed?: boolean): Statement[] { if (!argName) { argName = "val"; // fix this to maybe not always create a variable declaration if not necessary } - return [createVariableStatement(/*modifiers*/ undefined, [createVariableDeclaration(createIdentifier(argName), /*type*/ undefined, createAwait(node))])]; + return argUsed ? [createVariableStatement(/*modifiers*/ undefined, [createVariableDeclaration(createIdentifier(argName), /*type*/ undefined, createAwait(node))])] : + [createStatement(createAwait(node))]; } function getCallbackBody(func: Node, argName: string): NodeArray { @@ -107,19 +107,19 @@ namespace ts.codefix { } return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); } - function getArgName(funcNode: Node, defaultVal: string, checker:TypeChecker): string{ - if (isFunctionLikeDeclaration(funcNode)) { - return (funcNode.parameters[0].name).text; - } - else if(checker.getTypeAtLocation(funcNode).getCallSignatures().length > 0 && checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters.length > 0) { - let name = checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters[0].name - if (name !== "_" && name !== "()"){ //do i need this? and if i do, is this correct? + function getArgName(funcNode: Node, defaultVal: string, checker: TypeChecker): string { + if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { + const name = (funcNode.parameters[0].name).text; + if (name !== "_"){ return name; - } - else { + }else{ return defaultVal; } } + else if (checker.getTypeAtLocation(funcNode).getCallSignatures().length > 0 && checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters.length > 0) { + const name = checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters[0].name; + return name; + } else { return defaultVal; } From 4820eff4df0feb8dd13622b69d209644c6fa819b Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 15 Jun 2018 16:28:32 -0700 Subject: [PATCH 028/196] Added awaits to all callbacks that are function references (not defined inline) and updated tests accordingly --- src/services/codefixes/convertPromisesToAwaitAndAsync.ts | 7 ++++--- .../cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts | 6 +++--- tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts | 4 ++-- tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts | 4 ++-- tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts | 2 +- .../fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts | 2 +- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 28a7ff50aecd5..e88291c1a80b2 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -90,7 +90,7 @@ namespace ts.codefix { function getCallbackBody(func: Node, argName: string): NodeArray { switch (func.kind) { case SyntaxKind.Identifier: - return createNodeArray([(createReturn(createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)])))]); + return createNodeArray([(createReturn(createAwait(createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]))))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.ArrowFunction: if ((func).body.kind === SyntaxKind.Block) { @@ -110,9 +110,10 @@ namespace ts.codefix { function getArgName(funcNode: Node, defaultVal: string, checker: TypeChecker): string { if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { const name = (funcNode.parameters[0].name).text; - if (name !== "_"){ + if (name !== "_") { return name; - }else{ + } + else { return defaultVal; } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index 33cc1300c0288..e8ce58df38873 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -33,12 +33,12 @@ verify.codeFix({ var result = await fetch('http://yahoo.com'); } catch (rejection) { - return rej(rejection); + return await rej(rejection); } - return res(result); + return await res(result); } catch (err) { - return catch_err(err); + return await catch_err(err); } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts index 555b767a23e3f..0e7eb3e0a76d6 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts @@ -24,10 +24,10 @@ verify.codeFix({ `async function f():Promise { try { var result = await fetch('http://yahoo.com'); - return res(result); + return await res(result); } catch (err) { - return catch_err(err); + return await catch_err(err); } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index 54d27ab8e0603..62750091f8f57 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -28,9 +28,9 @@ verify.codeFix({ var result = await fetch('http://yahoo.com'); } catch (err) { - return rej(err); + return await rej(err); } - return res(result); + return await res(result); } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts index 2e01c0548b677..e48c35b79b9e7 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts @@ -21,7 +21,7 @@ verify.codeFix({ newFileContent: `async function f():Promise { var result = await fetch('http://yahoo.com'); - return res(result); + return await res(result); } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts index ae2aa8eb2ec24..d6f41cff8ff55 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -21,7 +21,7 @@ verify.codeFix({ newFileContent: `async function f():Promise { var result = await fetch('http://yahoo.com'); - return res(result); + return await res(result); } function res(result){ From 84521cb3f9ab95c5a1755cf59cb1f515e84b0a1e Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 18 Jun 2018 09:45:19 -0700 Subject: [PATCH 029/196] adding tests for multiple thens --- .../codeFixPromiseToAsyncMultipleThens.ts | 39 +++++++++++++++++++ ...xPromiseToAsyncMultipleThensSameVarName.ts | 39 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts new file mode 100644 index 0000000000000..2344cc66fcb38 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts @@ -0,0 +1,39 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(res).then(res2); +////} +//// +////function res(result){ +//// return result.ok; +////} +//// +////function res2(result){ +//// console.log(result); +///} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + let result = await fetch('http://yahoo.com'); + let result2 = await res(result); + return await res2(result2); +} + +function res(result){ + return result.ok; +} + +function res2(result2){ + console.log(result2); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts new file mode 100644 index 0000000000000..6fb096fefbcb2 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts @@ -0,0 +1,39 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(res).then(res2); +////} +//// +////function res(result){ +//// return result.ok; +////} +//// +////function res2(result){ +//// console.log(result); +///} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + let result = await fetch('http://yahoo.com'); + let temp = await res(result); + return await res2(temp); +} + +function res(result){ + return result.ok; +} + +function res2(result){ + console.log(result); +}`, +}); From f9e3840a86af7b6f989a76614b848ed28fb8d753 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 18 Jun 2018 09:45:19 -0700 Subject: [PATCH 030/196] adding tests for multiple thens --- .../codeFixPromiseToAsyncMultipleThens.ts | 39 +++++++++++++++++++ ...xPromiseToAsyncMultipleThensSameVarName.ts | 39 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts new file mode 100644 index 0000000000000..2344cc66fcb38 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts @@ -0,0 +1,39 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(res).then(res2); +////} +//// +////function res(result){ +//// return result.ok; +////} +//// +////function res2(result){ +//// console.log(result); +///} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + let result = await fetch('http://yahoo.com'); + let result2 = await res(result); + return await res2(result2); +} + +function res(result){ + return result.ok; +} + +function res2(result2){ + console.log(result2); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts new file mode 100644 index 0000000000000..6fb096fefbcb2 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts @@ -0,0 +1,39 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(res).then(res2); +////} +//// +////function res(result){ +//// return result.ok; +////} +//// +////function res2(result){ +//// console.log(result); +///} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + let result = await fetch('http://yahoo.com'); + let temp = await res(result); + return await res2(temp); +} + +function res(result){ + return result.ok; +} + +function res2(result){ + console.log(result); +}`, +}); From 8a80eefbf5f40d4ecf83b2d82a43793d6d0d0c61 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 18 Jun 2018 10:20:36 -0700 Subject: [PATCH 031/196] Fixed merge conflicts --- .../codeFixPromiseToAsyncCatchAndRejRef.ts | 56 +++++++++++++++++++ .../codeFixPromiseToAsyncCatchRef.ts | 41 ++++++++++++++ .../fourslash/codeFixPromiseToAsyncRejRef.ts | 43 ++++++++++++++ ...codeFixPromiseToAsyncResRef_NoReturnVal.ts | 30 ++++++++++ 4 files changed, 170 insertions(+) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts new file mode 100644 index 0000000000000..e8ce58df38873 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -0,0 +1,56 @@ +/// + +// @target: es6 + +////function [|f|](): Promise { +//// return fetch('http://yahoo.com').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); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f(): Promise { + try { + try { + var result = await fetch('http://yahoo.com'); + } + catch (rejection) { + return await rej(rejection); + } + return await res(result); + } + catch (err) { + return await catch_err(err); + } +} + +function res(result){ + console.log(result); +} + +function rej(rejection){ + return rejection.ok; +} + +function catch_err(err){ + console.log(err); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts new file mode 100644 index 0000000000000..0e7eb3e0a76d6 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts @@ -0,0 +1,41 @@ +/// + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(res).catch(catch_err) +////} +//// +////function res(result){ +//// console.log(result); +////} +//// +////function catch_err(err){ +//// console.log(err); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + return await res(result); + } + catch (err) { + return await catch_err(err); + } +} + +function res(result){ + console.log(result); +} + +function catch_err(err){ + console.log(err); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts new file mode 100644 index 0000000000000..62750091f8f57 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -0,0 +1,43 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(res, rej); +////} +//// +////function res(result){ +//// console.log(result); +////} +//// +////function rej(err){ +//// console.log(err); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + } + catch (err) { + return await rej(err); + } + return await res(result); +} + +function res(result){ + console.log(result); +} + +function rej(err){ + console.log(err); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts new file mode 100644 index 0000000000000..d6f41cff8ff55 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -0,0 +1,30 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(res); +////} +//// +////function res(result){ +//// console.log(result); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + var result = await fetch('http://yahoo.com'); + return await res(result); +} + +function res(result){ + console.log(result); +}`, +}); From dd69f0da74fb03d9a822fd1ca9b583df8186eb54 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 15 Jun 2018 14:51:52 -0700 Subject: [PATCH 032/196] cherry pick --- .../codeFixPromiseToAsyncCatchAndRejRef.ts | 9 +++++++ .../codeFixPromiseToAsyncCatchRef.ts | 7 +++++ .../codeFixPromiseToAsyncIgnoreArgs1.ts | 22 +++++++++++++++ .../codeFixPromiseToAsyncIgnoreArgs2.ts | 22 +++++++++++++++ .../fourslash/codeFixPromiseToAsyncRej.ts | 27 +++++++++++++++++++ .../fourslash/codeFixPromiseToAsyncRejRef.ts | 6 +++++ ...codeFixPromiseToAsyncResRef_NoReturnVal.ts | 4 +++ 7 files changed, 97 insertions(+) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncRej.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index e8ce58df38873..d7ced85dc3db6 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -33,12 +33,21 @@ verify.codeFix({ var result = await fetch('http://yahoo.com'); } catch (rejection) { +<<<<<<< HEAD return await rej(rejection); } return await res(result); } catch (err) { return await catch_err(err); +======= + return rej(rejection); + } + return res(result); + } + catch (err) { + return catch_err(err); +>>>>>>> 602a8a9941... Fixed spacing on tests and added a couple more tests } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts index 0e7eb3e0a76d6..d88a3a701d30c 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts @@ -24,10 +24,17 @@ verify.codeFix({ `async function f():Promise { try { var result = await fetch('http://yahoo.com'); +<<<<<<< HEAD return await res(result); } catch (err) { return await catch_err(err); +======= + return res(result); + } + catch (err) { + return catch_err(err); +>>>>>>> 602a8a9941... Fixed spacing on tests and added a couple more tests } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts new file mode 100644 index 0000000000000..e26fca8145988 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts @@ -0,0 +1,22 @@ +/// + +// @target: es6 + +////function [|f|](): Promise { +//// return fetch('http://yahoo.com').then( _ => { console.log(done); }); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f(): Promise { + await fetch('http://yahoo.com'); + console.log(done); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts new file mode 100644 index 0000000000000..8bbc1cbeaa1ab --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts @@ -0,0 +1,22 @@ +/// + +// @target: es6 + +////function [|f|](): Promise { +//// return fetch('http://yahoo.com').then( () => { console.log(done); }); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f(): Promise { + await fetch('http://yahoo.com'); + console.log(done); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts new file mode 100644 index 0000000000000..555a202d00a45 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -0,0 +1,27 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + } + catch (rejection) { + console.log("rejected:", rejection); + } + console.log(result); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index 62750091f8f57..18c90adff156e 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -28,9 +28,15 @@ verify.codeFix({ var result = await fetch('http://yahoo.com'); } catch (err) { +<<<<<<< HEAD return await rej(err); } return await res(result); +======= + return rej(err); + } + return res(result); +>>>>>>> 602a8a9941... Fixed spacing on tests and added a couple more tests } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts index d6f41cff8ff55..8962804d5ac93 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -21,7 +21,11 @@ verify.codeFix({ newFileContent: `async function f():Promise { var result = await fetch('http://yahoo.com'); +<<<<<<< HEAD return await res(result); +======= + return res(result); +>>>>>>> 602a8a9941... Fixed spacing on tests and added a couple more tests } function res(result){ From 6e4810d3cf88aa271eaac8330ca019c91dd1d848 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 15 Jun 2018 13:53:24 -0700 Subject: [PATCH 033/196] More cherry pick --- .../fourslash/codeFixPromiseToAsyncCatch.ts | 28 ++++++++++++++++ .../codeFixPromiseToAsyncCatchAndRej.ts | 33 +++++++++++++++++++ .../codeFixPromiseToAsyncCatchAndRejRef.ts | 3 -- .../codeFixPromiseToAsyncCatchRef.ts | 12 +++---- .../codeFixPromiseToAsyncCatch_noBrackets.ts | 27 +++++++++++++++ .../fourslash/codeFixPromiseToAsyncNoRes.ts | 25 ++++++++++++++ .../codeFixPromiseToAsyncNoSuggestion.ts | 9 +++++ .../fourslash/codeFixPromiseToAsyncRej.ts | 14 ++++++++ .../fourslash/codeFixPromiseToAsyncRejRef.ts | 15 +++++++++ .../codeFixPromiseToAsyncRej_NoBrackets.ts | 27 +++++++++++++++ .../fourslash/codeFixPromiseToAsyncResRef.ts | 30 +++++++++++++++++ ...codeFixPromiseToAsyncResRef_NoReturnVal.ts | 4 --- 12 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts new file mode 100644 index 0000000000000..f6232a881ada6 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts @@ -0,0 +1,28 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => { console.log(result); }).catch(err => { console.log(err); }); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + console.log(result); + } + catch (err) { + console.log(err); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts new file mode 100644 index 0000000000000..6dcc1246cf1fa --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts @@ -0,0 +1,33 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) }); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + try { + try { + var result = await fetch('http://yahoo.com'); + } + catch (rejection) { + console.log("rejected:", rejection); + } + console.log(result); + } + catch (err) { + console.log(err); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index d7ced85dc3db6..41a0d7087a9b5 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -33,21 +33,18 @@ verify.codeFix({ var result = await fetch('http://yahoo.com'); } catch (rejection) { -<<<<<<< HEAD return await rej(rejection); } return await res(result); } catch (err) { return await catch_err(err); -======= return rej(rejection); } return res(result); } catch (err) { return catch_err(err); ->>>>>>> 602a8a9941... Fixed spacing on tests and added a couple more tests } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts index d88a3a701d30c..2e496037259a1 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts @@ -24,18 +24,16 @@ verify.codeFix({ `async function f():Promise { try { var result = await fetch('http://yahoo.com'); -<<<<<<< HEAD return await res(result); } catch (err) { return await catch_err(err); -======= - return res(result); - } - catch (err) { - return catch_err(err); ->>>>>>> 602a8a9941... Fixed spacing on tests and added a couple more tests } + return res(result); + } + catch(err) { + return catch_err(err); + } } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts new file mode 100644 index 0000000000000..cd910c18629ac --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts @@ -0,0 +1,27 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => console.log(result)).catch(err => console.log(err)); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + console.log(result); + } + catch (err) { + console.log(err); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts new file mode 100644 index 0000000000000..5e93f915628b4 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts @@ -0,0 +1,25 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(null, rejection => console.log("rejected:", rejection); ); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f() { + try{ + var result = await fetch('http://yahoo.com'); + }catch(rejection){ + console.log("rejected", rejection); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts new file mode 100644 index 0000000000000..394a470f3710a --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts @@ -0,0 +1,9 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com') +////} + +verify.getSuggestionDiagnostics([]); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts index 555a202d00a45..fcdb422ce2572 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -2,7 +2,11 @@ // @target: es6 +<<<<<<< HEAD ////function [|f|]():Promise { +======= +////function [|f|]():Promise { +>>>>>>> b806922834... Fixed formatting and spacing on tests and added es6 target //// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); ////} @@ -15,6 +19,7 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: +<<<<<<< HEAD `async function f():Promise { try { var result = await fetch('http://yahoo.com'); @@ -23,5 +28,14 @@ verify.codeFix({ console.log("rejected:", rejection); } console.log(result); +======= +`async function f() { + try{ + var result = await fetch('http://yahoo.com'); + }catch(rejection){ + console.log("rejected:", rejection); + } + console.log(result); +>>>>>>> b806922834... Fixed formatting and spacing on tests and added es6 target }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index 18c90adff156e..6053e8ede161e 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -2,7 +2,11 @@ // @target: es6 +<<<<<<< HEAD ////function [|f|]():Promise { +======= +////function [|f|]():Promise { +>>>>>>> b806922834... Fixed formatting and spacing on tests and added es6 target //// return fetch('http://yahoo.com').then(res, rej); ////} //// @@ -23,6 +27,7 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: +<<<<<<< HEAD `async function f():Promise { try { var result = await fetch('http://yahoo.com'); @@ -37,6 +42,16 @@ verify.codeFix({ } return res(result); >>>>>>> 602a8a9941... Fixed spacing on tests and added a couple more tests +======= +`async function f() { + try { + var result = await fetch('http://yahoo.com'); + } + catch(err){ + return rej(err); + } + return res(result); +>>>>>>> b806922834... Fixed formatting and spacing on tests and added es6 target } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts new file mode 100644 index 0000000000000..04a07a91a2ee6 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts @@ -0,0 +1,27 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => console.log(result), rejection => console.log("rejected:", rejection)); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + try { + var result = await fetch('http://yahoo.com'); + } + catch (rejection) { + console.log("rejected:", rejection); + } + console.log(result); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts new file mode 100644 index 0000000000000..6c589e62e2a41 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts @@ -0,0 +1,30 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(res); +////} +//// +////function res(result){ +//// return result.ok; +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + var result = await fetch('http://yahoo.com); + return res(result); +} + +function res(result){ + return result.ok; +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts index 8962804d5ac93..d6f41cff8ff55 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -21,11 +21,7 @@ verify.codeFix({ newFileContent: `async function f():Promise { var result = await fetch('http://yahoo.com'); -<<<<<<< HEAD return await res(result); -======= - return res(result); ->>>>>>> 602a8a9941... Fixed spacing on tests and added a couple more tests } function res(result){ From 024ad02fb2f34001bac67179a22886a84f171d60 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 14 Jun 2018 09:12:18 -0700 Subject: [PATCH 034/196] cherry pick --- .../cases/fourslash/codeFixPromiseToAsync.ts | 21 +++++++++++++++++++ .../codeFixPromiseToAsyncCatch_noBrackets.ts | 1 + .../fourslash/codeFixPromiseToAsyncRej.ts | 14 ------------- .../fourslash/codeFixPromiseToAsyncRejRef.ts | 21 ------------------- .../codeFixPromiseToAsync_NoBrackets.ts | 21 +++++++++++++++++++ 5 files changed, 43 insertions(+), 35 deletions(-) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsync.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts new file mode 100644 index 0000000000000..49c77c964a766 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsync.ts @@ -0,0 +1,21 @@ +/// + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => { console.log(result); }); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f() { + var result = await fetch('http://yahoo.com); + console.log(result); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts index cd910c18629ac..c36286e67ea17 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts @@ -11,6 +11,7 @@ verify.getSuggestionDiagnostics([{ code: 80006, }]); + verify.codeFix({ description: "Convert to use async and await", index: 0, diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts index fcdb422ce2572..555a202d00a45 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -2,11 +2,7 @@ // @target: es6 -<<<<<<< HEAD ////function [|f|]():Promise { -======= -////function [|f|]():Promise { ->>>>>>> b806922834... Fixed formatting and spacing on tests and added es6 target //// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); ////} @@ -19,7 +15,6 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -<<<<<<< HEAD `async function f():Promise { try { var result = await fetch('http://yahoo.com'); @@ -28,14 +23,5 @@ verify.codeFix({ console.log("rejected:", rejection); } console.log(result); -======= -`async function f() { - try{ - var result = await fetch('http://yahoo.com'); - }catch(rejection){ - console.log("rejected:", rejection); - } - console.log(result); ->>>>>>> b806922834... Fixed formatting and spacing on tests and added es6 target }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index 6053e8ede161e..62750091f8f57 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -2,11 +2,7 @@ // @target: es6 -<<<<<<< HEAD ////function [|f|]():Promise { -======= -////function [|f|]():Promise { ->>>>>>> b806922834... Fixed formatting and spacing on tests and added es6 target //// return fetch('http://yahoo.com').then(res, rej); ////} //// @@ -27,31 +23,14 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -<<<<<<< HEAD `async function f():Promise { try { var result = await fetch('http://yahoo.com'); } catch (err) { -<<<<<<< HEAD return await rej(err); } return await res(result); -======= - return rej(err); - } - return res(result); ->>>>>>> 602a8a9941... Fixed spacing on tests and added a couple more tests -======= -`async function f() { - try { - var result = await fetch('http://yahoo.com'); - } - catch(err){ - return rej(err); - } - return res(result); ->>>>>>> b806922834... Fixed formatting and spacing on tests and added es6 target } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts new file mode 100644 index 0000000000000..e7b5c2d4620a1 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts @@ -0,0 +1,21 @@ +/// + +////function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => console.log(result)); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f() { + var result = await fetch('http://yahoo.com); + console.log(result); +}`, +}); From 015f972e27ecfe88887fe94bb4294afff42dbcf1 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 18 Jun 2018 15:09:02 -0700 Subject: [PATCH 035/196] Initial commit of multiple thens --- .../convertPromisesToAwaitAndAsync.ts | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index e88291c1a80b2..cc078116949e2 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -32,12 +32,16 @@ namespace ts.codefix { } } + function returnsAPromise(node: CallExpression, checker: TypeChecker): boolean { + return checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression; + } + function parseCallback(node: Expression, checker: TypeChecker, argName?: string, argUsed?: boolean): Statement[] { if (!node) { return; } - if (node.kind === SyntaxKind.CallExpression && checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression) { + if (node.kind === SyntaxKind.CallExpression && returnsAPromise(node as CallExpression, checker)) { return parsePromiseCall(node as CallExpression, argName, argUsed); } else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { @@ -56,7 +60,7 @@ namespace ts.codefix { const argName = getArgName(func, "arg", checker); const tryBlock = createBlock(parseCallback(node.expression, checker, argName, argName !== "arg")); // instead of using e -> get the paramater of the catch function and use that - const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName))); + const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName, node, checker))); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } @@ -70,12 +74,12 @@ namespace ts.codefix { const argNameRej = getArgName(rej, "e", checker); const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes, argNameRes !== "val")); - const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej))); + const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, node, checker, /* isRej */ true))); - return [createTry(tryBlock, catchClause, /*finalllyBlock*/ undefined) as Statement].concat(getCallbackBody(res, argNameRes)); + return [createTry(tryBlock, catchClause, /*finalllyBlock*/ undefined) as Statement].concat(getCallbackBody(res, argNameRes, node, checker)); } else { - return parseCallback(node.expression, checker, argNameRes, argNameRes !== "val").concat(getCallbackBody(res, argNameRes)); + return parseCallback(node.expression, checker, argNameRes, argNameRes !== "val").concat(getCallbackBody(res, argNameRes, node, checker)); } } @@ -84,13 +88,38 @@ namespace ts.codefix { argName = "val"; // fix this to maybe not always create a variable declaration if not necessary } return argUsed ? [createVariableStatement(/*modifiers*/ undefined, [createVariableDeclaration(createIdentifier(argName), /*type*/ undefined, createAwait(node))])] : - [createStatement(createAwait(node))]; + [createStatement(createAwait(node))]; } - function getCallbackBody(func: Node, argName: string): NodeArray { + + function getLastDotThen(node: Expression, checker: TypeChecker): CallExpression { + if (!node || !node.parent) { + return undefined; + } + + let parent = node.parent; + if (parent.kind === SyntaxKind.CallExpression && isCallback(parent as CallExpression, "then", checker)) { + return parent as CallExpression; + } + else { + return getLastDotThen(node.parent as Expression, checker); + } + + } + + function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, isRej: boolean = false): NodeArray { switch (func.kind) { case SyntaxKind.Identifier: - return createNodeArray([(createReturn(createAwait(createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]))))]); + + let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]) + if (!getLastDotThen(parent, checker) || (parent.expression).name.text === "catch" || isRej) { + return createNodeArray([(createReturn(createAwait(synthCall)))]); + } + + let lastDotThen = getLastDotThen(parent, checker); + let tempArgName = lastDotThen ? getArgName(lastDotThen.arguments[0], "temp", checker) : argName; + return createNodeArray([createVariableStatement(/*modifiers*/ undefined, [createVariableDeclaration(tempArgName, undefined, (createAwait(synthCall)))])]); + case SyntaxKind.FunctionDeclaration: case SyntaxKind.ArrowFunction: if ((func).body.kind === SyntaxKind.Block) { @@ -101,18 +130,20 @@ namespace ts.codefix { } } } + function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; } return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); } + function getArgName(funcNode: Node, defaultVal: string, checker: TypeChecker): string { if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { const name = (funcNode.parameters[0].name).text; if (name !== "_") { return name; - } + } else { return defaultVal; } From 41e007e5dd50be00bdc049346ec7dcd8692be9df Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 18 Jun 2018 15:34:33 -0700 Subject: [PATCH 036/196] Fixed error in fourslash case --- tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts index 2344cc66fcb38..d315b1ce82fc2 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts @@ -10,8 +10,8 @@ //// return result.ok; ////} //// -////function res2(result){ -//// console.log(result); +////function res2(result2){ +//// console.log(result2); ///} verify.getSuggestionDiagnostics([{ From 68e0195e9c5bcdb339df9cfa49fd82e1466d99e2 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 18 Jun 2018 16:10:15 -0700 Subject: [PATCH 037/196] oops --- tests/cases/fourslash/codeFixPromiseToAsync.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts index 139d933967942..61c2216805e92 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync.ts @@ -1,6 +1,5 @@ /// -<<<<<<< HEAD // @target: es6 ////function [|f|](): Promise { From 4f972ff7eae364cf368c21d2dff3d6747f6fa784 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 18 Jun 2018 16:11:28 -0700 Subject: [PATCH 038/196] fixed more merge conflicts --- package-lock.json | 217 ++++++++++++------ package.json | 6 + src/harness/tsconfig.json | 1 + src/server/tsconfig.json | 1 + src/server/tsconfig.library.json | 1 + .../convertPromisesToAwaitAndAsync.ts | 3 +- src/services/tsconfig.json | 1 + .../codeFixPromiseToAsyncFunctionRef.ts | 28 --- .../codeFixPromiseToAsyncMultipleThens.ts | 4 +- 9 files changed, 155 insertions(+), 107 deletions(-) delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncFunctionRef.ts diff --git a/package-lock.json b/package-lock.json index d60ec3b75452d..253a5ece94881 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "typescript", - "version": "2.9.0", + "version": "3.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -36,9 +36,9 @@ } }, "@octokit/rest": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.4.0.tgz", - "integrity": "sha512-cmDdt71Kufz0AZt/EPlJbce9NDhoc9ggJm9P7h6Y+WOds19Q6vBJAPApKTIpN4VpnvwB1XHEOdmqP7gZdPCNKw==", + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.6.2.tgz", + "integrity": "sha512-t+D5t08oTjnVG6BHoxqfN+Yugc85V2dCx6opoXgWW09/KbQf5djZ7sGNoLlPZPfp6WQnw03/2reAXANidHWIow==", "dev": true, "requires": { "before-after-hook": "1.1.0", @@ -174,6 +174,15 @@ "@types/node": "8.5.5" } }, + "@types/jake": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/jake/-/jake-0.0.30.tgz", + "integrity": "sha512-F5txkK3aW+fAi4YExGq4Q0W+jJXIhjUvqwMNFPh8kmM+ZU90S/KdDcEV4H+Ug9fHFPAVHG+veaHE8ZHmJJxHCw==", + "dev": true, + "requires": { + "@types/node": "8.5.5" + } + }, "@types/merge2": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/merge2/-/merge2-1.1.4.tgz", @@ -213,8 +222,15 @@ "@types/node": { "version": "8.5.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.5.tgz", - "integrity": "sha512-JRnfoh0Ll4ElmIXKxbUfcOodkGvcNHljct6mO1X9hE/mlrMzAx0hYCLAD7sgT53YAY1HdlpzUcV0CkmDqUqTuA==", - "dev": true + "integrity": "sha512-JRnfoh0Ll4ElmIXKxbUfcOodkGvcNHljct6mO1X9hE/mlrMzAx0hYCLAD7sgT53YAY1HdlpzUcV0CkmDqUqTuA==" + }, + "@types/node-fetch": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.1.1.tgz", + "integrity": "sha512-IfDil0fSMq59n4UsIzgRd5gsgmgn9dl9PzZbguKPsGBpLEIFC7Fr4AroQjIeCYOcDsP1Lszm10wirpaEP26Lhg==", + "requires": { + "@types/node": "8.5.5" + } }, "@types/orchestrator": { "version": "0.3.2", @@ -242,6 +258,15 @@ "@types/node": "8.5.5" } }, + "@types/source-map-support": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.4.0.tgz", + "integrity": "sha512-9oVAi1Jlr274pbMGPEe0S3IPImV9knVNafa6E4MookD/fjOZAE6EmLkFX5ZjtZ9OXNPi2FCIZzUSMvwAUUKeSg==", + "dev": true, + "requires": { + "@types/node": "8.5.5" + } + }, "@types/through2": { "version": "2.0.33", "resolved": "https://registry.npmjs.org/@types/through2/-/through2-2.0.33.tgz", @@ -275,6 +300,16 @@ "@types/node": "8.5.5" } }, + "JSONStream": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", + "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", + "dev": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -725,9 +760,9 @@ "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", "dev": true, "requires": { + "JSONStream": "1.3.2", "combine-source-map": "0.8.0", "defined": "1.0.0", - "JSONStream": "1.3.2", "safe-buffer": "5.1.2", "through2": "2.0.3", "umd": "3.0.3" @@ -749,11 +784,12 @@ "dev": true }, "browserify": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.0.tgz", - "integrity": "sha512-yotdAkp/ZbgDesHQBYU37zjc29JDH4iXT8hjzM1fdUVWogjARX0S1cKeX24Ci6zZ+jG+ADmCTRt6xvtmJnI+BQ==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.2.tgz", + "integrity": "sha512-fMES05wq1Oukts6ksGUU2TMVHHp06LyQt0SIwbXIHm7waSrQmNBZePsU0iM/4f94zbvb/wHma+D1YrdzWYnF/A==", "dev": true, "requires": { + "JSONStream": "1.3.2", "assert": "1.4.1", "browser-pack": "6.1.0", "browser-resolve": "1.11.2", @@ -775,7 +811,6 @@ "https-browserify": "1.0.0", "inherits": "2.0.3", "insert-module-globals": "7.0.6", - "JSONStream": "1.3.2", "labeled-stream-splicer": "2.0.1", "mkdirp": "0.5.1", "module-deps": "6.0.2", @@ -1132,12 +1167,6 @@ } } }, - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", - "dev": true - }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -1596,6 +1625,14 @@ "minimalistic-crypto-utils": "1.0.1" } }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "0.4.23" + } + }, "end-of-stream": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", @@ -1639,10 +1676,9 @@ } }, "es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", - "dev": true + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" }, "es6-promisify": { "version": "5.0.0", @@ -2308,12 +2344,6 @@ "natives": "1.1.3" } }, - "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", - "dev": true - }, "gulp": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", @@ -2523,7 +2553,7 @@ "source-map": "0.6.1", "through2": "2.0.3", "vinyl": "2.1.0", - "vinyl-fs": "3.0.2" + "vinyl-fs": "3.0.3" }, "dependencies": { "glob-stream": { @@ -2585,9 +2615,9 @@ } }, "vinyl-fs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.2.tgz", - "integrity": "sha512-AUSFda1OukBwuLPBTbyuO4IRWgfXmqC4UTW0f8xrCa8Hkv9oyIU+NSqBlgfOLZRoUt7cHdo75hKQghCywpIyIw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", "dev": true, "requires": { "fs-mkdirp-stream": "1.0.0", @@ -2890,6 +2920,14 @@ } } }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": "2.1.2" + } + }, "ieee754": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.11.tgz", @@ -2933,10 +2971,10 @@ "integrity": "sha512-R3sidKJr3SsggqQQ5cEwQb3pWG8RNx0UnpyeiOSR6jorRIeAOzH2gkTWnNdMnyRiVbjrG047K7UCtlMkQ1Mo9w==", "dev": true, "requires": { + "JSONStream": "1.3.2", "combine-source-map": "0.8.0", "concat-stream": "1.6.2", "is-buffer": "1.1.6", - "JSONStream": "1.3.2", "lexical-scope": "1.2.0", "path-is-absolute": "1.0.1", "process": "0.11.10", @@ -3137,6 +3175,11 @@ "is-unc-path": "1.0.0" } }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", @@ -3182,6 +3225,26 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "1.7.3", + "whatwg-fetch": "2.0.4" + }, + "dependencies": { + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + } + } + }, "istanbul": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", @@ -3323,16 +3386,6 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, - "JSONStream": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", - "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", - "dev": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "kew": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", @@ -3627,9 +3680,9 @@ } }, "merge2": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.1.tgz", - "integrity": "sha512-wUqcG5pxrAcaFI1lkqkMnk3Q7nUxV/NWfpAFSeWUwG9TRODnBDCUHa75mi3o3vLWQ5N4CQERWCauSlP0I3ZqUg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", + "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==", "dev": true }, "micromatch": { @@ -3729,24 +3782,30 @@ } }, "mocha": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.1.1.tgz", - "integrity": "sha512-kKKs/H1KrMMQIEsWNxGmb4/BGsmj0dkeyotEvbrAuQ01FcWRLssUNXCEUZk6SZtyJBi6EE7SL0zDDtItw1rGhw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { "browser-stdout": "1.3.1", - "commander": "2.11.0", + "commander": "2.15.1", "debug": "3.1.0", "diff": "3.5.0", "escape-string-regexp": "1.0.5", "glob": "7.1.2", - "growl": "1.10.3", + "growl": "1.10.5", "he": "1.1.1", "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "4.4.0" + "supports-color": "5.4.0" }, "dependencies": { + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -3756,20 +3815,11 @@ "ms": "2.0.0" } }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } } } }, @@ -3785,6 +3835,7 @@ "integrity": "sha512-KWBI3009iRnHjRlxRhe8nJ6kdeBTg4sMi5N6AZgg5f1/v5S7EBCRBOY854I4P5Anl4kx6AJH+4bBBC2Gi3nkvg==", "dev": true, "requires": { + "JSONStream": "1.3.2", "browser-resolve": "1.11.2", "cached-path-relative": "1.0.1", "concat-stream": "1.6.2", @@ -3792,7 +3843,6 @@ "detective": "5.1.0", "duplexer2": "0.1.4", "inherits": "2.0.3", - "JSONStream": "1.3.2", "parents": "1.0.1", "readable-stream": "2.3.6", "resolve": "1.7.1", @@ -3898,8 +3948,7 @@ "node-fetch": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", - "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=", - "dev": true + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" }, "nopt": { "version": "3.0.6", @@ -4624,6 +4673,11 @@ "ret": "0.1.15" } }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "sander": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", @@ -4636,6 +4690,12 @@ "rimraf": "2.6.2" }, "dependencies": { + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -4862,9 +4922,9 @@ } }, "source-map-support": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.5.tgz", - "integrity": "sha512-mR7/Nd5l1z6g99010shcXJiNEaf3fEtmLhRB/sBcQVJGodcHCULPp2y4Sfa43Kv2zq7T+Izmfp/WHCR6dYkQCA==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", + "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", "dev": true, "requires": { "buffer-from": "1.0.0", @@ -5224,9 +5284,9 @@ "dev": true }, "tslint": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", - "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz", + "integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=", "dev": true, "requires": { "babel-code-frame": "6.26.0", @@ -5303,9 +5363,9 @@ "dev": true }, "typescript": { - "version": "2.9.0-dev.20180430", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.0-dev.20180430.tgz", - "integrity": "sha512-KaYA4BpYsxHrnsZFs2DS/fhUkv9dTw72WMZBnCT2VFzn8R036PWjK8StbMaNHh4na9DEViGEEFg5bYBF3wDiIA==", + "version": "2.9.0-dev.20180519", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.0-dev.20180519.tgz", + "integrity": "sha512-WQKPDm2m+LlNYCpwsi0AClQW+S4JxcEzYIoBVw+259yL3JPDRok5a9J+3jKh+2u54KVdoKTSVNJdp2WxvMtZAA==", "dev": true }, "uglify-js": { @@ -5640,6 +5700,11 @@ "integrity": "sha512-EqzLchIMYLBjRPoqVsEkZOa/4Vr2RfOWbd58F+I/Gj79AYTrsseMunxbbSkbYfrqZaXSuPBBXNSOhtJgg0PpmA==", "dev": true }, + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + }, "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", diff --git a/package.json b/package.json index a7eb0dfd3894e..49893e2b9fd59 100644 --- a/package.json +++ b/package.json @@ -102,5 +102,11 @@ "fs": false, "os": false, "path": false + }, + "dependencies": { + "@types/node-fetch": "^2.1.1", + "es6-promise": "^4.2.4", + "isomorphic-fetch": "^2.2.1", + "node-fetch": "^2.1.2" } } diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 5b68f42c471f3..4170e96b64050 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -94,6 +94,7 @@ "../services/codefixes/convertFunctionToEs6Class.ts", "../services/codefixes/convertToEs6Module.ts", "../services/codefixes/convertPromisesToAwaitAndAsync.ts", + "../services/codefixes/convertPromisesToAwaitAndAsync.ts", "../services/codefixes/correctQualifiedNameToIndexedAccessType.ts", "../services/codefixes/fixClassIncorrectlyImplementsInterface.ts", "../services/codefixes/importFixes.ts", diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json index 8ae6974baf062..29725fe00ec6c 100644 --- a/src/server/tsconfig.json +++ b/src/server/tsconfig.json @@ -88,6 +88,7 @@ "../services/codefixes/addMissingInvocationForDecorator.ts", "../services/codefixes/annotateWithTypeFromJSDoc.ts", "../services/codefixes/convertFunctionToEs6Class.ts", + "../services/codefixes/convertPromisesToAwaitAndAsync.ts", "../services/codefixes/convertToEs6Module.ts", "../services/codefixes/correctQualifiedNameToIndexedAccessType.ts", "../services/codefixes/fixClassIncorrectlyImplementsInterface.ts", diff --git a/src/server/tsconfig.library.json b/src/server/tsconfig.library.json index 922af11e87963..e07aa3d80d1e9 100644 --- a/src/server/tsconfig.library.json +++ b/src/server/tsconfig.library.json @@ -93,6 +93,7 @@ "../services/refactorProvider.ts", "../services/codefixes/addMissingInvocationForDecorator.ts", "../services/codefixes/annotateWithTypeFromJSDoc.ts", + "../services/codefixes/convertPromisesToAwaitAndAsync.ts", "../services/codefixes/convertFunctionToEs6Class.ts", "../services/codefixes/convertToEs6Module.ts", "../services/codefixes/correctQualifiedNameToIndexedAccessType.ts", diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index cc078116949e2..33f45dc3103a2 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -87,7 +87,7 @@ namespace ts.codefix { if (!argName) { argName = "val"; // fix this to maybe not always create a variable declaration if not necessary } - return argUsed ? [createVariableStatement(/*modifiers*/ undefined, [createVariableDeclaration(createIdentifier(argName), /*type*/ undefined, createAwait(node))])] : + return argUsed ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([createVariableDeclaration(createIdentifier(argName), /*type*/ undefined, createAwait(node))], NodeFlags.Let)))] : [createStatement(createAwait(node))]; } @@ -131,6 +131,7 @@ namespace ts.codefix { } } + function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 7e1ccc9c3afc0..e7c811e461807 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -85,6 +85,7 @@ "codefixes/addMissingInvocationForDecorator.ts", "codefixes/annotateWithTypeFromJSDoc.ts", "codefixes/convertFunctionToEs6Class.ts", + "codefixes/convertPromisesToAwaitAndAsync.ts", "codefixes/convertToEs6Module.ts", "codefixes/correctQualifiedNameToIndexedAccessType.ts", "codefixes/fixClassIncorrectlyImplementsInterface.ts", diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncFunctionRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncFunctionRef.ts deleted file mode 100644 index 934b3db922564..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncFunctionRef.ts +++ /dev/null @@ -1,28 +0,0 @@ -/// - -////function f():Promise { -//// return fetch('http://yahoo.com').then(res); -////} -//// -////function res(result){ -//// console.log(result); -////} - -verify.getSuggestionDiagnostics([{ - message: "Convert to use async and await", - code: 95055, - reportsUnnecessary: true, -}]); - -verify.codeFix({ - description: "Remove unreachable code", - index: 0, - newFileContent: -`async function f() { - var result = await fetch('http://yahoo.com); - res(result); -} -function res(result){ - console.log(result); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts index fc654ff2b969d..18dce62d39007 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts @@ -10,9 +10,9 @@ //// return result.ok; ////} //// -///function res2(result2){ +////function res2(result2){ //// console.log(result2); -///} +////} verify.getSuggestionDiagnostics([{ message: "This may be converted to use async and await.", From bc9d55484cc37c7942d5e53cd382e0b6c0522676 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 18 Jun 2018 16:18:40 -0700 Subject: [PATCH 039/196] Refactored to use let declarations --- tests/cases/fourslash/codeFixPromiseToAsync.ts | 2 +- tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts | 2 +- .../cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts | 3 ++- .../fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts | 9 ++------- tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts | 7 +------ .../fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts | 2 +- .../fourslash/codeFixPromiseToAsyncMultipleThens.ts | 2 +- tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts | 2 +- tests/cases/fourslash/codeFixPromiseToAsyncRej.ts | 3 ++- tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts | 3 ++- .../fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts | 3 ++- tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts | 2 +- .../fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts | 2 +- 13 files changed, 18 insertions(+), 24 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts index 49c77c964a766..300d66a614683 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync.ts @@ -15,7 +15,7 @@ verify.codeFix({ index: 0, newFileContent: `async function f() { - var result = await fetch('http://yahoo.com); + let result = await fetch('http://yahoo.com); console.log(result); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts index f6232a881ada6..51532a46183ef 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts @@ -18,7 +18,7 @@ verify.codeFix({ newFileContent: `async function f():Promise { try { - var result = await fetch('http://yahoo.com'); + let result = await fetch('http://yahoo.com'); console.log(result); } catch (err) { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts index 6dcc1246cf1fa..37bb9b74607ca 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts @@ -18,8 +18,9 @@ verify.codeFix({ newFileContent: `async function f():Promise { try { + let result; try { - var result = await fetch('http://yahoo.com'); + result = await fetch('http://yahoo.com'); } catch (rejection) { console.log("rejected:", rejection); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index 41a0d7087a9b5..d29dc6ff489b2 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -29,20 +29,15 @@ verify.codeFix({ newFileContent: `async function f(): Promise { try { + let result; try { - var result = await fetch('http://yahoo.com'); + result = await fetch('http://yahoo.com'); } catch (rejection) { return await rej(rejection); } return await res(result); } - catch (err) { - return await catch_err(err); - return rej(rejection); - } - return res(result); - } catch (err) { return catch_err(err); } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts index 2e496037259a1..87d4bccbd95c1 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts @@ -23,17 +23,12 @@ verify.codeFix({ newFileContent: `async function f():Promise { try { - var result = await fetch('http://yahoo.com'); + let result = await fetch('http://yahoo.com'); return await res(result); } catch (err) { return await catch_err(err); } - return res(result); - } - catch(err) { - return catch_err(err); - } } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts index c36286e67ea17..be80b44c689ff 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts @@ -18,7 +18,7 @@ verify.codeFix({ newFileContent: `async function f():Promise { try { - var result = await fetch('http://yahoo.com'); + let result = await fetch('http://yahoo.com'); console.log(result); } catch (err) { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts index d315b1ce82fc2..18dce62d39007 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts @@ -12,7 +12,7 @@ //// ////function res2(result2){ //// console.log(result2); -///} +////} verify.getSuggestionDiagnostics([{ message: "This may be converted to use async and await.", diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts index 5e93f915628b4..33d8572bebb5f 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts @@ -17,7 +17,7 @@ verify.codeFix({ newFileContent: `async function f() { try{ - var result = await fetch('http://yahoo.com'); + await fetch('http://yahoo.com'); }catch(rejection){ console.log("rejected", rejection); } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts index 555a202d00a45..62ed47b1b78e9 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -16,8 +16,9 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { + let result; try { - var result = await fetch('http://yahoo.com'); + result = await fetch('http://yahoo.com'); } catch (rejection) { console.log("rejected:", rejection); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index 62750091f8f57..4c38688e2111d 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -24,8 +24,9 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { + let result; try { - var result = await fetch('http://yahoo.com'); + result = await fetch('http://yahoo.com'); } catch (err) { return await rej(err); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts index 04a07a91a2ee6..a703a25f61a73 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts @@ -16,8 +16,9 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { + let result; try { - var result = await fetch('http://yahoo.com'); + result = await fetch('http://yahoo.com'); } catch (rejection) { console.log("rejected:", rejection); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts index 6c589e62e2a41..f5fe4209ff1df 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts @@ -20,7 +20,7 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - var result = await fetch('http://yahoo.com); + let result = await fetch('http://yahoo.com); return res(result); } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts index d6f41cff8ff55..973280a43065c 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -20,7 +20,7 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - var result = await fetch('http://yahoo.com'); + await fetch('http://yahoo.com'); return await res(result); } From 515b5ffb12a9b3eabb4d9514b4f08656578071c9 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 18 Jun 2018 19:10:08 -0700 Subject: [PATCH 040/196] initial commit of var -> let refactor --- src/services/codefixes/convertPromisesToAwaitAndAsync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 33f45dc3103a2..ab5de7b9e2802 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -118,7 +118,7 @@ namespace ts.codefix { let lastDotThen = getLastDotThen(parent, checker); let tempArgName = lastDotThen ? getArgName(lastDotThen.arguments[0], "temp", checker) : argName; - return createNodeArray([createVariableStatement(/*modifiers*/ undefined, [createVariableDeclaration(tempArgName, undefined, (createAwait(synthCall)))])]); + return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.ArrowFunction: From 8104e3613a85b39533d10a9f2a4db900056225f3 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 18 Jun 2018 19:30:07 -0700 Subject: [PATCH 041/196] Only create a let declaration when there is a rej or catch --- src/services/codefixes/convertPromisesToAwaitAndAsync.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index ab5de7b9e2802..1ca12267d6553 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -73,10 +73,11 @@ namespace ts.codefix { if (rej) { const argNameRej = getArgName(rej, "e", checker); + const varDecl: Statement = createVariableStatement(/* modifiers */ undefined, createVariableDeclarationList([createVariableDeclaration(argNameRes)], NodeFlags.Let)); const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes, argNameRes !== "val")); const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, node, checker, /* isRej */ true))); - return [createTry(tryBlock, catchClause, /*finalllyBlock*/ undefined) as Statement].concat(getCallbackBody(res, argNameRes, node, checker)); + return [varDecl, createTry(tryBlock, catchClause, /*finalllyBlock*/ undefined) as Statement].concat(getCallbackBody(res, argNameRes, node, checker)); } else { return parseCallback(node.expression, checker, argNameRes, argNameRes !== "val").concat(getCallbackBody(res, argNameRes, node, checker)); From 4075943b43da1bb696260bda473ee7c07f4231bd Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 19 Jun 2018 13:44:31 -0700 Subject: [PATCH 042/196] Fixed rej after code to have labels to correct semantics. Also added tests for multile catches, and promise.all and .race() --- .../codeFixPromiseToAsyncCatchAndRej.ts | 15 +++++---- .../codeFixPromiseToAsyncCatchAndRejRef.ts | 13 +++++--- .../codeFixPromiseToAsyncMultipleCatchs.ts | 33 +++++++++++++++++++ .../codeFixPromiseToAsyncPromiseDotAll.ts | 24 ++++++++++++++ .../codeFixPromiseToAsyncPromiseDotRace.ts | 24 ++++++++++++++ .../fourslash/codeFixPromiseToAsyncRej.ts | 15 +++++---- .../fourslash/codeFixPromiseToAsyncRejRef.ts | 15 +++++---- .../codeFixPromiseToAsyncRej_NoBrackets.ts | 15 +++++---- 8 files changed, 125 insertions(+), 29 deletions(-) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts index 37bb9b74607ca..f0f1c816e631b 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts @@ -19,13 +19,16 @@ verify.codeFix({ `async function f():Promise { try { let result; - try { - result = await fetch('http://yahoo.com'); + label: { + try { + result = await fetch('http://yahoo.com'); + } + catch (rejection) { + console.log("rejected:", rejection); + break label; + } + console.log(result); } - catch (rejection) { - console.log("rejected:", rejection); - } - console.log(result); } catch (err) { console.log(err); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index d29dc6ff489b2..9a6692587911c 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -30,11 +30,14 @@ verify.codeFix({ `async function f(): Promise { try { let result; - try { - result = await fetch('http://yahoo.com'); - } - catch (rejection) { - return await rej(rejection); + label: { + try { + result = await fetch('http://yahoo.com'); + } + catch (rejection) { + return await rej(rejection); + break label; + } } return await res(result); } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts new file mode 100644 index 0000000000000..4cab73abbfebd --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts @@ -0,0 +1,33 @@ +/// + +// @target: es6 + +////function f(): Promise { +//// return fetch('http://yahoo.com').then(res => console.log(res)).catch(err => console.log("err")).catch(err2 => console.log("err2", err2)); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f(): Promise { + try { + let res; + try { + res = await fetch("http://yahoo.com"); + return await console.log(res); + } + catch (err) { + return await console.log("err"); + } + } + catch (err2) { + return await console.log("err2"); + } +}}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts b/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts new file mode 100644 index 0000000000000..605d0d93bc593 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts @@ -0,0 +1,24 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return Promise.all([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(function(vals){ +//// vals.forEach(console.log); +//// }) +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`async function f():Promise { + let vals = await Promise.all([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); + return vals.forEach(console.log); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts b/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts new file mode 100644 index 0000000000000..a702eebe0ae18 --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts @@ -0,0 +1,24 @@ +/// + +// @target: es6 + +////function [|f|]():Promise { +//// return Promise.race([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(val => console.log(val)); +////} + + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: + +`async function f():Promise { + let val = await Promise.race([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); + return await console.log(val); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts index 62ed47b1b78e9..dd134512cde63 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -17,12 +17,15 @@ verify.codeFix({ newFileContent: `async function f():Promise { let result; - try { - result = await fetch('http://yahoo.com'); + label:{ + try { + result = await fetch('http://yahoo.com'); + } + catch (rejection) { + console.log("rejected:", rejection); + break label; + } + console.log(result); } - catch (rejection) { - console.log("rejected:", rejection); - } - console.log(result); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index 4c38688e2111d..87693594b6772 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -25,13 +25,16 @@ verify.codeFix({ newFileContent: `async function f():Promise { let result; - try { - result = await fetch('http://yahoo.com'); + label: { + try { + result = await fetch('http://yahoo.com'); + } + catch (err) { + return await rej(err); + break label; + } + return await res(result); } - catch (err) { - return await rej(err); - } - return await res(result); } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts index a703a25f61a73..6161e0b9549d1 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts @@ -17,12 +17,15 @@ verify.codeFix({ newFileContent: `async function f():Promise { let result; - try { - result = await fetch('http://yahoo.com'); + label: { + try { + result = await fetch('http://yahoo.com'); + } + catch (rejection) { + console.log("rejected:", rejection); + break label; + } + console.log(result); } - catch (rejection) { - console.log("rejected:", rejection); - } - console.log(result); }`, }); From 92ecaf1167d5f24c1f3b8db3bf086ec9071da631 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 19 Jun 2018 15:58:28 -0700 Subject: [PATCH 043/196] Fixed tests to not return awaits (redundant as async functions do this implicitly) --- tests/cases/fourslash/codeFixPromiseToAsync.ts | 6 +++--- .../fourslash/codeFixPromiseToAsyncCatch.ts | 4 ++-- .../codeFixPromiseToAsyncCatchAndRej.ts | 17 +++++++---------- .../codeFixPromiseToAsyncCatchAndRejRef.ts | 16 ++++++---------- .../fourslash/codeFixPromiseToAsyncCatchRef.ts | 4 ++-- .../codeFixPromiseToAsyncCatch_noBrackets.ts | 4 ++-- .../codeFixPromiseToAsyncIgnoreArgs1.ts | 2 +- .../codeFixPromiseToAsyncIgnoreArgs2.ts | 2 +- .../codeFixPromiseToAsyncMultipleCatchs.ts | 6 +++--- .../codeFixPromiseToAsyncMultipleThens.ts | 2 +- ...FixPromiseToAsyncMultipleThensSameVarName.ts | 2 +- .../fourslash/codeFixPromiseToAsyncNoRes.ts | 6 +++--- .../codeFixPromiseToAsyncPromiseDotAll.ts | 2 +- .../codeFixPromiseToAsyncPromiseDotRace.ts | 2 +- .../cases/fourslash/codeFixPromiseToAsyncRej.ts | 15 ++++++--------- .../fourslash/codeFixPromiseToAsyncRejRef.ts | 15 ++++++--------- .../codeFixPromiseToAsyncRej_NoBrackets.ts | 16 ++++++---------- .../codeFixPromiseToAsyncResRef_NoReturnVal.ts | 2 +- 18 files changed, 53 insertions(+), 70 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts index 300d66a614683..c78d7be36e69c 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync.ts @@ -1,6 +1,6 @@ /// -////function [|f|]():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => { console.log(result); }); ////} @@ -14,8 +14,8 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { +`async function f():Promise { let result = await fetch('http://yahoo.com); - console.log(result); + return console.log(result); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts index 51532a46183ef..72be49496d207 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts @@ -19,10 +19,10 @@ verify.codeFix({ `async function f():Promise { try { let result = await fetch('http://yahoo.com'); - console.log(result); + return console.log(result); } catch (err) { - console.log(err); + return console.log(err); } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts index f0f1c816e631b..73451865a19dd 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts @@ -19,19 +19,16 @@ verify.codeFix({ `async function f():Promise { try { let result; - label: { - try { - result = await fetch('http://yahoo.com'); - } - catch (rejection) { - console.log("rejected:", rejection); - break label; - } - console.log(result); + try { + result = await fetch('http://yahoo.com'); + return console.log(result); + } + catch (rejection) { + return console.log("rejected:", rejection); } } catch (err) { - console.log(err); + return console.log(err); } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index 9a6692587911c..993bb08c4790a 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -29,17 +29,13 @@ verify.codeFix({ newFileContent: `async function f(): Promise { try { - let result; - label: { - try { - result = await fetch('http://yahoo.com'); - } - catch (rejection) { - return await rej(rejection); - break label; - } + try { + result = await fetch('http://yahoo.com'); + return res(result); + } + catch (rejection) { + return rej(rejection); } - return await res(result); } catch (err) { return catch_err(err); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts index 87d4bccbd95c1..4bf2dc495158c 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts @@ -24,10 +24,10 @@ verify.codeFix({ `async function f():Promise { try { let result = await fetch('http://yahoo.com'); - return await res(result); + return res(result); } catch (err) { - return await catch_err(err); + return catch_err(err); } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts index be80b44c689ff..d3c943fea2863 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts @@ -19,10 +19,10 @@ verify.codeFix({ `async function f():Promise { try { let result = await fetch('http://yahoo.com'); - console.log(result); + return console.log(result); } catch (err) { - console.log(err); + return console.log(err); } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts index e26fca8145988..2577626152f51 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts @@ -17,6 +17,6 @@ verify.codeFix({ newFileContent: `async function f(): Promise { await fetch('http://yahoo.com'); - console.log(done); + return console.log(done); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts index 8bbc1cbeaa1ab..4b69fd6698ed6 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts @@ -17,6 +17,6 @@ verify.codeFix({ newFileContent: `async function f(): Promise { await fetch('http://yahoo.com'); - console.log(done); + return console.log(done); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts index 4cab73abbfebd..16c0bbff6be41 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts @@ -20,14 +20,14 @@ verify.codeFix({ let res; try { res = await fetch("http://yahoo.com"); - return await console.log(res); + return console.log(res); } catch (err) { - return await console.log("err"); + return console.log("err"); } } catch (err2) { - return await console.log("err2"); + return console.log("err2"); } }}`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts index 18dce62d39007..a1250dada0f64 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts @@ -26,7 +26,7 @@ verify.codeFix({ `async function f():Promise { let result = await fetch('http://yahoo.com'); let result2 = await res(result); - return await res2(result2); + return res2(result2); } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts index 6fb096fefbcb2..aa28729845d56 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts @@ -26,7 +26,7 @@ verify.codeFix({ `async function f():Promise { let result = await fetch('http://yahoo.com'); let temp = await res(result); - return await res2(temp); + return res2(temp); } function res(result){ diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts index 33d8572bebb5f..9a8c152009a3a 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts @@ -2,7 +2,7 @@ // @target: es6 -////function [|f|]():Promise { +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(null, rejection => console.log("rejected:", rejection); ); ////} @@ -15,11 +15,11 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { +`async function f():Promise { try{ await fetch('http://yahoo.com'); }catch(rejection){ - console.log("rejected", rejection); + retrun console.log("rejected", rejection); } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts b/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts index 605d0d93bc593..52b315f86fe88 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts @@ -17,7 +17,7 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f():Promise { +`async function f():Promise { let vals = await Promise.all([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); return vals.forEach(console.log); }`, diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts b/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts index a702eebe0ae18..05fd06c6b3420 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts @@ -19,6 +19,6 @@ verify.codeFix({ `async function f():Promise { let val = await Promise.race([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); - return await console.log(val); + return console.log(val); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts index dd134512cde63..d016a4f38b535 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -17,15 +17,12 @@ verify.codeFix({ newFileContent: `async function f():Promise { let result; - label:{ - try { - result = await fetch('http://yahoo.com'); - } - catch (rejection) { - console.log("rejected:", rejection); - break label; - } - console.log(result); + try { + result = await fetch('http://yahoo.com'); + return console.log(result); + } + catch (rejection) { + return console.log("rejected:", rejection); } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index 87693594b6772..68737260fd18e 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -25,15 +25,12 @@ verify.codeFix({ newFileContent: `async function f():Promise { let result; - label: { - try { - result = await fetch('http://yahoo.com'); - } - catch (err) { - return await rej(err); - break label; - } - return await res(result); + try { + result = await fetch('http://yahoo.com'); + return res(result); + } + catch (err) { + return rej(err); } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts index 6161e0b9549d1..a0a89a6a091f5 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts @@ -16,16 +16,12 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - let result; - label: { - try { - result = await fetch('http://yahoo.com'); - } - catch (rejection) { - console.log("rejected:", rejection); - break label; - } - console.log(result); + try { + let result = await fetch('http://yahoo.com'); + return console.log(result); + } + catch (rejection) { + return console.log("rejected:", rejection); } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts index 973280a43065c..e9b35a0e5e019 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -21,7 +21,7 @@ verify.codeFix({ newFileContent: `async function f():Promise { await fetch('http://yahoo.com'); - return await res(result); + return res(result); } function res(result){ From 28dca7932edab622213ceb244e7cb609b0673830 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 19 Jun 2018 16:16:44 -0700 Subject: [PATCH 044/196] Removed await keyword from returns --- src/services/codefixes/convertPromisesToAwaitAndAsync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 1ca12267d6553..93ffdddf9fafa 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -114,7 +114,7 @@ namespace ts.codefix { let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]) if (!getLastDotThen(parent, checker) || (parent.expression).name.text === "catch" || isRej) { - return createNodeArray([(createReturn(createAwait(synthCall)))]); + return createNodeArray([createReturn(synthCall)]); } let lastDotThen = getLastDotThen(parent, checker); From 4ac17427b892a2609096c7f1bd9a290f8c024043 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 19 Jun 2018 16:20:09 -0700 Subject: [PATCH 045/196] fixed minor typos --- .../fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts | 2 +- tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts index e9b35a0e5e019..3109e975010cd 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts @@ -20,7 +20,7 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - await fetch('http://yahoo.com'); + let result = await fetch('http://yahoo.com'); return res(result); } diff --git a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts index e7b5c2d4620a1..7507f5137c23e 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts @@ -1,6 +1,8 @@ /// -////function [|f|]():Promise { +// @target: es6 + +////function [|f|]():Promise { //// return fetch('http://yahoo.com').then(result => console.log(result)); ////} @@ -14,8 +16,8 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`async function f() { - var result = await fetch('http://yahoo.com); +`async function f():Promise { + let result = await fetch('http://yahoo.com'); console.log(result); }`, }); From 0b75df3e2f8086a91663249a8a9431ac9459a1c0 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 19 Jun 2018 16:34:13 -0700 Subject: [PATCH 046/196] fixed more minors issues --- tests/cases/fourslash/codeFixPromiseToAsync.ts | 2 +- tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts index c78d7be36e69c..ea5b9443456c4 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync.ts @@ -15,7 +15,7 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - let result = await fetch('http://yahoo.com); + let result = await fetch('http://yahoo.com'); return console.log(result); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts index 7507f5137c23e..4fc0572b97bd4 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts @@ -17,7 +17,7 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - let result = await fetch('http://yahoo.com'); - console.log(result); + let result = await fetch('http://yahoo.com'); + console.log(result); }`, }); From f99201075fe656e32a7abd7894725ee6ed7a4898 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 19 Jun 2018 17:18:57 -0700 Subject: [PATCH 047/196] Can't return statements in a block --- tests/cases/fourslash/codeFixPromiseToAsync.ts | 2 +- tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts | 4 ++-- tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts | 6 +++--- tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts | 2 +- tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts | 2 +- tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts index ea5b9443456c4..cda3f9813cbea 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync.ts @@ -16,6 +16,6 @@ verify.codeFix({ newFileContent: `async function f():Promise { let result = await fetch('http://yahoo.com'); - return console.log(result); + console.log(result); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts index 72be49496d207..51532a46183ef 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts @@ -19,10 +19,10 @@ verify.codeFix({ `async function f():Promise { try { let result = await fetch('http://yahoo.com'); - return console.log(result); + console.log(result); } catch (err) { - return console.log(err); + console.log(err); } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts index 73451865a19dd..2ec7897524712 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts @@ -21,14 +21,14 @@ verify.codeFix({ let result; try { result = await fetch('http://yahoo.com'); - return console.log(result); + console.log(result); } catch (rejection) { - return console.log("rejected:", rejection); + console.log("rejected:", rejection); } } catch (err) { - return console.log(err); + console.log(err); } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts index 2577626152f51..e26fca8145988 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts @@ -17,6 +17,6 @@ verify.codeFix({ newFileContent: `async function f(): Promise { await fetch('http://yahoo.com'); - return console.log(done); + console.log(done); }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts index 4b69fd6698ed6..9b6efec0da042 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts @@ -3,7 +3,7 @@ // @target: es6 ////function [|f|](): Promise { -//// return fetch('http://yahoo.com').then( () => { console.log(done); }); +//// return fetch('http://yahoo.com').then( () => console.log(done) ); ////} verify.getSuggestionDiagnostics([{ diff --git a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts index 4fc0572b97bd4..f2b80df0cc0d0 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts @@ -18,6 +18,6 @@ verify.codeFix({ newFileContent: `async function f():Promise { let result = await fetch('http://yahoo.com'); - console.log(result); + return console.log(result); }`, }); From f113a41a0deb58f874be70c02fe76ada339b7496 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 19 Jun 2018 17:23:47 -0700 Subject: [PATCH 048/196] fixed let declarations to be inside try blocks --- tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts | 3 +-- .../cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts | 2 +- .../cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts | 3 +-- tests/cases/fourslash/codeFixPromiseToAsyncRej.ts | 7 +++---- tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts | 3 +-- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts index 2ec7897524712..8b837bf557d4c 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts @@ -18,9 +18,8 @@ verify.codeFix({ newFileContent: `async function f():Promise { try { - let result; try { - result = await fetch('http://yahoo.com'); + let result = await fetch('http://yahoo.com'); console.log(result); } catch (rejection) { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index 993bb08c4790a..e13de3d18bb4b 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -30,7 +30,7 @@ verify.codeFix({ `async function f(): Promise { try { try { - result = await fetch('http://yahoo.com'); + let result = await fetch('http://yahoo.com'); return res(result); } catch (rejection) { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts index 16c0bbff6be41..1205211f501b1 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts @@ -17,9 +17,8 @@ verify.codeFix({ newFileContent: `async function f(): Promise { try { - let res; try { - res = await fetch("http://yahoo.com"); + let res = await fetch("http://yahoo.com"); return console.log(res); } catch (err) { diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts index d016a4f38b535..d3b705874f7bb 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts @@ -16,13 +16,12 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - let result; try { - result = await fetch('http://yahoo.com'); - return console.log(result); + let result = await fetch('http://yahoo.com'); + console.log(result); } catch (rejection) { - return console.log("rejected:", rejection); + console.log("rejected:", rejection); } }`, }); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts index 68737260fd18e..631bae21af20c 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts @@ -24,9 +24,8 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - let result; try { - result = await fetch('http://yahoo.com'); + let result = await fetch('http://yahoo.com'); return res(result); } catch (err) { From abeace986c604b682d1a3ae4c4b97d294f6d1bca Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 19 Jun 2018 17:33:41 -0700 Subject: [PATCH 049/196] fixed spacing --- tests/cases/fourslash/codeFixPromiseToAsync.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts index cda3f9813cbea..2759120593c99 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsync.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsync.ts @@ -15,7 +15,7 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - let result = await fetch('http://yahoo.com'); - console.log(result); + let result = await fetch('http://yahoo.com'); + console.log(result); }`, }); From 806d14ac01af3e78857636ebf6466d43e37a2233 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 19 Jun 2018 17:38:58 -0700 Subject: [PATCH 050/196] removed unnessecary variable declarations --- .../codefixes/convertPromisesToAwaitAndAsync.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 93ffdddf9fafa..be81b5831f099 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -59,8 +59,8 @@ namespace ts.codefix { const func = node.arguments[0]; const argName = getArgName(func, "arg", checker); const tryBlock = createBlock(parseCallback(node.expression, checker, argName, argName !== "arg")); - // instead of using e -> get the paramater of the catch function and use that const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName, node, checker))); + return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } @@ -73,11 +73,10 @@ namespace ts.codefix { if (rej) { const argNameRej = getArgName(rej, "e", checker); - const varDecl: Statement = createVariableStatement(/* modifiers */ undefined, createVariableDeclarationList([createVariableDeclaration(argNameRes)], NodeFlags.Let)); - const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes, argNameRes !== "val")); + const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes, argNameRes !== "val").concat(getCallbackBody(res, argNameRes, node, checker))); const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, node, checker, /* isRej */ true))); - return [varDecl, createTry(tryBlock, catchClause, /*finalllyBlock*/ undefined) as Statement].concat(getCallbackBody(res, argNameRes, node, checker)); + return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else { return parseCallback(node.expression, checker, argNameRes, argNameRes !== "val").concat(getCallbackBody(res, argNameRes, node, checker)); @@ -88,8 +87,9 @@ namespace ts.codefix { if (!argName) { argName = "val"; // fix this to maybe not always create a variable declaration if not necessary } - return argUsed ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([createVariableDeclaration(createIdentifier(argName), /*type*/ undefined, createAwait(node))], NodeFlags.Let)))] : - [createStatement(createAwait(node))]; + + let varDecl = createVariableDeclaration(createIdentifier(argName), /*type*/ undefined, createAwait(node)); + return argUsed ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; } @@ -127,7 +127,7 @@ namespace ts.codefix { return (func).body.statements; } else { - return createNodeArray([createStatement((func).body as Expression)]); + return createNodeArray([createReturn((func).body as Expression)]); } } } From 4ed331e6353af82d5f7128bb283ecb0712496e6f Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 22 Jun 2018 08:44:13 -0700 Subject: [PATCH 051/196] switched to the checker function: isPromiseLike --- src/services/suggestionDiagnostics.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index cf5ff1ea2f7e1..44881459bbeb4 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -24,7 +24,7 @@ namespace ts { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration)); } } - if (node.modifiers && isAsyncFunction(node)) { + if (isAsyncFunction(node)) { break; } @@ -33,13 +33,14 @@ namespace ts { break; } - // collect all the return statements - // check that a property access expression exists in there and that it is a handler - const retStmts: ReturnStatement[] = []; - getReturnStmts(node, retStmts); - - if (isPromiseType(returnType) && retStmts.length > 0) { - diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_use_async_and_await)); + if (checker.isPromiseLikeType(returnType)) { + // collect all the return statements + // check that a property access expression exists in there and that it is a handler + const retStmts: ReturnStatement[] = []; + getReturnStmts(node, retStmts); + if(retStmts.length > 0){ + diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_use_async_and_await)); + } } break; } @@ -123,10 +124,6 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - function isPromiseType(T: Type): boolean { - return T.flags === TypeFlags.Object && T.symbol.name === "Promise"; - } - function getReturnStmts(node: Node, retStmts: ReturnStatement[]) { forEachChild(node, visit); From 32d07ba847785b35d57207bb228ba180f3826e22 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 22 Jun 2018 09:04:40 -0700 Subject: [PATCH 052/196] Refactored the getReturnStmts function --- src/services/suggestionDiagnostics.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 44881459bbeb4..ba1c388b703fa 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -36,9 +36,8 @@ namespace ts { if (checker.isPromiseLikeType(returnType)) { // collect all the return statements // check that a property access expression exists in there and that it is a handler - const retStmts: ReturnStatement[] = []; - getReturnStmts(node, retStmts); - if(retStmts.length > 0){ + const retStmts = getReturnStatements(node); + if (retStmts.length > 0) { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_use_async_and_await)); } } @@ -124,7 +123,9 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - function getReturnStmts(node: Node, retStmts: ReturnStatement[]) { + function getReturnStatements(node: Node): ReturnStatement[] { + + const retStmts: ReturnStatement[] = []; forEachChild(node, visit); function visit(node: Node) { @@ -148,5 +149,6 @@ namespace ts { forEachChild(node, visit); } + return retStmts; } } From bd54e37146825cc8012ea1212022974b262a6775 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 09:11:44 -0700 Subject: [PATCH 053/196] Added tests for arrow functions and methods --- .../codeFixPromiseToAsyncArrowFunction.ts | 23 ++++++++++++++++ .../fourslash/codeFixPromiseToAsyncMethod.ts | 27 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts create mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts b/tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts new file mode 100644 index 0000000000000..774c5f00ef5ef --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts @@ -0,0 +1,23 @@ +/// + +// @target: es6 + +////() => { +//// return fetch('http://yahoo.com').then(result => console.log(result)); +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`() => { + let result = await fetch('http://yahoo.com'); + return console.log(result); +}`, +}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts new file mode 100644 index 0000000000000..19ec4c6cbe06a --- /dev/null +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts @@ -0,0 +1,27 @@ +/// + +// @target: es6 + +////class Parser { +//// function [|f|]():Promise { +//// return fetch('http://yahoo.com').then(result => console.log(result)); +//// } +////} + +verify.getSuggestionDiagnostics([{ + message: "This may be converted to use async and await.", + code: 80006, +}]); + + +verify.codeFix({ + description: "Convert to use async and await", + index: 0, + newFileContent: +`class Parser { + function f():Promise { + let result = fetch('http://yahoo.com'); + return console.log(result); + } +}`, +}); From 3b690596e001b1c12c79d839b88484b31c54a9b5 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 09:28:34 -0700 Subject: [PATCH 054/196] Fixed minor issues on tests - added async keyword to result and use let instead of var --- tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts | 2 +- tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts index 19ec4c6cbe06a..9963052e963b3 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts @@ -19,7 +19,7 @@ verify.codeFix({ index: 0, newFileContent: `class Parser { - function f():Promise { + async function f():Promise { let result = fetch('http://yahoo.com'); return console.log(result); } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts index e48c35b79b9e7..9d3e1ddfa92d7 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts @@ -20,7 +20,7 @@ verify.codeFix({ index: 0, newFileContent: `async function f():Promise { - var result = await fetch('http://yahoo.com'); + let result = await fetch('http://yahoo.com'); return await res(result); } From b1f58379b355f32f3d5ab7a092ba7e0183492ef1 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 09:32:28 -0700 Subject: [PATCH 055/196] Forget await keyword --- tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts index 9963052e963b3..9eb576343b611 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts @@ -20,7 +20,7 @@ verify.codeFix({ newFileContent: `class Parser { async function f():Promise { - let result = fetch('http://yahoo.com'); + let result = await fetch('http://yahoo.com'); return console.log(result); } }`, From f32493695e5cd5da6b4fc513572bf4b4c9cdef87 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 10:22:09 -0700 Subject: [PATCH 056/196] Refactored functionality to get variable names --- .../convertPromisesToAwaitAndAsync.ts | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index be81b5831f099..ec43a9f79150c 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -33,16 +33,17 @@ namespace ts.codefix { } function returnsAPromise(node: CallExpression, checker: TypeChecker): boolean { - return checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && (node).expression.kind !== SyntaxKind.PropertyAccessExpression; + return checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && + !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, argName?: string, argUsed?: boolean): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, argName?: string): Statement[] { if (!node) { return; } if (node.kind === SyntaxKind.CallExpression && returnsAPromise(node as CallExpression, checker)) { - return parsePromiseCall(node as CallExpression, argName, argUsed); + return parsePromiseCall(node as CallExpression, argName); } else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { return parseThen(node as CallExpression, checker); @@ -51,14 +52,16 @@ namespace ts.codefix { return parseCatch(node as CallExpression, checker); } else if (node.kind === SyntaxKind.PropertyAccessExpression) { - return parseCallback((node).expression, checker, argName, argUsed); + return parseCallback((node).expression, checker, argName); } } function parseCatch(node: CallExpression, checker: TypeChecker): Statement[] { - const func = node.arguments[0]; - const argName = getArgName(func, "arg", checker); - const tryBlock = createBlock(parseCallback(node.expression, checker, argName, argName !== "arg")); + const func = getSynthesizedDeepClone(node.arguments[0]); + + let argName = getArgName(func, checker); + + const tryBlock = createBlock(parseCallback(node.expression, checker, argName)); const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName, node, checker))); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; @@ -68,31 +71,31 @@ namespace ts.codefix { const res = node.arguments[0]; const rej = node.arguments[1]; // TODO - what if this is a binding pattern and not an Identifier - const argNameRes = getArgName(res, "val", checker); + let argNameRes = getArgName(res, checker); if (rej) { - const argNameRej = getArgName(rej, "e", checker); + const argNameRej = getArgName(rej, checker); - const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes, argNameRes !== "val").concat(getCallbackBody(res, argNameRes, node, checker))); + const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker))); const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, node, checker, /* isRej */ true))); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else { - return parseCallback(node.expression, checker, argNameRes, argNameRes !== "val").concat(getCallbackBody(res, argNameRes, node, checker)); + return parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker)); } } - function parsePromiseCall(node: CallExpression, argName?: string, argUsed?: boolean): Statement[] { - if (!argName) { - argName = "val"; // fix this to maybe not always create a variable declaration if not necessary + function parsePromiseCall(node: CallExpression, argName?: string): Statement[] { + let localArgName = argName; + if (!localArgName) { + localArgName = "val"; // fix this to maybe not always create a variable declaration if not necessary } - let varDecl = createVariableDeclaration(createIdentifier(argName), /*type*/ undefined, createAwait(node)); - return argUsed ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; + let varDecl = createVariableDeclaration(createIdentifier(localArgName), /*type*/ undefined, createAwait(node)); + return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; } - function getLastDotThen(node: Expression, checker: TypeChecker): CallExpression { if (!node || !node.parent) { return undefined; @@ -118,7 +121,7 @@ namespace ts.codefix { } let lastDotThen = getLastDotThen(parent, checker); - let tempArgName = lastDotThen ? getArgName(lastDotThen.arguments[0], "temp", checker) : argName; + let tempArgName = lastDotThen ? getArgName(lastDotThen.arguments[0], checker, "temp") : argName; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); case SyntaxKind.FunctionDeclaration: @@ -140,7 +143,7 @@ namespace ts.codefix { return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); } - function getArgName(funcNode: Node, defaultVal: string, checker: TypeChecker): string { + function getArgName(funcNode: Node, checker: TypeChecker, defaultVal?: string): string { if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { const name = (funcNode.parameters[0].name).text; if (name !== "_") { From 2d3e5a9374a8a012d1c76f6bbda467114090596e Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 10:22:45 -0700 Subject: [PATCH 057/196] Fixed small issues in tests - don't return await --- tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts | 2 +- tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts index 67216529b4987..e13de3d18bb4b 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts @@ -38,7 +38,7 @@ verify.codeFix({ } } catch (err) { - return await catch_err(err); + return catch_err(err); } } diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts index 9d3e1ddfa92d7..67fc6a79c802d 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts @@ -21,7 +21,7 @@ verify.codeFix({ newFileContent: `async function f():Promise { let result = await fetch('http://yahoo.com'); - return await res(result); + return res(result); } function res(result){ From 3a23df81334ade269a7a218dfcc58a32018bbaa5 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 11:22:37 -0700 Subject: [PATCH 058/196] Created clones where necessary and added support for function expressions in callbacks --- .../convertPromisesToAwaitAndAsync.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index ec43a9f79150c..cd3181a5011dd 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -12,7 +12,8 @@ namespace ts.codefix { }); function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { // get the function declaration - returns a promise - const funcToConvert: FunctionLikeDeclaration = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)).valueDeclaration as FunctionLikeDeclaration; + const funcToConvert: FunctionLikeDeclaration = getSynthesizedDeepClone(getContainingFunction(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)) as FunctionLikeDeclaration); + // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); @@ -57,43 +58,47 @@ namespace ts.codefix { } function parseCatch(node: CallExpression, checker: TypeChecker): Statement[] { - const func = getSynthesizedDeepClone(node.arguments[0]); + const nodeClone = getSynthesizedDeepClone(node) + const func = getSynthesizedDeepClone(nodeClone.arguments[0]); let argName = getArgName(func, checker); - const tryBlock = createBlock(parseCallback(node.expression, checker, argName)); - const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName, node, checker))); + const tryBlock = createBlock(parseCallback(nodeClone.expression, checker, argName)); + const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName, nodeClone, checker))); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } function parseThen(node: CallExpression, checker: TypeChecker): Statement[] { const res = node.arguments[0]; - const rej = node.arguments[1]; + const rej = getSynthesizedDeepClone(node.arguments[1]); // TODO - what if this is a binding pattern and not an Identifier let argNameRes = getArgName(res, checker); + let nodeClone = getSynthesizedClone(node); if (rej) { const argNameRej = getArgName(rej, checker); - const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker))); - const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, node, checker, /* isRej */ true))); + const tryBlock = createBlock(parseCallback(nodeClone.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, nodeClone, checker))); + const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, nodeClone, checker, /* isRej */ true))); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else { - return parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker)); + return parseCallback(nodeClone.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, nodeClone, checker)); } } function parsePromiseCall(node: CallExpression, argName?: string): Statement[] { + let nodeClone = getSynthesizedClone(node); + let localArgName = argName; if (!localArgName) { localArgName = "val"; // fix this to maybe not always create a variable declaration if not necessary } - let varDecl = createVariableDeclaration(createIdentifier(localArgName), /*type*/ undefined, createAwait(node)); - return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; + let varDecl = createVariableDeclaration(createIdentifier(localArgName), /*type*/ undefined, createAwait(nodeClone)); + return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(nodeClone))]; } function getLastDotThen(node: Expression, checker: TypeChecker): CallExpression { @@ -108,7 +113,6 @@ namespace ts.codefix { else { return getLastDotThen(node.parent as Expression, checker); } - } function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, isRej: boolean = false): NodeArray { @@ -125,6 +129,7 @@ namespace ts.codefix { return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: if ((func).body.kind === SyntaxKind.Block) { return (func).body.statements; From 1b7249348c29ef947c4815e69a9154eee3aaeb64 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 11:23:24 -0700 Subject: [PATCH 059/196] Added curser markers and fixed typos --- .../fourslash/codeFixPromiseToAsyncMultipleCatchs.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts index 1205211f501b1..29e1b95e458f6 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts @@ -2,8 +2,8 @@ // @target: es6 -////function f(): Promise { -//// return fetch('http://yahoo.com').then(res => console.log(res)).catch(err => console.log("err")).catch(err2 => console.log("err2", err2)); +////function [|f|](): Promise { +//// return fetch('http://yahoo.com').then(res => console.log(res)).catch(err => console.log("err", err)).catch(err2 => console.log("err2", err2)); ////} verify.getSuggestionDiagnostics([{ @@ -18,15 +18,15 @@ verify.codeFix({ `async function f(): Promise { try { try { - let res = await fetch("http://yahoo.com"); + let res = await fetch('http://yahoo.com'); return console.log(res); } catch (err) { - return console.log("err"); + return console.log("err", err); } } catch (err2) { - return console.log("err2"); + return console.log("err2", err2); } -}}`, +}`, }); From a4fcd0302c266128419202130ca387ec8159b4d3 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 12:47:59 -0700 Subject: [PATCH 060/196] Fixed curser marker issue --- tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts b/tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts index 774c5f00ef5ef..d1f57f2024427 100644 --- a/tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts +++ b/tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts @@ -2,7 +2,7 @@ // @target: es6 -////() => { +////[|():Promise => {/**/|] //// return fetch('http://yahoo.com').then(result => console.log(result)); ////} @@ -16,7 +16,7 @@ verify.codeFix({ description: "Convert to use async and await", index: 0, newFileContent: -`() => { +`async ():Promise => { let result = await fetch('http://yahoo.com'); return console.log(result); }`, From cb1cfab7d11b5c8cc5c80a24354815ae786cf1b7 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 13:21:22 -0700 Subject: [PATCH 061/196] Removed unnessecary clones --- .../convertPromisesToAwaitAndAsync.ts | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index cd3181a5011dd..da929f3f8cea1 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -12,7 +12,7 @@ namespace ts.codefix { }); function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { // get the function declaration - returns a promise - const funcToConvert: FunctionLikeDeclaration = getSynthesizedDeepClone(getContainingFunction(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)) as FunctionLikeDeclaration); + const funcToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)) as FunctionLikeDeclaration; // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); @@ -34,8 +34,7 @@ namespace ts.codefix { } function returnsAPromise(node: CallExpression, checker: TypeChecker): boolean { - return checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && - !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); + return checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } function parseCallback(node: Expression, checker: TypeChecker, argName?: string): Statement[] { @@ -58,75 +57,69 @@ namespace ts.codefix { } function parseCatch(node: CallExpression, checker: TypeChecker): Statement[] { - const nodeClone = getSynthesizedDeepClone(node) - const func = getSynthesizedDeepClone(nodeClone.arguments[0]); + const func = getSynthesizedDeepClone(node.arguments[0]); + const argName = getArgName(func, checker); - let argName = getArgName(func, checker); - - const tryBlock = createBlock(parseCallback(nodeClone.expression, checker, argName)); - const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName, nodeClone, checker))); + const tryBlock = createBlock(parseCallback(node.expression, checker, argName)); + const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName, node, checker))); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } function parseThen(node: CallExpression, checker: TypeChecker): Statement[] { const res = node.arguments[0]; - const rej = getSynthesizedDeepClone(node.arguments[1]); + const rej = node.arguments[1]; // TODO - what if this is a binding pattern and not an Identifier - let argNameRes = getArgName(res, checker); - let nodeClone = getSynthesizedClone(node); + const argNameRes = getArgName(res, checker); if (rej) { const argNameRej = getArgName(rej, checker); - const tryBlock = createBlock(parseCallback(nodeClone.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, nodeClone, checker))); - const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, nodeClone, checker, /* isRej */ true))); + const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker))); + const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, node, checker, /* isRej */ true))); - return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; + return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else { - return parseCallback(nodeClone.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, nodeClone, checker)); + return parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker)); } } function parsePromiseCall(node: CallExpression, argName?: string): Statement[] { - let nodeClone = getSynthesizedClone(node); - let localArgName = argName; if (!localArgName) { localArgName = "val"; // fix this to maybe not always create a variable declaration if not necessary } - let varDecl = createVariableDeclaration(createIdentifier(localArgName), /*type*/ undefined, createAwait(nodeClone)); - return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(nodeClone))]; + const varDecl = createVariableDeclaration(createIdentifier(localArgName), /*type*/ undefined, createAwait(node)); + return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; } - function getLastDotThen(node: Expression, checker: TypeChecker): CallExpression { + function getNextDotThen(node: Expression, checker: TypeChecker): CallExpression { if (!node || !node.parent) { return undefined; } - let parent = node.parent; + const parent = node.parent; if (parent.kind === SyntaxKind.CallExpression && isCallback(parent as CallExpression, "then", checker)) { return parent as CallExpression; } else { - return getLastDotThen(node.parent as Expression, checker); + return getNextDotThen(node.parent as Expression, checker); } } - function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, isRej: boolean = false): NodeArray { + function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, isRej = false): NodeArray { switch (func.kind) { case SyntaxKind.Identifier: - - let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]) - if (!getLastDotThen(parent, checker) || (parent.expression).name.text === "catch" || isRej) { + const synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); + if (!getNextDotThen(parent, checker) || (parent.expression).name.text === "catch" || isRej) { return createNodeArray([createReturn(synthCall)]); } - - let lastDotThen = getLastDotThen(parent, checker); - let tempArgName = lastDotThen ? getArgName(lastDotThen.arguments[0], checker, "temp") : argName; - return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); + + const lastDotThen = getNextDotThen(parent, checker); + const tempArgName = lastDotThen ? getArgName(lastDotThen.arguments[0], checker, "temp") : argName; + return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: From 4c5af5a7d3c15b36f0c6602a8e0c7a25a24f84ed Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 13:35:38 -0700 Subject: [PATCH 062/196] Changed wording of the diagnostic --- src/compiler/diagnosticMessages.json | 6 +++--- src/services/codefixes/convertPromisesToAwaitAndAsync.ts | 4 ++-- src/services/suggestionDiagnostics.ts | 8 ++++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 047d4b9483d1e..2e8f9c33f2dc3 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3964,7 +3964,7 @@ "category": "Suggestion", "code": 80005 }, - "This may be converted to use async and await.": { + "This may be converted to an async function.": { "category": "Suggestion", "code": 80006 }, @@ -4285,11 +4285,11 @@ "category": "Message", "code": 95054 }, - "Convert to use async and await":{ + "Convert to async function":{ "category": "Message", "code": 95055 }, - "Convert all to use async and await": { + "Convert all to async functions": { "category": "Message", "code": 95056 } diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index da929f3f8cea1..4f06d679ca7c4 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -1,11 +1,11 @@ namespace ts.codefix { const fixId = "convertPromisesToAwaitAndAsync"; - const errorCodes = [Diagnostics.This_may_be_converted_to_use_async_and_await.code]; + const errorCodes = [Diagnostics.This_may_be_converted_to_an_async_function.code]; registerCodeFix({ errorCodes, getCodeActions(context: CodeFixContext) { const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncAwait(t, context.sourceFile, context.span.start, context.program.getTypeChecker())); - return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_use_async_and_await, fixId, Diagnostics.Convert_all_to_use_async_and_await)]; + return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_async_function, fixId, Diagnostics.Convert_all_to_async_functions)]; }, fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncAwait(changes, err.file!, err.start, context.program.getTypeChecker())), diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index ba1c388b703fa..1d3cd254bde1d 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -33,12 +33,16 @@ namespace ts { break; } + if(isFunctionLikeDeclaration(node) && !node.body){ + break; //break on ambient functions + } + if (checker.isPromiseLikeType(returnType)) { // collect all the return statements // check that a property access expression exists in there and that it is a handler - const retStmts = getReturnStatements(node); + const retStmts = getReturnStatementsWithPromiseCallbacks(node); if (retStmts.length > 0) { - diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_use_async_and_await)); + diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function)); } } break; From 40f136b122d300df44b7ce296ee81b8b0d28d080 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 13:37:03 -0700 Subject: [PATCH 063/196] Refactoring based on PR comments --- .../convertPromisesToAwaitAndAsync.ts | 4 +-- src/services/suggestionDiagnostics.ts | 29 ++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts index 4f06d679ca7c4..d7e1152e970d3 100644 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts @@ -67,8 +67,8 @@ namespace ts.codefix { } function parseThen(node: CallExpression, checker: TypeChecker): Statement[] { - const res = node.arguments[0]; - const rej = node.arguments[1]; + const [res, rej] = node.arguments; + // TODO - what if this is a binding pattern and not an Identifier const argNameRes = getArgName(res, checker); diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 1d3cd254bde1d..9f33df29992a4 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -17,6 +17,8 @@ namespace ts { switch (node.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.ArrowFunction: if (isJsFile) { const symbol = node.symbol; @@ -24,6 +26,7 @@ namespace ts { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration)); } } + if (isAsyncFunction(node)) { break; } @@ -127,23 +130,41 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - function getReturnStatements(node: Node): ReturnStatement[] { + function getReturnStatementsWithPromiseCallbacks(node: Node): ReturnStatement[] { const retStmts: ReturnStatement[] = []; forEachChild(node, visit); + /* + function visit(node: Node) { + if (isFunctionLike(node)) { + return; + } + + if (isReturnStatement(node)) { + if (isCallExpression(node) && isPropertyAccessExpression(node.expression) && + (node.expression.name.text === "then" || node.expression.name.text === "catch")) { + retStmts.push(node as ReturnStatement); + } + else if (!isFunctionLike(node)) { + forEachChild(node, visit); + } + } + } + */ + function visit(node: Node) { if (isFunctionLike(node)) { return; } - if (node.kind === SyntaxKind.ReturnStatement) { + if (isReturnStatement(node)) { forEachChild(node, hasCallback); } function hasCallback(child: Node) { - if (child.kind === SyntaxKind.CallExpression && (child).expression.kind === SyntaxKind.PropertyAccessExpression && - (((child).expression).name.text === "then" || ((child).expression).name.text === "catch")) { + if (isCallExpression(child) && isPropertyAccessExpression(child.expression) && + (child.expression.name.text === "then" || child.expression.name.text === "catch")) { retStmts.push(node as ReturnStatement); } else if (!isFunctionLike(child)) { From 685645740f6db344bfccefa2933c88b25f6bb923 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 14:02:43 -0700 Subject: [PATCH 064/196] Changed name of file and changed diagnostic messages --- src/harness/tsconfig.json | 3 +- src/server/tsconfig.json | 2 +- src/server/tsconfig.library.json | 2 +- .../codefixes/convertToAsyncFunction.ts | 162 ++++++++++++++++++ src/services/suggestionDiagnostics.ts | 1 + src/services/tsconfig.json | 2 +- 6 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 src/services/codefixes/convertToAsyncFunction.ts diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 4170e96b64050..c835f64e611cd 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -92,9 +92,8 @@ "../services/codefixes/addMissingInvocationForDecorator.ts", "../services/codefixes/annotateWithTypeFromJSDoc.ts", "../services/codefixes/convertFunctionToEs6Class.ts", + "../services/codefixes/convertToAsyncFunction.ts", "../services/codefixes/convertToEs6Module.ts", - "../services/codefixes/convertPromisesToAwaitAndAsync.ts", - "../services/codefixes/convertPromisesToAwaitAndAsync.ts", "../services/codefixes/correctQualifiedNameToIndexedAccessType.ts", "../services/codefixes/fixClassIncorrectlyImplementsInterface.ts", "../services/codefixes/importFixes.ts", diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json index 29725fe00ec6c..1e933772f076f 100644 --- a/src/server/tsconfig.json +++ b/src/server/tsconfig.json @@ -88,7 +88,7 @@ "../services/codefixes/addMissingInvocationForDecorator.ts", "../services/codefixes/annotateWithTypeFromJSDoc.ts", "../services/codefixes/convertFunctionToEs6Class.ts", - "../services/codefixes/convertPromisesToAwaitAndAsync.ts", + "../services/codefixes/convertToAsyncFunction.ts", "../services/codefixes/convertToEs6Module.ts", "../services/codefixes/correctQualifiedNameToIndexedAccessType.ts", "../services/codefixes/fixClassIncorrectlyImplementsInterface.ts", diff --git a/src/server/tsconfig.library.json b/src/server/tsconfig.library.json index e07aa3d80d1e9..3c7cda8d74b0a 100644 --- a/src/server/tsconfig.library.json +++ b/src/server/tsconfig.library.json @@ -93,8 +93,8 @@ "../services/refactorProvider.ts", "../services/codefixes/addMissingInvocationForDecorator.ts", "../services/codefixes/annotateWithTypeFromJSDoc.ts", - "../services/codefixes/convertPromisesToAwaitAndAsync.ts", "../services/codefixes/convertFunctionToEs6Class.ts", + "../services/codefixes/convertToAsyncFunction.ts", "../services/codefixes/convertToEs6Module.ts", "../services/codefixes/correctQualifiedNameToIndexedAccessType.ts", "../services/codefixes/fixClassIncorrectlyImplementsInterface.ts", diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts new file mode 100644 index 0000000000000..91d5ccedf91d1 --- /dev/null +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -0,0 +1,162 @@ +namespace ts.codefix { + const fixId = "convertToAsyncFunction"; + const errorCodes = [Diagnostics.This_may_be_converted_to_an_async_function.code]; + registerCodeFix({ + errorCodes, + getCodeActions(context: CodeFixContext) { + const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncAwait(t, context.sourceFile, context.span.start, context.program.getTypeChecker())); + return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_async_function, fixId, Diagnostics.Convert_all_to_async_functions)]; + }, + fixIds: [fixId], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncAwait(changes, err.file!, err.start, context.program.getTypeChecker())), + }); + function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { + // get the function declaration - returns a promise + const funcToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)) as FunctionLikeDeclaration; + + // add the async keyword + changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); + + const stmts: NodeArray = (funcToConvert.body).statements; + for (const stmt of stmts) { + forEachChild(stmt, function visit(node: Node) { + if (node.kind === SyntaxKind.CallExpression) { + const newNode = parseCallback(node as CallExpression, checker); + if (newNode) { + changes.replaceNodeWithNodes(sourceFile, stmt, newNode); + } + } + else if (!isFunctionLike(node)) { + forEachChild(node, visit); + } + }); + } + } + + function returnsAPromise(node: CallExpression, checker: TypeChecker): boolean { + return checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); + } + + function parseCallback(node: Expression, checker: TypeChecker, argName?: string): Statement[] { + if (!node) { + return; + } + + if (node.kind === SyntaxKind.CallExpression && returnsAPromise(node as CallExpression, checker)) { + return parsePromiseCall(node as CallExpression, argName); + } + else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { + return parseThen(node as CallExpression, checker); + } + else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "catch", checker)) { + return parseCatch(node as CallExpression, checker); + } + else if (node.kind === SyntaxKind.PropertyAccessExpression) { + return parseCallback((node).expression, checker, argName); + } + } + + function parseCatch(node: CallExpression, checker: TypeChecker): Statement[] { + const func = getSynthesizedDeepClone(node.arguments[0]); + const argName = getArgName(func, checker); + + const tryBlock = createBlock(parseCallback(node.expression, checker, argName)); + const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName, node, checker))); + + return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; + } + + function parseThen(node: CallExpression, checker: TypeChecker): Statement[] { + const [res, rej] = node.arguments; + + // TODO - what if this is a binding pattern and not an Identifier + const argNameRes = getArgName(res, checker); + + if (rej) { + const argNameRej = getArgName(rej, checker); + + const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker))); + const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, node, checker, /* isRej */ true))); + + return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; + } + else { + return parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker)); + } + } + + function parsePromiseCall(node: CallExpression, argName?: string): Statement[] { + let localArgName = argName; + if (!localArgName) { + localArgName = "val"; // fix this to maybe not always create a variable declaration if not necessary + } + + const varDecl = createVariableDeclaration(createIdentifier(localArgName), /*type*/ undefined, createAwait(node)); + return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; + } + + function getNextDotThen(node: Expression, checker: TypeChecker): CallExpression { + if (!node || !node.parent) { + return undefined; + } + + const parent = node.parent; + if (parent.kind === SyntaxKind.CallExpression && isCallback(parent as CallExpression, "then", checker)) { + return parent as CallExpression; + } + else { + return getNextDotThen(node.parent as Expression, checker); + } + } + + function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, isRej = false): NodeArray { + switch (func.kind) { + case SyntaxKind.Identifier: + const synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); + if (!getNextDotThen(parent, checker) || (parent.expression).name.text === "catch" || isRej) { + return createNodeArray([createReturn(synthCall)]); + } + + const lastDotThen = getNextDotThen(parent, checker); + const tempArgName = lastDotThen ? getArgName(lastDotThen.arguments[0], checker, "temp") : argName; + return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); + + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + if ((func).body.kind === SyntaxKind.Block) { + return (func).body.statements; + } + else { + return createNodeArray([createReturn((func).body as Expression)]); + } + } + } + + + function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { + if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { + return false; + } + return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); + } + + function getArgName(funcNode: Node, checker: TypeChecker, defaultVal?: string): string { + if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { + const name = (funcNode.parameters[0].name).text; + if (name !== "_") { + return name; + } + else { + return defaultVal; + } + } + else if (checker.getTypeAtLocation(funcNode).getCallSignatures().length > 0 && checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters.length > 0) { + const name = checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters[0].name; + return name; + } + else { + return defaultVal; + } + } +} \ No newline at end of file diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 9f33df29992a4..0206c19bbf141 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -174,6 +174,7 @@ namespace ts { forEachChild(node, visit); } + return retStmts; } } diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index e7c811e461807..1bbcf20093984 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -85,7 +85,7 @@ "codefixes/addMissingInvocationForDecorator.ts", "codefixes/annotateWithTypeFromJSDoc.ts", "codefixes/convertFunctionToEs6Class.ts", - "codefixes/convertPromisesToAwaitAndAsync.ts", + "codefixes/convertToAsyncFunction.ts", "codefixes/convertToEs6Module.ts", "codefixes/correctQualifiedNameToIndexedAccessType.ts", "codefixes/fixClassIncorrectlyImplementsInterface.ts", From 8dfeac3b7c9eb496b839f3db7ca3edbf66b4832d Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 14:03:43 -0700 Subject: [PATCH 065/196] file name change --- .../convertPromisesToAwaitAndAsync.ts | 162 ------------------ 1 file changed, 162 deletions(-) delete mode 100644 src/services/codefixes/convertPromisesToAwaitAndAsync.ts diff --git a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts b/src/services/codefixes/convertPromisesToAwaitAndAsync.ts deleted file mode 100644 index d7e1152e970d3..0000000000000 --- a/src/services/codefixes/convertPromisesToAwaitAndAsync.ts +++ /dev/null @@ -1,162 +0,0 @@ -namespace ts.codefix { - const fixId = "convertPromisesToAwaitAndAsync"; - const errorCodes = [Diagnostics.This_may_be_converted_to_an_async_function.code]; - registerCodeFix({ - errorCodes, - getCodeActions(context: CodeFixContext) { - const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncAwait(t, context.sourceFile, context.span.start, context.program.getTypeChecker())); - return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_async_function, fixId, Diagnostics.Convert_all_to_async_functions)]; - }, - fixIds: [fixId], - getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncAwait(changes, err.file!, err.start, context.program.getTypeChecker())), - }); - function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { - // get the function declaration - returns a promise - const funcToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)) as FunctionLikeDeclaration; - - // add the async keyword - changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); - - const stmts: NodeArray = (funcToConvert.body).statements; - for (const stmt of stmts) { - forEachChild(stmt, function visit(node: Node) { - if (node.kind === SyntaxKind.CallExpression) { - const newNode = parseCallback(node as CallExpression, checker); - if (newNode) { - changes.replaceNodeWithNodes(sourceFile, stmt, newNode); - } - } - else if (!isFunctionLike(node)) { - forEachChild(node, visit); - } - }); - } - } - - function returnsAPromise(node: CallExpression, checker: TypeChecker): boolean { - return checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); - } - - function parseCallback(node: Expression, checker: TypeChecker, argName?: string): Statement[] { - if (!node) { - return; - } - - if (node.kind === SyntaxKind.CallExpression && returnsAPromise(node as CallExpression, checker)) { - return parsePromiseCall(node as CallExpression, argName); - } - else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { - return parseThen(node as CallExpression, checker); - } - else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "catch", checker)) { - return parseCatch(node as CallExpression, checker); - } - else if (node.kind === SyntaxKind.PropertyAccessExpression) { - return parseCallback((node).expression, checker, argName); - } - } - - function parseCatch(node: CallExpression, checker: TypeChecker): Statement[] { - const func = getSynthesizedDeepClone(node.arguments[0]); - const argName = getArgName(func, checker); - - const tryBlock = createBlock(parseCallback(node.expression, checker, argName)); - const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName, node, checker))); - - return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; - } - - function parseThen(node: CallExpression, checker: TypeChecker): Statement[] { - const [res, rej] = node.arguments; - - // TODO - what if this is a binding pattern and not an Identifier - const argNameRes = getArgName(res, checker); - - if (rej) { - const argNameRej = getArgName(rej, checker); - - const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker))); - const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, node, checker, /* isRej */ true))); - - return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; - } - else { - return parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker)); - } - } - - function parsePromiseCall(node: CallExpression, argName?: string): Statement[] { - let localArgName = argName; - if (!localArgName) { - localArgName = "val"; // fix this to maybe not always create a variable declaration if not necessary - } - - const varDecl = createVariableDeclaration(createIdentifier(localArgName), /*type*/ undefined, createAwait(node)); - return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; - } - - function getNextDotThen(node: Expression, checker: TypeChecker): CallExpression { - if (!node || !node.parent) { - return undefined; - } - - const parent = node.parent; - if (parent.kind === SyntaxKind.CallExpression && isCallback(parent as CallExpression, "then", checker)) { - return parent as CallExpression; - } - else { - return getNextDotThen(node.parent as Expression, checker); - } - } - - function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, isRej = false): NodeArray { - switch (func.kind) { - case SyntaxKind.Identifier: - const synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); - if (!getNextDotThen(parent, checker) || (parent.expression).name.text === "catch" || isRej) { - return createNodeArray([createReturn(synthCall)]); - } - - const lastDotThen = getNextDotThen(parent, checker); - const tempArgName = lastDotThen ? getArgName(lastDotThen.arguments[0], checker, "temp") : argName; - return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); - - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - if ((func).body.kind === SyntaxKind.Block) { - return (func).body.statements; - } - else { - return createNodeArray([createReturn((func).body as Expression)]); - } - } - } - - - function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { - if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { - return false; - } - return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); - } - - function getArgName(funcNode: Node, checker: TypeChecker, defaultVal?: string): string { - if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { - const name = (funcNode.parameters[0].name).text; - if (name !== "_") { - return name; - } - else { - return defaultVal; - } - } - else if (checker.getTypeAtLocation(funcNode).getCallSignatures().length > 0 && checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters.length > 0) { - const name = checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters[0].name; - return name; - } - else { - return defaultVal; - } - } -} \ No newline at end of file From 8b7ed24cbb503d966213a2c29b090962762b6c8e Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 25 Jun 2018 16:57:28 -0700 Subject: [PATCH 066/196] Switched to baseline tests --- .../unittests/convertToAsyncFunction.ts | 286 ++++++++++++++++++ .../codefixes/convertToAsyncFunction.ts | 6 +- .../convertToAsyncFunction_ArrowFunction.ts | 11 + .../convertToAsyncFunction_Catch.ts | 16 + .../convertToAsyncFunction_CatchAndRej.ts | 21 ++ .../convertToAsyncFunction_CatchAndRejRef.ts | 39 +++ .../convertToAsyncFunction_CatchNoBrackets.ts | 16 + .../convertToAsyncFunction_CatchRef.ts | 29 ++ .../convertToAsyncFunction_IgnoreArgs1.ts | 11 + .../convertToAsyncFunction_IgnoreArgs2.ts | 11 + .../convertToAsyncFunction_MultipleCatches.ts | 21 ++ .../convertToAsyncFunction_MultipleThens.ts | 24 ++ ...oAsyncFunction_MultipleThensSameVarName.ts | 25 ++ .../convertToAsyncFunction_basic.ts | 11 + 14 files changed, 524 insertions(+), 3 deletions(-) create mode 100644 src/harness/unittests/convertToAsyncFunction.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunction.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Catch.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRej.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRejRef.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchNoBrackets.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchRef.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_IgnoreArgs1.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_IgnoreArgs2.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleCatches.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basic.ts diff --git a/src/harness/unittests/convertToAsyncFunction.ts b/src/harness/unittests/convertToAsyncFunction.ts new file mode 100644 index 0000000000000..96536a7ee1e8c --- /dev/null +++ b/src/harness/unittests/convertToAsyncFunction.ts @@ -0,0 +1,286 @@ +/// +/// + +namespace ts { + interface Range { + pos: number; + end: number; + name: string; + } + + interface Test { + source: string; + ranges: Map; + } + + function getTest(source: string): Test { + const activeRanges: Range[] = []; + let text = ""; + let lastPos = 0; + let pos = 0; + const ranges = createMap(); + + while (pos < source.length) { + if (source.charCodeAt(pos) === CharacterCodes.openBracket && + (source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) { + const saved = pos; + pos += 2; + const s = pos; + consumeIdentifier(); + const e = pos; + if (source.charCodeAt(pos) === CharacterCodes.bar) { + pos++; + text += source.substring(lastPos, saved); + const name = s === e + ? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted" + : source.substring(s, e); + activeRanges.push({ name, pos: text.length, end: undefined }); + lastPos = pos; + continue; + } + else { + pos = saved; + } + } + else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) { + text += source.substring(lastPos, pos); + activeRanges[activeRanges.length - 1].end = text.length; + const range = activeRanges.pop(); + if (range.name in ranges) { + throw new Error(`Duplicate name of range ${range.name}`); + } + ranges.set(range.name, range); + pos += 2; + lastPos = pos; + continue; + } + pos++; + } + text += source.substring(lastPos, pos); + + function consumeIdentifier() { + while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) { + pos++; + } + } + return { source: text, ranges }; + } + + + const newLineCharacter = "\n"; + const formatOptions: FormatCodeSettings = { + indentSize: 4, + tabSize: 4, + newLineCharacter, + convertTabsToSpaces: true, + indentStyle: IndentStyle.Smart, + insertSpaceAfterConstructor: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + }; + + const notImplementedHost: LanguageServiceHost = { + getCompilationSettings: notImplemented, + getScriptFileNames: notImplemented, + getScriptVersion: notImplemented, + getScriptSnapshot: notImplemented, + getDefaultLibFileName: notImplemented, + getCurrentDirectory: notImplemented, + }; + + function testConvertToAsyncFunction(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage, includeLib?: boolean) { + const t = getTest(text); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${caption} does not specify selection range`); + } + + [Extension.Ts, Extension.Js].forEach(extension => + it(`${caption} [${extension}]`, () => runBaseline(extension))); + + function runBaseline(extension: Extension) { + const path = "/a" + extension; + const program = makeProgram({ path, content: t.source }, includeLib); + + if (hasSyntacticDiagnostics(program)) { + // Don't bother generating JS baselines for inputs that aren't valid JS. + assert.equal(Extension.Js, extension, "Syntactic diagnostics found in non-JS file"); + return; + } + + const sourceFile = program.getSourceFile(path); + const context: CodeFixContext = { + cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, + program, + sourceFile: sourceFile, + errorCode: 80006, + span: { start: 0, length: 12 }, + host: notImplementedHost, + preferences: defaultPreferences, + formatContext: formatting.getFormatContext(formatOptions) + }; + + + debugger; + const actions = codefix.getFixes(context); + const action = find(actions, info => info.description === description.message); + + Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, () => { + 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 = makeProgram({ path, content: newText }, includeLib); + assert.isFalse(hasSyntacticDiagnostics(diagProgram)); + return data.join(newLineCharacter); + }); + } + + function makeProgram(f: { path: string, content: string }, includeLib?: boolean) { + const host = projectSystem.createServerHost(includeLib ? [f, projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required + const projectService = projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + const program = projectService.inferredProjects[0].getLanguageService().getProgram(); + return program; + } + + function hasSyntacticDiagnostics(program: Program) { + const diags = program.getSyntacticDiagnostics(); + return length(diags) > 0; + } + } + + describe("convertToAsyncFunctions", () => { + testConvertToAsyncFunctions("convertToAsyncFunction_basic", ` +function [#|f|](): Promise{ + return fetch('https://typescriptlang.org').then(result => { console.log(result) }); +}`); + testConvertToAsyncFunctions("convertToAsyncFunction_ArrowFunction", ` +[#|():Promise => {|] + return fetch('https://typescriptlang.org').then(result => console.log(result)); +}`); + testConvertToAsyncFunctions("convertToAsyncFunction_Catch", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(result => { console.log(result); }).catch(err => { console.log(err); }); +}`); + testConvertToAsyncFunctions("convertToAsyncFunction_CatchAndRej", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) }); +}`); + testConvertToAsyncFunctions("convertToAsyncFunction_CatchAndRejRef", ` +function [#|f|]():Promise { + 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); +}`); + testConvertToAsyncFunctions("convertToAsyncFunction_CatchRef", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(res).catch(catch_err) +} +function res(result){ + console.log(result); +} +function catch_err(err){ + console.log(err); +} +`); + testConvertToAsyncFunctions("convertToAsyncFunction_CatchNoBrackets", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(result => console.log(result)).catch(err => console.log(err)); +}` + ); + testConvertToAsyncFunctions("convertToAsyncFunction_IgnoreArgs1", ` +function [#|f|](): Promise { + return fetch('https://typescriptlang.org').then( _ => { console.log("done"); }); +}` + ); + testConvertToAsyncFunctions("convertToAsyncFunction_IgnoreArgs2", ` +function [#|f|](): Promise { + return fetch('https://typescriptlang.org').then( () => console.log("done") ); +}` + ); + testConvertToAsyncFunctions("convertToAsyncFunction_Method", ` +class Parser { + [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(result => console.log(result)); + } +}` + ); + testConvertToAsyncFunctions("convertToAsyncFunction_MultipleCatches", ` +function [#|f|](): Promise { + return fetch('https://typescriptlang.org').then(res => console.log(res)).catch(err => console.log("err", err)).catch(err2 => console.log("err2", err2)); +}` + ); + testConvertToAsyncFunctions("convertToAsyncFunction_MultipleThens", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(res).then(res2); +} +function res(result){ + return result.ok; +} +function res2(result2){ + console.log(result2); +}` + ); + testConvertToAsyncFunctions("convertToAsyncFunction_MultipleThensSameVarName", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(res).then(res2); +} +function res(result){ + return result.ok; +} +function res2(result){ + console.log(result); +} +` + ); + testConvertToAsyncFunctions("convertToAsyncFunction_NoRes", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(null, rejection => console.log("rejected:", rejection); ); +} +` + ); + testConvertToAsyncFunctions("convertToAsyncFunction_NoSuggestion", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org'); +} +` + ); + testConvertToAsyncFunctions("convertToAsyncFunction_PromiseDotAll", ` +function [#|f|]():Promise{ + return Promise.all([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(function(vals){ + vals.forEach(console.log); + }); +} +` + ); +}); + + function testConvertToAsyncFunctions(caption: string, text: string, includeLib?: boolean) { + testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.Convert_to_async_function, includeLib); + } +} \ No newline at end of file diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 91d5ccedf91d1..cec1c1d7bb1f1 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -4,13 +4,13 @@ namespace ts.codefix { registerCodeFix({ errorCodes, getCodeActions(context: CodeFixContext) { - const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncAwait(t, context.sourceFile, context.span.start, context.program.getTypeChecker())); + const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncFunction(t, context.sourceFile, context.span.start, context.program.getTypeChecker())); return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_async_function, fixId, Diagnostics.Convert_all_to_async_functions)]; }, fixIds: [fixId], - getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncAwait(changes, err.file!, err.start, context.program.getTypeChecker())), + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file!, err.start, context.program.getTypeChecker())), }); - function convertToAsyncAwait(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { + function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { // get the function declaration - returns a promise const funcToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)) as FunctionLikeDeclaration; diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunction.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunction.ts new file mode 100644 index 0000000000000..fd58eed2d26ee --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunction.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +/*[#|*/():Promise => {/*|]*/ + return fetch('https://typescriptlang.org').then(result => console.log(result)); +} +// ==ASYNC FUNCTION::Convert to async function== + +async ():Promise => { + let result = await fetch('https://typescriptlang.org'); + return console.log(result); +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Catch.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Catch.ts new file mode 100644 index 0000000000000..a0da1875cb88f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Catch.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(result => { console.log(result); }).catch(err => { console.log(err); }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + try { + let result = await fetch('https://typescriptlang.org'); + console.log(result); + } + catch (err) { + console.log(err); + } +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRej.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRej.ts new file mode 100644 index 0000000000000..197b59b3bdce2 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRej.ts @@ -0,0 +1,21 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + 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 { + try { + try { + let result = await fetch('https://typescriptlang.org'); + console.log(result); + } + catch (rejection) { + console.log("rejected:", rejection); + } + } + catch (err) { + console.log(err); + } +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRejRef.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRejRef.ts new file mode 100644 index 0000000000000..405ea99f30704 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRejRef.ts @@ -0,0 +1,39 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + 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 { + try { + try { + let 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); +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchNoBrackets.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchNoBrackets.ts new file mode 100644 index 0000000000000..f388950e920bc --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchNoBrackets.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(result => console.log(result)).catch(err => console.log(err)); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + try { + let result = await fetch('https://typescriptlang.org'); + return console.log(result); + } + catch (err) { + return console.log(err); + } +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchRef.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchRef.ts new file mode 100644 index 0000000000000..5db2fb1f75bac --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchRef.ts @@ -0,0 +1,29 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(res).catch(catch_err) +} +function res(result){ + console.log(result); +} +function catch_err(err){ + console.log(err); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + try { + let result = await fetch('https://typescriptlang.org'); + return res(result); + } + catch (err) { + return catch_err(err); + } +} +function res(result){ + console.log(result); +} +function catch_err(err){ + console.log(err); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_IgnoreArgs1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_IgnoreArgs1.ts new file mode 100644 index 0000000000000..2cb2ef5b32d9b --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_IgnoreArgs1.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise { + return fetch('https://typescriptlang.org').then( _ => { console.log("done"); }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise { + await fetch('https://typescriptlang.org'); + console.log("done"); +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_IgnoreArgs2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_IgnoreArgs2.ts new file mode 100644 index 0000000000000..395226496947f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_IgnoreArgs2.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise { + return fetch('https://typescriptlang.org').then( () => console.log("done") ); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise { + await fetch('https://typescriptlang.org'); + return console.log("done"); +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleCatches.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleCatches.ts new file mode 100644 index 0000000000000..7f0bec63c3e51 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleCatches.ts @@ -0,0 +1,21 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise { + return fetch('https://typescriptlang.org').then(res => console.log(res)).catch(err => console.log("err", err)).catch(err2 => console.log("err2", err2)); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise { + try { + try { + let res = await fetch('https://typescriptlang.org'); + return console.log(res); + } + catch (err) { + return console.log("err", err); + } + } + catch (err2) { + return console.log("err2", err2); + } +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts new file mode 100644 index 0000000000000..8a3359bf363fc --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts @@ -0,0 +1,24 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(res).then(res2); +} +function res(result){ + return result.ok; +} +function res2(result2){ + console.log(result2); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + let result = await fetch('https://typescriptlang.org'); + let result2 = await res(result); + return res2(result2); +} +function res(result){ + return result.ok; +} +function res2(result2){ + console.log(result2); +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts new file mode 100644 index 0000000000000..2a6c56305a61e --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts @@ -0,0 +1,25 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(res).then(res2); +} +function res(result){ + return result.ok; +} +function res2(result){ + console.log(result); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + let result = await fetch('https://typescriptlang.org'); + let temp = await res(result); + return res2(temp); +} +function res(result){ + return result.ok; +} +function res2(result){ + console.log(result); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basic.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basic.ts new file mode 100644 index 0000000000000..ed5f0f16f138f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basic.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise{ + return fetch('https://typescriptlang.org').then(result => { console.log(result) }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise{ + let result = await fetch('https://typescriptlang.org'); + console.log(result); +} \ No newline at end of file From 9895781617687db2dbf2daf5b35d6cc87655ef71 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 26 Jun 2018 14:34:53 -0700 Subject: [PATCH 067/196] Initial commit of creating new names when necessary --- .../codefixes/convertToAsyncFunction.ts | 104 +++++++++++------- src/services/suggestionDiagnostics.ts | 2 +- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index cec1c1d7bb1f1..7a9b08192a63e 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -17,11 +17,13 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); - const stmts: NodeArray = (funcToConvert.body).statements; - for (const stmt of stmts) { + //const stmts: NodeArray = (funcToConvert.body).statements; + const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvert); + for (const stmt of retStmts) { forEachChild(stmt, function visit(node: Node) { if (node.kind === SyntaxKind.CallExpression) { - const newNode = parseCallback(node as CallExpression, checker); + const usedNames: string[] = [] + const newNode = parseCallback(node as CallExpression, checker, usedNames); if (newNode) { changes.replaceNodeWithNodes(sourceFile, stmt, newNode); } @@ -37,61 +39,69 @@ namespace ts.codefix { return checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, argName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, usedNames: string[], argName?: string): Statement[] { if (!node) { return; } if (node.kind === SyntaxKind.CallExpression && returnsAPromise(node as CallExpression, checker)) { - return parsePromiseCall(node as CallExpression, argName); + return parsePromiseCall(node as CallExpression, usedNames, checker, argName); } else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { - return parseThen(node as CallExpression, checker); + return parseThen(node as CallExpression, checker, usedNames); } else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "catch", checker)) { - return parseCatch(node as CallExpression, checker); + return parseCatch(node as CallExpression, checker, usedNames); } else if (node.kind === SyntaxKind.PropertyAccessExpression) { - return parseCallback((node).expression, checker, argName); + return parseCallback((node).expression, checker, usedNames, argName); } } - function parseCatch(node: CallExpression, checker: TypeChecker): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, usedNames: string[]): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); - const argName = getArgName(func, checker); + let argName = getArgName(func, checker, usedNames); - const tryBlock = createBlock(parseCallback(node.expression, checker, argName)); - const catchClause = createCatchClause(argName, createBlock(getCallbackBody(func, argName, node, checker))); + const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames, argName)); + + let [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, usedNames); + const catchClause = createCatchClause(argNameNew, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, usedNames: string[]): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier - const argNameRes = getArgName(res, checker); + let argNameRes = getArgName(res, checker, usedNames); if (rej) { - const argNameRej = getArgName(rej, checker); + const usedCatchNames: string[] = [] + const argNameRej = getArgName(rej, checker, usedCatchNames); + + let [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames); + const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames, argNameResNew).concat(callbackBody)); - const tryBlock = createBlock(parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker))); - const catchClause = createCatchClause(argNameRej, createBlock(getCallbackBody(rej, argNameRej, node, checker, /* isRej */ true))); + let [callbackBody2, argNameRejNew] = getCallbackBody(rej, argNameRej, node, checker, usedCatchNames, /* isRej */ true); + const catchClause = createCatchClause(argNameRejNew, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else { - return parseCallback(node.expression, checker, argNameRes).concat(getCallbackBody(res, argNameRes, node, checker)); + let [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames); + return parseCallback(node.expression, checker, usedNames, argNameResNew).concat(callbackBody); } } - function parsePromiseCall(node: CallExpression, argName?: string): Statement[] { - let localArgName = argName; - if (!localArgName) { - localArgName = "val"; // fix this to maybe not always create a variable declaration if not necessary - } + function parsePromiseCall(node: CallExpression, usedNames: string[], checker: TypeChecker, argName: string): Statement[] { + if (!argName){ + argName = getArgName(node, checker, usedNames); + } - const varDecl = createVariableDeclaration(createIdentifier(localArgName), /*type*/ undefined, createAwait(node)); + usedNames.push(argName) + + const varDecl = createVariableDeclaration(argName, /*type*/ undefined, createAwait(node)); return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; } @@ -109,31 +119,35 @@ namespace ts.codefix { } } - function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, isRej = false): NodeArray { + function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, usedNames: string[], isRej = false): [NodeArray, string] { switch (func.kind) { case SyntaxKind.Identifier: - const synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); - if (!getNextDotThen(parent, checker) || (parent.expression).name.text === "catch" || isRej) { - return createNodeArray([createReturn(synthCall)]); + let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); + const nextDotThen = getNextDotThen(parent, checker); + if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { + return [createNodeArray([createReturn(synthCall)]), argName]; } + + const tempArgName = getArgName(nextDotThen.arguments[0], checker, usedNames); + usedNames.push(tempArgName); - const lastDotThen = getNextDotThen(parent, checker); - const tempArgName = lastDotThen ? getArgName(lastDotThen.arguments[0], checker, "temp") : argName; - return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); + argName = getArgName(func, checker, usedNames); + synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); + + return [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]), argName]; case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: if ((func).body.kind === SyntaxKind.Block) { - return (func).body.statements; + return [(func).body.statements, argName]; } else { - return createNodeArray([createReturn((func).body as Expression)]); + return [createNodeArray([createReturn((func).body as Expression)]), argName] } } } - function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; @@ -141,22 +155,28 @@ namespace ts.codefix { return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); } - function getArgName(funcNode: Node, checker: TypeChecker, defaultVal?: string): string { + function getArgName(funcNode: Node, checker: TypeChecker, usedNames: string[]): string { if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { const name = (funcNode.parameters[0].name).text; - if (name !== "_") { + if (name !== "_" && !isArgUsed(name, usedNames)) { return name; } - else { - return defaultVal; - } } else if (checker.getTypeAtLocation(funcNode).getCallSignatures().length > 0 && checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters.length > 0) { const name = checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters[0].name; - return name; + if (!isArgUsed(name, usedNames)){ + return name; + } } - else { - return defaultVal; + else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])){ + return (funcNode.arguments[0]).text; } + + return "NEWNAME"; + } + + function isArgUsed(name: string, usedNames: string[]): boolean { + let nameUsed: string[] = usedNames.filter(element => element === name); + return nameUsed.length > 0; } } \ No newline at end of file diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 0206c19bbf141..37c4341555d4b 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -130,7 +130,7 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - function getReturnStatementsWithPromiseCallbacks(node: Node): ReturnStatement[] { + export function getReturnStatementsWithPromiseCallbacks(node: Node): ReturnStatement[] { const retStmts: ReturnStatement[] = []; forEachChild(node, visit); From d64ddb5493336dd01f77f6256ce3d743ab117e80 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 26 Jun 2018 14:57:13 -0700 Subject: [PATCH 068/196] generating variable names with _1, _2, ..., _n when scopes intersect --- src/services/codefixes/convertToAsyncFunction.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 7a9b08192a63e..d2e2dd2c20724 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -155,15 +155,18 @@ namespace ts.codefix { return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); } + function getArgName(funcNode: Node, checker: TypeChecker, usedNames: string[]): string { + let name; + if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { - const name = (funcNode.parameters[0].name).text; + name = (funcNode.parameters[0].name).text; if (name !== "_" && !isArgUsed(name, usedNames)) { return name; } } else if (checker.getTypeAtLocation(funcNode).getCallSignatures().length > 0 && checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters.length > 0) { - const name = checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters[0].name; + name = checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters[0].name; if (!isArgUsed(name, usedNames)){ return name; } @@ -172,9 +175,14 @@ namespace ts.codefix { return (funcNode.arguments[0]).text; } - return "NEWNAME"; + return name + "_" + getArgName.varNameItr; } + namespace getArgName { + export let varNameItr = 1; + } + + function isArgUsed(name: string, usedNames: string[]): boolean { let nameUsed: string[] = usedNames.filter(element => element === name); return nameUsed.length > 0; From 97156d1d120cf7553f7db839d175fed74ec29db2 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 26 Jun 2018 15:16:17 -0700 Subject: [PATCH 069/196] Fixed generating new variables to not occur when _ is used as an argument --- src/services/codefixes/convertToAsyncFunction.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index d2e2dd2c20724..fdb87f630847c 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -95,13 +95,14 @@ namespace ts.codefix { } function parsePromiseCall(node: CallExpression, usedNames: string[], checker: TypeChecker, argName: string): Statement[] { - if (!argName){ - argName = getArgName(node, checker, usedNames); + let localArgName = argName; + if (!localArgName){ + localArgName = getArgName(node, checker, usedNames); } - usedNames.push(argName) + usedNames.push(localArgName) - const varDecl = createVariableDeclaration(argName, /*type*/ undefined, createAwait(node)); + const varDecl = createVariableDeclaration(localArgName, /*type*/ undefined, createAwait(node)); return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; } @@ -175,6 +176,10 @@ namespace ts.codefix { return (funcNode.arguments[0]).text; } + if (name === undefined || name === "_"){ + return undefined; + } + return name + "_" + getArgName.varNameItr; } From cd0d4920ec7c9c269e2d80f68683d70ad353c089 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 27 Jun 2018 11:25:08 -0700 Subject: [PATCH 070/196] Initial commit of baseline tests --- .../unittests/convertToAsyncFunction.ts | 95 +++++++++++++------ 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/src/harness/unittests/convertToAsyncFunction.ts b/src/harness/unittests/convertToAsyncFunction.ts index 96536a7ee1e8c..d4b62d3175438 100644 --- a/src/harness/unittests/convertToAsyncFunction.ts +++ b/src/harness/unittests/convertToAsyncFunction.ts @@ -119,22 +119,33 @@ namespace ts { return; } + const f = { + path: path, + content: t.source + }; + const sourceFile = program.getSourceFile(path); + const host = projectSystem.createServerHost([f, projectSystem.libFile]); + const projectService = projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + const languageService = projectService.inferredProjects[0].getLanguageService(); const context: CodeFixContext = { - cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - program, - sourceFile: sourceFile, errorCode: 80006, - span: { start: 0, length: 12 }, - host: notImplementedHost, + span: { start: selectionRange.pos, length: selectionRange.end - selectionRange.pos }, + sourceFile, + program, + cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, preferences: defaultPreferences, + host: notImplementedHost, formatContext: formatting.getFormatContext(formatOptions) - }; + } + const diagnostics = languageService.getSuggestionDiagnostics(f.path); + const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === description.message); + assert.isNotNull(diagnostic); - debugger; const actions = codefix.getFixes(context); - const action = find(actions, info => info.description === description.message); + const action = find(actions, action => action.description === description.message); Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, () => { const data: string[] = []; @@ -167,24 +178,45 @@ namespace ts { } } + function testConvertToAsyncFunctionFailed(caption: string, text: string, description: DiagnosticMessage) { + it(caption, () => { + const t = extractTest(text); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${caption} does not specify selection range`); + } + const f = { + path: "/a.ts", + content: t.source + }; + const host = projectSystem.createServerHost([f, projectSystem.libFile]); + const projectService = projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + const languageService = projectService.inferredProjects[0].getLanguageService(); + + const actions = languageService.getSuggestionDiagnostics(f.path); + assert.isUndefined(find(actions, action => action.messageText === description.message)); + }); + } + describe("convertToAsyncFunctions", () => { - testConvertToAsyncFunctions("convertToAsyncFunction_basic", ` + _testConvertToAsyncFunction("convertToAsyncFunction_basic", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(result => { console.log(result) }); }`); - testConvertToAsyncFunctions("convertToAsyncFunction_ArrowFunction", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunction", ` [#|():Promise => {|] return fetch('https://typescriptlang.org').then(result => console.log(result)); }`); - testConvertToAsyncFunctions("convertToAsyncFunction_Catch", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Catch", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => { console.log(result); }).catch(err => { console.log(err); }); }`); - testConvertToAsyncFunctions("convertToAsyncFunction_CatchAndRej", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchAndRej", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) }); }`); - testConvertToAsyncFunctions("convertToAsyncFunction_CatchAndRejRef", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchAndRejRef", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res, rej).catch(catch_err) } @@ -197,7 +229,7 @@ function rej(rejection){ function catch_err(err){ console.log(err); }`); - testConvertToAsyncFunctions("convertToAsyncFunction_CatchRef", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchRef", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).catch(catch_err) } @@ -208,34 +240,34 @@ function catch_err(err){ console.log(err); } `); - testConvertToAsyncFunctions("convertToAsyncFunction_CatchNoBrackets", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchNoBrackets", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result)).catch(err => console.log(err)); }` ); - testConvertToAsyncFunctions("convertToAsyncFunction_IgnoreArgs1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs1", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then( _ => { console.log("done"); }); }` ); - testConvertToAsyncFunctions("convertToAsyncFunction_IgnoreArgs2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs2", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then( () => console.log("done") ); }` ); - testConvertToAsyncFunctions("convertToAsyncFunction_Method", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Method", ` class Parser { [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result)); } }` ); - testConvertToAsyncFunctions("convertToAsyncFunction_MultipleCatches", ` + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleCatches", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then(res => console.log(res)).catch(err => console.log("err", err)).catch(err2 => console.log("err2", err2)); }` ); - testConvertToAsyncFunctions("convertToAsyncFunction_MultipleThens", ` + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThens", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).then(res2); } @@ -246,7 +278,7 @@ function res2(result2){ console.log(result2); }` ); - testConvertToAsyncFunctions("convertToAsyncFunction_MultipleThensSameVarName", ` + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThensSameVarName", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).then(res2); } @@ -254,33 +286,40 @@ function res(result){ return result.ok; } function res2(result){ - console.log(result); + return result.bodyUsed; } ` ); - testConvertToAsyncFunctions("convertToAsyncFunction_NoRes", ` + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(null, rejection => console.log("rejected:", rejection); ); } ` ); - testConvertToAsyncFunctions("convertToAsyncFunction_NoSuggestion", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestion", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org'); } ` ); - testConvertToAsyncFunctions("convertToAsyncFunction_PromiseDotAll", ` + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseDotAll", ` function [#|f|]():Promise{ - return Promise.all([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(function(vals){ + return Promise.all([fetch('https://typescriptlang.org'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(function(vals){ vals.forEach(console.log); }); } ` ); -}); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionNoPromise", ` + function [#|f|]():void{ + }`); + }); - function testConvertToAsyncFunctions(caption: string, text: string, includeLib?: boolean) { + function _testConvertToAsyncFunction(caption: string, text: string, includeLib?: boolean) { testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.Convert_to_async_function, includeLib); } + + function _testConvertToAsyncFunctionFailed(caption: string, text: string) { + testConvertToAsyncFunctionFailed(caption, text, Diagnostics.Convert_to_async_function); + } } \ No newline at end of file From 588cb568dbc2c0e0a4e1aaf4efc30b0f15df502d Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 27 Jun 2018 14:33:00 -0700 Subject: [PATCH 071/196] Switched to baseline tests --- .../unittests/convertToAsyncFunction.ts | 69 ++++++++++++++++++- .../convertToAsyncFunction_Method.ts | 15 ++++ ...oAsyncFunction_MultipleThensSameVarName.ts | 10 +-- .../convertToAsyncFunction_NoBrackets.ts | 12 ++++ .../convertToAsyncFunction_NoRes.ts | 16 +++++ .../convertToAsyncFunction_NoRes2.ts | 16 +++++ .../convertToAsyncFunction_NoRes3.ts | 16 +++++ .../convertToAsyncFunction_Rej.ts | 17 +++++ .../convertToAsyncFunction_RejNoBrackets.ts | 17 +++++ .../convertToAsyncFunction_RejRef.ts | 29 ++++++++ .../convertToAsyncFunction_ResRef.ts | 18 +++++ ...onvertToAsyncFunction_ResRefNoReturnVal.ts | 18 +++++ .../cases/fourslash/codeFixPromiseToAsync.ts | 22 ------ .../codeFixPromiseToAsyncArrowFunction.ts | 23 ------- .../fourslash/codeFixPromiseToAsyncCatch.ts | 28 -------- .../codeFixPromiseToAsyncCatchAndRej.ts | 33 --------- .../codeFixPromiseToAsyncCatchAndRejRef.ts | 56 --------------- .../codeFixPromiseToAsyncCatchRef.ts | 41 ----------- .../codeFixPromiseToAsyncCatch_noBrackets.ts | 27 -------- .../codeFixPromiseToAsyncIgnoreArgs1.ts | 22 ------ .../codeFixPromiseToAsyncIgnoreArgs2.ts | 22 ------ .../fourslash/codeFixPromiseToAsyncMethod.ts | 27 -------- .../codeFixPromiseToAsyncMultipleCatchs.ts | 32 --------- .../codeFixPromiseToAsyncMultipleThens.ts | 39 ----------- ...xPromiseToAsyncMultipleThensSameVarName.ts | 39 ----------- .../fourslash/codeFixPromiseToAsyncNoRes.ts | 25 ------- .../codeFixPromiseToAsyncNoSuggestion.ts | 9 --- .../codeFixPromiseToAsyncPromiseDotAll.ts | 24 ------- .../codeFixPromiseToAsyncPromiseDotRace.ts | 24 ------- .../fourslash/codeFixPromiseToAsyncRej.ts | 27 -------- .../fourslash/codeFixPromiseToAsyncRejRef.ts | 43 ------------ .../codeFixPromiseToAsyncRej_NoBrackets.ts | 27 -------- .../fourslash/codeFixPromiseToAsyncResRef.ts | 30 -------- ...codeFixPromiseToAsyncResRef_NoReturnVal.ts | 30 -------- .../codeFixPromiseToAsync_NoBrackets.ts | 23 ------- 35 files changed, 245 insertions(+), 681 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Method.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoBrackets.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes2.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes3.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Rej.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejNoBrackets.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejRef.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRef.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRefNoReturnVal.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsync.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncRej.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts delete mode 100644 tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts diff --git a/src/harness/unittests/convertToAsyncFunction.ts b/src/harness/unittests/convertToAsyncFunction.ts index d4b62d3175438..78ca2c225edd8 100644 --- a/src/harness/unittests/convertToAsyncFunction.ts +++ b/src/harness/unittests/convertToAsyncFunction.ts @@ -292,7 +292,19 @@ function res2(result){ ); _testConvertToAsyncFunction("convertToAsyncFunction_NoRes", ` function [#|f|]():Promise { - return fetch('https://typescriptlang.org').then(null, rejection => console.log("rejected:", rejection); ); + return fetch('https://typescriptlang.org').then(null, rejection => console.log("rejected:", rejection)); +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes2", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(undefined).catch(rej => console.log(rej)); +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes3", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').catch(rej => console.log(rej)); } ` ); @@ -311,8 +323,59 @@ function [#|f|]():Promise{ ` ); _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionNoPromise", ` - function [#|f|]():void{ - }`); +function [#|f|]():void{ +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_Rej", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_RejRef", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(res, rej); +} +function res(result){ + console.log(result); +} +function rej(err){ + console.log(err); +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_RejNoBrackets", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(result => console.log(result), rejection => console.log("rejected:", rejection)); +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_ResRef", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(res); +} +function res(result){ + return result.ok; +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(res); +} +function res(result){ + console.log(result); +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoBrackets", ` +function [#|f|]():Promise { + return fetch('https://typescriptlang.org').then(result => console.log(result)); +} +` + ); + }); function _testConvertToAsyncFunction(caption: string, text: string, includeLib?: boolean) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Method.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Method.ts new file mode 100644 index 0000000000000..3c04c235355b1 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Method.ts @@ -0,0 +1,15 @@ +// ==ORIGINAL== + +class Parser { + /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(result => console.log(result)); + } +} +// ==ASYNC FUNCTION::Convert to async function== + +class Parser { + async f():Promise { + let result = await fetch('https://typescriptlang.org'); + return console.log(result); + } +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts index 2a6c56305a61e..2d62a0f4dea42 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts @@ -7,19 +7,19 @@ function res(result){ return result.ok; } function res2(result){ - console.log(result); + return result.bodyUsed; } // ==ASYNC FUNCTION::Convert to async function== async function f():Promise { - let result = await fetch('https://typescriptlang.org'); - let temp = await res(result); - return res2(temp); + let result_1 = await fetch('https://typescriptlang.org'); + let result = await res(result_1); + return res2(result); } function res(result){ return result.ok; } function res2(result){ - console.log(result); + return result.bodyUsed; } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoBrackets.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoBrackets.ts new file mode 100644 index 0000000000000..a4f9f7cd152a4 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoBrackets.ts @@ -0,0 +1,12 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(result => console.log(result)); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + let result = await fetch('https://typescriptlang.org'); + return console.log(result); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes.ts new file mode 100644 index 0000000000000..8817f682a8108 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(null, rejection => console.log("rejected:", rejection)); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + try { + await fetch('https://typescriptlang.org'); + } + catch (rejection) { + return console.log("rejected:", rejection); + } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes2.ts new file mode 100644 index 0000000000000..33d2f9808c97d --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes2.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(undefined).catch(rej => console.log(rej)); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + try { + await fetch('https://typescriptlang.org'); + } + catch (rej) { + return console.log(rej); + } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes3.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes3.ts new file mode 100644 index 0000000000000..469cf1cf9897b --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes3.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').catch(rej => console.log(rej)); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + try { + await fetch('https://typescriptlang.org'); + } + catch (rej) { + return console.log(rej); + } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Rej.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Rej.ts new file mode 100644 index 0000000000000..304ce7eb8655f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Rej.ts @@ -0,0 +1,17 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + try { + let result = await fetch('https://typescriptlang.org'); + console.log(result); + } + catch (rejection) { + console.log("rejected:", rejection); + } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejNoBrackets.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejNoBrackets.ts new file mode 100644 index 0000000000000..7cc94a2beea8a --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejNoBrackets.ts @@ -0,0 +1,17 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(result => console.log(result), rejection => console.log("rejected:", rejection)); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + try { + let result = await fetch('https://typescriptlang.org'); + return console.log(result); + } + catch (rejection) { + return console.log("rejected:", rejection); + } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejRef.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejRef.ts new file mode 100644 index 0000000000000..da8b0b013afe6 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejRef.ts @@ -0,0 +1,29 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(res, rej); +} +function res(result){ + console.log(result); +} +function rej(err){ + console.log(err); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + try { + let result = await fetch('https://typescriptlang.org'); + return res(result); + } + catch (err) { + return rej(err); + } +} +function res(result){ + console.log(result); +} +function rej(err){ + console.log(err); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRef.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRef.ts new file mode 100644 index 0000000000000..10361219188b9 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRef.ts @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(res); +} +function res(result){ + return result.ok; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + let result = await fetch('https://typescriptlang.org'); + return res(result); +} +function res(result){ + return result.ok; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRefNoReturnVal.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRefNoReturnVal.ts new file mode 100644 index 0000000000000..851884ac7d7a0 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRefNoReturnVal.ts @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise { + return fetch('https://typescriptlang.org').then(res); +} +function res(result){ + console.log(result); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise { + let result = await fetch('https://typescriptlang.org'); + return res(result); +} +function res(result){ + console.log(result); +} diff --git a/tests/cases/fourslash/codeFixPromiseToAsync.ts b/tests/cases/fourslash/codeFixPromiseToAsync.ts deleted file mode 100644 index 4fe3c9310f4ea..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsync.ts +++ /dev/null @@ -1,22 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(result => { console.log(result); }); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - let result = await fetch('http://yahoo.com'); - console.log(result); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts b/tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts deleted file mode 100644 index d1f57f2024427..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncArrowFunction.ts +++ /dev/null @@ -1,23 +0,0 @@ -/// - -// @target: es6 - -////[|():Promise => {/**/|] -//// return fetch('http://yahoo.com').then(result => console.log(result)); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async ():Promise => { - let result = await fetch('http://yahoo.com'); - return console.log(result); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts deleted file mode 100644 index 51532a46183ef..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch.ts +++ /dev/null @@ -1,28 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(result => { console.log(result); }).catch(err => { console.log(err); }); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - try { - let result = await fetch('http://yahoo.com'); - console.log(result); - } - catch (err) { - console.log(err); - } -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts deleted file mode 100644 index 8b837bf557d4c..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRej.ts +++ /dev/null @@ -1,33 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) }); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - try { - try { - let result = await fetch('http://yahoo.com'); - console.log(result); - } - catch (rejection) { - console.log("rejected:", rejection); - } - } - catch (err) { - console.log(err); - } -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts deleted file mode 100644 index e13de3d18bb4b..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchAndRejRef.ts +++ /dev/null @@ -1,56 +0,0 @@ -/// - -// @target: es6 - -////function [|f|](): Promise { -//// return fetch('http://yahoo.com').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); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f(): Promise { - try { - try { - let result = await fetch('http://yahoo.com'); - 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); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts deleted file mode 100644 index 4bf2dc495158c..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatchRef.ts +++ /dev/null @@ -1,41 +0,0 @@ -/// - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(res).catch(catch_err) -////} -//// -////function res(result){ -//// console.log(result); -////} -//// -////function catch_err(err){ -//// console.log(err); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - try { - let result = await fetch('http://yahoo.com'); - return res(result); - } - catch (err) { - return catch_err(err); - } -} - -function res(result){ - console.log(result); -} - -function catch_err(err){ - console.log(err); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts deleted file mode 100644 index 05c8dec6f37e4..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncCatch_noBrackets.ts +++ /dev/null @@ -1,27 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(result => console.log(result)).catch(err => console.log(err)); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - try { - let result = await fetch('http://yahoo.com'); - return console.log(result); - } - catch (err) { - return console.log(err); - } -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts deleted file mode 100644 index e26fca8145988..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs1.ts +++ /dev/null @@ -1,22 +0,0 @@ -/// - -// @target: es6 - -////function [|f|](): Promise { -//// return fetch('http://yahoo.com').then( _ => { console.log(done); }); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f(): Promise { - await fetch('http://yahoo.com'); - console.log(done); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts b/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts deleted file mode 100644 index 9b6efec0da042..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncIgnoreArgs2.ts +++ /dev/null @@ -1,22 +0,0 @@ -/// - -// @target: es6 - -////function [|f|](): Promise { -//// return fetch('http://yahoo.com').then( () => console.log(done) ); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f(): Promise { - await fetch('http://yahoo.com'); - return console.log(done); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts deleted file mode 100644 index 9eb576343b611..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMethod.ts +++ /dev/null @@ -1,27 +0,0 @@ -/// - -// @target: es6 - -////class Parser { -//// function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(result => console.log(result)); -//// } -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`class Parser { - async function f():Promise { - let result = await fetch('http://yahoo.com'); - return console.log(result); - } -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts deleted file mode 100644 index 29e1b95e458f6..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleCatchs.ts +++ /dev/null @@ -1,32 +0,0 @@ -/// - -// @target: es6 - -////function [|f|](): Promise { -//// return fetch('http://yahoo.com').then(res => console.log(res)).catch(err => console.log("err", err)).catch(err2 => console.log("err2", err2)); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f(): Promise { - try { - try { - let res = await fetch('http://yahoo.com'); - return console.log(res); - } - catch (err) { - return console.log("err", err); - } - } - catch (err2) { - return console.log("err2", err2); - } -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts deleted file mode 100644 index a1250dada0f64..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThens.ts +++ /dev/null @@ -1,39 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(res).then(res2); -////} -//// -////function res(result){ -//// return result.ok; -////} -//// -////function res2(result2){ -//// console.log(result2); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - let result = await fetch('http://yahoo.com'); - let result2 = await res(result); - return res2(result2); -} - -function res(result){ - return result.ok; -} - -function res2(result2){ - console.log(result2); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts b/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts deleted file mode 100644 index aa28729845d56..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncMultipleThensSameVarName.ts +++ /dev/null @@ -1,39 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(res).then(res2); -////} -//// -////function res(result){ -//// return result.ok; -////} -//// -////function res2(result){ -//// console.log(result); -///} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - let result = await fetch('http://yahoo.com'); - let temp = await res(result); - return res2(temp); -} - -function res(result){ - return result.ok; -} - -function res2(result){ - console.log(result); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts deleted file mode 100644 index 9a8c152009a3a..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncNoRes.ts +++ /dev/null @@ -1,25 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(null, rejection => console.log("rejected:", rejection); ); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - try{ - await fetch('http://yahoo.com'); - }catch(rejection){ - retrun console.log("rejected", rejection); - } -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts b/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts deleted file mode 100644 index 394a470f3710a..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncNoSuggestion.ts +++ /dev/null @@ -1,9 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com') -////} - -verify.getSuggestionDiagnostics([]); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts b/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts deleted file mode 100644 index 52b315f86fe88..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotAll.ts +++ /dev/null @@ -1,24 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return Promise.all([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(function(vals){ -//// vals.forEach(console.log); -//// }) -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - let vals = await Promise.all([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); - return vals.forEach(console.log); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts b/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts deleted file mode 100644 index 05fd06c6b3420..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncPromiseDotRace.ts +++ /dev/null @@ -1,24 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return Promise.race([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(val => console.log(val)); -////} - - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: - -`async function f():Promise { - let val = await Promise.race([fetch('http://yahoo.com'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); - return console.log(val); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts deleted file mode 100644 index d3b705874f7bb..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej.ts +++ /dev/null @@ -1,27 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - try { - let result = await fetch('http://yahoo.com'); - console.log(result); - } - catch (rejection) { - console.log("rejected:", rejection); - } -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts deleted file mode 100644 index 631bae21af20c..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRejRef.ts +++ /dev/null @@ -1,43 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(res, rej); -////} -//// -////function res(result){ -//// console.log(result); -////} -//// -////function rej(err){ -//// console.log(err); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - try { - let result = await fetch('http://yahoo.com'); - return res(result); - } - catch (err) { - return rej(err); - } -} - -function res(result){ - console.log(result); -} - -function rej(err){ - console.log(err); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts deleted file mode 100644 index a0a89a6a091f5..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncRej_NoBrackets.ts +++ /dev/null @@ -1,27 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(result => console.log(result), rejection => console.log("rejected:", rejection)); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - try { - let result = await fetch('http://yahoo.com'); - return console.log(result); - } - catch (rejection) { - return console.log("rejected:", rejection); - } -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts deleted file mode 100644 index 67fc6a79c802d..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef.ts +++ /dev/null @@ -1,30 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(res); -////} -//// -////function res(result){ -//// return result.ok; -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - let result = await fetch('http://yahoo.com'); - return res(result); -} - -function res(result){ - return result.ok; -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts b/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts deleted file mode 100644 index 3109e975010cd..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsyncResRef_NoReturnVal.ts +++ /dev/null @@ -1,30 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(res); -////} -//// -////function res(result){ -//// console.log(result); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - let result = await fetch('http://yahoo.com'); - return res(result); -} - -function res(result){ - console.log(result); -}`, -}); diff --git a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts b/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts deleted file mode 100644 index f2b80df0cc0d0..0000000000000 --- a/tests/cases/fourslash/codeFixPromiseToAsync_NoBrackets.ts +++ /dev/null @@ -1,23 +0,0 @@ -/// - -// @target: es6 - -////function [|f|]():Promise { -//// return fetch('http://yahoo.com').then(result => console.log(result)); -////} - -verify.getSuggestionDiagnostics([{ - message: "This may be converted to use async and await.", - code: 80006, -}]); - - -verify.codeFix({ - description: "Convert to use async and await", - index: 0, - newFileContent: -`async function f():Promise { - let result = await fetch('http://yahoo.com'); - return console.log(result); -}`, -}); From a57c6d090001474e8859e0d9798dac0979766af9 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 27 Jun 2018 14:34:26 -0700 Subject: [PATCH 072/196] Fixed to not pass argName to different scopes (catch -> try scope) --- src/services/codefixes/convertToAsyncFunction.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index fdb87f630847c..1f3d12b5f8e21 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -62,7 +62,7 @@ namespace ts.codefix { const func = getSynthesizedDeepClone(node.arguments[0]); let argName = getArgName(func, checker, usedNames); - const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames, argName)); + const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames)); let [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, usedNames); const catchClause = createCatchClause(argNameNew, createBlock(callbackBody)); @@ -88,10 +88,13 @@ namespace ts.codefix { return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } - else { + else if (res) { let [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames); return parseCallback(node.expression, checker, usedNames, argNameResNew).concat(callbackBody); } + else { + return parseCallback(node.expression, checker, usedNames); + } } function parsePromiseCall(node: CallExpression, usedNames: string[], checker: TypeChecker, argName: string): Statement[] { @@ -123,6 +126,9 @@ namespace ts.codefix { function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, usedNames: string[], isRej = false): [NodeArray, string] { switch (func.kind) { case SyntaxKind.Identifier: + if (!argName) { + break; + } let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); const nextDotThen = getNextDotThen(parent, checker); if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { @@ -147,6 +153,7 @@ namespace ts.codefix { return [createNodeArray([createReturn((func).body as Expression)]), argName] } } + return [createNodeArray([]), ""]; } function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { From fc2c9be455d63d1d8b0e4c48b2efa4331dc82445 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 27 Jun 2018 14:51:49 -0700 Subject: [PATCH 073/196] Added tests for finally --- .../unittests/convertToAsyncFunction.ts | 19 +++++++++++++++++++ .../convertToAsyncFunction_Finally1.ts | 11 +++++++++++ .../convertToAsyncFunction_Finally2.ts | 11 +++++++++++ .../convertToAsyncFunction_Finally3.ts | 11 +++++++++++ 4 files changed, 52 insertions(+) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally1.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally2.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally3.ts diff --git a/src/harness/unittests/convertToAsyncFunction.ts b/src/harness/unittests/convertToAsyncFunction.ts index 78ca2c225edd8..384ea4bd9dc0d 100644 --- a/src/harness/unittests/convertToAsyncFunction.ts +++ b/src/harness/unittests/convertToAsyncFunction.ts @@ -373,9 +373,28 @@ function res(result){ function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result)); } +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_Finally1", ` +function [#|finallyTest|](): Promise { + return fetch("https://typescriptlang.org").then(res => console.log(res)).catch(rej => console.log("error", rej)).finally(console.log("finally!")); +} +` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_Finally2", ` +function [#|finallyTest|](): Promise { + return fetch("https://typescriptlang.org").then(res => console.log(res)).finally(console.log("finally!")); +} ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_Finally3", ` +function [#|finallyTest|](): Promise { + return fetch("https://typescriptlang.org").finally(console.log("finally!")); +} +` + ); }); function _testConvertToAsyncFunction(caption: string, text: string, includeLib?: boolean) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally1.ts new file mode 100644 index 0000000000000..c232991b37790 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally1.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +function /*[#|*/finallyTest/*|]*/(): Promise { + return fetch("https://typescriptlang.org").then(res => console.log(res)).catch(rej => console.log("error", rej)).finally(console.log("finally!")); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function finallyTest(): Promise { + return fetch("https://typescriptlang.org").then(res => console.log(res)).catch(rej => console.log("error", rej)).finally(console.log("finally!")); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally2.ts new file mode 100644 index 0000000000000..984f54da87724 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally2.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +function /*[#|*/finallyTest/*|]*/(): Promise { + return fetch("https://typescriptlang.org").then(res => console.log(res)).finally(console.log("finally!")); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function finallyTest(): Promise { + return fetch("https://typescriptlang.org").then(res => console.log(res)).finally(console.log("finally!")); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally3.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally3.ts new file mode 100644 index 0000000000000..8f7dfd953a831 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally3.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +function /*[#|*/finallyTest/*|]*/(): Promise { + return fetch("https://typescriptlang.org").finally(console.log("finally!")); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function finallyTest(): Promise { + return fetch("https://typescriptlang.org").finally(console.log("finally!")); +} From 7ba13d82cb8e6ee4799a291a02235b794d2db76f Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 27 Jun 2018 14:56:01 -0700 Subject: [PATCH 074/196] Fixed linting errors --- .../unittests/convertToAsyncFunction.ts | 6 +-- .../codefixes/convertToAsyncFunction.ts | 38 +++++++++---------- src/services/suggestionDiagnostics.ts | 6 +-- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/harness/unittests/convertToAsyncFunction.ts b/src/harness/unittests/convertToAsyncFunction.ts index 384ea4bd9dc0d..bf653cc44ff00 100644 --- a/src/harness/unittests/convertToAsyncFunction.ts +++ b/src/harness/unittests/convertToAsyncFunction.ts @@ -120,7 +120,7 @@ namespace ts { } const f = { - path: path, + path, content: t.source }; @@ -138,7 +138,7 @@ namespace ts { preferences: defaultPreferences, host: notImplementedHost, formatContext: formatting.getFormatContext(formatOptions) - } + }; const diagnostics = languageService.getSuggestionDiagnostics(f.path); const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === description.message); @@ -152,7 +152,7 @@ namespace ts { data.push(`// ==ORIGINAL==`); data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/")); const changes = action.changes; - assert.lengthOf(changes, 1) + assert.lengthOf(changes, 1); data.push(`// ==ASYNC FUNCTION::${action.description}==`); const newText = textChanges.applyChanges(sourceFile.text, changes[0].textChanges); diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 1f3d12b5f8e21..f275a97fa2b60 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -17,12 +17,12 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); - //const stmts: NodeArray = (funcToConvert.body).statements; + // const stmts: NodeArray = (funcToConvert.body).statements; const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvert); for (const stmt of retStmts) { forEachChild(stmt, function visit(node: Node) { if (node.kind === SyntaxKind.CallExpression) { - const usedNames: string[] = [] + const usedNames: string[] = []; const newNode = parseCallback(node as CallExpression, checker, usedNames); if (newNode) { changes.replaceNodeWithNodes(sourceFile, stmt, newNode); @@ -60,11 +60,11 @@ namespace ts.codefix { function parseCatch(node: CallExpression, checker: TypeChecker, usedNames: string[]): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); - let argName = getArgName(func, checker, usedNames); + const argName = getArgName(func, checker, usedNames); const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames)); - let [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, usedNames); + const [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, usedNames); const catchClause = createCatchClause(argNameNew, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; @@ -74,22 +74,22 @@ namespace ts.codefix { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier - let argNameRes = getArgName(res, checker, usedNames); + const argNameRes = getArgName(res, checker, usedNames); if (rej) { - const usedCatchNames: string[] = [] + const usedCatchNames: string[] = []; const argNameRej = getArgName(rej, checker, usedCatchNames); - let [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames); + const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames); const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames, argNameResNew).concat(callbackBody)); - let [callbackBody2, argNameRejNew] = getCallbackBody(rej, argNameRej, node, checker, usedCatchNames, /* isRej */ true); + const [callbackBody2, argNameRejNew] = getCallbackBody(rej, argNameRej, node, checker, usedCatchNames, /* isRej */ true); const catchClause = createCatchClause(argNameRejNew, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - let [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames); + const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames); return parseCallback(node.expression, checker, usedNames, argNameResNew).concat(callbackBody); } else { @@ -99,11 +99,11 @@ namespace ts.codefix { function parsePromiseCall(node: CallExpression, usedNames: string[], checker: TypeChecker, argName: string): Statement[] { let localArgName = argName; - if (!localArgName){ + if (!localArgName) { localArgName = getArgName(node, checker, usedNames); - } + } - usedNames.push(localArgName) + usedNames.push(localArgName); const varDecl = createVariableDeclaration(localArgName, /*type*/ undefined, createAwait(node)); return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; @@ -134,13 +134,12 @@ namespace ts.codefix { if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { return [createNodeArray([createReturn(synthCall)]), argName]; } - + const tempArgName = getArgName(nextDotThen.arguments[0], checker, usedNames); usedNames.push(tempArgName); argName = getArgName(func, checker, usedNames); synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); - return [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]), argName]; case SyntaxKind.FunctionDeclaration: @@ -150,7 +149,7 @@ namespace ts.codefix { return [(func).body.statements, argName]; } else { - return [createNodeArray([createReturn((func).body as Expression)]), argName] + return [createNodeArray([createReturn((func).body as Expression)]), argName]; } } return [createNodeArray([]), ""]; @@ -163,7 +162,6 @@ namespace ts.codefix { return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); } - function getArgName(funcNode: Node, checker: TypeChecker, usedNames: string[]): string { let name; @@ -175,15 +173,15 @@ namespace ts.codefix { } else if (checker.getTypeAtLocation(funcNode).getCallSignatures().length > 0 && checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters.length > 0) { name = checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters[0].name; - if (!isArgUsed(name, usedNames)){ + if (!isArgUsed(name, usedNames)) { return name; } } - else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])){ + else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { return (funcNode.arguments[0]).text; } - if (name === undefined || name === "_"){ + if (name === undefined || name === "_") { return undefined; } @@ -196,7 +194,7 @@ namespace ts.codefix { function isArgUsed(name: string, usedNames: string[]): boolean { - let nameUsed: string[] = usedNames.filter(element => element === name); + const nameUsed: string[] = usedNames.filter(element => element === name); return nameUsed.length > 0; } } \ No newline at end of file diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 37c4341555d4b..28bb1f6345149 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -36,8 +36,8 @@ namespace ts { break; } - if(isFunctionLikeDeclaration(node) && !node.body){ - break; //break on ambient functions + if (isFunctionLikeDeclaration(node) && !node.body) { + break; // break on ambient functions } if (checker.isPromiseLikeType(returnType)) { @@ -163,7 +163,7 @@ namespace ts { } function hasCallback(child: Node) { - if (isCallExpression(child) && isPropertyAccessExpression(child.expression) && + if (isCallExpression(child) && isPropertyAccessExpression(child.expression) && (child.expression.name.text === "then" || child.expression.name.text === "catch")) { retStmts.push(node as ReturnStatement); } From 28aef075b048fc625ca732b7e465b13f5729f925 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 28 Jun 2018 10:38:39 -0700 Subject: [PATCH 075/196] Fixed globalPromiseType bug --- src/compiler/checker.ts | 6 +++ src/compiler/diagnosticMessages.json | 6 ++- src/compiler/types.ts | 4 ++ .../codefixes/convertToAsyncFunction.ts | 42 ++++++++++++------- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d194fc983a664..1d65b979b52c8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11817,6 +11817,12 @@ namespace ts { !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, globalPromiseType); } + function isPromiseLikeType(type: Type): boolean { + const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ false); // this is only called from the language service, so don't report errors if the promise type doesn't exist + return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalPromiseType || + !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, globalPromiseType); + } + function isArrayLikeType(type: Type): boolean { // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, // or if it is not the undefined or null type and if it is assignable to ReadonlyArray diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 2e8f9c33f2dc3..f9bb3737527aa 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3969,6 +3969,10 @@ "code": 80006 }, + "This may be converted to an async function.": { + "category": "Suggestion", + "code": 80006 + }, "Add missing 'super()' call": { "category": "Message", "code": 90001 @@ -4293,4 +4297,4 @@ "category": "Message", "code": 95056 } -} +} \ No newline at end of file diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 952c87ff8e5d5..225acb5c5b7c6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3047,6 +3047,10 @@ namespace ts { */ /* @internal */ isArrayLikeType(type: Type): boolean; /* @internal */ isPromiseLikeType(type: Type): boolean; +<<<<<<< HEAD +======= + +>>>>>>> 4c82130bb6... Fixed globalPromiseType bug /* @internal */ getAllPossiblePropertiesOfTypes(type: ReadonlyArray): Symbol[]; /* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags, excludeGlobals: boolean): Symbol | undefined; /* @internal */ getJsxNamespace(location?: Node): string; diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index f275a97fa2b60..3b5c3d16e4cfb 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -36,12 +36,17 @@ namespace ts.codefix { } function returnsAPromise(node: CallExpression, checker: TypeChecker): boolean { - return checker.isPromiseLikeType(checker.getTypeAtLocation(node)) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); + const nodeType = checker.getTypeAtLocation(node); + if (!nodeType) { + return false; + } + + return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } function parseCallback(node: Expression, checker: TypeChecker, usedNames: string[], argName?: string): Statement[] { if (!node) { - return; + return []; } if (node.kind === SyntaxKind.CallExpression && returnsAPromise(node as CallExpression, checker)) { @@ -56,9 +61,12 @@ namespace ts.codefix { else if (node.kind === SyntaxKind.PropertyAccessExpression) { return parseCallback((node).expression, checker, usedNames, argName); } + + return []; } function parseCatch(node: CallExpression, checker: TypeChecker, usedNames: string[]): Statement[] { + //const func = getSynthesizedDeepClone(node.arguments[0]); const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, usedNames); @@ -97,7 +105,7 @@ namespace ts.codefix { } } - function parsePromiseCall(node: CallExpression, usedNames: string[], checker: TypeChecker, argName: string): Statement[] { + function parsePromiseCall(node: CallExpression, usedNames: string[], checker: TypeChecker, argName: string | undefined): Statement[] { let localArgName = argName; if (!localArgName) { localArgName = getArgName(node, checker, usedNames); @@ -109,7 +117,7 @@ namespace ts.codefix { return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; } - function getNextDotThen(node: Expression, checker: TypeChecker): CallExpression { + function getNextDotThen(node: Expression, checker: TypeChecker): CallExpression | undefined { if (!node || !node.parent) { return undefined; } @@ -144,13 +152,12 @@ namespace ts.codefix { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - if ((func).body.kind === SyntaxKind.Block) { - return [(func).body.statements, argName]; - } - else { - return [createNodeArray([createReturn((func).body as Expression)]), argName]; + if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { + return [func.body.statements, argName]; } + break; + case SyntaxKind.ArrowFunction: + return [createNodeArray([createReturn((func).body as Expression)]), argName]; } return [createNodeArray([]), ""]; } @@ -159,11 +166,18 @@ namespace ts.codefix { if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; } - return (node.expression).name.text === funcName && checker.isPromiseLikeType(checker.getTypeAtLocation(node)); + + const nodeType = checker.getTypeAtLocation(node); + if (!nodeType) { + return false; + } + + return (node.expression).name.text === funcName && checker.isPromiseLikeType(nodeType); } function getArgName(funcNode: Node, checker: TypeChecker, usedNames: string[]): string { let name; + const funcNodeType = checker.getTypeAtLocation(funcNode); if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { name = (funcNode.parameters[0].name).text; @@ -171,8 +185,8 @@ namespace ts.codefix { return name; } } - else if (checker.getTypeAtLocation(funcNode).getCallSignatures().length > 0 && checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters.length > 0) { - name = checker.getTypeAtLocation(funcNode).getCallSignatures()[0].parameters[0].name; + else if (funcNodeType && funcNodeType.getCallSignatures().length > 0 && funcNodeType.getCallSignatures()[0].parameters.length > 0) { + name = funcNodeType.getCallSignatures()[0].parameters[0].name; if (!isArgUsed(name, usedNames)) { return name; } @@ -182,7 +196,7 @@ namespace ts.codefix { } if (name === undefined || name === "_") { - return undefined; + return ""; } return name + "_" + getArgName.varNameItr; From 81cd5254e379ff35b7f15e549e656795fc605633 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 29 Jun 2018 10:26:39 -0700 Subject: [PATCH 076/196] Added test cases for promises in a .then() and conflicting variable names in callbacks --- .../unittests/convertToAsyncFunction.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/harness/unittests/convertToAsyncFunction.ts b/src/harness/unittests/convertToAsyncFunction.ts index bf653cc44ff00..1246fce9d2e6c 100644 --- a/src/harness/unittests/convertToAsyncFunction.ts +++ b/src/harness/unittests/convertToAsyncFunction.ts @@ -393,6 +393,31 @@ function [#|finallyTest|](): Promise { function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").finally(console.log("finally!")); } +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromise", ` +function innerPromise(): Promise { + var blob = fetch("https://typescriptlang.org").then(resp => { + var blob2 = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error: ${err}'); + retrun blob2; + }).then(blob => { + return blob.toString(); + }); + + return blob; +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerVarNameConflict", ` +function f(): Promise { + var blob = fetch("https://typescriptlang.org").then(resp => { + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error: ${err}'); + }).then(blob => { + return blob.toString(); + }); + + return blob; +} ` ); }); From 182648aaf3edb4b7dc6ff0f6f66ff7258df50c3d Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 29 Jun 2018 12:42:37 -0700 Subject: [PATCH 077/196] Cherry-pick fixes --- src/compiler/checker.ts | 7 ------- src/compiler/types.ts | 4 ---- 2 files changed, 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1d65b979b52c8..22eecbee794ed 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -420,7 +420,6 @@ namespace ts { let globalObjectType: ObjectType; let globalFunctionType: ObjectType; let globalArrayType: GenericType; - let globalPromiseType: GenericType; let globalReadonlyArrayType: GenericType; let globalStringType: ObjectType; let globalNumberType: ObjectType; @@ -11812,11 +11811,6 @@ namespace ts { return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalArrayType; } - function isPromiseLikeType(type: Type): boolean { - return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalPromiseType /*|| (type).target === globalReadonlyArrayType)*/ || - !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, globalPromiseType); - } - function isPromiseLikeType(type: Type): boolean { const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ false); // this is only called from the language service, so don't report errors if the promise type doesn't exist return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalPromiseType || @@ -27041,7 +27035,6 @@ namespace ts { getSymbolLinks(unknownSymbol).type = unknownType; // Initialize special types - globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true); globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true); globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 225acb5c5b7c6..952c87ff8e5d5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3047,10 +3047,6 @@ namespace ts { */ /* @internal */ isArrayLikeType(type: Type): boolean; /* @internal */ isPromiseLikeType(type: Type): boolean; -<<<<<<< HEAD -======= - ->>>>>>> 4c82130bb6... Fixed globalPromiseType bug /* @internal */ getAllPossiblePropertiesOfTypes(type: ReadonlyArray): Symbol[]; /* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags, excludeGlobals: boolean): Symbol | undefined; /* @internal */ getJsxNamespace(location?: Node): string; From aed1c73d93aa085bd22313ad73c789267d1fbe23 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 29 Jun 2018 13:44:00 -0700 Subject: [PATCH 078/196] merge - good state --- .../codefixes/convertToAsyncFunction.ts | 2 +- src/services/suggestionDiagnostics.ts | 21 +++++++++----- src/testRunner/tsconfig.json | 1 + .../unittests/convertToAsyncFunction.ts | 28 +++++++++---------- 4 files changed, 29 insertions(+), 23 deletions(-) rename src/{harness => testRunner}/unittests/convertToAsyncFunction.ts (94%) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 3b5c3d16e4cfb..6c6fe5bfc7e58 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -12,7 +12,7 @@ namespace ts.codefix { }); function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { // get the function declaration - returns a promise - const funcToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position, /*includeEndPosition*/ false)) as FunctionLikeDeclaration; + const funcToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position)) as FunctionLikeDeclaration; // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 72f1a29a07cba..454343d90adbd 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -3,6 +3,7 @@ namespace ts { export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] { program.getSemanticDiagnostics(sourceFile, cancellationToken); const diags: DiagnosticWithLocation[] = []; + const checker = program.getDiagnosticsProducingTypeChecker(); if (sourceFile.commonJsModuleIndicator && (programContainsEs6Modules(program) || compilerOptionsIndicateEs6Modules(program.getCompilerOptions())) && @@ -69,7 +70,7 @@ namespace ts { } if(isFunctionLikeDeclaration(node) || isArrowFunction(node) || isMethodDeclaration(node)){ - addConvertToAsyncFunctionDiagnostics() + addConvertToAsyncFunctionDiagnostics(node, checker, diags) } node.forEachChild(check); } @@ -112,18 +113,24 @@ namespace ts { } } - function addConvertToAsyncFunctionDiagnostics(node: Node, checker: TypeChecker, diags: DiagnosticsWithLocation[]): void{ - if(isAsyncFunction(node)){ - break; + function addConvertToAsyncFunctionDiagnostics(node: Node, checker: TypeChecker, diags: DiagnosticWithLocation[]): void{ + if (isAsyncFunction(node)) { + return; } - const returnType = checker.getReturnTypeOfSignature(checker.getSignatureFromDeclaration(node)); + const signature = checker.getSignatureFromDeclaration(node) + if (!signature) { + return; + } + + + const returnType = checker.getReturnTypeOfSignature(signature); if (!returnType || !returnType.symbol) { - break; + return; } if (isFunctionLikeDeclaration(node) && !node.body) { - break; // break on ambient functions + return; // break on ambient functions } if (checker.isPromiseLikeType(returnType)) { diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 3943d4f4f85ca..7fcc292a293bb 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -48,6 +48,7 @@ "unittests/compileOnSave.ts", "unittests/configurationExtension.ts", "unittests/convertCompilerOptionsFromJson.ts", + "unittests/convertToAsyncFunction.ts", "unittests/convertToBase64.ts", "unittests/convertTypeAcquisitionFromJson.ts", "unittests/customTransforms.ts", diff --git a/src/harness/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts similarity index 94% rename from src/harness/unittests/convertToAsyncFunction.ts rename to src/testRunner/unittests/convertToAsyncFunction.ts index 1246fce9d2e6c..8ff2bdcdd4fe0 100644 --- a/src/harness/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -1,6 +1,3 @@ -/// -/// - namespace ts { interface Range { pos: number; @@ -34,7 +31,7 @@ namespace ts { const name = s === e ? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted" : source.substring(s, e); - activeRanges.push({ name, pos: text.length, end: undefined }); + activeRanges.push({ name, pos: text.length, end: undefined! }); lastPos = pos; continue; } @@ -45,7 +42,7 @@ namespace ts { else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) { text += source.substring(lastPos, pos); activeRanges[activeRanges.length - 1].end = text.length; - const range = activeRanges.pop(); + const range = activeRanges.pop()!; if (range.name in ranges) { throw new Error(`Duplicate name of range ${range.name}`); } @@ -101,7 +98,7 @@ namespace ts { function testConvertToAsyncFunction(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage, includeLib?: boolean) { const t = getTest(text); - const selectionRange = t.ranges.get("selection"); + const selectionRange = t.ranges.get("selection")!; if (!selectionRange) { throw new Error(`Test ${caption} does not specify selection range`); } @@ -111,7 +108,7 @@ namespace ts { function runBaseline(extension: Extension) { const path = "/a" + extension; - const program = makeProgram({ path, content: t.source }, includeLib); + const program = makeProgram({ path, content: t.source }, includeLib)!; if (hasSyntacticDiagnostics(program)) { // Don't bother generating JS baselines for inputs that aren't valid JS. @@ -124,7 +121,7 @@ namespace ts { content: t.source }; - const sourceFile = program.getSourceFile(path); + const sourceFile = program.getSourceFile(path)!; const host = projectSystem.createServerHost([f, projectSystem.libFile]); const projectService = projectSystem.createProjectService(host); projectService.openClientFile(f.path); @@ -144,8 +141,9 @@ namespace ts { const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === description.message); assert.isNotNull(diagnostic); - const actions = codefix.getFixes(context); - const action = find(actions, action => action.description === description.message); + const actions = codefix.getFixes(context) + const action = find(actions, action => action.description === description.message)!; + assert.isNotNull(action); Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, () => { const data: string[] = []; @@ -158,7 +156,7 @@ namespace ts { const newText = textChanges.applyChanges(sourceFile.text, changes[0].textChanges); data.push(newText); - const diagProgram = makeProgram({ path, content: newText }, includeLib); + const diagProgram = makeProgram({ path, content: newText }, includeLib)!; assert.isFalse(hasSyntacticDiagnostics(diagProgram)); return data.join(newLineCharacter); }); @@ -396,9 +394,9 @@ function [#|finallyTest|](): Promise { ` ); _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromise", ` -function innerPromise(): Promise { +function [#|innerPromise|](): Promise { var blob = fetch("https://typescriptlang.org").then(resp => { - var blob2 = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error: ${err}'); + var blob2 = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); retrun blob2; }).then(blob => { return blob.toString(); @@ -409,9 +407,9 @@ function innerPromise(): Promise { ` ); _testConvertToAsyncFunction("convertToAsyncFunction_InnerVarNameConflict", ` -function f(): Promise { +function [#|f|](): Promise { var blob = fetch("https://typescriptlang.org").then(resp => { - var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error: ${err}'); + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); }).then(blob => { return blob.toString(); }); From b23946e5545af40b439e2b2802f89b094d39a9d8 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 29 Jun 2018 16:30:27 -0700 Subject: [PATCH 079/196] Fixed casting to use isType() functions --- .../codefixes/convertToAsyncFunction.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 6c6fe5bfc7e58..629ca78b336e6 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -24,7 +24,7 @@ namespace ts.codefix { if (node.kind === SyntaxKind.CallExpression) { const usedNames: string[] = []; const newNode = parseCallback(node as CallExpression, checker, usedNames); - if (newNode) { + if (newNode.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNode); } } @@ -49,24 +49,23 @@ namespace ts.codefix { return []; } - if (node.kind === SyntaxKind.CallExpression && returnsAPromise(node as CallExpression, checker)) { - return parsePromiseCall(node as CallExpression, usedNames, checker, argName); + if (isCallExpression(node) && returnsAPromise(node, checker)) { + return parsePromiseCall(node, usedNames, checker, argName); } - else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { - return parseThen(node as CallExpression, checker, usedNames); + else if (isCallExpression(node) && isCallback(node, "then", checker)) { + return parseThen(node, checker, usedNames); } - else if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "catch", checker)) { - return parseCatch(node as CallExpression, checker, usedNames); + else if (isCallExpression(node) && isCallback(node, "catch", checker)) { + return parseCatch(node, checker, usedNames); } - else if (node.kind === SyntaxKind.PropertyAccessExpression) { - return parseCallback((node).expression, checker, usedNames, argName); + else if (isPropertyAccessExpression(node)) { + return parseCallback(node.expression, checker, usedNames, argName); } return []; } function parseCatch(node: CallExpression, checker: TypeChecker, usedNames: string[]): Statement[] { - //const func = getSynthesizedDeepClone(node.arguments[0]); const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, usedNames); @@ -152,12 +151,13 @@ namespace ts.codefix { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { return [func.body.statements, argName]; + }else if(isArrowFunction(func)){ + return [createNodeArray([createReturn((func).body as Expression)]), argName]; } break; - case SyntaxKind.ArrowFunction: - return [createNodeArray([createReturn((func).body as Expression)]), argName]; } return [createNodeArray([]), ""]; } From 99bc200ce51dd3d5c037dbb1f325ba10bdf1f74a Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 2 Jul 2018 16:00:03 -0700 Subject: [PATCH 080/196] initial commit of nested inner callbacks --- .../codefixes/convertToAsyncFunction.ts | 64 +++++++++++++------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 629ca78b336e6..8c6394885f6cc 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -21,9 +21,9 @@ namespace ts.codefix { const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvert); for (const stmt of retStmts) { forEachChild(stmt, function visit(node: Node) { - if (node.kind === SyntaxKind.CallExpression) { + if (isCallExpression(node)) { const usedNames: string[] = []; - const newNode = parseCallback(node as CallExpression, checker, usedNames); + const newNode = parseCallback(node, checker, usedNames, node); if (newNode.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNode); } @@ -44,7 +44,7 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, usedNames: string[], argName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, usedNames: string[], outermostParent: CallExpression, argName?: string): Statement[] { if (!node) { return []; } @@ -53,31 +53,31 @@ namespace ts.codefix { return parsePromiseCall(node, usedNames, checker, argName); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, usedNames); + return parseThen(node, checker, usedNames, outermostParent); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, usedNames); + return parseCatch(node, checker, usedNames, outermostParent); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, usedNames, argName); + return parseCallback(node.expression, checker, usedNames, outermostParent, argName); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, usedNames: string[]): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, usedNames: string[], outermostParent: CallExpression): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, usedNames); - const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames)); + const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames, outermostParent)); - const [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, usedNames); + const [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, usedNames, outermostParent); const catchClause = createCatchClause(argNameNew, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, usedNames: string[]): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, usedNames: string[], outermostParent: CallExpression): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier @@ -87,20 +87,20 @@ namespace ts.codefix { const usedCatchNames: string[] = []; const argNameRej = getArgName(rej, checker, usedCatchNames); - const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames); - const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames, argNameResNew).concat(callbackBody)); + const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames, outermostParent); + const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames, outermostParent, argNameResNew).concat(callbackBody)); - const [callbackBody2, argNameRejNew] = getCallbackBody(rej, argNameRej, node, checker, usedCatchNames, /* isRej */ true); + const [callbackBody2, argNameRejNew] = getCallbackBody(rej, argNameRej, node, checker, usedCatchNames, outermostParent, /* isRej */ true); const catchClause = createCatchClause(argNameRejNew, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames); - return parseCallback(node.expression, checker, usedNames, argNameResNew).concat(callbackBody); + const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames, outermostParent); + return parseCallback(node.expression, checker, usedNames, outermostParent, argNameResNew).concat(callbackBody); } else { - return parseCallback(node.expression, checker, usedNames); + return parseCallback(node.expression, checker, usedNames, outermostParent); } } @@ -130,7 +130,7 @@ namespace ts.codefix { } } - function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, usedNames: string[], isRej = false): [NodeArray, string] { + function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, usedNames: string[], outermostParent: CallExpression, isRej = false): [NodeArray, string] { switch (func.kind) { case SyntaxKind.Identifier: if (!argName) { @@ -153,9 +153,33 @@ namespace ts.codefix { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - return [func.body.statements, argName]; - }else if(isArrowFunction(func)){ - return [createNodeArray([createReturn((func).body as Expression)]), argName]; + let innerCbBody: Statement[] = []; + for (const stmt of func.body.statements) { + forEachChild(stmt, function visit(node: Node) { + if (isCallExpression(node)) { + let temp = parseCallback(node, checker, usedNames, outermostParent, argName); + innerCbBody = innerCbBody.concat(temp); + if (innerCbBody.length > 0) { + return; + } + } + else if (!isFunctionLike(node)) { + forEachChild(node, visit); + } + }); + } + + if(innerCbBody.length > 0){ + return [createNodeArray(innerCbBody), argName]; + } + + return [func.body.statements, argName]; //TODO: go in here and rename all usages of argName if argName changes + + } else if (isArrowFunction(func)) { + //if there is another outer dot then, don't actually return + const nextOutermostDotThen = getNextDotThen(outermostParent, checker) + return nextOutermostDotThen ? [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(argName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]), argName] + : [createNodeArray([createReturn(func.body as Expression)]), argName]; } break; } From ae5efa4ac0d88530ba9cff9f2d5a5451ff214cb0 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 3 Jul 2018 14:40:32 -0700 Subject: [PATCH 081/196] initial commit of renaming before refactor --- .../codefixes/convertToAsyncFunction.ts | 57 ++++++++++++++++++- src/services/utilities.ts | 35 ++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 8c6394885f6cc..1857528dfa27f 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -17,7 +17,35 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); - // const stmts: NodeArray = (funcToConvert.body).statements; + // rename conflicting variables + let allVarNames: Map<[Symbol]> = new MapCtr(); + forEachChild(funcToConvert, function visit(node: Node) { + if (isIdentifier(node)) { + let symbol = checker.getSymbolAtLocation(node); + if (symbol && allVarNames.get(node.text) && allVarNames.get(node.text)!.filter(elem => elem === symbol).length == 0) { + allVarNames.get(node.text)!.push(symbol); + } + else if (symbol && !allVarNames.get(node.text)) { + allVarNames.set(node.text, [symbol]) + } + } + + forEachChild(node, visit); + }); + + forEachChild(funcToConvert, function visit(node: Node) { + const symbol = isIdentifier(node) ? checker.getSymbolAtLocation(node) : undefined; + + // don't rename the first instance of the variable + if (isIdentifier(node) && symbol && allVarNames.get(node.text) && allVarNames.get(node.text)!.length > 1 && allVarNames.get(node.text)![0] !== symbol) { + getSynthesizedMaybeRenamedDeepClone(node); + } + else { + getSynthesizedDeepClone(node); + } + forEachChild(node, visit); + }); + const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvert); for (const stmt of retStmts) { forEachChild(stmt, function visit(node: Node) { @@ -178,14 +206,37 @@ namespace ts.codefix { } else if (isArrowFunction(func)) { //if there is another outer dot then, don't actually return const nextOutermostDotThen = getNextDotThen(outermostParent, checker) - return nextOutermostDotThen ? [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(argName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]), argName] - : [createNodeArray([createReturn(func.body as Expression)]), argName]; + + if (!nextOutermostDotThen) { + usedNames.push(argName); + renameAllUsages(argName, getArgName(func, checker, usedNames), [func.body]); + } + + return nextOutermostDotThen ? [createNodeArray([createReturn(func.body as Expression)]), argName] + : [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(argName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]), getArgName(func, checker, usedNames)] } break; } return [createNodeArray([]), ""]; } + function renameAllUsages(oldName: string, newName: string, codeToRename: Node[]): Node[] { + let renamedCode: Node[] = []; + for (let stmt of codeToRename){ + forEachChild(stmt, function visit(node: Node){ + if (isIdentifier(node) && node.text === oldName) { + newName; + //change the text name + } + else if (!isFunctionLike(node)){ + forEachChild(node, visit); + } + }); + } + + return renamedCode; + } + function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 300a1a0a2e024..7abfd81b0bac6 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1632,6 +1632,41 @@ namespace ts { return clone; } + export function getSynthesizedMaybeRenamedDeepClone(node: T, includeTrivia = true ): T{ + const clone = node && getSynthesizedMaybeRenamedDeepCloneWorker(node as NonNullable) + if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); + return clone; + } + + function getSynthesizedMaybeRenamedDeepCloneWorker(node: T): T { + const visited = visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); + if (visited === node) { + // This only happens for leaf nodes - internal nodes always see their children change. + let clone; + if (isIdentifier(node)) { + clone = createIdentifier("NEWNAME"); + } + else { + clone = getSynthesizedClone(node); + } + + if (isStringLiteral(clone)) { + clone.textSourceNode = node as any; + } + else if (isNumericLiteral(clone)) { + clone.numericLiteralFlags = (node as any).numericLiteralFlags; + } + return setTextRange(clone as T, node); + } + + // PERF: As an optimization, rather than calling getSynthesizedClone, we'll update + // the new node created by visitEachChild with the extra changes getSynthesizedClone + // would have made. + visited.parent = undefined!; + return visited; + } + + function getSynthesizedDeepCloneWorker(node: T): T { const visited = visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); if (visited === node) { From a030b1ef1c5b6c130c63f0ee02b29a5198b38715 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 5 Jul 2018 10:13:49 -0700 Subject: [PATCH 082/196] initial commit of renaming using getSynthesizedDeepClone --- .../codefixes/convertToAsyncFunction.ts | 115 +++++++++--------- src/services/utilities.ts | 47 ++----- 2 files changed, 68 insertions(+), 94 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 1857528dfa27f..860e20887b18e 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -18,40 +18,32 @@ namespace ts.codefix { changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); // rename conflicting variables - let allVarNames: Map<[Symbol]> = new MapCtr(); + let varNamesMap: Map = new MapCtr(); + let allVarNames: string[] = []; + forEachChild(funcToConvert, function visit(node: Node) { if (isIdentifier(node)) { let symbol = checker.getSymbolAtLocation(node); - if (symbol && allVarNames.get(node.text) && allVarNames.get(node.text)!.filter(elem => elem === symbol).length == 0) { - allVarNames.get(node.text)!.push(symbol); - } - else if (symbol && !allVarNames.get(node.text)) { - allVarNames.set(node.text, [symbol]) + if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { + const numVarsSameName = allVarNames.filter(elem => elem === node.text).length; + allVarNames.push(node.text); + const newName = numVarsSameName === 0 ? node.text : node.text + "_" + numVarsSameName; + varNamesMap.set(String(getSymbolId(symbol)), newName); } - } - - forEachChild(node, visit); + } + forEachChild(node, visit); }); - forEachChild(funcToConvert, function visit(node: Node) { - const symbol = isIdentifier(node) ? checker.getSymbolAtLocation(node) : undefined; - - // don't rename the first instance of the variable - if (isIdentifier(node) && symbol && allVarNames.get(node.text) && allVarNames.get(node.text)!.length > 1 && allVarNames.get(node.text)![0] !== symbol) { - getSynthesizedMaybeRenamedDeepClone(node); - } - else { - getSynthesizedDeepClone(node); - } - forEachChild(node, visit); - }); + let funcToConvertClone = getSynthesizedDeepClone(funcToConvert, true, varNamesMap, checker); + //changes.replaceNode(sourceFile, funcToConvert, funcToConvertClone); - const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvert); + + const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvertClone); for (const stmt of retStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const usedNames: string[] = []; - const newNode = parseCallback(node, checker, usedNames, node); + //const usedNames: string[] = []; + const newNode = parseCallback(node, checker, node); if (newNode.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNode); } @@ -72,73 +64,73 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, usedNames: string[], outermostParent: CallExpression, argName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, /*usedNames: string[],*/ outermostParent: CallExpression, argName?: string): Statement[] { if (!node) { return []; } if (isCallExpression(node) && returnsAPromise(node, checker)) { - return parsePromiseCall(node, usedNames, checker, argName); + return parsePromiseCall(node, /*usedNames,*/ checker, argName); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, usedNames, outermostParent); + return parseThen(node, checker, /*usedNames,*/ outermostParent); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, usedNames, outermostParent); + return parseCatch(node, checker, /*usedNames,*/ outermostParent); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, usedNames, outermostParent, argName); + return parseCallback(node.expression, checker, /*usedNames,*/ outermostParent, argName); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, usedNames: string[], outermostParent: CallExpression): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, /*usedNames: string[],*/ outermostParent: CallExpression): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); - const argName = getArgName(func, checker, usedNames); + const argName = getArgName(func, checker /*, usedNames*/); - const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames, outermostParent)); + const tryBlock = createBlock(parseCallback(node.expression, checker, /*usedNames,*/ outermostParent)); - const [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, usedNames, outermostParent); + const [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, /*usedNames,*/ outermostParent); const catchClause = createCatchClause(argNameNew, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, usedNames: string[], outermostParent: CallExpression): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, /*usedNames: string[],*/ outermostParent: CallExpression): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier - const argNameRes = getArgName(res, checker, usedNames); + const argNameRes = getArgName(res, checker /*, usedNames*/); if (rej) { - const usedCatchNames: string[] = []; - const argNameRej = getArgName(rej, checker, usedCatchNames); + //const usedCatchNames: string[] = []; + const argNameRej = getArgName(rej, checker /*, usedCatchNames*/); - const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames, outermostParent); - const tryBlock = createBlock(parseCallback(node.expression, checker, usedNames, outermostParent, argNameResNew).concat(callbackBody)); + const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, /*usedNames,*/ outermostParent); + const tryBlock = createBlock(parseCallback(node.expression, checker, /*usedNames,*/ outermostParent, argNameResNew).concat(callbackBody)); - const [callbackBody2, argNameRejNew] = getCallbackBody(rej, argNameRej, node, checker, usedCatchNames, outermostParent, /* isRej */ true); + const [callbackBody2, argNameRejNew] = getCallbackBody(rej, argNameRej, node, checker, /*usedCatchNames,*/ outermostParent, /* isRej */ true); const catchClause = createCatchClause(argNameRejNew, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, usedNames, outermostParent); - return parseCallback(node.expression, checker, usedNames, outermostParent, argNameResNew).concat(callbackBody); + const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, /*usedNames,*/ outermostParent); + return parseCallback(node.expression, checker, /*usedNames,*/ outermostParent, argNameResNew).concat(callbackBody); } else { - return parseCallback(node.expression, checker, usedNames, outermostParent); + return parseCallback(node.expression, checker, /*usedNames,*/ outermostParent); } } - function parsePromiseCall(node: CallExpression, usedNames: string[], checker: TypeChecker, argName: string | undefined): Statement[] { + function parsePromiseCall(node: CallExpression, /*usedNames: string[],*/ checker: TypeChecker, argName: string | undefined): Statement[] { let localArgName = argName; if (!localArgName) { - localArgName = getArgName(node, checker, usedNames); + localArgName = getArgName(node, checker /*, usedNames*/); } - usedNames.push(localArgName); + //usedNames.push(localArgName); const varDecl = createVariableDeclaration(localArgName, /*type*/ undefined, createAwait(node)); return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; @@ -158,7 +150,7 @@ namespace ts.codefix { } } - function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, usedNames: string[], outermostParent: CallExpression, isRej = false): [NodeArray, string] { + function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, /*usedNames: string[],*/ outermostParent: CallExpression, isRej = false): [NodeArray, string] { switch (func.kind) { case SyntaxKind.Identifier: if (!argName) { @@ -170,10 +162,10 @@ namespace ts.codefix { return [createNodeArray([createReturn(synthCall)]), argName]; } - const tempArgName = getArgName(nextDotThen.arguments[0], checker, usedNames); - usedNames.push(tempArgName); + const tempArgName = getArgName(nextDotThen.arguments[0], checker/*, usedNames*/); + //usedNames.push(tempArgName); - argName = getArgName(func, checker, usedNames); + argName = getArgName(func, checker/*, usedNames*/); synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); return [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]), argName]; @@ -185,7 +177,7 @@ namespace ts.codefix { for (const stmt of func.body.statements) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - let temp = parseCallback(node, checker, usedNames, outermostParent, argName); + let temp = parseCallback(node, checker, /*usedNames,*/ outermostParent, argName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; @@ -208,12 +200,12 @@ namespace ts.codefix { const nextOutermostDotThen = getNextDotThen(outermostParent, checker) if (!nextOutermostDotThen) { - usedNames.push(argName); - renameAllUsages(argName, getArgName(func, checker, usedNames), [func.body]); + //usedNames.push(argName); + renameAllUsages(argName, getArgName(func, checker/*, usedNames*/), [func.body]); } return nextOutermostDotThen ? [createNodeArray([createReturn(func.body as Expression)]), argName] - : [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(argName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]), getArgName(func, checker, usedNames)] + : [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(argName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]), getArgName(func, checker /*, usedNames*/)] } break; } @@ -250,21 +242,23 @@ namespace ts.codefix { return (node.expression).name.text === funcName && checker.isPromiseLikeType(nodeType); } - function getArgName(funcNode: Node, checker: TypeChecker, usedNames: string[]): string { + function getArgName(funcNode: Node, checker: TypeChecker, /*usedNames: string[]*/): string { let name; const funcNodeType = checker.getTypeAtLocation(funcNode); if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { name = (funcNode.parameters[0].name).text; + /* if (name !== "_" && !isArgUsed(name, usedNames)) { return name; - } + }*/ } else if (funcNodeType && funcNodeType.getCallSignatures().length > 0 && funcNodeType.getCallSignatures()[0].parameters.length > 0) { name = funcNodeType.getCallSignatures()[0].parameters[0].name; + /* if (!isArgUsed(name, usedNames)) { return name; - } + }*/ } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { return (funcNode.arguments[0]).text; @@ -274,9 +268,13 @@ namespace ts.codefix { return ""; } - return name + "_" + getArgName.varNameItr; + return name; + + /*return name + "_" + getArgName.varNameItr;*/ + } + /* namespace getArgName { export let varNameItr = 1; } @@ -286,4 +284,5 @@ namespace ts.codefix { const nameUsed: string[] = usedNames.filter(element => element === name); return nameUsed.length > 0; } + */ } \ No newline at end of file diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 7abfd81b0bac6..14655f82203f0 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1626,49 +1626,24 @@ namespace ts { * WARNING: This is an expensive operation and is only intended to be used in refactorings * and code fixes (because those are triggered by explicit user actions). */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true): T { - const clone = node && getSynthesizedDeepCloneWorker(node as NonNullable); - if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); - return clone; - } + export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker): T { + const clone = renameMap && checker && needsRenaming(node, checker) ? + node && createIdentifier(renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))!) : + node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, checker); - export function getSynthesizedMaybeRenamedDeepClone(node: T, includeTrivia = true ): T{ - const clone = node && getSynthesizedMaybeRenamedDeepCloneWorker(node as NonNullable) if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); - return clone; + return clone as T; } - function getSynthesizedMaybeRenamedDeepCloneWorker(node: T): T { - const visited = visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); - if (visited === node) { - // This only happens for leaf nodes - internal nodes always see their children change. - let clone; - if (isIdentifier(node)) { - clone = createIdentifier("NEWNAME"); - } - else { - clone = getSynthesizedClone(node); - } - - if (isStringLiteral(clone)) { - clone.textSourceNode = node as any; - } - else if (isNumericLiteral(clone)) { - clone.numericLiteralFlags = (node as any).numericLiteralFlags; - } - return setTextRange(clone as T, node); - } - - // PERF: As an optimization, rather than calling getSynthesizedClone, we'll update - // the new node created by visitEachChild with the extra changes getSynthesizedClone - // would have made. - visited.parent = undefined!; - return visited; + function needsRenaming(node: T | undefined, checker: TypeChecker): boolean{ + return !!(node && isIdentifier(node!) && checker.getSymbolAtLocation(node!)); } + function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker): T { + const visited = visitEachChild(node, function wrapper(node){ + return getSynthesizedDeepClone(node, true, renameMap, checker); + }, nullTransformationContext); - function getSynthesizedDeepCloneWorker(node: T): T { - const visited = visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); if (visited === node) { // This only happens for leaf nodes - internal nodes always see their children change. const clone = getSynthesizedClone(node); From 209a14fbe485b63b265bc88b1d00b7281a9e7daa Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 5 Jul 2018 10:40:45 -0700 Subject: [PATCH 083/196] Fixed finding inner promises bug --- src/services/codefixes/convertToAsyncFunction.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 860e20887b18e..0cc6b674e3c33 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -174,7 +174,8 @@ namespace ts.codefix { case SyntaxKind.ArrowFunction: if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { let innerCbBody: Statement[] = []; - for (const stmt of func.body.statements) { + let innerRetStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(func.body); + for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { let temp = parseCallback(node, checker, /*usedNames,*/ outermostParent, argName); From 2e470cd67285900c49513a0d6e5e8f9c2d206357 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 5 Jul 2018 11:16:55 -0700 Subject: [PATCH 084/196] First commit of conflicting var names working --- .../codefixes/convertToAsyncFunction.ts | 89 +++++++------------ 1 file changed, 33 insertions(+), 56 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 0cc6b674e3c33..0293f2e9dd83c 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -35,14 +35,12 @@ namespace ts.codefix { }); let funcToConvertClone = getSynthesizedDeepClone(funcToConvert, true, varNamesMap, checker); - //changes.replaceNode(sourceFile, funcToConvert, funcToConvertClone); const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvertClone); for (const stmt of retStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - //const usedNames: string[] = []; const newNode = parseCallback(node, checker, node); if (newNode.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNode); @@ -64,74 +62,73 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, /*usedNames: string[],*/ outermostParent: CallExpression, argName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, prevArgName?: string): Statement[] { if (!node) { return []; } if (isCallExpression(node) && returnsAPromise(node, checker)) { - return parsePromiseCall(node, /*usedNames,*/ checker, argName); + return parsePromiseCall(node, checker, prevArgName); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, /*usedNames,*/ outermostParent); + return parseThen(node, checker, outermostParent, prevArgName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, /*usedNames,*/ outermostParent); + return parseCatch(node, checker, outermostParent); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, /*usedNames,*/ outermostParent, argName); + return parseCallback(node.expression, checker, outermostParent, prevArgName); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, /*usedNames: string[],*/ outermostParent: CallExpression): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); - const argName = getArgName(func, checker /*, usedNames*/); + const argName = getArgName(func, checker); - const tryBlock = createBlock(parseCallback(node.expression, checker, /*usedNames,*/ outermostParent)); + const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent)); - const [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, /*usedNames,*/ outermostParent); + const [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, outermostParent); const catchClause = createCatchClause(argNameNew, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, /*usedNames: string[],*/ outermostParent: CallExpression): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, prevArgName?: string): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier - const argNameRes = getArgName(res, checker /*, usedNames*/); + const argNameRes = getArgName(res, checker); if (rej) { //const usedCatchNames: string[] = []; - const argNameRej = getArgName(rej, checker /*, usedCatchNames*/); + const argNameRej = getArgName(rej, checker); - const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, /*usedNames,*/ outermostParent); - const tryBlock = createBlock(parseCallback(node.expression, checker, /*usedNames,*/ outermostParent, argNameResNew).concat(callbackBody)); + const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, outermostParent); + const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent, argNameResNew).concat(callbackBody)); - const [callbackBody2, argNameRejNew] = getCallbackBody(rej, argNameRej, node, checker, /*usedCatchNames,*/ outermostParent, /* isRej */ true); + const [callbackBody2, argNameRejNew] = getCallbackBody(rej, argNameRej, node, checker, outermostParent, /* isRej */ true); const catchClause = createCatchClause(argNameRejNew, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, /*usedNames,*/ outermostParent); - return parseCallback(node.expression, checker, /*usedNames,*/ outermostParent, argNameResNew).concat(callbackBody); + const [callbackBody, argNameResNew] = getCallbackBody(res, prevArgName ? prevArgName : argNameRes, node, checker, outermostParent); + argNameResNew; + return parseCallback(node.expression, checker, outermostParent, argNameRes).concat(callbackBody); } else { - return parseCallback(node.expression, checker, /*usedNames,*/ outermostParent); + return parseCallback(node.expression, checker, outermostParent); } } - function parsePromiseCall(node: CallExpression, /*usedNames: string[],*/ checker: TypeChecker, argName: string | undefined): Statement[] { + function parsePromiseCall(node: CallExpression, checker: TypeChecker, argName: string | undefined): Statement[] { let localArgName = argName; if (!localArgName) { - localArgName = getArgName(node, checker /*, usedNames*/); + localArgName = getArgName(node, checker); } - //usedNames.push(localArgName); - const varDecl = createVariableDeclaration(localArgName, /*type*/ undefined, createAwait(node)); return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; } @@ -150,35 +147,37 @@ namespace ts.codefix { } } - function getCallbackBody(func: Node, argName: string, parent: CallExpression, checker: TypeChecker, /*usedNames: string[],*/ outermostParent: CallExpression, isRej = false): [NodeArray, string] { + function getCallbackBody(func: Node, prevArgName: string, parent: CallExpression, checker: TypeChecker, outermostParent: CallExpression, isRej = false): [NodeArray, string] { switch (func.kind) { case SyntaxKind.Identifier: - if (!argName) { + if (!prevArgName) { break; } - let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); + let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(prevArgName)]); const nextDotThen = getNextDotThen(parent, checker); if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { - return [createNodeArray([createReturn(synthCall)]), argName]; + return [createNodeArray([createReturn(synthCall)]), prevArgName]; } const tempArgName = getArgName(nextDotThen.arguments[0], checker/*, usedNames*/); //usedNames.push(tempArgName); - argName = getArgName(func, checker/*, usedNames*/); - synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); - return [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]), argName]; + prevArgName = getArgName(func, checker/*, usedNames*/); + synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(prevArgName)]); + return [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]), prevArgName]; case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: + const argName = getArgName(func, checker); + if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { let innerCbBody: Statement[] = []; let innerRetStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(func.body); for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - let temp = parseCallback(node, checker, /*usedNames,*/ outermostParent, argName); + let temp = parseCallback(node, checker, outermostParent, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; @@ -200,37 +199,15 @@ namespace ts.codefix { //if there is another outer dot then, don't actually return const nextOutermostDotThen = getNextDotThen(outermostParent, checker) - if (!nextOutermostDotThen) { - //usedNames.push(argName); - renameAllUsages(argName, getArgName(func, checker/*, usedNames*/), [func.body]); - } - return nextOutermostDotThen ? [createNodeArray([createReturn(func.body as Expression)]), argName] - : [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(argName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]), getArgName(func, checker /*, usedNames*/)] + : [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]), argName]; } break; } return [createNodeArray([]), ""]; } - function renameAllUsages(oldName: string, newName: string, codeToRename: Node[]): Node[] { - let renamedCode: Node[] = []; - for (let stmt of codeToRename){ - forEachChild(stmt, function visit(node: Node){ - if (isIdentifier(node) && node.text === oldName) { - newName; - //change the text name - } - else if (!isFunctionLike(node)){ - forEachChild(node, visit); - } - }); - } - - return renamedCode; - } - - function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { + function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; } From bc618d5b52a54c8a071ca4fe29ae67c0bee5f9a0 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 5 Jul 2018 17:11:25 -0700 Subject: [PATCH 085/196] added synthesized variable decl names to the varNamesMap --- .../codefixes/convertToAsyncFunction.ts | 166 ++++++++---------- 1 file changed, 78 insertions(+), 88 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 0293f2e9dd83c..03201f7c76548 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -17,33 +17,15 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); - // rename conflicting variables - let varNamesMap: Map = new MapCtr(); - let allVarNames: string[] = []; - - forEachChild(funcToConvert, function visit(node: Node) { - if (isIdentifier(node)) { - let symbol = checker.getSymbolAtLocation(node); - if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { - const numVarsSameName = allVarNames.filter(elem => elem === node.text).length; - allVarNames.push(node.text); - const newName = numVarsSameName === 0 ? node.text : node.text + "_" + numVarsSameName; - varNamesMap.set(String(getSymbolId(symbol)), newName); - } - } - forEachChild(node, visit); - }); - - let funcToConvertClone = getSynthesizedDeepClone(funcToConvert, true, varNamesMap, checker); + const funcToConvertRenamed = renameCollidingVarNames(funcToConvert, checker); - - const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvertClone); + const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed); for (const stmt of retStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const newNode = parseCallback(node, checker, node); - if (newNode.length > 0) { - changes.replaceNodeWithNodes(sourceFile, stmt, newNode); + const newNodes = parseCallback(node, checker, node); + if (newNodes.length > 0) { + changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } } else if (!isFunctionLike(node)) { @@ -53,6 +35,40 @@ namespace ts.codefix { } } + function renameCollidingVarNames(nodeToRename: Node, checker: TypeChecker): Node { + let varNamesMap: Map = new MapCtr(); + let allVarNames: string[] = []; + + forEachChild(nodeToRename, function visit(node: Node) { + + if (isIdentifier(node)) { + let type = checker.getTypeAtLocation(node); + let symbol = checker.getSymbolAtLocation(node); + + if (symbol && type && type.getCallSignatures().length > 0 && type.getCallSignatures()[0].parameters.length > 0) { + let synthName = type.getCallSignatures()[0].parameters[0].name; + let newName = getNewNameIfConflict(synthName, allVarNames); + varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(createIdentifier(synthName))))), newName); + allVarNames.push(synthName); + } + if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { + let newName = getNewNameIfConflict(node.text, allVarNames); + varNamesMap.set(String(getSymbolId(symbol)), newName); + allVarNames.push(node.text); + } + } + + forEachChild(node, visit); + }); + + return getSynthesizedDeepClone(nodeToRename, true, varNamesMap, checker); + } + + function getNewNameIfConflict(name: string, allVarNames: string[]) { + const numVarsSameName = allVarNames.filter(elem => elem === name).length; + return numVarsSameName === 0 ? name : name + "_" + numVarsSameName; + } + function returnsAPromise(node: CallExpression, checker: TypeChecker): boolean { const nodeType = checker.getTypeAtLocation(node); if (!nodeType) { @@ -74,7 +90,7 @@ namespace ts.codefix { return parseThen(node, checker, outermostParent, prevArgName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, outermostParent); + return parseCatch(node, checker, outermostParent, prevArgName); } else if (isPropertyAccessExpression(node)) { return parseCallback(node.expression, checker, outermostParent, prevArgName); @@ -83,14 +99,14 @@ namespace ts.codefix { return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, prevArgName?: string): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker); - const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent)); + const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent, argName)); - const [callbackBody, argNameNew] = getCallbackBody(func, argName, node, checker, outermostParent); - const catchClause = createCatchClause(argNameNew, createBlock(callbackBody)); + const callbackBody = getCallbackBody(func, prevArgName ? prevArgName : argName, node, checker, outermostParent); + const catchClause = createCatchClause(argName, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } @@ -102,35 +118,30 @@ namespace ts.codefix { const argNameRes = getArgName(res, checker); if (rej) { - //const usedCatchNames: string[] = []; const argNameRej = getArgName(rej, checker); - const [callbackBody, argNameResNew] = getCallbackBody(res, argNameRes, node, checker, outermostParent); - const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent, argNameResNew).concat(callbackBody)); + const callbackBody = getCallbackBody(res, prevArgName ? prevArgName : argNameRes, node, checker, outermostParent); + const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent, argNameRes).concat(callbackBody)); - const [callbackBody2, argNameRejNew] = getCallbackBody(rej, argNameRej, node, checker, outermostParent, /* isRej */ true); - const catchClause = createCatchClause(argNameRejNew, createBlock(callbackBody2)); + const callbackBody2 = getCallbackBody(rej, prevArgName ? prevArgName : argNameRej, node, checker, outermostParent, /* isRej */ true); + const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - const [callbackBody, argNameResNew] = getCallbackBody(res, prevArgName ? prevArgName : argNameRes, node, checker, outermostParent); - argNameResNew; + const callbackBody = getCallbackBody(res, prevArgName ? prevArgName : argNameRes, node, checker, outermostParent); return parseCallback(node.expression, checker, outermostParent, argNameRes).concat(callbackBody); } else { - return parseCallback(node.expression, checker, outermostParent); + return parseCallback(node.expression, checker, outermostParent, prevArgName); } } - function parsePromiseCall(node: CallExpression, checker: TypeChecker, argName: string | undefined): Statement[] { - let localArgName = argName; - if (!localArgName) { - localArgName = getArgName(node, checker); - } + function parsePromiseCall(node: CallExpression, checker: TypeChecker, prevArgName?: string): Statement[] { + let argName = getArgName(node, checker); - const varDecl = createVariableDeclaration(localArgName, /*type*/ undefined, createAwait(node)); - return argName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; + const varDecl = createVariableDeclaration(prevArgName ? prevArgName : argName, /*type*/ undefined, createAwait(node)); + return prevArgName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; } function getNextDotThen(node: Expression, checker: TypeChecker): CallExpression | undefined { @@ -147,29 +158,26 @@ namespace ts.codefix { } } - function getCallbackBody(func: Node, prevArgName: string, parent: CallExpression, checker: TypeChecker, outermostParent: CallExpression, isRej = false): [NodeArray, string] { + function getCallbackBody(func: Node, prevArgName: string, parent: CallExpression, checker: TypeChecker, outermostParent: CallExpression, isRej = false): NodeArray { switch (func.kind) { case SyntaxKind.Identifier: if (!prevArgName) { break; } - let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(prevArgName)]); + + let argName = getArgName(func, checker); + let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); const nextDotThen = getNextDotThen(parent, checker); if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { - return [createNodeArray([createReturn(synthCall)]), prevArgName]; + return createNodeArray([createReturn(synthCall)]); } - const tempArgName = getArgName(nextDotThen.arguments[0], checker/*, usedNames*/); - //usedNames.push(tempArgName); - - prevArgName = getArgName(func, checker/*, usedNames*/); - synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(prevArgName)]); - return [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(tempArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]), prevArgName]; + synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); + return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: - const argName = getArgName(func, checker); if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { let innerCbBody: Statement[] = []; @@ -189,25 +197,26 @@ namespace ts.codefix { }); } - if(innerCbBody.length > 0){ - return [createNodeArray(innerCbBody), argName]; + if (innerCbBody.length > 0) { + return createNodeArray(innerCbBody); } - return [func.body.statements, argName]; //TODO: go in here and rename all usages of argName if argName changes + return func.body.statements; } else if (isArrowFunction(func)) { //if there is another outer dot then, don't actually return - const nextOutermostDotThen = getNextDotThen(outermostParent, checker) - - return nextOutermostDotThen ? [createNodeArray([createReturn(func.body as Expression)]), argName] - : [createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]), argName]; + const nextOutermostDotThen = getNextDotThen(outermostParent, checker); + + return nextOutermostDotThen ? + createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]) : + createNodeArray([createReturn(func.body as Expression)]) } break; } - return [createNodeArray([]), ""]; + return createNodeArray([]); } - function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { + function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; } @@ -220,47 +229,28 @@ namespace ts.codefix { return (node.expression).name.text === funcName && checker.isPromiseLikeType(nodeType); } - function getArgName(funcNode: Node, checker: TypeChecker, /*usedNames: string[]*/): string { + function getArgName(funcNode: Node, checker: TypeChecker): string { let name; const funcNodeType = checker.getTypeAtLocation(funcNode); if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { name = (funcNode.parameters[0].name).text; - /* - if (name !== "_" && !isArgUsed(name, usedNames)) { - return name; - }*/ } else if (funcNodeType && funcNodeType.getCallSignatures().length > 0 && funcNodeType.getCallSignatures()[0].parameters.length > 0) { name = funcNodeType.getCallSignatures()[0].parameters[0].name; - /* - if (!isArgUsed(name, usedNames)) { - return name; - }*/ } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { - return (funcNode.arguments[0]).text; + name = (funcNode.arguments[0]).text; + } + else if (isIdentifier(funcNode)) { + name = funcNode.text; + TODO => instead, we need to grab this from the var names map } if (name === undefined || name === "_") { return ""; } - return name; - - /*return name + "_" + getArgName.varNameItr;*/ - - } - - /* - namespace getArgName { - export let varNameItr = 1; - } - - - function isArgUsed(name: string, usedNames: string[]): boolean { - const nameUsed: string[] = usedNames.filter(element => element === name); - return nameUsed.length > 0; + return name; } - */ } \ No newline at end of file From 13fefe62f6e034a3309b8bf82e12e30dff4b1d14 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 6 Jul 2018 11:56:23 -0700 Subject: [PATCH 086/196] Fixed synthesizing new variable declaration names after renames --- .../codefixes/convertToAsyncFunction.ts | 81 ++++++++++--------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 03201f7c76548..c4cee4173078d 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -17,13 +17,15 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); - const funcToConvertRenamed = renameCollidingVarNames(funcToConvert, checker); + let varNamesMap: Map = new MapCtr(); + let synthNamesMap: Map = new MapCtr(); + const funcToConvertRenamed = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed); for (const stmt of retStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node); + const newNodes = parseCallback(node, checker, node, synthNamesMap); if (newNodes.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } @@ -35,8 +37,7 @@ namespace ts.codefix { } } - function renameCollidingVarNames(nodeToRename: Node, checker: TypeChecker): Node { - let varNamesMap: Map = new MapCtr(); + function renameCollidingVarNames(nodeToRename: Node, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map): Node { let allVarNames: string[] = []; forEachChild(nodeToRename, function visit(node: Node) { @@ -50,6 +51,7 @@ namespace ts.codefix { let newName = getNewNameIfConflict(synthName, allVarNames); varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(createIdentifier(synthName))))), newName); allVarNames.push(synthName); + synthNamesMap.set(node.text, synthName); } if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { let newName = getNewNameIfConflict(node.text, allVarNames); @@ -78,70 +80,75 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, prevArgName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string): Statement[] { if (!node) { return []; } if (isCallExpression(node) && returnsAPromise(node, checker)) { - return parsePromiseCall(node, checker, prevArgName); + return parsePromiseCall(node, prevArgName); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, outermostParent, prevArgName); + return parseThen(node, checker, outermostParent, synthNamesMap, prevArgName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, outermostParent, prevArgName); + return parseCatch(node, checker, outermostParent, synthNamesMap, prevArgName); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, outermostParent, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, prevArgName?: string): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); - const argName = getArgName(func, checker); + const argName = getArgName(func, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent, argName)); + const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent, synthNamesMap, argName)); - const callbackBody = getCallbackBody(func, prevArgName ? prevArgName : argName, node, checker, outermostParent); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, outermostParent, synthNamesMap); const catchClause = createCatchClause(argName, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, prevArgName?: string): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier - const argNameRes = getArgName(res, checker); + if (!res) { + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName); + } + + + const argNameRes = getArgName(res, checker, synthNamesMap); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, outermostParent, synthNamesMap); if (rej) { - const argNameRej = getArgName(rej, checker); + const argNameRej = getArgName(rej, checker, synthNamesMap); - const callbackBody = getCallbackBody(res, prevArgName ? prevArgName : argNameRes, node, checker, outermostParent); - const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent, argNameRes).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent, synthNamesMap, argNameRes).concat(callbackBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName ? prevArgName : argNameRej, node, checker, outermostParent, /* isRej */ true); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, outermostParent, synthNamesMap, true); const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - const callbackBody = getCallbackBody(res, prevArgName ? prevArgName : argNameRes, node, checker, outermostParent); - return parseCallback(node.expression, checker, outermostParent, argNameRes).concat(callbackBody); - } - else { - return parseCallback(node.expression, checker, outermostParent, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, argNameRes).concat(callbackBody); } + + return []; } - function parsePromiseCall(node: CallExpression, checker: TypeChecker, prevArgName?: string): Statement[] { - let argName = getArgName(node, checker); + function parsePromiseCall(node: CallExpression, prevArgName?: string): Statement[] { + if (prevArgName) { + const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); + return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] + } - const varDecl = createVariableDeclaration(prevArgName ? prevArgName : argName, /*type*/ undefined, createAwait(node)); - return prevArgName ? [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] : [createStatement(createAwait(node))]; + return [createStatement(createAwait(node))]; } function getNextDotThen(node: Expression, checker: TypeChecker): CallExpression | undefined { @@ -158,14 +165,12 @@ namespace ts.codefix { } } - function getCallbackBody(func: Node, prevArgName: string, parent: CallExpression, checker: TypeChecker, outermostParent: CallExpression, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, isRej = false): NodeArray { + if (!prevArgName) { + prevArgName = argName; + } switch (func.kind) { case SyntaxKind.Identifier: - if (!prevArgName) { - break; - } - - let argName = getArgName(func, checker); let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); const nextDotThen = getNextDotThen(parent, checker); if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { @@ -185,7 +190,7 @@ namespace ts.codefix { for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - let temp = parseCallback(node, checker, outermostParent, prevArgName); + let temp = parseCallback(node, checker, outermostParent, synthNamesMap, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; @@ -229,7 +234,7 @@ namespace ts.codefix { return (node.expression).name.text === funcName && checker.isPromiseLikeType(nodeType); } - function getArgName(funcNode: Node, checker: TypeChecker): string { + function getArgName(funcNode: Node, checker: TypeChecker, synthNamesMap: Map): string { let name; const funcNodeType = checker.getTypeAtLocation(funcNode); @@ -238,13 +243,13 @@ namespace ts.codefix { } else if (funcNodeType && funcNodeType.getCallSignatures().length > 0 && funcNodeType.getCallSignatures()[0].parameters.length > 0) { name = funcNodeType.getCallSignatures()[0].parameters[0].name; + // TODO : maybe get rid of this } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { name = (funcNode.arguments[0]).text; } else if (isIdentifier(funcNode)) { - name = funcNode.text; - TODO => instead, we need to grab this from the var names map + name = synthNamesMap.get(funcNode.text); } if (name === undefined || name === "_") { From f3dafadc03bef8436a8331b0278ce1f19c3c740c Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 6 Jul 2018 12:29:27 -0700 Subject: [PATCH 087/196] Fixed synthMap to have the correct values --- src/services/codefixes/convertToAsyncFunction.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index c4cee4173078d..4d4a0237274c7 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -51,7 +51,7 @@ namespace ts.codefix { let newName = getNewNameIfConflict(synthName, allVarNames); varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(createIdentifier(synthName))))), newName); allVarNames.push(synthName); - synthNamesMap.set(node.text, synthName); + synthNamesMap.set(node.text, newName); } if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { let newName = getNewNameIfConflict(node.text, allVarNames); @@ -172,7 +172,7 @@ namespace ts.codefix { switch (func.kind) { case SyntaxKind.Identifier: let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); - const nextDotThen = getNextDotThen(parent, checker); + const nextDotThen = getNextDotThen(parent.original as Expression, checker); if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { return createNodeArray([createReturn(synthCall)]); } @@ -210,7 +210,7 @@ namespace ts.codefix { } else if (isArrowFunction(func)) { //if there is another outer dot then, don't actually return - const nextOutermostDotThen = getNextDotThen(outermostParent, checker); + const nextOutermostDotThen = getNextDotThen(outermostParent.original as Expression, checker); return nextOutermostDotThen ? createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]) : From fe1c20ce6ec6deb773ca2304368d53173538a3ed Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 6 Jul 2018 13:48:26 -0700 Subject: [PATCH 088/196] Fixed inner promise returning bug --- src/services/codefixes/convertToAsyncFunction.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 4d4a0237274c7..2f707062be699 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -166,11 +166,16 @@ namespace ts.codefix { } function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, isRej = false): NodeArray { - if (!prevArgName) { + if (!prevArgName && argName) { prevArgName = argName; - } + } + switch (func.kind) { case SyntaxKind.Identifier: + if (!prevArgName || !argName) { + break; + } + let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); const nextDotThen = getNextDotThen(parent.original as Expression, checker); if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { @@ -190,7 +195,7 @@ namespace ts.codefix { for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - let temp = parseCallback(node, checker, outermostParent, synthNamesMap, prevArgName); + let temp = parseCallback(node, checker, node, synthNamesMap, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; @@ -213,7 +218,7 @@ namespace ts.codefix { const nextOutermostDotThen = getNextDotThen(outermostParent.original as Expression, checker); return nextOutermostDotThen ? - createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]) : + createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]) : createNodeArray([createReturn(func.body as Expression)]) } break; From 7e0c435e860a68319e28e743d55738eddea9d430 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 6 Jul 2018 15:34:09 -0700 Subject: [PATCH 089/196] Added more tests --- .../unittests/convertToAsyncFunction.ts | 106 +++++++++++++++++- 1 file changed, 102 insertions(+), 4 deletions(-) diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 8ff2bdcdd4fe0..e9ad7d91f66b0 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -366,26 +366,26 @@ function res(result){ console.log(result); } ` - ); + ); _testConvertToAsyncFunction("convertToAsyncFunction_NoBrackets", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result)); } ` - ); + ); _testConvertToAsyncFunction("convertToAsyncFunction_Finally1", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").then(res => console.log(res)).catch(rej => console.log("error", rej)).finally(console.log("finally!")); } ` - ); + ); _testConvertToAsyncFunction("convertToAsyncFunction_Finally2", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").then(res => console.log(res)).finally(console.log("finally!")); } ` - ); + ); _testConvertToAsyncFunction("convertToAsyncFunction_Finally3", ` function [#|finallyTest|](): Promise { @@ -418,7 +418,105 @@ function [#|f|](): Promise { } ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseSimple", ` +function [#|f|](): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(blob => blob.byteOffset); + }).then(blob => { + return blob.toString(); + }); +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen", ` +function [#|f|]() { + return Promise.resolve().then(function () { + return 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_Scope", ` +function [#|f|]() { + var var1:Response, var2; + return fetch('https://typescriptlang.org').then( _ => + Promise.resolve().then( res => { + var2 = "test"; + return fetch("https://microsoft.com"); + }).then(res => + var1 === res + ) + ).then(res); + } +` ); + +_testConvertToAsyncFunction("convertToAsyncFunction_Conditionals", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res => { + if (res.ok) { + return fetch("https://microsoft.com"); + } else { + if (res.buffer.length > 5) { + return res; + } else { + return fetch("https://github.com"); + } + } }); +} +` +); + +_testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThen", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return result; +} + +function rej(reject){ + return reject; +} +` +); + +_testConvertToAsyncFunction("convertToAsyncFunction_Scope2", ` +function [#|f|](){ + var i:number; + return fetch("https://typescriptlang.org").then(i => i.ok).then(res => i+1).catch(err => i-1) +} +` +); + +_testConvertToAsyncFunction("convertToAsyncFunction_Loop", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res => { for(let i=0; i<10; i++){ + console.log(res); + }}) +} +` +); + +_testConvertToAsyncFunction("convertToAsyncFunction_Conditional2", ` +function [#|f|](){ + var res = 100; + if (res > 50) { + return fetch("https://typescriptlang.org").then(res => console.log(res)); + } + else { + return fetch("https://typescriptlang.org").then(res_func); + } +} +` +); + +}); function _testConvertToAsyncFunction(caption: string, text: string, includeLib?: boolean) { testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.Convert_to_async_function, includeLib); From 0eb995e7e1e3c085d42d1c613aed82a6720ddcf1 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 6 Jul 2018 16:20:41 -0700 Subject: [PATCH 090/196] More tests --- .../unittests/convertToAsyncFunction.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index e9ad7d91f66b0..df7af2eb6292a 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -516,6 +516,34 @@ function [#|f|](){ ` ); +_testConvertToAsyncFunction("convertToAsyncFunction_Scope3", ` +function [#|f|]() { + var obj; + return fetch("https://typescriptlang.org").then(function (res) { + obj = { + func: function f() { + console.log(res); + } + }; + }); +} +` +); + +_testConvertToAsyncFunction("convertToAsyncFunction_NestedFunction", ` +function [#|f|]() { + function fn2(){ + function fn3(){ + return fetch("https://typescriptlang.org").then(res => console.log(res)); + } + return fn3(); + } + return fn2(); +} +`); + + + }); function _testConvertToAsyncFunction(caption: string, text: string, includeLib?: boolean) { From 7d89e1e6e316fe2dec18633d2776a76c295d47a5 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 9 Jul 2018 12:56:13 -0700 Subject: [PATCH 091/196] Initial commit of checking for more to refactor in arrow function body --- .../codefixes/convertToAsyncFunction.ts | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 2f707062be699..2352dbd59fdbf 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -121,7 +121,7 @@ namespace ts.codefix { return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName); } - + const argNameRes = getArgName(res, checker, synthNamesMap); const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, outermostParent, synthNamesMap); @@ -167,7 +167,7 @@ namespace ts.codefix { function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, isRej = false): NodeArray { if (!prevArgName && argName) { - prevArgName = argName; + prevArgName = argName; } switch (func.kind) { @@ -190,23 +190,8 @@ namespace ts.codefix { case SyntaxKind.ArrowFunction: if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - let innerCbBody: Statement[] = []; - let innerRetStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(func.body); - for (const stmt of innerRetStmts) { - forEachChild(stmt, function visit(node: Node) { - if (isCallExpression(node)) { - let temp = parseCallback(node, checker, node, synthNamesMap, prevArgName); - innerCbBody = innerCbBody.concat(temp); - if (innerCbBody.length > 0) { - return; - } - } - else if (!isFunctionLike(node)) { - forEachChild(node, visit); - } - }); - } - + let innerRetStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(func.body as Node); + let innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } @@ -215,17 +200,42 @@ namespace ts.codefix { } else if (isArrowFunction(func)) { //if there is another outer dot then, don't actually return + + let innerCbBody = getInnerCallbackBody(checker, [createReturn(func.body as Expression)], synthNamesMap, prevArgName); + if (innerCbBody.length > 0) { + return createNodeArray(innerCbBody); + } + const nextOutermostDotThen = getNextDotThen(outermostParent.original as Expression, checker); return nextOutermostDotThen ? createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]) : - createNodeArray([createReturn(func.body as Expression)]) + createNodeArray([createReturn(func.body as Expression)]); } break; } return createNodeArray([]); } + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: ReturnStatement[], synthNamesMap: Map, prevArgName?: string) { + let innerCbBody: Statement[] = []; + for (const stmt of innerRetStmts) { + forEachChild(stmt, function visit(node: Node) { + if (isCallExpression(node)) { + let temp = parseCallback(node, checker, node, synthNamesMap, prevArgName); + innerCbBody = innerCbBody.concat(temp); + if (innerCbBody.length > 0) { + return; + } + } + else if (!isFunctionLike(node)) { + forEachChild(node, visit); + } + }); + } + return innerCbBody; + } + function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; @@ -254,7 +264,7 @@ namespace ts.codefix { name = (funcNode.arguments[0]).text; } else if (isIdentifier(funcNode)) { - name = synthNamesMap.get(funcNode.text); + name = synthNamesMap.get(funcNode.text); } if (name === undefined || name === "_") { From 78e6dd7090bdba2d360f4c9b3d2b741f3c0ccbc6 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 9 Jul 2018 17:38:33 -0700 Subject: [PATCH 092/196] Buggy commit -> removed returns from arrow function body --- .../codefixes/convertToAsyncFunction.ts | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 2352dbd59fdbf..6f6de35a5e962 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -46,15 +46,17 @@ namespace ts.codefix { let type = checker.getTypeAtLocation(node); let symbol = checker.getSymbolAtLocation(node); + let newName = getNewNameIfConflict(node.text, allVarNames); + if (symbol && type && type.getCallSignatures().length > 0 && type.getCallSignatures()[0].parameters.length > 0) { let synthName = type.getCallSignatures()[0].parameters[0].name; - let newName = getNewNameIfConflict(synthName, allVarNames); - varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(createIdentifier(synthName))))), newName); + //let newParamName = getNewNameIfConflict(synthName, allVarNames); + varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(createIdentifier(synthName))))), synthName); allVarNames.push(synthName); - synthNamesMap.set(node.text, newName); + synthNamesMap.set(newName, synthName); } + if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { - let newName = getNewNameIfConflict(node.text, allVarNames); varNamesMap.set(String(getSymbolId(symbol)), newName); allVarNames.push(node.text); } @@ -86,7 +88,7 @@ namespace ts.codefix { } if (isCallExpression(node) && returnsAPromise(node, checker)) { - return parsePromiseCall(node, prevArgName); + return parsePromiseCall(node, outermostParent, checker, prevArgName); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { return parseThen(node, checker, outermostParent, synthNamesMap, prevArgName); @@ -105,7 +107,7 @@ namespace ts.codefix { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent, synthNamesMap, argName)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argName)); const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, outermostParent, synthNamesMap); const catchClause = createCatchClause(argName, createBlock(callbackBody)); @@ -128,7 +130,7 @@ namespace ts.codefix { if (rej) { const argNameRej = getArgName(rej, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, outermostParent, synthNamesMap, argNameRes).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argNameRes).concat(callbackBody)); const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, outermostParent, synthNamesMap, true); const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); @@ -136,29 +138,28 @@ namespace ts.codefix { return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, argNameRes).concat(callbackBody); + return parseCallback(node.expression, checker, node, synthNamesMap, argNameRes).concat(callbackBody); } return []; } - function parsePromiseCall(node: CallExpression, prevArgName?: string): Statement[] { - if (prevArgName) { + function parsePromiseCall(node: CallExpression, outerMostParent: Expression, checker: TypeChecker, prevArgName?: string): Statement[] { + if (prevArgName && getNextDotThen(outerMostParent, checker)) { const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] } - return [createStatement(createAwait(node))]; + return [createReturn(node)]; } function getNextDotThen(node: Expression, checker: TypeChecker): CallExpression | undefined { - if (!node || !node.parent) { + if (!node) { return undefined; } - const parent = node.parent; - if (parent.kind === SyntaxKind.CallExpression && isCallback(parent as CallExpression, "then", checker)) { - return parent as CallExpression; + if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { + return node as CallExpression; } else { return getNextDotThen(node.parent as Expression, checker); @@ -177,7 +178,7 @@ namespace ts.codefix { } let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); - const nextDotThen = getNextDotThen(parent.original as Expression, checker); + const nextDotThen = getNextDotThen(parent.original!.parent as Expression, checker); if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { return createNodeArray([createReturn(synthCall)]); } @@ -196,7 +197,8 @@ namespace ts.codefix { return createNodeArray(innerCbBody); } - return func.body.statements; + const nextOutermostDotThen = getNextDotThen(outermostParent.original as Expression, checker); + return nextOutermostDotThen ? removeReturns(func.body.statements, prevArgName!) : func.body.statements } else if (isArrowFunction(func)) { //if there is another outer dot then, don't actually return @@ -217,6 +219,22 @@ namespace ts.codefix { return createNodeArray([]); } + function removeReturns(stmts: NodeArray, prevArgName: string): NodeArray { + let ret: Statement[] = []; + for (let stmt of stmts) { + if (isReturnStatement(stmt)) { + if (stmt.expression){ + ret.push(createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], NodeFlags.Let)))); + } + } + else { + ret.push(stmt); + } + } + + return createNodeArray(ret); + } + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: ReturnStatement[], synthNamesMap: Map, prevArgName?: string) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { From 563d7b70c1758441684b4d4feb7719a2c4a20508 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 9 Jul 2018 17:48:26 -0700 Subject: [PATCH 093/196] Added new baselines --- .../convertToAsyncFunction_Scope2.ts | 20 ++++++++++++++++ .../convertToAsyncFunction_Scope3.js | 24 +++++++++++++++++++ .../convertToAsyncFunction_Scope3.ts | 24 +++++++++++++++++++ .../convertToAsyncFunction_UntypedFunction.js | 12 ++++++++++ .../convertToAsyncFunction_UntypedFunction.ts | 12 ++++++++++ 5 files changed, 92 insertions(+) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope2.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.ts diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope2.ts new file mode 100644 index 0000000000000..3bca50eec95d4 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope2.ts @@ -0,0 +1,20 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + var i:number; + return fetch("https://typescriptlang.org").then(i => i.ok).then(res => i+1).catch(err => i-1) +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + var i:number; + try { + let i_1 = await fetch("https://typescriptlang.org"); + let res = i_1.ok; + return i + 1; + } + catch (err) { + return i - 1; + } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js new file mode 100644 index 0000000000000..935b78ff7974f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js @@ -0,0 +1,24 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + var obj; + return fetch("https://typescriptlang.org").then(function (res) { + obj = { + func: function f() { + console.log(res); + } + }; + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + var obj; + let res = await fetch("https://typescriptlang.org"); + obj = { + func: function f_1() { + console.log(res); + } + }; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts new file mode 100644 index 0000000000000..935b78ff7974f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts @@ -0,0 +1,24 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + var obj; + return fetch("https://typescriptlang.org").then(function (res) { + obj = { + func: function f() { + console.log(res); + } + }; + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + var obj; + let res = await fetch("https://typescriptlang.org"); + obj = { + func: function f_1() { + console.log(res); + } + }; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.js new file mode 100644 index 0000000000000..40bd1c02eeb4b --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.js @@ -0,0 +1,12 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(res => console.log(res)); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let res = await Promise.resolve(); + return console.log(res); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.ts new file mode 100644 index 0000000000000..40bd1c02eeb4b --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.ts @@ -0,0 +1,12 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(res => console.log(res)); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let res = await Promise.resolve(); + return console.log(res); +} From 84139b6081cbdf63299b47bc3279a77a090bfef5 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 9 Jul 2018 17:48:43 -0700 Subject: [PATCH 094/196] Fixed nextDotThen bug --- src/services/codefixes/convertToAsyncFunction.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 6f6de35a5e962..6d948b155eb01 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -30,9 +30,9 @@ namespace ts.codefix { changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } } - else if (!isFunctionLike(node)) { + // else if (!isFunctionLike(node)) { forEachChild(node, visit); - } + //} }); } } From 9acf6088ad7761bce6f87785732048d043108f64 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 10 Jul 2018 10:40:12 -0700 Subject: [PATCH 095/196] Fixed to no longer rename function names -> instead rename local and synthesized variables if there is a conflict --- .../codefixes/convertToAsyncFunction.ts | 21 ++++++++++--- .../convertToAsyncFunction_Scope.ts | 31 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 6f6de35a5e962..cd36055691121 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -49,14 +49,27 @@ namespace ts.codefix { let newName = getNewNameIfConflict(node.text, allVarNames); if (symbol && type && type.getCallSignatures().length > 0 && type.getCallSignatures()[0].parameters.length > 0) { + // first, add the actual function name + if (allVarNames.filter(elem => elem === node.text).length > 0) { + // we have a conflict with the function name, but function names take precedence over variable names + varNamesMap.forEach((value: string, key: string) => { + if (value === node.text) { + varNamesMap.set(key, getNewNameIfConflict(node.text, allVarNames)); + return; + } + }); + } + + varNamesMap.set(String(getSymbolId(symbol)), node.text); + allVarNames.push(node.text); + + // next, add the new variable for the declaration let synthName = type.getCallSignatures()[0].parameters[0].name; - //let newParamName = getNewNameIfConflict(synthName, allVarNames); varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(createIdentifier(synthName))))), synthName); allVarNames.push(synthName); - synthNamesMap.set(newName, synthName); + synthNamesMap.set(node.text, synthName); } - - if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { + else if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { varNamesMap.set(String(getSymbolId(symbol)), newName); allVarNames.push(node.text); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts new file mode 100644 index 0000000000000..4771c45efcc3e --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts @@ -0,0 +1,31 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + var var1:Promise, var2; + return fetch('https://typescriptlang.org').then( _ => + Promise.resolve().then( res => { + var2 = "test"; + return fetch("https://microsoft.com"); + }).then(res => + var1 === res + ) + ).then(res); + } + function res(response){ + console.log(response); + } + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + var var1:Promise, var2; + return fetch('https://typescriptlang.org'); + let res_2 = await Promise.resolve(); + var2 = "test"; + let res_1 = fetch("https://microsoft.com"); + let response = var1 === res_1; + return res(response); + } + function res(response){ + console.log(response); + } From 2a98d64e500d8fc5475a44d2afc7521ffd972886 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 10 Jul 2018 13:35:35 -0700 Subject: [PATCH 096/196] Fixed getNextDotThen logic --- .../codefixes/convertToAsyncFunction.ts | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 2e3995f7afaad..f8805d9f28f11 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -30,9 +30,9 @@ namespace ts.codefix { changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } } - // else if (!isFunctionLike(node)) { + else if (!isFunctionLike(node)) { forEachChild(node, visit); - //} + } }); } } @@ -65,9 +65,10 @@ namespace ts.codefix { // next, add the new variable for the declaration let synthName = type.getCallSignatures()[0].parameters[0].name; - varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(createIdentifier(synthName))))), synthName); - allVarNames.push(synthName); - synthNamesMap.set(node.text, synthName); + let newSynthName = getNewNameIfConflict(synthName, allVarNames); + varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(createIdentifier(newSynthName))))), newSynthName); + allVarNames.push(newSynthName); + synthNamesMap.set(node.text, newSynthName); } else if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { varNamesMap.set(String(getSymbolId(symbol)), newName); @@ -101,13 +102,13 @@ namespace ts.codefix { } if (isCallExpression(node) && returnsAPromise(node, checker)) { - return parsePromiseCall(node, outermostParent, checker, prevArgName); + return parsePromiseCall(node, checker, prevArgName); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { return parseThen(node, checker, outermostParent, synthNamesMap, prevArgName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, outermostParent, synthNamesMap, prevArgName); + return parseCatch(node, checker, synthNamesMap, prevArgName); } else if (isPropertyAccessExpression(node)) { return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName); @@ -116,13 +117,13 @@ namespace ts.codefix { return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, prevArgName?: string): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argName)); - const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, outermostParent, synthNamesMap); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap); const catchClause = createCatchClause(argName, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; @@ -138,14 +139,14 @@ namespace ts.codefix { const argNameRes = getArgName(res, checker, synthNamesMap); - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, outermostParent, synthNamesMap); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap); if (rej) { const argNameRej = getArgName(rej, checker, synthNamesMap); const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argNameRes).concat(callbackBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, outermostParent, synthNamesMap, true); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, true); const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; @@ -157,8 +158,8 @@ namespace ts.codefix { return []; } - function parsePromiseCall(node: CallExpression, outerMostParent: Expression, checker: TypeChecker, prevArgName?: string): Statement[] { - if (prevArgName && getNextDotThen(outerMostParent, checker)) { + function parsePromiseCall(node: CallExpression, checker: TypeChecker, prevArgName?: string): Statement[] { + if (prevArgName && getNextDotThen(node.original!.parent as Expression, checker) && isPropertyAccessExpression(node.original!.parent)) { const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] } @@ -171,15 +172,15 @@ namespace ts.codefix { return undefined; } - if (node.kind === SyntaxKind.CallExpression && isCallback(node as CallExpression, "then", checker)) { - return node as CallExpression; + if (isCallExpression(node) && isCallback(node, "then", checker)) { + return node; } else { return getNextDotThen(node.parent as Expression, checker); } } - function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, isRej = false): NodeArray { if (!prevArgName && argName) { prevArgName = argName; } @@ -210,7 +211,7 @@ namespace ts.codefix { return createNodeArray(innerCbBody); } - const nextOutermostDotThen = getNextDotThen(outermostParent.original as Expression, checker); + const nextOutermostDotThen = getNextDotThen(parent.original!.parent as Expression, checker); return nextOutermostDotThen ? removeReturns(func.body.statements, prevArgName!) : func.body.statements } else if (isArrowFunction(func)) { @@ -221,7 +222,7 @@ namespace ts.codefix { return createNodeArray(innerCbBody); } - const nextOutermostDotThen = getNextDotThen(outermostParent.original as Expression, checker); + const nextOutermostDotThen = getNextDotThen(parent.original!.parent as Expression, checker); return nextOutermostDotThen ? createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]) : @@ -268,6 +269,7 @@ namespace ts.codefix { } function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { + // can probably get rid of this if statement if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; } From 2b31faaa6c03e6c8594b1725b22819ebc69c9c61 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 10 Jul 2018 13:36:32 -0700 Subject: [PATCH 097/196] Added tests and baselines --- .../unittests/convertToAsyncFunction.ts | 26 ++++++++++++-- .../convertToAsyncFunction_Conditional2.js | 33 ++++++++++++++++++ .../convertToAsyncFunction_Conditional2.ts | 33 ++++++++++++++++++ .../convertToAsyncFunction_Conditionals.js | 34 +++++++++++++++++++ .../convertToAsyncFunction_Conditionals.ts | 34 +++++++++++++++++++ ...nvertToAsyncFunction_InnerPromiseSimple.ts | 18 ++++++++++ ...oAsyncFunction_MultipleThensSameVarName.ts | 6 ++-- 7 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index df7af2eb6292a..e4e32ec87088e 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -442,7 +442,7 @@ function [#|f|]() { ); _testConvertToAsyncFunction("convertToAsyncFunction_Scope", ` function [#|f|]() { - var var1:Response, var2; + var var1:Promise, var2; return fetch('https://typescriptlang.org').then( _ => Promise.resolve().then( res => { var2 = "test"; @@ -452,6 +452,9 @@ function [#|f|]() { ) ).then(res); } + function res(response){ + console.log(response); + } ` ); _testConvertToAsyncFunction("convertToAsyncFunction_Conditionals", ` @@ -459,10 +462,12 @@ function [#|f|](){ return fetch("https://typescriptlang.org").then(res => { if (res.ok) { return fetch("https://microsoft.com"); - } else { + } + else { if (res.buffer.length > 5) { return res; - } else { + } + else { return fetch("https://github.com"); } } @@ -513,6 +518,10 @@ function [#|f|](){ return fetch("https://typescriptlang.org").then(res_func); } } + +function res_func(result){ + console.log(result); +} ` ); @@ -542,7 +551,18 @@ function [#|f|]() { } `); +_testConvertToAsyncFunction("convertToAsyncFunction_UntypedFunction", ` +function [#|f|]() { + return Promise.resolve().then(res => console.log(res)); +} +`); +_testConvertToAsyncFunction("convertToAsyncFunction_TernaryConditional", ` +function [#|f|]() { + let i; + return Promise.resolve().then(res => res ? i = res : i = 100); +} +`); }); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js new file mode 100644 index 0000000000000..606283aa14ad9 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js @@ -0,0 +1,33 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + var res = 100; + if (res > 50) { + return fetch("https://typescriptlang.org").then(res => console.log(res)); + } + else { + return fetch("https://typescriptlang.org").then(res_func); + } +} + +function res_func(result){ + console.log(result); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + var res = 100; + if (res > 50) { + let res_1 = await fetch("https://typescriptlang.org"); + return console.log(res_1); + } + else { + let result = await fetch("https://typescriptlang.org"); + return res_func(result); + } +} + +function res_func(result){ + console.log(result); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts new file mode 100644 index 0000000000000..606283aa14ad9 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts @@ -0,0 +1,33 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + var res = 100; + if (res > 50) { + return fetch("https://typescriptlang.org").then(res => console.log(res)); + } + else { + return fetch("https://typescriptlang.org").then(res_func); + } +} + +function res_func(result){ + console.log(result); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + var res = 100; + if (res > 50) { + let res_1 = await fetch("https://typescriptlang.org"); + return console.log(res_1); + } + else { + let result = await fetch("https://typescriptlang.org"); + return res_func(result); + } +} + +function res_func(result){ + console.log(result); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.js new file mode 100644 index 0000000000000..2deabfabdba5b --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.js @@ -0,0 +1,34 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res => { + if (res.ok) { + return fetch("https://microsoft.com"); + } + else { + if (res.buffer.length > 5) { + return res; + } + else { + return fetch("https://github.com"); + } + } + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let res = await fetch("https://typescriptlang.org"); + if (res.ok) { + return fetch("https://microsoft.com"); + } + else { + if (res.buffer.length > 5) { + return res; + } + else { + return fetch("https://github.com"); + } + } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.ts new file mode 100644 index 0000000000000..2deabfabdba5b --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.ts @@ -0,0 +1,34 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res => { + if (res.ok) { + return fetch("https://microsoft.com"); + } + else { + if (res.buffer.length > 5) { + return res; + } + else { + return fetch("https://github.com"); + } + } + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let res = await fetch("https://typescriptlang.org"); + if (res.ok) { + return fetch("https://microsoft.com"); + } + else { + if (res.buffer.length > 5) { + return res; + } + else { + return fetch("https://github.com"); + } + } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts new file mode 100644 index 0000000000000..71b4e2a934fa5 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(blob => blob.byteOffset); + }).then(blob => { + return blob.toString(); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise { + let resp = await fetch("https://typescriptlang.org"); + let blob = await resp.blob(); + let blob_1 = blob.byteOffset; + return blob_1.toString(); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts index 2d62a0f4dea42..7fe7e3dd131b8 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts @@ -13,9 +13,9 @@ function res2(result){ // ==ASYNC FUNCTION::Convert to async function== async function f():Promise { - let result_1 = await fetch('https://typescriptlang.org'); - let result = await res(result_1); - return res2(result); + let result = await fetch('https://typescriptlang.org'); + let result_1 = await res(result); + return res2(result_1); } function res(result){ return result.ok; From 70d41738b36022c3d33436e6cba6cceb7ea2e841 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 10 Jul 2018 14:12:29 -0700 Subject: [PATCH 098/196] Added tests and baselines --- .../unittests/convertToAsyncFunction.ts | 14 +++++++++++--- .../convertToAsyncFunction_Loop.js | 16 ++++++++++++++++ .../convertToAsyncFunction_Loop.ts | 16 ++++++++++++++++ .../convertToAsyncFunction_NoRes3.ts | 2 +- ...onvertToAsyncFunction_PromiseAllAndThen.js | 18 ++++++++++++++++++ ...onvertToAsyncFunction_PromiseAllAndThen.ts | 18 ++++++++++++++++++ ...nvertToAsyncFunction_PromiseAllAndThen2.js | 19 +++++++++++++++++++ ...nvertToAsyncFunction_PromiseAllAndThen2.ts | 19 +++++++++++++++++++ .../convertToAsyncFunction_PromiseDotAll.ts | 14 ++++++++++++++ .../convertToAsyncFunction_Scope.ts | 2 +- ...nvertToAsyncFunction_TernaryConditional.js | 14 ++++++++++++++ ...nvertToAsyncFunction_TernaryConditional.ts | 14 ++++++++++++++ 12 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseDotAll.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index e4e32ec87088e..1ca3ab35aa33a 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -434,9 +434,17 @@ function [#|f|]() { return 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_PromiseAllAndThen2", ` +function [#|f|]() { + return Promise.resolve().then(function () { + return 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_Loop.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.js new file mode 100644 index 0000000000000..2b73333d363cf --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.js @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res => { for(let i=0; i<10; i++){ + console.log(res); + }}) +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let res = await fetch("https://typescriptlang.org"); + for (let i = 0; i < 10; i++) { + console.log(res); + } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.ts new file mode 100644 index 0000000000000..2b73333d363cf --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res => { for(let i=0; i<10; i++){ + console.log(res); + }}) +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let res = await fetch("https://typescriptlang.org"); + for (let i = 0; i < 10; i++) { + console.log(res); + } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes3.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes3.ts index 469cf1cf9897b..09081dbabd5d8 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes3.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoRes3.ts @@ -8,7 +8,7 @@ function /*[#|*/f/*|]*/():Promise { async function f():Promise { try { - await fetch('https://typescriptlang.org'); + return fetch('https://typescriptlang.org'); } catch (rej) { return console.log(rej); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.js new file mode 100644 index 0000000000000..8b4b5afd88ca0 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.js @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(function () { + return 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 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_PromiseAllAndThen.ts new file mode 100644 index 0000000000000..8b4b5afd88ca0 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen.ts @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(function () { + return 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 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_PromiseAllAndThen2.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.js new file mode 100644 index 0000000000000..c9583f26ae490 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.js @@ -0,0 +1,19 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(function () { + return 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(); + let 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_PromiseAllAndThen2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.ts new file mode 100644 index 0000000000000..c9583f26ae490 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.ts @@ -0,0 +1,19 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().then(function () { + return 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(); + let 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_PromiseDotAll.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseDotAll.ts new file mode 100644 index 0000000000000..41ce94971d784 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseDotAll.ts @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/():Promise{ + return Promise.all([fetch('https://typescriptlang.org'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(function(vals){ + vals.forEach(console.log); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f():Promise{ + let vals = await Promise.all([fetch('https://typescriptlang.org'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); + vals.forEach(console.log); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts index 4771c45efcc3e..602e8eb8c6029 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts @@ -19,7 +19,7 @@ function /*[#|*/f/*|]*/() { async function f() { var var1:Promise, var2; - return fetch('https://typescriptlang.org'); + await fetch('https://typescriptlang.org'); let res_2 = await Promise.resolve(); var2 = "test"; let res_1 = fetch("https://microsoft.com"); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.js new file mode 100644 index 0000000000000..a8751dcb0288b --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.js @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let i; + return Promise.resolve().then(res => res ? i = res : i = 100); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let i; + let res = await Promise.resolve(); + return res ? i = res : i = 100; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.ts new file mode 100644 index 0000000000000..a8751dcb0288b --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.ts @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let i; + return Promise.resolve().then(res => res ? i = res : i = 100); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let i; + let res = await Promise.resolve(); + return res ? i = res : i = 100; +} From d0d4aff92fe5d8e6575dbe27da90b6a38fd2905c Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 10 Jul 2018 16:49:46 -0700 Subject: [PATCH 099/196] Initial buggy commit of returning vars that have callbacks elsewhere --- .../codefixes/convertToAsyncFunction.ts | 47 ++++++---- src/services/suggestionDiagnostics.ts | 90 ++++++++++--------- 2 files changed, 77 insertions(+), 60 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index f8805d9f28f11..deb8792f79c74 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -21,19 +21,28 @@ namespace ts.codefix { let synthNamesMap: Map = new MapCtr(); const funcToConvertRenamed = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); - const retStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed); + const retStmts: Node[] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); for (const stmt of retStmts) { - forEachChild(stmt, function visit(node: Node) { - if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap); - if (newNodes.length > 0) { - changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); - } - } - else if (!isFunctionLike(node)) { - forEachChild(node, visit); + if (isCallExpression(stmt)) { + const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap); + if (newNodes.length > 0) { + changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } - }); + + } + else { + forEachChild(stmt, function visit(node: Node) { + if (isCallExpression(node)) { + const newNodes = parseCallback(node, checker, node, synthNamesMap); + if (newNodes.length > 0) { + changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); + } + } + else if (!isFunctionLike(node)) { + forEachChild(node, visit); + } + }); + } } } @@ -159,10 +168,14 @@ namespace ts.codefix { } function parsePromiseCall(node: CallExpression, checker: TypeChecker, prevArgName?: string): Statement[] { - if (prevArgName && getNextDotThen(node.original!.parent as Expression, checker) && isPropertyAccessExpression(node.original!.parent)) { + let nextDotThen = getNextDotThen(node.original!.parent as Expression, checker); + if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] } + else if (!prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { + return [createStatement(createAwait(node))]; + } return [createReturn(node)]; } @@ -205,14 +218,14 @@ namespace ts.codefix { case SyntaxKind.ArrowFunction: if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - let innerRetStmts: ReturnStatement[] = getReturnStatementsWithPromiseCallbacks(func.body as Node); + let innerRetStmts: Node[] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); let innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } const nextOutermostDotThen = getNextDotThen(parent.original!.parent as Expression, checker); - return nextOutermostDotThen ? removeReturns(func.body.statements, prevArgName!) : func.body.statements + return nextOutermostDotThen ? removeReturns(func.body.statements, prevArgName!) : func.body.statements } else if (isArrowFunction(func)) { //if there is another outer dot then, don't actually return @@ -237,10 +250,10 @@ namespace ts.codefix { let ret: Statement[] = []; for (let stmt of stmts) { if (isReturnStatement(stmt)) { - if (stmt.expression){ + if (stmt.expression) { ret.push(createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], NodeFlags.Let)))); } - } + } else { ret.push(stmt); } @@ -249,7 +262,7 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: ReturnStatement[], synthNamesMap: Map, prevArgName?: string) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, prevArgName?: string) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 454343d90adbd..285569ed4470b 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -69,7 +69,7 @@ namespace ts { } } - if(isFunctionLikeDeclaration(node) || isArrowFunction(node) || isMethodDeclaration(node)){ + if (isFunctionLikeDeclaration(node) || isArrowFunction(node) || isMethodDeclaration(node)) { addConvertToAsyncFunctionDiagnostics(node, checker, diags) } node.forEachChild(check); @@ -113,30 +113,24 @@ namespace ts { } } - function addConvertToAsyncFunctionDiagnostics(node: Node, checker: TypeChecker, diags: DiagnosticWithLocation[]): void{ + function addConvertToAsyncFunctionDiagnostics(node: Node, checker: TypeChecker, diags: DiagnosticWithLocation[]): void { if (isAsyncFunction(node)) { return; } - const signature = checker.getSignatureFromDeclaration(node) - if (!signature) { - return; + if (isFunctionLikeDeclaration(node) && !node.body) { + return; // break on ambient functions } - - const returnType = checker.getReturnTypeOfSignature(signature); - if (!returnType || !returnType.symbol) { + const returnType = checker.getTypeAtLocation(node); + if (!returnType) { return; } - if (isFunctionLikeDeclaration(node) && !node.body) { - return; // break on ambient functions - } - if (checker.isPromiseLikeType(returnType)) { // collect all the return statements // check that a property access expression exists in there and that it is a handler - const retStmts = getReturnStatementsWithPromiseCallbacks(node); + const retStmts = getReturnStatementsWithPromiseCallbacks(node, checker); if (retStmts.length > 0) { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function)); } @@ -147,51 +141,61 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - export function getReturnStatementsWithPromiseCallbacks(node: Node): ReturnStatement[] { + export function getReturnStatementsWithPromiseCallbacks(node: Node, checker: TypeChecker): Node[] { - const retStmts: ReturnStatement[] = []; + const retStmts: Node[] = []; forEachChild(node, visit); - /* - function visit(node: Node) { - if (isFunctionLike(node)) { - return; - } + function visit(child: Node) { - if (isReturnStatement(node)) { - if (isCallExpression(node) && isPropertyAccessExpression(node.expression) && - (node.expression.name.text === "then" || node.expression.name.text === "catch")) { - retStmts.push(node as ReturnStatement); - } - else if (!isFunctionLike(node)) { - forEachChild(node, visit); - } - } - } - */ - - function visit(node: Node) { - if (isFunctionLike(node)) { + if (isFunctionLike(child)) { return; } - if (isReturnStatement(node)) { - forEachChild(node, hasCallback); + if (isReturnStatement(child)) { + forEachChild(child, hasCallback); } - function hasCallback(child: Node) { - if (isCallExpression(child) && isPropertyAccessExpression(child.expression) && - (child.expression.name.text === "then" || child.expression.name.text === "catch")) { - retStmts.push(node as ReturnStatement); + function hasCallback(returnChild: Node) { + let symbol = checker.getSymbolAtLocation(returnChild); + if (isCallExpression(returnChild) && isPropertyAccessExpression(returnChild.expression) && + (returnChild.expression.name.text === "then" || returnChild.expression.name.text === "catch")) { + retStmts.push(child as ReturnStatement); + } + else if (isIdentifier(returnChild)) { + forEachChild(node, findCallbackUses); } - else if (!isFunctionLike(child)) { - forEachChild(child, hasCallback); + else if (!isFunctionLike(returnChild)) { + forEachChild(returnChild, hasCallback); + } + + function findCallbackUses(identUse: Node) { + + if (isVariableDeclaration(identUse) && identUse.initializer && isCallback(identUse.initializer)){ + if (symbol === checker.getSymbolAtLocation(identUse.name)){ + retStmts.push(identUse.initializer); + } + } + else if (isCallback(identUse)) { + if (symbol === checker.getSymbolAtLocation((identUse).expression)){ + retStmts.push(identUse as CallExpression); + } + } + else { + forEachChild(identUse, findCallbackUses); + } } } - forEachChild(node, visit); + forEachChild(child, visit); } return retStmts; } + + function isCallback(node: Node): boolean { + return (isCallExpression(node) && isPropertyAccessExpression(node.expression) && + (node.expression.name.text === "then" || node.expression.name.text === "catch")); + + } } From b88022baef19a38a6f0178590b16e32f711c94fb Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 11 Jul 2018 17:08:30 -0700 Subject: [PATCH 100/196] More progress on returning variables that reference promises --- .../codefixes/convertToAsyncFunction.ts | 61 +++++++++++-------- src/services/suggestionDiagnostics.ts | 16 +++-- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index deb8792f79c74..5574a0d475e6f 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -22,18 +22,29 @@ namespace ts.codefix { const funcToConvertRenamed = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); const retStmts: Node[] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); + let retStmtName: string; + for (const stmt of retStmts) { + let varDeclName: string; + + if (isVariableDeclarationList(stmt)) { + varDeclName = (stmt.declarations[0].name).text; + } + + if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { + retStmtName = stmt.expression.text; + } + if (isCallExpression(stmt)) { const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap); if (newNodes.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } - } else { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap); + const newNodes = parseCallback(node, checker, node, synthNamesMap, retStmtName, varDeclName); if (newNodes.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } @@ -105,71 +116,71 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string, varDeclName?: string): Statement[] { if (!node) { return []; } if (isCallExpression(node) && returnsAPromise(node, checker)) { - return parsePromiseCall(node, checker, prevArgName); + return parsePromiseCall(node, checker, prevArgName, varDeclName); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, outermostParent, synthNamesMap, prevArgName); + return parseThen(node, checker, outermostParent, synthNamesMap, prevArgName, varDeclName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, synthNamesMap, prevArgName); - } + return parseCatch(node, checker, synthNamesMap, prevArgName, varDeclName); + } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName, varDeclName); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, prevArgName?: string): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, prevArgName?: string, varDeclName?: string): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argName)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argName, varDeclName)); - const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, varDeclName); const catchClause = createCatchClause(argName, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string, varDeclName?: string): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier if (!res) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName, varDeclName); } const argNameRes = getArgName(res, checker, synthNamesMap); - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, varDeclName); if (rej) { const argNameRej = getArgName(rej, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argNameRes).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argNameRes, varDeclName).concat(callbackBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, true); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, varDeclName, true); const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - return parseCallback(node.expression, checker, node, synthNamesMap, argNameRes).concat(callbackBody); + return parseCallback(node.expression, checker, node, synthNamesMap, argNameRes, varDeclName).concat(callbackBody); } return []; } - function parsePromiseCall(node: CallExpression, checker: TypeChecker, prevArgName?: string): Statement[] { + function parsePromiseCall(node: CallExpression, checker: TypeChecker, prevArgName?: string, varDeclName?: string): Statement[] { let nextDotThen = getNextDotThen(node.original!.parent as Expression, checker); - if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { + if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (prevArgName && varDeclName)) { const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] } @@ -193,7 +204,7 @@ namespace ts.codefix { } } - function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, varDeclName?: string, isRej = false): NodeArray { if (!prevArgName && argName) { prevArgName = argName; } @@ -219,7 +230,7 @@ namespace ts.codefix { if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { let innerRetStmts: Node[] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); - let innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName); + let innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } @@ -230,14 +241,14 @@ namespace ts.codefix { } else if (isArrowFunction(func)) { //if there is another outer dot then, don't actually return - let innerCbBody = getInnerCallbackBody(checker, [createReturn(func.body as Expression)], synthNamesMap, prevArgName); + let innerCbBody = getInnerCallbackBody(checker, [createReturn(func.body as Expression)], synthNamesMap, prevArgName, varDeclName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } const nextOutermostDotThen = getNextDotThen(parent.original!.parent as Expression, checker); - return nextOutermostDotThen ? + return nextOutermostDotThen || (prevArgName && varDeclName) ? createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]) : createNodeArray([createReturn(func.body as Expression)]); } @@ -262,12 +273,12 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, prevArgName?: string) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, prevArgName?: string, varDeclName?: string) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - let temp = parseCallback(node, checker, node, synthNamesMap, prevArgName); + let temp = parseCallback(node, checker, node, synthNamesMap, prevArgName, varDeclName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 285569ed4470b..b5ad149c8dd45 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -158,11 +158,13 @@ namespace ts { function hasCallback(returnChild: Node) { let symbol = checker.getSymbolAtLocation(returnChild); - if (isCallExpression(returnChild) && isPropertyAccessExpression(returnChild.expression) && - (returnChild.expression.name.text === "then" || returnChild.expression.name.text === "catch")) { + + if (isCallback(returnChild)) { retStmts.push(child as ReturnStatement); } - else if (isIdentifier(returnChild)) { + else if (isIdentifier(returnChild) && isReturnStatement(child) + && child.expression && isIdentifier(child.expression)) { + retStmts.push(child); forEachChild(node, findCallbackUses); } else if (!isFunctionLike(returnChild)) { @@ -171,9 +173,11 @@ namespace ts { function findCallbackUses(identUse: Node) { - if (isVariableDeclaration(identUse) && identUse.initializer && isCallback(identUse.initializer)){ - if (symbol === checker.getSymbolAtLocation(identUse.name)){ - retStmts.push(identUse.initializer); + // TODO -> fix for multiple length variable decl lists + if (isVariableDeclarationList(identUse) && identUse.declarations.length == 1 && + identUse.declarations[0].initializer && isCallback(identUse.declarations[0].initializer!)){ + if (symbol === checker.getSymbolAtLocation(identUse.declarations[0].name)){ + retStmts.push(identUse); } } else if (isCallback(identUse)) { From 71f941935a5c031597b12e6d8df2c43f4312b42d Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 12 Jul 2018 10:47:53 -0700 Subject: [PATCH 101/196] Returning variables that hold promises with callbacks working in simple cases --- .../codefixes/convertToAsyncFunction.ts | 24 +++++++++++-------- src/services/suggestionDiagnostics.ts | 5 +++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 5574a0d475e6f..3c70132b783c9 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -27,22 +27,25 @@ namespace ts.codefix { for (const stmt of retStmts) { let varDeclName: string; - if (isVariableDeclarationList(stmt)) { - varDeclName = (stmt.declarations[0].name).text; - } - - if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { - retStmtName = stmt.expression.text; - } - if (isCallExpression(stmt)) { const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap); if (newNodes.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } } + else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { + retStmtName = stmt.expression.text; + } else { forEachChild(stmt, function visit(node: Node) { + if (isVariableDeclarationList(node)) { + varDeclName = (node.declarations[0].name).text; + } + + if (isReturnStatement(node) && node.expression && isIdentifier(node.expression)) { + retStmtName = node.expression.text; + } + if (isCallExpression(node)) { const newNodes = parseCallback(node, checker, node, synthNamesMap, retStmtName, varDeclName); if (newNodes.length > 0) { @@ -129,7 +132,7 @@ namespace ts.codefix { } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { return parseCatch(node, checker, synthNamesMap, prevArgName, varDeclName); - } + } else if (isPropertyAccessExpression(node)) { return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName, varDeclName); } @@ -241,7 +244,8 @@ namespace ts.codefix { } else if (isArrowFunction(func)) { //if there is another outer dot then, don't actually return - let innerCbBody = getInnerCallbackBody(checker, [createReturn(func.body as Expression)], synthNamesMap, prevArgName, varDeclName); + let innerRetStmts: Node[] = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), checker); + let innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index b5ad149c8dd45..a4bba0cd52af5 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -171,13 +171,15 @@ namespace ts { forEachChild(returnChild, hasCallback); } + let parent: Node; + function findCallbackUses(identUse: Node) { // TODO -> fix for multiple length variable decl lists if (isVariableDeclarationList(identUse) && identUse.declarations.length == 1 && identUse.declarations[0].initializer && isCallback(identUse.declarations[0].initializer!)){ if (symbol === checker.getSymbolAtLocation(identUse.declarations[0].name)){ - retStmts.push(identUse); + retStmts.push(parent); } } else if (isCallback(identUse)) { @@ -186,6 +188,7 @@ namespace ts { } } else { + parent = identUse; forEachChild(identUse, findCallbackUses); } } From 08dd6a2f9bdc55dd69b3432952090090237d34d6 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 12 Jul 2018 10:48:53 -0700 Subject: [PATCH 102/196] Added tests and baselines --- .../unittests/convertToAsyncFunction.ts | 117 +++++++++++++++--- .../convertToAsyncFunction_VarReturn1.js | 14 +++ .../convertToAsyncFunction_VarReturn1.ts | 14 +++ 3 files changed, 125 insertions(+), 20 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 1ca3ab35aa33a..a9fa779d0f49d 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -406,6 +406,82 @@ function [#|innerPromise|](): Promise { } ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_VarReturn1", ` +function [#|f|]() { + let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); + return blob; +} +` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_VarReturn2", ` +function [#|f|]() { + let blob = fetch("https://typescriptlang.org") + blob.then(resp => console.log(resp)); + return blob; +} +` + ); +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn3", ` +function [#|f|]() { + let blob = fetch("https://typescriptlang.org") + let blob2 = blob.then(resp => console.log(resp)); + blob2.catch(err); + return blob; +} + +function err (rej) { + console.log(rej) +} +` + ); +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn4", ` +function [#|f|]() { + var blob = fetch("https://typescriptlang.org").then(res => console.log(res)), blob2 = fetch("https://microsoft.com").then(res => res.ok).catch(err); + return blob; +} +function err (rej) { + console.log(rej) +} +` + ); + +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn5", ` +function [#|f|]() { + var blob = fetch("https://typescriptlang.org").then(res => console.log(res)); + blob.then(x => x); + return blob; +} +` + ); + +_testConvertToAsyncFunction("convertToAsyncFunction_Param", ` +function [#|f|]() { + my_print(fetch("https://typescriptlang.org").then(res => console.log(res))) +} +function my_print (resp) { + if (resp.ok) { + console.log(resp.buffer); + } +} +` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_SeperateLines", ` +function [#|f|](): Promise { + var blob = fetch("https://typescriptlang.org") + blob.then(resp => { + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + }); + blob.then(blob => { + return blob.toString(); + }); + + return blob; +} +` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_InnerVarNameConflict", ` function [#|f|](): Promise { var blob = fetch("https://typescriptlang.org").then(resp => { @@ -437,8 +513,9 @@ function [#|f|]() { }); } ` -); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen2", ` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen2", ` function [#|f|]() { return Promise.resolve().then(function () { return Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { @@ -447,8 +524,8 @@ function [#|f|]() { }); } ` -); - _testConvertToAsyncFunction("convertToAsyncFunction_Scope", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_Scope", ` function [#|f|]() { var var1:Promise, var2; return fetch('https://typescriptlang.org').then( _ => @@ -465,7 +542,7 @@ function [#|f|]() { } ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_Conditionals", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Conditionals", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => { if (res.ok) { @@ -482,9 +559,9 @@ function [#|f|](){ }); } ` -); + ); -_testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThen", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThen", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -497,26 +574,26 @@ function rej(reject){ return reject; } ` -); + ); -_testConvertToAsyncFunction("convertToAsyncFunction_Scope2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Scope2", ` function [#|f|](){ var i:number; return fetch("https://typescriptlang.org").then(i => i.ok).then(res => i+1).catch(err => i-1) } ` -); + ); -_testConvertToAsyncFunction("convertToAsyncFunction_Loop", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Loop", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => { for(let i=0; i<10; i++){ console.log(res); }}) } ` -); + ); -_testConvertToAsyncFunction("convertToAsyncFunction_Conditional2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Conditional2", ` function [#|f|](){ var res = 100; if (res > 50) { @@ -531,9 +608,9 @@ function res_func(result){ console.log(result); } ` -); + ); -_testConvertToAsyncFunction("convertToAsyncFunction_Scope3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Scope3", ` function [#|f|]() { var obj; return fetch("https://typescriptlang.org").then(function (res) { @@ -545,9 +622,9 @@ function [#|f|]() { }); } ` -); + ); -_testConvertToAsyncFunction("convertToAsyncFunction_NestedFunction", ` + _testConvertToAsyncFunction("convertToAsyncFunction_NestedFunction", ` function [#|f|]() { function fn2(){ function fn3(){ @@ -559,20 +636,20 @@ function [#|f|]() { } `); -_testConvertToAsyncFunction("convertToAsyncFunction_UntypedFunction", ` + _testConvertToAsyncFunction("convertToAsyncFunction_UntypedFunction", ` function [#|f|]() { return Promise.resolve().then(res => console.log(res)); } `); -_testConvertToAsyncFunction("convertToAsyncFunction_TernaryConditional", ` + _testConvertToAsyncFunction("convertToAsyncFunction_TernaryConditional", ` function [#|f|]() { let i; return Promise.resolve().then(res => res ? i = res : i = 100); } `); -}); + }); function _testConvertToAsyncFunction(caption: string, text: string, includeLib?: boolean) { testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.Convert_to_async_function, includeLib); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.js new file mode 100644 index 0000000000000..6ebf2b965917e --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.js @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let resp = await fetch("https://typescriptlang.org"); + let blob = console.log(resp); + return blob; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.ts new file mode 100644 index 0000000000000..6ebf2b965917e --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.ts @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let resp = await fetch("https://typescriptlang.org"); + let blob = console.log(resp); + return blob; +} From 4e7a1c06e9bc3361f4a95c8014269e22b1bf6b37 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 13 Jul 2018 15:19:00 -0700 Subject: [PATCH 103/196] Added functionality to glue multiple callbacks together and refactor --- .../codefixes/convertToAsyncFunction.ts | 154 +++++++++++------- src/services/suggestionDiagnostics.ts | 19 +-- 2 files changed, 104 insertions(+), 69 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 3c70132b783c9..d99f4596127a0 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -8,7 +8,7 @@ namespace ts.codefix { return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_async_function, fixId, Diagnostics.Convert_all_to_async_functions)]; }, fixIds: [fixId], - getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file!, err.start, context.program.getTypeChecker())), + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file, err.start, context.program.getTypeChecker())), }); function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { // get the function declaration - returns a promise @@ -17,59 +17,95 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); - let varNamesMap: Map = new MapCtr(); - let synthNamesMap: Map = new MapCtr(); + const varNamesMap: Map = new MapCtr(); + const synthNamesMap: Map = new MapCtr(); const funcToConvertRenamed = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); const retStmts: Node[] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); - let retStmtName: string; - for (const stmt of retStmts) { - let varDeclName: string; - - if (isCallExpression(stmt)) { - const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap); - if (newNodes.length > 0) { - changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); - } + const glued = glueTogetherCallbacks(retStmts); + const gluedCallback = glued[0]; + let retStmtName = glued[1]; + if (gluedCallback) { + const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, retStmtName, retStmtName); + if (newNodes.length > 0) { + changes.replaceNodeRangeWithNodes(sourceFile, retStmts[1].original!, retStmts[retStmts.length - 1].original!, newNodes); } - else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { - retStmtName = stmt.expression.text; - } - else { - forEachChild(stmt, function visit(node: Node) { - if (isVariableDeclarationList(node)) { - varDeclName = (node.declarations[0].name).text; + } + else { + for (const stmt of retStmts) { + if (isCallExpression(stmt)) { + const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, retStmtName, retStmtName); + if (newNodes.length > 0) { + changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } + } + else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { + retStmtName = stmt.expression.text; + } + else { + forEachChild(stmt, function visit(node: Node) { - if (isReturnStatement(node) && node.expression && isIdentifier(node.expression)) { - retStmtName = node.expression.text; - } + if (isReturnStatement(node) && node.expression && isIdentifier(node.expression)) { + retStmtName = node.expression.text; + } - if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, retStmtName, varDeclName); - if (newNodes.length > 0) { - changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); + if (isCallExpression(node)) { + const newNodes = parseCallback(node, checker, node, synthNamesMap, retStmtName, retStmtName); + if (newNodes.length > 0) { + changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); + } } - } - else if (!isFunctionLike(node)) { - forEachChild(node, visit); - } - }); + else if (!isFunctionLike(node)) { + forEachChild(node, visit); + } + }); + } } } } + function glueTogetherCallbacks(retStmts: Node[]): [CallExpression | undefined, string] { + retStmts = retStmts.slice(0); + const stmt = retStmts.pop(); + if (!stmt) { + return [undefined, ""]; + } + + if (isExpressionStatement(stmt) && stmt.expression && isCallExpression(stmt.expression) + && stmt.expression.expression && isPropertyAccessExpression(stmt.expression.expression)) { + const callArgs: NodeArray = stmt.expression.arguments; + const funcName: Identifier = stmt.expression.expression.name; + const [gluedExpr, retName] = glueTogetherCallbacks(retStmts); + if (gluedExpr) { + const propertyAccessExpr = createPropertyAccess(gluedExpr, funcName); + return [createCall(propertyAccessExpr, /*typeArguments*/ undefined, callArgs), retName]; + } + } + // fix this for multiple declarations + else if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 0 && stmt.declarationList.declarations[0].initializer) { + return [glueTogetherCallbacks([stmt.declarationList.declarations[0].initializer!])[0], glueTogetherCallbacks(retStmts)[1]]; + } + else if (isCallExpression(stmt)) { + return [stmt, glueTogetherCallbacks(retStmts)[1]]; + } + else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { + return [undefined, stmt.expression.text]; + } + + return [undefined, ""]; + } + + function renameCollidingVarNames(nodeToRename: Node, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map): Node { - let allVarNames: string[] = []; + const allVarNames: string[] = []; forEachChild(nodeToRename, function visit(node: Node) { if (isIdentifier(node)) { - let type = checker.getTypeAtLocation(node); - let symbol = checker.getSymbolAtLocation(node); - - let newName = getNewNameIfConflict(node.text, allVarNames); + const type = checker.getTypeAtLocation(node); + const symbol = checker.getSymbolAtLocation(node); + const newName = getNewNameIfConflict(node.text, allVarNames); if (symbol && type && type.getCallSignatures().length > 0 && type.getCallSignatures()[0].parameters.length > 0) { // first, add the actual function name @@ -87,8 +123,8 @@ namespace ts.codefix { allVarNames.push(node.text); // next, add the new variable for the declaration - let synthName = type.getCallSignatures()[0].parameters[0].name; - let newSynthName = getNewNameIfConflict(synthName, allVarNames); + const synthName = type.getCallSignatures()[0].parameters[0].name; + const newSynthName = getNewNameIfConflict(synthName, allVarNames); varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(createIdentifier(newSynthName))))), newSynthName); allVarNames.push(newSynthName); synthNamesMap.set(node.text, newSynthName); @@ -102,7 +138,7 @@ namespace ts.codefix { forEachChild(node, visit); }); - return getSynthesizedDeepClone(nodeToRename, true, varNamesMap, checker); + return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, varNamesMap, checker); } function getNewNameIfConflict(name: string, allVarNames: string[]) { @@ -169,7 +205,7 @@ namespace ts.codefix { const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argNameRes, varDeclName).concat(callbackBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, varDeclName, true); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, varDeclName, /*isRej*/ true); const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; @@ -182,10 +218,10 @@ namespace ts.codefix { } function parsePromiseCall(node: CallExpression, checker: TypeChecker, prevArgName?: string, varDeclName?: string): Statement[] { - let nextDotThen = getNextDotThen(node.original!.parent as Expression, checker); + const nextDotThen = getNextDotThen(node.original!.parent as Expression, checker); if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (prevArgName && varDeclName)) { const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); - return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))] + return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))]; } else if (!prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { return [createStatement(createAwait(node))]; @@ -212,6 +248,9 @@ namespace ts.codefix { prevArgName = argName; } + const outerParent = parent.original ? parent.original.parent : undefined; + const nextDotThen = getNextDotThen(outerParent as Expression, checker); + switch (func.kind) { case SyntaxKind.Identifier: if (!prevArgName || !argName) { @@ -219,7 +258,6 @@ namespace ts.codefix { } let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); - const nextDotThen = getNextDotThen(parent.original!.parent as Expression, checker); if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { return createNodeArray([createReturn(synthCall)]); } @@ -232,28 +270,26 @@ namespace ts.codefix { case SyntaxKind.ArrowFunction: if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - let innerRetStmts: Node[] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); - let innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclName); + const innerRetStmts: Node[] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - const nextOutermostDotThen = getNextDotThen(parent.original!.parent as Expression, checker); - return nextOutermostDotThen ? removeReturns(func.body.statements, prevArgName!) : func.body.statements + return nextDotThen ? removeReturns(func.body.statements, prevArgName!) : func.body.statements; - } else if (isArrowFunction(func)) { - //if there is another outer dot then, don't actually return + } + else if (isArrowFunction(func)) { + // if there is another outer dot then, don't actually return - let innerRetStmts: Node[] = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), checker); - let innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclName); + const innerRetStmts: Node[] = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), checker); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - const nextOutermostDotThen = getNextDotThen(parent.original!.parent as Expression, checker); - - return nextOutermostDotThen || (prevArgName && varDeclName) ? - createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, (func as ArrowFunction).body as Expression)], NodeFlags.Let)))]) : + return nextDotThen || (prevArgName && varDeclName) ? + createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], NodeFlags.Let)))]) : createNodeArray([createReturn(func.body as Expression)]); } break; @@ -262,8 +298,8 @@ namespace ts.codefix { } function removeReturns(stmts: NodeArray, prevArgName: string): NodeArray { - let ret: Statement[] = []; - for (let stmt of stmts) { + const ret: Statement[] = []; + for (const stmt of stmts) { if (isReturnStatement(stmt)) { if (stmt.expression) { ret.push(createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], NodeFlags.Let)))); @@ -282,7 +318,7 @@ namespace ts.codefix { for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - let temp = parseCallback(node, checker, node, synthNamesMap, prevArgName, varDeclName); + const temp = parseCallback(node, checker, node, synthNamesMap, prevArgName, varDeclName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; @@ -297,7 +333,7 @@ namespace ts.codefix { } function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { - // can probably get rid of this if statement + // can probably get rid of this if statement if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; } diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index a4bba0cd52af5..90785fef378cf 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -70,7 +70,7 @@ namespace ts { } if (isFunctionLikeDeclaration(node) || isArrowFunction(node) || isMethodDeclaration(node)) { - addConvertToAsyncFunctionDiagnostics(node, checker, diags) + addConvertToAsyncFunctionDiagnostics(node, checker, diags); } node.forEachChild(check); } @@ -157,12 +157,12 @@ namespace ts { } function hasCallback(returnChild: Node) { - let symbol = checker.getSymbolAtLocation(returnChild); + const symbol = checker.getSymbolAtLocation(returnChild); if (isCallback(returnChild)) { retStmts.push(child as ReturnStatement); } - else if (isIdentifier(returnChild) && isReturnStatement(child) + else if (isIdentifier(returnChild) && isReturnStatement(child) && child.expression && isIdentifier(child.expression)) { retStmts.push(child); forEachChild(node, findCallbackUses); @@ -176,21 +176,21 @@ namespace ts { function findCallbackUses(identUse: Node) { // TODO -> fix for multiple length variable decl lists - if (isVariableDeclarationList(identUse) && identUse.declarations.length == 1 && - identUse.declarations[0].initializer && isCallback(identUse.declarations[0].initializer!)){ - if (symbol === checker.getSymbolAtLocation(identUse.declarations[0].name)){ + if (isVariableDeclarationList(identUse) && identUse.declarations.length === 1 && + identUse.declarations[0].initializer && (isCallExpression(identUse.declarations[0].initializer!))) { + if (symbol === checker.getSymbolAtLocation(identUse.declarations[0].name)) { retStmts.push(parent); } } else if (isCallback(identUse)) { - if (symbol === checker.getSymbolAtLocation((identUse).expression)){ - retStmts.push(identUse as CallExpression); + if (symbol === checker.getSymbolAtLocation(((identUse).expression).expression)) { + retStmts.push(parent as CallExpression); } } else { parent = identUse; forEachChild(identUse, findCallbackUses); - } + } } } @@ -203,6 +203,5 @@ namespace ts { function isCallback(node: Node): boolean { return (isCallExpression(node) && isPropertyAccessExpression(node.expression) && (node.expression.name.text === "then" || node.expression.name.text === "catch")); - } } From 67275d8343bec8884a285e81d0a451a19aa9aac9 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 13 Jul 2018 15:19:46 -0700 Subject: [PATCH 104/196] Added tests and baseliens --- .../unittests/convertToAsyncFunction.ts | 37 +++++++++++++++++-- .../convertToAsyncFunction_VarReturn2.js | 15 ++++++++ .../convertToAsyncFunction_VarReturn2.ts | 15 ++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index a9fa779d0f49d..79e5aa26c760b 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -141,7 +141,7 @@ namespace ts { const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === description.message); assert.isNotNull(diagnostic); - const actions = codefix.getFixes(context) + const actions = codefix.getFixes(context); const action = find(actions, action => action.description === description.message)!; assert.isNotNull(action); @@ -415,7 +415,7 @@ function [#|f|]() { ); _testConvertToAsyncFunction("convertToAsyncFunction_VarReturn2", ` function [#|f|]() { - let blob = fetch("https://typescriptlang.org") + let blob = fetch("https://typescriptlang.org"); blob.then(resp => console.log(resp)); return blob; } @@ -454,6 +454,37 @@ function [#|f|]() { ` ); +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn6", ` +function [#|f|]() { + var blob = fetch("https://typescriptlang.org"); + return blob; +} +` +); + +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn7", ` +function [#|f|]() { + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + return blob; +} +` +); + +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn8", ` +function [#|f|]() { + let blob = fetch("https://typescriptlang.org"); + if (!blob.ok){ + return blob; + } + blob.then(resp => console.log(resp)); + return blob; +} +` +); + _testConvertToAsyncFunction("convertToAsyncFunction_Param", ` function [#|f|]() { my_print(fetch("https://typescriptlang.org").then(res => console.log(res))) @@ -540,7 +571,7 @@ function [#|f|]() { function res(response){ console.log(response); } -` ); +`); _testConvertToAsyncFunction("convertToAsyncFunction_Conditionals", ` function [#|f|](){ diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.js new file mode 100644 index 0000000000000..d59741483a60f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.js @@ -0,0 +1,15 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob = fetch("https://typescriptlang.org"); + blob.then(resp => console.log(resp)); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let resp = await fetch("https://typescriptlang.org"); + let blob = console.log(resp); + return blob; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.ts new file mode 100644 index 0000000000000..d59741483a60f --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.ts @@ -0,0 +1,15 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob = fetch("https://typescriptlang.org"); + blob.then(resp => console.log(resp)); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let resp = await fetch("https://typescriptlang.org"); + let blob = console.log(resp); + return blob; +} From f00af95f60af60f4ffefa4400e3f0512d9528a3e Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 13 Jul 2018 15:19:57 -0700 Subject: [PATCH 105/196] Fixed linting errors --- src/services/utilities.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 14655f82203f0..84506edc26c8a 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1627,21 +1627,21 @@ namespace ts { * and code fixes (because those are triggered by explicit user actions). */ export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker): T { - const clone = renameMap && checker && needsRenaming(node, checker) ? - node && createIdentifier(renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))!) : + const clone = renameMap && checker && needsRenaming(node, checker) ? + node && createIdentifier(renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))!) : node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, checker); if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); return clone as T; } - function needsRenaming(node: T | undefined, checker: TypeChecker): boolean{ + function needsRenaming(node: T | undefined, checker: TypeChecker): boolean { return !!(node && isIdentifier(node!) && checker.getSymbolAtLocation(node!)); } function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker): T { - const visited = visitEachChild(node, function wrapper(node){ - return getSynthesizedDeepClone(node, true, renameMap, checker); + const visited = visitEachChild(node, function wrapper(node) { + return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, checker); }, nullTransformationContext); if (visited === node) { From ac1046b6ff9e6255f4ada97fbe60fc08ecfc02be Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 13 Jul 2018 16:12:44 -0700 Subject: [PATCH 106/196] Initial commit of buggy passing node flags --- .../codefixes/convertToAsyncFunction.ts | 67 +++++++++---------- src/services/suggestionDiagnostics.ts | 22 +++--- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index d99f4596127a0..dc5c7aa95bcd4 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -21,13 +21,13 @@ namespace ts.codefix { const synthNamesMap: Map = new MapCtr(); const funcToConvertRenamed = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); - const retStmts: Node[] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); + const [retStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); const glued = glueTogetherCallbacks(retStmts); const gluedCallback = glued[0]; let retStmtName = glued[1]; if (gluedCallback) { - const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, retStmtName, retStmtName); + const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, retStmtName, varDeclFlags); if (newNodes.length > 0) { changes.replaceNodeRangeWithNodes(sourceFile, retStmts[1].original!, retStmts[retStmts.length - 1].original!, newNodes); } @@ -35,7 +35,7 @@ namespace ts.codefix { else { for (const stmt of retStmts) { if (isCallExpression(stmt)) { - const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, retStmtName, retStmtName); + const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, retStmtName, varDeclFlags); if (newNodes.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } @@ -51,7 +51,7 @@ namespace ts.codefix { } if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, retStmtName, retStmtName); + const newNodes = parseCallback(node, checker, node, synthNamesMap, retStmtName, varDeclFlags); if (newNodes.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } @@ -155,73 +155,72 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string, varDeclName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { if (!node) { return []; } if (isCallExpression(node) && returnsAPromise(node, checker)) { - return parsePromiseCall(node, checker, prevArgName, varDeclName); + return parsePromiseCall(node, checker, prevArgName, varDeclFlags); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, outermostParent, synthNamesMap, prevArgName, varDeclName); + return parseThen(node, checker, outermostParent, synthNamesMap, prevArgName, varDeclFlags); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, synthNamesMap, prevArgName, varDeclName); + return parseCatch(node, checker, synthNamesMap, prevArgName, varDeclFlags); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName, varDeclName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName, varDeclFlags); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, prevArgName?: string, varDeclName?: string): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argName, varDeclName)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argName, varDeclFlags)); - const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, varDeclName); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap); const catchClause = createCatchClause(argName, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string, varDeclName?: string): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier if (!res) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName, varDeclName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName, varDeclFlags); } - const argNameRes = getArgName(res, checker, synthNamesMap); - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, varDeclName); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap); if (rej) { const argNameRej = getArgName(rej, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argNameRes, varDeclName).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argNameRes, varDeclFlags).concat(callbackBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, varDeclName, /*isRej*/ true); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, /*isRej*/ true); const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - return parseCallback(node.expression, checker, node, synthNamesMap, argNameRes, varDeclName).concat(callbackBody); + return parseCallback(node.expression, checker, node, synthNamesMap, argNameRes, varDeclFlags).concat(callbackBody); } return []; } - function parsePromiseCall(node: CallExpression, checker: TypeChecker, prevArgName?: string, varDeclName?: string): Statement[] { + function parsePromiseCall(node: CallExpression, checker: TypeChecker, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { const nextDotThen = getNextDotThen(node.original!.parent as Expression, checker); - if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (prevArgName && varDeclName)) { + if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (prevArgName && varDeclFlags)) { const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); - return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], NodeFlags.Let)))]; + return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], varDeclFlags ? varDeclFlags : NodeFlags.Let)))]; } else if (!prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { return [createStatement(createAwait(node))]; @@ -243,7 +242,7 @@ namespace ts.codefix { } } - function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, varDeclName?: string, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, isRej = false): NodeArray { if (!prevArgName && argName) { prevArgName = argName; } @@ -270,26 +269,26 @@ namespace ts.codefix { case SyntaxKind.ArrowFunction: if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - const innerRetStmts: Node[] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclName); + const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclFlags); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - return nextDotThen ? removeReturns(func.body.statements, prevArgName!) : func.body.statements; + return nextDotThen ? removeReturns(func.body.statements, prevArgName!, varDeclFlags) : func.body.statements; } else if (isArrowFunction(func)) { // if there is another outer dot then, don't actually return - const innerRetStmts: Node[] = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), checker); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclName); + const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), checker); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclFlags); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - return nextDotThen || (prevArgName && varDeclName) ? - createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], NodeFlags.Let)))]) : + return nextDotThen || (prevArgName && varDeclFlags) ? + createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], varDeclFlags ? varDeclFlags : NodeFlags.Let)))]) : createNodeArray([createReturn(func.body as Expression)]); } break; @@ -297,12 +296,12 @@ namespace ts.codefix { return createNodeArray([]); } - function removeReturns(stmts: NodeArray, prevArgName: string): NodeArray { + function removeReturns(stmts: NodeArray, prevArgName: string, varDeclFlags?: NodeFlags): NodeArray { const ret: Statement[] = []; for (const stmt of stmts) { if (isReturnStatement(stmt)) { if (stmt.expression) { - ret.push(createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], NodeFlags.Let)))); + ret.push(createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], varDeclFlags ? varDeclFlags : NodeFlags.Let)))); } } else { @@ -313,12 +312,12 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, prevArgName?: string, varDeclName?: string) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = parseCallback(node, checker, node, synthNamesMap, prevArgName, varDeclName); + const temp = parseCallback(node, checker, node, synthNamesMap, prevArgName, varDeclFlags); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 90785fef378cf..f8ef8c11bf6dd 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -141,9 +141,10 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - export function getReturnStatementsWithPromiseCallbacks(node: Node, checker: TypeChecker): Node[] { + export function getReturnStatementsWithPromiseCallbacks(node: Node, checker: TypeChecker): [Node[], NodeFlags] { const retStmts: Node[] = []; + let varDeclFlags = NodeFlags.Let; forEachChild(node, visit); function visit(child: Node) { @@ -174,13 +175,14 @@ namespace ts { let parent: Node; function findCallbackUses(identUse: Node) { - - // TODO -> fix for multiple length variable decl lists - if (isVariableDeclarationList(identUse) && identUse.declarations.length === 1 && - identUse.declarations[0].initializer && (isCallExpression(identUse.declarations[0].initializer!))) { - if (symbol === checker.getSymbolAtLocation(identUse.declarations[0].name)) { - retStmts.push(parent); + if (isVariableDeclarationList(identUse)){ + for (let varDecl of identUse.declarations) { + if (varDecl.initializer && isCallExpression(varDecl.initializer) && + symbol === checker.getSymbolAtLocation(varDecl.name)){ + retStmts.push(parent); + } } + varDeclFlags = identUse.flags; } else if (isCallback(identUse)) { if (symbol === checker.getSymbolAtLocation(((identUse).expression).expression)) { @@ -190,14 +192,12 @@ namespace ts { else { parent = identUse; forEachChild(identUse, findCallbackUses); - } + } } } - forEachChild(child, visit); } - - return retStmts; + return [retStmts, varDeclFlags]; } function isCallback(node: Node): boolean { From 607adedb37af2acc8e444e6f098dc4721deb49ea Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 16 Jul 2018 16:38:22 -0700 Subject: [PATCH 107/196] new strategy for getting next dot then and fixed bugs in gluing together statements --- .../codefixes/convertToAsyncFunction.ts | 124 +++++++++++------- 1 file changed, 80 insertions(+), 44 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index dc5c7aa95bcd4..a24b635392d88 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -19,7 +19,9 @@ namespace ts.codefix { const varNamesMap: Map = new MapCtr(); const synthNamesMap: Map = new MapCtr(); - const funcToConvertRenamed = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); + const lastDotThenMap: Map = new MapCtr(); + const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); + findLastDotThens(funcToConvertRenamed, lastDotThenMap, checker); const [retStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); @@ -27,7 +29,7 @@ namespace ts.codefix { const gluedCallback = glued[0]; let retStmtName = glued[1]; if (gluedCallback) { - const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, retStmtName, varDeclFlags); + const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, retStmtName, varDeclFlags); if (newNodes.length > 0) { changes.replaceNodeRangeWithNodes(sourceFile, retStmts[1].original!, retStmts[retStmts.length - 1].original!, newNodes); } @@ -35,7 +37,7 @@ namespace ts.codefix { else { for (const stmt of retStmts) { if (isCallExpression(stmt)) { - const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, retStmtName, varDeclFlags); + const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, retStmtName, varDeclFlags); if (newNodes.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } @@ -51,7 +53,7 @@ namespace ts.codefix { } if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, retStmtName, varDeclFlags); + const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, retStmtName, varDeclFlags); if (newNodes.length > 0) { changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); } @@ -84,10 +86,17 @@ namespace ts.codefix { } // fix this for multiple declarations else if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 0 && stmt.declarationList.declarations[0].initializer) { - return [glueTogetherCallbacks([stmt.declarationList.declarations[0].initializer!])[0], glueTogetherCallbacks(retStmts)[1]]; + const [gluedExpr, retName] = glueTogetherCallbacks(retStmts.concat(stmt.declarationList.declarations[0].initializer!)); + return [gluedExpr, retName]; } else if (isCallExpression(stmt)) { - return [stmt, glueTogetherCallbacks(retStmts)[1]]; + const [gluedExpr, retName] = glueTogetherCallbacks(retStmts); + if (gluedExpr) { + return [createCall(gluedExpr, undefined, stmt.arguments), retName]; + } + else { + return [stmt, retName] + } } else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { return [undefined, stmt.expression.text]; @@ -97,7 +106,49 @@ namespace ts.codefix { } - function renameCollidingVarNames(nodeToRename: Node, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map): Node { + function findLastDotThens(func: FunctionLikeDeclaration, lastDotThen: Map, checker: TypeChecker) { + if (!func.body) { + return; + } + + forEachChild(func.body, function visit(node: Node) { + if (isCallExpression(node) && isCallback(node, "then", checker)) { + lastDotThen.set(String(getNodeId(node)), false); + + for (let arg of node.arguments) { + forEachChild(arg, function visit(argChild: Expression) { + if (isCallExpression(argChild) && (returnsAPromise(argChild, checker) || isCallback(argChild, "then", checker))) { + lastDotThen.set(String(getNodeId(argChild)), false); + } + }); + } + + + forEachChild(node, function visit(child: Node) { + if (isCallExpression(child) && (returnsAPromise(child, checker) || isCallback(child, "then", checker))) { + if (!lastDotThen.get(String(getNodeId(child)))){ + lastDotThen.set(String(getNodeId(child)), true); + } + + for (let arg of child.arguments) { + forEachChild(arg, function visit(argChild: Expression) { + if (isCallExpression(argChild) && (returnsAPromise(argChild, checker) || isCallback(argChild, "then", checker))) { + lastDotThen.set(String(getNodeId(argChild)), true); + } + }); + } + } + + forEachChild(child, visit); + }); + } + else { + forEachChild(node, visit); + } + }); + } + + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map): FunctionLikeDeclaration { const allVarNames: string[] = []; forEachChild(nodeToRename, function visit(node: Node) { @@ -155,69 +206,69 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { if (!node) { return []; } if (isCallExpression(node) && returnsAPromise(node, checker)) { - return parsePromiseCall(node, checker, prevArgName, varDeclFlags); + return parsePromiseCall(node, lastDotThenMap, prevArgName, varDeclFlags); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, outermostParent, synthNamesMap, prevArgName, varDeclFlags); + return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, synthNamesMap, prevArgName, varDeclFlags); + return parseCatch(node, checker, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName, varDeclFlags); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argName, varDeclFlags)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argName, varDeclFlags)); - const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap); const catchClause = createCatchClause(argName, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier if (!res) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, prevArgName, varDeclFlags); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); } const argNameRes = getArgName(res, checker, synthNamesMap); - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap); if (rej) { const argNameRej = getArgName(rej, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, argNameRes, varDeclFlags).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes, varDeclFlags).concat(callbackBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, /*isRej*/ true); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, /*isRej*/ true); const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - return parseCallback(node.expression, checker, node, synthNamesMap, argNameRes, varDeclFlags).concat(callbackBody); + return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes, varDeclFlags).concat(callbackBody); } return []; } - function parsePromiseCall(node: CallExpression, checker: TypeChecker, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { - const nextDotThen = getNextDotThen(node.original!.parent as Expression, checker); + function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { + const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (prevArgName && varDeclFlags)) { const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], varDeclFlags ? varDeclFlags : NodeFlags.Let)))]; @@ -229,27 +280,12 @@ namespace ts.codefix { return [createReturn(node)]; } - function getNextDotThen(node: Expression, checker: TypeChecker): CallExpression | undefined { - if (!node) { - return undefined; - } - - if (isCallExpression(node) && isCallback(node, "then", checker)) { - return node; - } - else { - return getNextDotThen(node.parent as Expression, checker); - } - } - - function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, isRej = false): NodeArray { if (!prevArgName && argName) { prevArgName = argName; } - const outerParent = parent.original ? parent.original.parent : undefined; - const nextDotThen = getNextDotThen(outerParent as Expression, checker); - + const nextDotThen = lastDotThenMap.get(String(getNodeId(parent))); switch (func.kind) { case SyntaxKind.Identifier: if (!prevArgName || !argName) { @@ -270,7 +306,7 @@ namespace ts.codefix { if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclFlags); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } @@ -282,7 +318,7 @@ namespace ts.codefix { // if there is another outer dot then, don't actually return const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), checker); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, prevArgName, varDeclFlags); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } @@ -312,12 +348,12 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = parseCallback(node, checker, node, synthNamesMap, prevArgName, varDeclFlags); + const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; From 404705b75ab40c16f58a2f9a24e12e9ae0b9085c Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 16 Jul 2018 16:48:19 -0700 Subject: [PATCH 108/196] Fixed more bugs in gluing expressions together --- src/services/codefixes/convertToAsyncFunction.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index a24b635392d88..26d097f8e0d0c 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -92,7 +92,15 @@ namespace ts.codefix { else if (isCallExpression(stmt)) { const [gluedExpr, retName] = glueTogetherCallbacks(retStmts); if (gluedExpr) { - return [createCall(gluedExpr, undefined, stmt.arguments), retName]; + let lhs; + if (stmt.expression && isPropertyAccessExpression(stmt.expression)) { + lhs = createPropertyAccess(gluedExpr, stmt.expression.name); + } + else { + lhs = gluedExpr; + } + + return [createCall(lhs, undefined, stmt.arguments), retName]; } else { return [stmt, retName] From 26f3787fe588714bc48a7be3fd78c04c7f6e2b2b Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 17 Jul 2018 15:29:47 -0700 Subject: [PATCH 109/196] Initial commit of multiple var decls to refactor --- .../codefixes/convertToAsyncFunction.ts | 221 ++++++++++++++---- 1 file changed, 173 insertions(+), 48 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 26d097f8e0d0c..eb18018869c92 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -25,45 +25,170 @@ namespace ts.codefix { const [retStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); - const glued = glueTogetherCallbacks(retStmts); - const gluedCallback = glued[0]; - let retStmtName = glued[1]; - if (gluedCallback) { - const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, retStmtName, varDeclFlags); - if (newNodes.length > 0) { - changes.replaceNodeRangeWithNodes(sourceFile, retStmts[1].original!, retStmts[retStmts.length - 1].original!, newNodes); - } - } - else { - for (const stmt of retStmts) { - if (isCallExpression(stmt)) { - const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, retStmtName, varDeclFlags); - if (newNodes.length > 0) { - changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); + const retStmtsMap: Map = seperateCallbacksByVariable(retStmts, checker); + retStmtsMap.forEach((stmts: Node[]) => { + const glued = glueTogetherCallbacks(stmts); + const gluedCallback = glued[0]; + let retStmtName = glued[1]; + if (gluedCallback) { + const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, varDeclFlags, retStmtName); + + let i = 1; + while (i < stmts.length && i < newNodes.length) { + if (i === stmts.length - 1 && (i-1) < newNodes.length - 1) { + changes.replaceNodeWithNodes(sourceFile, stmts[i], newNodes.slice(i-1)); } + else { + changes.replaceNode(sourceFile, stmts[i], newNodes[i-1]); + } + + i++; } - else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { - retStmtName = stmt.expression.text; - } - else { - forEachChild(stmt, function visit(node: Node) { - if (isReturnStatement(node) && node.expression && isIdentifier(node.expression)) { - retStmtName = node.expression.text; + /* + if (newNodes.length > 0) { + changes.replaceNodeRangeWithNodes(sourceFile, retStmts[1].original!, retStmts[retStmts.length - 1].original!, newNodes); + }*/ + } + else { + for (const stmt of stmts) { + if (isCallExpression(stmt)) { + const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, varDeclFlags, retStmtName); + let i = 0; + while (i < stmts.length && i < newNodes.length) { + if (i === stmts.length - 1 && (i-1) < newNodes.length - 1) { + changes.replaceNodeWithNodes(sourceFile, stmts[i], newNodes.slice(i-1)); + } + else { + changes.replaceNode(sourceFile, stmts[i], newNodes[i-1]); + } + + i++; } - if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, retStmtName, varDeclFlags); - if (newNodes.length > 0) { - changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); + /* + if (newNodes.length > 0) { + changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); + }*/ + } + else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { + retStmtName = stmt.expression.text; + } + else { + forEachChild(stmt, function visit(node: Node) { + + if (isReturnStatement(node) && node.expression && isIdentifier(node.expression)) { + retStmtName = node.expression.text; } - } - else if (!isFunctionLike(node)) { - forEachChild(node, visit); - } - }); + + if (isCallExpression(node)) { + const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, retStmtName); + let i = 0; + while (i < stmts.length && i < newNodes.length) { + if (i === stmts.length - 1 && (i-1) < newNodes.length - 1) { + changes.replaceNodeWithNodes(sourceFile, stmts[i], newNodes.slice(i-1)); + } + else { + changes.replaceNode(sourceFile, stmts[i], newNodes[i-1]); + } + + i++; + } + /* + if (newNodes.length > 0) { + changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); + }*/ + } + else if (!isFunctionLike(node)) { + forEachChild(node, visit); + } + }); + } + } + } + }); + + } + + function seperateCallbacksByVariable(retStmts: Node[], checker: TypeChecker): Map { + retStmts = retStmts.slice(0); + let returnMap: Map = new MapCtr(); + let promiseVars: Identifier[] = []; + + for (let stmt of retStmts) { + let expr; + let name: Identifier | undefined; + if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 0) { + expr = stmt.declarationList.declarations[0]; + name = stmt.declarationList.declarations[0].name as Identifier; + } + else if (isAssignmentExpression(stmt)) { + expr = stmt.right; + name = isIdentifier(stmt.left) ? stmt.left : undefined; + } + else if (isCallExpression(stmt)) { + expr = stmt; + forEachChild(stmt, function visit(node: Node) { + if (!name && isPropertyAccessExpression(node) && isIdentifier(node.expression)) { + name = node.expression; + } + else { + forEachChild(node, visit); + } + }); + } + else if (isExpressionStatement(stmt)) { + retStmts.push(stmt.expression); + } + + if (!expr) { + continue; + } + + let added = false; + for (let varName of promiseVars) { + if (isUsedIn(varName, expr)) { + addToMap(returnMap, varName.text, expr); + added = true; + break; } } + + if (!added && name && (isCallExpression(expr) && returnsAPromise(expr, checker)) || + (isVariableDeclaration(expr) && expr.initializer && isCallExpression(expr.initializer) && returnsAPromise(expr.initializer, checker))) { + + promiseVars.push(name as Identifier); + addToMap(returnMap, (name).text, expr); + } + } + + if (returnMap.size === 0) { + returnMap.set("", retStmts); + } + return returnMap; + } + + function isUsedIn(variable: Identifier, expr: Node): boolean { + let isUsed = false; + forEachChild(expr, function visit(node: Node) { + if (isIdentifier(node) && variable.text === node.text) { + isUsed = true; + } + else { + forEachChild(node, visit); + } + }); + + return isUsed; + } + + + function addToMap(map: Map, key: string, val: T) { + if (!map.get(key)) { + map.set(key, [val]); + } + else { + map.get(key)!.push(val); } } @@ -95,7 +220,7 @@ namespace ts.codefix { let lhs; if (stmt.expression && isPropertyAccessExpression(stmt.expression)) { lhs = createPropertyAccess(gluedExpr, stmt.expression.name); - } + } else { lhs = gluedExpr; } @@ -134,7 +259,7 @@ namespace ts.codefix { forEachChild(node, function visit(child: Node) { if (isCallExpression(child) && (returnsAPromise(child, checker) || isCallback(child, "then", checker))) { - if (!lastDotThen.get(String(getNodeId(child)))){ + if (!lastDotThen.get(String(getNodeId(child)))) { lastDotThen.set(String(getNodeId(child)), true); } @@ -214,7 +339,7 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, prevArgName?: string): Statement[] { if (!node) { return []; } @@ -223,23 +348,23 @@ namespace ts.codefix { return parsePromiseCall(node, lastDotThenMap, prevArgName, varDeclFlags); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); + return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); + return parseCatch(node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, prevArgName?: string): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argName, varDeclFlags)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, argName)); const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap); const catchClause = createCatchClause(argName, createBlock(callbackBody)); @@ -247,12 +372,12 @@ namespace ts.codefix { return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, prevArgName?: string): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier if (!res) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); } const argNameRes = getArgName(res, checker, synthNamesMap); @@ -261,7 +386,7 @@ namespace ts.codefix { if (rej) { const argNameRej = getArgName(rej, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes, varDeclFlags).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, argNameRes).concat(callbackBody)); const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, /*isRej*/ true); const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); @@ -269,7 +394,7 @@ namespace ts.codefix { return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes, varDeclFlags).concat(callbackBody); + return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, argNameRes).concat(callbackBody); } return []; @@ -314,7 +439,7 @@ namespace ts.codefix { if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } @@ -326,12 +451,12 @@ namespace ts.codefix { // if there is another outer dot then, don't actually return const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), checker); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - return nextDotThen || (prevArgName && varDeclFlags) ? + return nextDotThen /* || (prevArgName && varDeclFlags) */ ? createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], varDeclFlags ? varDeclFlags : NodeFlags.Let)))]) : createNodeArray([createReturn(func.body as Expression)]); } @@ -356,12 +481,12 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, prevArgName?: string) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, prevArgName, varDeclFlags); + const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; From 50c6b4a0ae0a68b0ebd034b8d9b6d20b323e45a6 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 17 Jul 2018 15:58:55 -0700 Subject: [PATCH 110/196] Fixed expression statement bug --- .../codefixes/convertToAsyncFunction.ts | 109 ++++++++---------- 1 file changed, 51 insertions(+), 58 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index eb18018869c92..dc011a03d0f65 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -32,44 +32,13 @@ namespace ts.codefix { let retStmtName = glued[1]; if (gluedCallback) { const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, varDeclFlags, retStmtName); - - let i = 1; - while (i < stmts.length && i < newNodes.length) { - if (i === stmts.length - 1 && (i-1) < newNodes.length - 1) { - changes.replaceNodeWithNodes(sourceFile, stmts[i], newNodes.slice(i-1)); - } - else { - changes.replaceNode(sourceFile, stmts[i], newNodes[i-1]); - } - - i++; - } - - /* - if (newNodes.length > 0) { - changes.replaceNodeRangeWithNodes(sourceFile, retStmts[1].original!, retStmts[retStmts.length - 1].original!, newNodes); - }*/ + replaceNodes(changes, sourceFile, stmts, newNodes); } else { for (const stmt of stmts) { if (isCallExpression(stmt)) { const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, varDeclFlags, retStmtName); - let i = 0; - while (i < stmts.length && i < newNodes.length) { - if (i === stmts.length - 1 && (i-1) < newNodes.length - 1) { - changes.replaceNodeWithNodes(sourceFile, stmts[i], newNodes.slice(i-1)); - } - else { - changes.replaceNode(sourceFile, stmts[i], newNodes[i-1]); - } - - i++; - } - - /* - if (newNodes.length > 0) { - changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); - }*/ + replaceNodes(changes, sourceFile, stmts, newNodes); } else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { retStmtName = stmt.expression.text; @@ -83,21 +52,7 @@ namespace ts.codefix { if (isCallExpression(node)) { const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, retStmtName); - let i = 0; - while (i < stmts.length && i < newNodes.length) { - if (i === stmts.length - 1 && (i-1) < newNodes.length - 1) { - changes.replaceNodeWithNodes(sourceFile, stmts[i], newNodes.slice(i-1)); - } - else { - changes.replaceNode(sourceFile, stmts[i], newNodes[i-1]); - } - - i++; - } - /* - if (newNodes.length > 0) { - changes.replaceNodeWithNodes(sourceFile, stmt, newNodes); - }*/ + replaceNodes(changes, sourceFile, stmts, newNodes); } else if (!isFunctionLike(node)) { forEachChild(node, visit); @@ -107,19 +62,30 @@ namespace ts.codefix { } } }); + } + function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes: Node[], newNodes: Node[]) { + let i = 1; + while (i < oldNodes.length && i < newNodes.length) { + if (i === oldNodes.length - 1 && i < newNodes.length - 1) { + changes.replaceNodeWithNodes(sourceFile, oldNodes[i], newNodes.slice(i)); + } + else { + changes.replaceNode(sourceFile, oldNodes[i], newNodes[i]); + } + i++; + } } function seperateCallbacksByVariable(retStmts: Node[], checker: TypeChecker): Map { - retStmts = retStmts.slice(0); let returnMap: Map = new MapCtr(); let promiseVars: Identifier[] = []; - for (let stmt of retStmts) { - let expr; + function getNameAndExpr(stmt: Node): [Node | undefined, Identifier | undefined] { + let expr: Node | undefined; let name: Identifier | undefined; if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 0) { - expr = stmt.declarationList.declarations[0]; + expr = stmt; //fix this for a varDeclList name = stmt.declarationList.declarations[0].name as Identifier; } else if (isAssignmentExpression(stmt)) { @@ -137,8 +103,20 @@ namespace ts.codefix { } }); } - else if (isExpressionStatement(stmt)) { - retStmts.push(stmt.expression); + + return [expr, name]; + } + + for (let stmt of retStmts) { + let expr; + let name: Identifier | undefined; + + if (isExpressionStatement(stmt)) { + expr = stmt; + name = getNameAndExpr(stmt.expression)[1]; + } + else { + [expr, name] = getNameAndExpr(stmt); } if (!expr) { @@ -154,11 +132,25 @@ namespace ts.codefix { } } - if (!added && name && (isCallExpression(expr) && returnsAPromise(expr, checker)) || - (isVariableDeclaration(expr) && expr.initializer && isCallExpression(expr.initializer) && returnsAPromise(expr.initializer, checker))) { + if (!added && name) { + let callExpr: Node; + if (isVariableStatement(expr) && expr.declarationList.declarations.length === 1 && expr.declarationList.declarations[0].initializer) { + callExpr = expr.declarationList.declarations[0].initializer!; + } + else if (isAssignmentExpression(expr) && isCallExpression(expr.right) && returnsAPromise(expr.right, checker)) { + callExpr = expr.right; + } + else if (isExpressionStatement(expr)) { + callExpr = expr.expression; + } + else { + callExpr = expr; + } - promiseVars.push(name as Identifier); - addToMap(returnMap, (name).text, expr); + if (isCallExpression(callExpr) && returnsAPromise(callExpr, checker)) { + promiseVars.push(name as Identifier); + addToMap(returnMap, (name).text, expr); + } } } @@ -168,6 +160,7 @@ namespace ts.codefix { return returnMap; } + function isUsedIn(variable: Identifier, expr: Node): boolean { let isUsed = false; forEachChild(expr, function visit(node: Node) { From eae41dde66d0482e8263f4a225c6a2e4ed0ad3de Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 17 Jul 2018 16:11:10 -0700 Subject: [PATCH 111/196] Added a flag for a last line return stmt --- .../codefixes/convertToAsyncFunction.ts | 44 +++++++++---------- src/services/suggestionDiagnostics.ts | 7 +-- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index dc011a03d0f65..d7ce1c8cfbf03 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -23,7 +23,7 @@ namespace ts.codefix { const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); findLastDotThens(funcToConvertRenamed, lastDotThenMap, checker); - const [retStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); + const [retStmts, varDeclFlags, hasFollowingReturn] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); const retStmtsMap: Map = seperateCallbacksByVariable(retStmts, checker); retStmtsMap.forEach((stmts: Node[]) => { @@ -31,13 +31,13 @@ namespace ts.codefix { const gluedCallback = glued[0]; let retStmtName = glued[1]; if (gluedCallback) { - const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, varDeclFlags, retStmtName); + const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); replaceNodes(changes, sourceFile, stmts, newNodes); } else { for (const stmt of stmts) { if (isCallExpression(stmt)) { - const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, varDeclFlags, retStmtName); + const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); replaceNodes(changes, sourceFile, stmts, newNodes); } else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { @@ -51,7 +51,7 @@ namespace ts.codefix { } if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, retStmtName); + const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); replaceNodes(changes, sourceFile, stmts, newNodes); } else if (!isFunctionLike(node)) { @@ -332,7 +332,7 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, prevArgName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { if (!node) { return []; } @@ -341,45 +341,45 @@ namespace ts.codefix { return parsePromiseCall(node, lastDotThenMap, prevArgName, varDeclFlags); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); + return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); + return parseCatch(node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, prevArgName?: string): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, argName)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, argName)); - const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, hasFollowingReturn); const catchClause = createCatchClause(argName, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, prevArgName?: string): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier if (!res) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); } const argNameRes = getArgName(res, checker, synthNamesMap); - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap, hasFollowingReturn); if (rej) { const argNameRej = getArgName(rej, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, argNameRes).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, argNameRes).concat(callbackBody)); const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, /*isRej*/ true); const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); @@ -387,7 +387,7 @@ namespace ts.codefix { return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, argNameRes).concat(callbackBody); + return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, argNameRes).concat(callbackBody); } return []; @@ -406,7 +406,7 @@ namespace ts.codefix { return [createReturn(node)]; } - function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, hasFollowingReturn: boolean, isRej = false): NodeArray { if (!prevArgName && argName) { prevArgName = argName; } @@ -432,7 +432,7 @@ namespace ts.codefix { if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } @@ -444,12 +444,12 @@ namespace ts.codefix { // if there is another outer dot then, don't actually return const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), checker); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - return nextDotThen /* || (prevArgName && varDeclFlags) */ ? + return nextDotThen || hasFollowingReturn ? createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], varDeclFlags ? varDeclFlags : NodeFlags.Let)))]) : createNodeArray([createReturn(func.body as Expression)]); } @@ -474,12 +474,12 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, prevArgName?: string) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, hasFollowingReturn: boolean, prevArgName?: string) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, prevArgName); + const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index f8ef8c11bf6dd..b02460b35d5f8 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -141,10 +141,10 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - export function getReturnStatementsWithPromiseCallbacks(node: Node, checker: TypeChecker): [Node[], NodeFlags] { - + export function getReturnStatementsWithPromiseCallbacks(node: Node, checker: TypeChecker): [Node[], NodeFlags, boolean] { const retStmts: Node[] = []; let varDeclFlags = NodeFlags.Let; + let hasFollowingRetStmt = false; forEachChild(node, visit); function visit(child: Node) { @@ -165,6 +165,7 @@ namespace ts { } else if (isIdentifier(returnChild) && isReturnStatement(child) && child.expression && isIdentifier(child.expression)) { + hasFollowingRetStmt = true; retStmts.push(child); forEachChild(node, findCallbackUses); } @@ -197,7 +198,7 @@ namespace ts { } forEachChild(child, visit); } - return [retStmts, varDeclFlags]; + return [retStmts, varDeclFlags, hasFollowingRetStmt]; } function isCallback(node: Node): boolean { From 627ac682d02718812d29a26e1dfb1b047c1fa86f Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 17 Jul 2018 17:34:39 -0700 Subject: [PATCH 112/196] Fixed replacing nodes and bug in collecting exprs of the same var name --- .../codefixes/convertToAsyncFunction.ts | 69 ++++++++++++------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index d7ce1c8cfbf03..8c81c97e9a565 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -65,11 +65,18 @@ namespace ts.codefix { } function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes: Node[], newNodes: Node[]) { - let i = 1; + if (oldNodes.length > 0 && isReturnStatement(oldNodes[0]) && (oldNodes[0]).expression && isIdentifier((oldNodes[0]).expression!)) { + oldNodes = oldNodes.slice(1); + } + + let i = 0; while (i < oldNodes.length && i < newNodes.length) { if (i === oldNodes.length - 1 && i < newNodes.length - 1) { changes.replaceNodeWithNodes(sourceFile, oldNodes[i], newNodes.slice(i)); } + else if (i < oldNodes.length - 1 && i === newNodes.length - 1){ + changes.replaceNodeRange(sourceFile, oldNodes[i], oldNodes[oldNodes.length - 1], newNodes[i]); + } else { changes.replaceNode(sourceFile, oldNodes[i], newNodes[i]); } @@ -79,17 +86,17 @@ namespace ts.codefix { function seperateCallbacksByVariable(retStmts: Node[], checker: TypeChecker): Map { let returnMap: Map = new MapCtr(); - let promiseVars: Identifier[] = []; + let promiseVars: Map = new MapCtr(); - function getNameAndExpr(stmt: Node): [Node | undefined, Identifier | undefined] { - let expr: Node | undefined; + function getNameAndExpr(stmt: Node): [Node, Identifier | undefined] { + let expr: Node; let name: Identifier | undefined; if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 0) { expr = stmt; //fix this for a varDeclList name = stmt.declarationList.declarations[0].name as Identifier; } else if (isAssignmentExpression(stmt)) { - expr = stmt.right; + expr = stmt; name = isIdentifier(stmt.left) ? stmt.left : undefined; } else if (isCallExpression(stmt)) { @@ -103,12 +110,19 @@ namespace ts.codefix { } }); } + else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { + expr = stmt; + name = stmt.expression; + } + else { + expr = stmt; + } return [expr, name]; } for (let stmt of retStmts) { - let expr; + let expr: Node; let name: Identifier | undefined; if (isExpressionStatement(stmt)) { @@ -124,13 +138,20 @@ namespace ts.codefix { } let added = false; - for (let varName of promiseVars) { - if (isUsedIn(varName, expr)) { - addToMap(returnMap, varName.text, expr); + promiseVars.forEach((names: string[], varName: string) => { + if (isUsedIn(names.concat(varName), expr)) { + addToMap(returnMap, varName, expr); added = true; - break; + + if (isVariableStatement(expr) || isAssignmentExpression(expr)) { + let newName = getNameAndExpr(expr)[1]; + if (newName) { + addToMap(promiseVars, varName, newName.text); + } + } + return; } - } + }); if (!added && name) { let callExpr: Node; @@ -147,9 +168,9 @@ namespace ts.codefix { callExpr = expr; } - if (isCallExpression(callExpr) && returnsAPromise(callExpr, checker)) { - promiseVars.push(name as Identifier); - addToMap(returnMap, (name).text, expr); + if (isCallExpression(callExpr) && returnsAPromise(callExpr, checker) || isReturnStatement(callExpr)) { + addToMap(promiseVars, name.text, undefined); + addToMap(returnMap, name.text, expr); } } } @@ -161,10 +182,10 @@ namespace ts.codefix { } - function isUsedIn(variable: Identifier, expr: Node): boolean { + function isUsedIn(variables: string[], expr: Node): boolean { let isUsed = false; forEachChild(expr, function visit(node: Node) { - if (isIdentifier(node) && variable.text === node.text) { + if (isIdentifier(node) && variables.filter(name => name === node.text)){ isUsed = true; } else { @@ -407,9 +428,6 @@ namespace ts.codefix { } function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, hasFollowingReturn: boolean, isRej = false): NodeArray { - if (!prevArgName && argName) { - prevArgName = argName; - } const nextDotThen = lastDotThenMap.get(String(getNodeId(parent))); switch (func.kind) { @@ -449,10 +467,15 @@ namespace ts.codefix { return createNodeArray(innerCbBody); } - return nextDotThen || hasFollowingReturn ? - createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], varDeclFlags ? varDeclFlags : NodeFlags.Let)))]) : - createNodeArray([createReturn(func.body as Expression)]); - } + if (prevArgName) { + return (nextDotThen || hasFollowingReturn) ? + createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], varDeclFlags ? varDeclFlags : NodeFlags.Let)))]) : + createNodeArray([createReturn(func.body as Expression)]); + } + else { + return createNodeArray([createStatement(func.body as Expression)]); + } + } break; } return createNodeArray([]); From 82bc7415de13d772c962340034580a807fd21003 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 17 Jul 2018 17:37:06 -0700 Subject: [PATCH 113/196] Added tests and baselines --- .../unittests/convertToAsyncFunction.ts | 13 +++++++++++++ .../convertToAsyncFunction_VarReturn7.js | 19 +++++++++++++++++++ .../convertToAsyncFunction_VarReturn7.ts | 19 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 79e5aa26c760b..f85cfbc48e966 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -485,6 +485,19 @@ function [#|f|]() { ` ); +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn9", ` +function [#|f|]() { + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + blob3 = blob2.catch(rej => rej.ok); + return blob; +} +` +); + + _testConvertToAsyncFunction("convertToAsyncFunction_Param", ` function [#|f|]() { my_print(fetch("https://typescriptlang.org").then(res => console.log(res))) diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.js new file mode 100644 index 0000000000000..db594267a5533 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.js @@ -0,0 +1,19 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let resp = await fetch("https://typescriptlang.org"); + let res = await fetch("https://microsoft.com"); + console.log("res:", res); + let blob = console.log(resp); + return blob; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.ts new file mode 100644 index 0000000000000..db594267a5533 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.ts @@ -0,0 +1,19 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let resp = await fetch("https://typescriptlang.org"); + let res = await fetch("https://microsoft.com"); + console.log("res:", res); + let blob = console.log(resp); + return blob; +} From 6b25e010957b8474d48d3cbf2b49fe70b69c5953 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 17 Jul 2018 17:42:20 -0700 Subject: [PATCH 114/196] Fixed small bug with seperating vars --- src/services/codefixes/convertToAsyncFunction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 8c81c97e9a565..72c2c4561d0f8 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -185,7 +185,7 @@ namespace ts.codefix { function isUsedIn(variables: string[], expr: Node): boolean { let isUsed = false; forEachChild(expr, function visit(node: Node) { - if (isIdentifier(node) && variables.filter(name => name === node.text)){ + if (isIdentifier(node) && variables.filter(name => name === node.text).length > 0){ isUsed = true; } else { From 18e18d8e2ed495131b14f9a34576c046831c025a Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 18 Jul 2018 15:15:21 -0700 Subject: [PATCH 115/196] Initial commit of preserving node flags on declarations (var, let, const) --- .../codefixes/convertToAsyncFunction.ts | 104 +++++++++++++----- src/services/suggestionDiagnostics.ts | 20 ++-- 2 files changed, 86 insertions(+), 38 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 72c2c4561d0f8..6c2b438e73b72 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -25,6 +25,7 @@ namespace ts.codefix { const [retStmts, varDeclFlags, hasFollowingReturn] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); + let allNewNodes: Node[] = []; const retStmtsMap: Map = seperateCallbacksByVariable(retStmts, checker); retStmtsMap.forEach((stmts: Node[]) => { const glued = glueTogetherCallbacks(stmts); @@ -32,13 +33,15 @@ namespace ts.codefix { let retStmtName = glued[1]; if (gluedCallback) { const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); - replaceNodes(changes, sourceFile, stmts, newNodes); + allNewNodes = allNewNodes.concat(newNodes); + //replaceNodes(changes, sourceFile, stmts, newNodes); } else { for (const stmt of stmts) { if (isCallExpression(stmt)) { const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); - replaceNodes(changes, sourceFile, stmts, newNodes); + allNewNodes = allNewNodes.concat(newNodes); + //replaceNodes(changes, sourceFile, stmts, newNodes); } else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { retStmtName = stmt.expression.text; @@ -52,7 +55,8 @@ namespace ts.codefix { if (isCallExpression(node)) { const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); - replaceNodes(changes, sourceFile, stmts, newNodes); + allNewNodes = allNewNodes.concat(newNodes); + //replaceNodes(changes, sourceFile, stmts, newNodes); } else if (!isFunctionLike(node)) { forEachChild(node, visit); @@ -62,6 +66,8 @@ namespace ts.codefix { } } }); + + replaceNodes(changes, sourceFile, retStmts, allNewNodes); } function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes: Node[], newNodes: Node[]) { @@ -74,7 +80,7 @@ namespace ts.codefix { if (i === oldNodes.length - 1 && i < newNodes.length - 1) { changes.replaceNodeWithNodes(sourceFile, oldNodes[i], newNodes.slice(i)); } - else if (i < oldNodes.length - 1 && i === newNodes.length - 1){ + else if (i < oldNodes.length - 1 && i === newNodes.length - 1) { changes.replaceNodeRange(sourceFile, oldNodes[i], oldNodes[oldNodes.length - 1], newNodes[i]); } else { @@ -128,7 +134,7 @@ namespace ts.codefix { if (isExpressionStatement(stmt)) { expr = stmt; name = getNameAndExpr(stmt.expression)[1]; - } + } else { [expr, name] = getNameAndExpr(stmt); } @@ -185,7 +191,7 @@ namespace ts.codefix { function isUsedIn(variables: string[], expr: Node): boolean { let isUsed = false; forEachChild(expr, function visit(node: Node) { - if (isIdentifier(node) && variables.filter(name => name === node.text).length > 0){ + if (isIdentifier(node) && variables.filter(name => name === node.text).length > 0) { isUsed = true; } else { @@ -225,8 +231,7 @@ namespace ts.codefix { } // fix this for multiple declarations else if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 0 && stmt.declarationList.declarations[0].initializer) { - const [gluedExpr, retName] = glueTogetherCallbacks(retStmts.concat(stmt.declarationList.declarations[0].initializer!)); - return [gluedExpr, retName]; + return glueTogetherCallbacks(retStmts.concat(stmt.declarationList.declarations[0].initializer!)); } else if (isCallExpression(stmt)) { const [gluedExpr, retName] = glueTogetherCallbacks(retStmts); @@ -245,6 +250,14 @@ namespace ts.codefix { return [stmt, retName] } } + else if (isExpressionStatement(stmt) && stmt.expression && isAssignmentExpression(stmt.expression)) { + let [gluedExpr, retName] = glueTogetherCallbacks(retStmts.concat(stmt.expression.right)); + if (!retName && isIdentifier(stmt.expression.left)) { + retName = (stmt.expression.left).text; + } + + return [gluedExpr, retName]; + } else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { return [undefined, stmt.expression.text]; } @@ -353,7 +366,7 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { if (!node) { return []; } @@ -374,19 +387,19 @@ namespace ts.codefix { return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, argName)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn)); - const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, hasFollowingReturn); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn); const catchClause = createCatchClause(argName, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier @@ -395,14 +408,14 @@ namespace ts.codefix { } const argNameRes = getArgName(res, checker, synthNamesMap); - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap, hasFollowingReturn); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn); if (rej) { const argNameRej = getArgName(rej, checker, synthNamesMap); const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, argNameRes).concat(callbackBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, /*isRej*/ true); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, /*isRej*/ true); const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; @@ -414,11 +427,29 @@ namespace ts.codefix { return []; } - function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: NodeFlags): Statement[] { + function getNodeFlags(node: Node, varDeclFlags?: Map): NodeFlags | undefined { + if (varDeclFlags && varDeclFlags.get(String(getNodeId(node)))) { + let flags = varDeclFlags.get(String(getNodeId(node)))!; + if (flags === NodeFlags.Let || flags === NodeFlags.Const || flags === NodeFlags.None) { + return flags; + } + } + else { + return NodeFlags.Let; + } + } + + function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: Map): Statement[] { const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (prevArgName && varDeclFlags)) { + + // is an assignment + if (varDeclFlags && varDeclFlags.get(String(getNodeId(node))) === NodeFlags.None) { + return [createStatement(createAssignment(createIdentifier(prevArgName), createAwait(node)))]; + } + const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); - return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], varDeclFlags ? varDeclFlags : NodeFlags.Let)))]; + return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], getNodeFlags(node, varDeclFlags))))]; } else if (!prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { return [createStatement(createAwait(node))]; @@ -427,7 +458,7 @@ namespace ts.codefix { return [createReturn(node)]; } - function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, hasFollowingReturn: boolean, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, isRej = false): NodeArray { const nextDotThen = lastDotThenMap.get(String(getNodeId(parent))); switch (func.kind) { @@ -442,7 +473,13 @@ namespace ts.codefix { } synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); - return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); + + if (varDeclFlags && varDeclFlags.get(String(getNodeId(parent))) === NodeFlags.None) { + return createNodeArray([createStatement(createAssignment(createIdentifier(prevArgName), createAwait(synthCall)))]); + } + + return createNodeArray([createVariableStatement(/*modifiers*/ undefined, + (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (createAwait(synthCall)))], getNodeFlags(parent, varDeclFlags))))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: @@ -450,7 +487,7 @@ namespace ts.codefix { if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, hasFollowingReturn, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } @@ -462,31 +499,38 @@ namespace ts.codefix { // if there is another outer dot then, don't actually return const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), checker); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, hasFollowingReturn, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - if (prevArgName) { - return (nextDotThen || hasFollowingReturn) ? - createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], varDeclFlags ? varDeclFlags : NodeFlags.Let)))]) : - createNodeArray([createReturn(func.body as Expression)]); + if (prevArgName && (nextDotThen) || hasFollowingReturn) { + if (varDeclFlags && varDeclFlags.get(String(getNodeId(func))) === NodeFlags.None) { + return createNodeArray([createStatement(createAssignment(createIdentifier(prevArgName!), func.body as Expression))]); + } + + return createNodeArray([createVariableStatement(/*modifiers*/ undefined, + (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], NodeFlags.Let)))]); + } + else if (prevArgName) { + return createNodeArray([createReturn(func.body as Expression)]); } else { return createNodeArray([createStatement(func.body as Expression)]); } - } + } break; } return createNodeArray([]); } - function removeReturns(stmts: NodeArray, prevArgName: string, varDeclFlags?: NodeFlags): NodeArray { + function removeReturns(stmts: NodeArray, prevArgName: string, varDeclFlags?: Map): NodeArray { const ret: Statement[] = []; for (const stmt of stmts) { if (isReturnStatement(stmt)) { if (stmt.expression) { - ret.push(createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], varDeclFlags ? varDeclFlags : NodeFlags.Let)))); + ret.push(createVariableStatement(/*modifiers*/ undefined, + (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], getNodeFlags(stmt, varDeclFlags))))); } } else { @@ -497,12 +541,12 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: NodeFlags, hasFollowingReturn: boolean, prevArgName?: string) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, hasFollowingReturn: boolean, prevArgName?: string) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); + const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, new MapCtr(), hasFollowingReturn, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index b02460b35d5f8..3f6249cf64322 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -141,9 +141,9 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - export function getReturnStatementsWithPromiseCallbacks(node: Node, checker: TypeChecker): [Node[], NodeFlags, boolean] { + export function getReturnStatementsWithPromiseCallbacks(node: Node, checker: TypeChecker): [Node[], Map, boolean] { const retStmts: Node[] = []; - let varDeclFlags = NodeFlags.Let; + let varDeclFlagsMap: Map = new MapCtr(); let hasFollowingRetStmt = false; forEachChild(node, visit); @@ -164,7 +164,7 @@ namespace ts { retStmts.push(child as ReturnStatement); } else if (isIdentifier(returnChild) && isReturnStatement(child) - && child.expression && isIdentifier(child.expression)) { + && child.expression && isIdentifier(child.expression)) { hasFollowingRetStmt = true; retStmts.push(child); forEachChild(node, findCallbackUses); @@ -176,20 +176,24 @@ namespace ts { let parent: Node; function findCallbackUses(identUse: Node) { - if (isVariableDeclarationList(identUse)){ + if (isVariableDeclarationList(identUse)) { for (let varDecl of identUse.declarations) { - if (varDecl.initializer && isCallExpression(varDecl.initializer) && - symbol === checker.getSymbolAtLocation(varDecl.name)){ + if (varDecl.initializer && isCallExpression(varDecl.initializer) && + symbol === checker.getSymbolAtLocation(varDecl.name)) { retStmts.push(parent); + varDeclFlagsMap.set(String(getNodeId(varDecl.initializer)), identUse.flags); } } - varDeclFlags = identUse.flags; } else if (isCallback(identUse)) { if (symbol === checker.getSymbolAtLocation(((identUse).expression).expression)) { retStmts.push(parent as CallExpression); } } + else if (isAssignmentExpression(identUse)) { + retStmts.push(parent); + varDeclFlagsMap.set(String(getNodeId(identUse.right)), NodeFlags.None) + } else { parent = identUse; forEachChild(identUse, findCallbackUses); @@ -198,7 +202,7 @@ namespace ts { } forEachChild(child, visit); } - return [retStmts, varDeclFlags, hasFollowingRetStmt]; + return [retStmts, varDeclFlagsMap, hasFollowingRetStmt]; } function isCallback(node: Node): boolean { From c90ce2cc3e6eb397ee0f3a789a907deb2593c352 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 18 Jul 2018 16:40:33 -0700 Subject: [PATCH 116/196] Added tests and baselines --- .../unittests/convertToAsyncFunction.ts | 42 +++++++++++++++---- ... => convertToAsyncFunction_VarReturn01.js} | 0 ... => convertToAsyncFunction_VarReturn01.ts} | 0 ... => convertToAsyncFunction_VarReturn02.js} | 0 ... => convertToAsyncFunction_VarReturn02.ts} | 0 .../convertToAsyncFunction_VarReturn05.js | 16 +++++++ .../convertToAsyncFunction_VarReturn05.ts | 16 +++++++ .../convertToAsyncFunction_VarReturn7.js | 19 --------- .../convertToAsyncFunction_VarReturn7.ts | 19 --------- 9 files changed, 65 insertions(+), 47 deletions(-) rename tests/baselines/reference/convertToAsyncFunction/{convertToAsyncFunction_VarReturn1.js => convertToAsyncFunction_VarReturn01.js} (100%) rename tests/baselines/reference/convertToAsyncFunction/{convertToAsyncFunction_VarReturn1.ts => convertToAsyncFunction_VarReturn01.ts} (100%) rename tests/baselines/reference/convertToAsyncFunction/{convertToAsyncFunction_VarReturn2.js => convertToAsyncFunction_VarReturn02.js} (100%) rename tests/baselines/reference/convertToAsyncFunction/{convertToAsyncFunction_VarReturn2.ts => convertToAsyncFunction_VarReturn02.ts} (100%) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.js delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index f85cfbc48e966..1e274a4c489f7 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -406,14 +406,14 @@ function [#|innerPromise|](): Promise { } ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_VarReturn1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_VarReturn01", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); return blob; } ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_VarReturn2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_VarReturn02", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); blob.then(resp => console.log(resp)); @@ -421,7 +421,7 @@ function [#|f|]() { } ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn3", ` +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn03", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org") let blob2 = blob.then(resp => console.log(resp)); @@ -434,7 +434,7 @@ function err (rej) { } ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn4", ` +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn04", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org").then(res => console.log(res)), blob2 = fetch("https://microsoft.com").then(res => res.ok).catch(err); return blob; @@ -445,7 +445,7 @@ function err (rej) { ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn5", ` +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn05", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org").then(res => console.log(res)); blob.then(x => x); @@ -454,7 +454,7 @@ function [#|f|]() { ` ); -_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn6", ` +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn06", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org"); return blob; @@ -462,7 +462,7 @@ function [#|f|]() { ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn7", ` +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn07", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); let blob2 = fetch("https://microsoft.com"); @@ -473,7 +473,7 @@ function [#|f|]() { ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn8", ` +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn08", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); if (!blob.ok){ @@ -485,8 +485,9 @@ function [#|f|]() { ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn9", ` +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn09", ` function [#|f|]() { + let blob3; let blob = fetch("https://typescriptlang.org"); let blob2 = fetch("https://microsoft.com"); blob2.then(res => console.log("res:", res)); @@ -498,6 +499,29 @@ function [#|f|]() { ); +_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn10", ` +function [#|f|]() { + let blob3; + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + blob3 = blob2; + return blob; +} +` +); + +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn11", ` +function [#|f|]() { + let blob; + return blob; +} +` +); + + + _testConvertToAsyncFunction("convertToAsyncFunction_Param", ` function [#|f|]() { my_print(fetch("https://typescriptlang.org").then(res => console.log(res))) diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.js similarity index 100% rename from tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.js rename to tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.js diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.ts similarity index 100% rename from tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn1.ts rename to tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.ts diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.js similarity index 100% rename from tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.js rename to tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.js diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.ts similarity index 100% rename from tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn2.ts rename to tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.ts diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js new file mode 100644 index 0000000000000..803fed4f9cc6a --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + var blob = fetch("https://typescriptlang.org").then(res => console.log(res)); + blob.then(x => x); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let res = await fetch("https://typescriptlang.org"); + let x = console.log(res); + let blob = x; + return blob; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.ts new file mode 100644 index 0000000000000..8c1c5dc3d64d8 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + var blob = fetch("https://typescriptlang.org").then(res => console.log(res)); + blob.then(x => x); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let res = await fetch("https://typescriptlang.org"); + let x = console.log(res); + var blob = x; + return blob; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.js deleted file mode 100644 index db594267a5533..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.js +++ /dev/null @@ -1,19 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob = fetch("https://typescriptlang.org"); - let blob2 = fetch("https://microsoft.com"); - blob2.then(res => console.log("res:", res)); - blob.then(resp => console.log(resp)); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let resp = await fetch("https://typescriptlang.org"); - let res = await fetch("https://microsoft.com"); - console.log("res:", res); - let blob = console.log(resp); - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.ts deleted file mode 100644 index db594267a5533..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn7.ts +++ /dev/null @@ -1,19 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob = fetch("https://typescriptlang.org"); - let blob2 = fetch("https://microsoft.com"); - blob2.then(res => console.log("res:", res)); - blob.then(resp => console.log(resp)); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let resp = await fetch("https://typescriptlang.org"); - let res = await fetch("https://microsoft.com"); - console.log("res:", res); - let blob = console.log(resp); - return blob; -} From dbf7928ff919a928018ead5a5aaecc5502a3ca6b Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 19 Jul 2018 13:59:06 -0700 Subject: [PATCH 117/196] More work on returning variables that hold promises --- .../codefixes/convertToAsyncFunction.ts | 133 +++++++++--------- src/services/suggestionDiagnostics.ts | 30 ++-- 2 files changed, 91 insertions(+), 72 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 6c2b438e73b72..558d7dd6246fe 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -18,12 +18,12 @@ namespace ts.codefix { changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); const varNamesMap: Map = new MapCtr(); - const synthNamesMap: Map = new MapCtr(); + const synthNamesMap: Map = new MapCtr(); const lastDotThenMap: Map = new MapCtr(); const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); findLastDotThens(funcToConvertRenamed, lastDotThenMap, checker); - const [retStmts, varDeclFlags, hasFollowingReturn] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, checker); + const [retStmts, varDeclFlags, hasFollowingReturn] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed); let allNewNodes: Node[] = []; const retStmtsMap: Map = seperateCallbacksByVariable(retStmts, checker); @@ -34,29 +34,26 @@ namespace ts.codefix { if (gluedCallback) { const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); allNewNodes = allNewNodes.concat(newNodes); - //replaceNodes(changes, sourceFile, stmts, newNodes); } else { for (const stmt of stmts) { if (isCallExpression(stmt)) { const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); allNewNodes = allNewNodes.concat(newNodes); - //replaceNodes(changes, sourceFile, stmts, newNodes); } else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { - retStmtName = stmt.expression.text; + retStmtName = stmt.expression; } else { forEachChild(stmt, function visit(node: Node) { if (isReturnStatement(node) && node.expression && isIdentifier(node.expression)) { - retStmtName = node.expression.text; + retStmtName = node.expression; } if (isCallExpression(node)) { const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); allNewNodes = allNewNodes.concat(newNodes); - //replaceNodes(changes, sourceFile, stmts, newNodes); } else if (!isFunctionLike(node)) { forEachChild(node, visit); @@ -103,7 +100,7 @@ namespace ts.codefix { } else if (isAssignmentExpression(stmt)) { expr = stmt; - name = isIdentifier(stmt.left) ? stmt.left : undefined; + name = isIdentifier(stmt.left) ? stmt.left : getNameAndExpr(stmt.left)[1]; } else if (isCallExpression(stmt)) { expr = stmt; @@ -132,7 +129,7 @@ namespace ts.codefix { let name: Identifier | undefined; if (isExpressionStatement(stmt)) { - expr = stmt; + expr = stmt.expression; name = getNameAndExpr(stmt.expression)[1]; } else { @@ -212,11 +209,11 @@ namespace ts.codefix { } } - function glueTogetherCallbacks(retStmts: Node[]): [CallExpression | undefined, string] { + function glueTogetherCallbacks(retStmts: Node[]): [CallExpression | undefined, Identifier | undefined] { retStmts = retStmts.slice(0); const stmt = retStmts.pop(); if (!stmt) { - return [undefined, ""]; + return [undefined, undefined]; } if (isExpressionStatement(stmt) && stmt.expression && isCallExpression(stmt.expression) @@ -253,16 +250,23 @@ namespace ts.codefix { else if (isExpressionStatement(stmt) && stmt.expression && isAssignmentExpression(stmt.expression)) { let [gluedExpr, retName] = glueTogetherCallbacks(retStmts.concat(stmt.expression.right)); if (!retName && isIdentifier(stmt.expression.left)) { - retName = (stmt.expression.left).text; + retName = stmt.expression.left; } return [gluedExpr, retName]; } else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { - return [undefined, stmt.expression.text]; + return [undefined, stmt.expression]; + } + else if (isExpression(stmt)) { + forEachChild(stmt, function visit(node: Node){ + if (isIdentifier(node)) { + return [undefined, node.text]; + } + }); } - return [undefined, ""]; + return [undefined, undefined]; } @@ -308,41 +312,41 @@ namespace ts.codefix { }); } - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map): FunctionLikeDeclaration { - const allVarNames: string[] = []; + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map): FunctionLikeDeclaration { + const allVarNames: Identifier[] = []; forEachChild(nodeToRename, function visit(node: Node) { if (isIdentifier(node)) { const type = checker.getTypeAtLocation(node); const symbol = checker.getSymbolAtLocation(node); - const newName = getNewNameIfConflict(node.text, allVarNames); + const newName = getNewNameIfConflict(node, allVarNames); if (symbol && type && type.getCallSignatures().length > 0 && type.getCallSignatures()[0].parameters.length > 0) { // first, add the actual function name - if (allVarNames.filter(elem => elem === node.text).length > 0) { + if (allVarNames.filter(elem => elem.text === node.text).length > 0) { // we have a conflict with the function name, but function names take precedence over variable names varNamesMap.forEach((value: string, key: string) => { if (value === node.text) { - varNamesMap.set(key, getNewNameIfConflict(node.text, allVarNames)); + varNamesMap.set(key, getNewNameIfConflict(node, allVarNames).text); return; } }); } varNamesMap.set(String(getSymbolId(symbol)), node.text); - allVarNames.push(node.text); + allVarNames.push(node); // next, add the new variable for the declaration const synthName = type.getCallSignatures()[0].parameters[0].name; - const newSynthName = getNewNameIfConflict(synthName, allVarNames); - varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(createIdentifier(newSynthName))))), newSynthName); + const newSynthName = getNewNameIfConflict(createIdentifier(synthName), allVarNames); + varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(newSynthName)))), newSynthName.text); allVarNames.push(newSynthName); synthNamesMap.set(node.text, newSynthName); } else if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { - varNamesMap.set(String(getSymbolId(symbol)), newName); - allVarNames.push(node.text); + varNamesMap.set(String(getSymbolId(symbol)), newName.text); + allVarNames.push(node); } } @@ -352,9 +356,9 @@ namespace ts.codefix { return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, varNamesMap, checker); } - function getNewNameIfConflict(name: string, allVarNames: string[]) { - const numVarsSameName = allVarNames.filter(elem => elem === name).length; - return numVarsSameName === 0 ? name : name + "_" + numVarsSameName; + function getNewNameIfConflict(name: Identifier, allVarNames: Identifier[]): Identifier { + const numVarsSameName = allVarNames.filter(elem => elem.text === name.text).length; + return numVarsSameName === 0 ? name : createIdentifier(name.text + "_" + numVarsSameName); } function returnsAPromise(node: CallExpression, checker: TypeChecker): boolean { @@ -366,7 +370,7 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { if (!node) { return []; } @@ -387,19 +391,19 @@ namespace ts.codefix { return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn)); const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn); - const catchClause = createCatchClause(argName, createBlock(callbackBody)); + const catchClause = createCatchClause(argName.text, createBlock(callbackBody)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: string): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier @@ -416,7 +420,7 @@ namespace ts.codefix { const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, argNameRes).concat(callbackBody)); const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, /*isRej*/ true); - const catchClause = createCatchClause(argNameRej, createBlock(callbackBody2)); + const catchClause = createCatchClause(argNameRej.text, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } @@ -427,10 +431,10 @@ namespace ts.codefix { return []; } - function getNodeFlags(node: Node, varDeclFlags?: Map): NodeFlags | undefined { - if (varDeclFlags && varDeclFlags.get(String(getNodeId(node)))) { - let flags = varDeclFlags.get(String(getNodeId(node)))!; - if (flags === NodeFlags.Let || flags === NodeFlags.Const || flags === NodeFlags.None) { + function getNodeFlags(node: Identifier, varDeclFlags?: Map): NodeFlags | undefined { + if (varDeclFlags && varDeclFlags.has(node.text)) { + let flags = varDeclFlags.get(node.text)!; + if (flags & NodeFlags.Let || flags & NodeFlags.Const || flags & NodeFlags.None) { return flags; } } @@ -439,17 +443,17 @@ namespace ts.codefix { } } - function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: string, varDeclFlags?: Map): Statement[] { + function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: Identifier, varDeclFlags?: Map): Statement[] { const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (prevArgName && varDeclFlags)) { - // is an assignment - if (varDeclFlags && varDeclFlags.get(String(getNodeId(node))) === NodeFlags.None) { - return [createStatement(createAssignment(createIdentifier(prevArgName), createAwait(node)))]; + const nodeFlags = getNodeFlags(prevArgName, varDeclFlags); + if (varDeclFlags && varDeclFlags.has(prevArgName.text) && nodeFlags === undefined) { + return [createStatement(createAssignment(prevArgName, createAwait(node)))]; } const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); - return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], getNodeFlags(node, varDeclFlags))))]; + return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], nodeFlags)))]; } else if (!prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { return [createStatement(createAwait(node))]; @@ -458,7 +462,7 @@ namespace ts.codefix { return [createReturn(node)]; } - function getCallbackBody(func: Node, prevArgName: string | undefined, argName: string, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: Identifier | undefined, argName: Identifier, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, isRej = false): NodeArray { const nextDotThen = lastDotThenMap.get(String(getNodeId(parent))); switch (func.kind) { @@ -467,26 +471,27 @@ namespace ts.codefix { break; } - let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); + let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName]); if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { return createNodeArray([createReturn(synthCall)]); } - synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [createIdentifier(argName)]); + synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName]); - if (varDeclFlags && varDeclFlags.get(String(getNodeId(parent))) === NodeFlags.None) { - return createNodeArray([createStatement(createAssignment(createIdentifier(prevArgName), createAwait(synthCall)))]); + const nodeFlags = getNodeFlags(prevArgName, varDeclFlags); + if (varDeclFlags && varDeclFlags.has(prevArgName.text) && nodeFlags === undefined) { + return createNodeArray([createStatement(createAssignment(prevArgName, createAwait(synthCall)))]); } return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (createAwait(synthCall)))], getNodeFlags(parent, varDeclFlags))))]); + (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (createAwait(synthCall)))], nodeFlags)))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(func.body as Node, checker); + const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(func.body as Node); const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, hasFollowingReturn, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); @@ -498,19 +503,21 @@ namespace ts.codefix { else if (isArrowFunction(func)) { // if there is another outer dot then, don't actually return - const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), checker); + const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression))[0]; const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, hasFollowingReturn, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - if (prevArgName && (nextDotThen) || hasFollowingReturn) { - if (varDeclFlags && varDeclFlags.get(String(getNodeId(func))) === NodeFlags.None) { - return createNodeArray([createStatement(createAssignment(createIdentifier(prevArgName!), func.body as Expression))]); + if (prevArgName && (nextDotThen || hasFollowingReturn)) { + + const nodeFlags = getNodeFlags(prevArgName, varDeclFlags); + if (varDeclFlags && varDeclFlags.has(prevArgName.text) && nodeFlags === undefined) { + return createNodeArray([createStatement(createAssignment(prevArgName, func.body as Expression))]); } return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], NodeFlags.Let)))]); + (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], getNodeFlags(prevArgName, varDeclFlags))))]); } else if (prevArgName) { return createNodeArray([createReturn(func.body as Expression)]); @@ -524,13 +531,13 @@ namespace ts.codefix { return createNodeArray([]); } - function removeReturns(stmts: NodeArray, prevArgName: string, varDeclFlags?: Map): NodeArray { + function removeReturns(stmts: NodeArray, prevArgName: Identifier, varDeclFlags?: Map): NodeArray { const ret: Statement[] = []; for (const stmt of stmts) { if (isReturnStatement(stmt)) { if (stmt.expression) { ret.push(createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], getNodeFlags(stmt, varDeclFlags))))); + (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], getNodeFlags(prevArgName, varDeclFlags))))); } } else { @@ -541,7 +548,7 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, hasFollowingReturn: boolean, prevArgName?: string) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, hasFollowingReturn: boolean, prevArgName?: Identifier) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { @@ -574,26 +581,26 @@ namespace ts.codefix { return (node.expression).name.text === funcName && checker.isPromiseLikeType(nodeType); } - function getArgName(funcNode: Node, checker: TypeChecker, synthNamesMap: Map): string { - let name; + function getArgName(funcNode: Node, checker: TypeChecker, synthNamesMap: Map): Identifier { + let name: Identifier | undefined; const funcNodeType = checker.getTypeAtLocation(funcNode); if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { - name = (funcNode.parameters[0].name).text; + name = funcNode.parameters[0].name as Identifier; } else if (funcNodeType && funcNodeType.getCallSignatures().length > 0 && funcNodeType.getCallSignatures()[0].parameters.length > 0) { - name = funcNodeType.getCallSignatures()[0].parameters[0].name; + name = createIdentifier(funcNodeType.getCallSignatures()[0].parameters[0].name); // TODO : maybe get rid of this } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { - name = (funcNode.arguments[0]).text; + name = funcNode.arguments[0] as Identifier; } else if (isIdentifier(funcNode)) { name = synthNamesMap.get(funcNode.text); } - if (name === undefined || name === "_") { - return ""; + if (name === undefined || name.text === "_") { + return createIdentifier(""); } return name; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 3f6249cf64322..f9d3a6b9360f1 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -130,7 +130,7 @@ namespace ts { if (checker.isPromiseLikeType(returnType)) { // collect all the return statements // check that a property access expression exists in there and that it is a handler - const retStmts = getReturnStatementsWithPromiseCallbacks(node, checker); + const retStmts = getReturnStatementsWithPromiseCallbacks(node); if (retStmts.length > 0) { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function)); } @@ -141,7 +141,7 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - export function getReturnStatementsWithPromiseCallbacks(node: Node, checker: TypeChecker): [Node[], Map, boolean] { + export function getReturnStatementsWithPromiseCallbacks(node: Node): [Node[], Map, boolean] { const retStmts: Node[] = []; let varDeclFlagsMap: Map = new MapCtr(); let hasFollowingRetStmt = false; @@ -158,7 +158,7 @@ namespace ts { } function hasCallback(returnChild: Node) { - const symbol = checker.getSymbolAtLocation(returnChild); + //const symbol = checker.getSymbolAtLocation(returnChild); if (isCallback(returnChild)) { retStmts.push(child as ReturnStatement); @@ -178,21 +178,33 @@ namespace ts { function findCallbackUses(identUse: Node) { if (isVariableDeclarationList(identUse)) { for (let varDecl of identUse.declarations) { - if (varDecl.initializer && isCallExpression(varDecl.initializer) && - symbol === checker.getSymbolAtLocation(varDecl.name)) { + + /* + const maybeSymbol = checker.getSymbolAtLocation(varDecl.name); + const varDeclSymbol = !maybeSymbol && varDecl.original ? checker.getSymbolAtLocation((varDecl.original)!.name) : maybeSymbol;*/ + if (varDecl.initializer && isCallExpression(varDecl.initializer) /*&& + symbol === varDeclSymbol*/) { retStmts.push(parent); - varDeclFlagsMap.set(String(getNodeId(varDecl.initializer)), identUse.flags); + let flags = identUse.original ? identUse.original.flags : identUse.flags; + varDeclFlagsMap.set((varDecl.name).text, flags); } } } else if (isCallback(identUse)) { - if (symbol === checker.getSymbolAtLocation(((identUse).expression).expression)) { + /* + const expr = ((identUse).expression).expression; + const maybeSymbol = checker.getSymbolAtLocation(expr); + const varDeclSymbol = !maybeSymbol && expr.original ? checker.getSymbolAtLocation(expr.original) : maybeSymbol; + if (symbol === varDeclSymbol) {*/ retStmts.push(parent as CallExpression); - } + //} } else if (isAssignmentExpression(identUse)) { retStmts.push(parent); - varDeclFlagsMap.set(String(getNodeId(identUse.right)), NodeFlags.None) + /* + const maybeSymbol = checker.getSymbolAtLocation(identUse.left); + const varDeclSymbol = !maybeSymbol && identUse.left.original ? checker.getSymbolAtLocation(identUse.left.original) : maybeSymbol;*/ + varDeclFlagsMap.set((identUse.left).text, NodeFlags.None) } else { parent = identUse; From 3088e436f7da9de352384d84597b493c44ce5c0f Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 19 Jul 2018 15:57:30 -0700 Subject: [PATCH 118/196] Fixed assignment exprsesions --- .../codefixes/convertToAsyncFunction.ts | 69 +++++++++++++++---- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 558d7dd6246fe..b8d9f0853bea9 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -64,14 +64,31 @@ namespace ts.codefix { } }); - replaceNodes(changes, sourceFile, retStmts, allNewNodes); + replaceNodes(changes, sourceFile, removeUnglued(retStmts, checker), allNewNodes); } - function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes: Node[], newNodes: Node[]) { - if (oldNodes.length > 0 && isReturnStatement(oldNodes[0]) && (oldNodes[0]).expression && isIdentifier((oldNodes[0]).expression!)) { - oldNodes = oldNodes.slice(1); + function removeUnglued(retStmts: Node[], checker: TypeChecker): Node[] { + let newRetStmts: Node[] = []; + for (let stmt of retStmts) { + let keepStmt = false; + forEachChild(stmt, function visit(node) { + if (isCallExpression(node) && (isCallback(node, "then", checker) || isCallback(node, "catch", checker) || returnsAPromise(node, checker))) { + keepStmt = true; + } + else { + forEachChild(node, visit); + } + }); + + if (keepStmt) { + newRetStmts.push(stmt); + } } + return newRetStmts; + } + + function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes: Node[], newNodes: Node[]) { let i = 0; while (i < oldNodes.length && i < newNodes.length) { if (i === oldNodes.length - 1 && i < newNodes.length - 1) { @@ -142,7 +159,7 @@ namespace ts.codefix { let added = false; promiseVars.forEach((names: string[], varName: string) => { - if (isUsedIn(names.concat(varName), expr)) { + if (!added && isUsedIn(names.concat(varName), expr)) { addToMap(returnMap, varName, expr); added = true; @@ -152,7 +169,6 @@ namespace ts.codefix { addToMap(promiseVars, varName, newName.text); } } - return; } }); @@ -161,7 +177,7 @@ namespace ts.codefix { if (isVariableStatement(expr) && expr.declarationList.declarations.length === 1 && expr.declarationList.declarations[0].initializer) { callExpr = expr.declarationList.declarations[0].initializer!; } - else if (isAssignmentExpression(expr) && isCallExpression(expr.right) && returnsAPromise(expr.right, checker)) { + else if (isAssignmentExpression(expr)) { callExpr = expr.right; } else if (isExpressionStatement(expr)) { @@ -211,7 +227,7 @@ namespace ts.codefix { function glueTogetherCallbacks(retStmts: Node[]): [CallExpression | undefined, Identifier | undefined] { retStmts = retStmts.slice(0); - const stmt = retStmts.pop(); + let stmt = retStmts.pop(); if (!stmt) { return [undefined, undefined]; } @@ -255,15 +271,42 @@ namespace ts.codefix { return [gluedExpr, retName]; } + else if (isBinaryExpression(stmt) && stmt.operatorToken.kind === SyntaxKind.EqualsToken){ + let [gluedExpr, retName] = glueTogetherCallbacks(retStmts.concat(stmt.right)); + if (!retName && isIdentifier(stmt.left)) { + retName = stmt.left; + } + return [gluedExpr, retName]; + } else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { return [undefined, stmt.expression]; } else if (isExpression(stmt)) { - forEachChild(stmt, function visit(node: Node){ - if (isIdentifier(node)) { - return [undefined, node.text]; - } - }); + if (isVariableDeclaration(stmt)) { + stmt = stmt.initializer; + } + else if (isAssignmentExpression(stmt)) { + stmt = stmt.right; + } + + if (!stmt) { + return [undefined, undefined]; + } + + let retName; + if (isIdentifier(stmt)) { + retName = stmt; + } + else { + forEachChild(stmt, function visit(node: Node) { + if (isIdentifier(node)) { + retName = node; + } + }); + } + + let gluedExpr = glueTogetherCallbacks(retStmts); + return [gluedExpr[0], retName]; } return [undefined, undefined]; From 963a759b160f0e6c7ab51df3e773afb070bf7018 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 19 Jul 2018 15:58:48 -0700 Subject: [PATCH 119/196] edited tests and added baselines --- .../unittests/convertToAsyncFunction.ts | 1 + .../convertToAsyncFunction_VarReturn09.js | 27 +++++++++++++++++++ .../convertToAsyncFunction_VarReturn09.ts | 27 +++++++++++++++++++ .../convertToAsyncFunction_VarReturn10.js | 25 +++++++++++++++++ .../convertToAsyncFunction_VarReturn10.ts | 25 +++++++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 1e274a4c489f7..7c37f10e2407f 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -506,6 +506,7 @@ function [#|f|]() { let blob2 = fetch("https://microsoft.com"); blob2.then(res => console.log("res:", res)); blob.then(resp => console.log(resp)); + blob3 = fetch("test.com"); blob3 = blob2; return blob; } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.js new file mode 100644 index 0000000000000..5f912944b5589 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.js @@ -0,0 +1,27 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob3; + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + blob3 = blob2.catch(rej => rej.ok); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let blob3; + let resp = await fetch("https://typescriptlang.org"); + let blob = console.log(resp); + try { + let res = await fetch("https://microsoft.com"); + console.log("res:", res); + } + catch (rej) { + blob3 = rej.ok; + } + return blob; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.ts new file mode 100644 index 0000000000000..5f912944b5589 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.ts @@ -0,0 +1,27 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob3; + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + blob3 = blob2.catch(rej => rej.ok); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let blob3; + let resp = await fetch("https://typescriptlang.org"); + let blob = console.log(resp); + try { + let res = await fetch("https://microsoft.com"); + console.log("res:", res); + } + catch (rej) { + blob3 = rej.ok; + } + return blob; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.js new file mode 100644 index 0000000000000..8221b0d7b7bdb --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.js @@ -0,0 +1,25 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob3; + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + blob3 = fetch("test.com"); + blob3 = blob2; + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let blob3; + let resp = await fetch("https://typescriptlang.org"); + let blob = console.log(resp); + let res = await fetch("https://microsoft.com"); + let blob2 = console.log("res:", res); + blob3 = await fetch("test.com"); + blob3 = blob2; + return blob; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.ts new file mode 100644 index 0000000000000..8221b0d7b7bdb --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.ts @@ -0,0 +1,25 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob3; + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + blob3 = fetch("test.com"); + blob3 = blob2; + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let blob3; + let resp = await fetch("https://typescriptlang.org"); + let blob = console.log(resp); + let res = await fetch("https://microsoft.com"); + let blob2 = console.log("res:", res); + blob3 = await fetch("test.com"); + blob3 = blob2; + return blob; +} From c2879dee95ea8833f8e25ca89778069a96bdfea4 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 19 Jul 2018 16:09:48 -0700 Subject: [PATCH 120/196] Fixed node flags bug --- .../codefixes/convertToAsyncFunction.ts | 19 ++++++++----------- src/services/suggestionDiagnostics.ts | 6 +++--- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index b8d9f0853bea9..ee83ab92cdb90 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -413,7 +413,7 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { if (!node) { return []; } @@ -434,7 +434,7 @@ namespace ts.codefix { return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); @@ -446,7 +446,7 @@ namespace ts.codefix { return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier @@ -474,19 +474,16 @@ namespace ts.codefix { return []; } - function getNodeFlags(node: Identifier, varDeclFlags?: Map): NodeFlags | undefined { + function getNodeFlags(node: Identifier, varDeclFlags?: Map): NodeFlags | undefined { if (varDeclFlags && varDeclFlags.has(node.text)) { - let flags = varDeclFlags.get(node.text)!; - if (flags & NodeFlags.Let || flags & NodeFlags.Const || flags & NodeFlags.None) { - return flags; - } + return varDeclFlags.get(node.text); } else { return NodeFlags.Let; } } - function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: Identifier, varDeclFlags?: Map): Statement[] { + function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: Identifier, varDeclFlags?: Map): Statement[] { const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (prevArgName && varDeclFlags)) { // is an assignment @@ -505,7 +502,7 @@ namespace ts.codefix { return [createReturn(node)]; } - function getCallbackBody(func: Node, prevArgName: Identifier | undefined, argName: Identifier, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: Identifier | undefined, argName: Identifier, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, isRej = false): NodeArray { const nextDotThen = lastDotThenMap.get(String(getNodeId(parent))); switch (func.kind) { @@ -574,7 +571,7 @@ namespace ts.codefix { return createNodeArray([]); } - function removeReturns(stmts: NodeArray, prevArgName: Identifier, varDeclFlags?: Map): NodeArray { + function removeReturns(stmts: NodeArray, prevArgName: Identifier, varDeclFlags?: Map): NodeArray { const ret: Statement[] = []; for (const stmt of stmts) { if (isReturnStatement(stmt)) { diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index f9d3a6b9360f1..cc42063d6a757 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -141,9 +141,9 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - export function getReturnStatementsWithPromiseCallbacks(node: Node): [Node[], Map, boolean] { + export function getReturnStatementsWithPromiseCallbacks(node: Node): [Node[], Map, boolean] { const retStmts: Node[] = []; - let varDeclFlagsMap: Map = new MapCtr(); + let varDeclFlagsMap: Map = new MapCtr(); let hasFollowingRetStmt = false; forEachChild(node, visit); @@ -204,7 +204,7 @@ namespace ts { /* const maybeSymbol = checker.getSymbolAtLocation(identUse.left); const varDeclSymbol = !maybeSymbol && identUse.left.original ? checker.getSymbolAtLocation(identUse.left.original) : maybeSymbol;*/ - varDeclFlagsMap.set((identUse.left).text, NodeFlags.None) + varDeclFlagsMap.set((identUse.left).text, undefined) } else { parent = identUse; From b446923c144e15557c2f360904d1c84c3b734e57 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 19 Jul 2018 17:04:01 -0700 Subject: [PATCH 121/196] Fixed var decl lists with multiple var decls --- .../codefixes/convertToAsyncFunction.ts | 26 ++++++++++++++++--- src/services/suggestionDiagnostics.ts | 8 +++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index ee83ab92cdb90..99c101d1a9268 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -112,7 +112,7 @@ namespace ts.codefix { let expr: Node; let name: Identifier | undefined; if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 0) { - expr = stmt; //fix this for a varDeclList + expr = stmt; name = stmt.declarationList.declarations[0].name as Identifier; } else if (isAssignmentExpression(stmt)) { @@ -141,6 +141,22 @@ namespace ts.codefix { return [expr, name]; } + function seperateVarDecls(stmts: Node[]): Node[] { + let ret: Node[] = []; + for (let stmt of stmts) { + if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 1) { + for (let varDecl of stmt.declarationList.declarations) { + ret.push(createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(varDecl.name, undefined, varDecl.initializer)], stmt.flags))); + } + } + else { + ret.push(stmt); + } + } + return ret; + } + + retStmts = seperateVarDecls(retStmts); for (let stmt of retStmts) { let expr: Node; let name: Identifier | undefined; @@ -187,7 +203,7 @@ namespace ts.codefix { callExpr = expr; } - if (isCallExpression(callExpr) && returnsAPromise(callExpr, checker) || isReturnStatement(callExpr)) { + if (isCallExpression(callExpr) && returnsAPromise(callExpr, checker) || isReturnStatement(callExpr) || callExpr.flags & NodeFlags.Synthesized) { addToMap(promiseVars, name.text, undefined); addToMap(returnMap, name.text, expr); } @@ -507,7 +523,7 @@ namespace ts.codefix { const nextDotThen = lastDotThenMap.get(String(getNodeId(parent))); switch (func.kind) { case SyntaxKind.Identifier: - if (!prevArgName || !argName) { + if (!argName) { break; } @@ -518,6 +534,10 @@ namespace ts.codefix { synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName]); + if (!prevArgName) { + break; + } + const nodeFlags = getNodeFlags(prevArgName, varDeclFlags); if (varDeclFlags && varDeclFlags.has(prevArgName.text) && nodeFlags === undefined) { return createNodeArray([createStatement(createAssignment(prevArgName, createAwait(synthCall)))]); diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index cc42063d6a757..8997e4240c7f9 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -177,6 +177,8 @@ namespace ts { function findCallbackUses(identUse: Node) { if (isVariableDeclarationList(identUse)) { + + let isCallback = false; for (let varDecl of identUse.declarations) { /* @@ -184,11 +186,15 @@ namespace ts { const varDeclSymbol = !maybeSymbol && varDecl.original ? checker.getSymbolAtLocation((varDecl.original)!.name) : maybeSymbol;*/ if (varDecl.initializer && isCallExpression(varDecl.initializer) /*&& symbol === varDeclSymbol*/) { - retStmts.push(parent); + isCallback = true; let flags = identUse.original ? identUse.original.flags : identUse.flags; varDeclFlagsMap.set((varDecl.name).text, flags); } } + + if (isCallback) { + retStmts.push(parent); + } } else if (isCallback(identUse)) { /* From 3cf0dfdf4638da40fa353b98a3458fd17c3ca010 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 19 Jul 2018 17:04:45 -0700 Subject: [PATCH 122/196] Added baselines --- .../convertToAsyncFunction_VarReturn04.js | 27 +++++++++++++++++++ .../convertToAsyncFunction_VarReturn04.ts | 27 +++++++++++++++++++ .../convertToAsyncFunction_VarReturn05.js | 2 +- .../convertToAsyncFunction_VarReturn07.js | 19 +++++++++++++ .../convertToAsyncFunction_VarReturn07.ts | 19 +++++++++++++ 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.ts diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.js new file mode 100644 index 0000000000000..f5efd9400bf44 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.js @@ -0,0 +1,27 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + var blob = fetch("https://typescriptlang.org").then(res => console.log(res)), blob2 = fetch("https://microsoft.com").then(res => res.ok).catch(err); + return blob; +} +function err (rej) { + console.log(rej) +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let res = await fetch("https://typescriptlang.org"); + var blob = console.log(res); + try { + let res_1 = await fetch("https://microsoft.com"); + res_1.ok; + } + catch (rej) { + return err(rej); + } + return blob; +} +function err (rej) { + console.log(rej) +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.ts new file mode 100644 index 0000000000000..f5efd9400bf44 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.ts @@ -0,0 +1,27 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + var blob = fetch("https://typescriptlang.org").then(res => console.log(res)), blob2 = fetch("https://microsoft.com").then(res => res.ok).catch(err); + return blob; +} +function err (rej) { + console.log(rej) +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let res = await fetch("https://typescriptlang.org"); + var blob = console.log(res); + try { + let res_1 = await fetch("https://microsoft.com"); + res_1.ok; + } + catch (rej) { + return err(rej); + } + return blob; +} +function err (rej) { + console.log(rej) +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js index 803fed4f9cc6a..8c1c5dc3d64d8 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js @@ -11,6 +11,6 @@ function /*[#|*/f/*|]*/() { async function f() { let res = await fetch("https://typescriptlang.org"); let x = console.log(res); - let blob = x; + var blob = x; return blob; } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.js new file mode 100644 index 0000000000000..ad2bcc427b598 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.js @@ -0,0 +1,19 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let resp = await fetch("https://typescriptlang.org"); + let blob = console.log(resp); + let res = await fetch("https://microsoft.com"); + console.log("res:", res); + return blob; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.ts new file mode 100644 index 0000000000000..ad2bcc427b598 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.ts @@ -0,0 +1,19 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let blob = fetch("https://typescriptlang.org"); + let blob2 = fetch("https://microsoft.com"); + blob2.then(res => console.log("res:", res)); + blob.then(resp => console.log(resp)); + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let resp = await fetch("https://typescriptlang.org"); + let blob = console.log(resp); + let res = await fetch("https://microsoft.com"); + console.log("res:", res); + return blob; +} From 9e3302efc97cb1bbffe377ed99235889c829abec Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Sun, 22 Jul 2018 17:31:45 -0700 Subject: [PATCH 123/196] Keep track of last ret statement --- .../codefixes/convertToAsyncFunction.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 99c101d1a9268..77af03ab38a24 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -36,6 +36,7 @@ namespace ts.codefix { allNewNodes = allNewNodes.concat(newNodes); } else { + retStmtName = createIdentifier("empty"); for (const stmt of stmts) { if (isCallExpression(stmt)) { const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); @@ -429,7 +430,7 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: Identifier): Statement[] { if (!node) { return []; } @@ -450,7 +451,7 @@ namespace ts.codefix { return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: Identifier): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); @@ -462,7 +463,7 @@ namespace ts.codefix { return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, prevArgName?: Identifier): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: Identifier): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier @@ -478,7 +479,7 @@ namespace ts.codefix { const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, argNameRes).concat(callbackBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, /*isRej*/ true); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, /*isRej*/ true); const catchClause = createCatchClause(argNameRej.text, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; @@ -518,7 +519,7 @@ namespace ts.codefix { return [createReturn(node)]; } - function getCallbackBody(func: Node, prevArgName: Identifier | undefined, argName: Identifier, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: boolean, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: Identifier | undefined, argName: Identifier, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, isRej = false): NodeArray { const nextDotThen = lastDotThenMap.get(String(getNodeId(parent))); switch (func.kind) { @@ -551,13 +552,14 @@ namespace ts.codefix { case SyntaxKind.ArrowFunction: if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(func.body as Node); + const retAppended = hasFollowingReturn ? createBlock(func.body.statements.concat(hasFollowingReturn)) : func.body; + const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(retAppended as Node); const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, hasFollowingReturn, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - return nextDotThen ? removeReturns(func.body.statements, prevArgName!, varDeclFlags) : func.body.statements; + return (nextDotThen || hasFollowingReturn) ? removeReturns(func.body.statements, prevArgName!, varDeclFlags) : func.body.statements; } else if (isArrowFunction(func)) { @@ -608,7 +610,7 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, hasFollowingReturn: boolean, prevArgName?: Identifier) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: Identifier) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { From 2b04c4ac4a19f392cf4fb3180bf466fa952c2d19 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 23 Jul 2018 11:13:01 -0700 Subject: [PATCH 124/196] Reuse variable declarations if possible. Fixed some linting errors --- .../codefixes/convertToAsyncFunction.ts | 146 ++++++++++-------- src/services/suggestionDiagnostics.ts | 18 +-- 2 files changed, 91 insertions(+), 73 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 77af03ab38a24..d747ae163f8ea 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -18,12 +18,14 @@ namespace ts.codefix { changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); const varNamesMap: Map = new MapCtr(); - const synthNamesMap: Map = new MapCtr(); + const synthNamesMap: Map<[Identifier, number]> = new MapCtr(); // identifier boolean pair to indicate if it has been declared const lastDotThenMap: Map = new MapCtr(); - const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); + const varDeclFlagsMap: Map = new MapCtr(); + + const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap, varDeclFlagsMap); findLastDotThens(funcToConvertRenamed, lastDotThenMap, checker); - const [retStmts, varDeclFlags, hasFollowingReturn] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed); + const [retStmts, varDeclFlags, hasFollowingReturn] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, varDeclFlagsMap); let allNewNodes: Node[] = []; const retStmtsMap: Map = seperateCallbacksByVariable(retStmts, checker); @@ -32,14 +34,15 @@ namespace ts.codefix { const gluedCallback = glued[0]; let retStmtName = glued[1]; if (gluedCallback) { - const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); + let retStmtNameArg: [Identifier, number] | undefined = retStmtName ? [retStmtName, 1] : undefined; + const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtNameArg); allNewNodes = allNewNodes.concat(newNodes); } else { - retStmtName = createIdentifier("empty"); + let retStmtNameArg: [Identifier, number] = [createIdentifier("empty"), 1]; for (const stmt of stmts) { if (isCallExpression(stmt)) { - const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); + const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtNameArg); allNewNodes = allNewNodes.concat(newNodes); } else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { @@ -53,7 +56,7 @@ namespace ts.codefix { } if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtName); + const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtNameArg); allNewNodes = allNewNodes.concat(newNodes); } else if (!isFunctionLike(node)) { @@ -106,14 +109,14 @@ namespace ts.codefix { } function seperateCallbacksByVariable(retStmts: Node[], checker: TypeChecker): Map { - let returnMap: Map = new MapCtr(); - let promiseVars: Map = new MapCtr(); + const returnMap: Map = new MapCtr(); + const promiseVars: Map = new MapCtr(); function getNameAndExpr(stmt: Node): [Node, Identifier | undefined] { let expr: Node; let name: Identifier | undefined; if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 0) { - expr = stmt; + expr = stmt; name = stmt.declarationList.declarations[0].name as Identifier; } else if (isAssignmentExpression(stmt)) { @@ -143,11 +146,11 @@ namespace ts.codefix { } function seperateVarDecls(stmts: Node[]): Node[] { - let ret: Node[] = []; - for (let stmt of stmts) { + const ret: Node[] = []; + for (const stmt of stmts) { if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 1) { - for (let varDecl of stmt.declarationList.declarations) { - ret.push(createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(varDecl.name, undefined, varDecl.initializer)], stmt.flags))); + for (const varDecl of stmt.declarationList.declarations) { + ret.push(createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(varDecl.name, /*type*/ undefined, varDecl.initializer)], stmt.flags))); } } else { @@ -158,7 +161,7 @@ namespace ts.codefix { } retStmts = seperateVarDecls(retStmts); - for (let stmt of retStmts) { + for (const stmt of retStmts) { let expr: Node; let name: Identifier | undefined; @@ -181,7 +184,7 @@ namespace ts.codefix { added = true; if (isVariableStatement(expr) || isAssignmentExpression(expr)) { - let newName = getNameAndExpr(expr)[1]; + const newName = getNameAndExpr(expr)[1]; if (newName) { addToMap(promiseVars, varName, newName.text); } @@ -205,7 +208,7 @@ namespace ts.codefix { } if (isCallExpression(callExpr) && returnsAPromise(callExpr, checker) || isReturnStatement(callExpr) || callExpr.flags & NodeFlags.Synthesized) { - addToMap(promiseVars, name.text, undefined); + addToMap(promiseVars, name.text, /*value*/ undefined); addToMap(returnMap, name.text, expr); } } @@ -274,10 +277,10 @@ namespace ts.codefix { lhs = gluedExpr; } - return [createCall(lhs, undefined, stmt.arguments), retName]; + return [createCall(lhs, /*typeArguments*/ undefined, stmt.arguments), retName]; } else { - return [stmt, retName] + return [stmt, retName]; } } else if (isExpressionStatement(stmt) && stmt.expression && isAssignmentExpression(stmt.expression)) { @@ -288,7 +291,7 @@ namespace ts.codefix { return [gluedExpr, retName]; } - else if (isBinaryExpression(stmt) && stmt.operatorToken.kind === SyntaxKind.EqualsToken){ + else if (isBinaryExpression(stmt) && stmt.operatorToken.kind === SyntaxKind.EqualsToken) { let [gluedExpr, retName] = glueTogetherCallbacks(retStmts.concat(stmt.right)); if (!retName && isIdentifier(stmt.left)) { retName = stmt.left; @@ -322,7 +325,7 @@ namespace ts.codefix { }); } - let gluedExpr = glueTogetherCallbacks(retStmts); + const gluedExpr = glueTogetherCallbacks(retStmts); return [gluedExpr[0], retName]; } @@ -339,7 +342,7 @@ namespace ts.codefix { if (isCallExpression(node) && isCallback(node, "then", checker)) { lastDotThen.set(String(getNodeId(node)), false); - for (let arg of node.arguments) { + for (const arg of node.arguments) { forEachChild(arg, function visit(argChild: Expression) { if (isCallExpression(argChild) && (returnsAPromise(argChild, checker) || isCallback(argChild, "then", checker))) { lastDotThen.set(String(getNodeId(argChild)), false); @@ -354,7 +357,7 @@ namespace ts.codefix { lastDotThen.set(String(getNodeId(child)), true); } - for (let arg of child.arguments) { + for (const arg of child.arguments) { forEachChild(arg, function visit(argChild: Expression) { if (isCallExpression(argChild) && (returnsAPromise(argChild, checker) || isCallback(argChild, "then", checker))) { lastDotThen.set(String(getNodeId(argChild)), true); @@ -372,7 +375,7 @@ namespace ts.codefix { }); } - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map): FunctionLikeDeclaration { + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map<[Identifier, number]>, varDeclFlags: Map): FunctionLikeDeclaration { const allVarNames: Identifier[] = []; forEachChild(nodeToRename, function visit(node: Node) { @@ -398,11 +401,12 @@ namespace ts.codefix { allVarNames.push(node); // next, add the new variable for the declaration - const synthName = type.getCallSignatures()[0].parameters[0].name; - const newSynthName = getNewNameIfConflict(createIdentifier(synthName), allVarNames); - varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(newSynthName)))), newSynthName.text); - allVarNames.push(newSynthName); - synthNamesMap.set(node.text, newSynthName); + const synthName = createIdentifier(type.getCallSignatures()[0].parameters[0].name); + varDeclFlags.get("test"); //delete this + varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(synthName)))), synthName.text); + allVarNames.push(synthName); + synthNamesMap.set(node.text, [synthName, allVarNames.filter(elem => elem.text === synthName.text).length]); + } else if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { varNamesMap.set(String(getSymbolId(symbol)), newName.text); @@ -430,7 +434,7 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: Identifier): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: [Identifier, number]): Statement[] { if (!node) { return []; } @@ -451,19 +455,26 @@ namespace ts.codefix { return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: Identifier): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: [Identifier, number]): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); + //TODO : create a variable declaration outside of the try block IF the prevArgName is referenced outside of the try block + let varDecl; + if (prevArgName) { + varDecl = createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let)); + varDeclFlags.set(prevArgName[0].text, undefined); + } const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn)); const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn); - const catchClause = createCatchClause(argName.text, createBlock(callbackBody)); + const catchClause = createCatchClause(argName[0].text, createBlock(callbackBody)); - return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined)]; + const tryStatement = createTry(tryBlock, catchClause, /*finallyBlock*/ undefined); + return varDecl ? [varDecl, tryStatement] : [tryStatement]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: Identifier): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: [Identifier, number]): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier @@ -472,6 +483,7 @@ namespace ts.codefix { } const argNameRes = getArgName(res, checker, synthNamesMap); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn); if (rej) { @@ -479,8 +491,9 @@ namespace ts.codefix { const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, argNameRes).concat(callbackBody)); + //TODO : create a variable declaration outside of the try block IF the prevArgName is referenced outside of the try block const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, /*isRej*/ true); - const catchClause = createCatchClause(argNameRej.text, createBlock(callbackBody2)); + const catchClause = createCatchClause(argNameRej[0].text, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } @@ -491,7 +504,7 @@ namespace ts.codefix { return []; } - function getNodeFlags(node: Identifier, varDeclFlags?: Map): NodeFlags | undefined { + function getNodeFlags(node: Identifier, varDeclFlags?: Map): NodeFlags | undefined { if (varDeclFlags && varDeclFlags.has(node.text)) { return varDeclFlags.get(node.text); } @@ -500,16 +513,17 @@ namespace ts.codefix { } } - function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: Identifier, varDeclFlags?: Map): Statement[] { + function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: [Identifier, number], varDeclFlags?: Map): Statement[] { const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (prevArgName && varDeclFlags)) { // is an assignment - const nodeFlags = getNodeFlags(prevArgName, varDeclFlags); - if (varDeclFlags && varDeclFlags.has(prevArgName.text) && nodeFlags === undefined) { - return [createStatement(createAssignment(prevArgName, createAwait(node)))]; + const nodeFlags = getNodeFlags(prevArgName[0], varDeclFlags); + if ((varDeclFlags && varDeclFlags.has(prevArgName[0].text) && nodeFlags === undefined) || + prevArgName[1] > 1) { + return [createStatement(createAssignment(prevArgName[0], createAwait(node)))]; } - const varDecl = createVariableDeclaration(prevArgName, /*type*/ undefined, createAwait(node)); + const varDecl = createVariableDeclaration(prevArgName[0], /*type*/ undefined, createAwait(node)); return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], nodeFlags)))]; } else if (!prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { @@ -519,7 +533,7 @@ namespace ts.codefix { return [createReturn(node)]; } - function getCallbackBody(func: Node, prevArgName: Identifier | undefined, argName: Identifier, parent: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, isRej = false): NodeArray { const nextDotThen = lastDotThenMap.get(String(getNodeId(parent))); switch (func.kind) { @@ -528,24 +542,26 @@ namespace ts.codefix { break; } - let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName]); + let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName[0]]); if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { return createNodeArray([createReturn(synthCall)]); } - synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName]); + synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName[0]]); if (!prevArgName) { break; } - const nodeFlags = getNodeFlags(prevArgName, varDeclFlags); - if (varDeclFlags && varDeclFlags.has(prevArgName.text) && nodeFlags === undefined) { - return createNodeArray([createStatement(createAssignment(prevArgName, createAwait(synthCall)))]); + const nodeFlags = getNodeFlags(prevArgName[0], varDeclFlags); + if ((varDeclFlags && varDeclFlags.has(prevArgName[0].text) && nodeFlags === undefined) || + prevArgName[1] > 1) { + return createNodeArray([createStatement(createAssignment(prevArgName[0], createAwait(synthCall)))]); } + prevArgName[1] -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, (createAwait(synthCall)))], nodeFlags)))]); + (createVariableDeclarationList([createVariableDeclaration(prevArgName[0], /*type*/ undefined, (createAwait(synthCall)))], nodeFlags)))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: @@ -553,19 +569,19 @@ namespace ts.codefix { if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { const retAppended = hasFollowingReturn ? createBlock(func.body.statements.concat(hasFollowingReturn)) : func.body; - const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(retAppended as Node); + const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(retAppended as Node, new MapCtr()); const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, hasFollowingReturn, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - return (nextDotThen || hasFollowingReturn) ? removeReturns(func.body.statements, prevArgName!, varDeclFlags) : func.body.statements; + return (nextDotThen || hasFollowingReturn) ? removeReturns(func.body.statements, prevArgName![0], varDeclFlags) : func.body.statements; } else if (isArrowFunction(func)) { // if there is another outer dot then, don't actually return - const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression))[0]; + const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), new MapCtr())[0]; const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, hasFollowingReturn, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); @@ -573,13 +589,15 @@ namespace ts.codefix { if (prevArgName && (nextDotThen || hasFollowingReturn)) { - const nodeFlags = getNodeFlags(prevArgName, varDeclFlags); - if (varDeclFlags && varDeclFlags.has(prevArgName.text) && nodeFlags === undefined) { - return createNodeArray([createStatement(createAssignment(prevArgName, func.body as Expression))]); + const nodeFlags = getNodeFlags(prevArgName[0], varDeclFlags); + if ((varDeclFlags && varDeclFlags.has(prevArgName[0].text) && nodeFlags === undefined) || + prevArgName[1] > 1) { + return createNodeArray([createStatement(createAssignment(prevArgName[0], func.body as Expression))]); } + prevArgName[1] -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName!, /*type*/ undefined, func.body as Expression)], getNodeFlags(prevArgName, varDeclFlags))))]); + (createVariableDeclarationList([createVariableDeclaration(prevArgName[0], /*type*/ undefined, func.body as Expression)], getNodeFlags(prevArgName[0], varDeclFlags))))]); } else if (prevArgName) { return createNodeArray([createReturn(func.body as Expression)]); @@ -593,7 +611,7 @@ namespace ts.codefix { return createNodeArray([]); } - function removeReturns(stmts: NodeArray, prevArgName: Identifier, varDeclFlags?: Map): NodeArray { + function removeReturns(stmts: NodeArray, prevArgName: Identifier, varDeclFlags?: Map): NodeArray { const ret: Statement[] = []; for (const stmt of stmts) { if (isReturnStatement(stmt)) { @@ -610,7 +628,7 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: Identifier) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: [Identifier, number]) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { @@ -643,26 +661,26 @@ namespace ts.codefix { return (node.expression).name.text === funcName && checker.isPromiseLikeType(nodeType); } - function getArgName(funcNode: Node, checker: TypeChecker, synthNamesMap: Map): Identifier { - let name: Identifier | undefined; + function getArgName(funcNode: Node, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>): [Identifier, number] { + let name: [Identifier, number] | undefined; const funcNodeType = checker.getTypeAtLocation(funcNode); if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { - name = funcNode.parameters[0].name as Identifier; + name = [funcNode.parameters[0].name as Identifier, 1]; } else if (funcNodeType && funcNodeType.getCallSignatures().length > 0 && funcNodeType.getCallSignatures()[0].parameters.length > 0) { - name = createIdentifier(funcNodeType.getCallSignatures()[0].parameters[0].name); + name = [createIdentifier(funcNodeType.getCallSignatures()[0].parameters[0].name), 1]; // TODO : maybe get rid of this } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { - name = funcNode.arguments[0] as Identifier; + name = [funcNode.arguments[0] as Identifier, 1]; } else if (isIdentifier(funcNode)) { name = synthNamesMap.get(funcNode.text); } - if (name === undefined || name.text === "_") { - return createIdentifier(""); + if (name === undefined || (name[0] && name[0].text === "_")) { + return [createIdentifier(""), 1]; } return name; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 8997e4240c7f9..b1dcb58886174 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -130,7 +130,8 @@ namespace ts { if (checker.isPromiseLikeType(returnType)) { // collect all the return statements // check that a property access expression exists in there and that it is a handler - const retStmts = getReturnStatementsWithPromiseCallbacks(node); + let varDeclFlagsMap: Map = new MapCtr(); + const retStmts = getReturnStatementsWithPromiseCallbacks(node, varDeclFlagsMap); if (retStmts.length > 0) { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function)); } @@ -141,10 +142,9 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - export function getReturnStatementsWithPromiseCallbacks(node: Node): [Node[], Map, boolean] { + export function getReturnStatementsWithPromiseCallbacks(node: Node, varDeclFlagsMap: Map): [Node[], Map, ReturnStatement | undefined] { const retStmts: Node[] = []; - let varDeclFlagsMap: Map = new MapCtr(); - let hasFollowingRetStmt = false; + let hasFollowingRetStmt: ReturnStatement | undefined; forEachChild(node, visit); function visit(child: Node) { @@ -165,7 +165,7 @@ namespace ts { } else if (isIdentifier(returnChild) && isReturnStatement(child) && child.expression && isIdentifier(child.expression)) { - hasFollowingRetStmt = true; + hasFollowingRetStmt = child; retStmts.push(child); forEachChild(node, findCallbackUses); } @@ -179,15 +179,14 @@ namespace ts { if (isVariableDeclarationList(identUse)) { let isCallback = false; - for (let varDecl of identUse.declarations) { - + for (const varDecl of identUse.declarations) { /* const maybeSymbol = checker.getSymbolAtLocation(varDecl.name); const varDeclSymbol = !maybeSymbol && varDecl.original ? checker.getSymbolAtLocation((varDecl.original)!.name) : maybeSymbol;*/ if (varDecl.initializer && isCallExpression(varDecl.initializer) /*&& symbol === varDeclSymbol*/) { isCallback = true; - let flags = identUse.original ? identUse.original.flags : identUse.flags; + const flags = identUse.original ? identUse.original.flags : identUse.flags; varDeclFlagsMap.set((varDecl.name).text, flags); } } @@ -210,7 +209,7 @@ namespace ts { /* const maybeSymbol = checker.getSymbolAtLocation(identUse.left); const varDeclSymbol = !maybeSymbol && identUse.left.original ? checker.getSymbolAtLocation(identUse.left.original) : maybeSymbol;*/ - varDeclFlagsMap.set((identUse.left).text, undefined) + varDeclFlagsMap.set((identUse.left).text, undefined); } else { parent = identUse; @@ -218,6 +217,7 @@ namespace ts { } } } + forEachChild(child, visit); } return [retStmts, varDeclFlagsMap, hasFollowingRetStmt]; From d3d43d3f97630350c0f6a11f76d3e27e1e883e27 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 23 Jul 2018 11:48:17 -0700 Subject: [PATCH 125/196] Fixed catch returns --- src/services/codefixes/convertToAsyncFunction.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index d747ae163f8ea..d4ac502eb3b3a 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -25,7 +25,7 @@ namespace ts.codefix { const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap, varDeclFlagsMap); findLastDotThens(funcToConvertRenamed, lastDotThenMap, checker); - const [retStmts, varDeclFlags, hasFollowingReturn] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, varDeclFlagsMap); + let [retStmts, varDeclFlags, hasFollowingReturn] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, varDeclFlagsMap); let allNewNodes: Node[] = []; const retStmtsMap: Map = seperateCallbacksByVariable(retStmts, checker); @@ -344,22 +344,21 @@ namespace ts.codefix { for (const arg of node.arguments) { forEachChild(arg, function visit(argChild: Expression) { - if (isCallExpression(argChild) && (returnsAPromise(argChild, checker) || isCallback(argChild, "then", checker))) { + if (isCallExpression(argChild) && (returnsAPromise(argChild, checker) || isCallback(argChild, "then", checker) || isCallback(argChild, "catch", checker))) { lastDotThen.set(String(getNodeId(argChild)), false); } }); } - forEachChild(node, function visit(child: Node) { - if (isCallExpression(child) && (returnsAPromise(child, checker) || isCallback(child, "then", checker))) { + if (isCallExpression(child) && (returnsAPromise(child, checker) || isCallback(child, "then", checker) || isCallback(child, "catch", checker))) { if (!lastDotThen.get(String(getNodeId(child)))) { lastDotThen.set(String(getNodeId(child)), true); } for (const arg of child.arguments) { forEachChild(arg, function visit(argChild: Expression) { - if (isCallExpression(argChild) && (returnsAPromise(argChild, checker) || isCallback(argChild, "then", checker))) { + if (isCallExpression(argChild) && (returnsAPromise(argChild, checker) || isCallback(argChild, "then", checker) || isCallback(child, "catch", checker))) { lastDotThen.set(String(getNodeId(argChild)), true); } }); @@ -543,7 +542,7 @@ namespace ts.codefix { } let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName[0]]); - if (!nextDotThen || (parent.expression).name.text === "catch" || isRej) { + if (!nextDotThen /*|| ((parent.expression).name.text === "catch" && !hasFollowingReturn)*/ || isRej) { return createNodeArray([createReturn(synthCall)]); } @@ -556,12 +555,12 @@ namespace ts.codefix { const nodeFlags = getNodeFlags(prevArgName[0], varDeclFlags); if ((varDeclFlags && varDeclFlags.has(prevArgName[0].text) && nodeFlags === undefined) || prevArgName[1] > 1) { - return createNodeArray([createStatement(createAssignment(prevArgName[0], createAwait(synthCall)))]); + return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName[0]), createAwait(synthCall)))]); } prevArgName[1] -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName[0], /*type*/ undefined, (createAwait(synthCall)))], nodeFlags)))]); + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]), /*type*/ undefined, (createAwait(synthCall)))], nodeFlags)))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: From 99320bc5d1e49590ae85fde6cd725824a47c19c8 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 23 Jul 2018 11:49:51 -0700 Subject: [PATCH 126/196] Added baselines --- ...vertToAsyncFunction_CatchFollowedByThen.js | 34 +++++++++++++++++++ ...vertToAsyncFunction_CatchFollowedByThen.ts | 34 +++++++++++++++++++ ...oAsyncFunction_MultipleThensSameVarName.ts | 4 +-- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js new file mode 100644 index 0000000000000..21017ddbbfd6d --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js @@ -0,0 +1,34 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return result; +} + +function rej(reject){ + return reject; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result; + try { + result = await fetch("https://typescriptlang.org"); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return result; +} + +function rej(reject){ + return reject; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts new file mode 100644 index 0000000000000..21017ddbbfd6d --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts @@ -0,0 +1,34 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return result; +} + +function rej(reject){ + return reject; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result; + try { + result = await fetch("https://typescriptlang.org"); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return result; +} + +function rej(reject){ + return reject; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts index 7fe7e3dd131b8..54d5b536d1b3a 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts @@ -14,8 +14,8 @@ function res2(result){ async function f():Promise { let result = await fetch('https://typescriptlang.org'); - let result_1 = await res(result); - return res2(result_1); + result = await res(result); + return res2(result); } function res(result){ return result.ok; From 5989e3a56d7543d387622a26ffb6f8755f4bbebc Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 23 Jul 2018 14:59:07 -0700 Subject: [PATCH 127/196] Fixed checks for no prevArg --- .../codefixes/convertToAsyncFunction.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index d4ac502eb3b3a..a2f6ea96bab9d 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -460,11 +460,11 @@ namespace ts.codefix { //TODO : create a variable declaration outside of the try block IF the prevArgName is referenced outside of the try block let varDecl; - if (prevArgName) { + if (prevArgName && lastDotThenMap.get(String(getNodeId(node)))) { varDecl = createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let)); varDeclFlags.set(prevArgName[0].text, undefined); } - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName)); const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn); const catchClause = createCatchClause(argName[0].text, createBlock(callbackBody)); @@ -514,18 +514,19 @@ namespace ts.codefix { function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: [Identifier, number], varDeclFlags?: Map): Statement[] { const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); - if (prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (prevArgName && varDeclFlags)) { + const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; + if (hasPrevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (hasPrevArgName && varDeclFlags)) { // is an assignment - const nodeFlags = getNodeFlags(prevArgName[0], varDeclFlags); - if ((varDeclFlags && varDeclFlags.has(prevArgName[0].text) && nodeFlags === undefined) || - prevArgName[1] > 1) { - return [createStatement(createAssignment(prevArgName[0], createAwait(node)))]; + const nodeFlags = getNodeFlags(prevArgName![0], varDeclFlags); + if ((varDeclFlags && varDeclFlags.has(prevArgName![0].text) && nodeFlags === undefined) || + prevArgName![1] > 1) { + return [createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), createAwait(node)))]; } - const varDecl = createVariableDeclaration(prevArgName[0], /*type*/ undefined, createAwait(node)); + const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, createAwait(node)); return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], nodeFlags)))]; } - else if (!prevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { + else if (!hasPrevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { return [createStatement(createAwait(node))]; } @@ -586,19 +587,21 @@ namespace ts.codefix { return createNodeArray(innerCbBody); } - if (prevArgName && (nextDotThen || hasFollowingReturn)) { - const nodeFlags = getNodeFlags(prevArgName[0], varDeclFlags); - if ((varDeclFlags && varDeclFlags.has(prevArgName[0].text) && nodeFlags === undefined) || - prevArgName[1] > 1) { - return createNodeArray([createStatement(createAssignment(prevArgName[0], func.body as Expression))]); + const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; + if (hasPrevArgName && (nextDotThen || hasFollowingReturn)) { + + const nodeFlags = getNodeFlags(prevArgName![0], varDeclFlags); + if ((varDeclFlags && varDeclFlags.has(prevArgName![0].text) && nodeFlags === undefined) || + prevArgName![1] > 1) { + return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), func.body as Expression))]); } - prevArgName[1] -= 1; + prevArgName![1] -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName[0], /*type*/ undefined, func.body as Expression)], getNodeFlags(prevArgName[0], varDeclFlags))))]); + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, func.body as Expression)], getNodeFlags(prevArgName![0], varDeclFlags))))]); } - else if (prevArgName) { + else if (hasPrevArgName) { return createNodeArray([createReturn(func.body as Expression)]); } else { From 136e2f3ae3ffd54cdc2ea2412b1a0f56c9044884 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 23 Jul 2018 14:59:43 -0700 Subject: [PATCH 128/196] Added baselines --- ...vertToAsyncFunction_CatchFollowedByThen.js | 1 + ...vertToAsyncFunction_CatchFollowedByThen.ts | 1 + ...ertToAsyncFunction_InnerVarNameConflict.ts | 28 +++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js index 21017ddbbfd6d..efce5bbce30d4 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js @@ -18,6 +18,7 @@ async function f(){ let result; try { result = await fetch("https://typescriptlang.org"); + result = await res(result); } catch (reject) { result = await rej(reject); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts index 21017ddbbfd6d..efce5bbce30d4 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts @@ -18,6 +18,7 @@ async function f(){ let result; try { result = await fetch("https://typescriptlang.org"); + result = await res(result); } catch (reject) { result = await rej(reject); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts new file mode 100644 index 0000000000000..1a804e9ff4b93 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts @@ -0,0 +1,28 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise { + var blob = fetch("https://typescriptlang.org").then(resp => { + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + }).then(blob => { + return blob.toString(); + }); + + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise { + let resp = await fetch("https://typescriptlang.org"); + let blob_3; + try { + let blob_2 = await resp.blob(); + blob_3 = blob_2.byteOffset; + } + catch (err) { + blob_3 = 'Error'; + } + let blob = blob_3.toString(); + + return blob; +} From 4d9584da559fd13b89187bca9bbbba75e53a242a Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 23 Jul 2018 15:40:03 -0700 Subject: [PATCH 129/196] Fixed check for arg name bug --- .../codefixes/convertToAsyncFunction.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index a2f6ea96bab9d..b6b4c0ebf165c 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -458,7 +458,6 @@ namespace ts.codefix { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); - //TODO : create a variable declaration outside of the try block IF the prevArgName is referenced outside of the try block let varDecl; if (prevArgName && lastDotThenMap.get(String(getNodeId(node)))) { varDecl = createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let)); @@ -535,10 +534,12 @@ namespace ts.codefix { function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, isRej = false): NodeArray { + const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; + const hasArgName = argName && argName[0].text.length > 0; const nextDotThen = lastDotThenMap.get(String(getNodeId(parent))); switch (func.kind) { case SyntaxKind.Identifier: - if (!argName) { + if (!hasArgName) { break; } @@ -549,19 +550,19 @@ namespace ts.codefix { synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName[0]]); - if (!prevArgName) { + if (!hasPrevArgName) { break; } - const nodeFlags = getNodeFlags(prevArgName[0], varDeclFlags); - if ((varDeclFlags && varDeclFlags.has(prevArgName[0].text) && nodeFlags === undefined) || - prevArgName[1] > 1) { - return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName[0]), createAwait(synthCall)))]); + const nodeFlags = getNodeFlags(prevArgName![0], varDeclFlags); + if ((varDeclFlags && varDeclFlags.has(prevArgName![0].text) && nodeFlags === undefined) || + prevArgName![1] > 1) { + return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), createAwait(synthCall)))]); } - prevArgName[1] -= 1; + prevArgName![1] -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]), /*type*/ undefined, (createAwait(synthCall)))], nodeFlags)))]); + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, (createAwait(synthCall)))], nodeFlags)))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: @@ -588,7 +589,6 @@ namespace ts.codefix { } - const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; if (hasPrevArgName && (nextDotThen || hasFollowingReturn)) { const nodeFlags = getNodeFlags(prevArgName![0], varDeclFlags); From 1ea6daf11dda002c25ba81ff29ac20f3a56b4bf3 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 24 Jul 2018 13:37:54 -0700 Subject: [PATCH 130/196] Removed all refactoring for variable returns without direct callbacks --- .../codefixes/convertToAsyncFunction.ts | 381 +++--------------- src/services/suggestionDiagnostics.ts | 65 +-- 2 files changed, 56 insertions(+), 390 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index b6b4c0ebf165c..dcd8f0bcaae71 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -18,81 +18,38 @@ namespace ts.codefix { changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); const varNamesMap: Map = new MapCtr(); - const synthNamesMap: Map<[Identifier, number]> = new MapCtr(); // identifier boolean pair to indicate if it has been declared + const synthNamesMap: Map<[Identifier, number]> = new MapCtr(); // number indicates the number of times it is used after declaration const lastDotThenMap: Map = new MapCtr(); - const varDeclFlagsMap: Map = new MapCtr(); - const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap, varDeclFlagsMap); + const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); findLastDotThens(funcToConvertRenamed, lastDotThenMap, checker); - let [retStmts, varDeclFlags, hasFollowingReturn] = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed, varDeclFlagsMap); - + let retStmts = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed); let allNewNodes: Node[] = []; - const retStmtsMap: Map = seperateCallbacksByVariable(retStmts, checker); - retStmtsMap.forEach((stmts: Node[]) => { - const glued = glueTogetherCallbacks(stmts); - const gluedCallback = glued[0]; - let retStmtName = glued[1]; - if (gluedCallback) { - let retStmtNameArg: [Identifier, number] | undefined = retStmtName ? [retStmtName, 1] : undefined; - const newNodes = parseCallback(gluedCallback, checker, gluedCallback, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtNameArg); + let retStmtNameArg: [Identifier, number] = [createIdentifier("empty"), 1]; + for (const stmt of retStmts) { + if (isCallExpression(stmt)) { + const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, retStmtNameArg); allNewNodes = allNewNodes.concat(newNodes); } else { - let retStmtNameArg: [Identifier, number] = [createIdentifier("empty"), 1]; - for (const stmt of stmts) { - if (isCallExpression(stmt)) { - const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtNameArg); + forEachChild(stmt, function visit(node: Node) { + if (isCallExpression(node)) { + const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, retStmtNameArg); allNewNodes = allNewNodes.concat(newNodes); } - else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { - retStmtName = stmt.expression; - } - else { - forEachChild(stmt, function visit(node: Node) { - - if (isReturnStatement(node) && node.expression && isIdentifier(node.expression)) { - retStmtName = node.expression; - } - - if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, retStmtNameArg); - allNewNodes = allNewNodes.concat(newNodes); - } - else if (!isFunctionLike(node)) { - forEachChild(node, visit); - } - }); + else if (!isFunctionLike(node)) { + forEachChild(node, visit); } - } - } - }); - - replaceNodes(changes, sourceFile, removeUnglued(retStmts, checker), allNewNodes); - } - - function removeUnglued(retStmts: Node[], checker: TypeChecker): Node[] { - let newRetStmts: Node[] = []; - for (let stmt of retStmts) { - let keepStmt = false; - forEachChild(stmt, function visit(node) { - if (isCallExpression(node) && (isCallback(node, "then", checker) || isCallback(node, "catch", checker) || returnsAPromise(node, checker))) { - keepStmt = true; - } - else { - forEachChild(node, visit); - } - }); - - if (keepStmt) { - newRetStmts.push(stmt); + }); } } - return newRetStmts; + replaceNodes(changes, sourceFile, retStmts, allNewNodes); } function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes: Node[], newNodes: Node[]) { + let i = 0; while (i < oldNodes.length && i < newNodes.length) { if (i === oldNodes.length - 1 && i < newNodes.length - 1) { @@ -108,231 +65,6 @@ namespace ts.codefix { } } - function seperateCallbacksByVariable(retStmts: Node[], checker: TypeChecker): Map { - const returnMap: Map = new MapCtr(); - const promiseVars: Map = new MapCtr(); - - function getNameAndExpr(stmt: Node): [Node, Identifier | undefined] { - let expr: Node; - let name: Identifier | undefined; - if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 0) { - expr = stmt; - name = stmt.declarationList.declarations[0].name as Identifier; - } - else if (isAssignmentExpression(stmt)) { - expr = stmt; - name = isIdentifier(stmt.left) ? stmt.left : getNameAndExpr(stmt.left)[1]; - } - else if (isCallExpression(stmt)) { - expr = stmt; - forEachChild(stmt, function visit(node: Node) { - if (!name && isPropertyAccessExpression(node) && isIdentifier(node.expression)) { - name = node.expression; - } - else { - forEachChild(node, visit); - } - }); - } - else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { - expr = stmt; - name = stmt.expression; - } - else { - expr = stmt; - } - - return [expr, name]; - } - - function seperateVarDecls(stmts: Node[]): Node[] { - const ret: Node[] = []; - for (const stmt of stmts) { - if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 1) { - for (const varDecl of stmt.declarationList.declarations) { - ret.push(createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(varDecl.name, /*type*/ undefined, varDecl.initializer)], stmt.flags))); - } - } - else { - ret.push(stmt); - } - } - return ret; - } - - retStmts = seperateVarDecls(retStmts); - for (const stmt of retStmts) { - let expr: Node; - let name: Identifier | undefined; - - if (isExpressionStatement(stmt)) { - expr = stmt.expression; - name = getNameAndExpr(stmt.expression)[1]; - } - else { - [expr, name] = getNameAndExpr(stmt); - } - - if (!expr) { - continue; - } - - let added = false; - promiseVars.forEach((names: string[], varName: string) => { - if (!added && isUsedIn(names.concat(varName), expr)) { - addToMap(returnMap, varName, expr); - added = true; - - if (isVariableStatement(expr) || isAssignmentExpression(expr)) { - const newName = getNameAndExpr(expr)[1]; - if (newName) { - addToMap(promiseVars, varName, newName.text); - } - } - } - }); - - if (!added && name) { - let callExpr: Node; - if (isVariableStatement(expr) && expr.declarationList.declarations.length === 1 && expr.declarationList.declarations[0].initializer) { - callExpr = expr.declarationList.declarations[0].initializer!; - } - else if (isAssignmentExpression(expr)) { - callExpr = expr.right; - } - else if (isExpressionStatement(expr)) { - callExpr = expr.expression; - } - else { - callExpr = expr; - } - - if (isCallExpression(callExpr) && returnsAPromise(callExpr, checker) || isReturnStatement(callExpr) || callExpr.flags & NodeFlags.Synthesized) { - addToMap(promiseVars, name.text, /*value*/ undefined); - addToMap(returnMap, name.text, expr); - } - } - } - - if (returnMap.size === 0) { - returnMap.set("", retStmts); - } - return returnMap; - } - - - function isUsedIn(variables: string[], expr: Node): boolean { - let isUsed = false; - forEachChild(expr, function visit(node: Node) { - if (isIdentifier(node) && variables.filter(name => name === node.text).length > 0) { - isUsed = true; - } - else { - forEachChild(node, visit); - } - }); - - return isUsed; - } - - - function addToMap(map: Map, key: string, val: T) { - if (!map.get(key)) { - map.set(key, [val]); - } - else { - map.get(key)!.push(val); - } - } - - function glueTogetherCallbacks(retStmts: Node[]): [CallExpression | undefined, Identifier | undefined] { - retStmts = retStmts.slice(0); - let stmt = retStmts.pop(); - if (!stmt) { - return [undefined, undefined]; - } - - if (isExpressionStatement(stmt) && stmt.expression && isCallExpression(stmt.expression) - && stmt.expression.expression && isPropertyAccessExpression(stmt.expression.expression)) { - const callArgs: NodeArray = stmt.expression.arguments; - const funcName: Identifier = stmt.expression.expression.name; - const [gluedExpr, retName] = glueTogetherCallbacks(retStmts); - if (gluedExpr) { - const propertyAccessExpr = createPropertyAccess(gluedExpr, funcName); - return [createCall(propertyAccessExpr, /*typeArguments*/ undefined, callArgs), retName]; - } - } - // fix this for multiple declarations - else if (isVariableStatement(stmt) && stmt.declarationList.declarations.length > 0 && stmt.declarationList.declarations[0].initializer) { - return glueTogetherCallbacks(retStmts.concat(stmt.declarationList.declarations[0].initializer!)); - } - else if (isCallExpression(stmt)) { - const [gluedExpr, retName] = glueTogetherCallbacks(retStmts); - if (gluedExpr) { - let lhs; - if (stmt.expression && isPropertyAccessExpression(stmt.expression)) { - lhs = createPropertyAccess(gluedExpr, stmt.expression.name); - } - else { - lhs = gluedExpr; - } - - return [createCall(lhs, /*typeArguments*/ undefined, stmt.arguments), retName]; - } - else { - return [stmt, retName]; - } - } - else if (isExpressionStatement(stmt) && stmt.expression && isAssignmentExpression(stmt.expression)) { - let [gluedExpr, retName] = glueTogetherCallbacks(retStmts.concat(stmt.expression.right)); - if (!retName && isIdentifier(stmt.expression.left)) { - retName = stmt.expression.left; - } - - return [gluedExpr, retName]; - } - else if (isBinaryExpression(stmt) && stmt.operatorToken.kind === SyntaxKind.EqualsToken) { - let [gluedExpr, retName] = glueTogetherCallbacks(retStmts.concat(stmt.right)); - if (!retName && isIdentifier(stmt.left)) { - retName = stmt.left; - } - return [gluedExpr, retName]; - } - else if (isReturnStatement(stmt) && stmt.expression && isIdentifier(stmt.expression)) { - return [undefined, stmt.expression]; - } - else if (isExpression(stmt)) { - if (isVariableDeclaration(stmt)) { - stmt = stmt.initializer; - } - else if (isAssignmentExpression(stmt)) { - stmt = stmt.right; - } - - if (!stmt) { - return [undefined, undefined]; - } - - let retName; - if (isIdentifier(stmt)) { - retName = stmt; - } - else { - forEachChild(stmt, function visit(node: Node) { - if (isIdentifier(node)) { - retName = node; - } - }); - } - - const gluedExpr = glueTogetherCallbacks(retStmts); - return [gluedExpr[0], retName]; - } - - return [undefined, undefined]; - } - - function findLastDotThens(func: FunctionLikeDeclaration, lastDotThen: Map, checker: TypeChecker) { if (!func.body) { return; @@ -374,7 +106,7 @@ namespace ts.codefix { }); } - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map<[Identifier, number]>, varDeclFlags: Map): FunctionLikeDeclaration { + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map<[Identifier, number]>): FunctionLikeDeclaration { const allVarNames: Identifier[] = []; forEachChild(nodeToRename, function visit(node: Node) { @@ -401,11 +133,10 @@ namespace ts.codefix { // next, add the new variable for the declaration const synthName = createIdentifier(type.getCallSignatures()[0].parameters[0].name); - varDeclFlags.get("test"); //delete this varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(synthName)))), synthName.text); allVarNames.push(synthName); synthNamesMap.set(node.text, [synthName, allVarNames.filter(elem => elem.text === synthName.text).length]); - + } else if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { varNamesMap.set(String(getSymbolId(symbol)), newName.text); @@ -433,70 +164,69 @@ namespace ts.codefix { return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: [Identifier, number]): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { if (!node) { return []; } if (isCallExpression(node) && returnsAPromise(node, checker)) { - return parsePromiseCall(node, lastDotThenMap, prevArgName, varDeclFlags); + return parsePromiseCall(node, lastDotThenMap, prevArgName); } else if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); + return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); + return parseCatch(node, checker, synthNamesMap, lastDotThenMap, prevArgName); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: [Identifier, number]): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); const argName = getArgName(func, checker, synthNamesMap); let varDecl; if (prevArgName && lastDotThenMap.get(String(getNodeId(node)))) { varDecl = createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let)); - varDeclFlags.set(prevArgName[0].text, undefined); } - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, prevArgName)); - const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap); const catchClause = createCatchClause(argName[0].text, createBlock(callbackBody)); const tryStatement = createTry(tryBlock, catchClause, /*finallyBlock*/ undefined); return varDecl ? [varDecl, tryStatement] : [tryStatement]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: [Identifier, number]): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier if (!res) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName); } const argNameRes = getArgName(res, checker, synthNamesMap); - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap); if (rej) { const argNameRej = getArgName(rej, checker, synthNamesMap); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, argNameRes).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes).concat(callbackBody)); //TODO : create a variable declaration outside of the try block IF the prevArgName is referenced outside of the try block - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, /*isRej*/ true); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, /*isRej*/ true); const catchClause = createCatchClause(argNameRej[0].text, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, varDeclFlags, hasFollowingReturn, argNameRes).concat(callbackBody); + return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes).concat(callbackBody); } return []; @@ -511,19 +241,17 @@ namespace ts.codefix { } } - function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: [Identifier, number], varDeclFlags?: Map): Statement[] { + function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; - if (hasPrevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent) || (hasPrevArgName && varDeclFlags)) { - // is an assignment - const nodeFlags = getNodeFlags(prevArgName![0], varDeclFlags); - if ((varDeclFlags && varDeclFlags.has(prevArgName![0].text) && nodeFlags === undefined) || - prevArgName![1] > 1) { + if (hasPrevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { + + if (prevArgName![1] > 1) { return [createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), createAwait(node)))]; } - const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, createAwait(node)); - return [createVariableStatement(/* modifiers */ undefined, (createVariableDeclarationList([varDecl], nodeFlags)))]; + const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), undefined, createAwait(node)); + return [createVariableStatement(undefined, (createVariableDeclarationList([varDecl], /*nodeFlags*/ NodeFlags.Let)))]; } else if (!hasPrevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { return [createStatement(createAwait(node))]; @@ -532,7 +260,8 @@ namespace ts.codefix { return [createReturn(node)]; } - function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, varDeclFlags: Map, hasFollowingReturn: ReturnStatement | undefined, isRej = false): NodeArray { + function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, + lastDotThenMap: Map, isRej = false): NodeArray { const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; const hasArgName = argName && argName[0].text.length > 0; @@ -544,7 +273,7 @@ namespace ts.codefix { } let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName[0]]); - if (!nextDotThen /*|| ((parent.expression).name.text === "catch" && !hasFollowingReturn)*/ || isRej) { + if (!nextDotThen || isRej) { return createNodeArray([createReturn(synthCall)]); } @@ -554,52 +283,46 @@ namespace ts.codefix { break; } - const nodeFlags = getNodeFlags(prevArgName![0], varDeclFlags); - if ((varDeclFlags && varDeclFlags.has(prevArgName![0].text) && nodeFlags === undefined) || - prevArgName![1] > 1) { + if (prevArgName![1] > 1) { return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), createAwait(synthCall)))]); } prevArgName![1] -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, (createAwait(synthCall)))], nodeFlags)))]); + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - const retAppended = hasFollowingReturn ? createBlock(func.body.statements.concat(hasFollowingReturn)) : func.body; - const [innerRetStmts, varDeclFlags] = getReturnStatementsWithPromiseCallbacks(retAppended as Node, new MapCtr()); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, hasFollowingReturn, prevArgName); + const innerRetStmts = getReturnStatementsWithPromiseCallbacks(func.body); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - return (nextDotThen || hasFollowingReturn) ? removeReturns(func.body.statements, prevArgName![0], varDeclFlags) : func.body.statements; + return (nextDotThen) ? removeReturns(func.body.statements, prevArgName![0]) : func.body.statements; } else if (isArrowFunction(func)) { // if there is another outer dot then, don't actually return - const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression), new MapCtr())[0]; - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, hasFollowingReturn, prevArgName); + const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression)); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - if (hasPrevArgName && (nextDotThen || hasFollowingReturn)) { - - const nodeFlags = getNodeFlags(prevArgName![0], varDeclFlags); - if ((varDeclFlags && varDeclFlags.has(prevArgName![0].text) && nodeFlags === undefined) || - prevArgName![1] > 1) { + if (hasPrevArgName && nextDotThen) { + if (prevArgName![1] > 1) { return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), func.body as Expression))]); } prevArgName![1] -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, func.body as Expression)], getNodeFlags(prevArgName![0], varDeclFlags))))]); + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, func.body as Expression)], NodeFlags.Let)))]); } else if (hasPrevArgName) { return createNodeArray([createReturn(func.body as Expression)]); @@ -630,12 +353,12 @@ namespace ts.codefix { return createNodeArray(ret); } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, hasFollowingReturn: ReturnStatement | undefined, prevArgName?: [Identifier, number]) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, new MapCtr(), hasFollowingReturn, prevArgName); + const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index b1dcb58886174..97247dea6522b 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -130,8 +130,7 @@ namespace ts { if (checker.isPromiseLikeType(returnType)) { // collect all the return statements // check that a property access expression exists in there and that it is a handler - let varDeclFlagsMap: Map = new MapCtr(); - const retStmts = getReturnStatementsWithPromiseCallbacks(node, varDeclFlagsMap); + const retStmts = getReturnStatementsWithPromiseCallbacks(node); if (retStmts.length > 0) { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function)); } @@ -142,9 +141,8 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } - export function getReturnStatementsWithPromiseCallbacks(node: Node, varDeclFlagsMap: Map): [Node[], Map, ReturnStatement | undefined] { + export function getReturnStatementsWithPromiseCallbacks(node: Node): Node[] { const retStmts: Node[] = []; - let hasFollowingRetStmt: ReturnStatement | undefined; forEachChild(node, visit); function visit(child: Node) { @@ -154,73 +152,18 @@ namespace ts { } if (isReturnStatement(child)) { - forEachChild(child, hasCallback); + forEachChild(child, hasCallback); } function hasCallback(returnChild: Node) { - //const symbol = checker.getSymbolAtLocation(returnChild); - if (isCallback(returnChild)) { retStmts.push(child as ReturnStatement); } - else if (isIdentifier(returnChild) && isReturnStatement(child) - && child.expression && isIdentifier(child.expression)) { - hasFollowingRetStmt = child; - retStmts.push(child); - forEachChild(node, findCallbackUses); - } - else if (!isFunctionLike(returnChild)) { - forEachChild(returnChild, hasCallback); - } - - let parent: Node; - - function findCallbackUses(identUse: Node) { - if (isVariableDeclarationList(identUse)) { - - let isCallback = false; - for (const varDecl of identUse.declarations) { - /* - const maybeSymbol = checker.getSymbolAtLocation(varDecl.name); - const varDeclSymbol = !maybeSymbol && varDecl.original ? checker.getSymbolAtLocation((varDecl.original)!.name) : maybeSymbol;*/ - if (varDecl.initializer && isCallExpression(varDecl.initializer) /*&& - symbol === varDeclSymbol*/) { - isCallback = true; - const flags = identUse.original ? identUse.original.flags : identUse.flags; - varDeclFlagsMap.set((varDecl.name).text, flags); - } - } - - if (isCallback) { - retStmts.push(parent); - } - } - else if (isCallback(identUse)) { - /* - const expr = ((identUse).expression).expression; - const maybeSymbol = checker.getSymbolAtLocation(expr); - const varDeclSymbol = !maybeSymbol && expr.original ? checker.getSymbolAtLocation(expr.original) : maybeSymbol; - if (symbol === varDeclSymbol) {*/ - retStmts.push(parent as CallExpression); - //} - } - else if (isAssignmentExpression(identUse)) { - retStmts.push(parent); - /* - const maybeSymbol = checker.getSymbolAtLocation(identUse.left); - const varDeclSymbol = !maybeSymbol && identUse.left.original ? checker.getSymbolAtLocation(identUse.left.original) : maybeSymbol;*/ - varDeclFlagsMap.set((identUse.left).text, undefined); - } - else { - parent = identUse; - forEachChild(identUse, findCallbackUses); - } - } } forEachChild(child, visit); } - return [retStmts, varDeclFlagsMap, hasFollowingRetStmt]; + return retStmts; } function isCallback(node: Node): boolean { From c71c5a5d6e0186de43da8da8af90aec599d871d1 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 24 Jul 2018 13:40:16 -0700 Subject: [PATCH 131/196] Removed uneccesary argument to getCallbackBody --- src/services/codefixes/convertToAsyncFunction.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index dcd8f0bcaae71..b5b6fcbb906be 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -220,7 +220,7 @@ namespace ts.codefix { const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes).concat(callbackBody)); //TODO : create a variable declaration outside of the try block IF the prevArgName is referenced outside of the try block - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, /*isRej*/ true); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap); const catchClause = createCatchClause(argNameRej[0].text, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; @@ -261,7 +261,7 @@ namespace ts.codefix { } function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, - lastDotThenMap: Map, isRej = false): NodeArray { + lastDotThenMap: Map): NodeArray { const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; const hasArgName = argName && argName[0].text.length > 0; @@ -273,7 +273,7 @@ namespace ts.codefix { } let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName[0]]); - if (!nextDotThen || isRej) { + if (!nextDotThen) { return createNodeArray([createReturn(synthCall)]); } From c374480b553b6080778b7452a756cf243768ea7a Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 24 Jul 2018 13:50:19 -0700 Subject: [PATCH 132/196] More code cleanup --- src/services/codefixes/convertToAsyncFunction.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index b5b6fcbb906be..0545c41fd2da7 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -187,7 +187,7 @@ namespace ts.codefix { function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { const func = getSynthesizedDeepClone(node.arguments[0]); - const argName = getArgName(func, checker, synthNamesMap); + const argName = getArgName(func, synthNamesMap); let varDecl; if (prevArgName && lastDotThenMap.get(String(getNodeId(node)))) { @@ -210,12 +210,12 @@ namespace ts.codefix { return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName); } - const argNameRes = getArgName(res, checker, synthNamesMap); + const argNameRes = getArgName(res, synthNamesMap); const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap); if (rej) { - const argNameRej = getArgName(rej, checker, synthNamesMap); + const argNameRej = getArgName(rej, synthNamesMap); const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes).concat(callbackBody)); @@ -373,7 +373,6 @@ namespace ts.codefix { } function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { - // can probably get rid of this if statement if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { return false; } @@ -386,17 +385,12 @@ namespace ts.codefix { return (node.expression).name.text === funcName && checker.isPromiseLikeType(nodeType); } - function getArgName(funcNode: Node, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>): [Identifier, number] { + function getArgName(funcNode: Node, synthNamesMap: Map<[Identifier, number]>): [Identifier, number] { let name: [Identifier, number] | undefined; - const funcNodeType = checker.getTypeAtLocation(funcNode); if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { name = [funcNode.parameters[0].name as Identifier, 1]; } - else if (funcNodeType && funcNodeType.getCallSignatures().length > 0 && funcNodeType.getCallSignatures()[0].parameters.length > 0) { - name = [createIdentifier(funcNodeType.getCallSignatures()[0].parameters[0].name), 1]; - // TODO : maybe get rid of this - } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { name = [funcNode.arguments[0] as Identifier, 1]; } From 5065ce9f81fa9d4f015b966363ade8a067742f71 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 24 Jul 2018 13:52:55 -0700 Subject: [PATCH 133/196] Fixed lint errors --- src/services/codefixes/convertToAsyncFunction.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 0545c41fd2da7..05165aaf22884 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -191,7 +191,7 @@ namespace ts.codefix { let varDecl; if (prevArgName && lastDotThenMap.get(String(getNodeId(node)))) { - varDecl = createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let)); + varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let)); } const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, prevArgName)); @@ -219,7 +219,7 @@ namespace ts.codefix { const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes).concat(callbackBody)); - //TODO : create a variable declaration outside of the try block IF the prevArgName is referenced outside of the try block + // TODO : create a variable declaration outside of the try block IF the prevArgName is referenced outside of the try block const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap); const catchClause = createCatchClause(argNameRej[0].text, createBlock(callbackBody2)); @@ -250,8 +250,8 @@ namespace ts.codefix { return [createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), createAwait(node)))]; } - const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), undefined, createAwait(node)); - return [createVariableStatement(undefined, (createVariableDeclarationList([varDecl], /*nodeFlags*/ NodeFlags.Let)))]; + const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, createAwait(node)); + return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], /*nodeFlags*/ NodeFlags.Let)))]; } else if (!hasPrevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { return [createStatement(createAwait(node))]; From d9597f0a7d390580b38563d733ee0b19ebe3c71a Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 24 Jul 2018 13:53:19 -0700 Subject: [PATCH 134/196] Adjusted unit tests to match new spec that does not provide refactor for promises without direct callbacks --- .../unittests/convertToAsyncFunction.ts | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 7c37f10e2407f..a89201e1d245c 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -395,25 +395,23 @@ function [#|finallyTest|](): Promise { ); _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromise", ` function [#|innerPromise|](): Promise { - var blob = fetch("https://typescriptlang.org").then(resp => { + return fetch("https://typescriptlang.org").then(resp => { var blob2 = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); - retrun blob2; + return blob2; }).then(blob => { return blob.toString(); }); - - return blob; } ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_VarReturn01", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn01", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); return blob; } ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_VarReturn02", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn02", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); blob.then(resp => console.log(resp)); @@ -421,7 +419,7 @@ function [#|f|]() { } ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn03", ` +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn03", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org") let blob2 = blob.then(resp => console.log(resp)); @@ -434,7 +432,7 @@ function err (rej) { } ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn04", ` +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn04", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org").then(res => console.log(res)), blob2 = fetch("https://microsoft.com").then(res => res.ok).catch(err); return blob; @@ -445,7 +443,7 @@ function err (rej) { ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn05", ` +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn05", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org").then(res => console.log(res)); blob.then(x => x); @@ -462,7 +460,7 @@ function [#|f|]() { ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn07", ` +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn07", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); let blob2 = fetch("https://microsoft.com"); @@ -473,7 +471,7 @@ function [#|f|]() { ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn08", ` +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn08", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); if (!blob.ok){ @@ -485,7 +483,7 @@ function [#|f|]() { ` ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn09", ` +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn09", ` function [#|f|]() { let blob3; let blob = fetch("https://typescriptlang.org"); @@ -499,7 +497,7 @@ function [#|f|]() { ); -_testConvertToAsyncFunction("convertToAsyncFunction_VarReturn10", ` +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn10", ` function [#|f|]() { let blob3; let blob = fetch("https://typescriptlang.org"); @@ -532,10 +530,26 @@ function my_print (resp) { console.log(resp.buffer); } } + + +` +); + + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns", ` +function [#|f|](): Promise { + let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); + if (x.ok) { + + return fetch("https://typescriptlang.org").then(res => console.log(res)); + } + return x.then(resp => { + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + }); +} ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_SeperateLines", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_SeperateLines", ` function [#|f|](): Promise { var blob = fetch("https://typescriptlang.org") blob.then(resp => { @@ -553,13 +567,11 @@ function [#|f|](): Promise { _testConvertToAsyncFunction("convertToAsyncFunction_InnerVarNameConflict", ` function [#|f|](): Promise { - var blob = fetch("https://typescriptlang.org").then(resp => { + return fetch("https://typescriptlang.org").then(resp => { var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); }).then(blob => { return blob.toString(); }); - - return blob; } ` ); @@ -693,7 +705,7 @@ function [#|f|]() { ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_NestedFunction", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NestedFunction", ` function [#|f|]() { function fn2(){ function fn3(){ From 366b812bb8f4739ebb9892579f200ecdfc2974e3 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 24 Jul 2018 17:05:30 -0700 Subject: [PATCH 135/196] Fixed dummy identifier bug --- src/services/codefixes/convertToAsyncFunction.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 05165aaf22884..f942f924c6ce2 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -26,16 +26,15 @@ namespace ts.codefix { let retStmts = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed); let allNewNodes: Node[] = []; - let retStmtNameArg: [Identifier, number] = [createIdentifier("empty"), 1]; for (const stmt of retStmts) { if (isCallExpression(stmt)) { - const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, retStmtNameArg); + const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap); allNewNodes = allNewNodes.concat(newNodes); } else { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, retStmtNameArg); + const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap); allNewNodes = allNewNodes.concat(newNodes); } else if (!isFunctionLike(node)) { @@ -260,8 +259,7 @@ namespace ts.codefix { return [createReturn(node)]; } - function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, - lastDotThenMap: Map): NodeArray { + function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map): NodeArray { const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; const hasArgName = argName && argName[0].text.length > 0; @@ -324,11 +322,8 @@ namespace ts.codefix { return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, func.body as Expression)], NodeFlags.Let)))]); } - else if (hasPrevArgName) { - return createNodeArray([createReturn(func.body as Expression)]); - } else { - return createNodeArray([createStatement(func.body as Expression)]); + return createNodeArray([createReturn(func.body as Expression)]); } } break; From 15249e11bb801c3738d5f867658523af678b9418 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 24 Jul 2018 18:13:17 -0700 Subject: [PATCH 136/196] Fixed the way we replace nodes --- .../codefixes/convertToAsyncFunction.ts | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index f942f924c6ce2..b7b0360cf9250 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -25,17 +25,17 @@ namespace ts.codefix { findLastDotThens(funcToConvertRenamed, lastDotThenMap, checker); let retStmts = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed); - let allNewNodes: Node[] = []; + let allNewNodes: Map = new MapCtr(); for (const stmt of retStmts) { if (isCallExpression(stmt)) { const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap); - allNewNodes = allNewNodes.concat(newNodes); + allNewNodes = allNewNodes.set(String(getNodeId(stmt)), newNodes); } else { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap); - allNewNodes = allNewNodes.concat(newNodes); + allNewNodes = allNewNodes.set(String(getNodeId(stmt)), newNodes); } else if (!isFunctionLike(node)) { forEachChild(node, visit); @@ -47,21 +47,15 @@ namespace ts.codefix { replaceNodes(changes, sourceFile, retStmts, allNewNodes); } - function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes: Node[], newNodes: Node[]) { - - let i = 0; - while (i < oldNodes.length && i < newNodes.length) { - if (i === oldNodes.length - 1 && i < newNodes.length - 1) { - changes.replaceNodeWithNodes(sourceFile, oldNodes[i], newNodes.slice(i)); - } - else if (i < oldNodes.length - 1 && i === newNodes.length - 1) { - changes.replaceNodeRange(sourceFile, oldNodes[i], oldNodes[oldNodes.length - 1], newNodes[i]); - } - else { - changes.replaceNode(sourceFile, oldNodes[i], newNodes[i]); + function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes_: Node[], allNewNodes: Map) { + let oldNodes = oldNodes_.slice(); + allNewNodes.forEach((value: Node[], key: string) => { + for (let stmt of oldNodes) { + if (String(getNodeId(stmt)) === key) { + changes.replaceNodeWithNodes(sourceFile, stmt, value); + } } - i++; - } + }); } function findLastDotThens(func: FunctionLikeDeclaration, lastDotThen: Map, checker: TypeChecker) { From cb9874367a234512e2f19c5c0a40e5909423c9f5 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 24 Jul 2018 18:14:41 -0700 Subject: [PATCH 137/196] Added tests and baselines --- .../unittests/convertToAsyncFunction.ts | 14 +++++++-- .../convertToAsyncFunction_InnerPromise.ts | 19 ++++++++++++ .../convertToAsyncFunction_InnerPromiseRet.ts | 24 +++++++++++++++ .../convertToAsyncFunction_SeperateLines.ts | 30 +++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_SeperateLines.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index a89201e1d245c..80d5633b3bfc3 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -404,6 +404,17 @@ function [#|innerPromise|](): Promise { } ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRet", ` +function [#|innerPromise|](): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + }).then(blob => { + return blob.toString(); + }); +} +` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn01", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); @@ -536,10 +547,9 @@ function my_print (resp) { ); _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns", ` -function [#|f|](): Promise { +function [#|f|](): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); if (x.ok) { - return fetch("https://typescriptlang.org").then(res => console.log(res)); } return x.then(resp => { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts new file mode 100644 index 0000000000000..ee508be4da31e --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts @@ -0,0 +1,19 @@ +// ==ORIGINAL== + +function /*[#|*/innerPromise/*|]*/(): Promise { + return fetch("https://typescriptlang.org").then(resp => { + var blob2 = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + return blob2; + }).then(blob => { + return blob.toString(); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function innerPromise(): Promise { + let resp = await fetch("https://typescriptlang.org"); + var blob2 = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + let blob_1 = blob2; + return blob_1.toString(); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts new file mode 100644 index 0000000000000..145f1b35f6adf --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts @@ -0,0 +1,24 @@ +// ==ORIGINAL== + +function /*[#|*/innerPromise/*|]*/(): Promise { + return fetch("https://typescriptlang.org").then(resp => { + return resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + }).then(blob => { + return blob.toString(); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function innerPromise(): Promise { + let resp = await fetch("https://typescriptlang.org"); + let blob_1; + try { + let blob = await resp.blob(); + let blob_1 = blob.byteOffset; + } + catch (err) { + let blob_1 = 'Error'; + } + return blob_1.toString(); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_SeperateLines.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_SeperateLines.ts new file mode 100644 index 0000000000000..9cabcf8887853 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_SeperateLines.ts @@ -0,0 +1,30 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise { + var blob = fetch("https://typescriptlang.org") + blob.then(resp => { + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + }); + blob.then(blob => { + return blob.toString(); + }); + + return blob; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise { + let resp = await fetch("https://typescriptlang.org"); + let blob_3; + try { + let blob_2 = await resp.blob(); + blob_3 = blob_2.byteOffset; + } + catch (err) { + blob_3 = 'Error'; + } + let blob = blob_3.toString(); + + return blob; +} From 02c37d9dffebf18f810cce40722f4816b3423fc8 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 25 Jul 2018 10:15:26 -0700 Subject: [PATCH 138/196] Fixed PR comments --- src/services/suggestionDiagnostics.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 97247dea6522b..e8891133dadb7 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -69,7 +69,7 @@ namespace ts { } } - if (isFunctionLikeDeclaration(node) || isArrowFunction(node) || isMethodDeclaration(node)) { + if (isFunctionLikeDeclaration(node)) { addConvertToAsyncFunctionDiagnostics(node, checker, diags); } node.forEachChild(check); @@ -113,15 +113,11 @@ namespace ts { } } - function addConvertToAsyncFunctionDiagnostics(node: Node, checker: TypeChecker, diags: DiagnosticWithLocation[]): void { - if (isAsyncFunction(node)) { + function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]): void { + if (isAsyncFunction(node) || !node.body) { return; } - if (isFunctionLikeDeclaration(node) && !node.body) { - return; // break on ambient functions - } - const returnType = checker.getTypeAtLocation(node); if (!returnType) { return; @@ -142,7 +138,7 @@ namespace ts { } export function getReturnStatementsWithPromiseCallbacks(node: Node): Node[] { - const retStmts: Node[] = []; + const returnStatements: Node[] = []; forEachChild(node, visit); function visit(child: Node) { @@ -157,13 +153,13 @@ namespace ts { function hasCallback(returnChild: Node) { if (isCallback(returnChild)) { - retStmts.push(child as ReturnStatement); + returnStatements.push(child as ReturnStatement); } } forEachChild(child, visit); } - return retStmts; + return returnStatements; } function isCallback(node: Node): boolean { From 4f26e8c868b0f97cc4268438ced232bc2643a62e Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 25 Jul 2018 10:16:40 -0700 Subject: [PATCH 139/196] Removed duplicate message --- src/compiler/diagnosticMessages.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d1679e1a6572e..c8ad8bb9eb2cf 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4098,11 +4098,6 @@ "category": "Suggestion", "code": 80005 }, - "This may be converted to an async function.": { - "category": "Suggestion", - "code": 80006 - }, - "This may be converted to an async function.": { "category": "Suggestion", "code": 80006 From 8ba209748cdb2bbec126d8eedbbdb2f2790596d6 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 25 Jul 2018 13:25:17 -0700 Subject: [PATCH 140/196] Fixed tests to include promise libraries --- .../unittests/convertToAsyncFunction.ts | 289 ++++++++++++++++-- 1 file changed, 263 insertions(+), 26 deletions(-) diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 80d5633b3bfc3..a21760f2e1ad2 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -63,6 +63,241 @@ namespace ts { return { source: text, ranges }; } + const libFile: ts.TestFSWithWatch.File = { + path: "/a/lib/lib.d.ts", + content: `/// +interface Boolean {} +interface Function {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +declare function fetch(input?, init?): Promise; +declare type PromiseConstructorLike = new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => void) => PromiseLike; +interface PromiseLike { + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): PromiseLike; +} +interface Promise { + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise; + + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise; +} +interface PromiseConstructor { + /** + * A reference to the prototype. + */ + readonly prototype: Promise; + + /** + * Creates a new Promise. + * @param executor A callback used to initialize the promise. This callback is passed two arguments: + * a resolve callback used resolve the promise with a value or the result of another promise, + * and a reject callback used to reject the promise with a provided reason or error. + */ + new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => void): Promise; + + /** + * Creates a Promise that is resolved with an array of results when all of the provided Promises + * resolve, or rejected when any Promise is rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + all(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike, T10 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>; + + /** + * Creates a Promise that is resolved with an array of results when all of the provided Promises + * resolve, or rejected when any Promise is rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + all(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>; + + /** + * Creates a Promise that is resolved with an array of results when all of the provided Promises + * resolve, or rejected when any Promise is rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + all(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>; + + /** + * Creates a Promise that is resolved with an array of results when all of the provided Promises + * resolve, or rejected when any Promise is rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + all(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6, T7]>; + + /** + * Creates a Promise that is resolved with an array of results when all of the provided Promises + * resolve, or rejected when any Promise is rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + all(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike, T6 | PromiseLike]): Promise<[T1, T2, T3, T4, T5, T6]>; + + /** + * Creates a Promise that is resolved with an array of results when all of the provided Promises + * resolve, or rejected when any Promise is rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + all(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike]): Promise<[T1, T2, T3, T4, T5]>; + + /** + * Creates a Promise that is resolved with an array of results when all of the provided Promises + * resolve, or rejected when any Promise is rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + all(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike ]): Promise<[T1, T2, T3, T4]>; + + /** + * Creates a Promise that is resolved with an array of results when all of the provided Promises + * resolve, or rejected when any Promise is rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + all(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike]): Promise<[T1, T2, T3]>; + + /** + * Creates a Promise that is resolved with an array of results when all of the provided Promises + * resolve, or rejected when any Promise is rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + all(values: [T1 | PromiseLike, T2 | PromiseLike]): Promise<[T1, T2]>; + + /** + * Creates a Promise that is resolved with an array of results when all of the provided Promises + * resolve, or rejected when any Promise is rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + all(values: (T | PromiseLike)[]): Promise; + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved + * or rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + race(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike, T10 | PromiseLike]): Promise; + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved + * or rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + race(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike]): Promise; + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved + * or rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + race(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike]): Promise; + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved + * or rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + race(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike]): Promise; + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved + * or rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + race(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike, T6 | PromiseLike]): Promise; + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved + * or rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + race(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike, T5 | PromiseLike]): Promise; + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved + * or rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + race(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike]): Promise; + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved + * or rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + race(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike]): Promise; + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved + * or rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + race(values: [T1 | PromiseLike, T2 | PromiseLike]): Promise; + + /** + * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved + * or rejected. + * @param values An array of Promises. + * @returns A new Promise. + */ + race(values: (T | PromiseLike)[]): Promise; + + /** + * Creates a new rejected promise for the provided reason. + * @param reason The reason the promise was rejected. + * @returns A new rejected Promise. + */ + reject(reason?: any): Promise; + + /** + * Creates a new resolved promise for the provided value. + * @param value A promise. + * @returns A promise whose internal state matches the provided promise. + */ + resolve(value: T | PromiseLike): Promise; + + /** + * Creates a new resolved promise . + * @returns A resolved promise. + */ + resolve(): Promise; +} + +declare var Promise: PromiseConstructor; +interface RegExp {} +interface String { charAt: any; } +interface Array {}` + }; const newLineCharacter = "\n"; const formatOptions: FormatCodeSettings = { @@ -122,7 +357,7 @@ namespace ts { }; const sourceFile = program.getSourceFile(path)!; - const host = projectSystem.createServerHost([f, projectSystem.libFile]); + const host = projectSystem.createServerHost([f, libFile]); const projectService = projectSystem.createProjectService(host); projectService.openClientFile(f.path); const languageService = projectService.inferredProjects[0].getLanguageService(); @@ -163,7 +398,8 @@ namespace ts { } function makeProgram(f: { path: string, content: string }, includeLib?: boolean) { - const host = projectSystem.createServerHost(includeLib ? [f, projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required + + const host = projectSystem.createServerHost(includeLib ? [f, libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required const projectService = projectSystem.createProjectService(host); projectService.openClientFile(f.path); const program = projectService.inferredProjects[0].getLanguageService().getProgram(); @@ -187,7 +423,7 @@ namespace ts { path: "/a.ts", content: t.source }; - const host = projectSystem.createServerHost([f, projectSystem.libFile]); + const host = projectSystem.createServerHost([f, libFile]); const projectService = projectSystem.createProjectService(host); projectService.openClientFile(f.path); const languageService = projectService.inferredProjects[0].getLanguageService(); @@ -430,7 +666,7 @@ function [#|f|]() { } ` ); -_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn03", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn03", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org") let blob2 = blob.then(resp => console.log(resp)); @@ -442,8 +678,8 @@ function err (rej) { console.log(rej) } ` - ); -_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn04", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn04", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org").then(res => console.log(res)), blob2 = fetch("https://microsoft.com").then(res => res.ok).catch(err); return blob; @@ -452,26 +688,26 @@ function err (rej) { console.log(rej) } ` - ); + ); -_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn05", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn05", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org").then(res => console.log(res)); blob.then(x => x); return blob; } ` - ); + ); -_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn06", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn06", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org"); return blob; } ` -); + ); -_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn07", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn07", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); let blob2 = fetch("https://microsoft.com"); @@ -480,9 +716,9 @@ function [#|f|]() { return blob; } ` -); + ); -_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn08", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn08", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); if (!blob.ok){ @@ -492,9 +728,9 @@ function [#|f|]() { return blob; } ` -); + ); -_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn09", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn09", ` function [#|f|]() { let blob3; let blob = fetch("https://typescriptlang.org"); @@ -505,10 +741,10 @@ function [#|f|]() { return blob; } ` -); + ); -_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn10", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn10", ` function [#|f|]() { let blob3; let blob = fetch("https://typescriptlang.org"); @@ -520,31 +756,32 @@ function [#|f|]() { return blob; } ` -); + ); -_testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn11", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn11", ` function [#|f|]() { let blob; return blob; } ` -); + ); -_testConvertToAsyncFunction("convertToAsyncFunction_Param", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Param", ` function [#|f|]() { - my_print(fetch("https://typescriptlang.org").then(res => console.log(res))) + return my_print(fetch("https://typescriptlang.org").then(res => console.log(res))) } function my_print (resp) { if (resp.ok) { console.log(resp.buffer); } + return resp; } ` -); + ); _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns", ` function [#|f|](): Promise { @@ -742,8 +979,8 @@ function [#|f|]() { }); - function _testConvertToAsyncFunction(caption: string, text: string, includeLib?: boolean) { - testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.Convert_to_async_function, includeLib); + function _testConvertToAsyncFunction(caption: string, text: string) { + testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.Convert_to_async_function, true); } function _testConvertToAsyncFunctionFailed(caption: string, text: string) { From 906b2ff4cd181d3a5b2d37848078cf472abea9fe Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 25 Jul 2018 13:26:08 -0700 Subject: [PATCH 141/196] Refactored to use exposed getPromisedTypeOfPromise --- src/compiler/checker.ts | 8 +------ src/compiler/types.ts | 2 +- .../codefixes/convertToAsyncFunction.ts | 4 ++-- src/services/suggestionDiagnostics.ts | 24 ++++++++++--------- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 298c6872c4bc5..a5d4190ca6b6a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -120,6 +120,7 @@ namespace ts { return node ? getTypeFromTypeNode(node) : errorType; }, getParameterType: getTypeAtPosition, + getPromisedTypeOfPromise, getReturnTypeOfSignature, getNullableType, getNonNullableType, @@ -291,7 +292,6 @@ namespace ts { getNeverType: () => neverType, isSymbolAccessible, isArrayLikeType, - isPromiseLikeType, getAllPossiblePropertiesOfTypes, getSuggestionForNonexistentProperty: (node, type) => getSuggestionForNonexistentProperty(node, type), getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), @@ -12076,12 +12076,6 @@ namespace ts { return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type).target === globalArrayType; } - function isPromiseLikeType(type: Type): boolean { - const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ false); // this is only called from the language service, so don't report errors if the promise type doesn't exist - return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalPromiseType || - !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, globalPromiseType); - } - function isArrayLikeType(type: Type): boolean { // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, // or if it is not the undefined or null type and if it is assignable to ReadonlyArray diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c014ae953566d..3e00acb9f9cb1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2885,6 +2885,7 @@ namespace ts { getBaseTypes(type: InterfaceType): BaseType[]; getBaseTypeOfLiteralType(type: Type): Type; getWidenedType(type: Type): Type; + getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined; getReturnTypeOfSignature(signature: Signature): Type; /** * Gets the type of a parameter at a given position in a signature. @@ -3046,7 +3047,6 @@ namespace ts { /* @internal */ getTypeCount(): number; /* @internal */ isArrayLikeType(type: Type): boolean; - /* @internal */ isPromiseLikeType(type: Type): boolean; /** * For a union, will include a property if it's defined in *any* of the member types. * So for `{ a } | { b }`, this will include both `a` and `b`. diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index b7b0360cf9250..97b416a0d2beb 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -154,7 +154,7 @@ namespace ts.codefix { return false; } - return checker.isPromiseLikeType(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); + return !!checker.getPromisedTypeOfPromise(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); } function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { @@ -371,7 +371,7 @@ namespace ts.codefix { return false; } - return (node.expression).name.text === funcName && checker.isPromiseLikeType(nodeType); + return (node.expression).name.text === funcName && !!checker.getPromisedTypeOfPromise(nodeType); } function getArgName(funcNode: Node, synthNamesMap: Map<[Identifier, number]>): [Identifier, number] { diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index e8891133dadb7..70d5dda742477 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -114,22 +114,24 @@ namespace ts { } function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]): void { - if (isAsyncFunction(node) || !node.body) { + + const functionType = checker.getTypeAtLocation(node); + if (isAsyncFunction(node) || !node.body || !functionType) { return; } - const returnType = checker.getTypeAtLocation(node); - if (!returnType) { + const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call); + const returnType = callSignatures.length ? checker.getReturnTypeOfSignature(callSignatures[0]) : undefined; + + if (!returnType || !checker.getPromisedTypeOfPromise(returnType)) { return; } - if (checker.isPromiseLikeType(returnType)) { - // collect all the return statements - // check that a property access expression exists in there and that it is a handler - const retStmts = getReturnStatementsWithPromiseCallbacks(node); - if (retStmts.length > 0) { - diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function)); - } + // collect all the return statements + // check that a property access expression exists in there and that it is a handler + const returnStatements = getReturnStatementsWithPromiseCallbacks(node); + if (returnStatements.length > 0) { + diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function)); } } @@ -148,7 +150,7 @@ namespace ts { } if (isReturnStatement(child)) { - forEachChild(child, hasCallback); + forEachChild(child, hasCallback); } function hasCallback(returnChild: Node) { From ccca0c0d1bf49c1e59ca7a75d454def97e5e78de Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 25 Jul 2018 14:00:29 -0700 Subject: [PATCH 142/196] Fixed rename bugs --- .../codefixes/convertToAsyncFunction.ts | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 97b416a0d2beb..313fb4eb73927 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -109,22 +109,14 @@ namespace ts.codefix { const symbol = checker.getSymbolAtLocation(node); const newName = getNewNameIfConflict(node, allVarNames); + // if the identifier refers to a function if (symbol && type && type.getCallSignatures().length > 0 && type.getCallSignatures()[0].parameters.length > 0) { - // first, add the actual function name - if (allVarNames.filter(elem => elem.text === node.text).length > 0) { - // we have a conflict with the function name, but function names take precedence over variable names - varNamesMap.forEach((value: string, key: string) => { - if (value === node.text) { - varNamesMap.set(key, getNewNameIfConflict(node, allVarNames).text); - return; - } - }); - } - + // add the function name varNamesMap.set(String(getSymbolId(symbol)), node.text); allVarNames.push(node); + - // next, add the new variable for the declaration + // next, add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) const synthName = createIdentifier(type.getCallSignatures()[0].parameters[0].name); varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(synthName)))), synthName.text); allVarNames.push(synthName); @@ -136,8 +128,9 @@ namespace ts.codefix { allVarNames.push(node); } } - - forEachChild(node, visit); + else { + forEachChild(node, visit); + } }); return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, varNamesMap, checker); From f0e1ff4df7df3add60a7bd7a7370d0a3a9215e78 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 25 Jul 2018 15:11:49 -0700 Subject: [PATCH 143/196] Fixed parseCallback failure cases --- .../codefixes/convertToAsyncFunction.ts | 31 ++++++++++--------- src/services/utilities.ts | 6 ++-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 313fb4eb73927..c8dd2bd601004 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -29,13 +29,17 @@ namespace ts.codefix { for (const stmt of retStmts) { if (isCallExpression(stmt)) { const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap); - allNewNodes = allNewNodes.set(String(getNodeId(stmt)), newNodes); + if (newNodes.length) { + allNewNodes = allNewNodes.set(String(getNodeId(stmt)), newNodes); + } } else { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap); - allNewNodes = allNewNodes.set(String(getNodeId(stmt)), newNodes); + if (newNodes.length) { + allNewNodes = allNewNodes.set(String(getNodeId(stmt)), newNodes); + } } else if (!isFunctionLike(node)) { forEachChild(node, visit); @@ -47,7 +51,7 @@ namespace ts.codefix { replaceNodes(changes, sourceFile, retStmts, allNewNodes); } - function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes_: Node[], allNewNodes: Map) { + function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes_: Node[], allNewNodes: Map) { let oldNodes = oldNodes_.slice(); allNewNodes.forEach((value: Node[], key: string) => { for (let stmt of oldNodes) { @@ -110,18 +114,14 @@ namespace ts.codefix { const newName = getNewNameIfConflict(node, allVarNames); // if the identifier refers to a function - if (symbol && type && type.getCallSignatures().length > 0 && type.getCallSignatures()[0].parameters.length > 0) { - // add the function name - varNamesMap.set(String(getSymbolId(symbol)), node.text); - allVarNames.push(node); - - - // next, add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) - const synthName = createIdentifier(type.getCallSignatures()[0].parameters[0].name); - varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(synthName)))), synthName.text); - allVarNames.push(synthName); - synthNamesMap.set(node.text, [synthName, allVarNames.filter(elem => elem.text === synthName.text).length]); - + if (symbol && type && type.getCallSignatures().length > 0) { + if (type.getCallSignatures()[0].parameters.length) { + // add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) + const synthName = createIdentifier(type.getCallSignatures()[0].parameters[0].name); + varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(synthName)))), synthName.text); + allVarNames.push(synthName); + synthNamesMap.set(node.text, [synthName, allVarNames.filter(elem => elem.text === synthName.text).length]); + } } else if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { varNamesMap.set(String(getSymbolId(symbol)), newName.text); @@ -178,6 +178,7 @@ namespace ts.codefix { let varDecl; if (prevArgName && lastDotThenMap.get(String(getNodeId(node)))) { varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let)); + prevArgName[1] += 2; } const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, prevArgName)); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 84506edc26c8a..4039c7d7f2b93 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1627,7 +1627,7 @@ namespace ts { * and code fixes (because those are triggered by explicit user actions). */ export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker): T { - const clone = renameMap && checker && needsRenaming(node, checker) ? + const clone = renameMap && checker && needsRenaming(node, checker, renameMap) ? node && createIdentifier(renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))!) : node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, checker); @@ -1635,8 +1635,8 @@ namespace ts { return clone as T; } - function needsRenaming(node: T | undefined, checker: TypeChecker): boolean { - return !!(node && isIdentifier(node!) && checker.getSymbolAtLocation(node!)); + function needsRenaming(node: T | undefined, checker: TypeChecker, renameMap: Map): boolean { + return !!((node && isIdentifier(node!) && checker.getSymbolAtLocation(node!)) && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))); } function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker): T { From 64b12762e2d9dcfb6375249049517857184f24a0 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 25 Jul 2018 15:12:37 -0700 Subject: [PATCH 144/196] Added tests and baselines --- .../unittests/convertToAsyncFunction.ts | 39 ++++++++++++++++++- .../convertToAsyncFunction_InnerPromiseRet.ts | 4 +- .../convertToAsyncFunction_Param2.ts | 32 +++++++++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Param2.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index a21760f2e1ad2..a2c54b3e9b42b 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -72,6 +72,26 @@ interface IArguments {} interface Number { toExponential: any; } interface Object {} declare function fetch(input?, init?): Promise; +interface Response extends Body { + readonly headers: Headers; + readonly ok: boolean; + readonly redirected: boolean; + readonly status: number; + readonly statusText: string; + readonly trailer: Promise; + readonly type: ResponseType; + readonly url: string; + clone(): Response; +} +interface Body { + readonly body: ReadableStream | null; + readonly bodyUsed: boolean; + arrayBuffer(): Promise; + blob(): Promise; + formData(): Promise; + json(): Promise; + text(): Promise; +} declare type PromiseConstructorLike = new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => void) => PromiseLike; interface PromiseLike { /** @@ -768,9 +788,9 @@ function [#|f|]() { - _testConvertToAsyncFunction("convertToAsyncFunction_Param", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Param1", ` function [#|f|]() { - return my_print(fetch("https://typescriptlang.org").then(res => console.log(res))) + return my_print(fetch("https://typescriptlang.org").then(res => console.log(res))); } function my_print (resp) { if (resp.ok) { @@ -779,10 +799,25 @@ function my_print (resp) { return resp; } +` + ); + +_testConvertToAsyncFunction("convertToAsyncFunction_Param2", ` +function [#|f|]() { + return my_print(fetch("https://typescriptlang.org").then(res => console.log(res))).catch(err => console.log("Error!", err)); +} +function my_print (resp): Promise { + if (resp.ok) { + console.log(resp.buffer); + } + return resp; +} + ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns", ` function [#|f|](): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts index 145f1b35f6adf..3de211dc1d7d5 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts @@ -15,10 +15,10 @@ async function innerPromise(): Promise { let blob_1; try { let blob = await resp.blob(); - let blob_1 = blob.byteOffset; + blob_1 = blob.byteOffset; } catch (err) { - let blob_1 = 'Error'; + blob_1 = 'Error'; } return blob_1.toString(); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Param2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Param2.ts new file mode 100644 index 0000000000000..167b6430a059c --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Param2.ts @@ -0,0 +1,32 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return my_print(fetch("https://typescriptlang.org").then(res => console.log(res))).catch(err => console.log("Error!", err)); +} +function my_print (resp): Promise { + if (resp.ok) { + console.log(resp.buffer); + } + return resp; +} + + + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + try { + return my_print(fetch("https://typescriptlang.org").then(res => console.log(res))); + } + catch (err) { + return console.log("Error!", err); + } +} +function my_print (resp): Promise { + if (resp.ok) { + console.log(resp.buffer); + } + return resp; +} + + From d3f125a1071a80a6c39479eeffe164d01a31adcf Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 26 Jul 2018 11:33:06 -0700 Subject: [PATCH 145/196] Fixed variable name bugs --- .../codefixes/convertToAsyncFunction.ts | 55 ++++++++++++++----- src/services/utilities.ts | 2 +- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index c8dd2bd601004..825d6639cf2ca 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -57,6 +57,7 @@ namespace ts.codefix { for (let stmt of oldNodes) { if (String(getNodeId(stmt)) === key) { changes.replaceNodeWithNodes(sourceFile, stmt, value); + break; } } }); @@ -67,27 +68,35 @@ namespace ts.codefix { return; } + function willBeParsed(node: Expression): boolean { + return returnsAPromise(node, checker) || (isCallExpression(node) && (isCallback(node, "then", checker) || isCallback(node, "catch", checker))); + } + forEachChild(func.body, function visit(node: Node) { if (isCallExpression(node) && isCallback(node, "then", checker)) { lastDotThen.set(String(getNodeId(node)), false); for (const arg of node.arguments) { forEachChild(arg, function visit(argChild: Expression) { - if (isCallExpression(argChild) && (returnsAPromise(argChild, checker) || isCallback(argChild, "then", checker) || isCallback(argChild, "catch", checker))) { + if (willBeParsed(argChild)) { lastDotThen.set(String(getNodeId(argChild)), false); } }); } forEachChild(node, function visit(child: Node) { - if (isCallExpression(child) && (returnsAPromise(child, checker) || isCallback(child, "then", checker) || isCallback(child, "catch", checker))) { + if (isExpression(child) && willBeParsed(child)) { if (!lastDotThen.get(String(getNodeId(child)))) { lastDotThen.set(String(getNodeId(child)), true); } + if (!isCallExpression(child)) { + return; + } + for (const arg of child.arguments) { forEachChild(arg, function visit(argChild: Expression) { - if (isCallExpression(argChild) && (returnsAPromise(argChild, checker) || isCallback(argChild, "then", checker) || isCallback(child, "catch", checker))) { + if (willBeParsed(argChild)) { lastDotThen.set(String(getNodeId(argChild)), true); } }); @@ -106,6 +115,11 @@ namespace ts.codefix { function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map<[Identifier, number]>): FunctionLikeDeclaration { const allVarNames: Identifier[] = []; + function isFunctionRef(node: Node): boolean { + const callExpr = climbPastPropertyAccess(node); + return !isCallExpression(callExpr) || callExpr.expression !== node; + } + forEachChild(nodeToRename, function visit(node: Node) { if (isIdentifier(node)) { @@ -114,7 +128,7 @@ namespace ts.codefix { const newName = getNewNameIfConflict(node, allVarNames); // if the identifier refers to a function - if (symbol && type && type.getCallSignatures().length > 0) { + if (symbol && type && type.getCallSignatures().length > 0 && isFunctionRef(node)) { if (type.getCallSignatures()[0].parameters.length) { // add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) const synthName = createIdentifier(type.getCallSignatures()[0].parameters[0].name); @@ -124,8 +138,16 @@ namespace ts.codefix { } } else if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { - varNamesMap.set(String(getSymbolId(symbol)), newName.text); - allVarNames.push(node); + for (const ident of allVarNames) { + if (ident.text === node.text && ident.symbol !== node.symbol){ + varNamesMap.set(String(getSymbolId(symbol)), newName.text); + } + } + + if (node.parent && isParameter(node.parent)) { + allVarNames.push(node) + synthNamesMap.set(node.text, [node, allVarNames.filter(elem => elem.text === node.text).length]); + } } } else { @@ -141,13 +163,13 @@ namespace ts.codefix { return numVarsSameName === 0 ? name : createIdentifier(name.text + "_" + numVarsSameName); } - function returnsAPromise(node: CallExpression, checker: TypeChecker): boolean { + function returnsAPromise(node: Expression, checker: TypeChecker): boolean { const nodeType = checker.getTypeAtLocation(node); if (!nodeType) { return false; } - return !!checker.getPromisedTypeOfPromise(nodeType) && !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker); + return !!checker.getPromisedTypeOfPromise(nodeType) && (!isCallExpression(node) || !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker)); } function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { @@ -155,10 +177,7 @@ namespace ts.codefix { return []; } - if (isCallExpression(node) && returnsAPromise(node, checker)) { - return parsePromiseCall(node, lastDotThenMap, prevArgName); - } - else if (isCallExpression(node) && isCallback(node, "then", checker)) { + if (isCallExpression(node) && isCallback(node, "then", checker)) { return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { @@ -167,6 +186,9 @@ namespace ts.codefix { else if (isPropertyAccessExpression(node)) { return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName); } + else if (returnsAPromise(node, checker)) { + return parsePromiseCall(node, lastDotThenMap, prevArgName); + } return []; } @@ -228,12 +250,13 @@ namespace ts.codefix { } } - function parsePromiseCall(node: CallExpression, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { + function parsePromiseCall(node: Expression, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; if (hasPrevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { if (prevArgName![1] > 1) { + prevArgName![1] -= 1; return [createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), createAwait(node)))]; } @@ -270,6 +293,7 @@ namespace ts.codefix { } if (prevArgName![1] > 1) { + prevArgName![1] -= 1; return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), createAwait(synthCall)))]); } @@ -303,6 +327,7 @@ namespace ts.codefix { if (hasPrevArgName && nextDotThen) { if (prevArgName![1] > 1) { + prevArgName![1] -= 1; return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), func.body as Expression))]); } @@ -336,6 +361,7 @@ namespace ts.codefix { return createNodeArray(ret); } + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { @@ -372,7 +398,8 @@ namespace ts.codefix { let name: [Identifier, number] | undefined; if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { - name = [funcNode.parameters[0].name as Identifier, 1]; + //name = [funcNode.parameters[0].name as Identifier, 1]; + name = synthNamesMap.get((funcNode.parameters[0].name).text); } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { name = [funcNode.arguments[0] as Identifier, 1]; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 4039c7d7f2b93..1eebad4afd298 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1636,7 +1636,7 @@ namespace ts { } function needsRenaming(node: T | undefined, checker: TypeChecker, renameMap: Map): boolean { - return !!((node && isIdentifier(node!) && checker.getSymbolAtLocation(node!)) && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))); + return !!(node && isIdentifier(node) && checker.getSymbolAtLocation(node) && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node)!)))); } function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker): T { From ebd69f28c4a7b5cecde9b143255152c3140ae5e2 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 31 Jul 2018 10:38:26 -0700 Subject: [PATCH 146/196] Fixed renames --- .../codefixes/convertToAsyncFunction.ts | 191 +++++++++++------- src/services/utilities.ts | 11 +- 2 files changed, 121 insertions(+), 81 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 825d6639cf2ca..c70fc02081941 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -4,31 +4,30 @@ namespace ts.codefix { registerCodeFix({ errorCodes, getCodeActions(context: CodeFixContext) { - const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncFunction(t, context.sourceFile, context.span.start, context.program.getTypeChecker())); + const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncFunction(t, context.sourceFile, context.span.start, context.program.getTypeChecker(), context)); return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_async_function, fixId, Diagnostics.Convert_all_to_async_functions)]; }, fixIds: [fixId], - getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file, err.start, context.program.getTypeChecker())), + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file, err.start, context.program.getTypeChecker(), context)), }); - function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { + function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker, context: CodeFixContextBase): void { // get the function declaration - returns a promise const funcToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position)) as FunctionLikeDeclaration; // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); - const varNamesMap: Map = new MapCtr(); const synthNamesMap: Map<[Identifier, number]> = new MapCtr(); // number indicates the number of times it is used after declaration const lastDotThenMap: Map = new MapCtr(); - const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, varNamesMap, synthNamesMap); + const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, synthNamesMap, context); findLastDotThens(funcToConvertRenamed, lastDotThenMap, checker); let retStmts = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed); let allNewNodes: Map = new MapCtr(); for (const stmt of retStmts) { if (isCallExpression(stmt)) { - const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap); + const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, context); if (newNodes.length) { allNewNodes = allNewNodes.set(String(getNodeId(stmt)), newNodes); } @@ -36,7 +35,7 @@ namespace ts.codefix { else { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap); + const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context); if (newNodes.length) { allNewNodes = allNewNodes.set(String(getNodeId(stmt)), newNodes); } @@ -112,14 +111,19 @@ namespace ts.codefix { }); } - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, varNamesMap: Map, synthNamesMap: Map<[Identifier, number]>): FunctionLikeDeclaration { + // varNamesMap holds all of the variables in original source code. synthNamesMap holds all of the variables created by the refactor + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, context: CodeFixContextBase): FunctionLikeDeclaration { const allVarNames: Identifier[] = []; function isFunctionRef(node: Node): boolean { - const callExpr = climbPastPropertyAccess(node); + const callExpr = climbPastPropertyAccess(node) return !isCallExpression(callExpr) || callExpr.expression !== node; } + function definedInFile(symbol: Symbol, sourceFile: SourceFile): boolean { + return symbol.valueDeclaration && symbol.valueDeclaration.getSourceFile() === sourceFile; + } + forEachChild(nodeToRename, function visit(node: Node) { if (isIdentifier(node)) { @@ -127,26 +131,33 @@ namespace ts.codefix { const symbol = checker.getSymbolAtLocation(node); const newName = getNewNameIfConflict(node, allVarNames); + if (symbol && !node.symbol) { + node.symbol = symbol; + } + // if the identifier refers to a function - if (symbol && type && type.getCallSignatures().length > 0 && isFunctionRef(node)) { + if (symbol && type && type.getCallSignatures().length > 0 && isFunctionRef(node) && definedInFile(symbol, context.sourceFile)) { if (type.getCallSignatures()[0].parameters.length) { // add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) const synthName = createIdentifier(type.getCallSignatures()[0].parameters[0].name); - varNamesMap.set(String(getSymbolId(checker.createSymbol(SymbolFlags.BlockScopedVariable, getEscapedTextOfIdentifierOrLiteral(synthName)))), synthName.text); allVarNames.push(synthName); - synthNamesMap.set(node.text, [synthName, allVarNames.filter(elem => elem.text === synthName.text).length]); + synthNamesMap.set(String(getSymbolId(symbol)), [synthName, allVarNames.filter(elem => elem.text === synthName.text).length]); } } - else if (symbol && !varNamesMap.get(String(getSymbolId(symbol)))) { + else if (symbol && definedInFile(symbol, context.sourceFile)) { + let setName = false; + for (const ident of allVarNames) { - if (ident.text === node.text && ident.symbol !== node.symbol){ - varNamesMap.set(String(getSymbolId(symbol)), newName.text); + if (ident.text === node.text && ident.symbol !== node.symbol) { + allVarNames.push(newName[0]); + synthNamesMap.set(String(getSymbolId(symbol)), newName); + setName = true; } } - if (node.parent && isParameter(node.parent)) { - allVarNames.push(node) - synthNamesMap.set(node.text, [node, allVarNames.filter(elem => elem.text === node.text).length]); + if (!setName) { + allVarNames.push(node); + synthNamesMap.set(String(getSymbolId(symbol)), [getSynthesizedDeepClone(node), allVarNames.filter(elem => elem.text === node.text).length]); } } } @@ -155,12 +166,12 @@ namespace ts.codefix { } }); - return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, varNamesMap, checker); + return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, synthNamesMap, checker); } - function getNewNameIfConflict(name: Identifier, allVarNames: Identifier[]): Identifier { + function getNewNameIfConflict(name: Identifier, allVarNames: Identifier[]): [Identifier, number] { const numVarsSameName = allVarNames.filter(elem => elem.text === name.text).length; - return numVarsSameName === 0 ? name : createIdentifier(name.text + "_" + numVarsSameName); + return numVarsSameName === 0 ? [name, 1] : [createIdentifier(name.text + "_" + numVarsSameName), numVarsSameName]; } function returnsAPromise(node: Expression, checker: TypeChecker): boolean { @@ -172,19 +183,19 @@ namespace ts.codefix { return !!checker.getPromisedTypeOfPromise(nodeType) && (!isCallExpression(node) || !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker)); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, prevArgName?: [Identifier, number]): Statement[] { if (!node) { return []; } if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName); + return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, context, prevArgName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, synthNamesMap, lastDotThenMap, prevArgName); + return parseCatch(node, checker, synthNamesMap, lastDotThenMap, context, prevArgName); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, context, prevArgName); } else if (returnsAPromise(node, checker)) { return parsePromiseCall(node, lastDotThenMap, prevArgName); @@ -193,49 +204,47 @@ namespace ts.codefix { return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { - const func = getSynthesizedDeepClone(node.arguments[0]); - const argName = getArgName(func, synthNamesMap); + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, prevArgName?: [Identifier, number]): Statement[] { + const func = node.arguments[0]; + const argName = getArgName(func, synthNamesMap, checker); let varDecl; if (prevArgName && lastDotThenMap.get(String(getNodeId(node)))) { varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let)); prevArgName[1] += 2; } - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, prevArgName)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, prevArgName)); - const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, context); const catchClause = createCatchClause(argName[0].text, createBlock(callbackBody)); const tryStatement = createTry(tryBlock, catchClause, /*finallyBlock*/ undefined); return varDecl ? [varDecl, tryStatement] : [tryStatement]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, prevArgName?: [Identifier, number]): Statement[] { const [res, rej] = node.arguments; // TODO - what if this is a binding pattern and not an Identifier if (!res) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, context, prevArgName); } - const argNameRes = getArgName(res, synthNamesMap); - - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap); + const argNameRes = getArgName(res, synthNamesMap, checker); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap, context); if (rej) { - const argNameRej = getArgName(rej, synthNamesMap); + const argNameRej = getArgName(rej, synthNamesMap, checker); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, argNameRes).concat(callbackBody)); - // TODO : create a variable declaration outside of the try block IF the prevArgName is referenced outside of the try block - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, context); const catchClause = createCatchClause(argNameRej[0].text, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, argNameRes).concat(callbackBody); + return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, argNameRes).concat(callbackBody); } return []; @@ -253,7 +262,8 @@ namespace ts.codefix { function parsePromiseCall(node: Expression, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; - if (hasPrevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { + const originalNodeParent = node.original ? node.original.parent : node.parent; + if (hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { if (prevArgName![1] > 1) { prevArgName![1] -= 1; @@ -263,14 +273,35 @@ namespace ts.codefix { const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, createAwait(node)); return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], /*nodeFlags*/ NodeFlags.Let)))]; } - else if (!hasPrevArgName && nextDotThen && isPropertyAccessExpression(node.original!.parent)) { + else if (!hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { return [createStatement(createAwait(node))]; } - return [createReturn(node)]; + return [createReturn(getSynthesizedDeepClone(node))]; } - function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map): NodeArray { + function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase): NodeArray { + + function createVariableDeclarationOrAssignment(prevArgName: [Identifier, number], rightHandSide: Expression): NodeArray { + if (prevArgName![1] > 1) { + prevArgName![1] -= 1; + return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), rightHandSide))]); + } + + prevArgName![1] -= 1; + return createNodeArray([createVariableStatement(/*modifiers*/ undefined, + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, rightHandSide)], NodeFlags.Let)))]); + } + + function subtractReferences(pos: number, argName: [Identifier, number]): void { + let refNode = argName[0].parent ? argName[0] : argName[0].original; + if (hasArgName && refNode) { + let numArgUses = FindAllReferences.getReferenceEntriesForNode(pos, refNode, context.program, [context.sourceFile], context.cancellationToken); + if (numArgUses) { + argName[1] -= numArgUses.length; + } + } + } const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; const hasArgName = argName && argName[0].text.length > 0; @@ -281,25 +312,16 @@ namespace ts.codefix { break; } - let synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName[0]]); + let synthCall = createCall(getSynthesizedDeepClone(func) as Identifier, /*typeArguments*/ undefined, [argName[0]]); if (!nextDotThen) { return createNodeArray([createReturn(synthCall)]); } - synthCall = createCall(func as Identifier, /*typeArguments*/ undefined, [argName[0]]); - if (!hasPrevArgName) { break; } - if (prevArgName![1] > 1) { - prevArgName![1] -= 1; - return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), createAwait(synthCall)))]); - } - - prevArgName![1] -= 1; - return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, (createAwait(synthCall)))], NodeFlags.Let)))]); + return createVariableDeclarationOrAssignment(prevArgName!, createAwait(synthCall)); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: @@ -307,36 +329,32 @@ namespace ts.codefix { if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { const innerRetStmts = getReturnStatementsWithPromiseCallbacks(func.body); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, prevArgName); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, prevArgName); + + subtractReferences(func.body.statements.pos, argName); + if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - return (nextDotThen) ? removeReturns(func.body.statements, prevArgName![0]) : func.body.statements; - + return nextDotThen ? removeReturns(func.body.statements, prevArgName![0]) : getSynthesizedDeepClones(func.body.statements); } else if (isArrowFunction(func)) { // if there is another outer dot then, don't actually return const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression)); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, prevArgName); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } + subtractReferences(func.body.pos, argName); if (hasPrevArgName && nextDotThen) { - if (prevArgName![1] > 1) { - prevArgName![1] -= 1; - return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), func.body as Expression))]); - } - - prevArgName![1] -= 1; - return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, func.body as Expression)], NodeFlags.Let)))]); + return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(func.body) as Expression); } else { - return createNodeArray([createReturn(func.body as Expression)]); + return createNodeArray([createReturn(getSynthesizedDeepClone(func.body) as Expression)]); } } break; @@ -354,7 +372,7 @@ namespace ts.codefix { } } else { - ret.push(stmt); + ret.push(getSynthesizedDeepClone(stmt)); } } @@ -362,12 +380,12 @@ namespace ts.codefix { } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, prevArgName?: [Identifier, number]) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, prevArgName?: [Identifier, number]) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, prevArgName); + const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; @@ -394,21 +412,42 @@ namespace ts.codefix { return (node.expression).name.text === funcName && !!checker.getPromisedTypeOfPromise(nodeType); } - function getArgName(funcNode: Node, synthNamesMap: Map<[Identifier, number]>): [Identifier, number] { + function getArgName(funcNode: Node, synthNamesMap: Map<[Identifier, number]>, checker: TypeChecker): [Identifier, number] { + + function getMapEntryIfExists(node: Identifier): [Identifier, number] { + let originalNode = getOriginalNode(node); + let symbol = getSymbol(originalNode); + + if (!symbol) { + return [node, 1]; + } + + let mapEntry = synthNamesMap.get(String(getSymbolId(symbol))); + return mapEntry ? mapEntry : [node, 1]; + } + + function getSymbol(node: Node): Symbol | undefined { + return node.symbol ? node.symbol : checker.getSymbolAtLocation(node); + } + + function getOriginalNode(node: Node): Node { + return node.original ? node.original : node; + } + let name: [Identifier, number] | undefined; if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { - //name = [funcNode.parameters[0].name as Identifier, 1]; - name = synthNamesMap.get((funcNode.parameters[0].name).text); + let param = funcNode.parameters[0].name as Identifier; + name = getMapEntryIfExists(param); } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { name = [funcNode.arguments[0] as Identifier, 1]; } else if (isIdentifier(funcNode)) { - name = synthNamesMap.get(funcNode.text); + name = getMapEntryIfExists(funcNode); } - if (name === undefined || (name[0] && name[0].text === "_")) { + if (!name || name[0] === undefined || name[0].text === "_" || name[0].text === "undefined") { return [createIdentifier(""), 1]; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 1eebad4afd298..6f5149f009ab7 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1626,20 +1626,21 @@ namespace ts { * WARNING: This is an expensive operation and is only intended to be used in refactorings * and code fixes (because those are triggered by explicit user actions). */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker): T { + export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map<[Identifier, number]>, checker?: TypeChecker): T { const clone = renameMap && checker && needsRenaming(node, checker, renameMap) ? - node && createIdentifier(renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))!) : + node && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))![0] : node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, checker); if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); return clone as T; } - function needsRenaming(node: T | undefined, checker: TypeChecker, renameMap: Map): boolean { - return !!(node && isIdentifier(node) && checker.getSymbolAtLocation(node) && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node)!)))); + function needsRenaming(node: T | undefined, checker: TypeChecker, renameMap: Map<[Identifier, number]>): boolean { + return !!(node && isIdentifier(node) && checker.getSymbolAtLocation(node) && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node)!)))) + && !checker.getTypeAtLocation(node)!.getCallSignatures().length; } - function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker): T { + function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map<[Identifier, number]>, checker?: TypeChecker): T { const visited = visitEachChild(node, function wrapper(node) { return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, checker); }, nullTransformationContext); From b39772ddb68f3ec55dac26e3e367eccf91222171 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 31 Jul 2018 11:06:33 -0700 Subject: [PATCH 147/196] fixed linting errors and code review nits --- .../codefixes/convertToAsyncFunction.ts | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index c70fc02081941..cfa98e549b8bc 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -12,32 +12,35 @@ namespace ts.codefix { }); function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker, context: CodeFixContextBase): void { // get the function declaration - returns a promise - const funcToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position)) as FunctionLikeDeclaration; + const functionToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position)) as FunctionLikeDeclaration; + if (!functionToConvert) { + return; + } // add the async keyword - changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, funcToConvert); + changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert); - const synthNamesMap: Map<[Identifier, number]> = new MapCtr(); // number indicates the number of times it is used after declaration - const lastDotThenMap: Map = new MapCtr(); + const synthNamesMap: Map<[Identifier, number]> = createMap(); // number indicates the number of times it is used after declaration + const lastDotThenMap: Map = createMap(); - const funcToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(funcToConvert, checker, synthNamesMap, context); - findLastDotThens(funcToConvertRenamed, lastDotThenMap, checker); + const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context); + findLastDotThens(functionToConvertRenamed, lastDotThenMap, checker); - let retStmts = getReturnStatementsWithPromiseCallbacks(funcToConvertRenamed); - let allNewNodes: Map = new MapCtr(); - for (const stmt of retStmts) { - if (isCallExpression(stmt)) { - const newNodes = parseCallback(stmt, checker, stmt, synthNamesMap, lastDotThenMap, context); + const returnStatements = getReturnStatementsWithPromiseCallbacks(functionToConvertRenamed); + let allNewNodes: Map = createMap(); + for (const statement of returnStatements) { + if (isCallExpression(statement)) { + const newNodes = parseCallback(statement, checker, statement, synthNamesMap, lastDotThenMap, context); if (newNodes.length) { - allNewNodes = allNewNodes.set(String(getNodeId(stmt)), newNodes); + allNewNodes = allNewNodes.set(String(getNodeId(statement)), newNodes); } } else { - forEachChild(stmt, function visit(node: Node) { + forEachChild(statement, function visit(node: Node) { if (isCallExpression(node)) { const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context); if (newNodes.length) { - allNewNodes = allNewNodes.set(String(getNodeId(stmt)), newNodes); + allNewNodes = allNewNodes.set(String(getNodeId(statement)), newNodes); } } else if (!isFunctionLike(node)) { @@ -47,15 +50,14 @@ namespace ts.codefix { } } - replaceNodes(changes, sourceFile, retStmts, allNewNodes); + replaceNodes(changes, sourceFile, returnStatements, allNewNodes); } - function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes_: Node[], allNewNodes: Map) { - let oldNodes = oldNodes_.slice(); - allNewNodes.forEach((value: Node[], key: string) => { - for (let stmt of oldNodes) { - if (String(getNodeId(stmt)) === key) { - changes.replaceNodeWithNodes(sourceFile, stmt, value); + function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes: Node[], allNewNodes: Map) { + allNewNodes.forEach((value, key) => { + for (const statement of oldNodes) { + if (String(getNodeId(statement)) === key) { + changes.replaceNodeWithNodes(sourceFile, statement, value); break; } } @@ -116,7 +118,7 @@ namespace ts.codefix { const allVarNames: Identifier[] = []; function isFunctionRef(node: Node): boolean { - const callExpr = climbPastPropertyAccess(node) + const callExpr = climbPastPropertyAccess(node); return !isCallExpression(callExpr) || callExpr.expression !== node; } @@ -225,7 +227,6 @@ namespace ts.codefix { function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, prevArgName?: [Identifier, number]): Statement[] { const [res, rej] = node.arguments; - // TODO - what if this is a binding pattern and not an Identifier if (!res) { return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, context, prevArgName); } @@ -283,20 +284,20 @@ namespace ts.codefix { function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase): NodeArray { function createVariableDeclarationOrAssignment(prevArgName: [Identifier, number], rightHandSide: Expression): NodeArray { - if (prevArgName![1] > 1) { - prevArgName![1] -= 1; - return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), rightHandSide))]); + if (prevArgName[1] > 1) { + prevArgName[1] -= 1; + return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName[0]), rightHandSide))]); } - prevArgName![1] -= 1; + prevArgName[1] -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, rightHandSide)], NodeFlags.Let)))]); + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]), /*type*/ undefined, rightHandSide)], NodeFlags.Let)))]); } function subtractReferences(pos: number, argName: [Identifier, number]): void { - let refNode = argName[0].parent ? argName[0] : argName[0].original; + const refNode = argName[0].parent ? argName[0] : argName[0].original; if (hasArgName && refNode) { - let numArgUses = FindAllReferences.getReferenceEntriesForNode(pos, refNode, context.program, [context.sourceFile], context.cancellationToken); + const numArgUses = FindAllReferences.getReferenceEntriesForNode(pos, refNode, context.program, [context.sourceFile], context.cancellationToken); if (numArgUses) { argName[1] -= numArgUses.length; } @@ -312,7 +313,7 @@ namespace ts.codefix { break; } - let synthCall = createCall(getSynthesizedDeepClone(func) as Identifier, /*typeArguments*/ undefined, [argName[0]]); + const synthCall = createCall(getSynthesizedDeepClone(func) as Identifier, /*typeArguments*/ undefined, [argName[0]]); if (!nextDotThen) { return createNodeArray([createReturn(synthCall)]); } @@ -415,14 +416,14 @@ namespace ts.codefix { function getArgName(funcNode: Node, synthNamesMap: Map<[Identifier, number]>, checker: TypeChecker): [Identifier, number] { function getMapEntryIfExists(node: Identifier): [Identifier, number] { - let originalNode = getOriginalNode(node); - let symbol = getSymbol(originalNode); + const originalNode = getOriginalNode(node); + const symbol = getSymbol(originalNode); if (!symbol) { return [node, 1]; } - let mapEntry = synthNamesMap.get(String(getSymbolId(symbol))); + const mapEntry = synthNamesMap.get(String(getSymbolId(symbol))); return mapEntry ? mapEntry : [node, 1]; } @@ -437,7 +438,7 @@ namespace ts.codefix { let name: [Identifier, number] | undefined; if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { - let param = funcNode.parameters[0].name as Identifier; + const param = funcNode.parameters[0].name as Identifier; name = getMapEntryIfExists(param); } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { From db243ad2a410eed4121ca14b5352683517e65ffa Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 31 Jul 2018 11:43:10 -0700 Subject: [PATCH 148/196] Fixed arrow function implicit return bug --- src/services/codefixes/convertToAsyncFunction.ts | 2 -- src/services/suggestionDiagnostics.ts | 8 ++++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index cfa98e549b8bc..bbc6c36c4504a 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -342,14 +342,12 @@ namespace ts.codefix { } else if (isArrowFunction(func)) { // if there is another outer dot then, don't actually return - const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression)); const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - subtractReferences(func.body.pos, argName); if (hasPrevArgName && nextDotThen) { return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(func.body) as Expression); diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 70d5dda742477..b7c30f698bf49 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -141,10 +141,14 @@ namespace ts { export function getReturnStatementsWithPromiseCallbacks(node: Node): Node[] { const returnStatements: Node[] = []; - forEachChild(node, visit); + if (isFunctionLike(node)) { + forEachChild(node, visit); + } + else { + visit(node); + } function visit(child: Node) { - if (isFunctionLike(child)) { return; } From 9e548a0fdbc37d512430a08ad066edd8dda3d4e8 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 31 Jul 2018 11:44:47 -0700 Subject: [PATCH 149/196] Cleaned up baselines and fixed test linting errors --- .../unittests/convertToAsyncFunction.ts | 6 ++-- .../convertToAsyncFunction_Conditional2.js | 4 +-- .../convertToAsyncFunction_Conditional2.ts | 4 +-- .../convertToAsyncFunction_InnerPromise.ts | 4 +-- .../convertToAsyncFunction_InnerPromiseRet.ts | 8 ++--- ...nvertToAsyncFunction_InnerPromiseSimple.ts | 4 +-- ...ertToAsyncFunction_InnerVarNameConflict.ts | 17 ++--------- .../convertToAsyncFunction_MultipleReturns.ts | 23 ++++++++++++++ .../convertToAsyncFunction_MultipleThens.ts | 4 +-- .../convertToAsyncFunction_Scope.ts | 2 +- .../convertToAsyncFunction_Scope3.js | 2 +- .../convertToAsyncFunction_Scope3.ts | 2 +- .../convertToAsyncFunction_SeperateLines.ts | 30 ------------------- .../convertToAsyncFunction_VarReturn01.js | 14 --------- .../convertToAsyncFunction_VarReturn01.ts | 14 --------- .../convertToAsyncFunction_VarReturn02.js | 15 ---------- .../convertToAsyncFunction_VarReturn02.ts | 15 ---------- .../convertToAsyncFunction_VarReturn04.js | 27 ----------------- .../convertToAsyncFunction_VarReturn04.ts | 27 ----------------- .../convertToAsyncFunction_VarReturn05.js | 16 ---------- .../convertToAsyncFunction_VarReturn05.ts | 16 ---------- .../convertToAsyncFunction_VarReturn07.js | 19 ------------ .../convertToAsyncFunction_VarReturn07.ts | 19 ------------ .../convertToAsyncFunction_VarReturn09.js | 27 ----------------- .../convertToAsyncFunction_VarReturn09.ts | 27 ----------------- .../convertToAsyncFunction_VarReturn10.js | 25 ---------------- .../convertToAsyncFunction_VarReturn10.ts | 25 ---------------- 27 files changed, 46 insertions(+), 350 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_SeperateLines.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.js delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.js delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.js delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.js delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.js delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.js delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index a2c54b3e9b42b..7bc09f2debbad 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -63,7 +63,7 @@ namespace ts { return { source: text, ranges }; } - const libFile: ts.TestFSWithWatch.File = { + const libFile: TestFSWithWatch.File = { path: "/a/lib/lib.d.ts", content: `/// interface Boolean {} @@ -522,7 +522,7 @@ function [#|f|](): Promise { }` ); _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThens", ` -function [#|f|]():Promise { +function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).then(res2); } function res(result){ @@ -1015,7 +1015,7 @@ function [#|f|]() { }); function _testConvertToAsyncFunction(caption: string, text: string) { - testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.Convert_to_async_function, true); + testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.Convert_to_async_function, /*includeLib*/ true); } function _testConvertToAsyncFunctionFailed(caption: string, text: string) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js index 606283aa14ad9..7920dc5421426 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js @@ -19,8 +19,8 @@ function res_func(result){ async function f(){ var res = 100; if (res > 50) { - let res_1 = await fetch("https://typescriptlang.org"); - return console.log(res_1); + let res_2 = await fetch("https://typescriptlang.org"); + return console.log(res_2); } else { let result = await fetch("https://typescriptlang.org"); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts index 606283aa14ad9..7920dc5421426 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts @@ -19,8 +19,8 @@ function res_func(result){ async function f(){ var res = 100; if (res > 50) { - let res_1 = await fetch("https://typescriptlang.org"); - return console.log(res_1); + let res_2 = await fetch("https://typescriptlang.org"); + return console.log(res_2); } else { let result = await fetch("https://typescriptlang.org"); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts index ee508be4da31e..e7b864d2c0b68 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts @@ -14,6 +14,6 @@ function /*[#|*/innerPromise/*|]*/(): Promise { async function innerPromise(): Promise { let resp = await fetch("https://typescriptlang.org"); var blob2 = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); - let blob_1 = blob2; - return blob_1.toString(); + let blob_2 = blob2; + return blob_2.toString(); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts index 3de211dc1d7d5..e8f8ae40d5d6d 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts @@ -12,13 +12,13 @@ function /*[#|*/innerPromise/*|]*/(): Promise { async function innerPromise(): Promise { let resp = await fetch("https://typescriptlang.org"); - let blob_1; + let blob_2; try { let blob = await resp.blob(); - blob_1 = blob.byteOffset; + blob_2 = blob.byteOffset; } catch (err) { - blob_1 = 'Error'; + blob_2 = 'Error'; } - return blob_1.toString(); + return blob_2.toString(); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts index 71b4e2a934fa5..e28b374c6e151 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts @@ -13,6 +13,6 @@ function /*[#|*/f/*|]*/(): Promise { async function f(): Promise { let resp = await fetch("https://typescriptlang.org"); let blob = await resp.blob(); - let blob_1 = blob.byteOffset; - return blob_1.toString(); + let blob_2 = blob.byteOffset; + return blob_2.toString(); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts index 1a804e9ff4b93..7487f4744395d 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts @@ -1,28 +1,17 @@ // ==ORIGINAL== function /*[#|*/f/*|]*/(): Promise { - var blob = fetch("https://typescriptlang.org").then(resp => { + return fetch("https://typescriptlang.org").then(resp => { var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); }).then(blob => { return blob.toString(); }); - - return blob; } // ==ASYNC FUNCTION::Convert to async function== async function f(): Promise { let resp = await fetch("https://typescriptlang.org"); - let blob_3; - try { - let blob_2 = await resp.blob(); - blob_3 = blob_2.byteOffset; - } - catch (err) { - blob_3 = 'Error'; - } - let blob = blob_3.toString(); - - return blob; + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + return blob.toString(); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns.ts new file mode 100644 index 0000000000000..1250ea44359d2 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns.ts @@ -0,0 +1,23 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise { + let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); + if (x.ok) { + return fetch("https://typescriptlang.org").then(res => console.log(res)); + } + return x.then(resp => { + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise { + let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); + if (x.ok) { + let res_2 = await fetch("https://typescriptlang.org"); + return console.log(res_2); + } + let resp = await x; + var blob = resp.blob().then(blob_1 => blob_1.byteOffset).catch(err => 'Error'); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts index 8a3359bf363fc..8574f05b4087b 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts @@ -1,6 +1,6 @@ // ==ORIGINAL== -function /*[#|*/f/*|]*/():Promise { +function /*[#|*/f/*|]*/():Promise { return fetch('https://typescriptlang.org').then(res).then(res2); } function res(result){ @@ -11,7 +11,7 @@ function res2(result2){ } // ==ASYNC FUNCTION::Convert to async function== -async function f():Promise { +async function f():Promise { let result = await fetch('https://typescriptlang.org'); let result2 = await res(result); return res2(result2); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts index 602e8eb8c6029..1c6a9694fd7a9 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope.ts @@ -20,7 +20,7 @@ function /*[#|*/f/*|]*/() { async function f() { var var1:Promise, var2; await fetch('https://typescriptlang.org'); - let res_2 = await Promise.resolve(); + let res = await Promise.resolve(); var2 = "test"; let res_1 = fetch("https://microsoft.com"); let response = var1 === res_1; diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js index 935b78ff7974f..3b99a72534b25 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js @@ -17,7 +17,7 @@ async function f() { var obj; let res = await fetch("https://typescriptlang.org"); obj = { - func: function f_1() { + func: function f() { console.log(res); } }; diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts index 935b78ff7974f..3b99a72534b25 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts @@ -17,7 +17,7 @@ async function f() { var obj; let res = await fetch("https://typescriptlang.org"); obj = { - func: function f_1() { + func: function f() { console.log(res); } }; diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_SeperateLines.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_SeperateLines.ts deleted file mode 100644 index 9cabcf8887853..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_SeperateLines.ts +++ /dev/null @@ -1,30 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/(): Promise { - var blob = fetch("https://typescriptlang.org") - blob.then(resp => { - var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); - }); - blob.then(blob => { - return blob.toString(); - }); - - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f(): Promise { - let resp = await fetch("https://typescriptlang.org"); - let blob_3; - try { - let blob_2 = await resp.blob(); - blob_3 = blob_2.byteOffset; - } - catch (err) { - blob_3 = 'Error'; - } - let blob = blob_3.toString(); - - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.js deleted file mode 100644 index 6ebf2b965917e..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.js +++ /dev/null @@ -1,14 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let resp = await fetch("https://typescriptlang.org"); - let blob = console.log(resp); - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.ts deleted file mode 100644 index 6ebf2b965917e..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn01.ts +++ /dev/null @@ -1,14 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let resp = await fetch("https://typescriptlang.org"); - let blob = console.log(resp); - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.js deleted file mode 100644 index d59741483a60f..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.js +++ /dev/null @@ -1,15 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob = fetch("https://typescriptlang.org"); - blob.then(resp => console.log(resp)); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let resp = await fetch("https://typescriptlang.org"); - let blob = console.log(resp); - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.ts deleted file mode 100644 index d59741483a60f..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn02.ts +++ /dev/null @@ -1,15 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob = fetch("https://typescriptlang.org"); - blob.then(resp => console.log(resp)); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let resp = await fetch("https://typescriptlang.org"); - let blob = console.log(resp); - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.js deleted file mode 100644 index f5efd9400bf44..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.js +++ /dev/null @@ -1,27 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - var blob = fetch("https://typescriptlang.org").then(res => console.log(res)), blob2 = fetch("https://microsoft.com").then(res => res.ok).catch(err); - return blob; -} -function err (rej) { - console.log(rej) -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let res = await fetch("https://typescriptlang.org"); - var blob = console.log(res); - try { - let res_1 = await fetch("https://microsoft.com"); - res_1.ok; - } - catch (rej) { - return err(rej); - } - return blob; -} -function err (rej) { - console.log(rej) -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.ts deleted file mode 100644 index f5efd9400bf44..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn04.ts +++ /dev/null @@ -1,27 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - var blob = fetch("https://typescriptlang.org").then(res => console.log(res)), blob2 = fetch("https://microsoft.com").then(res => res.ok).catch(err); - return blob; -} -function err (rej) { - console.log(rej) -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let res = await fetch("https://typescriptlang.org"); - var blob = console.log(res); - try { - let res_1 = await fetch("https://microsoft.com"); - res_1.ok; - } - catch (rej) { - return err(rej); - } - return blob; -} -function err (rej) { - console.log(rej) -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js deleted file mode 100644 index 8c1c5dc3d64d8..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.js +++ /dev/null @@ -1,16 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - var blob = fetch("https://typescriptlang.org").then(res => console.log(res)); - blob.then(x => x); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let res = await fetch("https://typescriptlang.org"); - let x = console.log(res); - var blob = x; - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.ts deleted file mode 100644 index 8c1c5dc3d64d8..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn05.ts +++ /dev/null @@ -1,16 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - var blob = fetch("https://typescriptlang.org").then(res => console.log(res)); - blob.then(x => x); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let res = await fetch("https://typescriptlang.org"); - let x = console.log(res); - var blob = x; - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.js deleted file mode 100644 index ad2bcc427b598..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.js +++ /dev/null @@ -1,19 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob = fetch("https://typescriptlang.org"); - let blob2 = fetch("https://microsoft.com"); - blob2.then(res => console.log("res:", res)); - blob.then(resp => console.log(resp)); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let resp = await fetch("https://typescriptlang.org"); - let blob = console.log(resp); - let res = await fetch("https://microsoft.com"); - console.log("res:", res); - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.ts deleted file mode 100644 index ad2bcc427b598..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn07.ts +++ /dev/null @@ -1,19 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob = fetch("https://typescriptlang.org"); - let blob2 = fetch("https://microsoft.com"); - blob2.then(res => console.log("res:", res)); - blob.then(resp => console.log(resp)); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let resp = await fetch("https://typescriptlang.org"); - let blob = console.log(resp); - let res = await fetch("https://microsoft.com"); - console.log("res:", res); - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.js deleted file mode 100644 index 5f912944b5589..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.js +++ /dev/null @@ -1,27 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob3; - let blob = fetch("https://typescriptlang.org"); - let blob2 = fetch("https://microsoft.com"); - blob2.then(res => console.log("res:", res)); - blob.then(resp => console.log(resp)); - blob3 = blob2.catch(rej => rej.ok); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let blob3; - let resp = await fetch("https://typescriptlang.org"); - let blob = console.log(resp); - try { - let res = await fetch("https://microsoft.com"); - console.log("res:", res); - } - catch (rej) { - blob3 = rej.ok; - } - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.ts deleted file mode 100644 index 5f912944b5589..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn09.ts +++ /dev/null @@ -1,27 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob3; - let blob = fetch("https://typescriptlang.org"); - let blob2 = fetch("https://microsoft.com"); - blob2.then(res => console.log("res:", res)); - blob.then(resp => console.log(resp)); - blob3 = blob2.catch(rej => rej.ok); - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let blob3; - let resp = await fetch("https://typescriptlang.org"); - let blob = console.log(resp); - try { - let res = await fetch("https://microsoft.com"); - console.log("res:", res); - } - catch (rej) { - blob3 = rej.ok; - } - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.js deleted file mode 100644 index 8221b0d7b7bdb..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.js +++ /dev/null @@ -1,25 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob3; - let blob = fetch("https://typescriptlang.org"); - let blob2 = fetch("https://microsoft.com"); - blob2.then(res => console.log("res:", res)); - blob.then(resp => console.log(resp)); - blob3 = fetch("test.com"); - blob3 = blob2; - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let blob3; - let resp = await fetch("https://typescriptlang.org"); - let blob = console.log(resp); - let res = await fetch("https://microsoft.com"); - let blob2 = console.log("res:", res); - blob3 = await fetch("test.com"); - blob3 = blob2; - return blob; -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.ts deleted file mode 100644 index 8221b0d7b7bdb..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_VarReturn10.ts +++ /dev/null @@ -1,25 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/f/*|]*/() { - let blob3; - let blob = fetch("https://typescriptlang.org"); - let blob2 = fetch("https://microsoft.com"); - blob2.then(res => console.log("res:", res)); - blob.then(resp => console.log(resp)); - blob3 = fetch("test.com"); - blob3 = blob2; - return blob; -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function f() { - let blob3; - let resp = await fetch("https://typescriptlang.org"); - let blob = console.log(resp); - let res = await fetch("https://microsoft.com"); - let blob2 = console.log("res:", res); - blob3 = await fetch("test.com"); - blob3 = blob2; - return blob; -} From fd7977b9a63619c55132f9ab7609983d74af61c1 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 1 Aug 2018 11:30:46 -0700 Subject: [PATCH 150/196] Initial commmit of consts --- .../codefixes/convertToAsyncFunction.ts | 110 +++++++++--------- 1 file changed, 57 insertions(+), 53 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index bbc6c36c4504a..a4b2fcd52f10b 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -25,12 +25,13 @@ namespace ts.codefix { const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context); findLastDotThens(functionToConvertRenamed, lastDotThenMap, checker); + const constIdentifiers = getConstIdentifiers(synthNamesMap); const returnStatements = getReturnStatementsWithPromiseCallbacks(functionToConvertRenamed); let allNewNodes: Map = createMap(); for (const statement of returnStatements) { if (isCallExpression(statement)) { - const newNodes = parseCallback(statement, checker, statement, synthNamesMap, lastDotThenMap, context); + const newNodes = parseCallback(statement, checker, statement, synthNamesMap, lastDotThenMap, context, constIdentifiers); if (newNodes.length) { allNewNodes = allNewNodes.set(String(getNodeId(statement)), newNodes); } @@ -38,7 +39,7 @@ namespace ts.codefix { else { forEachChild(statement, function visit(node: Node) { if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context); + const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers); if (newNodes.length) { allNewNodes = allNewNodes.set(String(getNodeId(statement)), newNodes); } @@ -64,6 +65,16 @@ namespace ts.codefix { }); } + function getConstIdentifiers(synthNamesMap: Map<[Identifier, number]>): Identifier[] { + const constIdentifiers: Identifier[] = []; + synthNamesMap.forEach((val) => { + if (val[1] === 1) { + constIdentifiers.push(val[0]); + } + }); + return constIdentifiers; + } + function findLastDotThens(func: FunctionLikeDeclaration, lastDotThen: Map, checker: TypeChecker) { if (!func.body) { return; @@ -129,9 +140,8 @@ namespace ts.codefix { forEachChild(nodeToRename, function visit(node: Node) { if (isIdentifier(node)) { - const type = checker.getTypeAtLocation(node); const symbol = checker.getSymbolAtLocation(node); - const newName = getNewNameIfConflict(node, allVarNames); + const type = checker.getTypeAtLocation(node); if (symbol && !node.symbol) { node.symbol = symbol; @@ -139,14 +149,15 @@ namespace ts.codefix { // if the identifier refers to a function if (symbol && type && type.getCallSignatures().length > 0 && isFunctionRef(node) && definedInFile(symbol, context.sourceFile)) { - if (type.getCallSignatures()[0].parameters.length) { + if (type.getCallSignatures()[0].parameters.length && !synthNamesMap.get(String(getSymbolId(symbol)))) { // add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) - const synthName = createIdentifier(type.getCallSignatures()[0].parameters[0].name); - allVarNames.push(synthName); - synthNamesMap.set(String(getSymbolId(symbol)), [synthName, allVarNames.filter(elem => elem.text === synthName.text).length]); + const synthName = getNewNameIfConflict(createIdentifier(type.getCallSignatures()[0].parameters[0].name), allVarNames); + allVarNames.push(synthName[0]); + synthNamesMap.set(String(getSymbolId(symbol)), synthName); } } else if (symbol && definedInFile(symbol, context.sourceFile)) { + const newName = getNewNameIfConflict(node, allVarNames); let setName = false; for (const ident of allVarNames) { @@ -158,7 +169,9 @@ namespace ts.codefix { } if (!setName) { - allVarNames.push(node); + if (node.parent && isParameter(node.parent) || isVariableDeclaration(node.parent)) { + allVarNames.push(node); + } synthNamesMap.set(String(getSymbolId(symbol)), [getSynthesizedDeepClone(node), allVarNames.filter(elem => elem.text === node.text).length]); } } @@ -185,82 +198,81 @@ namespace ts.codefix { return !!checker.getPromisedTypeOfPromise(nodeType) && (!isCallExpression(node) || !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker)); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, prevArgName?: [Identifier, number]): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, + lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { if (!node) { return []; } if (isCallExpression(node) && isCallback(node, "then", checker)) { - return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, context, prevArgName); + return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); } else if (isCallExpression(node) && isCallback(node, "catch", checker)) { - return parseCatch(node, checker, synthNamesMap, lastDotThenMap, context, prevArgName); + return parseCatch(node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, context, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); } else if (returnsAPromise(node, checker)) { - return parsePromiseCall(node, lastDotThenMap, prevArgName); + return parsePromiseCall(node, lastDotThenMap, constIdentifiers, prevArgName); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, prevArgName?: [Identifier, number]): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { const func = node.arguments[0]; const argName = getArgName(func, synthNamesMap, checker); let varDecl; if (prevArgName && lastDotThenMap.get(String(getNodeId(node)))) { - varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let)); + varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let /*getNodeFlags(prevArgName[0], constIdentifiers)*/)); prevArgName[1] += 2; } - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, prevArgName)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName)); - const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, context); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers); const catchClause = createCatchClause(argName[0].text, createBlock(callbackBody)); const tryStatement = createTry(tryBlock, catchClause, /*finallyBlock*/ undefined); return varDecl ? [varDecl, tryStatement] : [tryStatement]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, prevArgName?: [Identifier, number]): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, + lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { + const [res, rej] = node.arguments; if (!res) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, context, prevArgName); + return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); } const argNameRes = getArgName(res, synthNamesMap, checker); - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap, context); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers); if (rej) { const argNameRej = getArgName(rej, synthNamesMap, checker); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, argNameRes).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, argNameRes).concat(callbackBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, context); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers); const catchClause = createCatchClause(argNameRej[0].text, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else if (res) { - return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, argNameRes).concat(callbackBody); + return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, argNameRes).concat(callbackBody); } return []; } - function getNodeFlags(node: Identifier, varDeclFlags?: Map): NodeFlags | undefined { - if (varDeclFlags && varDeclFlags.has(node.text)) { - return varDeclFlags.get(node.text); - } - else { - return NodeFlags.Let; - } + function getNodeFlags(node: Identifier, constIdentifiers: Identifier[]): NodeFlags { + let inArr: boolean = constIdentifiers.filter(elem => elem.text === node.text).length > 0; + return inArr ? NodeFlags.Const : NodeFlags.Let; } - function parsePromiseCall(node: Expression, lastDotThenMap: Map, prevArgName?: [Identifier, number]): Statement[] { + function parsePromiseCall(node: Expression, lastDotThenMap: Map, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; const originalNodeParent = node.original ? node.original.parent : node.parent; @@ -272,7 +284,7 @@ namespace ts.codefix { } const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, createAwait(node)); - return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], /*nodeFlags*/ NodeFlags.Let)))]; + return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], getNodeFlags(prevArgName![0], constIdentifiers))))]; } else if (!hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { return [createStatement(createAwait(node))]; @@ -281,7 +293,8 @@ namespace ts.codefix { return [createReturn(getSynthesizedDeepClone(node))]; } - function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase): NodeArray { + function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, + synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[]): NodeArray { function createVariableDeclarationOrAssignment(prevArgName: [Identifier, number], rightHandSide: Expression): NodeArray { if (prevArgName[1] > 1) { @@ -291,18 +304,9 @@ namespace ts.codefix { prevArgName[1] -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]), /*type*/ undefined, rightHandSide)], NodeFlags.Let)))]); + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]), /*type*/ undefined, rightHandSide)], getNodeFlags(prevArgName[0], constIdentifiers))))]); } - function subtractReferences(pos: number, argName: [Identifier, number]): void { - const refNode = argName[0].parent ? argName[0] : argName[0].original; - if (hasArgName && refNode) { - const numArgUses = FindAllReferences.getReferenceEntriesForNode(pos, refNode, context.program, [context.sourceFile], context.cancellationToken); - if (numArgUses) { - argName[1] -= numArgUses.length; - } - } - } const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; const hasArgName = argName && argName[0].text.length > 0; @@ -330,25 +334,23 @@ namespace ts.codefix { if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { const innerRetStmts = getReturnStatementsWithPromiseCallbacks(func.body); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, prevArgName); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); - subtractReferences(func.body.statements.pos, argName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - return nextDotThen ? removeReturns(func.body.statements, prevArgName![0]) : getSynthesizedDeepClones(func.body.statements); + return nextDotThen ? removeReturns(func.body.statements, prevArgName![0], constIdentifiers) : getSynthesizedDeepClones(func.body.statements); } else if (isArrowFunction(func)) { // if there is another outer dot then, don't actually return const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression)); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, prevArgName); + const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } - subtractReferences(func.body.pos, argName); if (hasPrevArgName && nextDotThen) { return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(func.body) as Expression); } @@ -361,13 +363,13 @@ namespace ts.codefix { return createNodeArray([]); } - function removeReturns(stmts: NodeArray, prevArgName: Identifier, varDeclFlags?: Map): NodeArray { + function removeReturns(stmts: NodeArray, prevArgName: Identifier, constIdentifiers: Identifier[]): NodeArray { const ret: Statement[] = []; for (const stmt of stmts) { if (isReturnStatement(stmt)) { if (stmt.expression) { ret.push(createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], getNodeFlags(prevArgName, varDeclFlags))))); + (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], getNodeFlags(prevArgName, constIdentifiers))))); } } else { @@ -379,12 +381,14 @@ namespace ts.codefix { } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, prevArgName?: [Identifier, number]) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, + context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]) { + let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context, prevArgName); + const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; From 994c5e74842fb7bab27c59da8db46714a7163257 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 1 Aug 2018 11:31:54 -0700 Subject: [PATCH 151/196] Modified tests to allow for consts --- .../unittests/convertToAsyncFunction.ts | 21 +++++++++++-- .../convertToAsyncFunction_ArrowFunction.ts | 2 +- .../convertToAsyncFunction_Catch.ts | 2 +- .../convertToAsyncFunction_CatchAndRej.ts | 2 +- .../convertToAsyncFunction_CatchAndRejRef.ts | 2 +- ...vertToAsyncFunction_CatchFollowedByThen.js | 2 +- ...vertToAsyncFunction_CatchFollowedByThen.ts | 2 +- .../convertToAsyncFunction_CatchNoBrackets.ts | 2 +- .../convertToAsyncFunction_CatchRef.ts | 2 +- .../convertToAsyncFunction_Conditional2.js | 6 ++-- .../convertToAsyncFunction_Conditional2.ts | 6 ++-- .../convertToAsyncFunction_InnerPromise.ts | 6 ++-- .../convertToAsyncFunction_InnerPromiseRet.ts | 12 +++---- ...nvertToAsyncFunction_InnerPromiseSimple.ts | 8 ++--- ...ertToAsyncFunction_InnerVarNameConflict.ts | 6 ++-- .../convertToAsyncFunction_Loop.js | 2 +- .../convertToAsyncFunction_Loop.ts | 2 +- .../convertToAsyncFunction_Method.ts | 2 +- .../convertToAsyncFunction_MultipleCatches.ts | 2 +- ...convertToAsyncFunction_MultipleReturns1.ts | 23 ++++++++++++++ .../convertToAsyncFunction_MultipleThens.ts | 4 +-- ...oAsyncFunction_MultipleThensSameVarName.ts | 10 +++--- .../convertToAsyncFunction_NoBrackets.ts | 2 +- ...nvertToAsyncFunction_PromiseAllAndThen2.js | 2 +- ...nvertToAsyncFunction_PromiseAllAndThen2.ts | 2 +- .../convertToAsyncFunction_PromiseDotAll.ts | 2 +- .../convertToAsyncFunction_Rej.ts | 2 +- .../convertToAsyncFunction_RejNoBrackets.ts | 2 +- .../convertToAsyncFunction_RejRef.ts | 2 +- .../convertToAsyncFunction_ResRef.ts | 2 +- ...onvertToAsyncFunction_ResRefNoReturnVal.ts | 2 +- .../convertToAsyncFunction_Scope1.ts | 31 +++++++++++++++++++ .../convertToAsyncFunction_Scope2.ts | 4 +-- .../convertToAsyncFunction_Scope3.js | 2 +- .../convertToAsyncFunction_Scope3.ts | 2 +- ...nvertToAsyncFunction_TernaryConditional.js | 2 +- ...nvertToAsyncFunction_TernaryConditional.ts | 2 +- .../convertToAsyncFunction_UntypedFunction.js | 2 +- .../convertToAsyncFunction_UntypedFunction.ts | 2 +- .../convertToAsyncFunction_basic.ts | 2 +- 40 files changed, 131 insertions(+), 62 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope1.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 7bc09f2debbad..ab21b4e285ca0 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -533,7 +533,7 @@ function res2(result2){ }` ); _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThensSameVarName", ` -function [#|f|]():Promise { +function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).then(res2); } function res(result){ @@ -818,7 +818,7 @@ function my_print (resp): Promise { ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns", ` + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns1", ` function [#|f|](): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); if (x.ok) { @@ -831,6 +831,21 @@ function [#|f|](): Promise { ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns2", ` +function [#|f|](): Promise { + let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); + if (x.ok) { + return fetch("https://typescriptlang.org").then(res => console.log(res)); + } + return x.then(resp => { + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + return fetch("https://micorosft.com").then(res => console.log("Another one!")); + }); +} +` + ); + + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_SeperateLines", ` function [#|f|](): Promise { var blob = fetch("https://typescriptlang.org") @@ -888,7 +903,7 @@ function [#|f|]() { } ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_Scope", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Scope1", ` function [#|f|]() { var var1:Promise, var2; return fetch('https://typescriptlang.org').then( _ => diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunction.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunction.ts index fd58eed2d26ee..4fa6f23580e5f 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunction.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunction.ts @@ -6,6 +6,6 @@ // ==ASYNC FUNCTION::Convert to async function== async ():Promise => { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); return console.log(result); } \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Catch.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Catch.ts index a0da1875cb88f..b356e47f85acc 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Catch.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Catch.ts @@ -7,7 +7,7 @@ function /*[#|*/f/*|]*/():Promise { async function f():Promise { try { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); console.log(result); } catch (err) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRej.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRej.ts index 197b59b3bdce2..247191aa1fb0a 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRej.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRej.ts @@ -8,7 +8,7 @@ function /*[#|*/f/*|]*/():Promise { async function f():Promise { try { try { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); console.log(result); } catch (rejection) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRejRef.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRejRef.ts index 405ea99f30704..883da342a6195 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRejRef.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchAndRejRef.ts @@ -17,7 +17,7 @@ function catch_err(err){ async function f():Promise { try { try { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); return res(result); } catch (rejection) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js index efce5bbce30d4..15cfa7e4fab3a 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js @@ -21,7 +21,7 @@ async function f(){ result = await res(result); } catch (reject) { - result = await rej(reject); + const result = await rej(reject); } return res(result); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts index efce5bbce30d4..15cfa7e4fab3a 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts @@ -21,7 +21,7 @@ async function f(){ result = await res(result); } catch (reject) { - result = await rej(reject); + const result = await rej(reject); } return res(result); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchNoBrackets.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchNoBrackets.ts index f388950e920bc..54c2b22921b1e 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchNoBrackets.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchNoBrackets.ts @@ -7,7 +7,7 @@ function /*[#|*/f/*|]*/():Promise { async function f():Promise { try { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); return console.log(result); } catch (err) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchRef.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchRef.ts index 5db2fb1f75bac..709e777668d11 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchRef.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchRef.ts @@ -14,7 +14,7 @@ function catch_err(err){ async function f():Promise { try { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); return res(result); } catch (err) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js index 7920dc5421426..b7bd2c087f12e 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.js @@ -19,11 +19,11 @@ function res_func(result){ async function f(){ var res = 100; if (res > 50) { - let res_2 = await fetch("https://typescriptlang.org"); - return console.log(res_2); + const res_1 = await fetch("https://typescriptlang.org"); + return console.log(res_1); } else { - let result = await fetch("https://typescriptlang.org"); + const result = await fetch("https://typescriptlang.org"); return res_func(result); } } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts index 7920dc5421426..b7bd2c087f12e 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditional2.ts @@ -19,11 +19,11 @@ function res_func(result){ async function f(){ var res = 100; if (res > 50) { - let res_2 = await fetch("https://typescriptlang.org"); - return console.log(res_2); + const res_1 = await fetch("https://typescriptlang.org"); + return console.log(res_1); } else { - let result = await fetch("https://typescriptlang.org"); + const result = await fetch("https://typescriptlang.org"); return res_func(result); } } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts index e7b864d2c0b68..5288a84875f2b 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromise.ts @@ -12,8 +12,8 @@ function /*[#|*/innerPromise/*|]*/(): Promise { // ==ASYNC FUNCTION::Convert to async function== async function innerPromise(): Promise { - let resp = await fetch("https://typescriptlang.org"); + const resp = await fetch("https://typescriptlang.org"); var blob2 = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); - let blob_2 = blob2; - return blob_2.toString(); + const blob_1 = blob2; + return blob_1.toString(); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts index e8f8ae40d5d6d..83c17aea8d863 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts @@ -11,14 +11,14 @@ function /*[#|*/innerPromise/*|]*/(): Promise { // ==ASYNC FUNCTION::Convert to async function== async function innerPromise(): Promise { - let resp = await fetch("https://typescriptlang.org"); - let blob_2; + const resp = await fetch("https://typescriptlang.org"); + let blob_1; try { - let blob = await resp.blob(); - blob_2 = blob.byteOffset; + const blob = await resp.blob(); + blob_1 = blob.byteOffset; } catch (err) { - blob_2 = 'Error'; + blob_1 = 'Error'; } - return blob_2.toString(); + return blob_1.toString(); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts index e28b374c6e151..522e97491f6b0 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseSimple.ts @@ -11,8 +11,8 @@ function /*[#|*/f/*|]*/(): Promise { // ==ASYNC FUNCTION::Convert to async function== async function f(): Promise { - let resp = await fetch("https://typescriptlang.org"); - let blob = await resp.blob(); - let blob_2 = blob.byteOffset; - return blob_2.toString(); + const resp = await fetch("https://typescriptlang.org"); + const blob = await resp.blob(); + const blob_1 = blob.byteOffset; + return blob_1.toString(); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts index 7487f4744395d..3570a90a0b166 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerVarNameConflict.ts @@ -11,7 +11,7 @@ function /*[#|*/f/*|]*/(): Promise { // ==ASYNC FUNCTION::Convert to async function== async function f(): Promise { - let resp = await fetch("https://typescriptlang.org"); - var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); - return blob.toString(); + const resp = await fetch("https://typescriptlang.org"); + var blob = resp.blob().then(blob_1 => blob_1.byteOffset).catch(err => 'Error'); + return blob_1.toString(); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.js index 2b73333d363cf..9ca280474d555 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.js @@ -9,7 +9,7 @@ function /*[#|*/f/*|]*/(){ // ==ASYNC FUNCTION::Convert to async function== async function f(){ - let res = await fetch("https://typescriptlang.org"); + const res = await fetch("https://typescriptlang.org"); for (let i = 0; i < 10; i++) { console.log(res); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.ts index 2b73333d363cf..9ca280474d555 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Loop.ts @@ -9,7 +9,7 @@ function /*[#|*/f/*|]*/(){ // ==ASYNC FUNCTION::Convert to async function== async function f(){ - let res = await fetch("https://typescriptlang.org"); + const res = await fetch("https://typescriptlang.org"); for (let i = 0; i < 10; i++) { console.log(res); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Method.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Method.ts index 3c04c235355b1..4737ce9249329 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Method.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Method.ts @@ -9,7 +9,7 @@ class Parser { class Parser { async f():Promise { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); return console.log(result); } } \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleCatches.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleCatches.ts index 7f0bec63c3e51..654df9a1e4c6f 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleCatches.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleCatches.ts @@ -8,7 +8,7 @@ function /*[#|*/f/*|]*/(): Promise { async function f(): Promise { try { try { - let res = await fetch('https://typescriptlang.org'); + const res = await fetch('https://typescriptlang.org'); return console.log(res); } catch (err) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts new file mode 100644 index 0000000000000..074091e33596c --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts @@ -0,0 +1,23 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise { + let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); + if (x.ok) { + return fetch("https://typescriptlang.org").then(res => console.log(res)); + } + return x.then(resp => { + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise { + let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); + if (x.ok) { + const res_1 = await fetch("https://typescriptlang.org"); + return console.log(res_1); + } + const resp = await x; + var blob = resp.blob().then(blob_1 => blob_1.byteOffset).catch(err => 'Error'); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts index 8574f05b4087b..f9329346ceda2 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThens.ts @@ -12,8 +12,8 @@ function res2(result2){ // ==ASYNC FUNCTION::Convert to async function== async function f():Promise { - let result = await fetch('https://typescriptlang.org'); - let result2 = await res(result); + const result = await fetch('https://typescriptlang.org'); + const result2 = await res(result); return res2(result2); } function res(result){ diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts index 54d5b536d1b3a..bc635f855759e 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleThensSameVarName.ts @@ -1,6 +1,6 @@ // ==ORIGINAL== -function /*[#|*/f/*|]*/():Promise { +function /*[#|*/f/*|]*/():Promise { return fetch('https://typescriptlang.org').then(res).then(res2); } function res(result){ @@ -12,10 +12,10 @@ function res2(result){ // ==ASYNC FUNCTION::Convert to async function== -async function f():Promise { - let result = await fetch('https://typescriptlang.org'); - result = await res(result); - return res2(result); +async function f():Promise { + const result = await fetch('https://typescriptlang.org'); + const result_1 = await res(result); + return res2(result_1); } function res(result){ return result.ok; diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoBrackets.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoBrackets.ts index a4f9f7cd152a4..923d73085f6bb 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoBrackets.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_NoBrackets.ts @@ -7,6 +7,6 @@ function /*[#|*/f/*|]*/():Promise { // ==ASYNC FUNCTION::Convert to async function== async function f():Promise { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); return console.log(result); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.js index c9583f26ae490..3fdcdf6dd8329 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.js @@ -12,7 +12,7 @@ function /*[#|*/f/*|]*/() { async function f() { await Promise.resolve(); - let res = await Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function() { + 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_PromiseAllAndThen2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.ts index c9583f26ae490..3fdcdf6dd8329 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseAllAndThen2.ts @@ -12,7 +12,7 @@ function /*[#|*/f/*|]*/() { async function f() { await Promise.resolve(); - let res = await Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function() { + 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_PromiseDotAll.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseDotAll.ts index 41ce94971d784..9e9601eca7af8 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseDotAll.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseDotAll.ts @@ -9,6 +9,6 @@ function /*[#|*/f/*|]*/():Promise{ // ==ASYNC FUNCTION::Convert to async function== async function f():Promise{ - let vals = await Promise.all([fetch('https://typescriptlang.org'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); + const vals = await Promise.all([fetch('https://typescriptlang.org'), fetch('https://microsoft.com'), fetch('https://youtube.com')]); vals.forEach(console.log); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Rej.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Rej.ts index 304ce7eb8655f..bf3ee9c420cd6 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Rej.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Rej.ts @@ -8,7 +8,7 @@ function /*[#|*/f/*|]*/():Promise { async function f():Promise { try { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); console.log(result); } catch (rejection) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejNoBrackets.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejNoBrackets.ts index 7cc94a2beea8a..8169745f5bde9 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejNoBrackets.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejNoBrackets.ts @@ -8,7 +8,7 @@ function /*[#|*/f/*|]*/():Promise { async function f():Promise { try { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); return console.log(result); } catch (rejection) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejRef.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejRef.ts index da8b0b013afe6..77fadff7ee993 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejRef.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_RejRef.ts @@ -14,7 +14,7 @@ function rej(err){ async function f():Promise { try { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); return res(result); } catch (err) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRef.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRef.ts index 10361219188b9..74ace51e4f5b8 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRef.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRef.ts @@ -10,7 +10,7 @@ function res(result){ // ==ASYNC FUNCTION::Convert to async function== async function f():Promise { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); return res(result); } function res(result){ diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRefNoReturnVal.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRefNoReturnVal.ts index 851884ac7d7a0..e1e5a28b75528 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRefNoReturnVal.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRefNoReturnVal.ts @@ -10,7 +10,7 @@ function res(result){ // ==ASYNC FUNCTION::Convert to async function== async function f():Promise { - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); return res(result); } function res(result){ diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope1.ts new file mode 100644 index 0000000000000..c1326b0528e28 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope1.ts @@ -0,0 +1,31 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + var var1:Promise, var2; + return fetch('https://typescriptlang.org').then( _ => + Promise.resolve().then( res => { + var2 = "test"; + return fetch("https://microsoft.com"); + }).then(res => + var1 === res + ) + ).then(res); + } + function res(response){ + console.log(response); + } + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + var var1:Promise, var2; + await fetch('https://typescriptlang.org'); + const res = await Promise.resolve(); + var2 = "test"; + const res_1 = fetch("https://microsoft.com"); + const response = var1 === res_1; + return res(response); + } + function res(response){ + console.log(response); + } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope2.ts index 3bca50eec95d4..ee7cdd695db28 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope2.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope2.ts @@ -10,8 +10,8 @@ function /*[#|*/f/*|]*/(){ async function f(){ var i:number; try { - let i_1 = await fetch("https://typescriptlang.org"); - let res = i_1.ok; + const i_1 = await fetch("https://typescriptlang.org"); + const res = i_1.ok; return i + 1; } catch (err) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js index 3b99a72534b25..f9b7c3c59a4da 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.js @@ -15,7 +15,7 @@ function /*[#|*/f/*|]*/() { async function f() { var obj; - let res = await fetch("https://typescriptlang.org"); + const res = await fetch("https://typescriptlang.org"); obj = { func: function f() { console.log(res); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts index 3b99a72534b25..f9b7c3c59a4da 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Scope3.ts @@ -15,7 +15,7 @@ function /*[#|*/f/*|]*/() { async function f() { var obj; - let res = await fetch("https://typescriptlang.org"); + const res = await fetch("https://typescriptlang.org"); obj = { func: function f() { console.log(res); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.js index a8751dcb0288b..35c2029c4fad8 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.js @@ -9,6 +9,6 @@ function /*[#|*/f/*|]*/() { async function f() { let i; - let res = await Promise.resolve(); + const res = await Promise.resolve(); return res ? i = res : i = 100; } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.ts index a8751dcb0288b..35c2029c4fad8 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_TernaryConditional.ts @@ -9,6 +9,6 @@ function /*[#|*/f/*|]*/() { async function f() { let i; - let res = await Promise.resolve(); + const res = await Promise.resolve(); return res ? i = res : i = 100; } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.js index 40bd1c02eeb4b..d6f0bc06540b2 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.js @@ -7,6 +7,6 @@ function /*[#|*/f/*|]*/() { // ==ASYNC FUNCTION::Convert to async function== async function f() { - let res = await Promise.resolve(); + const res = await Promise.resolve(); return console.log(res); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.ts index 40bd1c02eeb4b..d6f0bc06540b2 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_UntypedFunction.ts @@ -7,6 +7,6 @@ function /*[#|*/f/*|]*/() { // ==ASYNC FUNCTION::Convert to async function== async function f() { - let res = await Promise.resolve(); + const res = await Promise.resolve(); return console.log(res); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basic.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basic.ts index ed5f0f16f138f..0058253541cce 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basic.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basic.ts @@ -6,6 +6,6 @@ function /*[#|*/f/*|]*/(): Promise{ // ==ASYNC FUNCTION::Convert to async function== async function f(): Promise{ - let result = await fetch('https://typescriptlang.org'); + const result = await fetch('https://typescriptlang.org'); console.log(result); } \ No newline at end of file From 5b32226dc2647e01c64b61acb9c7d5672ecd88f9 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 1 Aug 2018 13:36:24 -0700 Subject: [PATCH 152/196] Addressed code review feedback --- .../codefixes/convertToAsyncFunction.ts | 166 +++++++++--------- src/services/suggestionDiagnostics.ts | 2 +- 2 files changed, 86 insertions(+), 82 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index a4b2fcd52f10b..16f270d8ef24c 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -20,29 +20,30 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert); - const synthNamesMap: Map<[Identifier, number]> = createMap(); // number indicates the number of times it is used after declaration - const lastDotThenMap: Map = createMap(); + const synthNamesMap: Map<[Identifier, number]> = createMap(); // number indicates the number of times it is used after declaration const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context); - findLastDotThens(functionToConvertRenamed, lastDotThenMap, checker); + const lastDotThenMap = findLastDotThens(functionToConvertRenamed, checker); const constIdentifiers = getConstIdentifiers(synthNamesMap); const returnStatements = getReturnStatementsWithPromiseCallbacks(functionToConvertRenamed); - let allNewNodes: Map = createMap(); + const allNewNodes: Map = createMap(); + + function startParse(node: CallExpression, nodeToReplace: Node) { + const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers); + if (newNodes.length) { + allNewNodes.set(getNodeId(nodeToReplace).toString(), newNodes); + } + } + for (const statement of returnStatements) { if (isCallExpression(statement)) { - const newNodes = parseCallback(statement, checker, statement, synthNamesMap, lastDotThenMap, context, constIdentifiers); - if (newNodes.length) { - allNewNodes = allNewNodes.set(String(getNodeId(statement)), newNodes); - } + startParse(statement, statement); } else { forEachChild(statement, function visit(node: Node) { if (isCallExpression(node)) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers); - if (newNodes.length) { - allNewNodes = allNewNodes.set(String(getNodeId(statement)), newNodes); - } + startParse(node, statement); } else if (!isFunctionLike(node)) { forEachChild(node, visit); @@ -55,15 +56,13 @@ namespace ts.codefix { } function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes: Node[], allNewNodes: Map) { - allNewNodes.forEach((value, key) => { - for (const statement of oldNodes) { - if (String(getNodeId(statement)) === key) { - changes.replaceNodeWithNodes(sourceFile, statement, value); - break; - } + for (const statement of oldNodes) { + const newNodes = allNewNodes.get(getNodeId(statement).toString()); + if (newNodes) { + changes.replaceNodeWithNodes(sourceFile, statement, newNodes); } - }); - } + } + } function getConstIdentifiers(synthNamesMap: Map<[Identifier, number]>): Identifier[] { const constIdentifiers: Identifier[] = []; @@ -75,32 +74,36 @@ namespace ts.codefix { return constIdentifiers; } - function findLastDotThens(func: FunctionLikeDeclaration, lastDotThen: Map, checker: TypeChecker) { + function findLastDotThens(func: FunctionLikeDeclaration, checker: TypeChecker): Map { if (!func.body) { - return; + return createMap(); } function willBeParsed(node: Expression): boolean { return returnsAPromise(node, checker) || (isCallExpression(node) && (isCallback(node, "then", checker) || isCallback(node, "catch", checker))); } + // maps nodes to boolean - true indicates that there is another .then() in the callback chain + const lastDotThen: Map = createMap(); + forEachChild(func.body, function visit(node: Node) { if (isCallExpression(node) && isCallback(node, "then", checker)) { - lastDotThen.set(String(getNodeId(node)), false); + // false - there is no following .then() in the callback chain + lastDotThen.set(getNodeId(node).toString(), false); for (const arg of node.arguments) { - forEachChild(arg, function visit(argChild: Expression) { + forEachChild(arg, function visitArg(argChild: Expression) { if (willBeParsed(argChild)) { - lastDotThen.set(String(getNodeId(argChild)), false); + // false - there is no following .then() in the callback chain + lastDotThen.set(getNodeId(argChild).toString(), false); } }); } forEachChild(node, function visit(child: Node) { if (isExpression(child) && willBeParsed(child)) { - if (!lastDotThen.get(String(getNodeId(child)))) { - lastDotThen.set(String(getNodeId(child)), true); - } + // true - there is a following .then() in the callback chain + lastDotThen.set(getNodeId(child).toString(), true); if (!isCallExpression(child)) { return; @@ -109,7 +112,8 @@ namespace ts.codefix { for (const arg of child.arguments) { forEachChild(arg, function visit(argChild: Expression) { if (willBeParsed(argChild)) { - lastDotThen.set(String(getNodeId(argChild)), true); + // true - there is a following .then() in the callback chain + lastDotThen.set(getNodeId(argChild).toString(), true); } }); } @@ -122,57 +126,56 @@ namespace ts.codefix { forEachChild(node, visit); } }); + + return lastDotThen; } - // varNamesMap holds all of the variables in original source code. synthNamesMap holds all of the variables created by the refactor - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, context: CodeFixContextBase): FunctionLikeDeclaration { - const allVarNames: Identifier[] = []; + function isFunctionRef(node: Node): boolean { + const callExpr = climbPastPropertyAccess(node); + return !isCallExpression(callExpr) || callExpr.expression !== node; + } - function isFunctionRef(node: Node): boolean { - const callExpr = climbPastPropertyAccess(node); - return !isCallExpression(callExpr) || callExpr.expression !== node; - } + function definedInFile(symbol: Symbol, sourceFile: SourceFile): boolean { + return symbol.valueDeclaration && symbol.valueDeclaration.getSourceFile() === sourceFile; + } - function definedInFile(symbol: Symbol, sourceFile: SourceFile): boolean { - return symbol.valueDeclaration && symbol.valueDeclaration.getSourceFile() === sourceFile; - } + // varNamesMap holds all of the variables in original source code. synthNamesMap holds all of the variables created by the refactor + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, context: CodeFixContextBase): FunctionLikeDeclaration { + const allVarNames: [Identifier, Symbol][] = []; forEachChild(nodeToRename, function visit(node: Node) { + const symbol = checker.getSymbolAtLocation(node); + const isDefinedInFile = symbol ? definedInFile(symbol, context.sourceFile) : undefined; - if (isIdentifier(node)) { - const symbol = checker.getSymbolAtLocation(node); + if (isIdentifier(node) && symbol && isDefinedInFile) { const type = checker.getTypeAtLocation(node); - if (symbol && !node.symbol) { - node.symbol = symbol; - } - // if the identifier refers to a function - if (symbol && type && type.getCallSignatures().length > 0 && isFunctionRef(node) && definedInFile(symbol, context.sourceFile)) { - if (type.getCallSignatures()[0].parameters.length && !synthNamesMap.get(String(getSymbolId(symbol)))) { + if (type && type.getCallSignatures().length > 0 && isFunctionRef(node)) { + if (type.getCallSignatures()[0].parameters.length && !synthNamesMap.get(getSymbolId(symbol).toString())) { // add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) const synthName = getNewNameIfConflict(createIdentifier(type.getCallSignatures()[0].parameters[0].name), allVarNames); - allVarNames.push(synthName[0]); - synthNamesMap.set(String(getSymbolId(symbol)), synthName); + allVarNames.push([synthName[0], symbol]); + synthNamesMap.set(getSymbolId(symbol).toString(), synthName); } } - else if (symbol && definedInFile(symbol, context.sourceFile)) { + else { const newName = getNewNameIfConflict(node, allVarNames); let setName = false; for (const ident of allVarNames) { - if (ident.text === node.text && ident.symbol !== node.symbol) { - allVarNames.push(newName[0]); - synthNamesMap.set(String(getSymbolId(symbol)), newName); + if (ident[0].text === node.text && ident[1] !== symbol) { + allVarNames.push([newName[0], symbol]); + synthNamesMap.set(getSymbolId(symbol).toString(), newName); setName = true; } } if (!setName) { if (node.parent && isParameter(node.parent) || isVariableDeclaration(node.parent)) { - allVarNames.push(node); + allVarNames.push([node, symbol]); } - synthNamesMap.set(String(getSymbolId(symbol)), [getSynthesizedDeepClone(node), allVarNames.filter(elem => elem.text === node.text).length]); + synthNamesMap.set(getSymbolId(symbol).toString(), [getSynthesizedDeepClone(node), allVarNames.filter(elem => elem[0].text === node.text).length]); } } } @@ -184,8 +187,8 @@ namespace ts.codefix { return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, synthNamesMap, checker); } - function getNewNameIfConflict(name: Identifier, allVarNames: Identifier[]): [Identifier, number] { - const numVarsSameName = allVarNames.filter(elem => elem.text === name.text).length; + function getNewNameIfConflict(name: Identifier, allVarNames: [Identifier, Symbol][]): [Identifier, number] { + const numVarsSameName = allVarNames.filter(elem => elem[0].text === name.text).length; return numVarsSameName === 0 ? [name, 1] : [createIdentifier(name.text + "_" + numVarsSameName), numVarsSameName]; } @@ -198,8 +201,9 @@ namespace ts.codefix { return !!checker.getPromisedTypeOfPromise(nodeType) && (!isCallExpression(node) || !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker)); } - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, - lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { + // dispatch function to recursively build the refactoring + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, + lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { if (!node) { return []; } @@ -225,7 +229,7 @@ namespace ts.codefix { const argName = getArgName(func, synthNamesMap, checker); let varDecl; - if (prevArgName && lastDotThenMap.get(String(getNodeId(node)))) { + if (prevArgName && lastDotThenMap.get(getNodeId(node).toString())) { varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let /*getNodeFlags(prevArgName[0], constIdentifiers)*/)); prevArgName[1] += 2; } @@ -238,8 +242,8 @@ namespace ts.codefix { return varDecl ? [varDecl, tryStatement] : [tryStatement]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, - lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, + lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { const [res, rej] = node.arguments; @@ -260,20 +264,20 @@ namespace ts.codefix { return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } - else if (res) { + else { return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, argNameRes).concat(callbackBody); } return []; } - function getNodeFlags(node: Identifier, constIdentifiers: Identifier[]): NodeFlags { - let inArr: boolean = constIdentifiers.filter(elem => elem.text === node.text).length > 0; + function getFlagOfIdentifier(node: Identifier, constIdentifiers: Identifier[]): NodeFlags { + const inArr: boolean = constIdentifiers.filter(elem => elem.text === node.text).length > 0; return inArr ? NodeFlags.Const : NodeFlags.Let; } function parsePromiseCall(node: Expression, lastDotThenMap: Map, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { - const nextDotThen = lastDotThenMap.get(String(getNodeId(node))); + const nextDotThen = lastDotThenMap.get(getNodeId(node).toString()); const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; const originalNodeParent = node.original ? node.original.parent : node.parent; if (hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { @@ -284,7 +288,7 @@ namespace ts.codefix { } const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, createAwait(node)); - return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], getNodeFlags(prevArgName![0], constIdentifiers))))]; + return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], getFlagOfIdentifier(prevArgName![0], constIdentifiers))))]; } else if (!hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { return [createStatement(createAwait(node))]; @@ -293,8 +297,8 @@ namespace ts.codefix { return [createReturn(getSynthesizedDeepClone(node))]; } - function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, - synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[]): NodeArray { + function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, + synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[]): NodeArray { function createVariableDeclarationOrAssignment(prevArgName: [Identifier, number], rightHandSide: Expression): NodeArray { if (prevArgName[1] > 1) { @@ -304,13 +308,13 @@ namespace ts.codefix { prevArgName[1] -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]), /*type*/ undefined, rightHandSide)], getNodeFlags(prevArgName[0], constIdentifiers))))]); + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName[0], constIdentifiers))))]); } const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; const hasArgName = argName && argName[0].text.length > 0; - const nextDotThen = lastDotThenMap.get(String(getNodeId(parent))); + const nextDotThen = lastDotThenMap.get(getNodeId(parent).toString()); switch (func.kind) { case SyntaxKind.Identifier: if (!hasArgName) { @@ -331,7 +335,7 @@ namespace ts.codefix { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: - + // Arrow functions with block bodies { } will enter this control flow if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { const innerRetStmts = getReturnStatementsWithPromiseCallbacks(func.body); const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); @@ -343,19 +347,19 @@ namespace ts.codefix { return nextDotThen ? removeReturns(func.body.statements, prevArgName![0], constIdentifiers) : getSynthesizedDeepClones(func.body.statements); } - else if (isArrowFunction(func)) { - // if there is another outer dot then, don't actually return - const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(func.body as Expression)); + else { + const funcBody = (func).body; + const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(funcBody as Expression)); const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } if (hasPrevArgName && nextDotThen) { - return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(func.body) as Expression); + return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression); } else { - return createNodeArray([createReturn(getSynthesizedDeepClone(func.body) as Expression)]); + return createNodeArray([createReturn(getSynthesizedDeepClone(funcBody) as Expression)]); } } break; @@ -369,7 +373,7 @@ namespace ts.codefix { if (isReturnStatement(stmt)) { if (stmt.expression) { ret.push(createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], getNodeFlags(prevArgName, constIdentifiers))))); + (createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], getFlagOfIdentifier(prevArgName, constIdentifiers))))); } } else { @@ -381,8 +385,8 @@ namespace ts.codefix { } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, - context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, + context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { @@ -425,7 +429,7 @@ namespace ts.codefix { return [node, 1]; } - const mapEntry = synthNamesMap.get(String(getSymbolId(symbol))); + const mapEntry = synthNamesMap.get(getSymbolId(symbol).toString()); return mapEntry ? mapEntry : [node, 1]; } diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index b7c30f698bf49..19c8242d346c0 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -142,7 +142,7 @@ namespace ts { export function getReturnStatementsWithPromiseCallbacks(node: Node): Node[] { const returnStatements: Node[] = []; if (isFunctionLike(node)) { - forEachChild(node, visit); + forEachChild(node, visit); } else { visit(node); From aa41e2fde04c87cf9cda2f2677b634098c6b4567 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 1 Aug 2018 14:23:15 -0700 Subject: [PATCH 153/196] Fixed bug - now preserves innner callback statements that do not need refactoring --- .../codefixes/convertToAsyncFunction.ts | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 16f270d8ef24c..36f698b7b2a82 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -20,7 +20,7 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert); - const synthNamesMap: Map<[Identifier, number]> = createMap(); // number indicates the number of times it is used after declaration + const synthNamesMap: Map<[Identifier, number]> = createMap(); // number indicates the number of times it is used after declaration const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context); const lastDotThenMap = findLastDotThens(functionToConvertRenamed, checker); @@ -62,7 +62,7 @@ namespace ts.codefix { changes.replaceNodeWithNodes(sourceFile, statement, newNodes); } } - } + } function getConstIdentifiers(synthNamesMap: Map<[Identifier, number]>): Identifier[] { const constIdentifiers: Identifier[] = []; @@ -337,20 +337,26 @@ namespace ts.codefix { case SyntaxKind.ArrowFunction: // Arrow functions with block bodies { } will enter this control flow if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - const innerRetStmts = getReturnStatementsWithPromiseCallbacks(func.body); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); - + const indices = getReturnStatementsWithPromiseCallbacksIndices(func.body); + let refactoredStmts: Statement[] = []; - if (innerCbBody.length > 0) { - return createNodeArray(innerCbBody); + for (let i=0; i elem === i).length) { + refactoredStmts = refactoredStmts.concat(getInnerCallbackBody(checker, [statement], synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName)); + } + else { + refactoredStmts.push(statement); + } } - return nextDotThen ? removeReturns(func.body.statements, prevArgName![0], constIdentifiers) : getSynthesizedDeepClones(func.body.statements); + return nextDotThen ? removeReturns(createNodeArray(refactoredStmts), prevArgName![0], constIdentifiers) : getSynthesizedDeepClones(createNodeArray(refactoredStmts)); } else { const funcBody = (func).body; const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(funcBody as Expression)); const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); + if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); } @@ -367,6 +373,17 @@ namespace ts.codefix { return createNodeArray([]); } + function getReturnStatementsWithPromiseCallbacksIndices(block: Block): number[] { + let indices: number[] = []; + for (let i = 0; i < block.statements.length; i++) { + let statement = block.statements[i]; + if (getReturnStatementsWithPromiseCallbacks(statement).length) { + indices.push(i); + } + } + return indices; + } + function removeReturns(stmts: NodeArray, prevArgName: Identifier, constIdentifiers: Identifier[]): NodeArray { const ret: Statement[] = []; for (const stmt of stmts) { From 41f0bb88ae5d7faa912ab5f2f3e8631eaadbc489 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 1 Aug 2018 14:26:31 -0700 Subject: [PATCH 154/196] Fixed linting errors --- src/services/codefixes/convertToAsyncFunction.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 36f698b7b2a82..19662741178db 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -340,8 +340,8 @@ namespace ts.codefix { const indices = getReturnStatementsWithPromiseCallbacksIndices(func.body); let refactoredStmts: Statement[] = []; - for (let i=0; i elem === i).length) { refactoredStmts = refactoredStmts.concat(getInnerCallbackBody(checker, [statement], synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName)); } @@ -374,9 +374,9 @@ namespace ts.codefix { } function getReturnStatementsWithPromiseCallbacksIndices(block: Block): number[] { - let indices: number[] = []; + const indices: number[] = []; for (let i = 0; i < block.statements.length; i++) { - let statement = block.statements[i]; + const statement = block.statements[i]; if (getReturnStatementsWithPromiseCallbacks(statement).length) { indices.push(i); } From 35b9638b28493b4edfa7e0aabe4bc2b005e8cefb Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 1 Aug 2018 14:55:04 -0700 Subject: [PATCH 155/196] addressed more code review comments --- .../codefixes/convertToAsyncFunction.ts | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 19662741178db..6e9066cff918e 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -80,14 +80,16 @@ namespace ts.codefix { } function willBeParsed(node: Expression): boolean { - return returnsAPromise(node, checker) || (isCallExpression(node) && (isCallback(node, "then", checker) || isCallback(node, "catch", checker))); + let nodeType = checker.getTypeAtLocation(node); + return !!nodeType && (returnsAPromise(node, nodeType, checker) || (isCallExpression(node) && !!checker.getPromisedTypeOfPromise(nodeType) && (hasPropertyAccessExpressionWithName(node, "then") || hasPropertyAccessExpressionWithName(node, "catch")))); } // maps nodes to boolean - true indicates that there is another .then() in the callback chain const lastDotThen: Map = createMap(); forEachChild(func.body, function visit(node: Node) { - if (isCallExpression(node) && isCallback(node, "then", checker)) { + let nodeType = checker.getTypeAtLocation(node); + if (isCallExpression(node) && nodeType && !!checker.getPromisedTypeOfPromise(nodeType) && hasPropertyAccessExpressionWithName(node, "then")) { // false - there is no following .then() in the callback chain lastDotThen.set(getNodeId(node).toString(), false); @@ -192,13 +194,8 @@ namespace ts.codefix { return numVarsSameName === 0 ? [name, 1] : [createIdentifier(name.text + "_" + numVarsSameName), numVarsSameName]; } - function returnsAPromise(node: Expression, checker: TypeChecker): boolean { - const nodeType = checker.getTypeAtLocation(node); - if (!nodeType) { - return false; - } - - return !!checker.getPromisedTypeOfPromise(nodeType) && (!isCallExpression(node) || !isCallback(node, "then", checker) && !isCallback(node, "catch", checker) && !isCallback(node, "finally", checker)); + function returnsAPromise(node: Expression, nodeType: Type, checker: TypeChecker): boolean { + return (!isCallExpression(node) || !hasPropertyAccessExpressionWithName(node, "then") && !hasPropertyAccessExpressionWithName(node, "catch")) && !!checker.getPromisedTypeOfPromise(nodeType); } // dispatch function to recursively build the refactoring @@ -208,16 +205,18 @@ namespace ts.codefix { return []; } - if (isCallExpression(node) && isCallback(node, "then", checker)) { + const nodeType = checker.getTypeAtLocation(node); + + if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "then") && nodeType && !!checker.getPromisedTypeOfPromise(nodeType)) { return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); } - else if (isCallExpression(node) && isCallback(node, "catch", checker)) { + else if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "catch") && nodeType && !!checker.getPromisedTypeOfPromise(nodeType)) { return parseCatch(node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); } else if (isPropertyAccessExpression(node)) { return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); } - else if (returnsAPromise(node, checker)) { + else if (nodeType && returnsAPromise(node, nodeType, checker)) { return parsePromiseCall(node, lastDotThenMap, constIdentifiers, prevArgName); } @@ -423,17 +422,14 @@ namespace ts.codefix { return innerCbBody; } - function isCallback(node: CallExpression, funcName: string, checker: TypeChecker): boolean { - if (node.expression.kind !== SyntaxKind.PropertyAccessExpression) { - return false; - } + function hasPropertyAccessExpressionWithName(node: CallExpression, funcName: string): boolean { - const nodeType = checker.getTypeAtLocation(node); - if (!nodeType) { + if (!isPropertyAccessExpression(node.expression)) { return false; } - return (node.expression).name.text === funcName && !!checker.getPromisedTypeOfPromise(nodeType); + return node.expression.name.text === funcName; + } function getArgName(funcNode: Node, synthNamesMap: Map<[Identifier, number]>, checker: TypeChecker): [Identifier, number] { From 4d8f6d28c061ccc243b4e473b917b9d8b423bb7a Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 1 Aug 2018 15:18:16 -0700 Subject: [PATCH 156/196] Updated tests --- .../unittests/convertToAsyncFunction.ts | 7 ++++- ...convertToAsyncFunction_MultipleReturns2.ts | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index ab21b4e285ca0..1f28a8e5e8f1b 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -458,6 +458,12 @@ interface Array {}` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(result => { console.log(result) }); }`); + _testConvertToAsyncFunction("convertToAsyncFunction_basicWithComments", ` +function [#|f|](): Promise{ + // here's a comment + return fetch('https://typescriptlang.org').then( /* another one! */ result => { /* comment */ console.log(result) }); +}`); + _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunction", ` [#|():Promise => {|] return fetch('https://typescriptlang.org').then(result => console.log(result)); @@ -817,7 +823,6 @@ function my_print (resp): Promise { ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns1", ` function [#|f|](): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts new file mode 100644 index 0000000000000..6569c1fb0ef99 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts @@ -0,0 +1,26 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise { + let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); + if (x.ok) { + return fetch("https://typescriptlang.org").then(res => console.log(res)); + } + return x.then(resp => { + var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); + return fetch("https://micorosft.com").then(res => console.log("Another one!")); + }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise { + let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); + if (x.ok) { + const res_1 = await fetch("https://typescriptlang.org"); + return console.log(res_1); + } + const resp = await x; + var blob = resp.blob().then(blob_1 => blob_1.byteOffset).catch(err => 'Error'); + const res_1 = await fetch("https://micorosft.com"); + return console.log("Another one!"); +} From cf331b48dc17dd52b4218d99f8c88ef19fd6f96a Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 3 Aug 2018 11:43:55 -0700 Subject: [PATCH 157/196] Removed calling checker.getTypeAtLocation() from suggestion diags --- src/services/suggestionDiagnostics.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 19c8242d346c0..b6af91af887d8 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -115,12 +115,12 @@ namespace ts { function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]): void { - const functionType = checker.getTypeAtLocation(node); + const functionType = node.type ? checker.getTypeFromTypeNode(node.type) : undefined; if (isAsyncFunction(node) || !node.body || !functionType) { return; } - const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call); + const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call); const returnType = callSignatures.length ? checker.getReturnTypeOfSignature(callSignatures[0]) : undefined; if (!returnType || !checker.getPromisedTypeOfPromise(returnType)) { From 60ababa4c2cb1df2adb653df745944a363e0a2f1 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 3 Aug 2018 13:56:51 -0700 Subject: [PATCH 158/196] added internal tags --- src/compiler/types.ts | 1 + src/services/suggestionDiagnostics.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3e00acb9f9cb1..5ab80bcd57dce 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2885,6 +2885,7 @@ namespace ts { getBaseTypes(type: InterfaceType): BaseType[]; getBaseTypeOfLiteralType(type: Type): Type; getWidenedType(type: Type): Type; + /* @internal */ getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined; getReturnTypeOfSignature(signature: Signature): Type; /** diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index b6af91af887d8..120d946c736f3 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -139,6 +139,7 @@ namespace ts { return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; } + /** @internal */ export function getReturnStatementsWithPromiseCallbacks(node: Node): Node[] { const returnStatements: Node[] = []; if (isFunctionLike(node)) { From d4721244d658d7797d3198052a4a9adf1dd15932 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 3 Aug 2018 14:41:08 -0700 Subject: [PATCH 159/196] Switched to use SynthIdentifier type --- .../codefixes/convertToAsyncFunction.ts | 101 +++++++++--------- src/services/suggestionDiagnostics.ts | 2 +- src/services/types.ts | 6 ++ src/services/utilities.ts | 8 +- 4 files changed, 62 insertions(+), 55 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 6e9066cff918e..cf87a08751c78 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -20,7 +20,7 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert); - const synthNamesMap: Map<[Identifier, number]> = createMap(); // number indicates the number of times it is used after declaration + const synthNamesMap: Map = createMap(); // number indicates the number of times it is used after declaration const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context); const lastDotThenMap = findLastDotThens(functionToConvertRenamed, checker); @@ -64,11 +64,11 @@ namespace ts.codefix { } } - function getConstIdentifiers(synthNamesMap: Map<[Identifier, number]>): Identifier[] { + function getConstIdentifiers(synthNamesMap: Map): Identifier[] { const constIdentifiers: Identifier[] = []; synthNamesMap.forEach((val) => { - if (val[1] === 1) { - constIdentifiers.push(val[0]); + if (val.numberOfUses === 1) { + constIdentifiers.push(val.identifier); } }); return constIdentifiers; @@ -142,9 +142,10 @@ namespace ts.codefix { } // varNamesMap holds all of the variables in original source code. synthNamesMap holds all of the variables created by the refactor - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, context: CodeFixContextBase): FunctionLikeDeclaration { + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase): FunctionLikeDeclaration { const allVarNames: [Identifier, Symbol][] = []; + forEachChild(nodeToRename, function visit(node: Node) { const symbol = checker.getSymbolAtLocation(node); const isDefinedInFile = symbol ? definedInFile(symbol, context.sourceFile) : undefined; @@ -157,7 +158,7 @@ namespace ts.codefix { if (type.getCallSignatures()[0].parameters.length && !synthNamesMap.get(getSymbolId(symbol).toString())) { // add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) const synthName = getNewNameIfConflict(createIdentifier(type.getCallSignatures()[0].parameters[0].name), allVarNames); - allVarNames.push([synthName[0], symbol]); + allVarNames.push([synthName.identifier, symbol]); synthNamesMap.set(getSymbolId(symbol).toString(), synthName); } } @@ -167,7 +168,7 @@ namespace ts.codefix { for (const ident of allVarNames) { if (ident[0].text === node.text && ident[1] !== symbol) { - allVarNames.push([newName[0], symbol]); + allVarNames.push([newName.identifier, symbol]); synthNamesMap.set(getSymbolId(symbol).toString(), newName); setName = true; } @@ -177,7 +178,7 @@ namespace ts.codefix { if (node.parent && isParameter(node.parent) || isVariableDeclaration(node.parent)) { allVarNames.push([node, symbol]); } - synthNamesMap.set(getSymbolId(symbol).toString(), [getSynthesizedDeepClone(node), allVarNames.filter(elem => elem[0].text === node.text).length]); + synthNamesMap.set(getSymbolId(symbol).toString(), {identifier: getSynthesizedDeepClone(node), numberOfUses: allVarNames.filter(elem => elem[0].text === node.text).length}); } } } @@ -189,9 +190,9 @@ namespace ts.codefix { return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, synthNamesMap, checker); } - function getNewNameIfConflict(name: Identifier, allVarNames: [Identifier, Symbol][]): [Identifier, number] { + function getNewNameIfConflict(name: Identifier, allVarNames: [Identifier, Symbol][]): SynthIdentifier { const numVarsSameName = allVarNames.filter(elem => elem[0].text === name.text).length; - return numVarsSameName === 0 ? [name, 1] : [createIdentifier(name.text + "_" + numVarsSameName), numVarsSameName]; + return numVarsSameName === 0 ? {identifier: name, numberOfUses: 1} : {identifier: createIdentifier(name.text + "_" + numVarsSameName), numberOfUses: numVarsSameName}; } function returnsAPromise(node: Expression, nodeType: Type, checker: TypeChecker): boolean { @@ -199,8 +200,8 @@ namespace ts.codefix { } // dispatch function to recursively build the refactoring - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, - lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { + function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, + lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: SynthIdentifier): Statement[] { if (!node) { return []; } @@ -223,26 +224,26 @@ namespace ts.codefix { return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { + function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: SynthIdentifier): Statement[] { const func = node.arguments[0]; const argName = getArgName(func, synthNamesMap, checker); let varDecl; if (prevArgName && lastDotThenMap.get(getNodeId(node).toString())) { - varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]))], NodeFlags.Let /*getNodeFlags(prevArgName[0], constIdentifiers)*/)); - prevArgName[1] += 2; + varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); + prevArgName.numberOfUses += 2; } const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName)); const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers); - const catchClause = createCatchClause(argName[0].text, createBlock(callbackBody)); + const catchClause = createCatchClause(argName.identifier.text, createBlock(callbackBody)); const tryStatement = createTry(tryBlock, catchClause, /*finallyBlock*/ undefined); return varDecl ? [varDecl, tryStatement] : [tryStatement]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map<[Identifier, number]>, - lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { + function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, + lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: SynthIdentifier): Statement[] { const [res, rej] = node.arguments; @@ -259,7 +260,7 @@ namespace ts.codefix { const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, argNameRes).concat(callbackBody)); const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers); - const catchClause = createCatchClause(argNameRej[0].text, createBlock(callbackBody2)); + const catchClause = createCatchClause(argNameRej.identifier.text, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } @@ -275,19 +276,19 @@ namespace ts.codefix { return inArr ? NodeFlags.Const : NodeFlags.Let; } - function parsePromiseCall(node: Expression, lastDotThenMap: Map, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]): Statement[] { + function parsePromiseCall(node: Expression, lastDotThenMap: Map, constIdentifiers: Identifier[], prevArgName?: SynthIdentifier): Statement[] { const nextDotThen = lastDotThenMap.get(getNodeId(node).toString()); - const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; + const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const originalNodeParent = node.original ? node.original.parent : node.parent; if (hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { - if (prevArgName![1] > 1) { - prevArgName![1] -= 1; - return [createStatement(createAssignment(getSynthesizedDeepClone(prevArgName![0]), createAwait(node)))]; + if (prevArgName!.numberOfUses > 1) { + prevArgName!.numberOfUses -= 1; + return [createStatement(createAssignment(getSynthesizedDeepClone(prevArgName!.identifier), createAwait(node)))]; } - const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName![0]), /*type*/ undefined, createAwait(node)); - return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], getFlagOfIdentifier(prevArgName![0], constIdentifiers))))]; + const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName!.identifier), /*type*/ undefined, createAwait(node)); + return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], getFlagOfIdentifier(prevArgName!.identifier, constIdentifiers))))]; } else if (!hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { return [createStatement(createAwait(node))]; @@ -296,23 +297,23 @@ namespace ts.codefix { return [createReturn(getSynthesizedDeepClone(node))]; } - function getCallbackBody(func: Node, prevArgName: [Identifier, number] | undefined, argName: [Identifier, number], parent: CallExpression, checker: TypeChecker, - synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[]): NodeArray { + function getCallbackBody(func: Node, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier, parent: CallExpression, checker: TypeChecker, + synthNamesMap: Map, lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[]): NodeArray { - function createVariableDeclarationOrAssignment(prevArgName: [Identifier, number], rightHandSide: Expression): NodeArray { - if (prevArgName[1] > 1) { - prevArgName[1] -= 1; - return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName[0]), rightHandSide))]); + function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression): NodeArray { + if (prevArgName.numberOfUses > 1) { + prevArgName.numberOfUses -= 1; + return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]); } - prevArgName[1] -= 1; + prevArgName.numberOfUses -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName[0]), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName[0], constIdentifiers))))]); + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, constIdentifiers))))]); } - const hasPrevArgName = prevArgName && prevArgName[0].text.length > 0; - const hasArgName = argName && argName[0].text.length > 0; + const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; + const hasArgName = argName && argName.identifier.text.length > 0; const nextDotThen = lastDotThenMap.get(getNodeId(parent).toString()); switch (func.kind) { case SyntaxKind.Identifier: @@ -320,7 +321,7 @@ namespace ts.codefix { break; } - const synthCall = createCall(getSynthesizedDeepClone(func) as Identifier, /*typeArguments*/ undefined, [argName[0]]); + const synthCall = createCall(getSynthesizedDeepClone(func) as Identifier, /*typeArguments*/ undefined, [argName.identifier]); if (!nextDotThen) { return createNodeArray([createReturn(synthCall)]); } @@ -349,7 +350,7 @@ namespace ts.codefix { } } - return nextDotThen ? removeReturns(createNodeArray(refactoredStmts), prevArgName![0], constIdentifiers) : getSynthesizedDeepClones(createNodeArray(refactoredStmts)); + return nextDotThen ? removeReturns(createNodeArray(refactoredStmts), prevArgName!.identifier, constIdentifiers) : getSynthesizedDeepClones(createNodeArray(refactoredStmts)); } else { const funcBody = (func).body; @@ -378,7 +379,7 @@ namespace ts.codefix { const statement = block.statements[i]; if (getReturnStatementsWithPromiseCallbacks(statement).length) { indices.push(i); - } + } } return indices; } @@ -401,8 +402,8 @@ namespace ts.codefix { } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map<[Identifier, number]>, lastDotThenMap: Map, - context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: [Identifier, number]) { + function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, + context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: SynthIdentifier) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { @@ -429,21 +430,21 @@ namespace ts.codefix { } return node.expression.name.text === funcName; - + } - function getArgName(funcNode: Node, synthNamesMap: Map<[Identifier, number]>, checker: TypeChecker): [Identifier, number] { + function getArgName(funcNode: Node, synthNamesMap: Map, checker: TypeChecker): SynthIdentifier { - function getMapEntryIfExists(node: Identifier): [Identifier, number] { + function getMapEntryIfExists(node: Identifier): SynthIdentifier { const originalNode = getOriginalNode(node); const symbol = getSymbol(originalNode); if (!symbol) { - return [node, 1]; + return {identifier: node, numberOfUses: 1}; } const mapEntry = synthNamesMap.get(getSymbolId(symbol).toString()); - return mapEntry ? mapEntry : [node, 1]; + return mapEntry ? mapEntry : {identifier: node, numberOfUses: 1}; } function getSymbol(node: Node): Symbol | undefined { @@ -454,21 +455,21 @@ namespace ts.codefix { return node.original ? node.original : node; } - let name: [Identifier, number] | undefined; + let name: SynthIdentifier | undefined; if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { const param = funcNode.parameters[0].name as Identifier; name = getMapEntryIfExists(param); } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { - name = [funcNode.arguments[0] as Identifier, 1]; + name = {identifier: funcNode.arguments[0] as Identifier, numberOfUses: 1}; } else if (isIdentifier(funcNode)) { name = getMapEntryIfExists(funcNode); } - if (!name || name[0] === undefined || name[0].text === "_" || name[0].text === "undefined") { - return [createIdentifier(""), 1]; + if (!name || name.identifier === undefined || name.identifier.text === "_" || name.identifier.text === "undefined") { + return {identifier: createIdentifier(""), numberOfUses: 1}; } return name; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 120d946c736f3..4b23862aba858 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -115,7 +115,7 @@ namespace ts { function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]): void { - const functionType = node.type ? checker.getTypeFromTypeNode(node.type) : undefined; + const functionType = node.type ? checker.getTypeFromTypeNode(node.type) : undefined; if (isAsyncFunction(node) || !node.body || !functionType) { return; } diff --git a/src/services/types.ts b/src/services/types.ts index be897f20cd43c..b1185244de4d7 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -169,6 +169,12 @@ namespace ts { fileName: Path; packageName: string; } + + /* @internal */ + export interface SynthIdentifier { + identifier: Identifier; + numberOfUses: number; + } // // Public interface of the host of a language service instance. diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 6f5149f009ab7..2a1c46f101e82 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1626,21 +1626,21 @@ namespace ts { * WARNING: This is an expensive operation and is only intended to be used in refactorings * and code fixes (because those are triggered by explicit user actions). */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map<[Identifier, number]>, checker?: TypeChecker): T { + export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker): T { const clone = renameMap && checker && needsRenaming(node, checker, renameMap) ? - node && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))![0] : + node && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))!.identifier : node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, checker); if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); return clone as T; } - function needsRenaming(node: T | undefined, checker: TypeChecker, renameMap: Map<[Identifier, number]>): boolean { + function needsRenaming(node: T | undefined, checker: TypeChecker, renameMap: Map): boolean { return !!(node && isIdentifier(node) && checker.getSymbolAtLocation(node) && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node)!)))) && !checker.getTypeAtLocation(node)!.getCallSignatures().length; } - function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map<[Identifier, number]>, checker?: TypeChecker): T { + function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker): T { const visited = visitEachChild(node, function wrapper(node) { return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, checker); }, nullTransformationContext); From 6c7a10ce1b0f6ccbd876e5599e62e244c603739c Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 3 Aug 2018 15:56:27 -0700 Subject: [PATCH 160/196] Added a transformer class --- .../codefixes/convertToAsyncFunction.ts | 114 ++++++++++-------- 1 file changed, 64 insertions(+), 50 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index cf87a08751c78..96b3d4d62ef2b 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -10,6 +10,22 @@ namespace ts.codefix { fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file, err.start, context.program.getTypeChecker(), context)), }); + + class Transformer { + checker: TypeChecker; + synthNamesMap: Map; + lastDotThenMap: Map; + context: CodeFixContextBase; + constIdentifiers: Identifier[]; + constructor(_checker: TypeChecker, _synthNamesMap: Map, _lastDotThenMap: Map, _context: CodeFixContextBase, _constIdentifiers: Identifier[]) { + this.checker = _checker; + this.synthNamesMap = _synthNamesMap; + this.lastDotThenMap = _lastDotThenMap; + this.context = _context; + this.constIdentifiers = _constIdentifiers; + } + } + function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker, context: CodeFixContextBase): void { // get the function declaration - returns a promise const functionToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position)) as FunctionLikeDeclaration; @@ -17,20 +33,24 @@ namespace ts.codefix { return; } - // add the async keyword - changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert); - const synthNamesMap: Map = createMap(); // number indicates the number of times it is used after declaration - const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context); const lastDotThenMap = findLastDotThens(functionToConvertRenamed, checker); const constIdentifiers = getConstIdentifiers(synthNamesMap); - const returnStatements = getReturnStatementsWithPromiseCallbacks(functionToConvertRenamed); + const transformer = new Transformer(checker, synthNamesMap, lastDotThenMap, context, constIdentifiers); + + if (!returnStatements.length) { + return; + } + + // add the async keyword + changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert); + const allNewNodes: Map = createMap(); function startParse(node: CallExpression, nodeToReplace: Node) { - const newNodes = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers); + const newNodes = parseCallback(node, transformer, node); if (newNodes.length) { allNewNodes.set(getNodeId(nodeToReplace).toString(), newNodes); } @@ -145,7 +165,6 @@ namespace ts.codefix { function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase): FunctionLikeDeclaration { const allVarNames: [Identifier, Symbol][] = []; - forEachChild(nodeToRename, function visit(node: Node) { const symbol = checker.getSymbolAtLocation(node); const isDefinedInFile = symbol ? definedInFile(symbol, context.sourceFile) : undefined; @@ -200,72 +219,69 @@ namespace ts.codefix { } // dispatch function to recursively build the refactoring - function parseCallback(node: Expression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, - lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: SynthIdentifier): Statement[] { + function parseCallback(node: Expression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] { if (!node) { return []; } - const nodeType = checker.getTypeAtLocation(node); + const nodeType = transformer.checker.getTypeAtLocation(node); - if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "then") && nodeType && !!checker.getPromisedTypeOfPromise(nodeType)) { - return parseThen(node, checker, outermostParent, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); + if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "then") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) { + return parseThen(node, transformer, outermostParent, prevArgName); } - else if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "catch") && nodeType && !!checker.getPromisedTypeOfPromise(nodeType)) { - return parseCatch(node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); + else if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "catch") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) { + return parseCatch(node, transformer, prevArgName); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); + return parseCallback(node.expression, transformer, outermostParent, prevArgName); } - else if (nodeType && returnsAPromise(node, nodeType, checker)) { - return parsePromiseCall(node, lastDotThenMap, constIdentifiers, prevArgName); + else if (nodeType && returnsAPromise(node, nodeType, transformer.checker)) { + return parsePromiseCall(node, transformer, prevArgName); } return []; } - function parseCatch(node: CallExpression, checker: TypeChecker, synthNamesMap: Map, lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: SynthIdentifier): Statement[] { + function parseCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { const func = node.arguments[0]; - const argName = getArgName(func, synthNamesMap, checker); + const argName = getArgName(func, transformer); let varDecl; - if (prevArgName && lastDotThenMap.get(getNodeId(node).toString())) { - varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); + if (prevArgName && transformer.lastDotThenMap.get(getNodeId(node).toString())) { + varDecl = createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); prevArgName.numberOfUses += 2; } - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName)); + const tryBlock = createBlock(parseCallback(node.expression, transformer, node, prevArgName)); - const callbackBody = getCallbackBody(func, prevArgName, argName, node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers); + const callbackBody = getCallbackBody(func, prevArgName, argName, node, transformer); const catchClause = createCatchClause(argName.identifier.text, createBlock(callbackBody)); const tryStatement = createTry(tryBlock, catchClause, /*finallyBlock*/ undefined); return varDecl ? [varDecl, tryStatement] : [tryStatement]; } - function parseThen(node: CallExpression, checker: TypeChecker, outermostParent: CallExpression, synthNamesMap: Map, - lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: SynthIdentifier): Statement[] { - + function parseThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression,prevArgName?: SynthIdentifier): Statement[] { const [res, rej] = node.arguments; if (!res) { - return parseCallback(node.expression, checker, outermostParent, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); + return parseCallback(node.expression, transformer, outermostParent); } - const argNameRes = getArgName(res, synthNamesMap, checker); - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers); + const argNameRes = getArgName(res, transformer); + const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, transformer); if (rej) { - const argNameRej = getArgName(rej, synthNamesMap, checker); + const argNameRej = getArgName(rej, transformer); - const tryBlock = createBlock(parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, argNameRes).concat(callbackBody)); + const tryBlock = createBlock(parseCallback(node.expression, transformer, node, argNameRes).concat(callbackBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, checker, synthNamesMap, lastDotThenMap, context, constIdentifiers); + const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, transformer); const catchClause = createCatchClause(argNameRej.identifier.text, createBlock(callbackBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else { - return parseCallback(node.expression, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, argNameRes).concat(callbackBody); + return parseCallback(node.expression, transformer, node, argNameRes).concat(callbackBody); } return []; @@ -276,8 +292,8 @@ namespace ts.codefix { return inArr ? NodeFlags.Const : NodeFlags.Let; } - function parsePromiseCall(node: Expression, lastDotThenMap: Map, constIdentifiers: Identifier[], prevArgName?: SynthIdentifier): Statement[] { - const nextDotThen = lastDotThenMap.get(getNodeId(node).toString()); + function parsePromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { + const nextDotThen = transformer.lastDotThenMap.get(getNodeId(node).toString()); const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const originalNodeParent = node.original ? node.original.parent : node.parent; if (hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { @@ -288,7 +304,7 @@ namespace ts.codefix { } const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName!.identifier), /*type*/ undefined, createAwait(node)); - return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], getFlagOfIdentifier(prevArgName!.identifier, constIdentifiers))))]; + return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], getFlagOfIdentifier(prevArgName!.identifier, transformer.constIdentifiers))))]; } else if (!hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { return [createStatement(createAwait(node))]; @@ -297,24 +313,23 @@ namespace ts.codefix { return [createReturn(getSynthesizedDeepClone(node))]; } - function getCallbackBody(func: Node, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier, parent: CallExpression, checker: TypeChecker, - synthNamesMap: Map, lastDotThenMap: Map, context: CodeFixContextBase, constIdentifiers: Identifier[]): NodeArray { + function getCallbackBody(func: Node, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier, parent: CallExpression, transformer: Transformer): NodeArray { function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression): NodeArray { if (prevArgName.numberOfUses > 1) { prevArgName.numberOfUses -= 1; + debugger; return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]); } - prevArgName.numberOfUses -= 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, constIdentifiers))))]); + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))]); } const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const hasArgName = argName && argName.identifier.text.length > 0; - const nextDotThen = lastDotThenMap.get(getNodeId(parent).toString()); + const nextDotThen = transformer.lastDotThenMap.get(getNodeId(parent).toString()); switch (func.kind) { case SyntaxKind.Identifier: if (!hasArgName) { @@ -343,19 +358,19 @@ namespace ts.codefix { for (let i = 0; i < func.body.statements.length; i++) { const statement = func.body.statements[i]; if (indices.filter(elem => elem === i).length) { - refactoredStmts = refactoredStmts.concat(getInnerCallbackBody(checker, [statement], synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName)); + refactoredStmts = refactoredStmts.concat(getInnerCallbackBody(transformer, [statement], prevArgName)); } else { refactoredStmts.push(statement); } } - return nextDotThen ? removeReturns(createNodeArray(refactoredStmts), prevArgName!.identifier, constIdentifiers) : getSynthesizedDeepClones(createNodeArray(refactoredStmts)); + return nextDotThen ? removeReturns(createNodeArray(refactoredStmts), prevArgName!.identifier, transformer.constIdentifiers) : getSynthesizedDeepClones(createNodeArray(refactoredStmts)); } else { const funcBody = (func).body; const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(funcBody as Expression)); - const innerCbBody = getInnerCallbackBody(checker, innerRetStmts, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); + const innerCbBody = getInnerCallbackBody(transformer, innerRetStmts, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); @@ -402,14 +417,13 @@ namespace ts.codefix { } - function getInnerCallbackBody(checker: TypeChecker, innerRetStmts: Node[], synthNamesMap: Map, lastDotThenMap: Map, - context: CodeFixContextBase, constIdentifiers: Identifier[], prevArgName?: SynthIdentifier) { + function getInnerCallbackBody(transformer: Transformer, innerRetStmts: Node[], prevArgName?: SynthIdentifier) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = parseCallback(node, checker, node, synthNamesMap, lastDotThenMap, context, constIdentifiers, prevArgName); + const temp = parseCallback(node, transformer, node, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; @@ -433,7 +447,7 @@ namespace ts.codefix { } - function getArgName(funcNode: Node, synthNamesMap: Map, checker: TypeChecker): SynthIdentifier { + function getArgName(funcNode: Node, transformer: Transformer): SynthIdentifier { function getMapEntryIfExists(node: Identifier): SynthIdentifier { const originalNode = getOriginalNode(node); @@ -443,12 +457,12 @@ namespace ts.codefix { return {identifier: node, numberOfUses: 1}; } - const mapEntry = synthNamesMap.get(getSymbolId(symbol).toString()); + const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); return mapEntry ? mapEntry : {identifier: node, numberOfUses: 1}; } function getSymbol(node: Node): Symbol | undefined { - return node.symbol ? node.symbol : checker.getSymbolAtLocation(node); + return node.symbol ? node.symbol : transformer.checker.getSymbolAtLocation(node); } function getOriginalNode(node: Node): Node { From 774aece1afe6ad7b7cdbf8738e3d396bc4a70654 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 3 Aug 2018 16:06:23 -0700 Subject: [PATCH 161/196] Removed export of custom type --- src/services/codefixes/convertToAsyncFunction.ts | 6 ++++++ src/services/types.ts | 6 ------ src/services/utilities.ts | 10 +++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 96b3d4d62ef2b..cf680b54d9cf0 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -11,6 +11,12 @@ namespace ts.codefix { getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file, err.start, context.program.getTypeChecker(), context)), }); + + interface SynthIdentifier { + identifier: Identifier; + numberOfUses: number; + } + class Transformer { checker: TypeChecker; synthNamesMap: Map; diff --git a/src/services/types.ts b/src/services/types.ts index b1185244de4d7..5dc4d36540a31 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -170,12 +170,6 @@ namespace ts { packageName: string; } - /* @internal */ - export interface SynthIdentifier { - identifier: Identifier; - numberOfUses: number; - } - // // Public interface of the host of a language service instance. // diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 2a1c46f101e82..13063c3001353 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1620,13 +1620,17 @@ namespace ts { return position; } + type RenamedIdentifier = { + identifier: Identifier; + } + /** * Creates a deep, memberwise clone of a node with no source map location. * * WARNING: This is an expensive operation and is only intended to be used in refactorings * and code fixes (because those are triggered by explicit user actions). */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker): T { + export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker): T { const clone = renameMap && checker && needsRenaming(node, checker, renameMap) ? node && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))!.identifier : node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, checker); @@ -1635,12 +1639,12 @@ namespace ts { return clone as T; } - function needsRenaming(node: T | undefined, checker: TypeChecker, renameMap: Map): boolean { + function needsRenaming(node: T | undefined, checker: TypeChecker, renameMap: Map): boolean { return !!(node && isIdentifier(node) && checker.getSymbolAtLocation(node) && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node)!)))) && !checker.getTypeAtLocation(node)!.getCallSignatures().length; } - function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker): T { + function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker): T { const visited = visitEachChild(node, function wrapper(node) { return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, checker); }, nullTransformationContext); From b73bb5510004fde2ebab58b9468d9a1734744103 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 3 Aug 2018 16:07:16 -0700 Subject: [PATCH 162/196] Cleaned up tests --- src/testRunner/unittests/convertToAsyncFunction.ts | 6 +++--- .../convertToAsyncFunction_Finally1.ts | 11 ----------- .../convertToAsyncFunction_Finally2.ts | 11 ----------- .../convertToAsyncFunction_Finally3.ts | 11 ----------- 4 files changed, 3 insertions(+), 36 deletions(-) delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally1.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally2.ts delete mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally3.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 1f28a8e5e8f1b..aff7d685c6b42 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -635,21 +635,21 @@ function [#|f|]():Promise { } ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_Finally1", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally1", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").then(res => console.log(res)).catch(rej => console.log("error", rej)).finally(console.log("finally!")); } ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_Finally2", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally2", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").then(res => console.log(res)).finally(console.log("finally!")); } ` ); - _testConvertToAsyncFunction("convertToAsyncFunction_Finally3", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally3", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").finally(console.log("finally!")); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally1.ts deleted file mode 100644 index c232991b37790..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally1.ts +++ /dev/null @@ -1,11 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/finallyTest/*|]*/(): Promise { - return fetch("https://typescriptlang.org").then(res => console.log(res)).catch(rej => console.log("error", rej)).finally(console.log("finally!")); -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function finallyTest(): Promise { - return fetch("https://typescriptlang.org").then(res => console.log(res)).catch(rej => console.log("error", rej)).finally(console.log("finally!")); -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally2.ts deleted file mode 100644 index 984f54da87724..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally2.ts +++ /dev/null @@ -1,11 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/finallyTest/*|]*/(): Promise { - return fetch("https://typescriptlang.org").then(res => console.log(res)).finally(console.log("finally!")); -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function finallyTest(): Promise { - return fetch("https://typescriptlang.org").then(res => console.log(res)).finally(console.log("finally!")); -} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally3.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally3.ts deleted file mode 100644 index 8f7dfd953a831..0000000000000 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Finally3.ts +++ /dev/null @@ -1,11 +0,0 @@ -// ==ORIGINAL== - -function /*[#|*/finallyTest/*|]*/(): Promise { - return fetch("https://typescriptlang.org").finally(console.log("finally!")); -} - -// ==ASYNC FUNCTION::Convert to async function== - -async function finallyTest(): Promise { - return fetch("https://typescriptlang.org").finally(console.log("finally!")); -} From 87dd68325d8abfc3eff5bbe1503998e2bebf04a2 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 3 Aug 2018 16:08:28 -0700 Subject: [PATCH 163/196] Removed unnecessary file --- src/server/tsconfig.library.json | 144 ------------------------------- 1 file changed, 144 deletions(-) delete mode 100644 src/server/tsconfig.library.json diff --git a/src/server/tsconfig.library.json b/src/server/tsconfig.library.json deleted file mode 100644 index 3c7cda8d74b0a..0000000000000 --- a/src/server/tsconfig.library.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "compilerOptions": { - "noImplicitAny": true, - "noImplicitThis": true, - "alwaysStrict": true, - "preserveConstEnums": true, - "pretty": true, - "outFile": "../../built/local/tsserverlibrary.js", - "sourceMap": true, - "stripInternal": true, - "target": "es5", - "noUnusedLocals": true, - "noUnusedParameters": true, - "declaration": true, - "types": [] - }, - "files": [ - "../compiler/types.ts", - "../compiler/performance.ts", - "../compiler/core.ts", - "../compiler/sys.ts", - "../compiler/diagnosticInformationMap.generated.ts", - "../compiler/scanner.ts", - "../compiler/utilities.ts", - "../compiler/parser.ts", - "../compiler/binder.ts", - "../compiler/symbolWalker.ts", - "../compiler/moduleNameResolver.ts", - "../compiler/checker.ts", - "../compiler/factory.ts", - "../compiler/visitor.ts", - "../compiler/transformers/utilities.ts", - "../compiler/transformers/destructuring.ts", - "../compiler/transformers/ts.ts", - "../compiler/transformers/es2017.ts", - "../compiler/transformers/esnext.ts", - "../compiler/transformers/jsx.ts", - "../compiler/transformers/es2016.ts", - "../compiler/transformers/es2015.ts", - "../compiler/transformers/es5.ts", - "../compiler/transformers/generators.ts", - "../compiler/transformers/module/module.ts", - "../compiler/transformers/module/system.ts", - "../compiler/transformers/module/es2015.ts", - "../compiler/transformers/declarations/diagnostics.ts", - "../compiler/transformers/declarations.ts", - "../compiler/transformer.ts", - "../compiler/sourcemap.ts", - "../compiler/comments.ts", - "../compiler/emitter.ts", - "../compiler/watchUtilities.ts", - "../compiler/program.ts", - "../compiler/builderState.ts", - "../compiler/builder.ts", - "../compiler/resolutionCache.ts", - "../compiler/watch.ts", - "../compiler/commandLineParser.ts", - - "../services/types.ts", - "../services/utilities.ts", - "../services/classifier.ts", - "../services/pathCompletions.ts", - "../services/completions.ts", - "../services/documentHighlights.ts", - "../services/documentRegistry.ts", - "../services/importTracker.ts", - "../services/findAllReferences.ts", - "../services/goToDefinition.ts", - "../services/jsDoc.ts", - "../services/semver.ts", - "../services/jsTyping.ts", - "../services/navigateTo.ts", - "../services/navigationBar.ts", - "../services/organizeImports.ts", - "../services/getEditsForFileRename.ts", - "../services/outliningElementsCollector.ts", - "../services/patternMatcher.ts", - "../services/preProcess.ts", - "../services/rename.ts", - "../services/signatureHelp.ts", - "../services/suggestionDiagnostics.ts", - "../services/symbolDisplay.ts", - "../services/transpile.ts", - "../services/formatting/formattingContext.ts", - "../services/formatting/formattingScanner.ts", - "../services/formatting/rule.ts", - "../services/formatting/rules.ts", - "../services/formatting/rulesMap.ts", - "../services/formatting/formatting.ts", - "../services/formatting/smartIndenter.ts", - "../services/textChanges.ts", - "../services/codeFixProvider.ts", - "../services/refactorProvider.ts", - "../services/codefixes/addMissingInvocationForDecorator.ts", - "../services/codefixes/annotateWithTypeFromJSDoc.ts", - "../services/codefixes/convertFunctionToEs6Class.ts", - "../services/codefixes/convertToAsyncFunction.ts", - "../services/codefixes/convertToEs6Module.ts", - "../services/codefixes/correctQualifiedNameToIndexedAccessType.ts", - "../services/codefixes/fixClassIncorrectlyImplementsInterface.ts", - "../services/codefixes/importFixes.ts", - "../services/codefixes/fixSpelling.ts", - "../services/codefixes/fixAddMissingMember.ts", - "../services/codefixes/fixCannotFindModule.ts", - "../services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts", - "../services/codefixes/fixClassSuperMustPrecedeThisAccess.ts", - "../services/codefixes/fixConstructorForDerivedNeedSuperCall.ts", - "../services/codefixes/fixExtendsInterfaceBecomesImplements.ts", - "../services/codefixes/fixForgottenThisPropertyAccess.ts", - "../services/codefixes/fixUnusedIdentifier.ts", - "../services/codefixes/fixUnreachableCode.ts", - "../services/codefixes/fixUnusedLabel.ts", - "../services/codefixes/fixJSDocTypes.ts", - "../services/codefixes/fixAwaitInSyncFunction.ts", - "../services/codefixes/disableJsDiagnostics.ts", - "../services/codefixes/helpers.ts", - "../services/codefixes/inferFromUsage.ts", - "../services/codefixes/fixInvalidImportSyntax.ts", - "../services/codefixes/fixStrictClassInitialization.ts", - "../services/codefixes/moduleSpecifiers.ts", - "../services/codefixes/requireInTs.ts", - "../services/codefixes/useDefaultImport.ts", - "../services/codefixes/fixAddModuleReferTypeMissingTypeof.ts", - "../services/refactors/extractSymbol.ts", - "../services/refactors/generateGetAccessorAndSetAccessor.ts", - "../services/refactors/moveToNewFile.ts", - "../services/sourcemaps.ts", - "../services/services.ts", - "../services/breakpoints.ts", - "../services/transform.ts", - "../services/shims.ts", - - "types.ts", - "shared.ts", - "utilities.ts", - "protocol.ts", - "scriptInfo.ts", - "typingsCache.ts", - "project.ts", - "editorServices.ts", - "session.ts", - "scriptVersionCache.ts" - ] -} From 1a4eb08d06be481ef1db6db1341e9840f5d97507 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 6 Aug 2018 11:25:19 -0700 Subject: [PATCH 164/196] Added seperate variables for synthesized uses and original uses, fixed lastDotThens, and cleaned up renames --- .../codefixes/convertToAsyncFunction.ts | 131 ++++++++---------- src/services/utilities.ts | 33 +++-- 2 files changed, 82 insertions(+), 82 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index cf680b54d9cf0..b06c2ee6c8fd1 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -14,7 +14,8 @@ namespace ts.codefix { interface SynthIdentifier { identifier: Identifier; - numberOfUses: number; + numberOfUsesOriginal: number; + numberOfUsesSynthesized: number; } class Transformer { @@ -23,12 +24,14 @@ namespace ts.codefix { lastDotThenMap: Map; context: CodeFixContextBase; constIdentifiers: Identifier[]; - constructor(_checker: TypeChecker, _synthNamesMap: Map, _lastDotThenMap: Map, _context: CodeFixContextBase, _constIdentifiers: Identifier[]) { + originalTypeMap: Map; + constructor(_checker: TypeChecker, _synthNamesMap: Map, _lastDotThenMap: Map, _context: CodeFixContextBase, _constIdentifiers: Identifier[], _originalTypeMap: Map) { this.checker = _checker; this.synthNamesMap = _synthNamesMap; this.lastDotThenMap = _lastDotThenMap; this.context = _context; this.constIdentifiers = _constIdentifiers; + this.originalTypeMap = _originalTypeMap; } } @@ -40,11 +43,12 @@ namespace ts.codefix { } const synthNamesMap: Map = createMap(); // number indicates the number of times it is used after declaration - const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context); - const lastDotThenMap = findLastDotThens(functionToConvertRenamed, checker); + const originalTypeMap: Map = createMap(); + const lastDotThenMap = findLastDotThens(functionToConvert, checker); + const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, lastDotThenMap, originalTypeMap); const constIdentifiers = getConstIdentifiers(synthNamesMap); const returnStatements = getReturnStatementsWithPromiseCallbacks(functionToConvertRenamed); - const transformer = new Transformer(checker, synthNamesMap, lastDotThenMap, context, constIdentifiers); + const transformer = new Transformer(checker, synthNamesMap, lastDotThenMap, context, constIdentifiers, originalTypeMap); if (!returnStatements.length) { return; @@ -93,7 +97,7 @@ namespace ts.codefix { function getConstIdentifiers(synthNamesMap: Map): Identifier[] { const constIdentifiers: Identifier[] = []; synthNamesMap.forEach((val) => { - if (val.numberOfUses === 1) { + if (val.numberOfUsesOriginal === 0) { constIdentifiers.push(val.identifier); } }); @@ -105,10 +109,6 @@ namespace ts.codefix { return createMap(); } - function willBeParsed(node: Expression): boolean { - let nodeType = checker.getTypeAtLocation(node); - return !!nodeType && (returnsAPromise(node, nodeType, checker) || (isCallExpression(node) && !!checker.getPromisedTypeOfPromise(nodeType) && (hasPropertyAccessExpressionWithName(node, "then") || hasPropertyAccessExpressionWithName(node, "catch")))); - } // maps nodes to boolean - true indicates that there is another .then() in the callback chain const lastDotThen: Map = createMap(); @@ -117,41 +117,29 @@ namespace ts.codefix { let nodeType = checker.getTypeAtLocation(node); if (isCallExpression(node) && nodeType && !!checker.getPromisedTypeOfPromise(nodeType) && hasPropertyAccessExpressionWithName(node, "then")) { // false - there is no following .then() in the callback chain - lastDotThen.set(getNodeId(node).toString(), false); + lastDotThen.set(getNodeId(node).toString(), false); - for (const arg of node.arguments) { - forEachChild(arg, function visitArg(argChild: Expression) { - if (willBeParsed(argChild)) { - // false - there is no following .then() in the callback chain - lastDotThen.set(getNodeId(argChild).toString(), false); - } - }); - } - forEachChild(node, function visit(child: Node) { - if (isExpression(child) && willBeParsed(child)) { - // true - there is a following .then() in the callback chain - lastDotThen.set(getNodeId(child).toString(), true); + forEachChild(node, function checkChildren(child: Node){ + forEachChild(child, checkChildren); - if (!isCallExpression(child)) { - return; - } + let nodeType = checker.getTypeAtLocation(child); + if (isCallExpression(child) && nodeType && !!checker.getPromisedTypeOfPromise(nodeType) && hasPropertyAccessExpressionWithName(child, "then") + && child.parent && !isPropertyAccessExpression(child.parent) && lastDotThen.get(getNodeId(child.parent).toString()) === false) { - for (const arg of child.arguments) { - forEachChild(arg, function visit(argChild: Expression) { - if (willBeParsed(argChild)) { - // true - there is a following .then() in the callback chain - lastDotThen.set(getNodeId(argChild).toString(), true); - } - }); - } + + // false - there is no following .then() in the callback chain + lastDotThen.set(getNodeId(child).toString(), false); } + else if(isCallExpression(child) || isIdentifier(child) && nodeType && checker.getPromisedTypeOfPromise(nodeType)) { - forEachChild(child, visit); + // true - there is a following .then() in the callback chain + lastDotThen.set(getNodeId(child).toString(), true); + } }); } else { - forEachChild(node, visit); + forEachChild(node, visit); } }); @@ -168,7 +156,7 @@ namespace ts.codefix { } // varNamesMap holds all of the variables in original source code. synthNamesMap holds all of the variables created by the refactor - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase): FunctionLikeDeclaration { + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, lastDotThenMap: Map, originalType: Map): FunctionLikeDeclaration { const allVarNames: [Identifier, Symbol][] = []; forEachChild(nodeToRename, function visit(node: Node) { @@ -183,27 +171,27 @@ namespace ts.codefix { if (type.getCallSignatures()[0].parameters.length && !synthNamesMap.get(getSymbolId(symbol).toString())) { // add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) const synthName = getNewNameIfConflict(createIdentifier(type.getCallSignatures()[0].parameters[0].name), allVarNames); - allVarNames.push([synthName.identifier, symbol]); synthNamesMap.set(getSymbolId(symbol).toString(), synthName); + allVarNames.push([synthName.identifier, symbol]); } } - else { + else if (node.parent && isParameter(node.parent) || isVariableDeclaration(node.parent)) { const newName = getNewNameIfConflict(node, allVarNames); let setName = false; for (const ident of allVarNames) { if (ident[0].text === node.text && ident[1] !== symbol) { - allVarNames.push([newName.identifier, symbol]); synthNamesMap.set(getSymbolId(symbol).toString(), newName); + allVarNames.push([newName.identifier, symbol]); setName = true; } } if (!setName) { - if (node.parent && isParameter(node.parent) || isVariableDeclaration(node.parent)) { + synthNamesMap.set(getSymbolId(symbol).toString(), { identifier: getSynthesizedDeepClone(node), numberOfUsesOriginal: allVarNames.filter(elem => elem[0].text === node.text).length, numberOfUsesSynthesized: 0 }); + if (node.parent && (isParameter(node.parent) && lastDotThenMap.get(getNodeId(node.parent.parent.parent).toString())) || isVariableDeclaration(node.parent)) { allVarNames.push([node, symbol]); } - synthNamesMap.set(getSymbolId(symbol).toString(), {identifier: getSynthesizedDeepClone(node), numberOfUses: allVarNames.filter(elem => elem[0].text === node.text).length}); } } } @@ -212,12 +200,12 @@ namespace ts.codefix { } }); - return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, synthNamesMap, checker); + return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, synthNamesMap, lastDotThenMap, checker, originalType); } function getNewNameIfConflict(name: Identifier, allVarNames: [Identifier, Symbol][]): SynthIdentifier { const numVarsSameName = allVarNames.filter(elem => elem[0].text === name.text).length; - return numVarsSameName === 0 ? {identifier: name, numberOfUses: 1} : {identifier: createIdentifier(name.text + "_" + numVarsSameName), numberOfUses: numVarsSameName}; + return numVarsSameName === 0 ? { identifier: name, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 } : { identifier: createIdentifier(name.text + "_" + numVarsSameName), numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; } function returnsAPromise(node: Expression, nodeType: Type, checker: TypeChecker): boolean { @@ -230,7 +218,10 @@ namespace ts.codefix { return []; } - const nodeType = transformer.checker.getTypeAtLocation(node); + let nodeType = transformer.checker.getTypeAtLocation(node); + if (nodeType && (nodeType).intrinsicName === "error" && isIdentifier(node)) { + nodeType = transformer.originalTypeMap.get(node.text); + } if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "then") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) { return parseThen(node, transformer, outermostParent, prevArgName); @@ -255,7 +246,7 @@ namespace ts.codefix { let varDecl; if (prevArgName && transformer.lastDotThenMap.get(getNodeId(node).toString())) { varDecl = createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); - prevArgName.numberOfUses += 2; + prevArgName.numberOfUsesOriginal += 2; } const tryBlock = createBlock(parseCallback(node.expression, transformer, node, prevArgName)); @@ -266,7 +257,7 @@ namespace ts.codefix { return varDecl ? [varDecl, tryStatement] : [tryStatement]; } - function parseThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression,prevArgName?: SynthIdentifier): Statement[] { + function parseThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] { const [res, rej] = node.arguments; if (!res) { @@ -302,36 +293,28 @@ namespace ts.codefix { const nextDotThen = transformer.lastDotThenMap.get(getNodeId(node).toString()); const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const originalNodeParent = node.original ? node.original.parent : node.parent; - if (hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { - - if (prevArgName!.numberOfUses > 1) { - prevArgName!.numberOfUses -= 1; - return [createStatement(createAssignment(getSynthesizedDeepClone(prevArgName!.identifier), createAwait(node)))]; - } - - const varDecl = createVariableDeclaration(getSynthesizedDeepClone(prevArgName!.identifier), /*type*/ undefined, createAwait(node)); - return [createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([varDecl], getFlagOfIdentifier(prevArgName!.identifier, transformer.constIdentifiers))))]; + if (hasPrevArgName && nextDotThen && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) { + return createVariableDeclarationOrAssignment(prevArgName!, createAwait(node), transformer).concat(); //hack to make the types match } - else if (!hasPrevArgName && nextDotThen && isPropertyAccessExpression(originalNodeParent)) { + else if (!hasPrevArgName && nextDotThen && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) { return [createStatement(createAwait(node))]; } return [createReturn(getSynthesizedDeepClone(node))]; } - function getCallbackBody(func: Node, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier, parent: CallExpression, transformer: Transformer): NodeArray { - - function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression): NodeArray { - if (prevArgName.numberOfUses > 1) { - prevArgName.numberOfUses -= 1; - debugger; - return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]); - } - - return createNodeArray([createVariableStatement(/*modifiers*/ undefined, - (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))]); + function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression, transformer: Transformer): NodeArray { + if (prevArgName.numberOfUsesSynthesized < prevArgName.numberOfUsesOriginal) { + prevArgName.numberOfUsesSynthesized += 1; + debugger; + return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]); } + return createNodeArray([createVariableStatement(/*modifiers*/ undefined, + (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))]); + } + + function getCallbackBody(func: Node, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier, parent: CallExpression, transformer: Transformer): NodeArray { const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const hasArgName = argName && argName.identifier.text.length > 0; @@ -351,7 +334,7 @@ namespace ts.codefix { break; } - return createVariableDeclarationOrAssignment(prevArgName!, createAwait(synthCall)); + return createVariableDeclarationOrAssignment(prevArgName!, createAwait(synthCall), transformer); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: @@ -383,7 +366,7 @@ namespace ts.codefix { } if (hasPrevArgName && nextDotThen) { - return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression); + return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression, transformer); } else { return createNodeArray([createReturn(getSynthesizedDeepClone(funcBody) as Expression)]); @@ -460,11 +443,11 @@ namespace ts.codefix { const symbol = getSymbol(originalNode); if (!symbol) { - return {identifier: node, numberOfUses: 1}; + return { identifier: node, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; } const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); - return mapEntry ? mapEntry : {identifier: node, numberOfUses: 1}; + return mapEntry ? mapEntry : { identifier: node, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; } function getSymbol(node: Node): Symbol | undefined { @@ -482,14 +465,14 @@ namespace ts.codefix { name = getMapEntryIfExists(param); } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { - name = {identifier: funcNode.arguments[0] as Identifier, numberOfUses: 1}; + name = { identifier: funcNode.arguments[0] as Identifier, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; } else if (isIdentifier(funcNode)) { name = getMapEntryIfExists(funcNode); } if (!name || name.identifier === undefined || name.identifier.text === "_" || name.identifier.text === "undefined") { - return {identifier: createIdentifier(""), numberOfUses: 1}; + return { identifier: createIdentifier(""), numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; } return name; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 13063c3001353..65c78bbd97561 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1630,23 +1630,40 @@ namespace ts { * WARNING: This is an expensive operation and is only intended to be used in refactorings * and code fixes (because those are triggered by explicit user actions). */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker): T { - const clone = renameMap && checker && needsRenaming(node, checker, renameMap) ? - node && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node!)!)))!.identifier : - node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, checker); + export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, nodeIdMap?: Map, checker?: TypeChecker, originalType?: Map): T { + + const nodeNeedsRenaming = renameMap && checker && needsRenaming(node, checker, renameMap); + const clone = nodeNeedsRenaming ? + node && createIdentifier(renameMap!.get(String(getSymbolId(checker!.getSymbolAtLocation(node!)!)))!.identifier.text) : + node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, nodeIdMap, checker, originalType); if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); + + if (nodeNeedsRenaming && originalType) { + let newIdentifier = renameMap!.get(String(getSymbolId(checker!.getSymbolAtLocation(node!)!)))!.identifier; + let type = checker!.getTypeAtLocation(newIdentifier); + if (type) { + originalType.set(newIdentifier.text, type); + } + } + + if (node && nodeIdMap && nodeIdMap.get(getNodeId(node!).toString()) !== undefined){ + let val = nodeIdMap.get(getNodeId(node!).toString()); + nodeIdMap.delete(getNodeId(node!).toString()); + nodeIdMap.set(getNodeId(clone).toString(), val!); + } + return clone as T; } function needsRenaming(node: T | undefined, checker: TypeChecker, renameMap: Map): boolean { - return !!(node && isIdentifier(node) && checker.getSymbolAtLocation(node) && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node)!)))) - && !checker.getTypeAtLocation(node)!.getCallSignatures().length; + return !!(node && isIdentifier(node) && checker.getSymbolAtLocation(node) && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node)!))) + && !checker.getTypeAtLocation(node)!.getCallSignatures().length); //never rename a function } - function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker): T { + function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, nodeIdMap?: Map, checker?: TypeChecker, originalType?: Map): T { const visited = visitEachChild(node, function wrapper(node) { - return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, checker); + return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, nodeIdMap, checker, originalType); }, nullTransformationContext); if (visited === node) { From de28298d8c07f43454625d1766fe7818ca7377f0 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 6 Aug 2018 11:26:00 -0700 Subject: [PATCH 165/196] Cleaned up baselines --- .../convertToAsyncFunction_Conditionals.js | 2 +- .../convertToAsyncFunction_Conditionals.ts | 2 +- .../convertToAsyncFunction_MultipleReturns1.ts | 4 ++-- .../convertToAsyncFunction_MultipleReturns2.ts | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.js index 2deabfabdba5b..637469938a749 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.js @@ -19,7 +19,7 @@ function /*[#|*/f/*|]*/(){ // ==ASYNC FUNCTION::Convert to async function== async function f(){ - let res = await fetch("https://typescriptlang.org"); + const res = await fetch("https://typescriptlang.org"); if (res.ok) { return fetch("https://microsoft.com"); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.ts index 2deabfabdba5b..637469938a749 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_Conditionals.ts @@ -19,7 +19,7 @@ function /*[#|*/f/*|]*/(){ // ==ASYNC FUNCTION::Convert to async function== async function f(){ - let res = await fetch("https://typescriptlang.org"); + const res = await fetch("https://typescriptlang.org"); if (res.ok) { return fetch("https://microsoft.com"); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts index 074091e33596c..736668c5b9696 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts @@ -15,8 +15,8 @@ function /*[#|*/f/*|]*/(): Promise { async function f(): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); if (x.ok) { - const res_1 = await fetch("https://typescriptlang.org"); - return console.log(res_1); + const res = await fetch("https://typescriptlang.org"); + return console.log(res); } const resp = await x; var blob = resp.blob().then(blob_1 => blob_1.byteOffset).catch(err => 'Error'); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts index 6569c1fb0ef99..85836e569ceee 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts @@ -16,11 +16,11 @@ function /*[#|*/f/*|]*/(): Promise { async function f(): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); if (x.ok) { - const res_1 = await fetch("https://typescriptlang.org"); - return console.log(res_1); + const res = await fetch("https://typescriptlang.org"); + return console.log(res); } const resp = await x; var blob = resp.blob().then(blob_1 => blob_1.byteOffset).catch(err => 'Error'); - const res_1 = await fetch("https://micorosft.com"); + const res = await fetch("https://micorosft.com"); return console.log("Another one!"); } From 32d9baea10af61570903eba6b74ef7b87bd511c2 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 6 Aug 2018 12:37:22 -0700 Subject: [PATCH 166/196] Addressed code review comments --- .../codefixes/convertToAsyncFunction.ts | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index b06c2ee6c8fd1..a0151ef6ceed0 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -59,8 +59,8 @@ namespace ts.codefix { const allNewNodes: Map = createMap(); - function startParse(node: CallExpression, nodeToReplace: Node) { - const newNodes = parseCallback(node, transformer, node); + function startTransformation(node: CallExpression, nodeToReplace: Node) { + const newNodes = transformCallback(node, transformer, node); if (newNodes.length) { allNewNodes.set(getNodeId(nodeToReplace).toString(), newNodes); } @@ -68,12 +68,12 @@ namespace ts.codefix { for (const statement of returnStatements) { if (isCallExpression(statement)) { - startParse(statement, statement); + startTransformation(statement, statement); } else { forEachChild(statement, function visit(node: Node) { if (isCallExpression(node)) { - startParse(node, statement); + startTransformation(node, statement); } else if (!isFunctionLike(node)) { forEachChild(node, visit); @@ -151,7 +151,7 @@ namespace ts.codefix { return !isCallExpression(callExpr) || callExpr.expression !== node; } - function definedInFile(symbol: Symbol, sourceFile: SourceFile): boolean { + function declaredInFile(symbol: Symbol, sourceFile: SourceFile): boolean { return symbol.valueDeclaration && symbol.valueDeclaration.getSourceFile() === sourceFile; } @@ -161,7 +161,7 @@ namespace ts.codefix { forEachChild(nodeToRename, function visit(node: Node) { const symbol = checker.getSymbolAtLocation(node); - const isDefinedInFile = symbol ? definedInFile(symbol, context.sourceFile) : undefined; + const isDefinedInFile = symbol ? declaredInFile(symbol, context.sourceFile) : undefined; if (isIdentifier(node) && symbol && isDefinedInFile) { const type = checker.getTypeAtLocation(node); @@ -213,7 +213,7 @@ namespace ts.codefix { } // dispatch function to recursively build the refactoring - function parseCallback(node: Expression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] { + function transformCallback(node: Expression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] { if (!node) { return []; } @@ -224,22 +224,22 @@ namespace ts.codefix { } if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "then") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) { - return parseThen(node, transformer, outermostParent, prevArgName); + return transformThen(node, transformer, outermostParent, prevArgName); } else if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "catch") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) { - return parseCatch(node, transformer, prevArgName); + return transformCatch(node, transformer, prevArgName); } else if (isPropertyAccessExpression(node)) { - return parseCallback(node.expression, transformer, outermostParent, prevArgName); + return transformCallback(node.expression, transformer, outermostParent, prevArgName); } else if (nodeType && returnsAPromise(node, nodeType, transformer.checker)) { - return parsePromiseCall(node, transformer, prevArgName); + return transformPromiseCall(node, transformer, prevArgName); } return []; } - function parseCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { + function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { const func = node.arguments[0]; const argName = getArgName(func, transformer); @@ -248,7 +248,7 @@ namespace ts.codefix { varDecl = createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); prevArgName.numberOfUsesOriginal += 2; } - const tryBlock = createBlock(parseCallback(node.expression, transformer, node, prevArgName)); + const tryBlock = createBlock(transformCallback(node.expression, transformer, node, prevArgName)); const callbackBody = getCallbackBody(func, prevArgName, argName, node, transformer); const catchClause = createCatchClause(argName.identifier.text, createBlock(callbackBody)); @@ -257,11 +257,11 @@ namespace ts.codefix { return varDecl ? [varDecl, tryStatement] : [tryStatement]; } - function parseThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] { + function transformThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] { const [res, rej] = node.arguments; if (!res) { - return parseCallback(node.expression, transformer, outermostParent); + return transformCallback(node.expression, transformer, outermostParent); } const argNameRes = getArgName(res, transformer); @@ -270,7 +270,7 @@ namespace ts.codefix { if (rej) { const argNameRej = getArgName(rej, transformer); - const tryBlock = createBlock(parseCallback(node.expression, transformer, node, argNameRes).concat(callbackBody)); + const tryBlock = createBlock(transformCallback(node.expression, transformer, node, argNameRes).concat(callbackBody)); const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, transformer); const catchClause = createCatchClause(argNameRej.identifier.text, createBlock(callbackBody2)); @@ -278,18 +278,18 @@ namespace ts.codefix { return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else { - return parseCallback(node.expression, transformer, node, argNameRes).concat(callbackBody); + return transformCallback(node.expression, transformer, node, argNameRes).concat(callbackBody); } return []; } function getFlagOfIdentifier(node: Identifier, constIdentifiers: Identifier[]): NodeFlags { - const inArr: boolean = constIdentifiers.filter(elem => elem.text === node.text).length > 0; + const inArr: boolean = constIdentifiers.some(elem => elem.text === node.text); return inArr ? NodeFlags.Const : NodeFlags.Let; } - function parsePromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { + function transformPromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { const nextDotThen = transformer.lastDotThenMap.get(getNodeId(node).toString()); const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const originalNodeParent = node.original ? node.original.parent : node.parent; @@ -346,7 +346,7 @@ namespace ts.codefix { for (let i = 0; i < func.body.statements.length; i++) { const statement = func.body.statements[i]; - if (indices.filter(elem => elem === i).length) { + if (indices.some(elem => elem === i)) { refactoredStmts = refactoredStmts.concat(getInnerCallbackBody(transformer, [statement], prevArgName)); } else { @@ -412,7 +412,7 @@ namespace ts.codefix { for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = parseCallback(node, transformer, node, prevArgName); + const temp = transformCallback(node, transformer, node, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; @@ -447,7 +447,7 @@ namespace ts.codefix { } const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); - return mapEntry ? mapEntry : { identifier: node, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; + return mapEntry || { identifier: node, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; } function getSymbol(node: Node): Symbol | undefined { @@ -460,9 +460,11 @@ namespace ts.codefix { let name: SynthIdentifier | undefined; - if (isFunctionLikeDeclaration(funcNode) && funcNode.parameters.length > 0) { - const param = funcNode.parameters[0].name as Identifier; - name = getMapEntryIfExists(param); + if (isFunctionLikeDeclaration(funcNode)) { + if (funcNode.parameters.length > 0) { + const param = funcNode.parameters[0].name as Identifier; + name = getMapEntryIfExists(param); + } } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { name = { identifier: funcNode.arguments[0] as Identifier, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; From 5c01d7b95c47d66dabe265cfef0dd6b4b832bb92 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 6 Aug 2018 14:33:27 -0700 Subject: [PATCH 167/196] refactored getSynthesizedDeepClone --- src/services/utilities.ts | 57 ++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 65c78bbd97561..93b013a9f9eda 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -222,7 +222,7 @@ namespace ts { export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } { return node.kind === SyntaxKind.Identifier && isBreakOrContinueStatement(node.parent) && node.parent.label === node; - } + } export function isLabelOfLabeledStatement(node: Node): node is Identifier { return node.kind === SyntaxKind.Identifier && isLabeledStatement(node.parent) && node.parent.label === node; @@ -391,7 +391,7 @@ namespace ts { export function isThis(node: Node): boolean { switch (node.kind) { case SyntaxKind.ThisKeyword: - // case SyntaxKind.ThisType: TODO: GH#9267 + // case SyntaxKind.ThisType: TODO: GH#9267 return true; case SyntaxKind.Identifier: // 'this' as a parameter @@ -1620,8 +1620,8 @@ namespace ts { return position; } - type RenamedIdentifier = { - identifier: Identifier; + interface RenamedIdentifier { + identifier: Identifier; } /** @@ -1631,40 +1631,47 @@ namespace ts { * and code fixes (because those are triggered by explicit user actions). */ export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, nodeIdMap?: Map, checker?: TypeChecker, originalType?: Map): T { - - const nodeNeedsRenaming = renameMap && checker && needsRenaming(node, checker, renameMap); - const clone = nodeNeedsRenaming ? - node && createIdentifier(renameMap!.get(String(getSymbolId(checker!.getSymbolAtLocation(node!)!)))!.identifier.text) : - node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, nodeIdMap, checker, originalType); - if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); + let clone; + if (node && isIdentifier(node!) && renameMap && checker) { + const type = checker.getTypeAtLocation(node!); + const symbol = checker.getSymbolAtLocation(node!); + const renameInfo = symbol && renameMap.get(String(getSymbolId(symbol))); + + if (renameInfo && type && !type.getCallSignatures().length) { // never rename a function + clone = createIdentifier(renameInfo.identifier.text); + } - if (nodeNeedsRenaming && originalType) { - let newIdentifier = renameMap!.get(String(getSymbolId(checker!.getSymbolAtLocation(node!)!)))!.identifier; - let type = checker!.getTypeAtLocation(newIdentifier); - if (type) { - originalType.set(newIdentifier.text, type); + if (renameInfo && originalType) { + const newIdentifier = renameMap.get(String(getSymbolId(symbol!)))!.identifier; + if (type) { + originalType.set(newIdentifier.text, type); + } } + } - if (node && nodeIdMap && nodeIdMap.get(getNodeId(node!).toString()) !== undefined){ - let val = nodeIdMap.get(getNodeId(node!).toString()); - nodeIdMap.delete(getNodeId(node!).toString()); - nodeIdMap.set(getNodeId(clone).toString(), val!); + if (!clone) { + clone = node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, nodeIdMap, checker, originalType); + } + if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); + + if (nodeIdMap) { + const val = nodeIdMap.get(getNodeId(node!).toString()); + if (val !== undefined) { + nodeIdMap.delete(getNodeId(node!).toString()); + nodeIdMap.set(getNodeId(clone).toString(), val); + } } return clone as T; } - function needsRenaming(node: T | undefined, checker: TypeChecker, renameMap: Map): boolean { - return !!(node && isIdentifier(node) && checker.getSymbolAtLocation(node) && renameMap.get(String(getSymbolId(checker.getSymbolAtLocation(node)!))) - && !checker.getTypeAtLocation(node)!.getCallSignatures().length); //never rename a function - } function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, nodeIdMap?: Map, checker?: TypeChecker, originalType?: Map): T { const visited = visitEachChild(node, function wrapper(node) { - return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, nodeIdMap, checker, originalType); - }, nullTransformationContext); + return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, nodeIdMap, checker, originalType); + }, nullTransformationContext); if (visited === node) { // This only happens for leaf nodes - internal nodes always see their children change. From 57b7ad2e25e471edaf31664909580f6e463cb119 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 6 Aug 2018 14:34:17 -0700 Subject: [PATCH 168/196] Fixed linting errors --- .../codefixes/convertToAsyncFunction.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index a0151ef6ceed0..6d4950b465c12 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -114,16 +114,16 @@ namespace ts.codefix { const lastDotThen: Map = createMap(); forEachChild(func.body, function visit(node: Node) { - let nodeType = checker.getTypeAtLocation(node); + const nodeType = checker.getTypeAtLocation(node); if (isCallExpression(node) && nodeType && !!checker.getPromisedTypeOfPromise(nodeType) && hasPropertyAccessExpressionWithName(node, "then")) { // false - there is no following .then() in the callback chain - lastDotThen.set(getNodeId(node).toString(), false); + lastDotThen.set(getNodeId(node).toString(), false); - forEachChild(node, function checkChildren(child: Node){ + forEachChild(node, function checkChildren(child: Node) { forEachChild(child, checkChildren); - let nodeType = checker.getTypeAtLocation(child); + const nodeType = checker.getTypeAtLocation(child); if (isCallExpression(child) && nodeType && !!checker.getPromisedTypeOfPromise(nodeType) && hasPropertyAccessExpressionWithName(child, "then") && child.parent && !isPropertyAccessExpression(child.parent) && lastDotThen.get(getNodeId(child.parent).toString()) === false) { @@ -131,7 +131,7 @@ namespace ts.codefix { // false - there is no following .then() in the callback chain lastDotThen.set(getNodeId(child).toString(), false); } - else if(isCallExpression(child) || isIdentifier(child) && nodeType && checker.getPromisedTypeOfPromise(nodeType)) { + else if (isCallExpression(child) || isIdentifier(child) && nodeType && checker.getPromisedTypeOfPromise(nodeType)) { // true - there is a following .then() in the callback chain lastDotThen.set(getNodeId(child).toString(), true); @@ -139,7 +139,7 @@ namespace ts.codefix { }); } else { - forEachChild(node, visit); + forEachChild(node, visit); } }); @@ -245,7 +245,7 @@ namespace ts.codefix { let varDecl; if (prevArgName && transformer.lastDotThenMap.get(getNodeId(node).toString())) { - varDecl = createVariableStatement(undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); + varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); prevArgName.numberOfUsesOriginal += 2; } const tryBlock = createBlock(transformCallback(node.expression, transformer, node, prevArgName)); @@ -294,7 +294,7 @@ namespace ts.codefix { const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const originalNodeParent = node.original ? node.original.parent : node.parent; if (hasPrevArgName && nextDotThen && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) { - return createVariableDeclarationOrAssignment(prevArgName!, createAwait(node), transformer).concat(); //hack to make the types match + return createVariableDeclarationOrAssignment(prevArgName!, createAwait(node), transformer).concat(); // hack to make the types match } else if (!hasPrevArgName && nextDotThen && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) { return [createStatement(createAwait(node))]; @@ -447,7 +447,7 @@ namespace ts.codefix { } const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); - return mapEntry || { identifier: node, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; + return mapEntry || { identifier: node, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; } function getSymbol(node: Node): Symbol | undefined { From 9d6a0e61777c1ee5076b8dc59206b9ac0bee748d Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Mon, 6 Aug 2018 16:25:20 -0700 Subject: [PATCH 169/196] Switched transformer to an interface instead of a class --- src/services/codefixes/convertToAsyncFunction.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 6d4950b465c12..ed72cd869149b 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -18,21 +18,13 @@ namespace ts.codefix { numberOfUsesSynthesized: number; } - class Transformer { + interface Transformer { checker: TypeChecker; synthNamesMap: Map; lastDotThenMap: Map; context: CodeFixContextBase; constIdentifiers: Identifier[]; originalTypeMap: Map; - constructor(_checker: TypeChecker, _synthNamesMap: Map, _lastDotThenMap: Map, _context: CodeFixContextBase, _constIdentifiers: Identifier[], _originalTypeMap: Map) { - this.checker = _checker; - this.synthNamesMap = _synthNamesMap; - this.lastDotThenMap = _lastDotThenMap; - this.context = _context; - this.constIdentifiers = _constIdentifiers; - this.originalTypeMap = _originalTypeMap; - } } function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker, context: CodeFixContextBase): void { @@ -48,7 +40,7 @@ namespace ts.codefix { const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, lastDotThenMap, originalTypeMap); const constIdentifiers = getConstIdentifiers(synthNamesMap); const returnStatements = getReturnStatementsWithPromiseCallbacks(functionToConvertRenamed); - const transformer = new Transformer(checker, synthNamesMap, lastDotThenMap, context, constIdentifiers, originalTypeMap); + const transformer = {checker, synthNamesMap, lastDotThenMap, context, constIdentifiers, originalTypeMap}; if (!returnStatements.length) { return; @@ -109,7 +101,6 @@ namespace ts.codefix { return createMap(); } - // maps nodes to boolean - true indicates that there is another .then() in the callback chain const lastDotThen: Map = createMap(); @@ -119,7 +110,6 @@ namespace ts.codefix { // false - there is no following .then() in the callback chain lastDotThen.set(getNodeId(node).toString(), false); - forEachChild(node, function checkChildren(child: Node) { forEachChild(child, checkChildren); @@ -127,7 +117,6 @@ namespace ts.codefix { if (isCallExpression(child) && nodeType && !!checker.getPromisedTypeOfPromise(nodeType) && hasPropertyAccessExpressionWithName(child, "then") && child.parent && !isPropertyAccessExpression(child.parent) && lastDotThen.get(getNodeId(child.parent).toString()) === false) { - // false - there is no following .then() in the callback chain lastDotThen.set(getNodeId(child).toString(), false); } From 8f9e306ee35b41fc07a32a329cabe79a01c4f20a Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 7 Aug 2018 11:03:11 -0700 Subject: [PATCH 170/196] refactored lastDotThen function to make more sense --- .../codefixes/convertToAsyncFunction.ts | 92 ++++++++++--------- 1 file changed, 50 insertions(+), 42 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index ed72cd869149b..cd747a0af934f 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -21,7 +21,7 @@ namespace ts.codefix { interface Transformer { checker: TypeChecker; synthNamesMap: Map; - lastDotThenMap: Map; + setOfCallbacksToReturn: Map; context: CodeFixContextBase; constIdentifiers: Identifier[]; originalTypeMap: Map; @@ -36,11 +36,11 @@ namespace ts.codefix { const synthNamesMap: Map = createMap(); // number indicates the number of times it is used after declaration const originalTypeMap: Map = createMap(); - const lastDotThenMap = findLastDotThens(functionToConvert, checker); - const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, lastDotThenMap, originalTypeMap); + const setOfCallbacksToReturn = getAllCallbacksToReturn(functionToConvert, checker); + const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfCallbacksToReturn, originalTypeMap); const constIdentifiers = getConstIdentifiers(synthNamesMap); const returnStatements = getReturnStatementsWithPromiseCallbacks(functionToConvertRenamed); - const transformer = {checker, synthNamesMap, lastDotThenMap, context, constIdentifiers, originalTypeMap}; + const transformer = { checker, synthNamesMap, setOfCallbacksToReturn, context, constIdentifiers, originalTypeMap }; if (!returnStatements.length) { return; @@ -96,43 +96,39 @@ namespace ts.codefix { return constIdentifiers; } - function findLastDotThens(func: FunctionLikeDeclaration, checker: TypeChecker): Map { + + function getAllCallbacksToReturn(func: FunctionLikeDeclaration, checker: TypeChecker): Map { if (!func.body) { - return createMap(); + return createMap(); } - // maps nodes to boolean - true indicates that there is another .then() in the callback chain - const lastDotThen: Map = createMap(); + const setOfCallbacksToReturn: Map = createMap(); forEachChild(func.body, function visit(node: Node) { - const nodeType = checker.getTypeAtLocation(node); - if (isCallExpression(node) && nodeType && !!checker.getPromisedTypeOfPromise(nodeType) && hasPropertyAccessExpressionWithName(node, "then")) { - // false - there is no following .then() in the callback chain - lastDotThen.set(getNodeId(node).toString(), false); - - forEachChild(node, function checkChildren(child: Node) { - forEachChild(child, checkChildren); - - const nodeType = checker.getTypeAtLocation(child); - if (isCallExpression(child) && nodeType && !!checker.getPromisedTypeOfPromise(nodeType) && hasPropertyAccessExpressionWithName(child, "then") - && child.parent && !isPropertyAccessExpression(child.parent) && lastDotThen.get(getNodeId(child.parent).toString()) === false) { - - // false - there is no following .then() in the callback chain - lastDotThen.set(getNodeId(child).toString(), false); - } - else if (isCallExpression(child) || isIdentifier(child) && nodeType && checker.getPromisedTypeOfPromise(nodeType)) { - - // true - there is a following .then() in the callback chain - lastDotThen.set(getNodeId(child).toString(), true); - } - }); + if (isLastCallbackOfName(node, checker, "then")) { + setOfCallbacksToReturn.set(getNodeId(node).toString(), true); + forEach((node).arguments, visit); + } + else if (isLastCallbackOfName(node, checker, "catch")) { + setOfCallbacksToReturn.set(getNodeId(node).toString(), true); + forEachChild(node, visit); // if .catch() is the last callback, move leftward in the callback chain until we hit a .then() + } + else if (isLastCallbackOfName(node, checker)) { + setOfCallbacksToReturn.set(getNodeId(node).toString(), true); } else { forEachChild(node, visit); } }); - return lastDotThen; + return setOfCallbacksToReturn; + } + + function isLastCallbackOfName(node: Node, checker: TypeChecker, name?: string): boolean { + const isCallback = isCallExpression(node); + const isCallbackOfName = isCallback && (!name || hasPropertyAccessExpressionWithName(node as CallExpression, name)); + const nodeType = isCallbackOfName && checker.getTypeAtLocation(node); + return !!(nodeType && checker.getPromisedTypeOfPromise(nodeType)); } function isFunctionRef(node: Node): boolean { @@ -145,7 +141,7 @@ namespace ts.codefix { } // varNamesMap holds all of the variables in original source code. synthNamesMap holds all of the variables created by the refactor - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, lastDotThenMap: Map, originalType: Map): FunctionLikeDeclaration { + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllCallbacksToReturn: Map, originalType: Map): FunctionLikeDeclaration { const allVarNames: [Identifier, Symbol][] = []; forEachChild(nodeToRename, function visit(node: Node) { @@ -178,7 +174,7 @@ namespace ts.codefix { if (!setName) { synthNamesMap.set(getSymbolId(symbol).toString(), { identifier: getSynthesizedDeepClone(node), numberOfUsesOriginal: allVarNames.filter(elem => elem[0].text === node.text).length, numberOfUsesSynthesized: 0 }); - if (node.parent && (isParameter(node.parent) && lastDotThenMap.get(getNodeId(node.parent.parent.parent).toString())) || isVariableDeclaration(node.parent)) { + if ((isParameter(node.parent) && isCallbackOnTypePromise(node.parent.parent, setOfAllCallbacksToReturn)) || isVariableDeclaration(node.parent)) { allVarNames.push([node, symbol]); } } @@ -189,7 +185,18 @@ namespace ts.codefix { } }); - return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, synthNamesMap, lastDotThenMap, checker, originalType); + function isCallbackOnTypePromise(child: Node, setOfAllCallbacksToReturn: Map): boolean { + let node = child.parent; + if (isCallExpression(node) || isIdentifier(node) && !setOfAllCallbacksToReturn.get(getNodeId(node).toString())) { + const nodeType = checker.getTypeAtLocation(node); + const isPromise = nodeType && checker.getPromisedTypeOfPromise(nodeType); + return !!isPromise; + } + + return false; + } + + return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, synthNamesMap, setOfAllCallbacksToReturn, checker, originalType); } function getNewNameIfConflict(name: Identifier, allVarNames: [Identifier, Symbol][]): SynthIdentifier { @@ -233,9 +240,9 @@ namespace ts.codefix { const argName = getArgName(func, transformer); let varDecl; - if (prevArgName && transformer.lastDotThenMap.get(getNodeId(node).toString())) { + if (prevArgName && !transformer.setOfCallbacksToReturn.get(getNodeId(node).toString())) { varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); - prevArgName.numberOfUsesOriginal += 2; + prevArgName.numberOfUsesOriginal += 3; // Try block, catch block, and initial assignment } const tryBlock = createBlock(transformCallback(node.expression, transformer, node, prevArgName)); @@ -279,13 +286,13 @@ namespace ts.codefix { } function transformPromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { - const nextDotThen = transformer.lastDotThenMap.get(getNodeId(node).toString()); + const shouldReturn = transformer.setOfCallbacksToReturn.get(getNodeId(node).toString()); const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const originalNodeParent = node.original ? node.original.parent : node.parent; - if (hasPrevArgName && nextDotThen && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) { + if (hasPrevArgName && !shouldReturn && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) { return createVariableDeclarationOrAssignment(prevArgName!, createAwait(node), transformer).concat(); // hack to make the types match } - else if (!hasPrevArgName && nextDotThen && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) { + else if (!hasPrevArgName && !shouldReturn && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) { return [createStatement(createAwait(node))]; } @@ -307,7 +314,7 @@ namespace ts.codefix { const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const hasArgName = argName && argName.identifier.text.length > 0; - const nextDotThen = transformer.lastDotThenMap.get(getNodeId(parent).toString()); + const shouldReturn = transformer.setOfCallbacksToReturn.get(getNodeId(parent).toString()); switch (func.kind) { case SyntaxKind.Identifier: if (!hasArgName) { @@ -315,7 +322,7 @@ namespace ts.codefix { } const synthCall = createCall(getSynthesizedDeepClone(func) as Identifier, /*typeArguments*/ undefined, [argName.identifier]); - if (!nextDotThen) { + if (shouldReturn) { return createNodeArray([createReturn(synthCall)]); } @@ -343,7 +350,8 @@ namespace ts.codefix { } } - return nextDotThen ? removeReturns(createNodeArray(refactoredStmts), prevArgName!.identifier, transformer.constIdentifiers) : getSynthesizedDeepClones(createNodeArray(refactoredStmts)); + return shouldReturn ? getSynthesizedDeepClones(createNodeArray(refactoredStmts)) : + removeReturns(createNodeArray(refactoredStmts), prevArgName!.identifier, transformer.constIdentifiers); } else { const funcBody = (func).body; @@ -354,7 +362,7 @@ namespace ts.codefix { return createNodeArray(innerCbBody); } - if (hasPrevArgName && nextDotThen) { + if (hasPrevArgName && !shouldReturn) { return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression, transformer); } else { From 16fc028aa445083d2e027e7ea1cadf8cb5dbc9a8 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 7 Aug 2018 12:54:02 -0700 Subject: [PATCH 171/196] Don't reuse variable declarations --- .../codefixes/convertToAsyncFunction.ts | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index cd747a0af934f..448a53ab39acc 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -21,6 +21,7 @@ namespace ts.codefix { interface Transformer { checker: TypeChecker; synthNamesMap: Map; + allVarNames: [Identifier, Symbol][]; setOfCallbacksToReturn: Map; context: CodeFixContextBase; constIdentifiers: Identifier[]; @@ -36,11 +37,12 @@ namespace ts.codefix { const synthNamesMap: Map = createMap(); // number indicates the number of times it is used after declaration const originalTypeMap: Map = createMap(); + const allVarNames: [Identifier, Symbol][] = []; const setOfCallbacksToReturn = getAllCallbacksToReturn(functionToConvert, checker); - const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfCallbacksToReturn, originalTypeMap); + const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfCallbacksToReturn, originalTypeMap, allVarNames); const constIdentifiers = getConstIdentifiers(synthNamesMap); const returnStatements = getReturnStatementsWithPromiseCallbacks(functionToConvertRenamed); - const transformer = { checker, synthNamesMap, setOfCallbacksToReturn, context, constIdentifiers, originalTypeMap }; + const transformer = { checker, synthNamesMap, allVarNames, setOfCallbacksToReturn, context, constIdentifiers, originalTypeMap }; if (!returnStatements.length) { return; @@ -105,15 +107,15 @@ namespace ts.codefix { const setOfCallbacksToReturn: Map = createMap(); forEachChild(func.body, function visit(node: Node) { - if (isLastCallbackOfName(node, checker, "then")) { + if (isCallbackOnPromiseOfName(node, checker, "then")) { setOfCallbacksToReturn.set(getNodeId(node).toString(), true); forEach((node).arguments, visit); } - else if (isLastCallbackOfName(node, checker, "catch")) { + else if (isCallbackOnPromiseOfName(node, checker, "catch")) { setOfCallbacksToReturn.set(getNodeId(node).toString(), true); forEachChild(node, visit); // if .catch() is the last callback, move leftward in the callback chain until we hit a .then() } - else if (isLastCallbackOfName(node, checker)) { + else if (isCallbackOnPromiseOfName(node, checker)) { setOfCallbacksToReturn.set(getNodeId(node).toString(), true); } else { @@ -124,7 +126,7 @@ namespace ts.codefix { return setOfCallbacksToReturn; } - function isLastCallbackOfName(node: Node, checker: TypeChecker, name?: string): boolean { + function isCallbackOnPromiseOfName(node: Node, checker: TypeChecker, name?: string): boolean { const isCallback = isCallExpression(node); const isCallbackOfName = isCallback && (!name || hasPropertyAccessExpressionWithName(node as CallExpression, name)); const nodeType = isCallbackOfName && checker.getTypeAtLocation(node); @@ -141,8 +143,7 @@ namespace ts.codefix { } // varNamesMap holds all of the variables in original source code. synthNamesMap holds all of the variables created by the refactor - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllCallbacksToReturn: Map, originalType: Map): FunctionLikeDeclaration { - const allVarNames: [Identifier, Symbol][] = []; + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllCallbacksToReturn: Map, originalType: Map, allVarNames: [Identifier, Symbol][]): FunctionLikeDeclaration { forEachChild(nodeToRename, function visit(node: Node) { const symbol = checker.getSymbolAtLocation(node); @@ -238,11 +239,17 @@ namespace ts.codefix { function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { const func = node.arguments[0]; const argName = getArgName(func, transformer); + const shouldReturn = transformer.setOfCallbacksToReturn.get(getNodeId(node).toString()); let varDecl; - if (prevArgName && !transformer.setOfCallbacksToReturn.get(getNodeId(node).toString())) { + if (prevArgName && !shouldReturn) { + prevArgName.numberOfUsesOriginal = 2; // Try block and catch block varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); - prevArgName.numberOfUsesOriginal += 3; // Try block, catch block, and initial assignment + transformer.synthNamesMap.forEach((val, key) => { + if (val.identifier.text === prevArgName.identifier.text) { + transformer.synthNamesMap.set(key, getNewNameIfConflict(prevArgName.identifier, transformer.allVarNames)) + } + }) } const tryBlock = createBlock(transformCallback(node.expression, transformer, node, prevArgName)); @@ -424,13 +431,11 @@ namespace ts.codefix { } function hasPropertyAccessExpressionWithName(node: CallExpression, funcName: string): boolean { - if (!isPropertyAccessExpression(node.expression)) { return false; } return node.expression.name.text === funcName; - } function getArgName(funcNode: Node, transformer: Transformer): SynthIdentifier { From d1d5e769bfef778a171dd953ed834d9abee2fc89 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 7 Aug 2018 12:57:28 -0700 Subject: [PATCH 172/196] Replace nodes immediately as you refactor --- .../codefixes/convertToAsyncFunction.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 448a53ab39acc..ed02be9407300 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -51,13 +51,9 @@ namespace ts.codefix { // add the async keyword changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert); - const allNewNodes: Map = createMap(); - function startTransformation(node: CallExpression, nodeToReplace: Node) { const newNodes = transformCallback(node, transformer, node); - if (newNodes.length) { - allNewNodes.set(getNodeId(nodeToReplace).toString(), newNodes); - } + changes.replaceNodeWithNodes(sourceFile, nodeToReplace, newNodes); } for (const statement of returnStatements) { @@ -75,17 +71,6 @@ namespace ts.codefix { }); } } - - replaceNodes(changes, sourceFile, returnStatements, allNewNodes); - } - - function replaceNodes(changes: textChanges.ChangeTracker, sourceFile: SourceFile, oldNodes: Node[], allNewNodes: Map) { - for (const statement of oldNodes) { - const newNodes = allNewNodes.get(getNodeId(statement).toString()); - if (newNodes) { - changes.replaceNodeWithNodes(sourceFile, statement, newNodes); - } - } } function getConstIdentifiers(synthNamesMap: Map): Identifier[] { From 794c52b5115f04f46598fa4b18a980a25af2bd39 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 7 Aug 2018 17:30:06 -0700 Subject: [PATCH 173/196] Added comments and addressed code review comments --- .../codefixes/convertToAsyncFunction.ts | 40 +++++++++++++------ src/services/utilities.ts | 16 ++++---- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index ed02be9407300..a6ea10d6361f3 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -12,10 +12,16 @@ namespace ts.codefix { }); + /* + custom type to encapsulate information for variable declarations synthesized in the refactor + numberOfUsesOriginal - number of times the variable should be assigned in the refactor + numberOfUsesSynthesized - count of how many times the variable has been assigned so far + At the end of the refactor, numberOfUsesOriginal should === numberOfUsesSynthesized + */ interface SynthIdentifier { identifier: Identifier; - numberOfUsesOriginal: number; - numberOfUsesSynthesized: number; + numberOfUsesOriginal: number; + numberOfUsesSynthesized: number; } interface Transformer { @@ -38,7 +44,7 @@ namespace ts.codefix { const synthNamesMap: Map = createMap(); // number indicates the number of times it is used after declaration const originalTypeMap: Map = createMap(); const allVarNames: [Identifier, Symbol][] = []; - const setOfCallbacksToReturn = getAllCallbacksToReturn(functionToConvert, checker); + const setOfCallbacksToReturn = getAllPromiseExpressionsToReturn(functionToConvert, checker); const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfCallbacksToReturn, originalTypeMap, allVarNames); const constIdentifiers = getConstIdentifiers(synthNamesMap); const returnStatements = getReturnStatementsWithPromiseCallbacks(functionToConvertRenamed); @@ -84,7 +90,10 @@ namespace ts.codefix { } - function getAllCallbacksToReturn(func: FunctionLikeDeclaration, checker: TypeChecker): Map { + /* + Finds all of the expressions of promise type that should not be saved in a variable during the refactor + */ + function getAllPromiseExpressionsToReturn(func: FunctionLikeDeclaration, checker: TypeChecker): Map { if (!func.body) { return createMap(); } @@ -92,16 +101,18 @@ namespace ts.codefix { const setOfCallbacksToReturn: Map = createMap(); forEachChild(func.body, function visit(node: Node) { - if (isCallbackOnPromiseOfName(node, checker, "then")) { + if (isPromiseReturningExpression(node, checker, "then")) { setOfCallbacksToReturn.set(getNodeId(node).toString(), true); forEach((node).arguments, visit); } - else if (isCallbackOnPromiseOfName(node, checker, "catch")) { + else if (isPromiseReturningExpression(node, checker, "catch")) { setOfCallbacksToReturn.set(getNodeId(node).toString(), true); - forEachChild(node, visit); // if .catch() is the last callback, move leftward in the callback chain until we hit a .then() + // if .catch() is the last callback, move leftward in the callback chain until we hit something else that should be returned + forEachChild(node, visit); } - else if (isCallbackOnPromiseOfName(node, checker)) { - setOfCallbacksToReturn.set(getNodeId(node).toString(), true); + else if (isPromiseReturningExpression(node, checker)) { + setOfCallbacksToReturn.set(getNodeId(node).toString(), true); + // don't recurse here, since we won't refactor any children or arguments of the expression } else { forEachChild(node, visit); @@ -111,8 +122,8 @@ namespace ts.codefix { return setOfCallbacksToReturn; } - function isCallbackOnPromiseOfName(node: Node, checker: TypeChecker, name?: string): boolean { - const isCallback = isCallExpression(node); + function isPromiseReturningExpression(node: Node, checker: TypeChecker, name?: string): boolean { + const isCallback = (name && isCallExpression(node)) || (!name && isExpression(node)); const isCallbackOfName = isCallback && (!name || hasPropertyAccessExpressionWithName(node as CallExpression, name)); const nodeType = isCallbackOfName && checker.getTypeAtLocation(node); return !!(nodeType && checker.getPromisedTypeOfPromise(nodeType)); @@ -130,6 +141,7 @@ namespace ts.codefix { // varNamesMap holds all of the variables in original source code. synthNamesMap holds all of the variables created by the refactor function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllCallbacksToReturn: Map, originalType: Map, allVarNames: [Identifier, Symbol][]): FunctionLikeDeclaration { + let identsToRenameMap: Map = createMap(); forEachChild(nodeToRename, function visit(node: Node) { const symbol = checker.getSymbolAtLocation(node); const isDefinedInFile = symbol ? declaredInFile(symbol, context.sourceFile) : undefined; @@ -152,6 +164,7 @@ namespace ts.codefix { for (const ident of allVarNames) { if (ident[0].text === node.text && ident[1] !== symbol) { + identsToRenameMap.set(getSymbolId(symbol).toString(), newName.identifier); synthNamesMap.set(getSymbolId(symbol).toString(), newName); allVarNames.push([newName.identifier, symbol]); setName = true; @@ -159,6 +172,7 @@ namespace ts.codefix { } if (!setName) { + identsToRenameMap.set(getSymbolId(symbol).toString(), getSynthesizedDeepClone(node)); synthNamesMap.set(getSymbolId(symbol).toString(), { identifier: getSynthesizedDeepClone(node), numberOfUsesOriginal: allVarNames.filter(elem => elem[0].text === node.text).length, numberOfUsesSynthesized: 0 }); if ((isParameter(node.parent) && isCallbackOnTypePromise(node.parent.parent, setOfAllCallbacksToReturn)) || isVariableDeclaration(node.parent)) { allVarNames.push([node, symbol]); @@ -182,7 +196,7 @@ namespace ts.codefix { return false; } - return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, synthNamesMap, setOfAllCallbacksToReturn, checker, originalType); + return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, setOfAllCallbacksToReturn, checker, originalType); } function getNewNameIfConflict(name: Identifier, allVarNames: [Identifier, Symbol][]): SynthIdentifier { @@ -294,10 +308,10 @@ namespace ts.codefix { function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression, transformer: Transformer): NodeArray { if (prevArgName.numberOfUsesSynthesized < prevArgName.numberOfUsesOriginal) { prevArgName.numberOfUsesSynthesized += 1; - debugger; return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]); } + prevArgName.numberOfUsesSynthesized += 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))]); } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 93b013a9f9eda..2fcf6c9e0a0cc 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1620,17 +1620,15 @@ namespace ts { return position; } - interface RenamedIdentifier { - identifier: Identifier; - } - /** * Creates a deep, memberwise clone of a node with no source map location. * * WARNING: This is an expensive operation and is only intended to be used in refactorings * and code fixes (because those are triggered by explicit user actions). */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, nodeIdMap?: Map, checker?: TypeChecker, originalType?: Map): T { + export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, nodeIdMap?: Map, checker?: TypeChecker, originalType?: Map): T { + + // TODO use a callback to update the nodeIdMap and originalType let clone; if (node && isIdentifier(node!) && renameMap && checker) { @@ -1638,12 +1636,12 @@ namespace ts { const symbol = checker.getSymbolAtLocation(node!); const renameInfo = symbol && renameMap.get(String(getSymbolId(symbol))); - if (renameInfo && type && !type.getCallSignatures().length) { // never rename a function - clone = createIdentifier(renameInfo.identifier.text); + if (renameInfo) { // never rename a function TODO: do this on the renameFunction side + clone = createIdentifier(renameInfo.text); } if (renameInfo && originalType) { - const newIdentifier = renameMap.get(String(getSymbolId(symbol!)))!.identifier; + const newIdentifier = renameMap.get(String(getSymbolId(symbol!)))!; if (type) { originalType.set(newIdentifier.text, type); } @@ -1668,7 +1666,7 @@ namespace ts { } - function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, nodeIdMap?: Map, checker?: TypeChecker, originalType?: Map): T { + function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, nodeIdMap?: Map, checker?: TypeChecker, originalType?: Map): T { const visited = visitEachChild(node, function wrapper(node) { return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, nodeIdMap, checker, originalType); }, nullTransformationContext); From 9bc4aa50f801e8da4fdf3e692a730b4bc7a50d90 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 7 Aug 2018 17:48:29 -0700 Subject: [PATCH 174/196] Fixed deep clone function to take a calback --- .../codefixes/convertToAsyncFunction.ts | 31 ++++++++++++++++--- src/services/utilities.ts | 31 +++++-------------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index a6ea10d6361f3..2929869104c9f 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -20,8 +20,8 @@ namespace ts.codefix { */ interface SynthIdentifier { identifier: Identifier; - numberOfUsesOriginal: number; - numberOfUsesSynthesized: number; + numberOfUsesOriginal: number; + numberOfUsesSynthesized: number; } interface Transformer { @@ -108,10 +108,10 @@ namespace ts.codefix { else if (isPromiseReturningExpression(node, checker, "catch")) { setOfCallbacksToReturn.set(getNodeId(node).toString(), true); // if .catch() is the last callback, move leftward in the callback chain until we hit something else that should be returned - forEachChild(node, visit); + forEachChild(node, visit); } else if (isPromiseReturningExpression(node, checker)) { - setOfCallbacksToReturn.set(getNodeId(node).toString(), true); + setOfCallbacksToReturn.set(getNodeId(node).toString(), true); // don't recurse here, since we won't refactor any children or arguments of the expression } else { @@ -196,7 +196,28 @@ namespace ts.codefix { return false; } - return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, setOfAllCallbacksToReturn, checker, originalType); + function deepCloneCallback(node: Node, clone: Node) { + const type = checker.getTypeAtLocation(node); + const symbol = checker.getSymbolAtLocation(node); + const renameInfo = symbol && identsToRenameMap.get(String(getSymbolId(symbol))); + + if (renameInfo && originalType) { + const newIdentifier = identsToRenameMap.get(String(getSymbolId(symbol!)))!; + if (type) { + originalType.set(newIdentifier.text, type); + } + } + + if (setOfAllCallbacksToReturn) { + const val = setOfAllCallbacksToReturn.get(getNodeId(node!).toString()); + if (val !== undefined) { + setOfAllCallbacksToReturn.delete(getNodeId(node!).toString()); + setOfAllCallbacksToReturn.set(getNodeId(clone).toString(), val); + } + } + } + + return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, /*setOfAllCallbacksToReturn,*/ checker, /*originalType*/ deepCloneCallback); } function getNewNameIfConflict(name: Identifier, allVarNames: [Identifier, Symbol][]): SynthIdentifier { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 2fcf6c9e0a0cc..72060a5b901f8 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1626,49 +1626,32 @@ namespace ts { * WARNING: This is an expensive operation and is only intended to be used in refactorings * and code fixes (because those are triggered by explicit user actions). */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, nodeIdMap?: Map, checker?: TypeChecker, originalType?: Map): T { - - // TODO use a callback to update the nodeIdMap and originalType + export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any/*nodeIdMap?: Map, originalType?: Map*/): T { let clone; if (node && isIdentifier(node!) && renameMap && checker) { - const type = checker.getTypeAtLocation(node!); const symbol = checker.getSymbolAtLocation(node!); const renameInfo = symbol && renameMap.get(String(getSymbolId(symbol))); - if (renameInfo) { // never rename a function TODO: do this on the renameFunction side + if (renameInfo) { clone = createIdentifier(renameInfo.text); } - - if (renameInfo && originalType) { - const newIdentifier = renameMap.get(String(getSymbolId(symbol!)))!; - if (type) { - originalType.set(newIdentifier.text, type); - } - } - } if (!clone) { - clone = node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, nodeIdMap, checker, originalType); + clone = node && getSynthesizedDeepCloneWorker(node as NonNullable, renameMap, checker, callback); } - if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); - if (nodeIdMap) { - const val = nodeIdMap.get(getNodeId(node!).toString()); - if (val !== undefined) { - nodeIdMap.delete(getNodeId(node!).toString()); - nodeIdMap.set(getNodeId(clone).toString(), val); - } - } + if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); + if (callback && node) callback(node!, clone); return clone as T; } - function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, nodeIdMap?: Map, checker?: TypeChecker, originalType?: Map): T { + function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T { const visited = visitEachChild(node, function wrapper(node) { - return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, nodeIdMap, checker, originalType); + return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, checker, callback); }, nullTransformationContext); if (visited === node) { From e8a6fb909cbc7158b6fd19321f5f41565089d15b Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Tue, 7 Aug 2018 18:07:06 -0700 Subject: [PATCH 175/196] Addressed more code review comments - removed tuples --- .../codefixes/convertToAsyncFunction.ts | 27 +++++++++++-------- src/services/suggestionDiagnostics.ts | 6 ++--- src/services/types.ts | 2 +- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 2929869104c9f..324eeed556526 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -24,10 +24,15 @@ namespace ts.codefix { numberOfUsesSynthesized: number; } + interface SymbolAndIdentifier { + identifier: Identifier; + symbol: Symbol; + } + interface Transformer { checker: TypeChecker; synthNamesMap: Map; - allVarNames: [Identifier, Symbol][]; + allVarNames: SymbolAndIdentifier[]; setOfCallbacksToReturn: Map; context: CodeFixContextBase; constIdentifiers: Identifier[]; @@ -43,7 +48,7 @@ namespace ts.codefix { const synthNamesMap: Map = createMap(); // number indicates the number of times it is used after declaration const originalTypeMap: Map = createMap(); - const allVarNames: [Identifier, Symbol][] = []; + const allVarNames: SymbolAndIdentifier[] = []; const setOfCallbacksToReturn = getAllPromiseExpressionsToReturn(functionToConvert, checker); const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfCallbacksToReturn, originalTypeMap, allVarNames); const constIdentifiers = getConstIdentifiers(synthNamesMap); @@ -139,7 +144,7 @@ namespace ts.codefix { } // varNamesMap holds all of the variables in original source code. synthNamesMap holds all of the variables created by the refactor - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllCallbacksToReturn: Map, originalType: Map, allVarNames: [Identifier, Symbol][]): FunctionLikeDeclaration { + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllCallbacksToReturn: Map, originalType: Map, allVarNames: SymbolAndIdentifier[]): FunctionLikeDeclaration { let identsToRenameMap: Map = createMap(); forEachChild(nodeToRename, function visit(node: Node) { @@ -155,7 +160,7 @@ namespace ts.codefix { // add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) const synthName = getNewNameIfConflict(createIdentifier(type.getCallSignatures()[0].parameters[0].name), allVarNames); synthNamesMap.set(getSymbolId(symbol).toString(), synthName); - allVarNames.push([synthName.identifier, symbol]); + allVarNames.push({identifier: synthName.identifier, symbol}); } } else if (node.parent && isParameter(node.parent) || isVariableDeclaration(node.parent)) { @@ -163,19 +168,19 @@ namespace ts.codefix { let setName = false; for (const ident of allVarNames) { - if (ident[0].text === node.text && ident[1] !== symbol) { + if (ident.identifier.text === node.text && ident.symbol !== symbol) { identsToRenameMap.set(getSymbolId(symbol).toString(), newName.identifier); synthNamesMap.set(getSymbolId(symbol).toString(), newName); - allVarNames.push([newName.identifier, symbol]); + allVarNames.push({identifier: newName.identifier, symbol}); setName = true; } } if (!setName) { identsToRenameMap.set(getSymbolId(symbol).toString(), getSynthesizedDeepClone(node)); - synthNamesMap.set(getSymbolId(symbol).toString(), { identifier: getSynthesizedDeepClone(node), numberOfUsesOriginal: allVarNames.filter(elem => elem[0].text === node.text).length, numberOfUsesSynthesized: 0 }); + synthNamesMap.set(getSymbolId(symbol).toString(), { identifier: getSynthesizedDeepClone(node), numberOfUsesOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfUsesSynthesized: 0 }); if ((isParameter(node.parent) && isCallbackOnTypePromise(node.parent.parent, setOfAllCallbacksToReturn)) || isVariableDeclaration(node.parent)) { - allVarNames.push([node, symbol]); + allVarNames.push({identifier: node, symbol}); } } } @@ -220,8 +225,8 @@ namespace ts.codefix { return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, /*setOfAllCallbacksToReturn,*/ checker, /*originalType*/ deepCloneCallback); } - function getNewNameIfConflict(name: Identifier, allVarNames: [Identifier, Symbol][]): SynthIdentifier { - const numVarsSameName = allVarNames.filter(elem => elem[0].text === name.text).length; + function getNewNameIfConflict(name: Identifier, allVarNames: SymbolAndIdentifier[]): SynthIdentifier { + const numVarsSameName = allVarNames.filter(elem => elem.identifier.text === name.text).length; return numVarsSameName === 0 ? { identifier: name, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 } : { identifier: createIdentifier(name.text + "_" + numVarsSameName), numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; } @@ -236,7 +241,7 @@ namespace ts.codefix { } let nodeType = transformer.checker.getTypeAtLocation(node); - if (nodeType && (nodeType).intrinsicName === "error" && isIdentifier(node)) { + if (nodeType && nodeType.flags & 1 && (nodeType).intrinsicName === "error" && isIdentifier(node)) { nodeType = transformer.originalTypeMap.get(node.text); } diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 4b23862aba858..307ba403393bf 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -120,7 +120,7 @@ namespace ts { return; } - const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call); + const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call); const returnType = callSignatures.length ? checker.getReturnTypeOfSignature(callSignatures[0]) : undefined; if (!returnType || !checker.getPromisedTypeOfPromise(returnType)) { @@ -155,10 +155,10 @@ namespace ts { } if (isReturnStatement(child)) { - forEachChild(child, hasCallback); + forEachChild(child, addCallbacks); } - function hasCallback(returnChild: Node) { + function addCallbacks(returnChild: Node) { if (isCallback(returnChild)) { returnStatements.push(child as ReturnStatement); } diff --git a/src/services/types.ts b/src/services/types.ts index 5dc4d36540a31..be897f20cd43c 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -169,7 +169,7 @@ namespace ts { fileName: Path; packageName: string; } - + // // Public interface of the host of a language service instance. // From 55d4cade517afa8b4413007adabe83e9bdd97116 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 09:53:07 -0700 Subject: [PATCH 176/196] Addressed more code review comments - removed unecessary helper function --- .../codefixes/convertToAsyncFunction.ts | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 324eeed556526..a8fd7463ddae1 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -20,8 +20,8 @@ namespace ts.codefix { */ interface SynthIdentifier { identifier: Identifier; - numberOfUsesOriginal: number; - numberOfUsesSynthesized: number; + numberOfAssignmentsOriginal: number; + numberOfAssignmentsSynthesized: number; } interface SymbolAndIdentifier { @@ -87,7 +87,7 @@ namespace ts.codefix { function getConstIdentifiers(synthNamesMap: Map): Identifier[] { const constIdentifiers: Identifier[] = []; synthNamesMap.forEach((val) => { - if (val.numberOfUsesOriginal === 0) { + if (val.numberOfAssignmentsOriginal === 0) { constIdentifiers.push(val.identifier); } }); @@ -178,7 +178,7 @@ namespace ts.codefix { if (!setName) { identsToRenameMap.set(getSymbolId(symbol).toString(), getSynthesizedDeepClone(node)); - synthNamesMap.set(getSymbolId(symbol).toString(), { identifier: getSynthesizedDeepClone(node), numberOfUsesOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfUsesSynthesized: 0 }); + synthNamesMap.set(getSymbolId(symbol).toString(), { identifier: getSynthesizedDeepClone(node), numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); if ((isParameter(node.parent) && isCallbackOnTypePromise(node.parent.parent, setOfAllCallbacksToReturn)) || isVariableDeclaration(node.parent)) { allVarNames.push({identifier: node, symbol}); } @@ -227,11 +227,7 @@ namespace ts.codefix { function getNewNameIfConflict(name: Identifier, allVarNames: SymbolAndIdentifier[]): SynthIdentifier { const numVarsSameName = allVarNames.filter(elem => elem.identifier.text === name.text).length; - return numVarsSameName === 0 ? { identifier: name, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 } : { identifier: createIdentifier(name.text + "_" + numVarsSameName), numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; - } - - function returnsAPromise(node: Expression, nodeType: Type, checker: TypeChecker): boolean { - return (!isCallExpression(node) || !hasPropertyAccessExpressionWithName(node, "then") && !hasPropertyAccessExpressionWithName(node, "catch")) && !!checker.getPromisedTypeOfPromise(nodeType); + return numVarsSameName === 0 ? { identifier: name, numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 } : { identifier: createIdentifier(name.text + "_" + numVarsSameName), numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 }; } // dispatch function to recursively build the refactoring @@ -254,7 +250,7 @@ namespace ts.codefix { else if (isPropertyAccessExpression(node)) { return transformCallback(node.expression, transformer, outermostParent, prevArgName); } - else if (nodeType && returnsAPromise(node, nodeType, transformer.checker)) { + else if (nodeType && transformer.checker.getPromisedTypeOfPromise(nodeType)) { return transformPromiseCall(node, transformer, prevArgName); } @@ -268,7 +264,7 @@ namespace ts.codefix { let varDecl; if (prevArgName && !shouldReturn) { - prevArgName.numberOfUsesOriginal = 2; // Try block and catch block + prevArgName.numberOfAssignmentsOriginal = 2; // Try block and catch block varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); transformer.synthNamesMap.forEach((val, key) => { if (val.identifier.text === prevArgName.identifier.text) { @@ -332,12 +328,12 @@ namespace ts.codefix { } function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression, transformer: Transformer): NodeArray { - if (prevArgName.numberOfUsesSynthesized < prevArgName.numberOfUsesOriginal) { - prevArgName.numberOfUsesSynthesized += 1; + if (prevArgName.numberOfAssignmentsSynthesized < prevArgName.numberOfAssignmentsOriginal) { + prevArgName.numberOfAssignmentsSynthesized += 1; return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]); } - prevArgName.numberOfUsesSynthesized += 1; + prevArgName.numberOfAssignmentsSynthesized += 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))]); } @@ -470,11 +466,11 @@ namespace ts.codefix { const symbol = getSymbol(originalNode); if (!symbol) { - return { identifier: node, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; + return { identifier: node, numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 }; } const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); - return mapEntry || { identifier: node, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; + return mapEntry || { identifier: node, numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 }; } function getSymbol(node: Node): Symbol | undefined { @@ -494,14 +490,14 @@ namespace ts.codefix { } } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { - name = { identifier: funcNode.arguments[0] as Identifier, numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; + name = { identifier: funcNode.arguments[0] as Identifier, numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 }; } else if (isIdentifier(funcNode)) { name = getMapEntryIfExists(funcNode); } if (!name || name.identifier === undefined || name.identifier.text === "_" || name.identifier.text === "undefined") { - return { identifier: createIdentifier(""), numberOfUsesOriginal: 0, numberOfUsesSynthesized: 0 }; + return { identifier: createIdentifier(""), numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 }; } return name; From 1cfee778bc5a3902a84a000b907175971d14034e Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 10:16:51 -0700 Subject: [PATCH 177/196] Refactored renameCollidingVarNames to be mroe readable --- .../codefixes/convertToAsyncFunction.ts | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index a8fd7463ddae1..b2a54243bafa9 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -26,7 +26,7 @@ namespace ts.codefix { interface SymbolAndIdentifier { identifier: Identifier; - symbol: Symbol; + symbol: Symbol; } interface Transformer { @@ -143,51 +143,54 @@ namespace ts.codefix { return symbol.valueDeclaration && symbol.valueDeclaration.getSourceFile() === sourceFile; } - // varNamesMap holds all of the variables in original source code. synthNamesMap holds all of the variables created by the refactor + /* + Renaming of identifiers may be neccesary as the refactor changes scopes - + This function collects all existing identifier names and names of identifiers that will be created in the refactor. + It then checks for any collisions and renames them through getSynthesizedDeepClone + */ function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllCallbacksToReturn: Map, originalType: Map, allVarNames: SymbolAndIdentifier[]): FunctionLikeDeclaration { let identsToRenameMap: Map = createMap(); forEachChild(nodeToRename, function visit(node: Node) { + if (!isIdentifier(node)) { + forEachChild(node, visit); + return; + } + const symbol = checker.getSymbolAtLocation(node); - const isDefinedInFile = symbol ? declaredInFile(symbol, context.sourceFile) : undefined; + const isDefinedInFile = symbol && declaredInFile(symbol, context.sourceFile); - if (isIdentifier(node) && symbol && isDefinedInFile) { + if (symbol && isDefinedInFile) { const type = checker.getTypeAtLocation(node); + const callSignatures = type && type.getCallSignatures(); + const symbolIdString = getSymbolId(symbol).toString(); // if the identifier refers to a function - if (type && type.getCallSignatures().length > 0 && isFunctionRef(node)) { - if (type.getCallSignatures()[0].parameters.length && !synthNamesMap.get(getSymbolId(symbol).toString())) { + if (callSignatures && callSignatures.length > 0 && isFunctionRef(node)) { + if (callSignatures[0].parameters.length && !synthNamesMap.has(symbolIdString)) { // add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) - const synthName = getNewNameIfConflict(createIdentifier(type.getCallSignatures()[0].parameters[0].name), allVarNames); - synthNamesMap.set(getSymbolId(symbol).toString(), synthName); - allVarNames.push({identifier: synthName.identifier, symbol}); + const synthName = getNewNameIfConflict(createIdentifier(callSignatures[0].parameters[0].name), allVarNames); + synthNamesMap.set(symbolIdString, synthName); + allVarNames.push({ identifier: synthName.identifier, symbol }); } } - else if (node.parent && isParameter(node.parent) || isVariableDeclaration(node.parent)) { - const newName = getNewNameIfConflict(node, allVarNames); - let setName = false; - - for (const ident of allVarNames) { - if (ident.identifier.text === node.text && ident.symbol !== symbol) { - identsToRenameMap.set(getSymbolId(symbol).toString(), newName.identifier); - synthNamesMap.set(getSymbolId(symbol).toString(), newName); - allVarNames.push({identifier: newName.identifier, symbol}); - setName = true; - } - } + else if (node.parent && (isParameter(node.parent) || isVariableDeclaration(node.parent))) { - if (!setName) { - identsToRenameMap.set(getSymbolId(symbol).toString(), getSynthesizedDeepClone(node)); - synthNamesMap.set(getSymbolId(symbol).toString(), { identifier: getSynthesizedDeepClone(node), numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); + if (allVarNames.some(ident => ident.identifier.text === node.text && ident.symbol !== symbol)) { + const newName = getNewNameIfConflict(node, allVarNames); + identsToRenameMap.set(symbolIdString, newName.identifier); + synthNamesMap.set(symbolIdString, newName); + allVarNames.push({ identifier: newName.identifier, symbol }); + } + else { + identsToRenameMap.set(symbolIdString, getSynthesizedDeepClone(node)); + synthNamesMap.set(symbolIdString, { identifier: getSynthesizedDeepClone(node), numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); if ((isParameter(node.parent) && isCallbackOnTypePromise(node.parent.parent, setOfAllCallbacksToReturn)) || isVariableDeclaration(node.parent)) { - allVarNames.push({identifier: node, symbol}); + allVarNames.push({ identifier: node as Identifier, symbol }); } } } } - else { - forEachChild(node, visit); - } }); function isCallbackOnTypePromise(child: Node, setOfAllCallbacksToReturn: Map): boolean { @@ -202,10 +205,10 @@ namespace ts.codefix { } function deepCloneCallback(node: Node, clone: Node) { - const type = checker.getTypeAtLocation(node); - const symbol = checker.getSymbolAtLocation(node); - const renameInfo = symbol && identsToRenameMap.get(String(getSymbolId(symbol))); - + const type = checker.getTypeAtLocation(node); + const symbol = checker.getSymbolAtLocation(node); + const renameInfo = symbol && identsToRenameMap.get(String(getSymbolId(symbol))); + if (renameInfo && originalType) { const newIdentifier = identsToRenameMap.get(String(getSymbolId(symbol!)))!; if (type) { From a17738d2988656ff0e5bd7ca1a0ef104272b4188 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 10:44:39 -0700 Subject: [PATCH 178/196] More refactoring on renameCollidingVariables to make it more readable --- .../codefixes/convertToAsyncFunction.ts | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index b2a54243bafa9..57f1372b1b3a3 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -134,11 +134,6 @@ namespace ts.codefix { return !!(nodeType && checker.getPromisedTypeOfPromise(nodeType)); } - function isFunctionRef(node: Node): boolean { - const callExpr = climbPastPropertyAccess(node); - return !isCallExpression(callExpr) || callExpr.expression !== node; - } - function declaredInFile(symbol: Symbol, sourceFile: SourceFile): boolean { return symbol.valueDeclaration && symbol.valueDeclaration.getSourceFile() === sourceFile; } @@ -165,17 +160,17 @@ namespace ts.codefix { const callSignatures = type && type.getCallSignatures(); const symbolIdString = getSymbolId(symbol).toString(); - // if the identifier refers to a function - if (callSignatures && callSignatures.length > 0 && isFunctionRef(node)) { - if (callSignatures[0].parameters.length && !synthNamesMap.has(symbolIdString)) { - // add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) - const synthName = getNewNameIfConflict(createIdentifier(callSignatures[0].parameters[0].name), allVarNames); - synthNamesMap.set(symbolIdString, synthName); - allVarNames.push({ identifier: synthName.identifier, symbol }); - } + // if the identifier refers to a function we want to add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) + // Note - the choice of the first call signature is arbitrary + if (callSignatures && callSignatures.length > 0 && callSignatures[0].parameters.length && !synthNamesMap.has(symbolIdString)) { + const synthName = getNewNameIfConflict(createIdentifier(callSignatures[0].parameters[0].name), allVarNames); + synthNamesMap.set(symbolIdString, synthName); + allVarNames.push({ identifier: synthName.identifier, symbol }); } + // we only care about identifiers that are parameters and declarations (don't care about other uses) else if (node.parent && (isParameter(node.parent) || isVariableDeclaration(node.parent))) { + // if the identifier name conflicts with a different identifier that we've already seen if (allVarNames.some(ident => ident.identifier.text === node.text && ident.symbol !== symbol)) { const newName = getNewNameIfConflict(node, allVarNames); identsToRenameMap.set(symbolIdString, newName.identifier); @@ -183,10 +178,11 @@ namespace ts.codefix { allVarNames.push({ identifier: newName.identifier, symbol }); } else { - identsToRenameMap.set(symbolIdString, getSynthesizedDeepClone(node)); - synthNamesMap.set(symbolIdString, { identifier: getSynthesizedDeepClone(node), numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); + const identifier = getSynthesizedDeepClone(node); + identsToRenameMap.set(symbolIdString, identifier); + synthNamesMap.set(symbolIdString, { identifier, numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); if ((isParameter(node.parent) && isCallbackOnTypePromise(node.parent.parent, setOfAllCallbacksToReturn)) || isVariableDeclaration(node.parent)) { - allVarNames.push({ identifier: node as Identifier, symbol }); + allVarNames.push({ identifier, symbol }); } } } From 0f4d97c5b86ac3a5e318a3a2b1ff9e00b25936b8 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 11:35:43 -0700 Subject: [PATCH 179/196] Initial commit of synthesizing a type for code paths --- .../codefixes/convertToAsyncFunction.ts | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 57f1372b1b3a3..8f7a9212bd736 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -20,6 +20,7 @@ namespace ts.codefix { */ interface SynthIdentifier { identifier: Identifier; + type: Type | undefined; numberOfAssignmentsOriginal: number; numberOfAssignmentsSynthesized: number; } @@ -180,7 +181,7 @@ namespace ts.codefix { else { const identifier = getSynthesizedDeepClone(node); identsToRenameMap.set(symbolIdString, identifier); - synthNamesMap.set(symbolIdString, { identifier, numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); + synthNamesMap.set(symbolIdString, { identifier, type: undefined, numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); if ((isParameter(node.parent) && isCallbackOnTypePromise(node.parent.parent, setOfAllCallbacksToReturn)) || isVariableDeclaration(node.parent)) { allVarNames.push({ identifier, symbol }); } @@ -221,12 +222,15 @@ namespace ts.codefix { } } - return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, /*setOfAllCallbacksToReturn,*/ checker, /*originalType*/ deepCloneCallback); + return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, checker, deepCloneCallback); } function getNewNameIfConflict(name: Identifier, allVarNames: SymbolAndIdentifier[]): SynthIdentifier { const numVarsSameName = allVarNames.filter(elem => elem.identifier.text === name.text).length; - return numVarsSameName === 0 ? { identifier: name, numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 } : { identifier: createIdentifier(name.text + "_" + numVarsSameName), numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 }; + const numberOfAssignmentsOriginal = 0, numberOfAssignmentsSynthesized = 0; + const type = undefined; + const identifier = numVarsSameName == 0 ? name : createIdentifier(name.text + "_" + numVarsSameName); + return { identifier: identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized } ; } // dispatch function to recursively build the refactoring @@ -261,23 +265,44 @@ namespace ts.codefix { const argName = getArgName(func, transformer); const shouldReturn = transformer.setOfCallbacksToReturn.get(getNodeId(node).toString()); - let varDecl; + /* + 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) { prevArgName.numberOfAssignmentsOriginal = 2; // Try block and catch block - varDecl = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier))], NodeFlags.Let)); transformer.synthNamesMap.forEach((val, key) => { if (val.identifier.text === prevArgName.identifier.text) { transformer.synthNamesMap.set(key, getNewNameIfConflict(prevArgName.identifier, transformer.allVarNames)) } }) } + const tryBlock = createBlock(transformCallback(node.expression, transformer, node, prevArgName)); + const tryType = prevArgName && prevArgName.type; const callbackBody = getCallbackBody(func, prevArgName, argName, node, transformer); + const catchType = prevArgName && prevArgName.type; const catchClause = createCatchClause(argName.identifier.text, createBlock(callbackBody)); + + + /* + 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; + if (prevArgName && !shouldReturn) { + let typeArray: Type[] = []; + if (tryType) typeArray.push(tryType); + if (catchType) typeArray.push(catchType); + const unionType = transformer.checker.getUnionType(typeArray, UnionReduction.Subtype); + const unionTypeNode = isInJavaScriptFile(node) ? transformer.checker.typeToTypeNode(unionType) : undefined; + const varDecl = [createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), unionTypeNode)] + varDeclList = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList(varDecl, NodeFlags.Let)); + } const tryStatement = createTry(tryBlock, catchClause, /*finallyBlock*/ undefined); - return varDecl ? [varDecl, tryStatement] : [tryStatement]; + return varDeclList ? [varDeclList, tryStatement] : [tryStatement]; } function transformThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] { @@ -327,6 +352,8 @@ namespace ts.codefix { } function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression, transformer: Transformer): NodeArray { + prevArgName.type = transformer.checker.getTypeAtLocation(rightHandSide); + if (prevArgName.numberOfAssignmentsSynthesized < prevArgName.numberOfAssignmentsOriginal) { prevArgName.numberOfAssignmentsSynthesized += 1; return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]); @@ -460,16 +487,21 @@ namespace ts.codefix { function getArgName(funcNode: Node, transformer: Transformer): SynthIdentifier { + const numberOfAssignmentsOriginal = 0, numberOfAssignmentsSynthesized = 0; + const type = undefined; + function getMapEntryIfExists(node: Identifier): SynthIdentifier { const originalNode = getOriginalNode(node); const symbol = getSymbol(originalNode); + const identifier = node; + if (!symbol) { - return { identifier: node, numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 }; + return { identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized}; } const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); - return mapEntry || { identifier: node, numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 }; + return mapEntry || { identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } function getSymbol(node: Node): Symbol | undefined { @@ -489,14 +521,14 @@ namespace ts.codefix { } } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { - name = { identifier: funcNode.arguments[0] as Identifier, numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 }; + name = { identifier: funcNode.arguments[0] as Identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } else if (isIdentifier(funcNode)) { name = getMapEntryIfExists(funcNode); } if (!name || name.identifier === undefined || name.identifier.text === "_" || name.identifier.text === "undefined") { - return { identifier: createIdentifier(""), numberOfAssignmentsOriginal: 0, numberOfAssignmentsSynthesized: 0 }; + return { identifier: createIdentifier(""), type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } return name; From 52b7c9646effedfb1a3bc2fd44485c57176a6eee Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 12:20:49 -0700 Subject: [PATCH 180/196] Addressed code review comments - removed all instances of the incorrect word callback --- .../codefixes/convertToAsyncFunction.ts | 116 +++++++++--------- src/services/suggestionDiagnostics.ts | 12 +- 2 files changed, 65 insertions(+), 63 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 8f7a9212bd736..cafeb00706fb3 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -32,12 +32,13 @@ namespace ts.codefix { interface Transformer { checker: TypeChecker; - synthNamesMap: Map; + synthNamesMap: Map; // keys are the symbol id of the identifier allVarNames: SymbolAndIdentifier[]; - setOfCallbacksToReturn: Map; + setOfExpressionsToReturn: Map; // keys are the node ids of the expressions context: CodeFixContextBase; constIdentifiers: Identifier[]; - originalTypeMap: Map; + originalTypeMap: Map; // keys are the identifier's text + isInJSFile: boolean; } function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker, context: CodeFixContextBase): void { @@ -50,11 +51,12 @@ namespace ts.codefix { const synthNamesMap: Map = createMap(); // number indicates the number of times it is used after declaration const originalTypeMap: Map = createMap(); const allVarNames: SymbolAndIdentifier[] = []; - const setOfCallbacksToReturn = getAllPromiseExpressionsToReturn(functionToConvert, checker); - const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfCallbacksToReturn, originalTypeMap, allVarNames); + const isInJSFile = isInJavaScriptFile(functionToConvert); + const setOfExpressionsToReturn = getAllPromiseExpressionsToReturn(functionToConvert, checker); + const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfExpressionsToReturn, originalTypeMap, allVarNames); const constIdentifiers = getConstIdentifiers(synthNamesMap); - const returnStatements = getReturnStatementsWithPromiseCallbacks(functionToConvertRenamed); - const transformer = { checker, synthNamesMap, allVarNames, setOfCallbacksToReturn, context, constIdentifiers, originalTypeMap }; + const returnStatements = getReturnStatementsWithPromiseHandlers(functionToConvertRenamed); + const transformer = { checker, synthNamesMap, allVarNames, setOfExpressionsToReturn, context, constIdentifiers, originalTypeMap, isInJSFile }; if (!returnStatements.length) { return; @@ -64,7 +66,7 @@ namespace ts.codefix { changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert); function startTransformation(node: CallExpression, nodeToReplace: Node) { - const newNodes = transformCallback(node, transformer, node); + const newNodes = transformExpression(node, transformer, node); changes.replaceNodeWithNodes(sourceFile, nodeToReplace, newNodes); } @@ -85,6 +87,7 @@ namespace ts.codefix { } } + // Returns the identifiers that are never reassigned in the refactor function getConstIdentifiers(synthNamesMap: Map): Identifier[] { const constIdentifiers: Identifier[] = []; synthNamesMap.forEach((val) => { @@ -104,20 +107,20 @@ namespace ts.codefix { return createMap(); } - const setOfCallbacksToReturn: Map = createMap(); + const setOfExpressionsToReturn: Map = createMap(); forEachChild(func.body, function visit(node: Node) { if (isPromiseReturningExpression(node, checker, "then")) { - setOfCallbacksToReturn.set(getNodeId(node).toString(), true); + setOfExpressionsToReturn.set(getNodeId(node).toString(), true); forEach((node).arguments, visit); } else if (isPromiseReturningExpression(node, checker, "catch")) { - setOfCallbacksToReturn.set(getNodeId(node).toString(), true); - // if .catch() is the last callback, move leftward in the callback chain until we hit something else that should be returned + setOfExpressionsToReturn.set(getNodeId(node).toString(), true); + // if .catch() 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 (isPromiseReturningExpression(node, checker)) { - setOfCallbacksToReturn.set(getNodeId(node).toString(), true); + setOfExpressionsToReturn.set(getNodeId(node).toString(), true); // don't recurse here, since we won't refactor any children or arguments of the expression } else { @@ -125,13 +128,13 @@ namespace ts.codefix { } }); - return setOfCallbacksToReturn; + return setOfExpressionsToReturn; } function isPromiseReturningExpression(node: Node, checker: TypeChecker, name?: string): boolean { - const isCallback = (name && isCallExpression(node)) || (!name && isExpression(node)); - const isCallbackOfName = isCallback && (!name || hasPropertyAccessExpressionWithName(node as CallExpression, name)); - const nodeType = isCallbackOfName && checker.getTypeAtLocation(node); + const isNodeExpression = name ? isCallExpression(node) : isExpression(node); + const isExpressionOfName = isNodeExpression && (!name || hasPropertyAccessExpressionWithName(node as CallExpression, name)); + const nodeType = isExpressionOfName && checker.getTypeAtLocation(node); return !!(nodeType && checker.getPromisedTypeOfPromise(nodeType)); } @@ -144,7 +147,7 @@ namespace ts.codefix { This function collects all existing identifier names and names of identifiers that will be created in the refactor. It then checks for any collisions and renames them through getSynthesizedDeepClone */ - function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllCallbacksToReturn: Map, originalType: Map, allVarNames: SymbolAndIdentifier[]): FunctionLikeDeclaration { + function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllExpressionsToReturn: Map, originalType: Map, allVarNames: SymbolAndIdentifier[]): FunctionLikeDeclaration { let identsToRenameMap: Map = createMap(); forEachChild(nodeToRename, function visit(node: Node) { @@ -182,7 +185,7 @@ namespace ts.codefix { const identifier = getSynthesizedDeepClone(node); identsToRenameMap.set(symbolIdString, identifier); synthNamesMap.set(symbolIdString, { identifier, type: undefined, numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); - if ((isParameter(node.parent) && isCallbackOnTypePromise(node.parent.parent, setOfAllCallbacksToReturn)) || isVariableDeclaration(node.parent)) { + if ((isParameter(node.parent) && isExpressionOrCallOnTypePromise(node.parent.parent)) || isVariableDeclaration(node.parent)) { allVarNames.push({ identifier, symbol }); } } @@ -190,9 +193,11 @@ namespace ts.codefix { } }); - function isCallbackOnTypePromise(child: Node, setOfAllCallbacksToReturn: Map): boolean { + return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, checker, deepCloneCallback); + + function isExpressionOrCallOnTypePromise(child: Node): boolean { let node = child.parent; - if (isCallExpression(node) || isIdentifier(node) && !setOfAllCallbacksToReturn.get(getNodeId(node).toString())) { + if (isCallExpression(node) || isIdentifier(node) && !setOfAllExpressionsToReturn.get(getNodeId(node).toString())) { const nodeType = checker.getTypeAtLocation(node); const isPromise = nodeType && checker.getPromisedTypeOfPromise(nodeType); return !!isPromise; @@ -202,27 +207,25 @@ namespace ts.codefix { } function deepCloneCallback(node: Node, clone: Node) { - const type = checker.getTypeAtLocation(node); const symbol = checker.getSymbolAtLocation(node); - const renameInfo = symbol && identsToRenameMap.get(String(getSymbolId(symbol))); + const symboldIdString = symbol && getSymbolId(symbol).toString(); + const renameInfo = symbol && identsToRenameMap.get(symboldIdString!); - if (renameInfo && originalType) { - const newIdentifier = identsToRenameMap.get(String(getSymbolId(symbol!)))!; + if (renameInfo) { + const type = checker.getTypeAtLocation(node); + const newIdentifier = identsToRenameMap.get(symboldIdString!)!; if (type) { originalType.set(newIdentifier.text, type); } } - if (setOfAllCallbacksToReturn) { - const val = setOfAllCallbacksToReturn.get(getNodeId(node!).toString()); - if (val !== undefined) { - setOfAllCallbacksToReturn.delete(getNodeId(node!).toString()); - setOfAllCallbacksToReturn.set(getNodeId(clone).toString(), val); - } + const val = setOfAllExpressionsToReturn.get(getNodeId(node!).toString()); + if (val !== undefined) { + setOfAllExpressionsToReturn.delete(getNodeId(node!).toString()); + setOfAllExpressionsToReturn.set(getNodeId(clone).toString(), val); } } - return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, checker, deepCloneCallback); } function getNewNameIfConflict(name: Identifier, allVarNames: SymbolAndIdentifier[]): SynthIdentifier { @@ -234,7 +237,7 @@ namespace ts.codefix { } // dispatch function to recursively build the refactoring - function transformCallback(node: Expression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] { + function transformExpression(node: Expression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] { if (!node) { return []; } @@ -251,7 +254,7 @@ namespace ts.codefix { return transformCatch(node, transformer, prevArgName); } else if (isPropertyAccessExpression(node)) { - return transformCallback(node.expression, transformer, outermostParent, prevArgName); + return transformExpression(node.expression, transformer, outermostParent, prevArgName); } else if (nodeType && transformer.checker.getPromisedTypeOfPromise(nodeType)) { return transformPromiseCall(node, transformer, prevArgName); @@ -263,7 +266,7 @@ namespace ts.codefix { function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { const func = node.arguments[0]; const argName = getArgName(func, transformer); - const shouldReturn = transformer.setOfCallbacksToReturn.get(getNodeId(node).toString()); + const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString()); /* 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) @@ -279,14 +282,13 @@ namespace ts.codefix { }) } - const tryBlock = createBlock(transformCallback(node.expression, transformer, node, prevArgName)); + const tryBlock = createBlock(transformExpression(node.expression, transformer, node, prevArgName)); const tryType = prevArgName && prevArgName.type; - const callbackBody = getCallbackBody(func, prevArgName, argName, node, transformer); + const transformationBody = getTransformationBody(func, prevArgName, argName, node, transformer); const catchType = prevArgName && prevArgName.type; - const catchClause = createCatchClause(argName.identifier.text, createBlock(callbackBody)); + const catchClause = createCatchClause(argName.identifier.text, 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) */ @@ -296,7 +298,7 @@ namespace ts.codefix { if (tryType) typeArray.push(tryType); if (catchType) typeArray.push(catchType); const unionType = transformer.checker.getUnionType(typeArray, UnionReduction.Subtype); - const unionTypeNode = isInJavaScriptFile(node) ? transformer.checker.typeToTypeNode(unionType) : undefined; + const unionTypeNode = transformer.isInJSFile ? undefined : transformer.checker.typeToTypeNode(unionType); const varDecl = [createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), unionTypeNode)] varDeclList = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList(varDecl, NodeFlags.Let)); } @@ -309,24 +311,24 @@ namespace ts.codefix { const [res, rej] = node.arguments; if (!res) { - return transformCallback(node.expression, transformer, outermostParent); + return transformExpression(node.expression, transformer, outermostParent); } const argNameRes = getArgName(res, transformer); - const callbackBody = getCallbackBody(res, prevArgName, argNameRes, node, transformer); + const transformationBody = getTransformationBody(res, prevArgName, argNameRes, node, transformer); if (rej) { const argNameRej = getArgName(rej, transformer); - const tryBlock = createBlock(transformCallback(node.expression, transformer, node, argNameRes).concat(callbackBody)); + const tryBlock = createBlock(transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody)); - const callbackBody2 = getCallbackBody(rej, prevArgName, argNameRej, node, transformer); - const catchClause = createCatchClause(argNameRej.identifier.text, createBlock(callbackBody2)); + const transformationBody2 = getTransformationBody(rej, prevArgName, argNameRej, node, transformer); + const catchClause = createCatchClause(argNameRej.identifier.text, createBlock(transformationBody2)); return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; } else { - return transformCallback(node.expression, transformer, node, argNameRes).concat(callbackBody); + return transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody); } return []; @@ -338,7 +340,7 @@ namespace ts.codefix { } function transformPromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { - const shouldReturn = transformer.setOfCallbacksToReturn.get(getNodeId(node).toString()); + const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString()); const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const originalNodeParent = node.original ? node.original.parent : node.parent; if (hasPrevArgName && !shouldReturn && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) { @@ -364,11 +366,11 @@ namespace ts.codefix { (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))]); } - function getCallbackBody(func: Node, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier, parent: CallExpression, transformer: Transformer): NodeArray { + function getTransformationBody(func: Node, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier, parent: CallExpression, transformer: Transformer): NodeArray { const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const hasArgName = argName && argName.identifier.text.length > 0; - const shouldReturn = transformer.setOfCallbacksToReturn.get(getNodeId(parent).toString()); + const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(parent).toString()); switch (func.kind) { case SyntaxKind.Identifier: if (!hasArgName) { @@ -391,13 +393,13 @@ namespace ts.codefix { case SyntaxKind.ArrowFunction: // Arrow functions with block bodies { } will enter this control flow if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - const indices = getReturnStatementsWithPromiseCallbacksIndices(func.body); + const indices = getReturnStatementsWithPromiseHandlersIndices(func.body); let refactoredStmts: Statement[] = []; for (let i = 0; i < func.body.statements.length; i++) { const statement = func.body.statements[i]; if (indices.some(elem => elem === i)) { - refactoredStmts = refactoredStmts.concat(getInnerCallbackBody(transformer, [statement], prevArgName)); + refactoredStmts = refactoredStmts.concat(getInnerTransformationBody(transformer, [statement], prevArgName)); } else { refactoredStmts.push(statement); @@ -409,8 +411,8 @@ namespace ts.codefix { } else { const funcBody = (func).body; - const innerRetStmts = getReturnStatementsWithPromiseCallbacks(createReturn(funcBody as Expression)); - const innerCbBody = getInnerCallbackBody(transformer, innerRetStmts, prevArgName); + const innerRetStmts = getReturnStatementsWithPromiseHandlers(createReturn(funcBody as Expression)); + const innerCbBody = getInnerTransformationBody(transformer, innerRetStmts, prevArgName); if (innerCbBody.length > 0) { return createNodeArray(innerCbBody); @@ -428,11 +430,11 @@ namespace ts.codefix { return createNodeArray([]); } - function getReturnStatementsWithPromiseCallbacksIndices(block: Block): number[] { + function getReturnStatementsWithPromiseHandlersIndices(block: Block): number[] { const indices: number[] = []; for (let i = 0; i < block.statements.length; i++) { const statement = block.statements[i]; - if (getReturnStatementsWithPromiseCallbacks(statement).length) { + if (getReturnStatementsWithPromiseHandlers(statement).length) { indices.push(i); } } @@ -457,13 +459,13 @@ namespace ts.codefix { } - function getInnerCallbackBody(transformer: Transformer, innerRetStmts: Node[], prevArgName?: SynthIdentifier) { + function getInnerTransformationBody(transformer: Transformer, innerRetStmts: Node[], prevArgName?: SynthIdentifier) { let innerCbBody: Statement[] = []; for (const stmt of innerRetStmts) { forEachChild(stmt, function visit(node: Node) { if (isCallExpression(node)) { - const temp = transformCallback(node, transformer, node, prevArgName); + const temp = transformExpression(node, transformer, node, prevArgName); innerCbBody = innerCbBody.concat(temp); if (innerCbBody.length > 0) { return; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 307ba403393bf..51a1701c351dd 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -129,7 +129,7 @@ namespace ts { // collect all the return statements // check that a property access expression exists in there and that it is a handler - const returnStatements = getReturnStatementsWithPromiseCallbacks(node); + const returnStatements = getReturnStatementsWithPromiseHandlers(node); if (returnStatements.length > 0) { diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function)); } @@ -140,7 +140,7 @@ namespace ts { } /** @internal */ - export function getReturnStatementsWithPromiseCallbacks(node: Node): Node[] { + export function getReturnStatementsWithPromiseHandlers(node: Node): Node[] { const returnStatements: Node[] = []; if (isFunctionLike(node)) { forEachChild(node, visit); @@ -155,11 +155,11 @@ namespace ts { } if (isReturnStatement(child)) { - forEachChild(child, addCallbacks); + forEachChild(child, addHandlers); } - function addCallbacks(returnChild: Node) { - if (isCallback(returnChild)) { + function addHandlers(returnChild: Node) { + if (isPromiseHandler(returnChild)) { returnStatements.push(child as ReturnStatement); } } @@ -169,7 +169,7 @@ namespace ts { return returnStatements; } - function isCallback(node: Node): boolean { + function isPromiseHandler(node: Node): boolean { return (isCallExpression(node) && isPropertyAccessExpression(node.expression) && (node.expression.name.text === "then" || node.expression.name.text === "catch")); } From bb6b8695174268dc6b1c8d87a5cca6fade185f2c Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 15:33:28 -0700 Subject: [PATCH 181/196] Second commit of synthesized type --- .../codefixes/convertToAsyncFunction.ts | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index cafeb00706fb3..f1666313ffb53 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -48,7 +48,7 @@ namespace ts.codefix { return; } - const synthNamesMap: Map = createMap(); // number indicates the number of times it is used after declaration + const synthNamesMap: Map = createMap(); const originalTypeMap: Map = createMap(); const allVarNames: SymbolAndIdentifier[] = []; const isInJSFile = isInJavaScriptFile(functionToConvert); @@ -209,13 +209,15 @@ namespace ts.codefix { function deepCloneCallback(node: Node, clone: Node) { const symbol = checker.getSymbolAtLocation(node); const symboldIdString = symbol && getSymbolId(symbol).toString(); - const renameInfo = symbol && identsToRenameMap.get(symboldIdString!); + const renameInfo = symbol && synthNamesMap.get(symboldIdString!); if (renameInfo) { const type = checker.getTypeAtLocation(node); - const newIdentifier = identsToRenameMap.get(symboldIdString!)!; if (type) { - originalType.set(newIdentifier.text, type); + originalType.set(renameInfo.identifier.text, type); + if (isIdentifier(node)) { + originalType.set(node.text, type); + } } } @@ -233,7 +235,7 @@ namespace ts.codefix { const numberOfAssignmentsOriginal = 0, numberOfAssignmentsSynthesized = 0; const type = undefined; const identifier = numVarsSameName == 0 ? name : createIdentifier(name.text + "_" + numVarsSameName); - return { identifier: identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized } ; + return { identifier: identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } // dispatch function to recursively build the refactoring @@ -288,7 +290,7 @@ namespace ts.codefix { const transformationBody = getTransformationBody(func, prevArgName, argName, node, transformer); const catchType = prevArgName && prevArgName.type; const catchClause = createCatchClause(argName.identifier.text, 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) */ @@ -354,7 +356,6 @@ namespace ts.codefix { } function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression, transformer: Transformer): NodeArray { - prevArgName.type = transformer.checker.getTypeAtLocation(rightHandSide); if (prevArgName.numberOfAssignmentsSynthesized < prevArgName.numberOfAssignmentsOriginal) { prevArgName.numberOfAssignmentsSynthesized += 1; @@ -386,6 +387,10 @@ namespace ts.codefix { break; } + const type = transformer.originalTypeMap.get((func).text); + const callSignatures = type && transformer.checker.getSignaturesOfType(type, SignatureKind.Call); + const returnType = callSignatures && callSignatures[0].getReturnType(); + prevArgName!.type = returnType; return createVariableDeclarationOrAssignment(prevArgName!, createAwait(synthCall), transformer); case SyntaxKind.FunctionDeclaration: @@ -419,6 +424,10 @@ namespace ts.codefix { } if (hasPrevArgName && !shouldReturn) { + const type = transformer.checker.getTypeAtLocation(func); + const callSignatures = type && transformer.checker.getSignaturesOfType(type, SignatureKind.Call); + const returnType = callSignatures && callSignatures[0].getReturnType(); + prevArgName!.type = returnType; return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression, transformer); } else { @@ -496,10 +505,10 @@ namespace ts.codefix { const originalNode = getOriginalNode(node); const symbol = getSymbol(originalNode); const identifier = node; - + if (!symbol) { - return { identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized}; + return { identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); From f41da8082a9b430a1eaccc47febc12d18f67d52b Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 15:34:11 -0700 Subject: [PATCH 182/196] Added tests and baselines --- .../unittests/convertToAsyncFunction.ts | 61 ++++++++++++++++++- ...vertToAsyncFunction_CatchFollowedByThen.js | 6 +- ...vertToAsyncFunction_CatchFollowedByThen.ts | 8 +-- ...tion_CatchFollowedByThenMatchingTypes01.ts | 35 +++++++++++ ...tion_CatchFollowedByThenMatchingTypes02.ts | 27 ++++++++ .../convertToAsyncFunction_InnerPromiseRet.ts | 2 +- ...convertToAsyncFunction_MultipleReturns1.ts | 4 +- ...convertToAsyncFunction_MultipleReturns2.ts | 6 +- 8 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index aff7d685c6b42..6e6e2f02cb1fb 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -460,8 +460,9 @@ function [#|f|](): Promise{ }`); _testConvertToAsyncFunction("convertToAsyncFunction_basicWithComments", ` function [#|f|](): Promise{ - // here's a comment - return fetch('https://typescriptlang.org').then( /* another one! */ result => { /* comment */ console.log(result) }); + // a + /*b*/ return /*c*/ fetch( /*d*/ 'https://typescriptlang.org' /*e*/).then( /*f*/ result /*g*/ => { /*h*/ console.log(/*i*/ result /*j*/) /*k*/}/*l*/); + // m }`); _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunction", ` @@ -959,6 +960,62 @@ function rej(reject){ ` ); +_testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes01", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result): number { + return 5; +} + +function rej(reject): number { + return 3; +} +` + ); + +_testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res => 0).catch(rej => 1).then(res); +} + +function res(result): number { + return 5; +} +` + ); + + + _testConvertToAsyncFunction("convertToAsyncFunction_LocalReturn", ` +function [#|f|]() { + let x = fetch("https://typescriptlang.org").then(res => console.log(res)); + return x.catch(err => console.log("Error!", err)); +} + +`) + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseCallInner", ` +function [#|f|]() { + return fetch(Promise.resolve(1).then(res => "https://typescriptlang.org")).catch(err => console.log(err)); +} + +`) +_testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchFollowedByCall", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res).catch(rej).toString(); +} + +function res(result){ + return result; +} + +function rej(reject){ + return reject; +} +` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_Scope2", ` function [#|f|](){ var i:number; diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js index 15cfa7e4fab3a..43d41d3fc11de 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js @@ -17,11 +17,11 @@ function rej(reject){ async function f(){ let result; try { - result = await fetch("https://typescriptlang.org"); - result = await res(result); + let result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); } catch (reject) { - const result = await rej(reject); + result = await rej(reject); } return res(result); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts index 15cfa7e4fab3a..3b1ef68267594 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts @@ -15,13 +15,13 @@ function rej(reject){ // ==ASYNC FUNCTION::Convert to async function== async function f(){ - let result; + let result: any; try { - result = await fetch("https://typescriptlang.org"); - result = await res(result); + let result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); } catch (reject) { - const result = await rej(reject); + result = await rej(reject); } return res(result); } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01.ts new file mode 100644 index 0000000000000..65051d026d3cb --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01.ts @@ -0,0 +1,35 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result): number { + return 5; +} + +function rej(reject): number { + return 3; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result: number; + try { + let result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result): number { + return 5; +} + +function rej(reject): number { + return 3; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02.ts new file mode 100644 index 0000000000000..a385d5bb13abd --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02.ts @@ -0,0 +1,27 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res => 0).catch(rej => 1).then(res); +} + +function res(result): number { + return 5; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result: number; + try { + const res = await fetch("https://typescriptlang.org"); + result = 0; + } + catch (rej) { + result = 1; + } + return res(result); +} + +function res(result): number { + return 5; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts index 83c17aea8d863..098b3c4627afd 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_InnerPromiseRet.ts @@ -12,7 +12,7 @@ function /*[#|*/innerPromise/*|]*/(): Promise { async function innerPromise(): Promise { const resp = await fetch("https://typescriptlang.org"); - let blob_1; + let blob_1: any; try { const blob = await resp.blob(); blob_1 = blob.byteOffset; diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts index 736668c5b9696..074091e33596c 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns1.ts @@ -15,8 +15,8 @@ function /*[#|*/f/*|]*/(): Promise { async function f(): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); if (x.ok) { - const res = await fetch("https://typescriptlang.org"); - return console.log(res); + const res_1 = await fetch("https://typescriptlang.org"); + return console.log(res_1); } const resp = await x; var blob = resp.blob().then(blob_1 => blob_1.byteOffset).catch(err => 'Error'); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts index 85836e569ceee..6569c1fb0ef99 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_MultipleReturns2.ts @@ -16,11 +16,11 @@ function /*[#|*/f/*|]*/(): Promise { async function f(): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); if (x.ok) { - const res = await fetch("https://typescriptlang.org"); - return console.log(res); + const res_1 = await fetch("https://typescriptlang.org"); + return console.log(res_1); } const resp = await x; var blob = resp.blob().then(blob_1 => blob_1.byteOffset).catch(err => 'Error'); - const res = await fetch("https://micorosft.com"); + const res_1 = await fetch("https://micorosft.com"); return console.log("Another one!"); } From 2d21376ef121f3c02350666ee02d21c43b9dead2 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 15:46:09 -0700 Subject: [PATCH 183/196] More tests and baselines --- .../unittests/convertToAsyncFunction.ts | 2 ++ .../convertToAsyncFunction_LocalReturn.js | 20 +++++++++++++++++++ .../convertToAsyncFunction_LocalReturn.ts | 20 +++++++++++++++++++ ...onvertToAsyncFunction_basicWithComments.ts | 19 ++++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_LocalReturn.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_LocalReturn.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicWithComments.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 6e6e2f02cb1fb..79b3f38338222 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -460,6 +460,8 @@ function [#|f|](): Promise{ }`); _testConvertToAsyncFunction("convertToAsyncFunction_basicWithComments", ` function [#|f|](): Promise{ + /* Note - some of these comments are removed during the refactor. This is not ideal. */ + // a /*b*/ return /*c*/ fetch( /*d*/ 'https://typescriptlang.org' /*e*/).then( /*f*/ result /*g*/ => { /*h*/ console.log(/*i*/ result /*j*/) /*k*/}/*l*/); // m diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_LocalReturn.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_LocalReturn.js new file mode 100644 index 0000000000000..f9bc67dd19b71 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_LocalReturn.js @@ -0,0 +1,20 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let x = fetch("https://typescriptlang.org").then(res => console.log(res)); + return x.catch(err => console.log("Error!", err)); +} + + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let x = fetch("https://typescriptlang.org").then(res => console.log(res)); + try { + return x; + } + catch (err) { + return console.log("Error!", err); + } +} + diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_LocalReturn.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_LocalReturn.ts new file mode 100644 index 0000000000000..f9bc67dd19b71 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_LocalReturn.ts @@ -0,0 +1,20 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + let x = fetch("https://typescriptlang.org").then(res => console.log(res)); + return x.catch(err => console.log("Error!", err)); +} + + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + let x = fetch("https://typescriptlang.org").then(res => console.log(res)); + try { + return x; + } + catch (err) { + return console.log("Error!", err); + } +} + diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicWithComments.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicWithComments.ts new file mode 100644 index 0000000000000..84d0355357ac8 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicWithComments.ts @@ -0,0 +1,19 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(): Promise{ + /* Note - some of these comments are removed during the refactor. This is not ideal. */ + + // a + /*b*/ return /*c*/ fetch( /*d*/ 'https://typescriptlang.org' /*e*/).then( /*f*/ result /*g*/ => { /*h*/ console.log(/*i*/ result /*j*/) /*k*/}/*l*/); + // m +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f(): Promise{ + /* Note - some of these comments are removed during the refactor. This is not ideal. */ + + // a + /*b*/ const result = await fetch(/*d*/ 'https://typescriptlang.org' /*e*/); + console.log(result); /*k*/ + // m +} \ No newline at end of file From af20f70fe03bc1254a5c60d82d6699a5360431c2 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 16:21:16 -0700 Subject: [PATCH 184/196] fixed const bug and updated tests accordingly --- src/services/codefixes/convertToAsyncFunction.ts | 5 +++++ .../convertToAsyncFunction_CatchFollowedByThen.js | 2 +- .../convertToAsyncFunction_CatchFollowedByThen.ts | 2 +- ...vertToAsyncFunction_CatchFollowedByThenMatchingTypes01.ts | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index f1666313ffb53..3e38771a4f71e 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -282,6 +282,11 @@ namespace ts.codefix { transformer.synthNamesMap.set(key, getNewNameIfConflict(prevArgName.identifier, transformer.allVarNames)) } }) + + // update the constIdentifiers list + if (transformer.constIdentifiers.some(elem => elem.text === prevArgName.identifier.text)) { + transformer.constIdentifiers.push(getNewNameIfConflict(prevArgName.identifier, transformer.allVarNames).identifier); + } } const tryBlock = createBlock(transformExpression(node.expression, transformer, node, prevArgName)); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js index 43d41d3fc11de..cb39bbad5e1ad 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.js @@ -17,7 +17,7 @@ function rej(reject){ async function f(){ let result; try { - let result_1 = await fetch("https://typescriptlang.org"); + const result_1 = await fetch("https://typescriptlang.org"); result = await res(result_1); } catch (reject) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts index 3b1ef68267594..d675993aadfb5 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThen.ts @@ -17,7 +17,7 @@ function rej(reject){ async function f(){ let result: any; try { - let result_1 = await fetch("https://typescriptlang.org"); + const result_1 = await fetch("https://typescriptlang.org"); result = await res(result_1); } catch (reject) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01.ts index 65051d026d3cb..2c9a02e6637d8 100644 --- a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01.ts +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01.ts @@ -17,7 +17,7 @@ function rej(reject): number { async function f(){ let result: number; try { - let result_1 = await fetch("https://typescriptlang.org"); + const result_1 = await fetch("https://typescriptlang.org"); result = await res(result_1); } catch (reject) { From e36b55ac102f2aad4b6ac85c876878e6be8abbe8 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 16:31:28 -0700 Subject: [PATCH 185/196] Changed the way we get the union type --- .../codefixes/convertToAsyncFunction.ts | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 3e38771a4f71e..e18e82f900f92 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -20,7 +20,7 @@ namespace ts.codefix { */ interface SynthIdentifier { identifier: Identifier; - type: Type | undefined; + types: Type[]; numberOfAssignmentsOriginal: number; numberOfAssignmentsSynthesized: number; } @@ -184,7 +184,7 @@ namespace ts.codefix { else { const identifier = getSynthesizedDeepClone(node); identsToRenameMap.set(symbolIdString, identifier); - synthNamesMap.set(symbolIdString, { identifier, type: undefined, numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); + synthNamesMap.set(symbolIdString, { identifier, types: [], numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); if ((isParameter(node.parent) && isExpressionOrCallOnTypePromise(node.parent.parent)) || isVariableDeclaration(node.parent)) { allVarNames.push({ identifier, symbol }); } @@ -233,9 +233,8 @@ namespace ts.codefix { function getNewNameIfConflict(name: Identifier, allVarNames: SymbolAndIdentifier[]): SynthIdentifier { const numVarsSameName = allVarNames.filter(elem => elem.identifier.text === name.text).length; const numberOfAssignmentsOriginal = 0, numberOfAssignmentsSynthesized = 0; - const type = undefined; const identifier = numVarsSameName == 0 ? name : createIdentifier(name.text + "_" + numVarsSameName); - return { identifier: identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; + return { identifier: identifier, types: [], numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } // dispatch function to recursively build the refactoring @@ -290,10 +289,8 @@ namespace ts.codefix { } const tryBlock = createBlock(transformExpression(node.expression, transformer, node, prevArgName)); - const tryType = prevArgName && prevArgName.type; const transformationBody = getTransformationBody(func, prevArgName, argName, node, transformer); - const catchType = prevArgName && prevArgName.type; const catchClause = createCatchClause(argName.identifier.text, createBlock(transformationBody)); /* @@ -301,9 +298,7 @@ namespace ts.codefix { */ let varDeclList; if (prevArgName && !shouldReturn) { - let typeArray: Type[] = []; - if (tryType) typeArray.push(tryType); - if (catchType) typeArray.push(catchType); + let typeArray: Type[] = prevArgName.types; const unionType = transformer.checker.getUnionType(typeArray, UnionReduction.Subtype); const unionTypeNode = transformer.isInJSFile ? undefined : transformer.checker.typeToTypeNode(unionType); const varDecl = [createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), unionTypeNode)] @@ -379,23 +374,21 @@ namespace ts.codefix { const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(parent).toString()); switch (func.kind) { case SyntaxKind.Identifier: - if (!hasArgName) { - break; - } + if (!hasArgName) break; const synthCall = createCall(getSynthesizedDeepClone(func) as Identifier, /*typeArguments*/ undefined, [argName.identifier]); if (shouldReturn) { return createNodeArray([createReturn(synthCall)]); } - if (!hasPrevArgName) { - break; - } + if (!hasPrevArgName) break; const type = transformer.originalTypeMap.get((func).text); const callSignatures = type && transformer.checker.getSignaturesOfType(type, SignatureKind.Call); const returnType = callSignatures && callSignatures[0].getReturnType(); - prevArgName!.type = returnType; + if (returnType) { + prevArgName!.types.push(returnType); + } return createVariableDeclarationOrAssignment(prevArgName!, createAwait(synthCall), transformer); case SyntaxKind.FunctionDeclaration: @@ -432,7 +425,9 @@ namespace ts.codefix { const type = transformer.checker.getTypeAtLocation(func); const callSignatures = type && transformer.checker.getSignaturesOfType(type, SignatureKind.Call); const returnType = callSignatures && callSignatures[0].getReturnType(); - prevArgName!.type = returnType; + if (returnType) { + prevArgName!.types.push(returnType); + } return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression, transformer); } else { @@ -504,7 +499,7 @@ namespace ts.codefix { function getArgName(funcNode: Node, transformer: Transformer): SynthIdentifier { const numberOfAssignmentsOriginal = 0, numberOfAssignmentsSynthesized = 0; - const type = undefined; + const types: Type[] = []; function getMapEntryIfExists(node: Identifier): SynthIdentifier { const originalNode = getOriginalNode(node); @@ -513,11 +508,11 @@ namespace ts.codefix { if (!symbol) { - return { identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; + return { identifier, types, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); - return mapEntry || { identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; + return mapEntry || { identifier, types, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } function getSymbol(node: Node): Symbol | undefined { @@ -537,14 +532,14 @@ namespace ts.codefix { } } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { - name = { identifier: funcNode.arguments[0] as Identifier, type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; + name = { identifier: funcNode.arguments[0] as Identifier, types, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } else if (isIdentifier(funcNode)) { name = getMapEntryIfExists(funcNode); } if (!name || name.identifier === undefined || name.identifier.text === "_" || name.identifier.text === "undefined") { - return { identifier: createIdentifier(""), type, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; + return { identifier: createIdentifier(""), types, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } return name; From 31e5433c53d32dae90601cacc31821478701453c Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 16:49:00 -0700 Subject: [PATCH 186/196] Added more tests and baselines --- .../unittests/convertToAsyncFunction.ts | 99 +++++++++++++++++++ ...lowedByThenMatchingTypes01NoAnnotations.js | 35 +++++++ ...lowedByThenMatchingTypes01NoAnnotations.ts | 35 +++++++ ...lowedByThenMatchingTypes02NoAnnotations.js | 27 +++++ ...lowedByThenMatchingTypes02NoAnnotations.ts | 27 +++++ ...tion_CatchFollowedByThenMismatchTypes01.js | 35 +++++++ ...tion_CatchFollowedByThenMismatchTypes01.ts | 35 +++++++ ...tion_CatchFollowedByThenMismatchTypes03.js | 35 +++++++ ...tion_CatchFollowedByThenMismatchTypes03.ts | 35 +++++++ ...convertToAsyncFunction_PromiseCallInner.js | 18 ++++ ...convertToAsyncFunction_PromiseCallInner.ts | 18 ++++ 11 files changed, 399 insertions(+) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes01.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes01.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes03.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes03.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseCallInner.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseCallInner.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 79b3f38338222..83b2fd6c8725d 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -977,6 +977,22 @@ function rej(reject): number { ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return 3; +} +` + ); + + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => 0).catch(rej => 1).then(res); @@ -988,6 +1004,89 @@ function res(result): number { ` ); +_testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res => 0).catch(rej => 1).then(res); +} + +function res(result){ + return 5; +} +` +); + + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes01", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return "Error"; +} +` + ); + +_testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes02", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return reject; +} +` + ); + +_testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes03", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return Promise.resolve(1); +} +` + ); + +_testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes04", ` +interface a { + name: string; + age: number; +} + +interface b extends a { + color: string; +} + + +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return {name: "myName", age: 22, color: "red"}; +} + +function rej(reject){ + return {name: "myName", age: 27}; +} +` + ); + + + _testConvertToAsyncFunction("convertToAsyncFunction_LocalReturn", ` function [#|f|]() { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations.js new file mode 100644 index 0000000000000..53d4d82e8dbcb --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations.js @@ -0,0 +1,35 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return 3; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result; + try { + const result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return 3; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations.ts new file mode 100644 index 0000000000000..8e5e2c82a9735 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations.ts @@ -0,0 +1,35 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return 3; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result: number; + try { + const result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return 3; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations.js new file mode 100644 index 0000000000000..44a8c3b3b65f7 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations.js @@ -0,0 +1,27 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res => 0).catch(rej => 1).then(res); +} + +function res(result){ + return 5; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result; + try { + const res = await fetch("https://typescriptlang.org"); + result = 0; + } + catch (rej) { + result = 1; + } + return res(result); +} + +function res(result){ + return 5; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations.ts new file mode 100644 index 0000000000000..68563c3a348ea --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations.ts @@ -0,0 +1,27 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res => 0).catch(rej => 1).then(res); +} + +function res(result){ + return 5; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result: number; + try { + const res = await fetch("https://typescriptlang.org"); + result = 0; + } + catch (rej) { + result = 1; + } + return res(result); +} + +function res(result){ + return 5; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes01.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes01.js new file mode 100644 index 0000000000000..49cd8dd0cddba --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes01.js @@ -0,0 +1,35 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return "Error"; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result; + try { + const result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return "Error"; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes01.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes01.ts new file mode 100644 index 0000000000000..6bbd496de7d0a --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes01.ts @@ -0,0 +1,35 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return "Error"; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result: string | number; + try { + const result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return "Error"; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes03.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes03.js new file mode 100644 index 0000000000000..5ae54306d1122 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes03.js @@ -0,0 +1,35 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return Promise.resolve(1); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result; + try { + const result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return Promise.resolve(1); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes03.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes03.ts new file mode 100644 index 0000000000000..2ee8796ab203c --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes03.ts @@ -0,0 +1,35 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return Promise.resolve(1); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result: number | Promise; + try { + const result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return Promise.resolve(1); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseCallInner.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseCallInner.js new file mode 100644 index 0000000000000..0049c661c3904 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseCallInner.js @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return fetch(Promise.resolve(1).then(res => "https://typescriptlang.org")).catch(err => console.log(err)); +} + + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + try { + return fetch(Promise.resolve(1).then(res => "https://typescriptlang.org")); + } + catch (err) { + return console.log(err); + } +} + diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseCallInner.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseCallInner.ts new file mode 100644 index 0000000000000..0049c661c3904 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_PromiseCallInner.ts @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return fetch(Promise.resolve(1).then(res => "https://typescriptlang.org")).catch(err => console.log(err)); +} + + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + try { + return fetch(Promise.resolve(1).then(res => "https://typescriptlang.org")); + } + catch (err) { + return console.log(err); + } +} + From 39ba3eae4ed3152df514df3b6445e65ef6d7ecc4 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 16:52:16 -0700 Subject: [PATCH 187/196] Fixed linting errors --- .../codefixes/convertToAsyncFunction.ts | 28 +++++++++---------- src/services/utilities.ts | 2 +- .../unittests/convertToAsyncFunction.ts | 4 +-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index e18e82f900f92..53fa871bac150 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -12,7 +12,7 @@ namespace ts.codefix { }); - /* + /* custom type to encapsulate information for variable declarations synthesized in the refactor numberOfUsesOriginal - number of times the variable should be assigned in the refactor numberOfUsesSynthesized - count of how many times the variable has been assigned so far @@ -37,7 +37,7 @@ namespace ts.codefix { setOfExpressionsToReturn: Map; // keys are the node ids of the expressions context: CodeFixContextBase; constIdentifiers: Identifier[]; - originalTypeMap: Map; // keys are the identifier's text + originalTypeMap: Map; // keys are the identifier's text isInJSFile: boolean; } @@ -149,7 +149,7 @@ namespace ts.codefix { */ function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllExpressionsToReturn: Map, originalType: Map, allVarNames: SymbolAndIdentifier[]): FunctionLikeDeclaration { - let identsToRenameMap: Map = createMap(); + const identsToRenameMap: Map = createMap(); forEachChild(nodeToRename, function visit(node: Node) { if (!isIdentifier(node)) { forEachChild(node, visit); @@ -165,7 +165,7 @@ namespace ts.codefix { const symbolIdString = getSymbolId(symbol).toString(); // if the identifier refers to a function we want to add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) - // Note - the choice of the first call signature is arbitrary + // Note - the choice of the first call signature is arbitrary if (callSignatures && callSignatures.length > 0 && callSignatures[0].parameters.length && !synthNamesMap.has(symbolIdString)) { const synthName = getNewNameIfConflict(createIdentifier(callSignatures[0].parameters[0].name), allVarNames); synthNamesMap.set(symbolIdString, synthName); @@ -196,7 +196,7 @@ namespace ts.codefix { return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, checker, deepCloneCallback); function isExpressionOrCallOnTypePromise(child: Node): boolean { - let node = child.parent; + const node = child.parent; if (isCallExpression(node) || isIdentifier(node) && !setOfAllExpressionsToReturn.get(getNodeId(node).toString())) { const nodeType = checker.getTypeAtLocation(node); const isPromise = nodeType && checker.getPromisedTypeOfPromise(nodeType); @@ -221,9 +221,9 @@ namespace ts.codefix { } } - const val = setOfAllExpressionsToReturn.get(getNodeId(node!).toString()); + const val = setOfAllExpressionsToReturn.get(getNodeId(node).toString()); if (val !== undefined) { - setOfAllExpressionsToReturn.delete(getNodeId(node!).toString()); + setOfAllExpressionsToReturn.delete(getNodeId(node).toString()); setOfAllExpressionsToReturn.set(getNodeId(clone).toString(), val); } } @@ -233,8 +233,8 @@ namespace ts.codefix { function getNewNameIfConflict(name: Identifier, allVarNames: SymbolAndIdentifier[]): SynthIdentifier { const numVarsSameName = allVarNames.filter(elem => elem.identifier.text === name.text).length; const numberOfAssignmentsOriginal = 0, numberOfAssignmentsSynthesized = 0; - const identifier = numVarsSameName == 0 ? name : createIdentifier(name.text + "_" + numVarsSameName); - return { identifier: identifier, types: [], numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; + const identifier = numVarsSameName === 0 ? name : createIdentifier(name.text + "_" + numVarsSameName); + return { identifier, types: [], numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; } // dispatch function to recursively build the refactoring @@ -269,7 +269,7 @@ namespace ts.codefix { const argName = getArgName(func, transformer); const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString()); - /* + /* 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 @@ -278,9 +278,9 @@ namespace ts.codefix { prevArgName.numberOfAssignmentsOriginal = 2; // Try block and catch block transformer.synthNamesMap.forEach((val, key) => { if (val.identifier.text === prevArgName.identifier.text) { - transformer.synthNamesMap.set(key, getNewNameIfConflict(prevArgName.identifier, transformer.allVarNames)) + transformer.synthNamesMap.set(key, getNewNameIfConflict(prevArgName.identifier, transformer.allVarNames)); } - }) + }); // update the constIdentifiers list if (transformer.constIdentifiers.some(elem => elem.text === prevArgName.identifier.text)) { @@ -298,10 +298,10 @@ namespace ts.codefix { */ let varDeclList; if (prevArgName && !shouldReturn) { - let typeArray: Type[] = prevArgName.types; + const typeArray: Type[] = prevArgName.types; const unionType = transformer.checker.getUnionType(typeArray, UnionReduction.Subtype); const unionTypeNode = transformer.isInJSFile ? undefined : transformer.checker.typeToTypeNode(unionType); - const varDecl = [createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), unionTypeNode)] + const varDecl = [createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), unionTypeNode)]; varDeclList = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList(varDecl, NodeFlags.Let)); } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 72060a5b901f8..1b6c32b20add4 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1633,7 +1633,7 @@ namespace ts { const symbol = checker.getSymbolAtLocation(node!); const renameInfo = symbol && renameMap.get(String(getSymbolId(symbol))); - if (renameInfo) { + if (renameInfo) { clone = createIdentifier(renameInfo.text); } } diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 83b2fd6c8725d..df43e461429bb 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -1094,13 +1094,13 @@ function [#|f|]() { return x.catch(err => console.log("Error!", err)); } -`) +`); _testConvertToAsyncFunction("convertToAsyncFunction_PromiseCallInner", ` function [#|f|]() { return fetch(Promise.resolve(1).then(res => "https://typescriptlang.org")).catch(err => console.log(err)); } -`) +`); _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchFollowedByCall", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).toString(); From 8777657c222ce9f73e5d855a315f236dae9c92ca Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Wed, 8 Aug 2018 17:11:29 -0700 Subject: [PATCH 188/196] Addressed code review comments and removed extraneous comments --- .../codefixes/convertToAsyncFunction.ts | 24 ++++++++++--------- src/services/utilities.ts | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 53fa871bac150..97f25480799d0 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -149,7 +149,7 @@ namespace ts.codefix { */ function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, context: CodeFixContextBase, setOfAllExpressionsToReturn: Map, originalType: Map, allVarNames: SymbolAndIdentifier[]): FunctionLikeDeclaration { - const identsToRenameMap: Map = createMap(); + const identsToRenameMap: Map = createMap(); // key is the symbol id forEachChild(nodeToRename, function visit(node: Node) { if (!isIdentifier(node)) { forEachChild(node, visit); @@ -207,16 +207,18 @@ namespace ts.codefix { } function deepCloneCallback(node: Node, clone: Node) { - const symbol = checker.getSymbolAtLocation(node); - const symboldIdString = symbol && getSymbolId(symbol).toString(); - const renameInfo = symbol && synthNamesMap.get(symboldIdString!); - - if (renameInfo) { - const type = checker.getTypeAtLocation(node); - if (type) { - originalType.set(renameInfo.identifier.text, type); - if (isIdentifier(node)) { - originalType.set(node.text, type); + if (isIdentifier) { + const symbol = checker.getSymbolAtLocation(node); + const symboldIdString = symbol && getSymbolId(symbol).toString(); + const renameInfo = symbol && synthNamesMap.get(symboldIdString!); + + if (renameInfo) { + const type = checker.getTypeAtLocation(node); + if (type) { + originalType.set(renameInfo.identifier.text, type); + if (isIdentifier(node)) { + originalType.set(node.text, type); + } } } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 1b6c32b20add4..edaa3aecc3863 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1626,7 +1626,7 @@ namespace ts { * WARNING: This is an expensive operation and is only intended to be used in refactorings * and code fixes (because those are triggered by explicit user actions). */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any/*nodeIdMap?: Map, originalType?: Map*/): T { + export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T { let clone; if (node && isIdentifier(node!) && renameMap && checker) { From e6c676e00c7eda7ebf86483f38b0a111689e3649 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 9 Aug 2018 10:36:30 -0700 Subject: [PATCH 189/196] Added tests and baselines --- .../unittests/convertToAsyncFunction.ts | 20 ++++++- ...tion_CatchFollowedByThenMismatchTypes02.ts | 35 ++++++++++++ ...lowedByThenMismatchTypes02NoAnnotations.js | 35 ++++++++++++ ...lowedByThenMismatchTypes02NoAnnotations.ts | 35 ++++++++++++ ...tion_CatchFollowedByThenMismatchTypes04.ts | 55 +++++++++++++++++++ 5 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes04.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index df43e461429bb..d16a20b73d5c2 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -1039,12 +1039,28 @@ function res(result){ return 5; } +function rej(reject): Response{ + return reject; +} +` + ); + +_testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations", ` +function [#|f|](){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + function rej(reject){ return reject; } ` ); + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes03", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); @@ -1075,11 +1091,11 @@ function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } -function res(result){ +function res(result): b{ return {name: "myName", age: 22, color: "red"}; } -function rej(reject){ +function rej(reject): a{ return {name: "myName", age: 27}; } ` diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02.ts new file mode 100644 index 0000000000000..2d415c090e211 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02.ts @@ -0,0 +1,35 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject): Response{ + return reject; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result: number | Response; + try { + const result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return 5; +} + +function rej(reject): Response{ + return reject; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations.js new file mode 100644 index 0000000000000..f7be616009ffa --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations.js @@ -0,0 +1,35 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return reject; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result; + try { + const result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return reject; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations.ts new file mode 100644 index 0000000000000..5cfae1be7584c --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations.ts @@ -0,0 +1,35 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return reject; +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f(){ + let result: any; + try { + const result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result){ + return 5; +} + +function rej(reject){ + return reject; +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes04.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes04.ts new file mode 100644 index 0000000000000..ac542ca8cf595 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_CatchFollowedByThenMismatchTypes04.ts @@ -0,0 +1,55 @@ +// ==ORIGINAL== + +interface a { + name: string; + age: number; +} + +interface b extends a { + color: string; +} + + +function /*[#|*/f/*|]*/(){ + return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); +} + +function res(result): b{ + return {name: "myName", age: 22, color: "red"}; +} + +function rej(reject): a{ + return {name: "myName", age: 27}; +} + +// ==ASYNC FUNCTION::Convert to async function== + +interface a { + name: string; + age: number; +} + +interface b extends a { + color: string; +} + + +async function f(){ + let result: a; + try { + const result_1 = await fetch("https://typescriptlang.org"); + result = await res(result_1); + } + catch (reject) { + result = await rej(reject); + } + return res(result); +} + +function res(result): b{ + return {name: "myName", age: 22, color: "red"}; +} + +function rej(reject): a{ + return {name: "myName", age: 27}; +} From 54b6f2a6a64414c2a904eefdd7370a5e993091e7 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 9 Aug 2018 15:03:52 -0700 Subject: [PATCH 190/196] Addressed code review comments --- .../codefixes/convertToAsyncFunction.ts | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index cf66ed88e5c6d..7fadb31afc8fe 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -23,7 +23,6 @@ namespace ts.codefix { identifier: Identifier; types: Type[]; numberOfAssignmentsOriginal: number; - numberOfAssignmentsSynthesized: number; } interface SymbolAndIdentifier { @@ -132,6 +131,11 @@ namespace ts.codefix { return setOfExpressionsToReturn; } + + /* + Returns true if node is a promise returning expression + If name is not undefined, node is a promise returning call of name + */ function isPromiseReturningExpression(node: Node, checker: TypeChecker, name?: string): boolean { const isNodeExpression = name ? isCallExpression(node) : isExpression(node); const isExpressionOfName = isNodeExpression && (!name || hasPropertyAccessExpressionWithName(node as CallExpression, name)); @@ -166,9 +170,10 @@ namespace ts.codefix { const symbolIdString = getSymbolId(symbol).toString(); // if the identifier refers to a function we want to add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) - // Note - the choice of the first call signature is arbitrary - if (callSignatures && callSignatures.length > 0 && callSignatures[0].parameters.length && !synthNamesMap.has(symbolIdString)) { - const synthName = getNewNameIfConflict(createIdentifier(callSignatures[0].parameters[0].name), allVarNames); + // Note - the choice of the last call signature is arbitrary + const index = callSignatures.length - 1; + if (callSignatures && callSignatures.length > 0 && callSignatures[index].parameters.length && !synthNamesMap.has(symbolIdString)) { + const synthName = getNewNameIfConflict(createIdentifier(callSignatures[index].parameters[0].name), allVarNames); synthNamesMap.set(symbolIdString, synthName); allVarNames.push({ identifier: synthName.identifier, symbol }); } @@ -185,7 +190,7 @@ namespace ts.codefix { else { const identifier = getSynthesizedDeepClone(node); identsToRenameMap.set(symbolIdString, identifier); - synthNamesMap.set(symbolIdString, { identifier, types: [], numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length, numberOfAssignmentsSynthesized: 0 }); + synthNamesMap.set(symbolIdString, { identifier, types: [], numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length/*, numberOfAssignmentsSynthesized: 0*/ }); if ((isParameter(node.parent) && isExpressionOrCallOnTypePromise(node.parent.parent)) || isVariableDeclaration(node.parent)) { allVarNames.push({ identifier, symbol }); } @@ -235,9 +240,9 @@ namespace ts.codefix { function getNewNameIfConflict(name: Identifier, allVarNames: SymbolAndIdentifier[]): SynthIdentifier { const numVarsSameName = allVarNames.filter(elem => elem.identifier.text === name.text).length; - const numberOfAssignmentsOriginal = 0, numberOfAssignmentsSynthesized = 0; + const numberOfAssignmentsOriginal = 0; const identifier = numVarsSameName === 0 ? name : createIdentifier(name.text + "_" + numVarsSameName); - return { identifier, types: [], numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; + return { identifier, types: [], numberOfAssignmentsOriginal }; } // dispatch function to recursively build the refactoring @@ -247,7 +252,7 @@ namespace ts.codefix { } let nodeType = transformer.checker.getTypeAtLocation(node); - if (nodeType && nodeType.flags & 1 && (nodeType).intrinsicName === "error" && isIdentifier(node)) { + if (nodeType && nodeType.flags & TypeFlags.Any && (nodeType).intrinsicName === "error" && isIdentifier(node)) { nodeType = transformer.originalTypeMap.get(node.text)!; } @@ -360,12 +365,10 @@ namespace ts.codefix { function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression, transformer: Transformer): NodeArray { - if (prevArgName.numberOfAssignmentsSynthesized < prevArgName.numberOfAssignmentsOriginal) { - prevArgName.numberOfAssignmentsSynthesized += 1; + if (prevArgName.types.length < prevArgName.numberOfAssignmentsOriginal) { return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]); } - prevArgName.numberOfAssignmentsSynthesized += 1; return createNodeArray([createVariableStatement(/*modifiers*/ undefined, (createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))]); } @@ -389,10 +392,9 @@ namespace ts.codefix { const type = transformer.originalTypeMap.get((func).text); const callSignatures = type && transformer.checker.getSignaturesOfType(type, SignatureKind.Call); const returnType = callSignatures && callSignatures[0].getReturnType(); - if (returnType) { - prevArgName!.types.push(returnType); - } - return createVariableDeclarationOrAssignment(prevArgName!, createAwait(synthCall), transformer); + const varDeclOrAssignment = createVariableDeclarationOrAssignment(prevArgName!, createAwait(synthCall), transformer); + prevArgName!.types.push(returnType!); + return varDeclOrAssignment; case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: @@ -428,10 +430,9 @@ namespace ts.codefix { const type = transformer.checker.getTypeAtLocation(func); const callSignatures = type && transformer.checker.getSignaturesOfType(type, SignatureKind.Call); const returnType = callSignatures && callSignatures[0].getReturnType(); - if (returnType) { - prevArgName!.types.push(returnType); - } - return createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression, transformer); + const varDeclOrAssignment = createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression, transformer); + prevArgName!.types.push(returnType); + return varDeclOrAssignment; } else { return createNodeArray([createReturn(getSynthesizedDeepClone(funcBody) as Expression)]); @@ -501,31 +502,9 @@ namespace ts.codefix { function getArgName(funcNode: Node, transformer: Transformer): SynthIdentifier { - const numberOfAssignmentsOriginal = 0, numberOfAssignmentsSynthesized = 0; + const numberOfAssignmentsOriginal = 0; const types: Type[] = []; - function getMapEntryIfExists(node: Identifier): SynthIdentifier { - const originalNode = getOriginalNode(node); - const symbol = getSymbol(originalNode); - const identifier = node; - - - if (!symbol) { - return { identifier, types, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; - } - - const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); - return mapEntry || { identifier, types, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; - } - - function getSymbol(node: Node): Symbol | undefined { - return node.symbol ? node.symbol : transformer.checker.getSymbolAtLocation(node); - } - - function getOriginalNode(node: Node): Node { - return node.original ? node.original : node; - } - let name: SynthIdentifier | undefined; if (isFunctionLikeDeclaration(funcNode)) { @@ -535,16 +514,37 @@ namespace ts.codefix { } } else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) { - name = { identifier: funcNode.arguments[0] as Identifier, types, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; + name = { identifier: funcNode.arguments[0] as Identifier, types, numberOfAssignmentsOriginal }; } else if (isIdentifier(funcNode)) { name = getMapEntryIfExists(funcNode); } if (!name || name.identifier === undefined || name.identifier.text === "_" || name.identifier.text === "undefined") { - return { identifier: createIdentifier(""), types, numberOfAssignmentsOriginal, numberOfAssignmentsSynthesized }; + return { identifier: createIdentifier(""), types, numberOfAssignmentsOriginal }; } return name; + + function getMapEntryIfExists(node: Identifier): SynthIdentifier { + const originalNode = getOriginalNode(node); + const symbol = getSymbol(originalNode); + const identifier = node; + + if (!symbol) { + return { identifier, types, numberOfAssignmentsOriginal }; + } + + const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString()); + return mapEntry || { identifier, types, numberOfAssignmentsOriginal }; + } + + function getSymbol(node: Node): Symbol | undefined { + return node.symbol ? node.symbol : transformer.checker.getSymbolAtLocation(node); + } + + function getOriginalNode(node: Node): Node { + return node.original ? node.original : node; + } } } \ No newline at end of file From e2c02d6bf51c720a4d45bc9e707ae9c5b2630f82 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 9 Aug 2018 15:47:07 -0700 Subject: [PATCH 191/196] Created a rename synthesized deep clone function and addressed other code review comments regarding the original types map --- .../codefixes/convertToAsyncFunction.ts | 19 +++++++------------ src/services/utilities.ts | 18 ++++++++++++++---- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 7fadb31afc8fe..ef8416bd24e67 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -37,7 +37,7 @@ namespace ts.codefix { setOfExpressionsToReturn: Map; // keys are the node ids of the expressions context: CodeFixContextBase; constIdentifiers: Identifier[]; - originalTypeMap: Map; // keys are the identifier's text + originalTypeMap: Map; // keys are the node id of the identifier isInJSFile: boolean; } @@ -199,7 +199,7 @@ namespace ts.codefix { } }); - return getSynthesizedDeepClone(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, checker, deepCloneCallback); + return getSynthesizedDeepCloneWithRenames(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, checker, deepCloneCallback); function isExpressionOrCallOnTypePromise(child: Node): boolean { const node = child.parent; @@ -213,7 +213,7 @@ namespace ts.codefix { } function deepCloneCallback(node: Node, clone: Node) { - if (isIdentifier) { + if (isIdentifier(node)) { const symbol = checker.getSymbolAtLocation(node); const symboldIdString = symbol && getSymbolId(symbol).toString(); const renameInfo = symbol && synthNamesMap.get(symboldIdString!); @@ -221,10 +221,7 @@ namespace ts.codefix { if (renameInfo) { const type = checker.getTypeAtLocation(node); if (type) { - originalType.set(renameInfo.identifier.text, type); - if (isIdentifier(node)) { - originalType.set(node.text, type); - } + originalType.set(getNodeId(clone).toString(), type); } } } @@ -251,10 +248,8 @@ namespace ts.codefix { return []; } - let nodeType = transformer.checker.getTypeAtLocation(node); - if (nodeType && nodeType.flags & TypeFlags.Any && (nodeType).intrinsicName === "error" && isIdentifier(node)) { - nodeType = transformer.originalTypeMap.get(node.text)!; - } + const originalType = isIdentifier(node) && transformer.originalTypeMap.get(getNodeId(node).toString()); + const nodeType = originalType || transformer.checker.getTypeAtLocation(node); if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "then") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) { return transformThen(node, transformer, outermostParent, prevArgName); @@ -389,7 +384,7 @@ namespace ts.codefix { if (!hasPrevArgName) break; - const type = transformer.originalTypeMap.get((func).text); + const type = transformer.originalTypeMap.get(getNodeId(func).toString()); const callSignatures = type && transformer.checker.getSignaturesOfType(type, SignatureKind.Call); const returnType = callSignatures && callSignatures[0].getReturnType(); const varDeclOrAssignment = createVariableDeclarationOrAssignment(prevArgName!, createAwait(synthCall), transformer); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index ee14430286819..b1d5ec5b7c68f 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1650,7 +1650,13 @@ namespace ts { * WARNING: This is an expensive operation and is only intended to be used in refactorings * and code fixes (because those are triggered by explicit user actions). */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T { + export function getSynthesizedDeepClone(node: T, includeTrivia = true): T { + const clone = node && getSynthesizedDeepCloneWorker(node as NonNullable); + if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); + return clone; + } + + export function getSynthesizedDeepCloneWithRenames(node: T, includeTrivia = true, renameMap?: Map, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T { let clone; if (node && isIdentifier(node!) && renameMap && checker) { @@ -1674,9 +1680,9 @@ namespace ts { function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T { - const visited = visitEachChild(node, function wrapper(node) { - return getSynthesizedDeepClone(node, /*includeTrivia*/ true, renameMap, checker, callback); - }, nullTransformationContext); + const visited = (renameMap || checker || callback) ? + visitEachChild(node, wrapper, nullTransformationContext) : + visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); if (visited === node) { // This only happens for leaf nodes - internal nodes always see their children change. @@ -1695,6 +1701,10 @@ namespace ts { // would have made. visited.parent = undefined!; return visited; + + function wrapper(node: T) { + return getSynthesizedDeepCloneWithRenames(node, /*includeTrivia*/ true, renameMap, checker, callback); + } } export function getSynthesizedDeepClones(nodes: NodeArray, includeTrivia?: boolean): NodeArray; From 1e72a462e993df0b9ed1e214ede2b2c689e21fea Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 9 Aug 2018 17:19:03 -0700 Subject: [PATCH 192/196] addressed more code review comments --- .../codefixes/convertToAsyncFunction.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index ef8416bd24e67..24f57d808a6c5 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -35,7 +35,6 @@ namespace ts.codefix { synthNamesMap: Map; // keys are the symbol id of the identifier allVarNames: SymbolAndIdentifier[]; setOfExpressionsToReturn: Map; // keys are the node ids of the expressions - context: CodeFixContextBase; constIdentifiers: Identifier[]; originalTypeMap: Map; // keys are the node id of the identifier isInJSFile: boolean; @@ -56,7 +55,7 @@ namespace ts.codefix { const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfExpressionsToReturn, originalTypeMap, allVarNames); const constIdentifiers = getConstIdentifiers(synthNamesMap); const returnStatements = getReturnStatementsWithPromiseHandlers(functionToConvertRenamed); - const transformer = { checker, synthNamesMap, allVarNames, setOfExpressionsToReturn, context, constIdentifiers, originalTypeMap, isInJSFile }; + const transformer = { checker, synthNamesMap, allVarNames, setOfExpressionsToReturn, constIdentifiers, originalTypeMap, isInJSFile }; if (!returnStatements.length) { return; @@ -166,14 +165,13 @@ namespace ts.codefix { if (symbol && isDefinedInFile) { const type = checker.getTypeAtLocation(node); - const callSignatures = type && type.getCallSignatures(); + const lastCallSignature = getLastCallSignature(type, checker); const symbolIdString = getSymbolId(symbol).toString(); // if the identifier refers to a function we want to add the new synthesized variable for the declaration (ex. blob in let blob = res(arg)) // Note - the choice of the last call signature is arbitrary - const index = callSignatures.length - 1; - if (callSignatures && callSignatures.length > 0 && callSignatures[index].parameters.length && !synthNamesMap.has(symbolIdString)) { - const synthName = getNewNameIfConflict(createIdentifier(callSignatures[index].parameters[0].name), allVarNames); + if (lastCallSignature && lastCallSignature.parameters.length && !synthNamesMap.has(symbolIdString)) { + const synthName = getNewNameIfConflict(createIdentifier(lastCallSignature.parameters[0].name), allVarNames); synthNamesMap.set(symbolIdString, synthName); allVarNames.push({ identifier: synthName.identifier, symbol }); } @@ -346,6 +344,7 @@ namespace ts.codefix { function transformPromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] { const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString()); + // the identifier is empty when the handler (.then()) ignores the argument - In this situation we do not need to save the result of the promise returning call const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0; const originalNodeParent = node.original ? node.original.parent : node.parent; if (hasPrevArgName && !shouldReturn && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) { @@ -423,8 +422,7 @@ namespace ts.codefix { if (hasPrevArgName && !shouldReturn) { const type = transformer.checker.getTypeAtLocation(func); - const callSignatures = type && transformer.checker.getSignaturesOfType(type, SignatureKind.Call); - const returnType = callSignatures && callSignatures[0].getReturnType(); + const returnType = getLastCallSignature(type, transformer.checker).getReturnType(); const varDeclOrAssignment = createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression, transformer); prevArgName!.types.push(returnType); return varDeclOrAssignment; @@ -438,6 +436,11 @@ namespace ts.codefix { return createNodeArray([]); } + function getLastCallSignature(type: Type, checker: TypeChecker): Signature { + const callSignatures = type && checker.getSignaturesOfType(type, SignatureKind.Call); + return callSignatures && callSignatures[callSignatures.length - 1]; + } + function getReturnStatementsWithPromiseHandlersIndices(block: Block): number[] { const indices: number[] = []; for (let i = 0; i < block.statements.length; i++) { From ee994f1529a9792b5b916175aa92f88b8703d680 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 9 Aug 2018 17:47:02 -0700 Subject: [PATCH 193/196] code review changes --- src/services/codefixes/convertToAsyncFunction.ts | 14 ++++++++------ src/services/suggestionDiagnostics.ts | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 24f57d808a6c5..57662693d9f9f 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -292,7 +292,8 @@ namespace ts.codefix { const tryBlock = createBlock(transformExpression(node.expression, transformer, node, prevArgName)); const transformationBody = getTransformationBody(func, prevArgName, argName, node, transformer); - const catchClause = createCatchClause(argName.identifier.text, createBlock(transformationBody)); + const catchArg = argName.identifier.text.length > 0 ? argName.identifier.text : "e"; + const catchClause = createCatchClause(catchArg, 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) @@ -326,9 +327,11 @@ namespace ts.codefix { const tryBlock = createBlock(transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody)); const transformationBody2 = getTransformationBody(rej, prevArgName, argNameRej, node, transformer); - const catchClause = createCatchClause(argNameRej.identifier.text, createBlock(transformationBody2)); - return [createTry(tryBlock, catchClause, /*finallyBlock*/ undefined) as Statement]; + const catchArg = argNameRej.identifier.text.length > 0 ? argNameRej.identifier.text : "e"; + const catchClause = createCatchClause(catchArg, createBlock(transformationBody2)); + + return [createTry(tryBlock, catchClause, undefined) as Statement]; } else { return transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody); @@ -524,10 +527,9 @@ namespace ts.codefix { return name; - function getMapEntryIfExists(node: Identifier): SynthIdentifier { - const originalNode = getOriginalNode(node); + function getMapEntryIfExists(identifier: Identifier): SynthIdentifier { + const originalNode = getOriginalNode(identifier); const symbol = getSymbol(originalNode); - const identifier = node; if (!symbol) { return { identifier, types, numberOfAssignmentsOriginal }; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 51a1701c351dd..5d94ac3f47426 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -115,7 +115,7 @@ namespace ts { function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]): void { - const functionType = node.type ? checker.getTypeFromTypeNode(node.type) : undefined; + const functionType = checker.getTypeAtLocation(node); if (isAsyncFunction(node) || !node.body || !functionType) { return; } From 8bcac6915d6be76ad643ae6a268aac71b5e9bc5a Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 9 Aug 2018 17:48:05 -0700 Subject: [PATCH 194/196] added tests and baselines --- .../unittests/convertToAsyncFunction.ts | 7 +++++++ .../convertToAsyncFunction_ResRejNoArgsArrow.js | 17 +++++++++++++++++ .../convertToAsyncFunction_ResRejNoArgsArrow.ts | 17 +++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRejNoArgsArrow.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRejNoArgsArrow.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index f0a27511d9113..342a6cba0ff35 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -1206,6 +1206,13 @@ function [#|f|]() { } `); + _testConvertToAsyncFunction("convertToAsyncFunction_ResRejNoArgsArrow", ` + function [#|f|]() { + return Promise.resolve().then(() => 1, () => "a"); + } +`); + + }); function _testConvertToAsyncFunction(caption: string, text: string) { diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRejNoArgsArrow.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRejNoArgsArrow.js new file mode 100644 index 0000000000000..c874c6ea61240 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRejNoArgsArrow.js @@ -0,0 +1,17 @@ +// ==ORIGINAL== + + function /*[#|*/f/*|]*/() { + return Promise.resolve().then(() => 1, () => "a"); + } + +// ==ASYNC FUNCTION::Convert to async function== + + async function f() { + try { + await Promise.resolve(); + return 1; + } + catch (e) { + return "a"; + } + } diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRejNoArgsArrow.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRejNoArgsArrow.ts new file mode 100644 index 0000000000000..c874c6ea61240 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ResRejNoArgsArrow.ts @@ -0,0 +1,17 @@ +// ==ORIGINAL== + + function /*[#|*/f/*|]*/() { + return Promise.resolve().then(() => 1, () => "a"); + } + +// ==ASYNC FUNCTION::Convert to async function== + + async function f() { + try { + await Promise.resolve(); + return 1; + } + catch (e) { + return "a"; + } + } From 05cb7bf75881581b83275ee687ac9f19af851f77 Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Thu, 9 Aug 2018 17:57:17 -0700 Subject: [PATCH 195/196] Got rid of unnecessary function and fixed linting errors --- .../codefixes/convertToAsyncFunction.ts | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 57662693d9f9f..195586c6b6eba 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -292,7 +292,7 @@ namespace ts.codefix { const tryBlock = createBlock(transformExpression(node.expression, transformer, node, prevArgName)); const transformationBody = getTransformationBody(func, prevArgName, argName, node, transformer); - const catchArg = argName.identifier.text.length > 0 ? argName.identifier.text : "e"; + const catchArg = argName.identifier.text.length > 0 ? argName.identifier.text : "e"; const catchClause = createCatchClause(catchArg, createBlock(transformationBody)); /* @@ -328,10 +328,10 @@ namespace ts.codefix { const transformationBody2 = getTransformationBody(rej, prevArgName, argNameRej, node, transformer); - const catchArg = argNameRej.identifier.text.length > 0 ? argNameRej.identifier.text : "e"; + const catchArg = argNameRej.identifier.text.length > 0 ? argNameRej.identifier.text : "e"; const catchClause = createCatchClause(catchArg, createBlock(transformationBody2)); - return [createTry(tryBlock, catchClause, undefined) as Statement]; + return [createTry(tryBlock, catchClause, /* finallyBlock */ undefined) as Statement]; } else { return transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody); @@ -398,12 +398,10 @@ namespace ts.codefix { case SyntaxKind.ArrowFunction: // Arrow functions with block bodies { } will enter this control flow if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) { - const indices = getReturnStatementsWithPromiseHandlersIndices(func.body); let refactoredStmts: Statement[] = []; - for (let i = 0; i < func.body.statements.length; i++) { - const statement = func.body.statements[i]; - if (indices.some(elem => elem === i)) { + for (const statement of func.body.statements) { + if (getReturnStatementsWithPromiseHandlers(statement).length) { refactoredStmts = refactoredStmts.concat(getInnerTransformationBody(transformer, [statement], prevArgName)); } else { @@ -444,16 +442,6 @@ namespace ts.codefix { return callSignatures && callSignatures[callSignatures.length - 1]; } - function getReturnStatementsWithPromiseHandlersIndices(block: Block): number[] { - const indices: number[] = []; - for (let i = 0; i < block.statements.length; i++) { - const statement = block.statements[i]; - if (getReturnStatementsWithPromiseHandlers(statement).length) { - indices.push(i); - } - } - return indices; - } function removeReturns(stmts: NodeArray, prevArgName: Identifier, constIdentifiers: Identifier[]): NodeArray { const ret: Statement[] = []; From 12bd6fc81d9d3dc5f2cc05ac00741271e0469c4b Mon Sep 17 00:00:00 2001 From: Elizabeth Dinella Date: Fri, 10 Aug 2018 15:31:26 -0700 Subject: [PATCH 196/196] Fixed linting error --- src/services/suggestionDiagnostics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 5d94ac3f47426..51a1701c351dd 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -115,7 +115,7 @@ namespace ts { function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]): void { - const functionType = checker.getTypeAtLocation(node); + const functionType = node.type ? checker.getTypeFromTypeNode(node.type) : undefined; if (isAsyncFunction(node) || !node.body || !functionType) { return; }