• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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