internal/ls/callhierarchy.go (856 lines of code) (raw):

package ls import ( "context" "slices" "strings" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/astnav" "github.com/microsoft/typescript-go/internal/checker" "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/debug" "github.com/microsoft/typescript-go/internal/ls/lsconv" "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/printer" "github.com/microsoft/typescript-go/internal/scanner" ) type CallHierarchyDeclaration = *ast.Node // Indictates whether a node is named function or class expression. func isNamedExpression(node *ast.Node) bool { if node == nil { return false } if !ast.IsFunctionExpression(node) && !ast.IsClassExpression(node) { return false } name := node.Name() return name != nil && ast.IsIdentifier(name) } func isVariableLike(node *ast.Node) bool { if node == nil { return false } return ast.IsPropertyDeclaration(node) || ast.IsVariableDeclaration(node) } // Indicates whether a node is a function, arrow, or class expression assigned to a constant variable or class property. func isAssignedExpression(node *ast.Node) bool { if node == nil { return false } if !(ast.IsFunctionExpression(node) || ast.IsArrowFunction(node) || ast.IsClassExpression(node)) { return false } if node.Name() != nil { return false } parent := node.Parent if !isVariableLike(parent) { return false } if parent.Initializer() != node { return false } name := parent.Name() if !ast.IsIdentifier(name) { return false } return (ast.GetCombinedNodeFlags(parent)&ast.NodeFlagsConst) != 0 || ast.IsPropertyDeclaration(parent) } // Indicates whether a node could possibly be a call hierarchy declaration. // // See `resolveCallHierarchyDeclaration` for the specific rules. func isPossibleCallHierarchyDeclaration(node *ast.Node) bool { if node == nil { return false } return ast.IsSourceFile(node) || ast.IsModuleDeclaration(node) || ast.IsFunctionDeclaration(node) || ast.IsFunctionExpression(node) || ast.IsClassDeclaration(node) || ast.IsClassExpression(node) || ast.IsClassStaticBlockDeclaration(node) || ast.IsMethodDeclaration(node) || ast.IsMethodSignatureDeclaration(node) || ast.IsGetAccessorDeclaration(node) || ast.IsSetAccessorDeclaration(node) } // Indicates whether a node is a valid a call hierarchy declaration. // // See `resolveCallHierarchyDeclaration` for the specific rules. func isValidCallHierarchyDeclaration(node *ast.Node) bool { if node == nil { return false } if ast.IsSourceFile(node) { return true } if ast.IsModuleDeclaration(node) { return ast.IsIdentifier(node.Name()) } return ast.IsFunctionDeclaration(node) || ast.IsClassDeclaration(node) || ast.IsClassStaticBlockDeclaration(node) || ast.IsMethodDeclaration(node) || ast.IsMethodSignatureDeclaration(node) || ast.IsGetAccessorDeclaration(node) || ast.IsSetAccessorDeclaration(node) || isNamedExpression(node) || isAssignedExpression(node) } // Gets the node that can be used as a reference to a call hierarchy declaration. func getCallHierarchyDeclarationReferenceNode(node *ast.Node) *ast.Node { if node == nil { return nil } if ast.IsSourceFile(node) { return node } if name := node.Name(); name != nil { return name } if isAssignedExpression(node) { return node.Parent.Name() } if modifiers := node.Modifiers(); modifiers != nil { for _, mod := range modifiers.Nodes { if mod.Kind == ast.KindDefaultKeyword { return mod } } } debug.Assert(false, "Expected call hierarchy declaration to have a reference node") return nil } // Gets the symbol for a call hierarchy declaration. func getSymbolOfCallHierarchyDeclaration(c *checker.Checker, node *ast.Node) *ast.Symbol { if ast.IsClassStaticBlockDeclaration(node) { return nil } location := getCallHierarchyDeclarationReferenceNode(node) if location == nil { return nil } return c.GetSymbolAtLocation(location) } // Gets the text and range for the name of a call hierarchy declaration. func getCallHierarchyItemName(program *compiler.Program, node *ast.Node) (text string, pos int, end int) { if ast.IsSourceFile(node) { sourceFile := node.AsSourceFile() return sourceFile.FileName(), 0, 0 } if (ast.IsFunctionDeclaration(node) || ast.IsClassDeclaration(node)) && node.Name() == nil { if modifiers := node.Modifiers(); modifiers != nil { for _, mod := range modifiers.Nodes { if mod.Kind == ast.KindDefaultKeyword { sourceFile := ast.GetSourceFileOfNode(node) start := scanner.SkipTrivia(sourceFile.Text(), mod.Pos()) return "default", start, mod.End() } } } } if ast.IsClassStaticBlockDeclaration(node) { sourceFile := ast.GetSourceFileOfNode(node) pos := scanner.SkipTrivia(sourceFile.Text(), moveRangePastModifiers(node).Pos()) end := pos + 6 // "static".length c, done := program.GetTypeCheckerForFile(context.Background(), sourceFile) defer done() symbol := c.GetSymbolAtLocation(node.Parent) prefix := "" if symbol != nil { prefix = c.SymbolToString(symbol) + " " } return prefix + "static {}", pos, end } var declName *ast.Node if isAssignedExpression(node) { declName = node.Parent.Name() } else { declName = ast.GetNameOfDeclaration(node) } debug.AssertIsDefined(declName, "Expected call hierarchy item to have a name") if ast.IsIdentifier(declName) { text = declName.Text() } else if ast.IsStringOrNumericLiteralLike(declName) { text = declName.Text() } else if ast.IsComputedPropertyName(declName) { expr := declName.Expression() if ast.IsStringOrNumericLiteralLike(expr) { text = expr.Text() } } if text == "" { c, done := program.GetTypeCheckerForFile(context.Background(), ast.GetSourceFileOfNode(node)) defer done() symbol := c.GetSymbolAtLocation(declName) if symbol != nil { text = c.SymbolToString(symbol) } } // get the text from printing the node on a single line without comments... if text == "" { sourceFile := ast.GetSourceFileOfNode(node) writer, putWriter := printer.GetSingleLineStringWriter() defer putWriter() p := printer.NewPrinter(printer.PrinterOptions{RemoveComments: true}, printer.PrintHandlers{}, nil) p.Write(node, sourceFile, writer, nil) text = writer.String() } sourceFile := ast.GetSourceFileOfNode(node) namePos := scanner.SkipTrivia(sourceFile.Text(), declName.Pos()) return text, namePos, declName.End() } func getCallHierarchyItemContainerName(node *ast.Node) string { if isAssignedExpression(node) { parent := node.Parent if ast.IsPropertyDeclaration(parent) && ast.IsClassLike(parent.Parent) { if ast.IsClassExpression(parent.Parent) { if assignedName := ast.GetAssignedName(parent.Parent); assignedName != nil { return assignedName.Text() } } else { if name := parent.Parent.Name(); name != nil { return name.Text() } } } if ast.IsModuleBlock(parent.Parent.Parent.Parent) { modParent := parent.Parent.Parent.Parent.Parent if ast.IsModuleDeclaration(modParent) { if name := modParent.Name(); name != nil && ast.IsIdentifier(name) { return name.Text() } } } return "" } switch node.Kind { case ast.KindGetAccessor, ast.KindSetAccessor, ast.KindMethodDeclaration: if node.Parent.Kind == ast.KindObjectLiteralExpression { if assignedName := ast.GetAssignedName(node.Parent); assignedName != nil { return assignedName.Text() } } if name := ast.GetNameOfDeclaration(node.Parent); name != nil { return name.Text() } case ast.KindFunctionDeclaration, ast.KindClassDeclaration, ast.KindModuleDeclaration: if ast.IsModuleBlock(node.Parent) { if ast.IsModuleDeclaration(node.Parent.Parent) { if name := node.Parent.Parent.Name(); name != nil && ast.IsIdentifier(name) { return name.Text() } } } } return "" } func moveRangePastModifiers(node *ast.Node) core.TextRange { if modifiers := node.Modifiers(); modifiers != nil && len(modifiers.Nodes) > 0 { lastMod := modifiers.Nodes[len(modifiers.Nodes)-1] return core.NewTextRange(lastMod.End(), node.End()) } return core.NewTextRange(node.Pos(), node.End()) } // Finds the implementation of a function-like declaration, if one exists. func findImplementation(c *checker.Checker, node *ast.Node) *ast.Node { if node == nil { return nil } if !ast.IsFunctionLikeDeclaration(node) { return node } if node.Body() != nil { return node } if ast.IsConstructorDeclaration(node) { return ast.GetFirstConstructorWithBody(node.Parent) } if ast.IsFunctionDeclaration(node) || ast.IsMethodDeclaration(node) { symbol := getSymbolOfCallHierarchyDeclaration(c, node) if symbol != nil && symbol.ValueDeclaration != nil { if ast.IsFunctionLikeDeclaration(symbol.ValueDeclaration) && symbol.ValueDeclaration.Body() != nil { return symbol.ValueDeclaration } } return nil } return node } func findAllInitialDeclarations(c *checker.Checker, node *ast.Node) []*ast.Node { if ast.IsClassStaticBlockDeclaration(node) { return nil } symbol := getSymbolOfCallHierarchyDeclaration(c, node) if symbol == nil || symbol.Declarations == nil { return nil } type declKey struct { file string pos int } indices := make([]int, len(symbol.Declarations)) for i := range indices { indices[i] = i } keys := make([]declKey, len(symbol.Declarations)) for i, decl := range symbol.Declarations { keys[i] = declKey{ file: ast.GetSourceFileOfNode(decl).FileName(), pos: decl.Pos(), } } slices.SortFunc(indices, func(a, b int) int { if keys[a].file != keys[b].file { return strings.Compare(keys[a].file, keys[b].file) } return keys[a].pos - keys[b].pos }) var declarations []*ast.Node var lastDecl *ast.Node for _, i := range indices { decl := symbol.Declarations[i] if isValidCallHierarchyDeclaration(decl) { if lastDecl == nil || lastDecl.Parent != decl.Parent || lastDecl.End() != decl.Pos() { declarations = append(declarations, decl) } lastDecl = decl } } return declarations } // Find the implementation or the first declaration for a call hierarchy declaration. func findImplementationOrAllInitialDeclarations(c *checker.Checker, node *ast.Node) any { if ast.IsClassStaticBlockDeclaration(node) { return node } if ast.IsFunctionLikeDeclaration(node) { if impl := findImplementation(c, node); impl != nil { return impl } if decls := findAllInitialDeclarations(c, node); decls != nil { return decls } return node } if decls := findAllInitialDeclarations(c, node); decls != nil { return decls } return node } // Resolves the call hierarchy declaration for a node. func resolveCallHierarchyDeclaration(program *compiler.Program, location *ast.Node) (result any) { // A call hierarchy item must refer to either a SourceFile, Module Declaration, Class Static Block, or something intrinsically callable that has a name: // - Class Declarations // - Class Expressions (with a name) // - Function Declarations // - Function Expressions (with a name or assigned to a const variable) // - Arrow Functions (assigned to a const variable) // - Constructors // - Class `static {}` initializer blocks // - Methods // - Accessors // // If a call is contained in a non-named callable Node (function expression, arrow function, etc.), then // its containing `CallHierarchyItem` is a containing function or SourceFile that matches the above list. c, done := program.GetTypeChecker(context.Background()) defer done() followingSymbol := false for location != nil { if isValidCallHierarchyDeclaration(location) { return findImplementationOrAllInitialDeclarations(c, location) } if isPossibleCallHierarchyDeclaration(location) { ancestor := ast.FindAncestor(location, isValidCallHierarchyDeclaration) if ancestor != nil { return findImplementationOrAllInitialDeclarations(c, ancestor) } } if ast.IsDeclarationName(location) { if isValidCallHierarchyDeclaration(location.Parent) { return findImplementationOrAllInitialDeclarations(c, location.Parent) } if isPossibleCallHierarchyDeclaration(location.Parent) { ancestor := ast.FindAncestor(location.Parent, isValidCallHierarchyDeclaration) if ancestor != nil { return findImplementationOrAllInitialDeclarations(c, ancestor) } } if isVariableLike(location.Parent) { initializer := location.Parent.Initializer() if initializer != nil && isAssignedExpression(initializer) { return initializer } } return nil } if ast.IsConstructorDeclaration(location) { if isValidCallHierarchyDeclaration(location.Parent) { return location.Parent } return nil } if location.Kind == ast.KindStaticKeyword && ast.IsClassStaticBlockDeclaration(location.Parent) { location = location.Parent continue } // #39453 if ast.IsVariableDeclaration(location) { if initializer := location.Initializer(); initializer != nil && isAssignedExpression(initializer) { return initializer } } if !followingSymbol { symbol := c.GetSymbolAtLocation(location) if symbol != nil { if (symbol.Flags & ast.SymbolFlagsAlias) != 0 { symbol = c.GetAliasedSymbol(symbol) } if symbol.ValueDeclaration != nil { followingSymbol = true location = symbol.ValueDeclaration continue } } } return nil } return nil } // Creates a `CallHierarchyItem` for a call hierarchy declaration. func (l *LanguageService) createCallHierarchyItem(program *compiler.Program, node *ast.Node) *lsproto.CallHierarchyItem { sourceFile := ast.GetSourceFileOfNode(node) nameText, namePos, nameEnd := getCallHierarchyItemName(program, node) containerName := getCallHierarchyItemContainerName(node) kind := getSymbolKindFromNode(node) fullStart := scanner.SkipTriviaEx(sourceFile.Text(), node.Pos(), &scanner.SkipTriviaOptions{StopAtComments: true}) script := l.getScript(sourceFile.FileName()) span := l.converters.ToLSPRange(script, core.NewTextRange(fullStart, node.End())) selectionSpan := l.converters.ToLSPRange(script, core.NewTextRange(namePos, nameEnd)) item := &lsproto.CallHierarchyItem{ Name: nameText, Kind: kind, Uri: lsconv.FileNameToDocumentURI(sourceFile.FileName()), Range: span, SelectionRange: selectionSpan, } if containerName != "" { item.Detail = &containerName } return item } type callSite struct { declaration *ast.Node textRange core.TextRange sourceFile *ast.Node } func convertEntryToCallSite(entry *ReferenceEntry) *callSite { if entry.kind != entryKindNode { return nil } node := entry.node if !ast.IsCallOrNewExpressionTarget(node, true /*includeElementAccess*/, true /*skipPastOuterExpressions*/) && !ast.IsTaggedTemplateTag(node, true, true) && !ast.IsDecoratorTarget(node, true, true) && !ast.IsJsxOpeningLikeElementTagName(node, true, true) && !ast.IsRightSideOfPropertyAccess(node) && !ast.IsArgumentExpressionOfElementAccess(node) { return nil } sourceFile := ast.GetSourceFileOfNode(node) ancestor := ast.FindAncestor(node, isValidCallHierarchyDeclaration) if ancestor == nil { ancestor = sourceFile.AsNode() } start := scanner.SkipTrivia(sourceFile.Text(), node.Pos()) return &callSite{ declaration: ancestor, textRange: core.NewTextRange(start, node.End()), sourceFile: sourceFile.AsNode(), } } func getCallSiteGroupKey(site *callSite) ast.NodeId { return ast.GetNodeId(site.declaration) } func (l *LanguageService) convertCallSiteGroupToIncomingCall(program *compiler.Program, entries []*callSite) *lsproto.CallHierarchyIncomingCall { fromRanges := make([]lsproto.Range, len(entries)) for i, entry := range entries { script := l.getScript(entry.sourceFile.AsSourceFile().FileName()) fromRanges[i] = l.converters.ToLSPRange(script, entry.textRange) } slices.SortFunc(fromRanges, func(a, b lsproto.Range) int { return lsproto.CompareRanges(&a, &b) }) return &lsproto.CallHierarchyIncomingCall{ From: l.createCallHierarchyItem(program, entries[0].declaration), FromRanges: fromRanges, } } // Gets the call sites that call into the provided call hierarchy declaration. func (l *LanguageService) getIncomingCalls(ctx context.Context, program *compiler.Program, declaration *ast.Node) []*lsproto.CallHierarchyIncomingCall { // Source files and modules have no incoming calls. if ast.IsSourceFile(declaration) || ast.IsModuleDeclaration(declaration) || ast.IsClassStaticBlockDeclaration(declaration) { return nil } location := getCallHierarchyDeclarationReferenceNode(declaration) if location == nil { return nil } sourceFiles := program.GetSourceFiles() options := refOptions{use: referenceUseReferences} symbolsAndEntries := l.getReferencedSymbolsForNode(ctx, 0, location, program, sourceFiles, options, nil) var refEntries []*ReferenceEntry for _, symbolAndEntry := range symbolsAndEntries { refEntries = append(refEntries, symbolAndEntry.references...) } var callSites []*callSite for _, entry := range refEntries { if site := convertEntryToCallSite(entry); site != nil { callSites = append(callSites, site) } } if len(callSites) == 0 { return nil } grouped := make(map[ast.NodeId][]*callSite) for _, site := range callSites { key := getCallSiteGroupKey(site) grouped[key] = append(grouped[key], site) } var result []*lsproto.CallHierarchyIncomingCall for _, sites := range grouped { result = append(result, l.convertCallSiteGroupToIncomingCall(program, sites)) } slices.SortFunc(result, func(a, b *lsproto.CallHierarchyIncomingCall) int { if uriComp := strings.Compare(string(a.From.Uri), string(b.From.Uri)); uriComp != 0 { return uriComp } if len(a.FromRanges) == 0 || len(b.FromRanges) == 0 { return 0 } return lsproto.CompareRanges(&a.FromRanges[0], &b.FromRanges[0]) }) return result } type callSiteCollector struct { program *compiler.Program callSites []*callSite } func (c *callSiteCollector) recordCallSite(node *ast.Node) { var target *ast.Node switch { case ast.IsTaggedTemplateExpression(node): target = node.AsTaggedTemplateExpression().Tag case ast.IsJsxOpeningElement(node): target = node.TagName() case ast.IsJsxSelfClosingElement(node): target = node.TagName() case ast.IsPropertyAccessExpression(node) || ast.IsElementAccessExpression(node): target = node case ast.IsClassStaticBlockDeclaration(node): target = node case ast.IsCallExpression(node): target = node.Expression() case ast.IsNewExpression(node): target = node.Expression() case ast.IsDecorator(node): target = node.Expression() } if target == nil { return } declaration := resolveCallHierarchyDeclaration(c.program, target) if declaration == nil { return } sourceFile := ast.GetSourceFileOfNode(target) start := scanner.SkipTrivia(sourceFile.Text(), target.Pos()) textRange := core.NewTextRange(start, target.End()) switch decl := declaration.(type) { case *ast.Node: c.callSites = append(c.callSites, &callSite{ declaration: decl, textRange: textRange, sourceFile: sourceFile.AsNode(), }) case []*ast.Node: for _, d := range decl { c.callSites = append(c.callSites, &callSite{ declaration: d, textRange: textRange, sourceFile: sourceFile.AsNode(), }) } } } func (c *callSiteCollector) collect(node *ast.Node) { if node == nil { return } // do not descend into ambient nodes. if (node.Flags & ast.NodeFlagsAmbient) != 0 { return } // do not descend into other call site declarations, other than class member names if isValidCallHierarchyDeclaration(node) { if ast.IsClassLike(node) { for _, member := range node.Members() { if member.Name() != nil && ast.IsComputedPropertyName(member.Name()) { c.collect(member.Name().Expression()) } } } return } switch node.Kind { case ast.KindIdentifier, ast.KindImportEqualsDeclaration, ast.KindImportDeclaration, ast.KindExportDeclaration, ast.KindInterfaceDeclaration, ast.KindTypeAliasDeclaration: // do not descend into nodes that cannot contain callable nodes return case ast.KindClassStaticBlockDeclaration: c.recordCallSite(node) return case ast.KindTypeAssertionExpression, ast.KindAsExpression: // do not descend into the type side of an assertion c.collect(node.Expression()) return case ast.KindVariableDeclaration, ast.KindParameter: // do not descend into the type of a variable or parameter declaration c.collect(node.Name()) c.collect(node.Initializer()) return case ast.KindCallExpression: // do not descend into the type arguments of a call expression c.recordCallSite(node) c.collect(node.Expression()) for _, arg := range node.Arguments() { c.collect(arg) } return case ast.KindNewExpression: // do not descend into the type arguments of a new expression c.recordCallSite(node) c.collect(node.Expression()) for _, arg := range node.Arguments() { c.collect(arg) } return case ast.KindTaggedTemplateExpression: // do not descend into the type arguments of a tagged template expression c.recordCallSite(node) taggedTemplate := node.AsTaggedTemplateExpression() c.collect(taggedTemplate.Tag) c.collect(taggedTemplate.Template) return case ast.KindJsxOpeningElement, ast.KindJsxSelfClosingElement: // do not descend into the type arguments of a JsxOpeningLikeElement c.recordCallSite(node) c.collect(node.TagName()) c.collect(node.Attributes()) return case ast.KindDecorator: c.recordCallSite(node) c.collect(node.Expression()) return case ast.KindPropertyAccessExpression, ast.KindElementAccessExpression: c.recordCallSite(node) node.ForEachChild(func(child *ast.Node) bool { c.collect(child) return false }) return case ast.KindSatisfiesExpression: // do not descend into the type side of an assertion c.collect(node.Expression()) return } if ast.IsPartOfTypeNode(node) { // do not descend into types return } node.ForEachChild(func(child *ast.Node) bool { c.collect(child) return false }) } func collectCallSites(program *compiler.Program, c *checker.Checker, node *ast.Node) []*callSite { collector := &callSiteCollector{ program: program, callSites: make([]*callSite, 0), } switch node.Kind { case ast.KindSourceFile: for _, stmt := range node.Statements() { collector.collect(stmt) } case ast.KindModuleDeclaration: if body := node.Body(); !ast.HasSyntacticModifier(node, ast.ModifierFlagsAmbient) && body != nil && ast.IsModuleBlock(body) { for _, stmt := range body.Statements() { collector.collect(stmt) } } case ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor: impl := findImplementation(c, node) if impl != nil { for _, param := range impl.Parameters() { collector.collect(param) } collector.collect(impl.Body()) } case ast.KindClassDeclaration, ast.KindClassExpression: if modifiers := node.Modifiers(); modifiers != nil { for _, mod := range modifiers.Nodes { collector.collect(mod) } } heritage := ast.GetClassExtendsHeritageElement(node) if heritage != nil { collector.collect(heritage.Expression()) } for _, member := range node.Members() { if ast.CanHaveModifiers(member) && member.Modifiers() != nil { for _, mod := range member.Modifiers().Nodes { collector.collect(mod) } } if ast.IsPropertyDeclaration(member) { collector.collect(member.Initializer()) } else if ast.IsConstructorDeclaration(member) { if body := member.Body(); body != nil { for _, param := range member.Parameters() { collector.collect(param) } collector.collect(body) } } else if ast.IsClassStaticBlockDeclaration(member) { collector.collect(member) } } case ast.KindClassStaticBlockDeclaration: staticBlock := node.AsClassStaticBlockDeclaration() collector.collect(staticBlock.Body) default: debug.AssertNever(node) } return collector.callSites } func (l *LanguageService) convertCallSiteGroupToOutgoingCall(program *compiler.Program, entries []*callSite) *lsproto.CallHierarchyOutgoingCall { fromRanges := make([]lsproto.Range, len(entries)) for i, entry := range entries { script := l.getScript(entry.sourceFile.AsSourceFile().FileName()) fromRanges[i] = l.converters.ToLSPRange(script, entry.textRange) } slices.SortFunc(fromRanges, func(a, b lsproto.Range) int { return lsproto.CompareRanges(&a, &b) }) return &lsproto.CallHierarchyOutgoingCall{ To: l.createCallHierarchyItem(program, entries[0].declaration), FromRanges: fromRanges, } } // Gets the call sites that call out of the provided call hierarchy declaration. func (l *LanguageService) getOutgoingCalls(program *compiler.Program, declaration *ast.Node) []*lsproto.CallHierarchyOutgoingCall { if (declaration.Flags&ast.NodeFlagsAmbient) != 0 || ast.IsMethodSignatureDeclaration(declaration) { return nil } c, done := program.GetTypeChecker(context.Background()) defer done() callSites := collectCallSites(program, c, declaration) if len(callSites) == 0 { return nil } grouped := make(map[ast.NodeId][]*callSite) for _, site := range callSites { key := getCallSiteGroupKey(site) grouped[key] = append(grouped[key], site) } var result []*lsproto.CallHierarchyOutgoingCall for _, sites := range grouped { result = append(result, l.convertCallSiteGroupToOutgoingCall(program, sites)) } slices.SortFunc(result, func(a, b *lsproto.CallHierarchyOutgoingCall) int { if uriComp := strings.Compare(string(a.To.Uri), string(b.To.Uri)); uriComp != 0 { return uriComp } if len(a.FromRanges) == 0 || len(b.FromRanges) == 0 { return 0 } return lsproto.CompareRanges(&a.FromRanges[0], &b.FromRanges[0]) }) return result } func (l *LanguageService) ProvidePrepareCallHierarchy( ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position, ) (lsproto.CallHierarchyPrepareResponse, error) { program, file := l.getProgramAndFile(documentURI) node := astnav.GetTouchingPropertyName(file, int(l.converters.LineAndCharacterToPosition(file, position))) if node.Kind == ast.KindSourceFile { return lsproto.CallHierarchyItemsOrNull{}, nil } declaration := resolveCallHierarchyDeclaration(program, node) if declaration == nil { return lsproto.CallHierarchyItemsOrNull{}, nil } var items []*lsproto.CallHierarchyItem switch decl := declaration.(type) { case *ast.Node: items = []*lsproto.CallHierarchyItem{l.createCallHierarchyItem(program, decl)} case []*ast.Node: items = make([]*lsproto.CallHierarchyItem, len(decl)) for i, d := range decl { items[i] = l.createCallHierarchyItem(program, d) } } if items == nil { return lsproto.CallHierarchyItemsOrNull{}, nil } return lsproto.CallHierarchyItemsOrNull{CallHierarchyItems: &items}, nil } func (l *LanguageService) ProvideCallHierarchyIncomingCalls( ctx context.Context, item *lsproto.CallHierarchyItem, ) (lsproto.CallHierarchyIncomingCallsResponse, error) { program := l.GetProgram() fileName := item.Uri.FileName() file := program.GetSourceFile(fileName) if file == nil { return lsproto.CallHierarchyIncomingCallsOrNull{}, nil } pos := int(l.converters.LineAndCharacterToPosition(file, item.SelectionRange.Start)) var node *ast.Node if pos == 0 { node = file.AsNode() } else { node = astnav.GetTouchingPropertyName(file, pos) } if node == nil { return lsproto.CallHierarchyIncomingCallsOrNull{}, nil } declaration := resolveCallHierarchyDeclaration(program, node) if declaration == nil { return lsproto.CallHierarchyIncomingCallsOrNull{}, nil } var decl *ast.Node switch d := declaration.(type) { case *ast.Node: decl = d case []*ast.Node: if len(d) > 0 { decl = d[0] } } if decl == nil { return lsproto.CallHierarchyIncomingCallsOrNull{}, nil } calls := l.getIncomingCalls(ctx, program, decl) if calls == nil { return lsproto.CallHierarchyIncomingCallsOrNull{}, nil } return lsproto.CallHierarchyIncomingCallsOrNull{CallHierarchyIncomingCalls: &calls}, nil } func (l *LanguageService) ProvideCallHierarchyOutgoingCalls( ctx context.Context, item *lsproto.CallHierarchyItem, ) (lsproto.CallHierarchyOutgoingCallsResponse, error) { program := l.GetProgram() fileName := item.Uri.FileName() file := program.GetSourceFile(fileName) if file == nil { return lsproto.CallHierarchyOutgoingCallsOrNull{}, nil } pos := int(l.converters.LineAndCharacterToPosition(file, item.SelectionRange.Start)) var node *ast.Node if pos == 0 { node = file.AsNode() } else { node = astnav.GetTouchingPropertyName(file, pos) } if node == nil { return lsproto.CallHierarchyOutgoingCallsOrNull{}, nil } declaration := resolveCallHierarchyDeclaration(program, node) if declaration == nil { return lsproto.CallHierarchyOutgoingCallsOrNull{}, nil } var decl *ast.Node switch d := declaration.(type) { case *ast.Node: decl = d case []*ast.Node: if len(d) > 0 { decl = d[0] } } if decl == nil { return lsproto.CallHierarchyOutgoingCallsOrNull{}, nil } calls := l.getOutgoingCalls(program, decl) if calls == nil { return lsproto.CallHierarchyOutgoingCallsOrNull{}, nil } return lsproto.CallHierarchyOutgoingCallsOrNull{CallHierarchyOutgoingCalls: &calls}, nil }