• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.CallHierarchy {
3    export type NamedExpression =
4        | ClassExpression & { name: Identifier }
5        | FunctionExpression & { name: Identifier }
6        ;
7
8    /** Indictates whether a node is named function or class expression. */
9    function isNamedExpression(node: Node): node is NamedExpression {
10        return (isFunctionExpression(node) || isClassExpression(node)) && isNamedDeclaration(node);
11    }
12
13    export type ConstNamedExpression =
14        | ClassExpression & { name: undefined, parent: VariableDeclaration & { name: Identifier } }
15        | FunctionExpression & { name: undefined, parent: VariableDeclaration & { name: Identifier } }
16        | ArrowFunction & { name: undefined, parent: VariableDeclaration & { name: Identifier } }
17        ;
18
19    /** Indicates whether a node is a function, arrow, or class expression assigned to a constant variable. */
20    function isConstNamedExpression(node: Node): node is ConstNamedExpression {
21        return (isFunctionExpression(node) || isArrowFunction(node) || isClassExpression(node))
22            && isVariableDeclaration(node.parent)
23            && node === node.parent.initializer
24            && isIdentifier(node.parent.name)
25            && !!(getCombinedNodeFlags(node.parent) & NodeFlags.Const);
26    }
27
28    export type CallHierarchyDeclaration =
29        | SourceFile
30        | ModuleDeclaration & { name: Identifier }
31        | FunctionDeclaration
32        | ClassDeclaration
33        | StructDeclaration
34        | MethodDeclaration
35        | GetAccessorDeclaration
36        | SetAccessorDeclaration
37        | NamedExpression
38        | ConstNamedExpression
39        ;
40
41    /**
42     * Indicates whether a node could possibly be a call hierarchy declaration.
43     *
44     * See `resolveCallHierarchyDeclaration` for the specific rules.
45     */
46    function isPossibleCallHierarchyDeclaration(node: Node) {
47        return isSourceFile(node)
48            || isModuleDeclaration(node)
49            || isFunctionDeclaration(node)
50            || isFunctionExpression(node)
51            || isClassDeclaration(node)
52            || isClassExpression(node)
53            || isMethodDeclaration(node)
54            || isMethodSignature(node)
55            || isGetAccessorDeclaration(node)
56            || isSetAccessorDeclaration(node);
57    }
58
59    /**
60     * Indicates whether a node is a valid a call hierarchy declaration.
61     *
62     * See `resolveCallHierarchyDeclaration` for the specific rules.
63     */
64    function isValidCallHierarchyDeclaration(node: Node): node is CallHierarchyDeclaration {
65        return isSourceFile(node)
66            || isModuleDeclaration(node) && isIdentifier(node.name)
67            || isFunctionDeclaration(node)
68            || isClassDeclaration(node)
69            || isMethodDeclaration(node)
70            || isMethodSignature(node)
71            || isGetAccessorDeclaration(node)
72            || isSetAccessorDeclaration(node)
73            || isNamedExpression(node)
74            || isConstNamedExpression(node);
75    }
76
77    /** Gets the node that can be used as a reference to a call hierarchy declaration. */
78    function getCallHierarchyDeclarationReferenceNode(node: CallHierarchyDeclaration) {
79        if (isSourceFile(node)) return node;
80        if (isNamedDeclaration(node)) return node.name;
81        if (isConstNamedExpression(node)) return node.parent.name;
82        return Debug.checkDefined(node.modifiers && find(node.modifiers, isDefaultModifier));
83    }
84
85    function isDefaultModifier(node: Node) {
86        return node.kind === SyntaxKind.DefaultKeyword;
87    }
88
89    /** Gets the symbol for a call hierarchy declaration. */
90    function getSymbolOfCallHierarchyDeclaration(typeChecker: TypeChecker, node: CallHierarchyDeclaration) {
91        const location = getCallHierarchyDeclarationReferenceNode(node);
92        return location && typeChecker.getSymbolAtLocation(location);
93    }
94
95    /** Gets the text and range for the name of a call hierarchy declaration. */
96    function getCallHierarchyItemName(program: Program, node: CallHierarchyDeclaration): { text: string, pos: number, end: number } {
97        if (isSourceFile(node)) {
98            return { text: node.fileName, pos: 0, end: 0 };
99        }
100
101        if ((isFunctionDeclaration(node) || isClassDeclaration(node)) && !isNamedDeclaration(node)) {
102            const defaultModifier = node.modifiers && find(node.modifiers, isDefaultModifier);
103            if (defaultModifier) {
104                return { text: "default", pos: defaultModifier.getStart(), end: defaultModifier.getEnd() };
105            }
106        }
107
108        const declName = isConstNamedExpression(node) ? node.parent.name :
109            Debug.checkDefined(getNameOfDeclaration(node), "Expected call hierarchy item to have a name");
110
111        let text =
112            isIdentifier(declName) ? idText(declName) :
113            isStringOrNumericLiteralLike(declName) ? declName.text :
114            isComputedPropertyName(declName) ?
115                isStringOrNumericLiteralLike(declName.expression) ? declName.expression.text :
116                undefined :
117            undefined;
118        if (text === undefined) {
119            const typeChecker = program.getTypeChecker();
120            const symbol = typeChecker.getSymbolAtLocation(declName);
121            if (symbol) {
122                text = typeChecker.symbolToString(symbol, node);
123            }
124        }
125        if (text === undefined) {
126            // get the text from printing the node on a single line without comments...
127            const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true });
128            text = usingSingleLineStringWriter(writer => printer.writeNode(EmitHint.Unspecified, node, node.getSourceFile(), writer));
129        }
130        return { text, pos: declName.getStart(), end: declName.getEnd() };
131    }
132
133    function getCallHierarchItemContainerName(node: CallHierarchyDeclaration): string | undefined {
134        if (isConstNamedExpression(node)) {
135            if (isModuleBlock(node.parent.parent.parent.parent) && isIdentifier(node.parent.parent.parent.parent.parent.name)) {
136                return node.parent.parent.parent.parent.parent.name.getText();
137            }
138            return;
139        }
140
141        switch (node.kind) {
142            case SyntaxKind.GetAccessor:
143            case SyntaxKind.SetAccessor:
144            case SyntaxKind.MethodDeclaration:
145                if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) {
146                    return getAssignedName(node.parent)?.getText();
147                }
148                return getNameOfDeclaration(node.parent)?.getText();
149            case SyntaxKind.FunctionDeclaration:
150            case SyntaxKind.ClassDeclaration:
151            case SyntaxKind.StructDeclaration:
152            case SyntaxKind.ModuleDeclaration:
153                if (isModuleBlock(node.parent) && isIdentifier(node.parent.parent.name)) {
154                    return node.parent.parent.name.getText();
155                }
156        }
157    }
158
159    /** Finds the implementation of a function-like declaration, if one exists. */
160    function findImplementation(typeChecker: TypeChecker, node: Extract<CallHierarchyDeclaration, FunctionLikeDeclaration>): Extract<CallHierarchyDeclaration, FunctionLikeDeclaration> | undefined;
161    function findImplementation(typeChecker: TypeChecker, node: FunctionLikeDeclaration): FunctionLikeDeclaration | undefined;
162    function findImplementation(typeChecker: TypeChecker, node: FunctionLikeDeclaration): FunctionLikeDeclaration | undefined {
163        if (node.body) {
164            return node;
165        }
166        if (isConstructorDeclaration(node)) {
167            return getFirstConstructorWithBody(node.parent);
168        }
169        if (isFunctionDeclaration(node) || isMethodDeclaration(node)) {
170            const symbol = getSymbolOfCallHierarchyDeclaration(typeChecker, node);
171            if (symbol && symbol.valueDeclaration && isFunctionLikeDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.body) {
172                return symbol.valueDeclaration;
173            }
174            return undefined;
175        }
176        return node;
177    }
178
179    function findAllInitialDeclarations(typeChecker: TypeChecker, node: CallHierarchyDeclaration) {
180        const symbol = getSymbolOfCallHierarchyDeclaration(typeChecker, node);
181        let declarations: CallHierarchyDeclaration[] | undefined;
182        if (symbol && symbol.declarations) {
183            const indices = indicesOf(symbol.declarations);
184            const keys = map(symbol.declarations, decl => ({ file: decl.getSourceFile().fileName, pos: decl.pos }));
185            indices.sort((a, b) => compareStringsCaseSensitive(keys[a].file, keys[b].file) || keys[a].pos - keys[b].pos);
186            const sortedDeclarations = map(indices, i => symbol.declarations[i]);
187            let lastDecl: CallHierarchyDeclaration | undefined;
188            for (const decl of sortedDeclarations) {
189                if (isValidCallHierarchyDeclaration(decl)) {
190                    if (!lastDecl || lastDecl.parent !== decl.parent || lastDecl.end !== decl.pos) {
191                        declarations = append(declarations, decl);
192                    }
193                    lastDecl = decl;
194                }
195            }
196        }
197        return declarations;
198    }
199
200    /** Find the implementation or the first declaration for a call hierarchy declaration. */
201    function findImplementationOrAllInitialDeclarations(typeChecker: TypeChecker, node: CallHierarchyDeclaration): CallHierarchyDeclaration | CallHierarchyDeclaration[] {
202        if (isFunctionLikeDeclaration(node)) {
203            return findImplementation(typeChecker, node) ??
204                findAllInitialDeclarations(typeChecker, node) ??
205                node;
206        }
207        return findAllInitialDeclarations(typeChecker, node) ?? node;
208    }
209
210    /** Resolves the call hierarchy declaration for a node. */
211    export function resolveCallHierarchyDeclaration(program: Program, location: Node): CallHierarchyDeclaration | CallHierarchyDeclaration[] | undefined {
212        // A call hierarchy item must refer to either a SourceFile, Module Declaration, or something intrinsically callable that has a name:
213        // - Class Declarations
214        // - Class Expressions (with a name)
215        // - Function Declarations
216        // - Function Expressions (with a name or assigned to a const variable)
217        // - Arrow Functions (assigned to a const variable)
218        // - Constructors
219        // - Methods
220        // - Accessors
221        //
222        // If a call is contained in a non-named callable Node (function expression, arrow function, etc.), then
223        // its containing `CallHierarchyItem` is a containing function or SourceFile that matches the above list.
224
225        const typeChecker = program.getTypeChecker();
226        let followingSymbol = false;
227        while (true) {
228            if (isValidCallHierarchyDeclaration(location)) {
229                return findImplementationOrAllInitialDeclarations(typeChecker, location);
230            }
231            if (isPossibleCallHierarchyDeclaration(location)) {
232                const ancestor = findAncestor(location, isValidCallHierarchyDeclaration);
233                return ancestor && findImplementationOrAllInitialDeclarations(typeChecker, ancestor);
234            }
235            if (isDeclarationName(location)) {
236                if (isValidCallHierarchyDeclaration(location.parent)) {
237                    return findImplementationOrAllInitialDeclarations(typeChecker, location.parent);
238                }
239                if (isPossibleCallHierarchyDeclaration(location.parent)) {
240                    const ancestor = findAncestor(location.parent, isValidCallHierarchyDeclaration);
241                    return ancestor && findImplementationOrAllInitialDeclarations(typeChecker, ancestor);
242                }
243                if (isVariableDeclaration(location.parent) && location.parent.initializer && isConstNamedExpression(location.parent.initializer)) {
244                    return location.parent.initializer;
245                }
246                return undefined;
247            }
248            if (isConstructorDeclaration(location)) {
249                if (isValidCallHierarchyDeclaration(location.parent)) {
250                    return location.parent;
251                }
252                return undefined;
253            }
254            // #39453
255            if (isVariableDeclaration(location) && location.initializer && isConstNamedExpression(location.initializer)) {
256                return location.initializer;
257            }
258            if (!followingSymbol) {
259                let symbol = typeChecker.getSymbolAtLocation(location);
260                if (symbol) {
261                    if (symbol.flags & SymbolFlags.Alias) {
262                        symbol = typeChecker.getAliasedSymbol(symbol);
263                    }
264                    if (symbol.valueDeclaration) {
265                        followingSymbol = true;
266                        location = symbol.valueDeclaration;
267                        continue;
268                    }
269                }
270            }
271            return undefined;
272        }
273    }
274
275    /** Creates a `CallHierarchyItem` for a call hierarchy declaration. */
276    export function createCallHierarchyItem(program: Program, node: CallHierarchyDeclaration): CallHierarchyItem {
277        const sourceFile = node.getSourceFile();
278        const name = getCallHierarchyItemName(program, node);
279        const containerName = getCallHierarchItemContainerName(node);
280        const kind = getNodeKind(node);
281        const kindModifiers = getNodeModifiers(node);
282        const span = createTextSpanFromBounds(skipTrivia(sourceFile.text, node.getFullStart(), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true), node.getEnd());
283        const selectionSpan = createTextSpanFromBounds(name.pos, name.end);
284        return { file: sourceFile.fileName, kind, kindModifiers, name: name.text, containerName, span, selectionSpan };
285    }
286
287    function isDefined<T>(x: T): x is NonNullable<T> {
288        return x !== undefined;
289    }
290
291    interface CallSite {
292        declaration: CallHierarchyDeclaration;
293        range: TextRange;
294    }
295
296    function convertEntryToCallSite(entry: FindAllReferences.Entry): CallSite | undefined {
297        if (entry.kind === FindAllReferences.EntryKind.Node) {
298            const { node } = entry;
299            if (isCallOrNewExpressionTarget(node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true)
300                || isTaggedTemplateTag(node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true)
301                || isDecoratorTarget(node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true)
302                || isJsxOpeningLikeElementTagName(node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true)
303                || isRightSideOfPropertyAccess(node)
304                || isArgumentExpressionOfElementAccess(node)) {
305                const sourceFile = node.getSourceFile();
306                const ancestor = findAncestor(node, isValidCallHierarchyDeclaration) || sourceFile;
307                return { declaration: ancestor, range: createTextRangeFromNode(node, sourceFile) };
308            }
309        }
310    }
311
312    function getCallSiteGroupKey(entry: CallSite) {
313        return getNodeId(entry.declaration);
314    }
315
316    function createCallHierarchyIncomingCall(from: CallHierarchyItem, fromSpans: TextSpan[]): CallHierarchyIncomingCall {
317        return { from, fromSpans };
318    }
319
320    function convertCallSiteGroupToIncomingCall(program: Program, entries: readonly CallSite[]) {
321        return createCallHierarchyIncomingCall(createCallHierarchyItem(program, entries[0].declaration), map(entries, entry => createTextSpanFromRange(entry.range)));
322    }
323
324    /** Gets the call sites that call into the provided call hierarchy declaration. */
325    export function getIncomingCalls(program: Program, declaration: CallHierarchyDeclaration, cancellationToken: CancellationToken): CallHierarchyIncomingCall[] {
326        // Source files and modules have no incoming calls.
327        if (isSourceFile(declaration) || isModuleDeclaration(declaration)) {
328            return [];
329        }
330        const location = getCallHierarchyDeclarationReferenceNode(declaration);
331        const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, { use: FindAllReferences.FindReferencesUse.References }, convertEntryToCallSite), isDefined);
332        return calls ? group(calls, getCallSiteGroupKey, entries => convertCallSiteGroupToIncomingCall(program, entries)) : [];
333    }
334
335    function createCallSiteCollector(program: Program, callSites: CallSite[]): (node: Node | undefined) => void {
336        function recordCallSite(node: CallExpression | NewExpression | TaggedTemplateExpression | PropertyAccessExpression | ElementAccessExpression | Decorator | JsxOpeningLikeElement) {
337            const target =
338                isTaggedTemplateExpression(node) ? node.tag :
339                isJsxOpeningLikeElement(node) ? node.tagName :
340                isAccessExpression(node) ? node :
341                node.expression;
342            const declaration = resolveCallHierarchyDeclaration(program, target);
343            if (declaration) {
344                const range = createTextRangeFromNode(target, node.getSourceFile());
345                if (isArray(declaration)) {
346                    for (const decl of declaration) {
347                        callSites.push({ declaration: decl, range });
348                    }
349                }
350                else {
351                    callSites.push({ declaration, range });
352                }
353            }
354        }
355
356        function collect(node: Node | undefined) {
357            if (!node) return;
358            if (node.flags & NodeFlags.Ambient) {
359                // do not descend into ambient nodes.
360                return;
361            }
362
363            if (isValidCallHierarchyDeclaration(node)) {
364                // do not descend into other call site declarations, other than class member names
365                if (isClassLike(node)) {
366                    for (const member of node.members) {
367                        if (member.name && isComputedPropertyName(member.name)) {
368                            collect(member.name.expression);
369                        }
370                    }
371                }
372                return;
373            }
374
375            switch (node.kind) {
376                case SyntaxKind.Identifier:
377                case SyntaxKind.ImportEqualsDeclaration:
378                case SyntaxKind.ImportDeclaration:
379                case SyntaxKind.ExportDeclaration:
380                case SyntaxKind.InterfaceDeclaration:
381                case SyntaxKind.TypeAliasDeclaration:
382                    // do not descend into nodes that cannot contain callable nodes
383                    return;
384                case SyntaxKind.TypeAssertionExpression:
385                case SyntaxKind.AsExpression:
386                    // do not descend into the type side of an assertion
387                    collect((node as TypeAssertion | AsExpression).expression);
388                    return;
389                case SyntaxKind.VariableDeclaration:
390                case SyntaxKind.Parameter:
391                    // do not descend into the type of a variable or parameter declaration
392                    collect((node as VariableDeclaration | ParameterDeclaration).name);
393                    collect((node as VariableDeclaration | ParameterDeclaration).initializer);
394                    return;
395                case SyntaxKind.CallExpression:
396                    // do not descend into the type arguments of a call expression
397                    recordCallSite(node as CallExpression);
398                    collect((node as CallExpression).expression);
399                    forEach((node as CallExpression).arguments, collect);
400                    return;
401                case SyntaxKind.NewExpression:
402                    // do not descend into the type arguments of a new expression
403                    recordCallSite(node as NewExpression);
404                    collect((node as NewExpression).expression);
405                    forEach((node as NewExpression).arguments, collect);
406                    return;
407                case SyntaxKind.TaggedTemplateExpression:
408                    // do not descend into the type arguments of a tagged template expression
409                    recordCallSite(node as TaggedTemplateExpression);
410                    collect((node as TaggedTemplateExpression).tag);
411                    collect((node as TaggedTemplateExpression).template);
412                    return;
413                case SyntaxKind.JsxOpeningElement:
414                case SyntaxKind.JsxSelfClosingElement:
415                    // do not descend into the type arguments of a JsxOpeningLikeElement
416                    recordCallSite(node as JsxOpeningLikeElement);
417                    collect((node as JsxOpeningLikeElement).tagName);
418                    collect((node as JsxOpeningLikeElement).attributes);
419                    return;
420                case SyntaxKind.Decorator:
421                    recordCallSite(node as Decorator);
422                    collect((node as Decorator).expression);
423                    return;
424                case SyntaxKind.PropertyAccessExpression:
425                case SyntaxKind.ElementAccessExpression:
426                    recordCallSite(node as AccessExpression);
427                    forEachChild(node, collect);
428                    break;
429            }
430
431            if (isPartOfTypeNode(node)) {
432                // do not descend into types
433                return;
434            }
435
436            forEachChild(node, collect);
437        }
438        return collect;
439    }
440
441    function collectCallSitesOfSourceFile(node: SourceFile, collect: (node: Node | undefined) => void) {
442        forEach(node.statements, collect);
443    }
444
445    function collectCallSitesOfModuleDeclaration(node: ModuleDeclaration, collect: (node: Node | undefined) => void) {
446        if (!hasSyntacticModifier(node, ModifierFlags.Ambient) && node.body && isModuleBlock(node.body)) {
447            forEach(node.body.statements, collect);
448        }
449    }
450
451    function collectCallSitesOfFunctionLikeDeclaration(typeChecker: TypeChecker, node: FunctionLikeDeclaration, collect: (node: Node | undefined) => void) {
452        const implementation = findImplementation(typeChecker, node);
453        if (implementation) {
454            forEach(implementation.parameters, collect);
455            collect(implementation.body);
456        }
457    }
458
459    function collectCallSitesOfClassLikeDeclaration(node: ClassLikeDeclaration, collect: (node: Node | undefined) => void) {
460        forEach(node.decorators, collect);
461        const heritage = getClassExtendsHeritageElement(node);
462        if (heritage) {
463            collect(heritage.expression);
464        }
465        for (const member of node.members) {
466            forEach(member.decorators, collect);
467            if (isPropertyDeclaration(member)) {
468                collect(member.initializer);
469            }
470            else if (isConstructorDeclaration(member) && member.body) {
471                forEach(member.parameters, collect);
472                collect(member.body);
473            }
474        }
475    }
476
477    function collectCallSites(program: Program, node: CallHierarchyDeclaration) {
478        const callSites: CallSite[] = [];
479        const collect = createCallSiteCollector(program, callSites);
480        switch (node.kind) {
481            case SyntaxKind.SourceFile:
482                collectCallSitesOfSourceFile(node, collect);
483                break;
484            case SyntaxKind.ModuleDeclaration:
485                collectCallSitesOfModuleDeclaration(node, collect);
486                break;
487            case SyntaxKind.FunctionDeclaration:
488            case SyntaxKind.FunctionExpression:
489            case SyntaxKind.ArrowFunction:
490            case SyntaxKind.MethodDeclaration:
491            case SyntaxKind.GetAccessor:
492            case SyntaxKind.SetAccessor:
493                collectCallSitesOfFunctionLikeDeclaration(program.getTypeChecker(), node, collect);
494                break;
495            case SyntaxKind.ClassDeclaration:
496            case SyntaxKind.ClassExpression:
497            case SyntaxKind.StructDeclaration:
498                collectCallSitesOfClassLikeDeclaration(node, collect);
499                break;
500            default:
501                Debug.assertNever(node);
502        }
503        return callSites;
504    }
505
506    function createCallHierarchyOutgoingCall(to: CallHierarchyItem, fromSpans: TextSpan[]): CallHierarchyOutgoingCall {
507        return { to, fromSpans };
508    }
509
510    function convertCallSiteGroupToOutgoingCall(program: Program, entries: readonly CallSite[]) {
511        return createCallHierarchyOutgoingCall(createCallHierarchyItem(program, entries[0].declaration), map(entries, entry => createTextSpanFromRange(entry.range)));
512    }
513
514    /** Gets the call sites that call out of the provided call hierarchy declaration. */
515    export function getOutgoingCalls(program: Program, declaration: CallHierarchyDeclaration): CallHierarchyOutgoingCall[] {
516        if (declaration.flags & NodeFlags.Ambient || isMethodSignature(declaration)) {
517            return [];
518        }
519        return group(collectCallSites(program, declaration), getCallSiteGroupKey, entries => convertCallSiteGroupToOutgoingCall(program, entries));
520    }
521}
522