From d21c0783633680be1ddbc2d73db52e094ab3fa02 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Wed, 15 Aug 2018 16:19:54 -0700 Subject: [PATCH 1/8] Test for existence of diagnostic when running tests --- src/testRunner/unittests/convertToAsyncFunction.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index f39b8080ed234..f3bdc889c34db 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -362,11 +362,11 @@ interface Array {}` const diagnostics = languageService.getSuggestionDiagnostics(f.path); const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === description.message); - assert.isNotNull(diagnostic); + assert.exists(diagnostic); const actions = codefix.getFixes(context); const action = find(actions, action => action.description === description.message)!; - assert.isNotNull(action); + assert.exists(action); const data: string[] = []; data.push(`// ==ORIGINAL==`); From cc4e1f833e1d15535e6aff11f12fa13973c9728f Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Thu, 16 Aug 2018 08:53:48 -0700 Subject: [PATCH 2/8] Look for correct description --- src/testRunner/unittests/convertToAsyncFunction.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index f3bdc889c34db..a1e8169e45d56 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -319,7 +319,7 @@ interface String { charAt: any; } interface Array {}` }; - function testConvertToAsyncFunction(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage, includeLib?: boolean) { + function testConvertToAsyncFunction(caption: string, text: string, baselineFolder: string, diagnosticDescription: DiagnosticMessage, codeFixDescription: DiagnosticMessage, includeLib?: boolean) { const t = getTest(text); const selectionRange = t.ranges.get("selection")!; if (!selectionRange) { @@ -361,11 +361,11 @@ interface Array {}` }; const diagnostics = languageService.getSuggestionDiagnostics(f.path); - const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === description.message); + const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === diagnosticDescription.message); assert.exists(diagnostic); const actions = codefix.getFixes(context); - const action = find(actions, action => action.description === description.message)!; + const action = find(actions, action => action.description === codeFixDescription.message)!; assert.exists(action); const data: string[] = []; @@ -380,7 +380,7 @@ interface Array {}` const diagProgram = makeProgram({ path, content: newText }, includeLib)!; assert.isFalse(hasSyntacticDiagnostics(diagProgram)); - Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, data.join(newLineCharacter)); + Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, () => data.join(newLineCharacter)); } function makeProgram(f: { path: string, content: string }, includeLib?: boolean) { @@ -1182,7 +1182,7 @@ function [#|f|]() { }); function _testConvertToAsyncFunction(caption: string, text: string) { - testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.Convert_to_async_function, /*includeLib*/ true); + testConvertToAsyncFunction(caption, text, "convertToAsyncFunction", Diagnostics.This_may_be_converted_to_an_async_function, Diagnostics.Convert_to_async_function, /*includeLib*/ true); } function _testConvertToAsyncFunctionFailed(caption: string, text: string) { From 158f0b0c0b9f7a14b4af10041c033418ce96e61a Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Tue, 28 Aug 2018 14:28:11 -0700 Subject: [PATCH 3/8] Allow codefix to apply to function expression in variable declaration --- src/services/codefixes/convertToAsyncFunction.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 195586c6b6eba..1650baac0ea41 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -42,7 +42,16 @@ 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 functionToConvert: FunctionLikeDeclaration = getContainingFunction(getTokenAtPosition(sourceFile, position)) as FunctionLikeDeclaration; + const tokenAtPosition = getTokenAtPosition(sourceFile, position); + let functionToConvert: FunctionLikeDeclaration; + if (isIdentifier(tokenAtPosition) && isVariableDeclaration(tokenAtPosition.parent) && + tokenAtPosition.parent.initializer && isFunctionLikeDeclaration(tokenAtPosition.parent.initializer)) { + functionToConvert = tokenAtPosition.parent.initializer; + } + else { + functionToConvert = getContainingFunction(getTokenAtPosition(sourceFile, position)) as FunctionLikeDeclaration; + } + if (!functionToConvert) { return; } From 3ad6a66e69bc31d780eb1bef19dbdbf2f70c2893 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Tue, 28 Aug 2018 14:28:58 -0700 Subject: [PATCH 4/8] Add tests and validate diagnostic spans --- .../unittests/convertToAsyncFunction.ts | 16 ++++++++++++++++ ...tToAsyncFunction_ArrowFunctionNoAnnotation.js | 11 +++++++++++ ...tToAsyncFunction_ArrowFunctionNoAnnotation.ts | 11 +++++++++++ ...oAsyncFunction_basicNoReturnTypeAnnotation.js | 11 +++++++++++ ...oAsyncFunction_basicNoReturnTypeAnnotation.ts | 11 +++++++++++ ...rtToAsyncFunction_simpleFunctionExpression.js | 12 ++++++++++++ ...rtToAsyncFunction_simpleFunctionExpression.ts | 12 ++++++++++++ 7 files changed, 84 insertions(+) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunctionNoAnnotation.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunctionNoAnnotation.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicNoReturnTypeAnnotation.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicNoReturnTypeAnnotation.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_simpleFunctionExpression.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_simpleFunctionExpression.ts diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index a1e8169e45d56..186b9059a3d7b 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -363,6 +363,8 @@ interface Array {}` const diagnostics = languageService.getSuggestionDiagnostics(f.path); const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === diagnosticDescription.message); assert.exists(diagnostic); + assert.equal(diagnostic!.start, context.span.start); + assert.equal(diagnostic!.length, context.span.length); const actions = codefix.getFixes(context); const action = find(actions, action => action.description === codeFixDescription.message)!; @@ -423,6 +425,10 @@ interface Array {}` _testConvertToAsyncFunction("convertToAsyncFunction_basic", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(result => { console.log(result) }); +}`); + _testConvertToAsyncFunction("convertToAsyncFunction_basicNoReturnTypeAnnotation", ` +function [#|f|]() { + return fetch('https://typescriptlang.org').then(result => { console.log(result) }); }`); _testConvertToAsyncFunction("convertToAsyncFunction_basicWithComments", ` function [#|f|](): Promise{ @@ -436,6 +442,10 @@ function [#|f|](): Promise{ _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunction", ` [#|():Promise => {|] return fetch('https://typescriptlang.org').then(result => console.log(result)); +}`); + _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunctionNoAnnotation", ` +[#|() => {|] + return fetch('https://typescriptlang.org').then(result => console.log(result)); }`); _testConvertToAsyncFunction("convertToAsyncFunction_Catch", ` function [#|f|]():Promise { @@ -1178,6 +1188,12 @@ function [#|f|]() { } `); + _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpression", ` +const [#|foo|] = function () { + return fetch('https://typescriptlang.org').then(result => { console.log(result) }); +} +`); + }); diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunctionNoAnnotation.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunctionNoAnnotation.js new file mode 100644 index 0000000000000..500d546971bfb --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunctionNoAnnotation.js @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +/*[#|*/() => {/*|]*/ + return fetch('https://typescriptlang.org').then(result => console.log(result)); +} +// ==ASYNC FUNCTION::Convert to async function== + +async () => { + 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_ArrowFunctionNoAnnotation.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunctionNoAnnotation.ts new file mode 100644 index 0000000000000..500d546971bfb --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_ArrowFunctionNoAnnotation.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +/*[#|*/() => {/*|]*/ + return fetch('https://typescriptlang.org').then(result => console.log(result)); +} +// ==ASYNC FUNCTION::Convert to async function== + +async () => { + 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_basicNoReturnTypeAnnotation.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicNoReturnTypeAnnotation.js new file mode 100644 index 0000000000000..8aec78c667066 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicNoReturnTypeAnnotation.js @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return fetch('https://typescriptlang.org').then(result => { console.log(result) }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + const result = await fetch('https://typescriptlang.org'); + console.log(result); +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicNoReturnTypeAnnotation.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicNoReturnTypeAnnotation.ts new file mode 100644 index 0000000000000..8aec78c667066 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_basicNoReturnTypeAnnotation.ts @@ -0,0 +1,11 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return fetch('https://typescriptlang.org').then(result => { console.log(result) }); +} +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + const result = await fetch('https://typescriptlang.org'); + console.log(result); +} \ No newline at end of file diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_simpleFunctionExpression.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_simpleFunctionExpression.js new file mode 100644 index 0000000000000..a92497ca937d9 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_simpleFunctionExpression.js @@ -0,0 +1,12 @@ +// ==ORIGINAL== + +const /*[#|*/foo/*|]*/ = function () { + return fetch('https://typescriptlang.org').then(result => { console.log(result) }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +const foo = async function () { + const result = await fetch('https://typescriptlang.org'); + console.log(result); +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_simpleFunctionExpression.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_simpleFunctionExpression.ts new file mode 100644 index 0000000000000..a92497ca937d9 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_simpleFunctionExpression.ts @@ -0,0 +1,12 @@ +// ==ORIGINAL== + +const /*[#|*/foo/*|]*/ = function () { + return fetch('https://typescriptlang.org').then(result => { console.log(result) }); +} + +// ==ASYNC FUNCTION::Convert to async function== + +const foo = async function () { + const result = await fetch('https://typescriptlang.org'); + console.log(result); +} From 97e539339ddf175269b223fbbef7523aa791d6a0 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Tue, 28 Aug 2018 14:37:09 -0700 Subject: [PATCH 5/8] Add comment explaining special casing --- src/services/codefixes/convertToAsyncFunction.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 1650baac0ea41..6162e311266a7 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -44,6 +44,8 @@ namespace ts.codefix { // get the function declaration - returns a promise const tokenAtPosition = getTokenAtPosition(sourceFile, position); let functionToConvert: FunctionLikeDeclaration; + + // if the parent of a FunctionLikeDeclaration is a variable declaration, the convertToAsync diagnostic will be reported on the variable name if (isIdentifier(tokenAtPosition) && isVariableDeclaration(tokenAtPosition.parent) && tokenAtPosition.parent.initializer && isFunctionLikeDeclaration(tokenAtPosition.parent.initializer)) { functionToConvert = tokenAtPosition.parent.initializer; From bb892d951d59496566d38e117cad3cfe038569e8 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Wed, 29 Aug 2018 15:54:19 -0700 Subject: [PATCH 6/8] Use non-diagnostics-producing typechecker to get type --- src/services/suggestionDiagnostics.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 51a1701c351dd..167bcb6bbac75 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -3,7 +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(); + const checker = program.getTypeChecker(); if (sourceFile.commonJsModuleIndicator && (programContainsEs6Modules(program) || compilerOptionsIndicateEs6Modules(program.getCompilerOptions())) && @@ -115,11 +115,12 @@ namespace ts { function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]): void { - const functionType = node.type ? checker.getTypeFromTypeNode(node.type) : undefined; - if (isAsyncFunction(node) || !node.body || !functionType) { + if (isAsyncFunction(node) || !node.body) { return; } + const functionType = checker.getTypeAtLocation(node); + const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call); const returnType = callSignatures.length ? checker.getReturnTypeOfSignature(callSignatures[0]) : undefined; From f4765a6ea3435f8d6691325c7a19d3be4db243c8 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Fri, 24 Aug 2018 09:05:13 -0700 Subject: [PATCH 7/8] Fix error introduced by rebase --- src/testRunner/unittests/convertToAsyncFunction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testRunner/unittests/convertToAsyncFunction.ts b/src/testRunner/unittests/convertToAsyncFunction.ts index 186b9059a3d7b..99788e1310e98 100644 --- a/src/testRunner/unittests/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/convertToAsyncFunction.ts @@ -382,7 +382,7 @@ interface Array {}` const diagProgram = makeProgram({ path, content: newText }, includeLib)!; assert.isFalse(hasSyntacticDiagnostics(diagProgram)); - Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, () => data.join(newLineCharacter)); + Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, data.join(newLineCharacter)); } function makeProgram(f: { path: string, content: string }, includeLib?: boolean) { From 64bbf8925cc91e4fd528584ab9984b7a13f014f0 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Thu, 30 Aug 2018 16:53:46 -0700 Subject: [PATCH 8/8] Allow for undefined in type --- 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 6162e311266a7..76cdf4714660a 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -43,7 +43,7 @@ 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 tokenAtPosition = getTokenAtPosition(sourceFile, position); - let functionToConvert: FunctionLikeDeclaration; + let functionToConvert: FunctionLikeDeclaration | undefined; // if the parent of a FunctionLikeDeclaration is a variable declaration, the convertToAsync diagnostic will be reported on the variable name if (isIdentifier(tokenAtPosition) && isVariableDeclaration(tokenAtPosition.parent) && @@ -51,7 +51,7 @@ namespace ts.codefix { functionToConvert = tokenAtPosition.parent.initializer; } else { - functionToConvert = getContainingFunction(getTokenAtPosition(sourceFile, position)) as FunctionLikeDeclaration; + functionToConvert = tryCast(getContainingFunction(getTokenAtPosition(sourceFile, position)), isFunctionLikeDeclaration); } if (!functionToConvert) {