1/* @internal */ 2namespace ts.refactor.addOrRemoveBracesToArrowFunction { 3 const refactorName = "Convert overload list to single signature"; 4 const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message; 5 6 const functionOverloadAction = { 7 name: refactorName, 8 description: refactorDescription, 9 kind: "refactor.rewrite.function.overloadList", 10 }; 11 registerRefactor(refactorName, { 12 kinds: [functionOverloadAction.kind], 13 getEditsForAction: getRefactorEditsToConvertOverloadsToOneSignature, 14 getAvailableActions: getRefactorActionsToConvertOverloadsToOneSignature 15 }); 16 17 function getRefactorActionsToConvertOverloadsToOneSignature(context: RefactorContext): readonly ApplicableRefactorInfo[] { 18 const { file, startPosition, program } = context; 19 const info = getConvertableOverloadListAtPosition(file, startPosition, program); 20 if (!info) return emptyArray; 21 22 return [{ 23 name: refactorName, 24 description: refactorDescription, 25 actions: [functionOverloadAction] 26 }]; 27 } 28 29 function getRefactorEditsToConvertOverloadsToOneSignature(context: RefactorContext): RefactorEditInfo | undefined { 30 const { file, startPosition, program } = context; 31 const signatureDecls = getConvertableOverloadListAtPosition(file, startPosition, program); 32 if (!signatureDecls) return undefined; 33 34 const checker = program.getTypeChecker(); 35 36 const lastDeclaration = signatureDecls[signatureDecls.length - 1]; 37 let updated = lastDeclaration; 38 switch (lastDeclaration.kind) { 39 case SyntaxKind.MethodSignature: { 40 updated = factory.updateMethodSignature( 41 lastDeclaration, 42 lastDeclaration.modifiers, 43 lastDeclaration.name, 44 lastDeclaration.questionToken, 45 lastDeclaration.typeParameters, 46 getNewParametersForCombinedSignature(signatureDecls), 47 lastDeclaration.type, 48 ); 49 break; 50 } 51 case SyntaxKind.MethodDeclaration: { 52 updated = factory.updateMethodDeclaration( 53 lastDeclaration, 54 lastDeclaration.modifiers, 55 lastDeclaration.asteriskToken, 56 lastDeclaration.name, 57 lastDeclaration.questionToken, 58 lastDeclaration.typeParameters, 59 getNewParametersForCombinedSignature(signatureDecls), 60 lastDeclaration.type, 61 lastDeclaration.body 62 ); 63 break; 64 } 65 case SyntaxKind.CallSignature: { 66 updated = factory.updateCallSignature( 67 lastDeclaration, 68 lastDeclaration.typeParameters, 69 getNewParametersForCombinedSignature(signatureDecls), 70 lastDeclaration.type, 71 ); 72 break; 73 } 74 case SyntaxKind.Constructor: { 75 updated = factory.updateConstructorDeclaration( 76 lastDeclaration, 77 lastDeclaration.modifiers, 78 getNewParametersForCombinedSignature(signatureDecls), 79 lastDeclaration.body 80 ); 81 break; 82 } 83 case SyntaxKind.ConstructSignature: { 84 updated = factory.updateConstructSignature( 85 lastDeclaration, 86 lastDeclaration.typeParameters, 87 getNewParametersForCombinedSignature(signatureDecls), 88 lastDeclaration.type, 89 ); 90 break; 91 } 92 case SyntaxKind.FunctionDeclaration: { 93 updated = factory.updateFunctionDeclaration( 94 lastDeclaration, 95 lastDeclaration.modifiers, 96 lastDeclaration.asteriskToken, 97 lastDeclaration.name, 98 lastDeclaration.typeParameters, 99 getNewParametersForCombinedSignature(signatureDecls), 100 lastDeclaration.type, 101 lastDeclaration.body 102 ); 103 break; 104 } 105 default: return Debug.failBadSyntaxKind(lastDeclaration, "Unhandled signature kind in overload list conversion refactoring"); 106 } 107 108 if (updated === lastDeclaration) { 109 return; // No edits to apply, do nothing 110 } 111 112 const edits = textChanges.ChangeTracker.with(context, t => { 113 t.replaceNodeRange(file, signatureDecls[0], signatureDecls[signatureDecls.length - 1], updated); 114 }); 115 116 return { renameFilename: undefined, renameLocation: undefined, edits }; 117 118 function getNewParametersForCombinedSignature(signatureDeclarations: (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]): NodeArray<ParameterDeclaration> { 119 const lastSig = signatureDeclarations[signatureDeclarations.length - 1]; 120 if (isFunctionLikeDeclaration(lastSig) && lastSig.body) { 121 // Trim away implementation signature arguments (they should already be compatible with overloads, but are likely less precise to guarantee compatability with the overloads) 122 signatureDeclarations = signatureDeclarations.slice(0, signatureDeclarations.length - 1); 123 } 124 return factory.createNodeArray([ 125 factory.createParameterDeclaration( 126 /*modifiers*/ undefined, 127 factory.createToken(SyntaxKind.DotDotDotToken), 128 "args", 129 /*questionToken*/ undefined, 130 factory.createUnionTypeNode(map(signatureDeclarations, convertSignatureParametersToTuple)) 131 ) 132 ]); 133 } 134 135 function convertSignatureParametersToTuple(decl: MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration): TupleTypeNode { 136 const members = map(decl.parameters, convertParameterToNamedTupleMember); 137 return setEmitFlags(factory.createTupleTypeNode(members), some(members, m => !!length(getSyntheticLeadingComments(m))) ? EmitFlags.None : EmitFlags.SingleLine); 138 } 139 140 function convertParameterToNamedTupleMember(p: ParameterDeclaration): NamedTupleMember { 141 Debug.assert(isIdentifier(p.name)); // This is checked during refactoring applicability checking 142 const result = setTextRange(factory.createNamedTupleMember( 143 p.dotDotDotToken, 144 p.name, 145 p.questionToken, 146 p.type || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) 147 ), p); 148 const parameterDocComment = p.symbol && p.symbol.getDocumentationComment(checker); 149 if (parameterDocComment) { 150 const newComment = displayPartsToString(parameterDocComment); 151 if (newComment.length) { 152 setSyntheticLeadingComments(result, [{ 153 text: `* 154${newComment.split("\n").map(c => ` * ${c}`).join("\n")} 155 `, 156 kind: SyntaxKind.MultiLineCommentTrivia, 157 pos: -1, 158 end: -1, 159 hasTrailingNewLine: true, 160 hasLeadingNewline: true, 161 }]); 162 } 163 } 164 return result; 165 } 166 167 } 168 169 function isConvertableSignatureDeclaration(d: Node): d is MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration { 170 switch (d.kind) { 171 case SyntaxKind.MethodSignature: 172 case SyntaxKind.MethodDeclaration: 173 case SyntaxKind.CallSignature: 174 case SyntaxKind.Constructor: 175 case SyntaxKind.ConstructSignature: 176 case SyntaxKind.FunctionDeclaration: 177 return true; 178 } 179 return false; 180 } 181 182 function getConvertableOverloadListAtPosition(file: SourceFile, startPosition: number, program: Program) { 183 const node = getTokenAtPosition(file, startPosition); 184 const containingDecl = findAncestor(node, isConvertableSignatureDeclaration); 185 if (!containingDecl) { 186 return; 187 } 188 if (isFunctionLikeDeclaration(containingDecl) && containingDecl.body && rangeContainsPosition(containingDecl.body, startPosition)) { 189 return; 190 } 191 192 const checker = program.getTypeChecker(); 193 const signatureSymbol = containingDecl.symbol; 194 if (!signatureSymbol) { 195 return; 196 } 197 const decls = signatureSymbol.declarations; 198 if (length(decls) <= 1) { 199 return; 200 } 201 if (!every(decls, d => getSourceFileOfNode(d) === file)) { 202 return; 203 } 204 if (!isConvertableSignatureDeclaration(decls![0])) { 205 return; 206 } 207 const kindOne = decls![0].kind; 208 if (!every(decls, d => d.kind === kindOne)) { 209 return; 210 } 211 const signatureDecls = decls as (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]; 212 if (some(signatureDecls, d => !!d.typeParameters || some(d.parameters, p => !!p.modifiers || !isIdentifier(p.name)))) { 213 return; 214 } 215 const signatures = mapDefined(signatureDecls, d => checker.getSignatureFromDeclaration(d)); 216 if (length(signatures) !== length(decls)) { 217 return; 218 } 219 const returnOne = checker.getReturnTypeOfSignature(signatures[0]); 220 if (!every(signatures, s => checker.getReturnTypeOfSignature(s) === returnOne)) { 221 return; 222 } 223 224 return signatureDecls; 225 } 226} 227