Skip to content

Commit

Permalink
Add refactoring to convert list of signatures to single overload
Browse files Browse the repository at this point in the history
  • Loading branch information
weswigham committed May 12, 2020
1 parent 6b754a9 commit ac57f0a
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5697,6 +5697,10 @@
"category": "Message",
"code": 95117
},
"Convert overload list to single signature": {
"category": "Message",
"code": 95118
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
155 changes: 155 additions & 0 deletions src/services/refactors/convertOverloadListToSingleSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/* @internal */
namespace ts.refactor.addOrRemoveBracesToArrowFunction {
const refactorName = "Convert overload list to single signature";
const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message;
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });


function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
const { file, startPosition, program } = context;
const info = getConvertableOverloadListAtPosition(file, startPosition, program);
if (!info) return emptyArray;

return [{
name: refactorName,
description: refactorDescription,
actions: [{
name: refactorName,
description: refactorDescription
}]
}];
}

function getEditsForAction(context: RefactorContext): RefactorEditInfo | undefined {
const { file, startPosition, program } = context;
const signatureDecls = getConvertableOverloadListAtPosition(file, startPosition, program);
if (!signatureDecls) return undefined;

const lastDeclaration = signatureDecls[signatureDecls.length - 1];
let updated = lastDeclaration;
switch (lastDeclaration.kind) {
case SyntaxKind.MethodSignature: {
updated = updateMethodSignature(
lastDeclaration,
lastDeclaration.typeParameters,
getNewParametersForCombinedSignature(signatureDecls),
lastDeclaration.type,
lastDeclaration.name,
lastDeclaration.questionToken
);
break;
}
case SyntaxKind.CallSignature: {
updated = updateCallSignature(
lastDeclaration,
lastDeclaration.typeParameters,
getNewParametersForCombinedSignature(signatureDecls),
lastDeclaration.type,
);
break;
}
case SyntaxKind.ConstructSignature: {
updated = updateConstructSignature(
lastDeclaration,
lastDeclaration.typeParameters,
getNewParametersForCombinedSignature(signatureDecls),
lastDeclaration.type,
);
break;
}
case SyntaxKind.FunctionDeclaration: {
updated = updateFunctionDeclaration(
lastDeclaration,
lastDeclaration.decorators,
lastDeclaration.modifiers,
lastDeclaration.asteriskToken,
lastDeclaration.name,
lastDeclaration.typeParameters,
getNewParametersForCombinedSignature(signatureDecls),
lastDeclaration.type,
lastDeclaration.body
);
break;
}
default: return Debug.failBadSyntaxKind(lastDeclaration, "Unhandled signature kind in overload list conversion refactoring");
}

if (updated === lastDeclaration) {
return; // No edits to apply, do nothing
}

const edits = textChanges.ChangeTracker.with(context, t => {
t.replaceNodeRange(file, signatureDecls[0], signatureDecls[signatureDecls.length - 1], updated);
});

return { renameFilename: undefined, renameLocation: undefined, edits };

function getNewParametersForCombinedSignature(signatureDeclarations: (MethodSignature | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]): NodeArray<ParameterDeclaration> {
return createNodeArray([
createParameter(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createToken(SyntaxKind.DotDotDotToken),
"args",
/*questionToken*/ undefined,
createUnionTypeNode(map(signatureDeclarations, convertSignatureParametersToTuple))
)
]);
}

function convertSignatureParametersToTuple(decl: MethodSignature | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionDeclaration): TupleTypeNode {
return setEmitFlags(createTupleTypeNode(map(decl.parameters, convertParameterToNamedTupleMember)), EmitFlags.SingleLine);
}

function convertParameterToNamedTupleMember(p: ParameterDeclaration): NamedTupleMember {
Debug.assert(isIdentifier(p.name)); // This is checked during refactoring applicability checking
return setTextRange(createNamedTupleMember(
p.dotDotDotToken,
p.name,
p.questionToken,
p.type || createKeywordTypeNode(SyntaxKind.AnyKeyword)
), p);
}
}

function getConvertableOverloadListAtPosition(file: SourceFile, startPosition: number, program: Program) {
const node = getTokenAtPosition(file, startPosition);
const containingDecl = findAncestor(node, n => isFunctionLikeDeclaration(n));
if (!containingDecl) {
return;
}
const checker = program.getTypeChecker();
const signatureSymbol = containingDecl.symbol;
if (!signatureSymbol) {
return;
}
const decls = signatureSymbol.declarations;
if (length(decls) <= 1) {
return;
}
if (!every(decls, d => getSourceFileOfNode(d) === file)) {
return;
}
const kindOne = decls[0].kind;
if (kindOne !== SyntaxKind.MethodSignature && kindOne !== SyntaxKind.CallSignature && kindOne !== SyntaxKind.ConstructSignature && kindOne !== SyntaxKind.FunctionDeclaration) {
return;
}
if (!every(decls, d => d.kind === kindOne)) {
return;
}
const signatureDecls = decls as (MethodSignature | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[];
if (some(signatureDecls, d => !!d.typeParameters || some(d.parameters, p => !!p.decorators || !!p.modifiers || !isIdentifier(p.name)))) {
return;
}
const signatures = mapDefined(signatureDecls, d => checker.getSignatureFromDeclaration(d));
if (length(signatures) !== length(decls)) {
return;
}
const returnOne = checker.getReturnTypeOfSignature(signatures[0]);
if (!every(signatures, s => checker.getReturnTypeOfSignature(s) === returnOne)) {
return;
}

return signatureDecls;
}
}
1 change: 1 addition & 0 deletions src/services/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"codefixes/fixExpectedComma.ts",
"refactors/convertExport.ts",
"refactors/convertImport.ts",
"refactors/convertOverloadListToSingleSignature.ts",
"refactors/extractSymbol.ts",
"refactors/extractType.ts",
"refactors/generateGetAccessorAndSetAccessor.ts",
Expand Down
14 changes: 14 additions & 0 deletions tests/cases/fourslash/refactorOverloadListToSingleSignature1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// <reference path='fourslash.ts' />

/////*a*/declare function foo(): void;
////declare function foo(a: string): void;
////declare function foo(a: number, b: number): void;
////declare function foo(...rest: symbol[]): void;/*b*/

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert overload list to single signature",
actionName: "Convert overload list to single signature",
actionDescription: ts.Diagnostics.Convert_overload_list_to_single_signature.message,
newContent: `declare function foo(...args: [] | [a: string] | [a: number, b: number] | [...rest: symbol[]]): void;`,
});
49 changes: 49 additions & 0 deletions tests/cases/fourslash/refactorOverloadListToSingleSignature2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/// <reference path='fourslash.ts' />

/////*a*/declare function foo(): void;
/////**
//// * @param a a string param doc
//// */
////declare function foo(a: string): void;
/////**
//// * @param a a number param doc
//// * @param b b number param doc
//// */
////declare function foo(a: number, b: number): void;
/////**
//// * @param rest rest param doc
//// */
////declare function foo(...rest: symbol[]): void;/*b*/

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert overload list to single signature",
actionName: "Convert overload list to single signature",
actionDescription: ts.Diagnostics.Convert_overload_list_to_single_signature.message,
// Aspirational:
// newContent: `declare function foo(...args: [] | [
// /**
// * a string param doc
// */
// a: string
//] | [
// /**
// * a number param doc
// */
// a: number,
// /**
// * b number param doc
// */
// b: number
//] | [
// /**
// * rest param doc
// */
// ...rest: symbol[]
//]): void;`,
// Actual:
newContent: `/**
* @param rest rest param doc
*/
declare function foo(...args: [] | [a: string] | [a: number, b: number] | [...rest: symbol[]]): void;`
});

0 comments on commit ac57f0a

Please sign in to comment.