Skip to content

Commit 65a28e8

Browse files
committed
feat(40750): add refactoring to infer a return type annotation to a function
1 parent c88e44a commit 65a28e8

14 files changed

+277
-0
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5955,6 +5955,10 @@
59555955
"category": "Message",
59565956
"code": 95144
59575957
},
5958+
"Infer function return type": {
5959+
"category": "Message",
5960+
"code": 95145
5961+
},
59585962

59595963
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
59605964
"category": "Error",
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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.returnType));
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 ConvertableDeclaration =
33+
| FunctionDeclaration
34+
| FunctionExpression
35+
| ArrowFunction
36+
| MethodDeclaration;
37+
38+
interface Info {
39+
declaration: ConvertableDeclaration;
40+
returnType: 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, isConvertableDeclaration);
48+
if (!declaration || declaration.type) return;
49+
50+
const typeChecker = context.program.getTypeChecker();
51+
const type = typeChecker.getTypeAtLocation(declaration);
52+
const signatures = type.getCallSignatures();
53+
if (!signatures.length) return;
54+
55+
const returnType = typeChecker.typeToTypeNode(first(signatures).getReturnType(), declaration, NodeBuilderFlags.NoTruncation);
56+
if (returnType) {
57+
return { declaration, returnType };
58+
}
59+
}
60+
61+
function isConvertableDeclaration(node: Node): node is ConvertableDeclaration {
62+
switch (node.kind) {
63+
case SyntaxKind.FunctionDeclaration:
64+
case SyntaxKind.FunctionExpression:
65+
case SyntaxKind.ArrowFunction:
66+
case SyntaxKind.MethodDeclaration:
67+
return true;
68+
default:
69+
return false;
70+
}
71+
}
72+
}

src/services/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"refactors/convertParamsToDestructuredObject.ts",
121121
"refactors/convertStringOrTemplateLiteral.ts",
122122
"refactors/convertArrowFunctionOrFunctionExpression.ts",
123+
"refactors/inferFunctionReturnType.ts",
123124
"services.ts",
124125
"breakpoints.ts",
125126
"transform.ts",
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: 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+
});
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<T>/*b*/() {
4+
//// return 1 as T;
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<T>(): T {
14+
return 1 as T;
15+
}`
16+
});

0 commit comments

Comments
 (0)