• 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,
14        getAvailableActions
15    });
16
17    function getAvailableActions(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 getEditsForAction(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.decorators,
55                    lastDeclaration.modifiers,
56                    lastDeclaration.asteriskToken,
57                    lastDeclaration.name,
58                    lastDeclaration.questionToken,
59                    lastDeclaration.typeParameters,
60                    getNewParametersForCombinedSignature(signatureDecls),
61                    lastDeclaration.type,
62                    lastDeclaration.body
63                );
64                break;
65            }
66            case SyntaxKind.CallSignature: {
67                updated = factory.updateCallSignature(
68                    lastDeclaration,
69                    lastDeclaration.typeParameters,
70                    getNewParametersForCombinedSignature(signatureDecls),
71                    lastDeclaration.type,
72                );
73                break;
74            }
75            case SyntaxKind.Constructor: {
76                updated = factory.updateConstructorDeclaration(
77                    lastDeclaration,
78                    lastDeclaration.decorators,
79                    lastDeclaration.modifiers,
80                    getNewParametersForCombinedSignature(signatureDecls),
81                    lastDeclaration.body
82                );
83                break;
84            }
85            case SyntaxKind.ConstructSignature: {
86                updated = factory.updateConstructSignature(
87                    lastDeclaration,
88                    lastDeclaration.typeParameters,
89                    getNewParametersForCombinedSignature(signatureDecls),
90                    lastDeclaration.type,
91                );
92                break;
93            }
94            case SyntaxKind.FunctionDeclaration: {
95                updated = factory.updateFunctionDeclaration(
96                    lastDeclaration,
97                    lastDeclaration.decorators,
98                    lastDeclaration.modifiers,
99                    lastDeclaration.asteriskToken,
100                    lastDeclaration.name,
101                    lastDeclaration.typeParameters,
102                    getNewParametersForCombinedSignature(signatureDecls),
103                    lastDeclaration.type,
104                    lastDeclaration.body
105                );
106                break;
107            }
108            default: return Debug.failBadSyntaxKind(lastDeclaration, "Unhandled signature kind in overload list conversion refactoring");
109        }
110
111        if (updated === lastDeclaration) {
112            return; // No edits to apply, do nothing
113        }
114
115        const edits = textChanges.ChangeTracker.with(context, t => {
116            t.replaceNodeRange(file, signatureDecls[0], signatureDecls[signatureDecls.length - 1], updated);
117        });
118
119        return { renameFilename: undefined, renameLocation: undefined, edits };
120
121        function getNewParametersForCombinedSignature(signatureDeclarations: (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]): NodeArray<ParameterDeclaration> {
122            const lastSig = signatureDeclarations[signatureDeclarations.length - 1];
123            if (isFunctionLikeDeclaration(lastSig) && lastSig.body) {
124                // Trim away implementation signature arguments (they should already be compatible with overloads, but are likely less precise to guarantee compatability with the overloads)
125                signatureDeclarations = signatureDeclarations.slice(0, signatureDeclarations.length - 1);
126            }
127            return factory.createNodeArray([
128                factory.createParameterDeclaration(
129                    /*decorators*/ undefined,
130                    /*modifiers*/ undefined,
131                    factory.createToken(SyntaxKind.DotDotDotToken),
132                    "args",
133                    /*questionToken*/ undefined,
134                    factory.createUnionTypeNode(map(signatureDeclarations, convertSignatureParametersToTuple))
135                )
136            ]);
137        }
138
139        function convertSignatureParametersToTuple(decl: MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration): TupleTypeNode {
140            const members = map(decl.parameters, convertParameterToNamedTupleMember);
141            return setEmitFlags(factory.createTupleTypeNode(members), some(members, m => !!length(getSyntheticLeadingComments(m))) ? EmitFlags.None : EmitFlags.SingleLine);
142        }
143
144        function convertParameterToNamedTupleMember(p: ParameterDeclaration): NamedTupleMember {
145            Debug.assert(isIdentifier(p.name)); // This is checked during refactoring applicability checking
146            const result = setTextRange(factory.createNamedTupleMember(
147                p.dotDotDotToken,
148                p.name,
149                p.questionToken,
150                p.type || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)
151            ), p);
152            const parameterDocComment = p.symbol && p.symbol.getDocumentationComment(checker);
153            if (parameterDocComment) {
154                const newComment = displayPartsToString(parameterDocComment);
155                if (newComment.length) {
156                    setSyntheticLeadingComments(result, [{
157                        text: `*
158${newComment.split("\n").map(c => ` * ${c}`).join("\n")}
159 `,
160                        kind: SyntaxKind.MultiLineCommentTrivia,
161                        pos: -1,
162                        end: -1,
163                        hasTrailingNewLine: true,
164                        hasLeadingNewline: true,
165                    }]);
166                }
167            }
168            return result;
169        }
170
171    }
172
173    function isConvertableSignatureDeclaration(d: Node): d is MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration {
174        switch (d.kind) {
175            case SyntaxKind.MethodSignature:
176            case SyntaxKind.MethodDeclaration:
177            case SyntaxKind.CallSignature:
178            case SyntaxKind.Constructor:
179            case SyntaxKind.ConstructSignature:
180            case SyntaxKind.FunctionDeclaration:
181                return true;
182        }
183        return false;
184    }
185
186    function getConvertableOverloadListAtPosition(file: SourceFile, startPosition: number, program: Program) {
187        const node = getTokenAtPosition(file, startPosition);
188        const containingDecl = findAncestor(node, isConvertableSignatureDeclaration);
189        if (!containingDecl) {
190            return;
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.decorators || !!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