Skip to content

Commit ac57f0a

Browse files
committed
Add refactoring to convert list of signatures to single overload
1 parent 6b754a9 commit ac57f0a

File tree

5 files changed

+223
-0
lines changed

5 files changed

+223
-0
lines changed

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -5697,6 +5697,10 @@
56975697
"category": "Message",
56985698
"code": 95117
56995699
},
5700+
"Convert overload list to single signature": {
5701+
"category": "Message",
5702+
"code": 95118
5703+
},
57005704

57015705
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
57025706
"category": "Error",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/* @internal */
2+
namespace ts.refactor.addOrRemoveBracesToArrowFunction {
3+
const refactorName = "Convert overload list to single signature";
4+
const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message;
5+
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
6+
7+
8+
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
9+
const { file, startPosition, program } = context;
10+
const info = getConvertableOverloadListAtPosition(file, startPosition, program);
11+
if (!info) return emptyArray;
12+
13+
return [{
14+
name: refactorName,
15+
description: refactorDescription,
16+
actions: [{
17+
name: refactorName,
18+
description: refactorDescription
19+
}]
20+
}];
21+
}
22+
23+
function getEditsForAction(context: RefactorContext): RefactorEditInfo | undefined {
24+
const { file, startPosition, program } = context;
25+
const signatureDecls = getConvertableOverloadListAtPosition(file, startPosition, program);
26+
if (!signatureDecls) return undefined;
27+
28+
const lastDeclaration = signatureDecls[signatureDecls.length - 1];
29+
let updated = lastDeclaration;
30+
switch (lastDeclaration.kind) {
31+
case SyntaxKind.MethodSignature: {
32+
updated = updateMethodSignature(
33+
lastDeclaration,
34+
lastDeclaration.typeParameters,
35+
getNewParametersForCombinedSignature(signatureDecls),
36+
lastDeclaration.type,
37+
lastDeclaration.name,
38+
lastDeclaration.questionToken
39+
);
40+
break;
41+
}
42+
case SyntaxKind.CallSignature: {
43+
updated = updateCallSignature(
44+
lastDeclaration,
45+
lastDeclaration.typeParameters,
46+
getNewParametersForCombinedSignature(signatureDecls),
47+
lastDeclaration.type,
48+
);
49+
break;
50+
}
51+
case SyntaxKind.ConstructSignature: {
52+
updated = updateConstructSignature(
53+
lastDeclaration,
54+
lastDeclaration.typeParameters,
55+
getNewParametersForCombinedSignature(signatureDecls),
56+
lastDeclaration.type,
57+
);
58+
break;
59+
}
60+
case SyntaxKind.FunctionDeclaration: {
61+
updated = updateFunctionDeclaration(
62+
lastDeclaration,
63+
lastDeclaration.decorators,
64+
lastDeclaration.modifiers,
65+
lastDeclaration.asteriskToken,
66+
lastDeclaration.name,
67+
lastDeclaration.typeParameters,
68+
getNewParametersForCombinedSignature(signatureDecls),
69+
lastDeclaration.type,
70+
lastDeclaration.body
71+
);
72+
break;
73+
}
74+
default: return Debug.failBadSyntaxKind(lastDeclaration, "Unhandled signature kind in overload list conversion refactoring");
75+
}
76+
77+
if (updated === lastDeclaration) {
78+
return; // No edits to apply, do nothing
79+
}
80+
81+
const edits = textChanges.ChangeTracker.with(context, t => {
82+
t.replaceNodeRange(file, signatureDecls[0], signatureDecls[signatureDecls.length - 1], updated);
83+
});
84+
85+
return { renameFilename: undefined, renameLocation: undefined, edits };
86+
87+
function getNewParametersForCombinedSignature(signatureDeclarations: (MethodSignature | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]): NodeArray<ParameterDeclaration> {
88+
return createNodeArray([
89+
createParameter(
90+
/*decorators*/ undefined,
91+
/*modifiers*/ undefined,
92+
createToken(SyntaxKind.DotDotDotToken),
93+
"args",
94+
/*questionToken*/ undefined,
95+
createUnionTypeNode(map(signatureDeclarations, convertSignatureParametersToTuple))
96+
)
97+
]);
98+
}
99+
100+
function convertSignatureParametersToTuple(decl: MethodSignature | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionDeclaration): TupleTypeNode {
101+
return setEmitFlags(createTupleTypeNode(map(decl.parameters, convertParameterToNamedTupleMember)), EmitFlags.SingleLine);
102+
}
103+
104+
function convertParameterToNamedTupleMember(p: ParameterDeclaration): NamedTupleMember {
105+
Debug.assert(isIdentifier(p.name)); // This is checked during refactoring applicability checking
106+
return setTextRange(createNamedTupleMember(
107+
p.dotDotDotToken,
108+
p.name,
109+
p.questionToken,
110+
p.type || createKeywordTypeNode(SyntaxKind.AnyKeyword)
111+
), p);
112+
}
113+
}
114+
115+
function getConvertableOverloadListAtPosition(file: SourceFile, startPosition: number, program: Program) {
116+
const node = getTokenAtPosition(file, startPosition);
117+
const containingDecl = findAncestor(node, n => isFunctionLikeDeclaration(n));
118+
if (!containingDecl) {
119+
return;
120+
}
121+
const checker = program.getTypeChecker();
122+
const signatureSymbol = containingDecl.symbol;
123+
if (!signatureSymbol) {
124+
return;
125+
}
126+
const decls = signatureSymbol.declarations;
127+
if (length(decls) <= 1) {
128+
return;
129+
}
130+
if (!every(decls, d => getSourceFileOfNode(d) === file)) {
131+
return;
132+
}
133+
const kindOne = decls[0].kind;
134+
if (kindOne !== SyntaxKind.MethodSignature && kindOne !== SyntaxKind.CallSignature && kindOne !== SyntaxKind.ConstructSignature && kindOne !== SyntaxKind.FunctionDeclaration) {
135+
return;
136+
}
137+
if (!every(decls, d => d.kind === kindOne)) {
138+
return;
139+
}
140+
const signatureDecls = decls as (MethodSignature | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[];
141+
if (some(signatureDecls, d => !!d.typeParameters || some(d.parameters, p => !!p.decorators || !!p.modifiers || !isIdentifier(p.name)))) {
142+
return;
143+
}
144+
const signatures = mapDefined(signatureDecls, d => checker.getSignatureFromDeclaration(d));
145+
if (length(signatures) !== length(decls)) {
146+
return;
147+
}
148+
const returnOne = checker.getReturnTypeOfSignature(signatures[0]);
149+
if (!every(signatures, s => checker.getReturnTypeOfSignature(s) === returnOne)) {
150+
return;
151+
}
152+
153+
return signatureDecls;
154+
}
155+
}

src/services/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
"codefixes/fixExpectedComma.ts",
103103
"refactors/convertExport.ts",
104104
"refactors/convertImport.ts",
105+
"refactors/convertOverloadListToSingleSignature.ts",
105106
"refactors/extractSymbol.ts",
106107
"refactors/extractType.ts",
107108
"refactors/generateGetAccessorAndSetAccessor.ts",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
/////*a*/declare function foo(): void;
4+
////declare function foo(a: string): void;
5+
////declare function foo(a: number, b: number): void;
6+
////declare function foo(...rest: symbol[]): void;/*b*/
7+
8+
goTo.select("a", "b");
9+
edit.applyRefactor({
10+
refactorName: "Convert overload list to single signature",
11+
actionName: "Convert overload list to single signature",
12+
actionDescription: ts.Diagnostics.Convert_overload_list_to_single_signature.message,
13+
newContent: `declare function foo(...args: [] | [a: string] | [a: number, b: number] | [...rest: symbol[]]): void;`,
14+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
/////*a*/declare function foo(): void;
4+
/////**
5+
//// * @param a a string param doc
6+
//// */
7+
////declare function foo(a: string): void;
8+
/////**
9+
//// * @param a a number param doc
10+
//// * @param b b number param doc
11+
//// */
12+
////declare function foo(a: number, b: number): void;
13+
/////**
14+
//// * @param rest rest param doc
15+
//// */
16+
////declare function foo(...rest: symbol[]): void;/*b*/
17+
18+
goTo.select("a", "b");
19+
edit.applyRefactor({
20+
refactorName: "Convert overload list to single signature",
21+
actionName: "Convert overload list to single signature",
22+
actionDescription: ts.Diagnostics.Convert_overload_list_to_single_signature.message,
23+
// Aspirational:
24+
// newContent: `declare function foo(...args: [] | [
25+
// /**
26+
// * a string param doc
27+
// */
28+
// a: string
29+
//] | [
30+
// /**
31+
// * a number param doc
32+
// */
33+
// a: number,
34+
// /**
35+
// * b number param doc
36+
// */
37+
// b: number
38+
//] | [
39+
// /**
40+
// * rest param doc
41+
// */
42+
// ...rest: symbol[]
43+
//]): void;`,
44+
// Actual:
45+
newContent: `/**
46+
* @param rest rest param doc
47+
*/
48+
declare function foo(...args: [] | [a: string] | [a: number, b: number] | [...rest: symbol[]]): void;`
49+
});

0 commit comments

Comments
 (0)