diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0d7448ab650ab..0a67f42934ed8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14153,6 +14153,17 @@ namespace ts { Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature, )); } + if ((getFunctionFlags(node) & FunctionFlags.Async) === 0 + // exclude cases where source itself is promisy - this way we don't make a suggestion when relating + // an IPromise and a Promise that are slightly different + && !getTypeOfPropertyOfType(sourceReturn, "then" as __String) + && checkTypeRelatedTo(createPromiseType(sourceReturn), targetReturn, relation, /*errorNode*/ undefined) + ) { + addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( + node, + Diagnostics.Did_you_mean_to_mark_this_function_as_async + )); + } return true; } } diff --git a/src/services/codefixes/addMissingAsync.ts b/src/services/codefixes/addMissingAsync.ts new file mode 100644 index 0000000000000..59b0f2f279da4 --- /dev/null +++ b/src/services/codefixes/addMissingAsync.ts @@ -0,0 +1,87 @@ +/* @internal */ +namespace ts.codefix { + type ContextualTrackChangesFunction = (cb: (changeTracker: textChanges.ChangeTracker) => void) => FileTextChanges[]; + const fixId = "addMissingAsync"; + const errorCodes = [ + Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code, + Diagnostics.Type_0_is_not_assignable_to_type_1.code, + Diagnostics.Type_0_is_not_comparable_to_type_1.code + ]; + + registerCodeFix({ + fixIds: [fixId], + errorCodes, + getCodeActions: context => { + const { sourceFile, errorCode, cancellationToken, program, span } = context; + const diagnostic = find(program.getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile, cancellationToken), getIsMatchingAsyncError(span, errorCode)); + const directSpan = diagnostic && diagnostic.relatedInformation && find(diagnostic.relatedInformation, r => r.code === Diagnostics.Did_you_mean_to_mark_this_function_as_async.code) as TextSpan | undefined; + + const decl = getFixableErrorSpanDeclaration(sourceFile, directSpan); + if (!decl) { + return; + } + + const trackChanges: ContextualTrackChangesFunction = cb => textChanges.ChangeTracker.with(context, cb); + return [getFix(context, decl, trackChanges)]; + }, + getAllCodeActions: context => { + const { sourceFile } = context; + const fixedDeclarations = createMap(); + return codeFixAll(context, errorCodes, (t, diagnostic) => { + const span = diagnostic.relatedInformation && find(diagnostic.relatedInformation, r => r.code === Diagnostics.Did_you_mean_to_mark_this_function_as_async.code) as TextSpan | undefined; + const decl = getFixableErrorSpanDeclaration(sourceFile, span); + if (!decl) { + return; + } + const trackChanges: ContextualTrackChangesFunction = cb => (cb(t), []); + return getFix(context, decl, trackChanges, fixedDeclarations); + }); + }, + }); + + type FixableDeclaration = ArrowFunction | FunctionDeclaration | FunctionExpression | MethodDeclaration; + function getFix(context: CodeFixContext | CodeFixAllContext, decl: FixableDeclaration, trackChanges: ContextualTrackChangesFunction, fixedDeclarations?: Map) { + const changes = trackChanges(t => makeChange(t, context.sourceFile, decl, fixedDeclarations)); + return createCodeFixAction(fixId, changes, Diagnostics.Add_async_modifier_to_containing_function, fixId, Diagnostics.Add_all_missing_async_modifiers); + } + + function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, insertionSite: FixableDeclaration, fixedDeclarations?: Map) { + if (fixedDeclarations) { + if (fixedDeclarations.has(getNodeId(insertionSite).toString())) { + return; + } + } + fixedDeclarations?.set(getNodeId(insertionSite).toString(), true); + const cloneWithModifier = getSynthesizedDeepClone(insertionSite, /*includeTrivia*/ true); + cloneWithModifier.modifiers = createNodeArray(createModifiersFromModifierFlags(getModifierFlags(insertionSite) | ModifierFlags.Async)); + cloneWithModifier.modifierFlagsCache = 0; + changeTracker.replaceNode( + sourceFile, + insertionSite, + cloneWithModifier); + } + + function getFixableErrorSpanDeclaration(sourceFile: SourceFile, span: TextSpan | undefined): FixableDeclaration | undefined { + if (!span) return undefined; + const token = getTokenAtPosition(sourceFile, span.start); + // Checker has already done work to determine that async might be possible, and has attached + // related info to the node, so start by finding the signature that exactly matches up + // with the diagnostic range. + const decl = findAncestor(token, node => { + if (node.getStart(sourceFile) < span.start || node.getEnd() > textSpanEnd(span)) { + return "quit"; + } + return (isArrowFunction(node) || isMethodDeclaration(node) || isFunctionExpression(node) || isFunctionDeclaration(node)) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile)); + }) as FixableDeclaration | undefined; + + return decl; + } + + function getIsMatchingAsyncError(span: TextSpan, errorCode: number) { + return ({ start, length, relatedInformation, code }: Diagnostic) => + isNumber(start) && isNumber(length) && textSpansEqual({ start, length }, span) && + code === errorCode && + !!relatedInformation && + some(relatedInformation, related => related.code === Diagnostics.Did_you_mean_to_mark_this_function_as_async.code); + } +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 190e61fffcc3c..58e43ee9921d5 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -48,6 +48,7 @@ "refactorProvider.ts", "codefixes/addConvertToUnknownForNonOverlappingTypes.ts", "codefixes/addEmptyExportDeclaration.ts", + "codefixes/addMissingAsync.ts", "codefixes/addMissingAwait.ts", "codefixes/addMissingConst.ts", "codefixes/addMissingDeclareProperty.ts", diff --git a/tests/baselines/reference/errorOnUnionVsObjectShouldDeeplyDisambiguate.errors.txt b/tests/baselines/reference/errorOnUnionVsObjectShouldDeeplyDisambiguate.errors.txt index 62c559e417b7e..ae3564fa70570 100644 --- a/tests/baselines/reference/errorOnUnionVsObjectShouldDeeplyDisambiguate.errors.txt +++ b/tests/baselines/reference/errorOnUnionVsObjectShouldDeeplyDisambiguate.errors.txt @@ -37,41 +37,51 @@ tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts(27,16): err ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:3:8: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:18:10: Did you mean to mark this function as 'async'? c: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:4:8: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:19:10: Did you mean to mark this function as 'async'? d: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:5:8: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:20:10: Did you mean to mark this function as 'async'? e: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:6:8: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:21:10: Did you mean to mark this function as 'async'? f: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:7:8: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:22:10: Did you mean to mark this function as 'async'? g: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:8:8: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:23:10: Did you mean to mark this function as 'async'? h: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:9:8: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:24:10: Did you mean to mark this function as 'async'? i: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:10:8: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:25:10: Did you mean to mark this function as 'async'? j: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:11:8: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:26:10: Did you mean to mark this function as 'async'? k: () => 123 ~~~ !!! error TS2322: Type 'number' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:12:8: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:27:10: Did you mean to mark this function as 'async'? } } \ No newline at end of file diff --git a/tests/baselines/reference/errorOnUnionVsObjectShouldDeeplyDisambiguate2.errors.txt b/tests/baselines/reference/errorOnUnionVsObjectShouldDeeplyDisambiguate2.errors.txt index 694a367bf9b76..41970afe08654 100644 --- a/tests/baselines/reference/errorOnUnionVsObjectShouldDeeplyDisambiguate2.errors.txt +++ b/tests/baselines/reference/errorOnUnionVsObjectShouldDeeplyDisambiguate2.errors.txt @@ -37,41 +37,51 @@ tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts(27,14): er ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:3:6: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:18:8: Did you mean to mark this function as 'async'? c: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:4:6: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:19:8: Did you mean to mark this function as 'async'? d: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:5:6: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:20:8: Did you mean to mark this function as 'async'? e: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:6:6: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:21:8: Did you mean to mark this function as 'async'? f: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:7:6: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:22:8: Did you mean to mark this function as 'async'? g: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:8:6: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:23:8: Did you mean to mark this function as 'async'? h: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:9:6: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:24:8: Did you mean to mark this function as 'async'? i: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:10:6: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:25:8: Did you mean to mark this function as 'async'? j: () => "hello", ~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:11:6: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:26:8: Did you mean to mark this function as 'async'? k: () => 123 ~~~ !!! error TS2322: Type 'number' is not assignable to type 'Promise'. !!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:12:6: The expected type comes from the return type of this signature. +!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate2.ts:27:8: Did you mean to mark this function as 'async'? } } \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingAsync.ts b/tests/cases/fourslash/codeFixAddMissingAsync.ts new file mode 100644 index 0000000000000..f4c6876a17cff --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingAsync.ts @@ -0,0 +1,26 @@ +/// +////interface Stuff { +//// b: () => Promise; +////} +//// +////function foo(): Stuff | Date { +//// return { +//// b: () => "hello", +//// } +////} + +verify.codeFix({ + description: ts.Diagnostics.Add_async_modifier_to_containing_function.message, + index: 0, + newFileContent: +`interface Stuff { + b: () => Promise; +} + +function foo(): Stuff | Date { + return { + b: async () => "hello", + } +}` + }); + \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingAsync2.ts b/tests/cases/fourslash/codeFixAddMissingAsync2.ts new file mode 100644 index 0000000000000..3f610ae141bbd --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingAsync2.ts @@ -0,0 +1,26 @@ +/// +////interface Stuff { +//// b: () => Promise; +////} +//// +////function foo(): Stuff | Date { +//// return { +//// b: _ => "hello", +//// } +////} + +verify.codeFix({ + description: ts.Diagnostics.Add_async_modifier_to_containing_function.message, + index: 0, + newFileContent: +`interface Stuff { + b: () => Promise; +} + +function foo(): Stuff | Date { + return { + b: async (_) => "hello", + } +}` + }); + \ No newline at end of file