1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
15 "golang.org/x/tools/go/ast/astutil"
16 "golang.org/x/tools/internal/event"
17 "golang.org/x/tools/internal/lsp/debug/tag"
18 "golang.org/x/tools/internal/lsp/protocol"
19 "golang.org/x/tools/internal/span"
20 errors "golang.org/x/xerrors"
23 // PrepareCallHierarchy returns an array of CallHierarchyItem for a file and the position within the file.
24 func PrepareCallHierarchy(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyItem, error) {
25 ctx, done := event.Start(ctx, "source.PrepareCallHierarchy")
28 identifier, err := Identifier(ctx, snapshot, fh, pos)
30 if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
35 // The identifier can be nil if it is an import spec.
36 if identifier == nil {
40 if _, ok := identifier.Declaration.obj.Type().Underlying().(*types.Signature); !ok {
44 if len(identifier.Declaration.MappedRange) == 0 {
47 declMappedRange := identifier.Declaration.MappedRange[0]
48 rng, err := declMappedRange.Range()
53 callHierarchyItem := protocol.CallHierarchyItem{
54 Name: identifier.Name,
55 Kind: protocol.Function,
56 Tags: []protocol.SymbolTag{},
57 Detail: fmt.Sprintf("%s • %s", identifier.Declaration.obj.Pkg().Path(), filepath.Base(declMappedRange.URI().Filename())),
58 URI: protocol.DocumentURI(declMappedRange.URI()),
62 return []protocol.CallHierarchyItem{callHierarchyItem}, nil
65 // IncomingCalls returns an array of CallHierarchyIncomingCall for a file and the position within the file.
66 func IncomingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyIncomingCall, error) {
67 ctx, done := event.Start(ctx, "source.IncomingCalls")
70 refs, err := References(ctx, snapshot, fh, pos, false)
72 if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
78 return toProtocolIncomingCalls(ctx, snapshot, refs)
81 // toProtocolIncomingCalls returns an array of protocol.CallHierarchyIncomingCall for ReferenceInfo's.
82 // References inside same enclosure are assigned to the same enclosing function.
83 func toProtocolIncomingCalls(ctx context.Context, snapshot Snapshot, refs []*ReferenceInfo) ([]protocol.CallHierarchyIncomingCall, error) {
84 // an enclosing node could have multiple calls to a reference, we only show the enclosure
85 // once in the result but highlight all calls using FromRanges (ranges at which the calls occur)
86 var incomingCalls = map[protocol.Location]*protocol.CallHierarchyIncomingCall{}
87 for _, ref := range refs {
88 refRange, err := ref.Range()
93 callItem, err := enclosingNodeCallItem(snapshot, ref.pkg, ref.URI(), ref.ident.NamePos)
95 event.Error(ctx, "error getting enclosing node", err, tag.Method.Of(ref.Name))
98 loc := protocol.Location{
100 Range: callItem.Range,
103 if incomingCall, ok := incomingCalls[loc]; ok {
104 incomingCall.FromRanges = append(incomingCall.FromRanges, refRange)
107 incomingCalls[loc] = &protocol.CallHierarchyIncomingCall{
109 FromRanges: []protocol.Range{refRange},
113 incomingCallItems := make([]protocol.CallHierarchyIncomingCall, 0, len(incomingCalls))
114 for _, callItem := range incomingCalls {
115 incomingCallItems = append(incomingCallItems, *callItem)
117 return incomingCallItems, nil
120 // enclosingNodeCallItem creates a CallHierarchyItem representing the function call at pos
121 func enclosingNodeCallItem(snapshot Snapshot, pkg Package, uri span.URI, pos token.Pos) (protocol.CallHierarchyItem, error) {
122 pgf, err := pkg.File(uri)
124 return protocol.CallHierarchyItem{}, err
127 var funcDecl *ast.FuncDecl
128 var funcLit *ast.FuncLit // innermost function literal
130 // Find the enclosing function, if any, and the number of func literals in between.
131 path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)
133 for _, node := range path {
134 switch n := node.(type) {
147 nameIdent := path[len(path)-1].(*ast.File).Name
148 kind := protocol.Package
150 nameIdent = funcDecl.Name
151 kind = protocol.Function
154 nameStart, nameEnd := nameIdent.NamePos, nameIdent.NamePos+token.Pos(len(nameIdent.Name))
156 nameStart, nameEnd = funcLit.Type.Func, funcLit.Type.Params.Pos()
157 kind = protocol.Function
159 rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, nameStart, nameEnd).Range()
161 return protocol.CallHierarchyItem{}, err
164 name := nameIdent.Name
165 for i := 0; i < litCount; i++ {
169 return protocol.CallHierarchyItem{
172 Tags: []protocol.SymbolTag{},
173 Detail: fmt.Sprintf("%s • %s", pkg.PkgPath(), filepath.Base(uri.Filename())),
174 URI: protocol.DocumentURI(uri),
180 // OutgoingCalls returns an array of CallHierarchyOutgoingCall for a file and the position within the file.
181 func OutgoingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyOutgoingCall, error) {
182 ctx, done := event.Start(ctx, "source.OutgoingCalls")
185 identifier, err := Identifier(ctx, snapshot, fh, pos)
187 if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
193 if _, ok := identifier.Declaration.obj.Type().Underlying().(*types.Signature); !ok {
196 if identifier.Declaration.node == nil {
199 if len(identifier.Declaration.MappedRange) == 0 {
202 declMappedRange := identifier.Declaration.MappedRange[0]
203 callExprs, err := collectCallExpressions(snapshot.FileSet(), declMappedRange.m, identifier.Declaration.node)
208 return toProtocolOutgoingCalls(ctx, snapshot, fh, callExprs)
211 // collectCallExpressions collects call expression ranges inside a function.
212 func collectCallExpressions(fset *token.FileSet, mapper *protocol.ColumnMapper, node ast.Node) ([]protocol.Range, error) {
213 type callPos struct {
216 callPositions := []callPos{}
218 ast.Inspect(node, func(n ast.Node) bool {
219 if call, ok := n.(*ast.CallExpr); ok {
220 var start, end token.Pos
221 switch n := call.Fun.(type) {
222 case *ast.SelectorExpr:
223 start, end = n.Sel.NamePos, call.Lparen
225 start, end = n.NamePos, call.Lparen
227 // ignore any other kind of call expressions
228 // for ex: direct function literal calls since that's not an 'outgoing' call
231 callPositions = append(callPositions, callPos{start: start, end: end})
236 callRanges := []protocol.Range{}
237 for _, call := range callPositions {
238 callRange, err := NewMappedRange(fset, mapper, call.start, call.end).Range()
242 callRanges = append(callRanges, callRange)
244 return callRanges, nil
247 // toProtocolOutgoingCalls returns an array of protocol.CallHierarchyOutgoingCall for ast call expressions.
248 // Calls to the same function are assigned to the same declaration.
249 func toProtocolOutgoingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, callRanges []protocol.Range) ([]protocol.CallHierarchyOutgoingCall, error) {
250 // Multiple calls could be made to the same function, defined by "same declaration
251 // AST node & same idenfitier name" to provide a unique identifier key even when
252 // the func is declared in a struct or interface.
257 outgoingCalls := map[key]*protocol.CallHierarchyOutgoingCall{}
258 for _, callRange := range callRanges {
259 identifier, err := Identifier(ctx, snapshot, fh, callRange.Start)
261 if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
267 // ignore calls to builtin functions
268 if identifier.Declaration.obj.Pkg() == nil {
272 if outgoingCall, ok := outgoingCalls[key{identifier.Declaration.node, identifier.Name}]; ok {
273 outgoingCall.FromRanges = append(outgoingCall.FromRanges, callRange)
277 if len(identifier.Declaration.MappedRange) == 0 {
280 declMappedRange := identifier.Declaration.MappedRange[0]
281 rng, err := declMappedRange.Range()
286 outgoingCalls[key{identifier.Declaration.node, identifier.Name}] = &protocol.CallHierarchyOutgoingCall{
287 To: protocol.CallHierarchyItem{
288 Name: identifier.Name,
289 Kind: protocol.Function,
290 Tags: []protocol.SymbolTag{},
291 Detail: fmt.Sprintf("%s • %s", identifier.Declaration.obj.Pkg().Path(), filepath.Base(declMappedRange.URI().Filename())),
292 URI: protocol.DocumentURI(declMappedRange.URI()),
296 FromRanges: []protocol.Range{callRange},
300 outgoingCallItems := make([]protocol.CallHierarchyOutgoingCall, 0, len(outgoingCalls))
301 for _, callItem := range outgoingCalls {
302 outgoingCallItems = append(outgoingCallItems, *callItem)
304 return outgoingCallItems, nil