+++ /dev/null
-// 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"
- "go/types"
- "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"
- "golang.org/x/tools/internal/span"
- 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) || errors.Is(err, errNoObjectFound) {
- return nil, nil
- }
- return nil, err
- }
- // The identifier can be nil if it is an import spec.
- if identifier == nil {
- return nil, nil
- }
-
- if _, ok := identifier.Declaration.obj.Type().Underlying().(*types.Signature); !ok {
- return nil, nil
- }
-
- if len(identifier.Declaration.MappedRange) == 0 {
- return nil, nil
- }
- declMappedRange := identifier.Declaration.MappedRange[0]
- rng, err := declMappedRange.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.Declaration.obj.Pkg().Path(), filepath.Base(declMappedRange.URI().Filename())),
- URI: protocol.DocumentURI(declMappedRange.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()
-
- refs, err := References(ctx, snapshot, fh, pos, false)
- if err != nil {
- if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
- return nil, nil
- }
- return nil, err
- }
-
- return toProtocolIncomingCalls(ctx, snapshot, refs)
-}
-
-// 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.Location]*protocol.CallHierarchyIncomingCall{}
- for _, ref := range refs {
- refRange, err := ref.Range()
- if err != nil {
- return nil, err
- }
-
- callItem, err := enclosingNodeCallItem(snapshot, ref.pkg, ref.URI(), ref.ident.NamePos)
- if err != nil {
- event.Error(ctx, "error getting enclosing node", err, tag.Method.Of(ref.Name))
- continue
- }
- loc := protocol.Location{
- URI: callItem.URI,
- Range: callItem.Range,
- }
-
- if incomingCall, ok := incomingCalls[loc]; ok {
- incomingCall.FromRanges = append(incomingCall.FromRanges, refRange)
- continue
- }
- incomingCalls[loc] = &protocol.CallHierarchyIncomingCall{
- From: callItem,
- FromRanges: []protocol.Range{refRange},
- }
- }
-
- incomingCallItems := make([]protocol.CallHierarchyIncomingCall, 0, len(incomingCalls))
- for _, callItem := range incomingCalls {
- incomingCallItems = append(incomingCallItems, *callItem)
- }
- return incomingCallItems, nil
-}
-
-// enclosingNodeCallItem creates a CallHierarchyItem representing the function call at pos
-func enclosingNodeCallItem(snapshot Snapshot, pkg Package, uri span.URI, pos token.Pos) (protocol.CallHierarchyItem, error) {
- pgf, err := pkg.File(uri)
- if err != nil {
- return protocol.CallHierarchyItem{}, 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, pos, pos)
-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
- kind := protocol.Package
- if funcDecl != nil {
- nameIdent = funcDecl.Name
- kind = protocol.Function
- }
-
- nameStart, nameEnd := nameIdent.NamePos, nameIdent.NamePos+token.Pos(len(nameIdent.Name))
- if funcLit != nil {
- nameStart, nameEnd = funcLit.Type.Func, funcLit.Type.Params.Pos()
- kind = protocol.Function
- }
- rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, nameStart, nameEnd).Range()
- if err != nil {
- return protocol.CallHierarchyItem{}, err
- }
-
- name := nameIdent.Name
- for i := 0; i < litCount; i++ {
- name += ".func()"
- }
-
- return protocol.CallHierarchyItem{
- Name: name,
- Kind: kind,
- Tags: []protocol.SymbolTag{},
- Detail: fmt.Sprintf("%s • %s", pkg.PkgPath(), filepath.Base(uri.Filename())),
- URI: protocol.DocumentURI(uri),
- Range: rng,
- SelectionRange: rng,
- }, nil
-}
-
-// 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()
-
- identifier, err := Identifier(ctx, snapshot, fh, pos)
- if err != nil {
- if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
- return nil, nil
- }
- return nil, err
- }
-
- if _, ok := identifier.Declaration.obj.Type().Underlying().(*types.Signature); !ok {
- return nil, nil
- }
-
- if len(identifier.Declaration.MappedRange) == 0 {
- return nil, nil
- }
- declMappedRange := identifier.Declaration.MappedRange[0]
- callExprs, err := collectCallExpressions(snapshot.FileSet(), declMappedRange.m, identifier.Declaration.node)
- if err != nil {
- return nil, err
- }
-
- return toProtocolOutgoingCalls(ctx, snapshot, fh, callExprs)
-}
-
-// collectCallExpressions collects call expression ranges inside a function.
-func collectCallExpressions(fset *token.FileSet, mapper *protocol.ColumnMapper, node ast.Node) ([]protocol.Range, error) {
- type callPos struct {
- start, end token.Pos
- }
- callPositions := []callPos{}
-
- ast.Inspect(node, func(n ast.Node) bool {
- if call, ok := n.(*ast.CallExpr); ok {
- var start, end token.Pos
- switch n := call.Fun.(type) {
- case *ast.SelectorExpr:
- start, end = n.Sel.NamePos, call.Lparen
- case *ast.Ident:
- start, end = n.NamePos, call.Lparen
- default:
- // ignore any other kind of call expressions
- // for ex: direct function literal calls since that's not an 'outgoing' call
- return false
- }
- callPositions = append(callPositions, callPos{start: start, end: end})
- }
- return true
- })
-
- callRanges := []protocol.Range{}
- for _, call := range callPositions {
- callRange, err := NewMappedRange(fset, mapper, call.start, call.end).Range()
- if err != nil {
- return nil, err
- }
- callRanges = append(callRanges, callRange)
- }
- return callRanges, nil
-}
-
-// toProtocolOutgoingCalls returns an array of protocol.CallHierarchyOutgoingCall for ast call expressions.
-// Calls to the same function are assigned to the same declaration.
-func toProtocolOutgoingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, callRanges []protocol.Range) ([]protocol.CallHierarchyOutgoingCall, error) {
- // multiple calls could be made to the same function
- var outgoingCalls = map[ast.Node]*protocol.CallHierarchyOutgoingCall{}
- for _, callRange := range callRanges {
- identifier, err := Identifier(ctx, snapshot, fh, callRange.Start)
- if err != nil {
- if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
- continue
- }
- return nil, err
- }
-
- // ignore calls to builtin functions
- if identifier.Declaration.obj.Pkg() == nil {
- continue
- }
-
- if outgoingCall, ok := outgoingCalls[identifier.Declaration.node]; ok {
- outgoingCall.FromRanges = append(outgoingCall.FromRanges, callRange)
- continue
- }
-
- if len(identifier.Declaration.MappedRange) == 0 {
- continue
- }
- declMappedRange := identifier.Declaration.MappedRange[0]
- rng, err := declMappedRange.Range()
- if err != nil {
- return nil, err
- }
-
- outgoingCalls[identifier.Declaration.node] = &protocol.CallHierarchyOutgoingCall{
- To: protocol.CallHierarchyItem{
- Name: identifier.Name,
- Kind: protocol.Function,
- Tags: []protocol.SymbolTag{},
- Detail: fmt.Sprintf("%s • %s", identifier.Declaration.obj.Pkg().Path(), filepath.Base(declMappedRange.URI().Filename())),
- URI: protocol.DocumentURI(declMappedRange.URI()),
- Range: rng,
- SelectionRange: rng,
- },
- FromRanges: []protocol.Range{callRange},
- }
- }
-
- outgoingCallItems := make([]protocol.CallHierarchyOutgoingCall, 0, len(outgoingCalls))
- for _, callItem := range outgoingCalls {
- outgoingCallItems = append(outgoingCallItems, *callItem)
- }
- return outgoingCallItems, nil
-}