Skip to content

Commit 0904865

Browse files
authored
feat(40750): add refactoring to infer a return type annotation to a function (microsoft#41052)
1 parent 3192754 commit 0904865

19 files changed

+367
-1
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5979,6 +5979,10 @@
59795979
"category": "Message",
59805980
"code": 95147
59815981
},
5982+
"Infer function return type": {
5983+
"category": "Message",
5984+
"code": 95148
5985+
},
59825986

59835987
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
59845988
"category": "Error",
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/* @internal */
2+
namespace ts.refactor.inferFunctionReturnType {
3+
const refactorName = "Infer function return type";
4+
const refactorDescription = Diagnostics.Infer_function_return_type.message;
5+
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
6+
7+
function getEditsForAction(context: RefactorContext): RefactorEditInfo | undefined {
8+
const info = getInfo(context);
9+
if (info) {
10+
const edits = textChanges.ChangeTracker.with(context, t =>
11+
t.tryInsertTypeAnnotation(context.file, info.declaration, info.returnTypeNode));
12+
return { renameFilename: undefined, renameLocation: undefined, edits };
13+
}
14+
return undefined;
15+
}
16+
17+
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
18+
const info = getInfo(context);
19+
if (info) {
20+
return [{
21+
name: refactorName,
22+
description: refactorDescription,
23+
actions: [{
24+
name: refactorName,
25+
description: refactorDescription
26+
}]
27+
}];
28+
}
29+
return emptyArray;
30+
}
31+
32+
type ConvertibleDeclaration =
33+
| FunctionDeclaration
34+
| FunctionExpression
35+
| ArrowFunction
36+
| MethodDeclaration;
37+
38+
interface Info {
39+
declaration: ConvertibleDeclaration;
40+
returnTypeNode: TypeNode;
41+
}
42+
43+
function getInfo(context: RefactorContext): Info | undefined {
44+
if (isInJSFile(context.file)) return;
45+
46+
const token = getTokenAtPosition(context.file, context.startPosition);
47+
const declaration = findAncestor(token, isConvertibleDeclaration);
48+
if (!declaration || !declaration.body || declaration.type) return;
49+
50+
const typeChecker = context.program.getTypeChecker();
51+
const returnType = tryGetReturnType(typeChecker, declaration);
52+
if (!returnType) return;
53+
54+
const returnTypeNode = typeChecker.typeToTypeNode(returnType, declaration, NodeBuilderFlags.NoTruncation);
55+
if (returnTypeNode) {
56+
return { declaration, returnTypeNode };
57+
}
58+
}
59+
60+
function isConvertibleDeclaration(node: Node): node is ConvertibleDeclaration {
61+
switch (node.kind) {
62+
case SyntaxKind.FunctionDeclaration:
63+
case SyntaxKind.FunctionExpression:
64+
case SyntaxKind.ArrowFunction:
65+
case SyntaxKind.MethodDeclaration:
66+
return true;
67+
default:
68+
return false;
69+
}
70+
}
71+
72+
function tryGetReturnType(typeChecker: TypeChecker, node: ConvertibleDeclaration): Type | undefined {
73+
if (typeChecker.isImplementationOfOverload(node)) {
74+
const signatures = typeChecker.getTypeAtLocation(node).getCallSignatures();
75+
if (signatures.length > 1) {
76+
return typeChecker.getUnionType(mapDefined(signatures, s => s.getReturnType()));
77+
}
78+
}
79+
const signature = typeChecker.getSignatureFromDeclaration(node);
80+
if (signature) {
81+
return typeChecker.getReturnTypeOfSignature(signature);
82+
}
83+
}
84+
}

src/services/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
"refactors/convertParamsToDestructuredObject.ts",
122122
"refactors/convertStringOrTemplateLiteral.ts",
123123
"refactors/convertArrowFunctionOrFunctionExpression.ts",
124+
"refactors/inferFunctionReturnType.ts",
124125
"services.ts",
125126
"breakpoints.ts",
126127
"transform.ts",

tests/cases/fourslash/refactorConvertExport_namedToDefault_alreadyHasDefault.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
////export default function g() {}
66

77
goTo.select("a", "b");
8-
verify.refactorsAvailable([]);
8+
verify.refactorsAvailable(["Infer function return type"]);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function /*a*/foo/*b*/() {
4+
//// return { x: 1, y: 1 };
5+
////}
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Infer function return type",
10+
actionName: "Infer function return type",
11+
actionDescription: "Infer function return type",
12+
newContent:
13+
`function foo(): { x: number; y: number; } {
14+
return { x: 1, y: 1 };
15+
}`
16+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function /*a*/foo/*b*/(x: number) {
4+
//// return x ? x : x > 1;
5+
////}
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Infer function return type",
10+
actionName: "Infer function return type",
11+
actionDescription: "Infer function return type",
12+
newContent:
13+
`function foo(x: number): number | boolean {
14+
return x ? x : x > 1;
15+
}`
16+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////interface F1 { x: number; y: number; }
4+
////type T1 = [number, number];
5+
////
6+
////function /*a*/foo/*b*/(num: number) {
7+
//// switch (num) {
8+
//// case 1:
9+
//// return { x: num, y: num } as F1;
10+
//// case 2:
11+
//// return [num, num] as T1;
12+
//// default:
13+
//// return num;
14+
//// }
15+
////}
16+
17+
goTo.select("a", "b");
18+
edit.applyRefactor({
19+
refactorName: "Infer function return type",
20+
actionName: "Infer function return type",
21+
actionDescription: "Infer function return type",
22+
newContent:
23+
`interface F1 { x: number; y: number; }
24+
type T1 = [number, number];
25+
26+
function foo(num: number): number | F1 | T1 {
27+
switch (num) {
28+
case 1:
29+
return { x: num, y: num } as F1;
30+
case 2:
31+
return [num, num] as T1;
32+
default:
33+
return num;
34+
}
35+
}`
36+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function f(x: number) {
4+
//// return 1;
5+
////}
6+
////function /*a*/f/*b*/(x: number) {
7+
//// return "1";
8+
////}
9+
10+
goTo.select("a", "b");
11+
edit.applyRefactor({
12+
refactorName: "Infer function return type",
13+
actionName: "Infer function return type",
14+
actionDescription: "Infer function return type",
15+
newContent:
16+
`function f(x: number) {
17+
return 1;
18+
}
19+
function f(x: number): string {
20+
return "1";
21+
}`
22+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function /*a*/f/*b*/(x: string): number;
4+
////function f(x: string | number) {
5+
//// return 1;
6+
////}
7+
8+
goTo.select("a", "b");
9+
verify.not.refactorAvailable("Infer function return type");
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function f(x: number): string;
4+
////function f(x: string): number;
5+
////function /*a*/f/*b*/(x: string | number) {
6+
//// return x === 1 ? 1 : "quit";
7+
////}
8+
9+
goTo.select("a", "b");
10+
edit.applyRefactor({
11+
refactorName: "Infer function return type",
12+
actionName: "Infer function return type",
13+
actionDescription: "Infer function return type",
14+
newContent:
15+
`function f(x: number): string;
16+
function f(x: string): number;
17+
function f(x: string | number): string | number {
18+
return x === 1 ? 1 : "quit";
19+
}`
20+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////interface Foo {
4+
//// x: number;
5+
////}
6+
////function f(x: number): Foo;
7+
////function f(x: string): number;
8+
////function /*a*/f/*b*/(x: string | number) {
9+
//// return x === 1 ? 1 : { x };
10+
////}
11+
12+
goTo.select("a", "b");
13+
edit.applyRefactor({
14+
refactorName: "Infer function return type",
15+
actionName: "Infer function return type",
16+
actionDescription: "Infer function return type",
17+
newContent:
18+
`interface Foo {
19+
x: number;
20+
}
21+
function f(x: number): Foo;
22+
function f(x: string): number;
23+
function f(x: string | number): number | Foo {
24+
return x === 1 ? 1 : { x };
25+
}`
26+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////class Foo {
4+
//// /*a*/method/*b*/() {
5+
//// return { x: 1, y: 1 };
6+
//// }
7+
////}
8+
9+
goTo.select("a", "b");
10+
edit.applyRefactor({
11+
refactorName: "Infer function return type",
12+
actionName: "Infer function return type",
13+
actionDescription: "Infer function return type",
14+
newContent:
15+
`class Foo {
16+
method(): { x: number; y: number; } {
17+
return { x: 1, y: 1 };
18+
}
19+
}`
20+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////const foo = /*a*/function/*b*/() {
4+
//// return { x: 1, y: 1 };
5+
////}
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Infer function return type",
10+
actionName: "Infer function return type",
11+
actionDescription: "Infer function return type",
12+
newContent:
13+
`const foo = function(): { x: number; y: number; } {
14+
return { x: 1, y: 1 };
15+
}`
16+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////const foo = /*a*/()/*b*/ => {
4+
//// return { x: 1, y: 1 };
5+
////}
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Infer function return type",
10+
actionName: "Infer function return type",
11+
actionDescription: "Infer function return type",
12+
newContent:
13+
`const foo = (): { x: number; y: number; } => {
14+
return { x: 1, y: 1 };
15+
}`
16+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function /*a*/foo/*b*/() {
4+
//// return 1;
5+
////}
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Infer function return type",
10+
actionName: "Infer function return type",
11+
actionDescription: "Infer function return type",
12+
newContent:
13+
`function foo(): number {
14+
return 1;
15+
}`
16+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function /*a*/foo/*b*/() {
4+
//// return "";
5+
////}
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Infer function return type",
10+
actionName: "Infer function return type",
11+
actionDescription: "Infer function return type",
12+
newContent:
13+
`function foo(): string {
14+
return "";
15+
}`
16+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function /*a*/foo/*b*/() {
4+
////}
5+
6+
goTo.select("a", "b");
7+
edit.applyRefactor({
8+
refactorName: "Infer function return type",
9+
actionName: "Infer function return type",
10+
actionDescription: "Infer function return type",
11+
newContent:
12+
`function foo(): void {
13+
}`
14+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////function /*a*/foo/*b*/() {
4+
//// const bar = 1 as any;
5+
//// return bar;
6+
////}
7+
8+
goTo.select("a", "b");
9+
edit.applyRefactor({
10+
refactorName: "Infer function return type",
11+
actionName: "Infer function return type",
12+
actionDescription: "Infer function return type",
13+
newContent:
14+
`function foo(): any {
15+
const bar = 1 as any;
16+
return bar;
17+
}`
18+
});

0 commit comments

Comments
 (0)