github.com/april1989/origin-go-tools@v0.0.32/internal/lsp/source/call_hierarchy.go (about) 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. 4 5 package source 6 7 import ( 8 "context" 9 "fmt" 10 "go/ast" 11 "go/token" 12 "go/types" 13 "path/filepath" 14 15 "github.com/april1989/origin-go-tools/go/ast/astutil" 16 "github.com/april1989/origin-go-tools/internal/event" 17 "github.com/april1989/origin-go-tools/internal/lsp/debug/tag" 18 "github.com/april1989/origin-go-tools/internal/lsp/protocol" 19 "github.com/april1989/origin-go-tools/internal/span" 20 errors "golang.org/x/xerrors" 21 ) 22 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") 26 defer done() 27 28 identifier, err := Identifier(ctx, snapshot, fh, pos) 29 if err != nil { 30 if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) { 31 return nil, nil 32 } 33 return nil, err 34 } 35 36 if _, ok := identifier.Declaration.obj.Type().Underlying().(*types.Signature); !ok { 37 return nil, nil 38 } 39 40 if len(identifier.Declaration.MappedRange) == 0 { 41 return nil, nil 42 } 43 declMappedRange := identifier.Declaration.MappedRange[0] 44 rng, err := declMappedRange.Range() 45 if err != nil { 46 return nil, err 47 } 48 49 callHierarchyItem := protocol.CallHierarchyItem{ 50 Name: identifier.Name, 51 Kind: protocol.Function, 52 Tags: []protocol.SymbolTag{}, 53 Detail: fmt.Sprintf("%s • %s", identifier.Declaration.obj.Pkg().Path(), filepath.Base(declMappedRange.URI().Filename())), 54 URI: protocol.DocumentURI(declMappedRange.URI()), 55 Range: rng, 56 SelectionRange: rng, 57 } 58 return []protocol.CallHierarchyItem{callHierarchyItem}, nil 59 } 60 61 // IncomingCalls returns an array of CallHierarchyIncomingCall for a file and the position within the file. 62 func IncomingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyIncomingCall, error) { 63 ctx, done := event.Start(ctx, "source.IncomingCalls") 64 defer done() 65 66 refs, err := References(ctx, snapshot, fh, pos, false) 67 if err != nil { 68 if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) { 69 return nil, nil 70 } 71 return nil, err 72 } 73 74 return toProtocolIncomingCalls(ctx, snapshot, refs) 75 } 76 77 // toProtocolIncomingCalls returns an array of protocol.CallHierarchyIncomingCall for ReferenceInfo's. 78 // References inside same enclosure are assigned to the same enclosing function. 79 func toProtocolIncomingCalls(ctx context.Context, snapshot Snapshot, refs []*ReferenceInfo) ([]protocol.CallHierarchyIncomingCall, error) { 80 // an enclosing node could have multiple calls to a reference, we only show the enclosure 81 // once in the result but highlight all calls using FromRanges (ranges at which the calls occur) 82 var incomingCalls = map[protocol.Location]*protocol.CallHierarchyIncomingCall{} 83 for _, ref := range refs { 84 refRange, err := ref.Range() 85 if err != nil { 86 return nil, err 87 } 88 89 callItem, err := enclosingNodeCallItem(snapshot, ref.pkg, ref.URI(), ref.ident.NamePos) 90 if err != nil { 91 event.Error(ctx, "error getting enclosing node", err, tag.Method.Of(ref.Name)) 92 continue 93 } 94 loc := protocol.Location{ 95 URI: callItem.URI, 96 Range: callItem.Range, 97 } 98 99 if incomingCall, ok := incomingCalls[loc]; ok { 100 incomingCall.FromRanges = append(incomingCall.FromRanges, refRange) 101 continue 102 } 103 incomingCalls[loc] = &protocol.CallHierarchyIncomingCall{ 104 From: callItem, 105 FromRanges: []protocol.Range{refRange}, 106 } 107 } 108 109 incomingCallItems := make([]protocol.CallHierarchyIncomingCall, 0, len(incomingCalls)) 110 for _, callItem := range incomingCalls { 111 incomingCallItems = append(incomingCallItems, *callItem) 112 } 113 return incomingCallItems, nil 114 } 115 116 // enclosingNodeCallItem creates a CallHierarchyItem representing the function call at pos 117 func enclosingNodeCallItem(snapshot Snapshot, pkg Package, uri span.URI, pos token.Pos) (protocol.CallHierarchyItem, error) { 118 pgf, err := pkg.File(uri) 119 if err != nil { 120 return protocol.CallHierarchyItem{}, err 121 } 122 123 var funcDecl *ast.FuncDecl 124 var funcLit *ast.FuncLit // innermost function literal 125 var litCount int 126 // Find the enclosing function, if any, and the number of func literals in between. 127 path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos) 128 outer: 129 for _, node := range path { 130 switch n := node.(type) { 131 case *ast.FuncDecl: 132 funcDecl = n 133 break outer 134 case *ast.FuncLit: 135 litCount++ 136 if litCount > 1 { 137 continue 138 } 139 funcLit = n 140 } 141 } 142 143 nameIdent := path[len(path)-1].(*ast.File).Name 144 kind := protocol.Package 145 if funcDecl != nil { 146 nameIdent = funcDecl.Name 147 kind = protocol.Function 148 } 149 150 nameStart, nameEnd := nameIdent.NamePos, nameIdent.NamePos+token.Pos(len(nameIdent.Name)) 151 if funcLit != nil { 152 nameStart, nameEnd = funcLit.Type.Func, funcLit.Type.Params.Pos() 153 kind = protocol.Function 154 } 155 rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, nameStart, nameEnd).Range() 156 if err != nil { 157 return protocol.CallHierarchyItem{}, err 158 } 159 160 name := nameIdent.Name 161 for i := 0; i < litCount; i++ { 162 name += ".func()" 163 } 164 165 return protocol.CallHierarchyItem{ 166 Name: name, 167 Kind: kind, 168 Tags: []protocol.SymbolTag{}, 169 Detail: fmt.Sprintf("%s • %s", pkg.PkgPath(), filepath.Base(uri.Filename())), 170 URI: protocol.DocumentURI(uri), 171 Range: rng, 172 SelectionRange: rng, 173 }, nil 174 } 175 176 // OutgoingCalls returns an array of CallHierarchyOutgoingCall for a file and the position within the file. 177 func OutgoingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyOutgoingCall, error) { 178 ctx, done := event.Start(ctx, "source.OutgoingCalls") 179 defer done() 180 181 identifier, err := Identifier(ctx, snapshot, fh, pos) 182 if err != nil { 183 if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) { 184 return nil, nil 185 } 186 return nil, err 187 } 188 189 if _, ok := identifier.Declaration.obj.Type().Underlying().(*types.Signature); !ok { 190 return nil, nil 191 } 192 193 if len(identifier.Declaration.MappedRange) == 0 { 194 return nil, nil 195 } 196 declMappedRange := identifier.Declaration.MappedRange[0] 197 callExprs, err := collectCallExpressions(snapshot.FileSet(), declMappedRange.m, identifier.Declaration.node) 198 if err != nil { 199 return nil, err 200 } 201 202 return toProtocolOutgoingCalls(ctx, snapshot, fh, callExprs) 203 } 204 205 // collectCallExpressions collects call expression ranges inside a function. 206 func collectCallExpressions(fset *token.FileSet, mapper *protocol.ColumnMapper, node ast.Node) ([]protocol.Range, error) { 207 type callPos struct { 208 start, end token.Pos 209 } 210 callPositions := []callPos{} 211 212 ast.Inspect(node, func(n ast.Node) bool { 213 if call, ok := n.(*ast.CallExpr); ok { 214 var start, end token.Pos 215 switch n := call.Fun.(type) { 216 case *ast.SelectorExpr: 217 start, end = n.Sel.NamePos, call.Lparen 218 case *ast.Ident: 219 start, end = n.NamePos, call.Lparen 220 default: 221 // ignore any other kind of call expressions 222 // for ex: direct function literal calls since that's not an 'outgoing' call 223 return false 224 } 225 callPositions = append(callPositions, callPos{start: start, end: end}) 226 } 227 return true 228 }) 229 230 callRanges := []protocol.Range{} 231 for _, call := range callPositions { 232 callRange, err := newMappedRange(fset, mapper, call.start, call.end).Range() 233 if err != nil { 234 return nil, err 235 } 236 callRanges = append(callRanges, callRange) 237 } 238 return callRanges, nil 239 } 240 241 // toProtocolOutgoingCalls returns an array of protocol.CallHierarchyOutgoingCall for ast call expressions. 242 // Calls to the same function are assigned to the same declaration. 243 func toProtocolOutgoingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, callRanges []protocol.Range) ([]protocol.CallHierarchyOutgoingCall, error) { 244 // multiple calls could be made to the same function 245 var outgoingCalls = map[ast.Node]*protocol.CallHierarchyOutgoingCall{} 246 for _, callRange := range callRanges { 247 identifier, err := Identifier(ctx, snapshot, fh, callRange.Start) 248 if err != nil { 249 if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) { 250 continue 251 } 252 return nil, err 253 } 254 255 // ignore calls to builtin functions 256 if identifier.Declaration.obj.Pkg() == nil { 257 continue 258 } 259 260 if outgoingCall, ok := outgoingCalls[identifier.Declaration.node]; ok { 261 outgoingCall.FromRanges = append(outgoingCall.FromRanges, callRange) 262 continue 263 } 264 265 if len(identifier.Declaration.MappedRange) == 0 { 266 continue 267 } 268 declMappedRange := identifier.Declaration.MappedRange[0] 269 rng, err := declMappedRange.Range() 270 if err != nil { 271 return nil, err 272 } 273 274 outgoingCalls[identifier.Declaration.node] = &protocol.CallHierarchyOutgoingCall{ 275 To: protocol.CallHierarchyItem{ 276 Name: identifier.Name, 277 Kind: protocol.Function, 278 Tags: []protocol.SymbolTag{}, 279 Detail: fmt.Sprintf("%s • %s", identifier.Declaration.obj.Pkg().Path(), filepath.Base(declMappedRange.URI().Filename())), 280 URI: protocol.DocumentURI(declMappedRange.URI()), 281 Range: rng, 282 SelectionRange: rng, 283 }, 284 FromRanges: []protocol.Range{callRange}, 285 } 286 } 287 288 outgoingCallItems := make([]protocol.CallHierarchyOutgoingCall, 0, len(outgoingCalls)) 289 for _, callItem := range outgoingCalls { 290 outgoingCallItems = append(outgoingCallItems, *callItem) 291 } 292 return outgoingCallItems, nil 293 }