internal: add call hierarchy cmd and lsp scaffolding
* adds gopls command line tool for call hierarchy
* adds lsp setup for call hierarchy
* adds handler for textDocument/prepareCallHierarchy to display selected
identifier and get incoming/outgoing calls for it
* setup testing
Change-Id: I0a0904abdbe11273a56162b6e5be93b97ceb9c26
Reviewed-on: https://go-review.googlesource.com/c/tools/+/246521
Run-TryBot: Danish Dua <danishdua@google.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/gopls/integration/parse/protocol.go b/gopls/integration/parse/protocol.go
index d812a54..d1e90ed 100644
--- a/gopls/integration/parse/protocol.go
+++ b/gopls/integration/parse/protocol.go
@@ -87,6 +87,10 @@
return new(p.TextDocumentPositionParams)
case "textDocument/foldingRange":
return new(p.FoldingRangeParams)
+ case "textDocument/incomingCalls":
+ return new(p.CallHierarchyIncomingCallsParams)
+ case "textDocument/outgoingCalls":
+ return new(p.CallHierarchyOutgoingCallsParams)
}
log.Fatalf("request(%s) undefined", m)
return ""
@@ -210,6 +214,10 @@
return []interface{}{new(p.Range), nil}
case "textDocument/foldingRange":
return []interface{}{new([]p.FoldingRange), nil}
+ case "callHierarchy/incomingCalls":
+ return []interface{}{new([]p.CallHierarchyIncomingCall), nil}
+ case "callHierarchy/outgoingCalls":
+ return []interface{}{new([]p.CallHierarchyOutgoingCall), nil}
}
log.Fatalf("responses(%q) undefined", m)
return nil
@@ -307,4 +315,6 @@
"textDocument/rename": Mreq | Mcl,
"textDocument/prepareRename": Mreq | Mcl,
"textDocument/foldingRange": Mreq | Mcl,
+ "callHierarchy/incomingCalls": Mreq | Mcl,
+ "callHierarchy/outgoingCalls": Mreq | Mcl,
}
diff --git a/internal/lsp/call_hierarchy.go b/internal/lsp/call_hierarchy.go
new file mode 100644
index 0000000..f9307d7
--- /dev/null
+++ b/internal/lsp/call_hierarchy.go
@@ -0,0 +1,39 @@
+// 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 lsp
+
+import (
+ "context"
+
+ "golang.org/x/tools/internal/lsp/protocol"
+ "golang.org/x/tools/internal/lsp/source"
+)
+
+func (s *Server) prepareCallHierarchy(ctx context.Context, params *protocol.CallHierarchyPrepareParams) ([]protocol.CallHierarchyItem, error) {
+ snapshot, fh, ok, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go)
+ if !ok {
+ return nil, err
+ }
+
+ return source.PrepareCallHierarchy(ctx, snapshot, fh, params.Position)
+}
+
+func (s *Server) incomingCalls(ctx context.Context, params *protocol.CallHierarchyIncomingCallsParams) ([]protocol.CallHierarchyIncomingCall, error) {
+ snapshot, fh, ok, err := s.beginFileRequest(ctx, params.Item.URI, source.Go)
+ if !ok {
+ return nil, err
+ }
+
+ return source.IncomingCalls(ctx, snapshot, fh, params.Item.Range.Start)
+}
+
+func (s *Server) outgoingCalls(ctx context.Context, params *protocol.CallHierarchyOutgoingCallsParams) ([]protocol.CallHierarchyOutgoingCall, error) {
+ snapshot, fh, ok, err := s.beginFileRequest(ctx, params.Item.URI, source.Go)
+ if !ok {
+ return nil, err
+ }
+
+ return source.OutgoingCalls(ctx, snapshot, fh, params.Item.Range.Start)
+}
diff --git a/internal/lsp/cmd/call_hierarchy.go b/internal/lsp/cmd/call_hierarchy.go
new file mode 100644
index 0000000..b7b5fee
--- /dev/null
+++ b/internal/lsp/cmd/call_hierarchy.go
@@ -0,0 +1,117 @@
+// 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 cmd
+
+import (
+ "context"
+ "flag"
+ "fmt"
+
+ "golang.org/x/tools/internal/lsp/protocol"
+ "golang.org/x/tools/internal/span"
+ "golang.org/x/tools/internal/tool"
+)
+
+// callHierarchy implements the callHierarchy verb for gopls
+type callHierarchy struct {
+ app *Application
+}
+
+func (c *callHierarchy) Name() string { return "call_hierarchy" }
+func (c *callHierarchy) Usage() string { return "<position>" }
+func (c *callHierarchy) ShortHelp() string { return "display selected identifier's call hierarchy" }
+func (c *callHierarchy) DetailedHelp(f *flag.FlagSet) {
+ fmt.Fprint(f.Output(), `
+Example:
+
+ $ # 1-indexed location (:line:column or :#offset) of the target identifier
+ $ gopls call_hierarchy helper/helper.go:8:6
+ $ gopls call_hierarchy helper/helper.go:#53
+
+ gopls call_hierarchy flags are:
+`)
+ f.PrintDefaults()
+}
+
+func (c *callHierarchy) Run(ctx context.Context, args ...string) error {
+ if len(args) != 1 {
+ return tool.CommandLineErrorf("call_hierarchy expects 1 argument (position)")
+ }
+
+ conn, err := c.app.connect(ctx)
+ if err != nil {
+ return err
+ }
+ defer conn.terminate(ctx)
+
+ from := span.Parse(args[0])
+ file := conn.AddFile(ctx, from.URI())
+ if file.err != nil {
+ return file.err
+ }
+
+ columnMapper := file.mapper
+ loc, err := columnMapper.Location(from)
+ if err != nil {
+ return err
+ }
+
+ p := protocol.CallHierarchyPrepareParams{
+ TextDocumentPositionParams: protocol.TextDocumentPositionParams{
+ TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
+ Position: loc.Range.Start,
+ },
+ }
+
+ callItems, err := conn.PrepareCallHierarchy(ctx, &p)
+ if err != nil {
+ return err
+ }
+ if len(callItems) == 0 {
+ return fmt.Errorf("function declaration identifier not found at %v", args[0])
+ }
+
+ for _, item := range callItems {
+ incomingCalls, err := conn.IncomingCalls(ctx, &protocol.CallHierarchyIncomingCallsParams{Item: item})
+ if err != nil {
+ return err
+ }
+ for i, call := range incomingCalls {
+ printString, err := toPrintString(columnMapper, call.From)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("caller[%d]: %s\n", i, printString)
+ }
+
+ printString, err := toPrintString(columnMapper, item)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("identifier: %s\n", printString)
+
+ outgoingCalls, err := conn.OutgoingCalls(ctx, &protocol.CallHierarchyOutgoingCallsParams{Item: item})
+ if err != nil {
+ return err
+ }
+ for i, call := range outgoingCalls {
+ printString, err := toPrintString(columnMapper, call.To)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("callee[%d]: %s\n", i, printString)
+ }
+ }
+
+ return nil
+}
+
+func toPrintString(mapper *protocol.ColumnMapper, item protocol.CallHierarchyItem) (string, error) {
+ span, err := mapper.Span(protocol.Location{URI: item.URI, Range: item.Range})
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("%v %v at %v", item.Detail, item.Name, span), nil
+}
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index 1b142ec..92a817a 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -175,6 +175,7 @@
func (app *Application) featureCommands() []tool.Application {
return []tool.Application{
+ &callHierarchy{app: app},
&check{app: app},
&definition{app: app},
&foldingRanges{app: app},
diff --git a/internal/lsp/cmd/test/call_hierarchy.go b/internal/lsp/cmd/test/call_hierarchy.go
new file mode 100644
index 0000000..6ec689d
--- /dev/null
+++ b/internal/lsp/cmd/test/call_hierarchy.go
@@ -0,0 +1,50 @@
+// 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 cmdtest
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+ "testing"
+
+ "golang.org/x/tools/internal/lsp/tests"
+ "golang.org/x/tools/internal/span"
+)
+
+func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests.CallHierarchyResult) {
+ var result []string
+ // TODO: add expectedCalls.IncomingCalls and expectedCalls.OutgoingCalls to this array once implemented
+ result = append(result, fmt.Sprint(spn))
+
+ sort.Strings(result) // to make tests deterministic
+ expect := r.Normalize(strings.Join(result, "\n"))
+
+ uri := spn.URI()
+ filename := uri.Filename()
+ target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column())
+
+ got, stderr := r.NormalizeGoplsCmd(t, "call_hierarchy", target)
+ got = cleanCallHierarchyCmdResult(got)
+ if stderr != "" {
+ t.Errorf("call_hierarchy failed for %s: %s", target, stderr)
+ } else if expect != got {
+ t.Errorf("call_hierarchy failed for %s expected:\n%s\ngot:\n%s", target, expect, got)
+ }
+}
+
+// removes all info except URI and Range from printed output and sorts the result
+// ex: "identifier: func() d at file://callhierarchy/callhierarchy.go:19:6-7" -> "file://callhierarchy/callhierarchy.go:19:6-7"
+func cleanCallHierarchyCmdResult(output string) string {
+ var clean []string
+ for _, out := range strings.Split(output, "\n") {
+ if out == "" {
+ continue
+ }
+ clean = append(clean, out[strings.LastIndex(out, " ")+1:])
+ }
+ sort.Strings(clean)
+ return strings.Join(clean, "\n")
+}
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index caab6d2..ed9a74e 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -85,7 +85,8 @@
return &protocol.InitializeResult{
Capabilities: protocol.ServerCapabilities{
- CodeActionProvider: codeActionProvider,
+ CallHierarchyProvider: true,
+ CodeActionProvider: codeActionProvider,
CompletionProvider: protocol.CompletionOptions{
TriggerCharacters: []string{"."},
},
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 3e16515..579f51d 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -99,6 +99,50 @@
}
}
+func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests.CallHierarchyResult) {
+ mapper, err := r.data.Mapper(spn.URI())
+ if err != nil {
+ t.Fatal(err)
+ }
+ loc, err := mapper.Location(spn)
+ if err != nil {
+ t.Fatalf("failed for %v: %v", spn, err)
+ }
+
+ params := &protocol.CallHierarchyPrepareParams{
+ TextDocumentPositionParams: protocol.TextDocumentPositionParams{
+ TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
+ Position: loc.Range.Start,
+ },
+ }
+
+ items, err := r.server.PrepareCallHierarchy(r.ctx, params)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(items) == 0 {
+ t.Errorf("expected call hierarchy item to be returned for identifier at %v\n", loc.Range)
+ }
+
+ callLocation := protocol.Location{
+ URI: items[0].URI,
+ Range: items[0].Range,
+ }
+ if callLocation != loc {
+ t.Errorf("expected server.PrepareCallHierarchy to return identifier at %v but got %v\n", loc, callLocation)
+ }
+
+ // TODO: add span comparison tests for expectedCalls once call hierarchy is implemented
+ incomingCalls, err := r.server.IncomingCalls(r.ctx, &protocol.CallHierarchyIncomingCallsParams{Item: items[0]})
+ if len(incomingCalls) != 0 {
+ t.Errorf("expected no incoming calls but got %d", len(incomingCalls))
+ }
+ outgoingCalls, err := r.server.OutgoingCalls(r.ctx, &protocol.CallHierarchyOutgoingCallsParams{Item: items[0]})
+ if len(outgoingCalls) != 0 {
+ t.Errorf("expected no outgoing calls but got %d", len(outgoingCalls))
+ }
+}
+
func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) {
if source.DetectLanguage("", uri.Filename()) != source.Mod {
return
diff --git a/internal/lsp/server_gen.go b/internal/lsp/server_gen.go
index 8604bb9..6f9eeb8 100644
--- a/internal/lsp/server_gen.go
+++ b/internal/lsp/server_gen.go
@@ -100,8 +100,8 @@
return s.implementation(ctx, params)
}
-func (s *Server) IncomingCalls(context.Context, *protocol.CallHierarchyIncomingCallsParams) ([]protocol.CallHierarchyIncomingCall, error) {
- return nil, notImplemented("IncomingCalls")
+func (s *Server) IncomingCalls(ctx context.Context, params *protocol.CallHierarchyIncomingCallsParams) ([]protocol.CallHierarchyIncomingCall, error) {
+ return s.incomingCalls(ctx, params)
}
func (s *Server) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
@@ -124,12 +124,12 @@
return nil, notImplemented("OnTypeFormatting")
}
-func (s *Server) OutgoingCalls(context.Context, *protocol.CallHierarchyOutgoingCallsParams) ([]protocol.CallHierarchyOutgoingCall, error) {
- return nil, notImplemented("OutgoingCalls")
+func (s *Server) OutgoingCalls(ctx context.Context, params *protocol.CallHierarchyOutgoingCallsParams) ([]protocol.CallHierarchyOutgoingCall, error) {
+ return s.outgoingCalls(ctx, params)
}
-func (s *Server) PrepareCallHierarchy(context.Context, *protocol.CallHierarchyPrepareParams) ([]protocol.CallHierarchyItem, error) {
- return nil, notImplemented("PrepareCallHierarchy")
+func (s *Server) PrepareCallHierarchy(ctx context.Context, params *protocol.CallHierarchyPrepareParams) ([]protocol.CallHierarchyItem, error) {
+ return s.prepareCallHierarchy(ctx, params)
}
func (s *Server) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.Range, error) {
diff --git a/internal/lsp/source/call_hierarchy.go b/internal/lsp/source/call_hierarchy.go
new file mode 100644
index 0000000..6ec7a71
--- /dev/null
+++ b/internal/lsp/source/call_hierarchy.go
@@ -0,0 +1,68 @@
+// 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"
+ "go/ast"
+
+ "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 is not of type function
+ _, ok := identifier.Declaration.node.(*ast.FuncDecl)
+ if !ok {
+ event.Log(ctx, "invalid identifier type, 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: "func()",
+ 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()
+
+ return []protocol.CallHierarchyIncomingCall{}, 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()
+
+ return []protocol.CallHierarchyOutgoingCall{}, nil
+}
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 5db5d64..c8e4d99 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -96,6 +96,47 @@
}
}
+func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests.CallHierarchyResult) {
+ mapper, err := r.data.Mapper(spn.URI())
+ if err != nil {
+ t.Fatal(err)
+ }
+ loc, err := mapper.Location(spn)
+ if err != nil {
+ t.Fatalf("failed for %v: %v", spn, err)
+ }
+ fh, err := r.view.Snapshot().GetFile(r.ctx, spn.URI())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ items, err := source.PrepareCallHierarchy(r.ctx, r.view.Snapshot(), fh, loc.Range.Start)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(items) == 0 {
+ t.Errorf("expected call hierarchy item to be returned for identifier at %v\n", loc.Range)
+ }
+
+ callLocation := protocol.Location{
+ URI: items[0].URI,
+ Range: items[0].Range,
+ }
+ if callLocation != loc {
+ t.Errorf("expected source.PrepareCallHierarchy to return identifier at %v but got %v\n", loc, callLocation)
+ }
+
+ // TODO: add span comparison tests for expectedCalls once call hierarchy is implemented
+ incomingCalls, err := source.IncomingCalls(r.ctx, r.view.Snapshot(), fh, loc.Range.Start)
+ if len(incomingCalls) != 0 {
+ t.Errorf("expected no incoming calls but got %d", len(incomingCalls))
+ }
+ outgoingCalls, err := source.OutgoingCalls(r.ctx, r.view.Snapshot(), fh, loc.Range.Start)
+ if len(outgoingCalls) != 0 {
+ t.Errorf("expected no outgoing calls but got %d", len(outgoingCalls))
+ }
+}
+
func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []*source.Diagnostic) {
fileID, got, err := source.FileDiagnostics(r.ctx, r.snapshot, uri)
if err != nil {
diff --git a/internal/lsp/testdata/lsp/primarymod/callhierarchy/callhierarchy.go b/internal/lsp/testdata/lsp/primarymod/callhierarchy/callhierarchy.go
new file mode 100644
index 0000000..b7c774b
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/callhierarchy/callhierarchy.go
@@ -0,0 +1,35 @@
+// 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 main
+
+func a() { //@mark(funcA, "a")
+ d()
+}
+
+func b() { //@mark(funcB, "b")
+ d()
+}
+
+func c() { //@mark(funcC, "c")
+ d()
+}
+
+func d() { //@mark(funcD, "d"),incomingcalls("d", funcA, funcB, funcC),outgoingcalls("d", funcE, funcF, funcG)
+ e()
+ f()
+ g()
+}
+
+func e() {} //@mark(funcE, "e")
+
+func f() {} //@mark(funcF, "f")
+
+func g() {} //@mark(funcG, "g")
+
+func main() {
+ a()
+ b()
+ c()
+}
diff --git a/internal/lsp/testdata/lsp/summary.txt.golden b/internal/lsp/testdata/lsp/summary.txt.golden
index 91b872a..1b3e050 100644
--- a/internal/lsp/testdata/lsp/summary.txt.golden
+++ b/internal/lsp/testdata/lsp/summary.txt.golden
@@ -1,4 +1,5 @@
-- summary --
+CallHierarchyCount = 1
CodeLensCount = 4
CompletionsCount = 241
CompletionSnippetCount = 81
diff --git a/internal/lsp/testdata/missingdep/summary.txt.golden b/internal/lsp/testdata/missingdep/summary.txt.golden
index 4be7cf6..354b1db 100644
--- a/internal/lsp/testdata/missingdep/summary.txt.golden
+++ b/internal/lsp/testdata/missingdep/summary.txt.golden
@@ -1,4 +1,5 @@
-- summary --
+CallHierarchyCount = 0
CodeLensCount = 0
CompletionsCount = 0
CompletionSnippetCount = 0
diff --git a/internal/lsp/testdata/missingtwodep/summary.txt.golden b/internal/lsp/testdata/missingtwodep/summary.txt.golden
index ce246c0..5cf929d 100644
--- a/internal/lsp/testdata/missingtwodep/summary.txt.golden
+++ b/internal/lsp/testdata/missingtwodep/summary.txt.golden
@@ -1,4 +1,5 @@
-- summary --
+CallHierarchyCount = 0
CodeLensCount = 0
CompletionsCount = 0
CompletionSnippetCount = 0
diff --git a/internal/lsp/testdata/unused/summary.txt.golden b/internal/lsp/testdata/unused/summary.txt.golden
index 3f09a08..d77a6c5 100644
--- a/internal/lsp/testdata/unused/summary.txt.golden
+++ b/internal/lsp/testdata/unused/summary.txt.golden
@@ -1,4 +1,5 @@
-- summary --
+CallHierarchyCount = 0
CodeLensCount = 0
CompletionsCount = 0
CompletionSnippetCount = 0
diff --git a/internal/lsp/testdata/upgradedep/summary.txt.golden b/internal/lsp/testdata/upgradedep/summary.txt.golden
index 2719246..db65585 100644
--- a/internal/lsp/testdata/upgradedep/summary.txt.golden
+++ b/internal/lsp/testdata/upgradedep/summary.txt.golden
@@ -1,4 +1,5 @@
-- summary --
+CallHierarchyCount = 0
CodeLensCount = 2
CompletionsCount = 0
CompletionSnippetCount = 0
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index c3178c3..bfdecd2 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -43,6 +43,7 @@
var UpdateGolden = flag.Bool("golden", false, "Update golden files")
+type CallHierarchy map[span.Span]*CallHierarchyResult
type CodeLens map[span.URI][]protocol.CodeLens
type Diagnostics map[span.URI][]*source.Diagnostic
type CompletionItems map[token.Pos]*source.CompletionItem
@@ -74,6 +75,7 @@
type Data struct {
Config packages.Config
Exported *packagestest.Exported
+ CallHierarchy CallHierarchy
CodeLens CodeLens
Diagnostics Diagnostics
CompletionItems CompletionItems
@@ -117,6 +119,7 @@
}
type Tests interface {
+ CallHierarchy(*testing.T, span.Span, *CallHierarchyResult)
CodeLens(*testing.T, span.URI, []protocol.CodeLens)
Diagnostics(*testing.T, span.URI, []*source.Diagnostic)
Completion(*testing.T, span.Span, Completion, CompletionItems)
@@ -197,6 +200,10 @@
PlaceholderSnippet string
}
+type CallHierarchyResult struct {
+ IncomingCalls, OutgoingCalls []span.Span
+}
+
type Link struct {
Src span.Span
Target string
@@ -274,6 +281,7 @@
var data []*Data
for _, folder := range folders {
datum := &Data{
+ CallHierarchy: make(CallHierarchy),
CodeLens: make(CodeLens),
Diagnostics: make(Diagnostics),
CompletionItems: make(CompletionItems),
@@ -425,6 +433,8 @@
"link": datum.collectLinks,
"suggestedfix": datum.collectSuggestedFixes,
"extractfunc": datum.collectFunctionExtractions,
+ "incomingcalls": datum.collectIncomingCalls,
+ "outgoingcalls": datum.collectOutgoingCalls,
}); err != nil {
t.Fatal(err)
}
@@ -495,6 +505,16 @@
}
}
+ t.Run("CallHierarchy", func(t *testing.T) {
+ t.Helper()
+ for spn, callHierarchyResult := range data.CallHierarchy {
+ t.Run(SpanName(spn), func(t *testing.T) {
+ t.Helper()
+ tests.CallHierarchy(t, spn, callHierarchyResult)
+ })
+ }
+ })
+
t.Run("Completion", func(t *testing.T) {
t.Helper()
eachCompletion(t, data.Completions, tests.Completion)
@@ -807,6 +827,7 @@
return count
}
+ fmt.Fprintf(buf, "CallHierarchyCount = %v\n", len(data.CallHierarchy))
fmt.Fprintf(buf, "CodeLensCount = %v\n", countCodeLens(data.CodeLens))
fmt.Fprintf(buf, "CompletionsCount = %v\n", countCompletions(data.Completions))
fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", snippetCount)
@@ -1060,6 +1081,26 @@
data.Implementations[src] = targets
}
+func (data *Data) collectIncomingCalls(src span.Span, calls []span.Span) {
+ if data.CallHierarchy[src] != nil {
+ data.CallHierarchy[src].IncomingCalls = calls
+ } else {
+ data.CallHierarchy[src] = &CallHierarchyResult{
+ IncomingCalls: calls,
+ }
+ }
+}
+
+func (data *Data) collectOutgoingCalls(src span.Span, calls []span.Span) {
+ if data.CallHierarchy[src] != nil {
+ data.CallHierarchy[src].OutgoingCalls = calls
+ } else {
+ data.CallHierarchy[src] = &CallHierarchyResult{
+ OutgoingCalls: calls,
+ }
+ }
+}
+
func (data *Data) collectHoverDefinitions(src, target span.Span) {
data.Definitions[src] = Definition{
Src: src,