-
Notifications
You must be signed in to change notification settings - Fork 12.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add refactoring to convert list of signatures to single overload
- Loading branch information
Showing
5 changed files
with
223 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
155 changes: 155 additions & 0 deletions
155
src/services/refactors/convertOverloadListToSingleSignature.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
tests/cases/fourslash/refactorOverloadListToSingleSignature1.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
49
tests/cases/fourslash/refactorOverloadListToSingleSignature2.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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;` | ||
}); |