diff --git a/gopls/integration/parse/protocol.go b/gopls/integration/parse/protocol.go index d812a54393..d1e90edfda 100644 --- a/gopls/integration/parse/protocol.go +++ b/gopls/integration/parse/protocol.go @@ -87,6 +87,10 @@ func Requests(m string) interface{} { 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 @@ func Responses(m string) []interface{} { 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 @@ var fromMethod = map[string]Msgtype{ "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 0000000000..f9307d7016 --- /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 0000000000..b7b5fee84d --- /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 "" } +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 1b142ec60c..92a817a5e3 100644 --- a/internal/lsp/cmd/cmd.go +++ b/internal/lsp/cmd/cmd.go @@ -175,6 +175,7 @@ func (app *Application) mainCommands() []tool.Application { 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 0000000000..6ec689dfc3 --- /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 caab6d2631..ed9a74eddd 100644 --- a/internal/lsp/general.go +++ b/internal/lsp/general.go @@ -85,7 +85,8 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitializ 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 3e16515666..579f51da4d 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -99,6 +99,50 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) { } } +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 8604bb9ce6..6f9eeb8596 100644 --- a/internal/lsp/server_gen.go +++ b/internal/lsp/server_gen.go @@ -100,8 +100,8 @@ func (s *Server) Implementation(ctx context.Context, params *protocol.Implementa 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 @@ func (s *Server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeForma 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 0000000000..6ec7a714a7 --- /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 5db5d64805..c8e4d99c18 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -96,6 +96,47 @@ func testSource(t *testing.T, exporter packagestest.Exporter) { } } +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 0000000000..b7c774bc3a --- /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 91b872a5ae..1b3e05098c 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 4be7cf6ea2..354b1db446 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 ce246c0340..5cf929d94d 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 3f09a08ae2..d77a6c51cd 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 2719246aac..db65585d3a 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 c3178c3759..bfdecd299a 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -43,6 +43,7 @@ const ( 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 Links map[span.URI][]Link type Data struct { Config packages.Config Exported *packagestest.Exported + CallHierarchy CallHierarchy CodeLens CodeLens Diagnostics Diagnostics CompletionItems CompletionItems @@ -117,6 +119,7 @@ type Data struct { } 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 @@ type CompletionSnippet struct { PlaceholderSnippet string } +type CallHierarchyResult struct { + IncomingCalls, OutgoingCalls []span.Span +} + type Link struct { Src span.Span Target string @@ -274,6 +281,7 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) []*Data { 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 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) []*Data { "link": datum.collectLinks, "suggestedfix": datum.collectSuggestedFixes, "extractfunc": datum.collectFunctionExtractions, + "incomingcalls": datum.collectIncomingCalls, + "outgoingcalls": datum.collectOutgoingCalls, }); err != nil { t.Fatal(err) } @@ -495,6 +505,16 @@ func Run(t *testing.T, tests Tests, data *Data) { } } + 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 @@ func checkData(t *testing.T, data *Data) { 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 @@ func (data *Data) collectImplementations(src span.Span, targets []span.Span) { 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,