mirror of
https://github.com/golang/go
synced 2024-11-18 16:14:46 -07:00
1365742343
* Adds incoming calls hierarchy to gopls. Returns function declarations/function literals/files enclosing the call/s to the function being inpected. * Updates cmd to show ranges where calls to function in consideration are made by the caller. * Added tests for incoming calls. Example: This example shows call hierarchy for PathEnclosingInterval in tools/go/ast/astutil.go Show Call Hierarchy View: https://imgur.com/a/9VhspgA Peek Call Hierarchy View: https://imgur.com/a/XlKubFk Note: * Function literals show up as <scope>.func() in call hierarchy since they don't have a name. Here scope is either the function enclosing the literal or a file for top level declarations * Top level calls (calls not inside a function, ex: to initialize exported variables) show up as the file name * Clicking on an item shows the the range where a call is made in the scope Change-Id: I56426139e4e550dfabe43c9e9f1838efd1e43e38 Reviewed-on: https://go-review.googlesource.com/c/tools/+/247699 Run-TryBot: Danish Dua <danishdua@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
198 lines
6.4 KiB
Go
198 lines
6.4 KiB
Go
// Copyright 2020 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package source
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"path/filepath"
|
|
|
|
"golang.org/x/tools/go/ast/astutil"
|
|
"golang.org/x/tools/internal/event"
|
|
"golang.org/x/tools/internal/lsp/debug/tag"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
// PrepareCallHierarchy returns an array of CallHierarchyItem for a file and the position within the file
|
|
func PrepareCallHierarchy(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyItem, error) {
|
|
ctx, done := event.Start(ctx, "source.prepareCallHierarchy")
|
|
defer done()
|
|
|
|
identifier, err := Identifier(ctx, snapshot, fh, pos)
|
|
if err != nil {
|
|
if errors.Is(err, ErrNoIdentFound) {
|
|
event.Log(ctx, err.Error(), tag.Position.Of(pos))
|
|
} else {
|
|
event.Error(ctx, "error getting identifier", err, tag.Position.Of(pos))
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// if identifier's declaration is not of type function declaration
|
|
_, ok := identifier.Declaration.node.(*ast.FuncDecl)
|
|
if !ok {
|
|
event.Log(ctx, "invalid identifier declaration, expected funtion declaration", tag.Position.Of(pos))
|
|
return nil, nil
|
|
}
|
|
rng, err := identifier.Range()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
callHierarchyItem := protocol.CallHierarchyItem{
|
|
Name: identifier.Name,
|
|
Kind: protocol.Function,
|
|
Tags: []protocol.SymbolTag{},
|
|
Detail: fmt.Sprintf("%s %s", identifier.pkg.PkgPath(), filepath.Base(fh.URI().Filename())),
|
|
URI: protocol.DocumentURI(fh.URI()),
|
|
Range: rng,
|
|
SelectionRange: rng,
|
|
}
|
|
return []protocol.CallHierarchyItem{callHierarchyItem}, nil
|
|
}
|
|
|
|
// IncomingCalls returns an array of CallHierarchyIncomingCall for a file and the position within the file
|
|
func IncomingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyIncomingCall, error) {
|
|
ctx, done := event.Start(ctx, "source.incomingCalls")
|
|
defer done()
|
|
|
|
qualifiedObjs, err := qualifiedObjsAtProtocolPos(ctx, snapshot, fh, pos)
|
|
if err != nil {
|
|
if errors.Is(err, errBuiltin) || errors.Is(err, ErrNoIdentFound) {
|
|
event.Log(ctx, err.Error(), tag.Position.Of(pos))
|
|
} else {
|
|
event.Error(ctx, "error getting identifier", err, tag.Position.Of(pos))
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
refs, err := references(ctx, snapshot, qualifiedObjs, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return toProtocolIncomingCalls(ctx, snapshot, refs)
|
|
}
|
|
|
|
// OutgoingCalls returns an array of CallHierarchyOutgoingCall for a file and the position within the file
|
|
func OutgoingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyOutgoingCall, error) {
|
|
ctx, done := event.Start(ctx, "source.outgoingCalls")
|
|
defer done()
|
|
|
|
// TODO: Remove this once the context is used.
|
|
_ = ctx // avoid staticcheck SA4006 warning
|
|
|
|
return []protocol.CallHierarchyOutgoingCall{}, nil
|
|
}
|
|
|
|
// toProtocolIncomingCalls returns an array of protocol.CallHierarchyIncomingCall for ReferenceInfo's.
|
|
// References inside same enclosure are assigned to the same enclosing function.
|
|
func toProtocolIncomingCalls(ctx context.Context, snapshot Snapshot, refs []*ReferenceInfo) ([]protocol.CallHierarchyIncomingCall, error) {
|
|
// an enclosing node could have multiple calls to a reference, we only show the enclosure
|
|
// once in the result but highlight all calls using FromRanges (ranges at which the calls occur)
|
|
var incomingCalls = map[protocol.Range]*protocol.CallHierarchyIncomingCall{}
|
|
for _, ref := range refs {
|
|
refRange, err := ref.Range()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
enclosingName, enclosingRange, err := enclosingNodeInfo(snapshot, ref)
|
|
if err != nil {
|
|
event.Error(ctx, "error getting enclosing node", err, tag.Method.Of(ref.Name))
|
|
continue
|
|
}
|
|
|
|
if incomingCall, ok := incomingCalls[enclosingRange]; ok {
|
|
incomingCall.FromRanges = append(incomingCall.FromRanges, refRange)
|
|
continue
|
|
}
|
|
|
|
incomingCalls[enclosingRange] = &protocol.CallHierarchyIncomingCall{
|
|
From: protocol.CallHierarchyItem{
|
|
Name: enclosingName,
|
|
Kind: protocol.Function,
|
|
Tags: []protocol.SymbolTag{},
|
|
Detail: fmt.Sprintf("%s • %s", ref.pkg.PkgPath(), filepath.Base(ref.URI().Filename())),
|
|
URI: protocol.DocumentURI(ref.URI()),
|
|
Range: enclosingRange,
|
|
SelectionRange: enclosingRange,
|
|
},
|
|
FromRanges: []protocol.Range{refRange},
|
|
}
|
|
}
|
|
|
|
incomingCallItems := make([]protocol.CallHierarchyIncomingCall, 0, len(incomingCalls))
|
|
for _, callItem := range incomingCalls {
|
|
incomingCallItems = append(incomingCallItems, *callItem)
|
|
}
|
|
return incomingCallItems, nil
|
|
}
|
|
|
|
// enclosingNodeInfo returns name and position for package/function declaration/function literal
|
|
// containing given call reference
|
|
func enclosingNodeInfo(snapshot Snapshot, ref *ReferenceInfo) (string, protocol.Range, error) {
|
|
pgf, err := ref.pkg.File(ref.URI())
|
|
if err != nil {
|
|
return "", protocol.Range{}, err
|
|
}
|
|
|
|
var funcDecl *ast.FuncDecl
|
|
var funcLit *ast.FuncLit // innermost function literal
|
|
var litCount int
|
|
// Find the enclosing function, if any, and the number of func literals in between.
|
|
path, _ := astutil.PathEnclosingInterval(pgf.File, ref.ident.NamePos, ref.ident.NamePos)
|
|
outer:
|
|
for _, node := range path {
|
|
switch n := node.(type) {
|
|
case *ast.FuncDecl:
|
|
funcDecl = n
|
|
break outer
|
|
case *ast.FuncLit:
|
|
litCount++
|
|
if litCount > 1 {
|
|
continue
|
|
}
|
|
funcLit = n
|
|
}
|
|
}
|
|
|
|
nameIdent := path[len(path)-1].(*ast.File).Name
|
|
if funcDecl != nil {
|
|
nameIdent = funcDecl.Name
|
|
}
|
|
|
|
nameStart, nameEnd := nameIdent.NamePos, nameIdent.NamePos+token.Pos(len(nameIdent.Name))
|
|
if funcLit != nil {
|
|
nameStart, nameEnd = funcLit.Type.Func, funcLit.Type.Params.Pos()
|
|
}
|
|
rng, err := posToProtocolRange(snapshot, ref.pkg, nameStart, nameEnd)
|
|
if err != nil {
|
|
return "", protocol.Range{}, err
|
|
}
|
|
|
|
name := nameIdent.Name
|
|
for i := 0; i < litCount; i++ {
|
|
name += ".func()"
|
|
}
|
|
|
|
return name, rng, nil
|
|
}
|
|
|
|
// posToProtocolRange returns protocol.Range for start and end token.Pos
|
|
func posToProtocolRange(snapshot Snapshot, pkg Package, start, end token.Pos) (protocol.Range, error) {
|
|
mappedRange, err := posToMappedRange(snapshot, pkg, start, end)
|
|
if err != nil {
|
|
return protocol.Range{}, err
|
|
}
|
|
protocolRange, err := mappedRange.Range()
|
|
if err != nil {
|
|
return protocol.Range{}, err
|
|
}
|
|
return protocolRange, nil
|
|
}
|