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