mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
internal/lsp: reorganize completion tests
Our completion tests check for a lot of different behaviors. It may be easier to develop if we have separate tests for things like deep completion and completion snippets. Change-Id: I7f4b0c0e52670f2a6c00247199933fd1ffa0096f Reviewed-on: https://go-review.googlesource.com/c/tools/+/196021 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
3ac2a5bbd9
commit
c006dc79eb
@ -33,7 +33,27 @@ func NewRunner(exporter packagestest.Exporter, data *tests.Data, ctx context.Con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
|
func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.CompletionItems) {
|
||||||
|
//TODO: add command line completions tests when it works
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) CompletionSnippets(t *testing.T, data tests.CompletionSnippets, items tests.CompletionItems) {
|
||||||
|
//TODO: add command line completions tests when it works
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) UnimportedCompletions(t *testing.T, data tests.UnimportedCompletions, items tests.CompletionItems) {
|
||||||
|
//TODO: add command line completions tests when it works
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) DeepCompletions(t *testing.T, data tests.DeepCompletions, items tests.CompletionItems) {
|
||||||
|
//TODO: add command line completions tests when it works
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, items tests.CompletionItems) {
|
||||||
|
//TODO: add command line completions tests when it works
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) {
|
||||||
//TODO: add command line completions tests when it works
|
//TODO: add command line completions tests when it works
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,11 +47,11 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara
|
|||||||
// When using deep completions/fuzzy matching, report results as incomplete so
|
// When using deep completions/fuzzy matching, report results as incomplete so
|
||||||
// client fetches updated completions after every key stroke.
|
// client fetches updated completions after every key stroke.
|
||||||
IsIncomplete: options.Completion.Deep,
|
IsIncomplete: options.Completion.Deep,
|
||||||
Items: s.toProtocolCompletionItems(candidates, rng, options),
|
Items: toProtocolCompletionItems(candidates, rng, options),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
|
func toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
|
||||||
var (
|
var (
|
||||||
items = make([]protocol.CompletionItem, 0, len(candidates))
|
items = make([]protocol.CompletionItem, 0, len(candidates))
|
||||||
numDeepCompletionsSeen int
|
numDeepCompletionsSeen int
|
||||||
|
151
internal/lsp/completion_test.go
Normal file
151
internal/lsp/completion_test.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package lsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
|
"golang.org/x/tools/internal/lsp/tests"
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.CompletionItems) {
|
||||||
|
for src, test := range data {
|
||||||
|
got := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
|
Deep: false,
|
||||||
|
FuzzyMatching: false,
|
||||||
|
Documentation: true,
|
||||||
|
})
|
||||||
|
if !strings.Contains(string(src.URI()), "builtins") {
|
||||||
|
got = tests.FilterBuiltins(got)
|
||||||
|
}
|
||||||
|
want := expected(t, test, items)
|
||||||
|
if diff := tests.DiffCompletionItems(want, got); diff != "" {
|
||||||
|
t.Errorf("%s: %s", src, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) CompletionSnippets(t *testing.T, data tests.CompletionSnippets, items tests.CompletionItems) {
|
||||||
|
for _, placeholders := range []bool{true, false} {
|
||||||
|
for src, expected := range data {
|
||||||
|
list := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
|
Placeholders: placeholders,
|
||||||
|
Deep: true,
|
||||||
|
Budget: 5 * time.Second,
|
||||||
|
FuzzyMatching: true,
|
||||||
|
})
|
||||||
|
got := tests.FindItem(list, *items[expected.CompletionItem])
|
||||||
|
want := expected.PlainSnippet
|
||||||
|
if placeholders {
|
||||||
|
want = expected.PlaceholderSnippet
|
||||||
|
}
|
||||||
|
if diff := tests.DiffSnippets(want, got); diff != "" {
|
||||||
|
t.Errorf("%s: %v", src, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) UnimportedCompletions(t *testing.T, data tests.UnimportedCompletions, items tests.CompletionItems) {
|
||||||
|
for src, test := range data {
|
||||||
|
got := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
|
Unimported: true,
|
||||||
|
})
|
||||||
|
if !strings.Contains(string(src.URI()), "builtins") {
|
||||||
|
got = tests.FilterBuiltins(got)
|
||||||
|
}
|
||||||
|
want := expected(t, test, items)
|
||||||
|
if diff := tests.DiffCompletionItems(want, got); diff != "" {
|
||||||
|
t.Errorf("%s: %s", src, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) DeepCompletions(t *testing.T, data tests.DeepCompletions, items tests.CompletionItems) {
|
||||||
|
for src, test := range data {
|
||||||
|
got := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
|
Deep: true,
|
||||||
|
Budget: 5 * time.Second,
|
||||||
|
Documentation: true,
|
||||||
|
})
|
||||||
|
if !strings.Contains(string(src.URI()), "builtins") {
|
||||||
|
got = tests.FilterBuiltins(got)
|
||||||
|
}
|
||||||
|
want := expected(t, test, items)
|
||||||
|
if msg := tests.DiffCompletionItems(want, got); msg != "" {
|
||||||
|
t.Errorf("%s: %s", src, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, items tests.CompletionItems) {
|
||||||
|
for src, test := range data {
|
||||||
|
got := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
|
FuzzyMatching: true,
|
||||||
|
Deep: true,
|
||||||
|
Budget: 5 * time.Second,
|
||||||
|
})
|
||||||
|
if !strings.Contains(string(src.URI()), "builtins") {
|
||||||
|
got = tests.FilterBuiltins(got)
|
||||||
|
}
|
||||||
|
want := expected(t, test, items)
|
||||||
|
if msg := tests.DiffCompletionItems(want, got); msg != "" {
|
||||||
|
t.Errorf("%s: %s", src, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) {
|
||||||
|
for src, test := range data {
|
||||||
|
got := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
|
FuzzyMatching: true,
|
||||||
|
Deep: true,
|
||||||
|
Budget: 5 * time.Second,
|
||||||
|
})
|
||||||
|
want := expected(t, test, items)
|
||||||
|
if msg := tests.CheckCompletionOrder(want, got); msg != "" {
|
||||||
|
t.Errorf("%s: %s", src, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expected(t *testing.T, test tests.Completion, items tests.CompletionItems) []protocol.CompletionItem {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var want []protocol.CompletionItem
|
||||||
|
for _, pos := range test.CompletionItems {
|
||||||
|
item := items[pos]
|
||||||
|
want = append(want, tests.ToProtocolCompletionItem(*item))
|
||||||
|
}
|
||||||
|
return want
|
||||||
|
}
|
||||||
|
func (r *runner) callCompletion(t *testing.T, src span.Span, options source.CompletionOptions) []protocol.CompletionItem {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
view := r.server.session.ViewOf(src.URI())
|
||||||
|
original := view.Options()
|
||||||
|
modified := original
|
||||||
|
modified.InsertTextFormat = protocol.SnippetTextFormat
|
||||||
|
modified.Completion = options
|
||||||
|
view.SetOptions(modified)
|
||||||
|
defer view.SetOptions(original)
|
||||||
|
|
||||||
|
list, err := r.server.Completion(r.ctx, &protocol.CompletionParams{
|
||||||
|
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
||||||
|
TextDocument: protocol.TextDocumentIdentifier{
|
||||||
|
URI: protocol.NewURI(src.URI()),
|
||||||
|
},
|
||||||
|
Position: protocol.Position{
|
||||||
|
Line: float64(src.Start().Line() - 1),
|
||||||
|
Character: float64(src.Start().Column() - 1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return list.Items
|
||||||
|
}
|
@ -15,7 +15,6 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages/packagestest"
|
"golang.org/x/tools/go/packages/packagestest"
|
||||||
"golang.org/x/tools/internal/lsp/cache"
|
"golang.org/x/tools/internal/lsp/cache"
|
||||||
@ -51,18 +50,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
|
|||||||
|
|
||||||
cache := cache.New()
|
cache := cache.New()
|
||||||
session := cache.NewSession(ctx)
|
session := cache.NewSession(ctx)
|
||||||
options := session.Options()
|
options := tests.DefaultOptions()
|
||||||
options.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
|
|
||||||
source.Go: {
|
|
||||||
protocol.SourceOrganizeImports: true,
|
|
||||||
protocol.QuickFix: true,
|
|
||||||
},
|
|
||||||
source.Mod: {},
|
|
||||||
source.Sum: {},
|
|
||||||
}
|
|
||||||
options.HoverKind = source.SynopsisDocumentation
|
|
||||||
// Crank this up so tests don't flake.
|
|
||||||
options.Completion.Budget = 5 * time.Second
|
|
||||||
session.SetOptions(options)
|
session.SetOptions(options)
|
||||||
options.Env = data.Config.Env
|
options.Env = data.Config.Env
|
||||||
session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options)
|
session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options)
|
||||||
@ -111,218 +99,6 @@ func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
|
|
||||||
for src, test := range data {
|
|
||||||
view := r.server.session.ViewOf(src.URI())
|
|
||||||
original := view.Options()
|
|
||||||
modified := original
|
|
||||||
|
|
||||||
// Set this as a default.
|
|
||||||
modified.Completion.Documentation = true
|
|
||||||
|
|
||||||
var want []source.CompletionItem
|
|
||||||
for _, pos := range test.CompletionItems {
|
|
||||||
want = append(want, *items[pos])
|
|
||||||
}
|
|
||||||
|
|
||||||
modified.Completion.Deep = strings.Contains(string(src.URI()), "deepcomplete")
|
|
||||||
modified.Completion.FuzzyMatching = strings.Contains(string(src.URI()), "fuzzymatch")
|
|
||||||
modified.Completion.Unimported = strings.Contains(string(src.URI()), "unimported")
|
|
||||||
view.SetOptions(modified)
|
|
||||||
|
|
||||||
list := r.runCompletion(t, src)
|
|
||||||
|
|
||||||
wantBuiltins := strings.Contains(string(src.URI()), "builtins")
|
|
||||||
var got []protocol.CompletionItem
|
|
||||||
for _, item := range list.Items {
|
|
||||||
if !wantBuiltins && isBuiltin(item) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
got = append(got, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch test.Type {
|
|
||||||
case tests.CompletionFull:
|
|
||||||
if diff := diffCompletionItems(want, got); diff != "" {
|
|
||||||
t.Errorf("%s: %s", src, diff)
|
|
||||||
}
|
|
||||||
case tests.CompletionPartial:
|
|
||||||
if msg := checkCompletionOrder(want, got); msg != "" {
|
|
||||||
t.Errorf("%s: %s", src, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
view.SetOptions(original)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, usePlaceholders := range []bool{true, false} {
|
|
||||||
|
|
||||||
for src, want := range snippets {
|
|
||||||
view := r.server.session.ViewOf(src.URI())
|
|
||||||
original := view.Options()
|
|
||||||
modified := original
|
|
||||||
|
|
||||||
modified.InsertTextFormat = protocol.SnippetTextFormat
|
|
||||||
modified.Completion.Deep = strings.Contains(string(src.URI()), "deepcomplete")
|
|
||||||
modified.Completion.FuzzyMatching = strings.Contains(string(src.URI()), "fuzzymatch")
|
|
||||||
modified.Completion.Unimported = strings.Contains(string(src.URI()), "unimported")
|
|
||||||
modified.Completion.Placeholders = usePlaceholders
|
|
||||||
view.SetOptions(modified)
|
|
||||||
|
|
||||||
list := r.runCompletion(t, src)
|
|
||||||
|
|
||||||
wantItem := items[want.CompletionItem]
|
|
||||||
var got *protocol.CompletionItem
|
|
||||||
for _, item := range list.Items {
|
|
||||||
if item.Label == wantItem.Label {
|
|
||||||
got = &item
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var expected string
|
|
||||||
if usePlaceholders {
|
|
||||||
expected = want.PlaceholderSnippet
|
|
||||||
} else {
|
|
||||||
expected = want.PlainSnippet
|
|
||||||
}
|
|
||||||
|
|
||||||
if expected == "" {
|
|
||||||
if got != nil {
|
|
||||||
t.Fatalf("%s:%d: expected no snippet but got %q", src.URI(), src.Start().Line(), got.TextEdit.NewText)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if got == nil {
|
|
||||||
t.Fatalf("%s:%d: couldn't find completion matching %q", src.URI(), src.Start().Line(), wantItem.Label)
|
|
||||||
}
|
|
||||||
|
|
||||||
if expected != got.TextEdit.NewText {
|
|
||||||
t.Errorf("%s: expected snippet %q, got %q", src, expected, got.TextEdit.NewText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
view.SetOptions(original)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *runner) runCompletion(t *testing.T, src span.Span) *protocol.CompletionList {
|
|
||||||
t.Helper()
|
|
||||||
list, err := r.server.Completion(r.ctx, &protocol.CompletionParams{
|
|
||||||
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
|
||||||
TextDocument: protocol.TextDocumentIdentifier{
|
|
||||||
URI: protocol.NewURI(src.URI()),
|
|
||||||
},
|
|
||||||
Position: protocol.Position{
|
|
||||||
Line: float64(src.Start().Line() - 1),
|
|
||||||
Character: float64(src.Start().Column() - 1),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBuiltin(item protocol.CompletionItem) bool {
|
|
||||||
// If a type has no detail, it is a builtin type.
|
|
||||||
if item.Detail == "" && item.Kind == protocol.TypeParameterCompletion {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Remaining builtin constants, variables, interfaces, and functions.
|
|
||||||
trimmed := item.Label
|
|
||||||
if i := strings.Index(trimmed, "("); i >= 0 {
|
|
||||||
trimmed = trimmed[:i]
|
|
||||||
}
|
|
||||||
switch trimmed {
|
|
||||||
case "append", "cap", "close", "complex", "copy", "delete",
|
|
||||||
"error", "false", "imag", "iota", "len", "make", "new",
|
|
||||||
"nil", "panic", "print", "println", "real", "recover", "true":
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// diffCompletionItems prints the diff between expected and actual completion
|
|
||||||
// test results.
|
|
||||||
func diffCompletionItems(want []source.CompletionItem, got []protocol.CompletionItem) string {
|
|
||||||
if len(got) != len(want) {
|
|
||||||
return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want))
|
|
||||||
}
|
|
||||||
for i, w := range want {
|
|
||||||
g := got[i]
|
|
||||||
if w.Label != g.Label {
|
|
||||||
return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label)
|
|
||||||
}
|
|
||||||
if w.Detail != g.Detail {
|
|
||||||
return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail)
|
|
||||||
}
|
|
||||||
if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") {
|
|
||||||
if w.Documentation != g.Documentation {
|
|
||||||
return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if wkind := toProtocolCompletionItemKind(w.Kind); wkind != g.Kind {
|
|
||||||
return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, wkind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCompletionOrder(want []source.CompletionItem, got []protocol.CompletionItem) string {
|
|
||||||
var (
|
|
||||||
matchedIdxs []int
|
|
||||||
lastGotIdx int
|
|
||||||
inOrder = true
|
|
||||||
)
|
|
||||||
for _, w := range want {
|
|
||||||
var found bool
|
|
||||||
for i, g := range got {
|
|
||||||
if w.Label == g.Label && w.Detail == g.Detail && toProtocolCompletionItemKind(w.Kind) == g.Kind {
|
|
||||||
matchedIdxs = append(matchedIdxs, i)
|
|
||||||
found = true
|
|
||||||
if i < lastGotIdx {
|
|
||||||
inOrder = false
|
|
||||||
}
|
|
||||||
lastGotIdx = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return summarizeCompletionItems(-1, []source.CompletionItem{w}, got, "didn't find expected completion")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Ints(matchedIdxs)
|
|
||||||
matched := make([]protocol.CompletionItem, 0, len(matchedIdxs))
|
|
||||||
for _, idx := range matchedIdxs {
|
|
||||||
matched = append(matched, got[idx])
|
|
||||||
}
|
|
||||||
|
|
||||||
if !inOrder {
|
|
||||||
return summarizeCompletionItems(-1, want, matched, "completions out of order")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func summarizeCompletionItems(i int, want []source.CompletionItem, got []protocol.CompletionItem, reason string, args ...interface{}) string {
|
|
||||||
msg := &bytes.Buffer{}
|
|
||||||
fmt.Fprint(msg, "completion failed")
|
|
||||||
if i >= 0 {
|
|
||||||
fmt.Fprintf(msg, " at %d", i)
|
|
||||||
}
|
|
||||||
fmt.Fprint(msg, " because of ")
|
|
||||||
fmt.Fprintf(msg, reason, args...)
|
|
||||||
fmt.Fprint(msg, ":\nexpected:\n")
|
|
||||||
for _, d := range want {
|
|
||||||
fmt.Fprintf(msg, " %v\n", d)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(msg, "got:\n")
|
|
||||||
for _, d := range got {
|
|
||||||
fmt.Fprintf(msg, " %v\n", d)
|
|
||||||
}
|
|
||||||
return msg.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
|
func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
|
||||||
for _, spn := range data {
|
for _, spn := range data {
|
||||||
uri := spn.URI()
|
uri := spn.URI()
|
||||||
@ -909,7 +685,7 @@ func (r *runner) diffSymbols(t *testing.T, uri span.URI, want []protocol.Documen
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func summarizeSymbols(t *testing.T, i int, want []protocol.DocumentSymbol, got []protocol.DocumentSymbol, reason string, args ...interface{}) string {
|
func summarizeSymbols(t *testing.T, i int, want, got []protocol.DocumentSymbol, reason string, args ...interface{}) string {
|
||||||
msg := &bytes.Buffer{}
|
msg := &bytes.Buffer{}
|
||||||
fmt.Fprint(msg, "document symbols failed")
|
fmt.Fprint(msg, "document symbols failed")
|
||||||
if i >= 0 {
|
if i >= 0 {
|
||||||
|
@ -49,7 +49,7 @@ func testSource(t *testing.T, exporter packagestest.Exporter) {
|
|||||||
|
|
||||||
cache := cache.New()
|
cache := cache.New()
|
||||||
session := cache.NewSession(ctx)
|
session := cache.NewSession(ctx)
|
||||||
options := session.Options()
|
options := tests.DefaultOptions()
|
||||||
options.Env = data.Config.Env
|
options.Env = data.Config.Env
|
||||||
r := &runner{
|
r := &runner{
|
||||||
view: session.NewView(ctx, "source_test", span.FileURI(data.Config.Dir), options),
|
view: session.NewView(ctx, "source_test", span.FileURI(data.Config.Dir), options),
|
||||||
@ -86,245 +86,194 @@ func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
|
func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.CompletionItems) {
|
||||||
ctx := r.ctx
|
|
||||||
for src, test := range data {
|
for src, test := range data {
|
||||||
var want []source.CompletionItem
|
var want []protocol.CompletionItem
|
||||||
for _, pos := range test.CompletionItems {
|
for _, pos := range test.CompletionItems {
|
||||||
want = append(want, *items[pos])
|
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
|
||||||
}
|
}
|
||||||
f, err := r.view.GetFile(ctx, src.URI())
|
prefix, list := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed for %v: %v", src, err)
|
|
||||||
}
|
|
||||||
deepComplete := strings.Contains(string(src.URI()), "deepcomplete")
|
|
||||||
fuzzyMatch := strings.Contains(string(src.URI()), "fuzzymatch")
|
|
||||||
unimported := strings.Contains(string(src.URI()), "unimported")
|
|
||||||
list, surrounding, err := source.Completion(ctx, r.view, f.(source.GoFile), protocol.Position{
|
|
||||||
Line: float64(src.Start().Line() - 1),
|
|
||||||
Character: float64(src.Start().Column() - 1),
|
|
||||||
}, source.CompletionOptions{
|
|
||||||
Documentation: true,
|
Documentation: true,
|
||||||
Deep: deepComplete,
|
FuzzyMatching: true,
|
||||||
FuzzyMatching: fuzzyMatch,
|
|
||||||
Unimported: unimported,
|
|
||||||
// Crank this up so tests don't flake.
|
|
||||||
Budget: 5 * time.Second,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if !strings.Contains(string(src.URI()), "builtins") {
|
||||||
t.Fatalf("failed for %v: %v", src, err)
|
list = tests.FilterBuiltins(list)
|
||||||
}
|
}
|
||||||
var (
|
var got []protocol.CompletionItem
|
||||||
prefix string
|
|
||||||
fuzzyMatcher *fuzzy.Matcher
|
|
||||||
)
|
|
||||||
if surrounding != nil {
|
|
||||||
prefix = strings.ToLower(surrounding.Prefix())
|
|
||||||
if deepComplete && prefix != "" {
|
|
||||||
fuzzyMatcher = fuzzy.NewMatcher(surrounding.Prefix(), fuzzy.Symbol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wantBuiltins := strings.Contains(string(src.URI()), "builtins")
|
|
||||||
var got []source.CompletionItem
|
|
||||||
for _, item := range list {
|
for _, item := range list {
|
||||||
if !wantBuiltins && isBuiltin(item) {
|
if !strings.HasPrefix(strings.ToLower(item.Label), prefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If deep completion is enabled, we need to use the fuzzy matcher to match
|
|
||||||
// the code's behavior.
|
|
||||||
if deepComplete {
|
|
||||||
if fuzzyMatcher != nil && fuzzyMatcher.Score(item.Label) < 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We let the client do fuzzy matching, so we return all possible candidates.
|
|
||||||
// To simplify testing, filter results with prefixes that don't match exactly.
|
|
||||||
if !strings.HasPrefix(strings.ToLower(item.Label), prefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
got = append(got, item)
|
got = append(got, item)
|
||||||
}
|
}
|
||||||
switch test.Type {
|
if diff := tests.DiffCompletionItems(want, got); diff != "" {
|
||||||
case tests.CompletionFull:
|
t.Errorf("%s: %s", src, diff)
|
||||||
if diff := diffCompletionItems(want, got); diff != "" {
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) CompletionSnippets(t *testing.T, data tests.CompletionSnippets, items tests.CompletionItems) {
|
||||||
|
for _, placeholders := range []bool{true, false} {
|
||||||
|
for src, expected := range data {
|
||||||
|
_, list := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
|
Placeholders: placeholders,
|
||||||
|
Deep: true,
|
||||||
|
Budget: 5 * time.Second,
|
||||||
|
})
|
||||||
|
got := tests.FindItem(list, *items[expected.CompletionItem])
|
||||||
|
want := expected.PlainSnippet
|
||||||
|
if placeholders {
|
||||||
|
want = expected.PlaceholderSnippet
|
||||||
|
}
|
||||||
|
if diff := tests.DiffSnippets(want, got); diff != "" {
|
||||||
t.Errorf("%s: %s", src, diff)
|
t.Errorf("%s: %s", src, diff)
|
||||||
}
|
}
|
||||||
case tests.CompletionPartial:
|
|
||||||
if msg := checkCompletionOrder(want, got); msg != "" {
|
|
||||||
t.Errorf("%s: %s", src, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, usePlaceholders := range []bool{true, false} {
|
|
||||||
for src, want := range snippets {
|
|
||||||
f, err := r.view.GetFile(ctx, src.URI())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed for %v: %v", src, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
list, _, err := source.Completion(ctx, r.view, f.(source.GoFile), protocol.Position{
|
|
||||||
Line: float64(src.Start().Line() - 1),
|
|
||||||
Character: float64(src.Start().Column() - 1),
|
|
||||||
}, source.CompletionOptions{
|
|
||||||
Documentation: true,
|
|
||||||
Deep: strings.Contains(string(src.URI()), "deepcomplete"),
|
|
||||||
FuzzyMatching: strings.Contains(string(src.URI()), "fuzzymatch"),
|
|
||||||
Placeholders: usePlaceholders,
|
|
||||||
// Crank this up so tests don't flake.
|
|
||||||
Budget: 5 * time.Second,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed for %v: %v", src, err)
|
|
||||||
}
|
|
||||||
wantItem := items[want.CompletionItem]
|
|
||||||
var got *source.CompletionItem
|
|
||||||
for _, item := range list {
|
|
||||||
if item.Label == wantItem.Label {
|
|
||||||
got = &item
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expected := want.PlainSnippet
|
|
||||||
if usePlaceholders {
|
|
||||||
expected = want.PlaceholderSnippet
|
|
||||||
}
|
|
||||||
if expected == "" {
|
|
||||||
if got != nil {
|
|
||||||
t.Fatalf("%s:%d: expected no matching snippet", src.URI(), src.Start().Line())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if got == nil {
|
|
||||||
t.Fatalf("%s:%d: couldn't find completion matching %q", src.URI(), src.Start().Line(), wantItem.Label)
|
|
||||||
}
|
|
||||||
actual := got.Snippet()
|
|
||||||
if expected != actual {
|
|
||||||
t.Errorf("%s: expected placeholder snippet %q, got %q", src, expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBuiltin(item source.CompletionItem) bool {
|
func (r *runner) UnimportedCompletions(t *testing.T, data tests.UnimportedCompletions, items tests.CompletionItems) {
|
||||||
// If a type has no detail, it is a builtin type.
|
for src, test := range data {
|
||||||
if item.Detail == "" && item.Kind == source.TypeCompletionItem {
|
var want []protocol.CompletionItem
|
||||||
return true
|
for _, pos := range test.CompletionItems {
|
||||||
|
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
|
||||||
|
}
|
||||||
|
_, got := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
|
Unimported: true,
|
||||||
|
})
|
||||||
|
if !strings.Contains(string(src.URI()), "builtins") {
|
||||||
|
got = tests.FilterBuiltins(got)
|
||||||
|
}
|
||||||
|
if diff := tests.DiffCompletionItems(want, got); diff != "" {
|
||||||
|
t.Errorf("%s: %s", src, diff)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Remaining builtin constants, variables, interfaces, and functions.
|
|
||||||
trimmed := item.Label
|
|
||||||
if i := strings.Index(trimmed, "("); i >= 0 {
|
|
||||||
trimmed = trimmed[:i]
|
|
||||||
}
|
|
||||||
switch trimmed {
|
|
||||||
case "append", "cap", "close", "complex", "copy", "delete",
|
|
||||||
"error", "false", "imag", "iota", "len", "make", "new",
|
|
||||||
"nil", "panic", "print", "println", "real", "recover", "true":
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// diffCompletionItems prints the diff between expected and actual completion
|
func (r *runner) DeepCompletions(t *testing.T, data tests.DeepCompletions, items tests.CompletionItems) {
|
||||||
// test results.
|
for src, test := range data {
|
||||||
func diffCompletionItems(want []source.CompletionItem, got []source.CompletionItem) string {
|
var want []protocol.CompletionItem
|
||||||
sort.SliceStable(got, func(i, j int) bool {
|
for _, pos := range test.CompletionItems {
|
||||||
return got[i].Score > got[j].Score
|
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
|
||||||
})
|
}
|
||||||
|
prefix, list := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
// duplicate the lsp/completion logic to limit deep candidates to keep expected
|
Deep: true,
|
||||||
// list short
|
Budget: 5 * time.Second,
|
||||||
var idx, seenDeepCompletions int
|
Documentation: true,
|
||||||
for _, item := range got {
|
})
|
||||||
if item.Depth > 0 {
|
if !strings.Contains(string(src.URI()), "builtins") {
|
||||||
if seenDeepCompletions >= 3 {
|
list = tests.FilterBuiltins(list)
|
||||||
|
}
|
||||||
|
fuzzyMatcher := fuzzy.NewMatcher(prefix, fuzzy.Symbol)
|
||||||
|
var got []protocol.CompletionItem
|
||||||
|
for _, item := range list {
|
||||||
|
if fuzzyMatcher.Score(item.Label) < 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenDeepCompletions++
|
got = append(got, item)
|
||||||
}
|
}
|
||||||
got[idx] = item
|
if msg := tests.DiffCompletionItems(want, got); msg != "" {
|
||||||
idx++
|
t.Errorf("%s: %s", src, msg)
|
||||||
}
|
|
||||||
got = got[:idx]
|
|
||||||
|
|
||||||
if len(got) != len(want) {
|
|
||||||
return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want))
|
|
||||||
}
|
|
||||||
for i, w := range want {
|
|
||||||
g := got[i]
|
|
||||||
if w.Label != g.Label {
|
|
||||||
return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label)
|
|
||||||
}
|
|
||||||
if w.Detail != g.Detail {
|
|
||||||
return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail)
|
|
||||||
}
|
|
||||||
if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") {
|
|
||||||
if w.Documentation != g.Documentation {
|
|
||||||
return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if w.Kind != g.Kind {
|
|
||||||
return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, w.Kind)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkCompletionOrder(want []source.CompletionItem, got []source.CompletionItem) string {
|
func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, items tests.CompletionItems) {
|
||||||
var (
|
for src, test := range data {
|
||||||
matchedIdxs []int
|
var want []protocol.CompletionItem
|
||||||
lastGotIdx int
|
for _, pos := range test.CompletionItems {
|
||||||
inOrder = true
|
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
|
||||||
)
|
}
|
||||||
for _, w := range want {
|
prefix, list := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
var found bool
|
FuzzyMatching: true,
|
||||||
for i, g := range got {
|
Deep: true,
|
||||||
if w.Label == g.Label && w.Detail == g.Detail && w.Kind == g.Kind {
|
Budget: 5 * time.Second,
|
||||||
matchedIdxs = append(matchedIdxs, i)
|
})
|
||||||
found = true
|
if !strings.Contains(string(src.URI()), "builtins") {
|
||||||
if i < lastGotIdx {
|
list = tests.FilterBuiltins(list)
|
||||||
inOrder = false
|
}
|
||||||
}
|
var fuzzyMatcher *fuzzy.Matcher
|
||||||
lastGotIdx = i
|
if prefix != "" {
|
||||||
break
|
fuzzyMatcher = fuzzy.NewMatcher(prefix, fuzzy.Symbol)
|
||||||
|
}
|
||||||
|
var got []protocol.CompletionItem
|
||||||
|
for _, item := range list {
|
||||||
|
if fuzzyMatcher != nil && fuzzyMatcher.Score(item.Label) < 0 {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
got = append(got, item)
|
||||||
}
|
}
|
||||||
if !found {
|
if msg := tests.DiffCompletionItems(want, got); msg != "" {
|
||||||
return summarizeCompletionItems(-1, []source.CompletionItem{w}, got, "didn't find expected completion")
|
t.Errorf("%s: %s", src, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Ints(matchedIdxs)
|
|
||||||
matched := make([]source.CompletionItem, 0, len(matchedIdxs))
|
|
||||||
for _, idx := range matchedIdxs {
|
|
||||||
matched = append(matched, got[idx])
|
|
||||||
}
|
|
||||||
|
|
||||||
if !inOrder {
|
|
||||||
return summarizeCompletionItems(-1, want, matched, "completions out of order")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func summarizeCompletionItems(i int, want []source.CompletionItem, got []source.CompletionItem, reason string, args ...interface{}) string {
|
func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) {
|
||||||
msg := &bytes.Buffer{}
|
for src, test := range data {
|
||||||
fmt.Fprint(msg, "completion failed")
|
var want []protocol.CompletionItem
|
||||||
if i >= 0 {
|
for _, pos := range test.CompletionItems {
|
||||||
fmt.Fprintf(msg, " at %d", i)
|
want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
|
||||||
|
}
|
||||||
|
prefix, list := r.callCompletion(t, src, source.CompletionOptions{
|
||||||
|
FuzzyMatching: true,
|
||||||
|
Deep: true,
|
||||||
|
Budget: 5 * time.Second,
|
||||||
|
})
|
||||||
|
if !strings.Contains(string(src.URI()), "builtins") {
|
||||||
|
list = tests.FilterBuiltins(list)
|
||||||
|
}
|
||||||
|
fuzzyMatcher := fuzzy.NewMatcher(prefix, fuzzy.Symbol)
|
||||||
|
var got []protocol.CompletionItem
|
||||||
|
for _, item := range list {
|
||||||
|
if fuzzyMatcher.Score(item.Label) < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
got = append(got, item)
|
||||||
|
}
|
||||||
|
if msg := tests.CheckCompletionOrder(want, got); msg != "" {
|
||||||
|
t.Errorf("%s: %s", src, msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprint(msg, " because of ")
|
}
|
||||||
fmt.Fprintf(msg, reason, args...)
|
|
||||||
fmt.Fprint(msg, ":\nexpected:\n")
|
func (r *runner) callCompletion(t *testing.T, src span.Span, options source.CompletionOptions) (string, []protocol.CompletionItem) {
|
||||||
for _, d := range want {
|
f, err := r.view.GetFile(r.ctx, src.URI())
|
||||||
fmt.Fprintf(msg, " %v\n", d)
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(msg, "got:\n")
|
list, surrounding, err := source.Completion(r.ctx, r.view, f.(source.GoFile), protocol.Position{
|
||||||
for _, d := range got {
|
Line: float64(src.Start().Line() - 1),
|
||||||
fmt.Fprintf(msg, " %v\n", d)
|
Character: float64(src.Start().Column() - 1),
|
||||||
|
}, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed for %v: %v", src, err)
|
||||||
}
|
}
|
||||||
return msg.String()
|
var prefix string
|
||||||
|
if surrounding != nil {
|
||||||
|
prefix = strings.ToLower(surrounding.Prefix())
|
||||||
|
}
|
||||||
|
// TODO(rstambler): In testing this out, I noticed that scores are equal,
|
||||||
|
// even when they shouldn't be. This needs more investigation.
|
||||||
|
sort.SliceStable(list, func(i, j int) bool {
|
||||||
|
return list[i].Score > list[j].Score
|
||||||
|
})
|
||||||
|
var numDeepCompletionsSeen int
|
||||||
|
var items []source.CompletionItem
|
||||||
|
// Apply deep completion filtering.
|
||||||
|
for _, item := range list {
|
||||||
|
if item.Depth > 0 {
|
||||||
|
if !options.Deep {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if numDeepCompletionsSeen >= source.MaxDeepCompletions {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
numDeepCompletionsSeen++
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
return prefix, tests.ToProtocolCompletionItems(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
|
func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package deepcomplete
|
package deep
|
||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ func wantsDeepB(deepB) {}
|
|||||||
func _() {
|
func _() {
|
||||||
var a deepA //@item(deepAVar, "a", "deepA", "var")
|
var a deepA //@item(deepAVar, "a", "deepA", "var")
|
||||||
a.b //@item(deepABField, "a.b", "deepB", "field")
|
a.b //@item(deepABField, "a.b", "deepB", "field")
|
||||||
wantsDeepB(a) //@complete(")", deepABField, deepAVar)
|
wantsDeepB(a) //@deep(")", deepABField, deepAVar)
|
||||||
|
|
||||||
deepA{a} //@snippet("}", deepABField, "a.b", "a.b")
|
deepA{a} //@snippet("}", deepABField, "a.b", "a.b")
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ func _() {
|
|||||||
context.Background() //@item(ctxBackground, "context.Background", "func() context.Context", "func", "Background returns a non-nil, empty Context.")
|
context.Background() //@item(ctxBackground, "context.Background", "func() context.Context", "func", "Background returns a non-nil, empty Context.")
|
||||||
context.TODO() //@item(ctxTODO, "context.TODO", "func() context.Context", "func", "TODO returns a non-nil, empty Context.")
|
context.TODO() //@item(ctxTODO, "context.TODO", "func() context.Context", "func", "TODO returns a non-nil, empty Context.")
|
||||||
|
|
||||||
wantsContext(c) //@completePartial(")", ctxBackground, ctxTODO)
|
wantsContext(c) //@rank(")", ctxBackground, ctxTODO)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
@ -39,7 +39,7 @@ func _() {
|
|||||||
}
|
}
|
||||||
var circle deepCircle //@item(deepCircle, "circle", "deepCircle", "var")
|
var circle deepCircle //@item(deepCircle, "circle", "deepCircle", "var")
|
||||||
circle.deepCircle //@item(deepCircleField, "circle.deepCircle", "*deepCircle", "field", "deepCircle is circular.")
|
circle.deepCircle //@item(deepCircleField, "circle.deepCircle", "*deepCircle", "field", "deepCircle is circular.")
|
||||||
var _ deepCircle = circ //@complete(" //", deepCircle, deepCircleField)
|
var _ deepCircle = circ //@deep(" //", deepCircle, deepCircleField)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
@ -57,7 +57,7 @@ func _() {
|
|||||||
var a deepEmbedA //@item(deepEmbedA, "a", "deepEmbedA", "var")
|
var a deepEmbedA //@item(deepEmbedA, "a", "deepEmbedA", "var")
|
||||||
a.deepEmbedB //@item(deepEmbedB, "a.deepEmbedB", "deepEmbedB", "field")
|
a.deepEmbedB //@item(deepEmbedB, "a.deepEmbedB", "deepEmbedB", "field")
|
||||||
a.deepEmbedC //@item(deepEmbedC, "a.deepEmbedC", "deepEmbedC", "field")
|
a.deepEmbedC //@item(deepEmbedC, "a.deepEmbedC", "deepEmbedC", "field")
|
||||||
wantsC(a) //@complete(")", deepEmbedC, deepEmbedA, deepEmbedB)
|
wantsC(a) //@deep(")", deepEmbedC, deepEmbedA, deepEmbedB)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
@ -67,7 +67,7 @@ func _() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nested{
|
nested{
|
||||||
a: 123, //@complete(" //", deepNestedField)
|
a: 123, //@deep(" //", deepNestedField)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,5 +86,5 @@ func _() {
|
|||||||
|
|
||||||
// "a.d" should be ranked above the deeper "a.b.c"
|
// "a.d" should be ranked above the deeper "a.b.c"
|
||||||
var i int
|
var i int
|
||||||
i = a //@complete(" //", deepAD, deepABC, deepA, deepAB)
|
i = a //@deep(" //", deepAD, deepABC, deepA, deepAB)
|
||||||
}
|
}
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package fuzzymatch
|
package fuzzy
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
var a struct {
|
var a struct {
|
||||||
@ -13,13 +13,13 @@ func _() {
|
|||||||
a.fabar //@item(fuzzFabarField, "a.fabar", "int", "field")
|
a.fabar //@item(fuzzFabarField, "a.fabar", "int", "field")
|
||||||
a.fooBar //@item(fuzzFooBarField, "a.fooBar", "string", "field")
|
a.fooBar //@item(fuzzFooBarField, "a.fooBar", "string", "field")
|
||||||
|
|
||||||
afa //@complete(" //", fuzzFabarField, fuzzFooBarField)
|
afa //@fuzzy(" //", fuzzFabarField, fuzzFooBarField)
|
||||||
afb //@complete(" //", fuzzFooBarField, fuzzFabarField)
|
afb //@fuzzy(" //", fuzzFooBarField, fuzzFabarField)
|
||||||
|
|
||||||
fab //@complete(" //", fuzzFabarField)
|
fab //@fuzzy(" //", fuzzFabarField)
|
||||||
|
|
||||||
var myString string
|
var myString string
|
||||||
myString = af //@complete(" //", fuzzFooBarField, fuzzFabarField)
|
myString = af //@fuzzy(" //", fuzzFooBarField, fuzzFabarField)
|
||||||
|
|
||||||
var b struct {
|
var b struct {
|
||||||
c struct {
|
c struct {
|
||||||
@ -40,9 +40,9 @@ func _() {
|
|||||||
b.c.d.e.abc //@item(fuzzABCstring, "b.c.d.e.abc", "string", "field")
|
b.c.d.e.abc //@item(fuzzABCstring, "b.c.d.e.abc", "string", "field")
|
||||||
|
|
||||||
// in depth order by default
|
// in depth order by default
|
||||||
abc //@complete(" //", fuzzABCInt, fuzzABCbool, fuzzABCfloat)
|
abc //@fuzzy(" //", fuzzABCInt, fuzzABCbool, fuzzABCfloat)
|
||||||
|
|
||||||
// deep candidate that matches expected type should still ranked first
|
// deep candidate that matches expected type should still ranked first
|
||||||
var s string
|
var s string
|
||||||
s = abc //@complete(" //", fuzzABCstring, fuzzABCInt, fuzzABCbool)
|
s = abc //@fuzzy(" //", fuzzABCstring, fuzzABCInt, fuzzABCbool)
|
||||||
}
|
}
|
@ -103,7 +103,7 @@ func _() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
"func(...) {}" //@item(litFunc, "func(...) {}", "", "var")
|
_ = "func(...) {}" //@item(litFunc, "func(...) {}", "", "var")
|
||||||
|
|
||||||
sort.Slice(nil, f) //@snippet(")", litFunc, "func(i, j int) bool {$0\\}", "func(i, j int) bool {$0\\}")
|
sort.Slice(nil, f) //@snippet(")", litFunc, "func(i, j int) bool {$0\\}", "func(i, j int) bool {$0\\}")
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ func main() {
|
|||||||
marker := strings.ReplaceAll(path, "/", "slash")
|
marker := strings.ReplaceAll(path, "/", "slash")
|
||||||
markers = append(markers, marker)
|
markers = append(markers, marker)
|
||||||
}
|
}
|
||||||
outf(" //@complete(\"\", %s)\n", strings.Join(markers, ", "))
|
outf(" //@unimported(\"\", %s)\n", strings.Join(markers, ", "))
|
||||||
outf("}\n")
|
outf("}\n")
|
||||||
outf("// Create markers for unimported std lib packages. Only for use by this test.\n")
|
outf("// Create markers for unimported std lib packages. Only for use by this test.\n")
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
package unimported
|
package unimported
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
//@complete("", archiveslashtar, archiveslashzip, bufio, bytes, compressslashbzip2, compressslashflate, compressslashgzip, compressslashlzw, compressslashzlib, containerslashheap, containerslashlist, containerslashring, context, crypto, cryptoslashaes, cryptoslashcipher, cryptoslashdes, cryptoslashdsa, cryptoslashecdsa, cryptoslashed25519, cryptoslashelliptic, cryptoslashhmac, cryptoslashmd5, cryptoslashrand, cryptoslashrc4, cryptoslashrsa, cryptoslashsha1, cryptoslashsha256, cryptoslashsha512, cryptoslashsubtle, cryptoslashtls, cryptoslashx509, cryptoslashx509slashpkix, databaseslashsql, databaseslashsqlslashdriver, debugslashdwarf, debugslashelf, debugslashgosym, debugslashmacho, debugslashpe, debugslashplan9obj, encoding, encodingslashascii85, encodingslashasn1, encodingslashbase32, encodingslashbase64, encodingslashbinary, encodingslashcsv, encodingslashgob, encodingslashhex, encodingslashjson, encodingslashpem, encodingslashxml, errors, expvar, flag, fmt, goslashast, goslashbuild, goslashconstant, goslashdoc, goslashformat, goslashimporter, goslashparser, goslashprinter, goslashscanner, goslashtoken, goslashtypes, hash, hashslashadler32, hashslashcrc32, hashslashcrc64, hashslashfnv, html, htmlslashtemplate, image, imageslashcolor, imageslashcolorslashpalette, imageslashdraw, imageslashgif, imageslashjpeg, imageslashpng, indexslashsuffixarray, io, ioslashioutil, log, logslashsyslog, math, mathslashbig, mathslashbits, mathslashcmplx, mathslashrand, mime, mimeslashmultipart, mimeslashquotedprintable, net, netslashhttp, netslashhttpslashcgi, netslashhttpslashcookiejar, netslashhttpslashfcgi, netslashhttpslashhttptest, netslashhttpslashhttptrace, netslashhttpslashhttputil, netslashhttpslashpprof, netslashmail, netslashrpc, netslashrpcslashjsonrpc, netslashsmtp, netslashtextproto, netslashurl, os, osslashexec, osslashsignal, osslashuser, path, pathslashfilepath, plugin, reflect, regexp, regexpslashsyntax, runtime, runtimeslashdebug, runtimeslashpprof, runtimeslashtrace, sort, strconv, strings, sync, syncslashatomic, syscall, syscallslashjs, testing, testingslashiotest, testingslashquick, textslashscanner, textslashtabwriter, textslashtemplate, textslashtemplateslashparse, time, unicode, unicodeslashutf16, unicodeslashutf8, unsafe)
|
//@unimported("", archiveslashtar, archiveslashzip, bufio, bytes, compressslashbzip2, compressslashflate, compressslashgzip, compressslashlzw, compressslashzlib, containerslashheap, containerslashlist, containerslashring, context, crypto, cryptoslashaes, cryptoslashcipher, cryptoslashdes, cryptoslashdsa, cryptoslashecdsa, cryptoslashed25519, cryptoslashelliptic, cryptoslashhmac, cryptoslashmd5, cryptoslashrand, cryptoslashrc4, cryptoslashrsa, cryptoslashsha1, cryptoslashsha256, cryptoslashsha512, cryptoslashsubtle, cryptoslashtls, cryptoslashx509, cryptoslashx509slashpkix, databaseslashsql, databaseslashsqlslashdriver, debugslashdwarf, debugslashelf, debugslashgosym, debugslashmacho, debugslashpe, debugslashplan9obj, encoding, encodingslashascii85, encodingslashasn1, encodingslashbase32, encodingslashbase64, encodingslashbinary, encodingslashcsv, encodingslashgob, encodingslashhex, encodingslashjson, encodingslashpem, encodingslashxml, errors, expvar, flag, fmt, goslashast, goslashbuild, goslashconstant, goslashdoc, goslashformat, goslashimporter, goslashparser, goslashprinter, goslashscanner, goslashtoken, goslashtypes, hash, hashslashadler32, hashslashcrc32, hashslashcrc64, hashslashfnv, html, htmlslashtemplate, image, imageslashcolor, imageslashcolorslashpalette, imageslashdraw, imageslashgif, imageslashjpeg, imageslashpng, indexslashsuffixarray, io, ioslashioutil, log, logslashsyslog, math, mathslashbig, mathslashbits, mathslashcmplx, mathslashrand, mime, mimeslashmultipart, mimeslashquotedprintable, net, netslashhttp, netslashhttpslashcgi, netslashhttpslashcookiejar, netslashhttpslashfcgi, netslashhttpslashhttptest, netslashhttpslashhttptrace, netslashhttpslashhttputil, netslashhttpslashpprof, netslashmail, netslashrpc, netslashrpcslashjsonrpc, netslashsmtp, netslashtextproto, netslashurl, os, osslashexec, osslashsignal, osslashuser, path, pathslashfilepath, plugin, reflect, regexp, regexpslashsyntax, runtime, runtimeslashdebug, runtimeslashpprof, runtimeslashtrace, sort, strconv, strings, sync, syncslashatomic, syscall, syscallslashjs, testing, testingslashiotest, testingslashquick, textslashscanner, textslashtabwriter, textslashtemplate, textslashtemplateslashparse, time, unicode, unicodeslashutf16, unicodeslashutf8, unsafe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create markers for unimported std lib packages. Only for use by this test.
|
// Create markers for unimported std lib packages. Only for use by this test.
|
||||||
|
193
internal/lsp/tests/completion.go
Normal file
193
internal/lsp/tests/completion.go
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToProtocolCompletionItems(items []source.CompletionItem) []protocol.CompletionItem {
|
||||||
|
var result []protocol.CompletionItem
|
||||||
|
for _, item := range items {
|
||||||
|
result = append(result, ToProtocolCompletionItem(item))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionItem {
|
||||||
|
return protocol.CompletionItem{
|
||||||
|
Label: item.Label,
|
||||||
|
Kind: toProtocolCompletionItemKind(item.Kind),
|
||||||
|
Detail: item.Detail,
|
||||||
|
Documentation: item.Documentation,
|
||||||
|
InsertText: item.InsertText,
|
||||||
|
TextEdit: &protocol.TextEdit{
|
||||||
|
NewText: item.Snippet(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toProtocolCompletionItemKind(kind source.CompletionItemKind) protocol.CompletionItemKind {
|
||||||
|
switch kind {
|
||||||
|
case source.InterfaceCompletionItem:
|
||||||
|
return protocol.InterfaceCompletion
|
||||||
|
case source.StructCompletionItem:
|
||||||
|
return protocol.StructCompletion
|
||||||
|
case source.TypeCompletionItem:
|
||||||
|
return protocol.TypeParameterCompletion // ??
|
||||||
|
case source.ConstantCompletionItem:
|
||||||
|
return protocol.ConstantCompletion
|
||||||
|
case source.FieldCompletionItem:
|
||||||
|
return protocol.FieldCompletion
|
||||||
|
case source.ParameterCompletionItem, source.VariableCompletionItem:
|
||||||
|
return protocol.VariableCompletion
|
||||||
|
case source.FunctionCompletionItem:
|
||||||
|
return protocol.FunctionCompletion
|
||||||
|
case source.MethodCompletionItem:
|
||||||
|
return protocol.MethodCompletion
|
||||||
|
case source.PackageCompletionItem:
|
||||||
|
return protocol.ModuleCompletion // ??
|
||||||
|
default:
|
||||||
|
return protocol.TextCompletion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterBuiltins(items []protocol.CompletionItem) []protocol.CompletionItem {
|
||||||
|
var got []protocol.CompletionItem
|
||||||
|
for _, item := range items {
|
||||||
|
if isBuiltin(item.Label, item.Detail, item.Kind) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
got = append(got, item)
|
||||||
|
}
|
||||||
|
return got
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBuiltin(label, detail string, kind protocol.CompletionItemKind) bool {
|
||||||
|
if detail == "" && kind == protocol.TypeParameterCompletion {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Remaining builtin constants, variables, interfaces, and functions.
|
||||||
|
trimmed := label
|
||||||
|
if i := strings.Index(trimmed, "("); i >= 0 {
|
||||||
|
trimmed = trimmed[:i]
|
||||||
|
}
|
||||||
|
switch trimmed {
|
||||||
|
case "append", "cap", "close", "complex", "copy", "delete",
|
||||||
|
"error", "false", "imag", "iota", "len", "make", "new",
|
||||||
|
"nil", "panic", "print", "println", "real", "recover", "true":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckCompletionOrder(want, got []protocol.CompletionItem) string {
|
||||||
|
var (
|
||||||
|
matchedIdxs []int
|
||||||
|
lastGotIdx int
|
||||||
|
inOrder = true
|
||||||
|
)
|
||||||
|
for _, w := range want {
|
||||||
|
var found bool
|
||||||
|
for i, g := range got {
|
||||||
|
if w.Label == g.Label && w.Detail == g.Detail && w.Kind == g.Kind {
|
||||||
|
matchedIdxs = append(matchedIdxs, i)
|
||||||
|
found = true
|
||||||
|
if i < lastGotIdx {
|
||||||
|
inOrder = false
|
||||||
|
}
|
||||||
|
lastGotIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return summarizeCompletionItems(-1, []protocol.CompletionItem{w}, got, "didn't find expected completion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Ints(matchedIdxs)
|
||||||
|
matched := make([]protocol.CompletionItem, 0, len(matchedIdxs))
|
||||||
|
for _, idx := range matchedIdxs {
|
||||||
|
matched = append(matched, got[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inOrder {
|
||||||
|
return summarizeCompletionItems(-1, want, matched, "completions out of order")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func DiffSnippets(want string, got *protocol.CompletionItem) string {
|
||||||
|
if want == "" {
|
||||||
|
if got != nil {
|
||||||
|
return fmt.Sprintf("expected no snippet but got %s", got.TextEdit.NewText)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if got == nil {
|
||||||
|
return fmt.Sprintf("couldn't find completion matching %q", want)
|
||||||
|
}
|
||||||
|
if want != got.TextEdit.NewText {
|
||||||
|
return fmt.Sprintf("expected snippet %q, got %q", want, got.TextEdit.NewText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindItem(list []protocol.CompletionItem, want source.CompletionItem) *protocol.CompletionItem {
|
||||||
|
for _, item := range list {
|
||||||
|
if item.Label == want.Label {
|
||||||
|
return &item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiffCompletionItems prints the diff between expected and actual completion
|
||||||
|
// test results.
|
||||||
|
func DiffCompletionItems(want, got []protocol.CompletionItem) string {
|
||||||
|
if len(got) != len(want) {
|
||||||
|
return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
g := got[i]
|
||||||
|
if w.Label != g.Label {
|
||||||
|
return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label)
|
||||||
|
}
|
||||||
|
if w.Detail != g.Detail {
|
||||||
|
return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail)
|
||||||
|
}
|
||||||
|
if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") {
|
||||||
|
if w.Documentation != g.Documentation {
|
||||||
|
return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if w.Kind != g.Kind {
|
||||||
|
return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, w.Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func summarizeCompletionItems(i int, want, got []protocol.CompletionItem, reason string, args ...interface{}) string {
|
||||||
|
msg := &bytes.Buffer{}
|
||||||
|
fmt.Fprint(msg, "completion failed")
|
||||||
|
if i >= 0 {
|
||||||
|
fmt.Fprintf(msg, " at %d", i)
|
||||||
|
}
|
||||||
|
fmt.Fprint(msg, " because of ")
|
||||||
|
fmt.Fprintf(msg, reason, args...)
|
||||||
|
fmt.Fprint(msg, ":\nexpected:\n")
|
||||||
|
for _, d := range want {
|
||||||
|
fmt.Fprintf(msg, " %v\n", d)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(msg, "got:\n")
|
||||||
|
for _, d := range got {
|
||||||
|
fmt.Fprintf(msg, " %v\n", d)
|
||||||
|
}
|
||||||
|
return msg.String()
|
||||||
|
}
|
91
internal/lsp/tests/diagnostics.go
Normal file
91
internal/lsp/tests/diagnostics.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiffDiagnostics prints the diff between expected and actual diagnostics test
|
||||||
|
// results.
|
||||||
|
func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string {
|
||||||
|
sortDiagnostics(want)
|
||||||
|
sortDiagnostics(got)
|
||||||
|
|
||||||
|
if len(got) != len(want) {
|
||||||
|
return summarizeDiagnostics(-1, want, got, "different lengths got %v want %v", len(got), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
g := got[i]
|
||||||
|
if w.Message != g.Message {
|
||||||
|
return summarizeDiagnostics(i, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
|
||||||
|
}
|
||||||
|
if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
|
||||||
|
return summarizeDiagnostics(i, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
|
||||||
|
}
|
||||||
|
// Special case for diagnostics on parse errors.
|
||||||
|
if strings.Contains(string(uri), "noparse") {
|
||||||
|
if protocol.ComparePosition(g.Range.Start, g.Range.End) != 0 || protocol.ComparePosition(w.Range.Start, g.Range.End) != 0 {
|
||||||
|
return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.Start)
|
||||||
|
}
|
||||||
|
} else if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range.
|
||||||
|
if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
|
||||||
|
return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if w.Severity != g.Severity {
|
||||||
|
return summarizeDiagnostics(i, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
|
||||||
|
}
|
||||||
|
if w.Source != g.Source {
|
||||||
|
return summarizeDiagnostics(i, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortDiagnostics(d []source.Diagnostic) {
|
||||||
|
sort.Slice(d, func(i int, j int) bool {
|
||||||
|
return compareDiagnostic(d[i], d[j]) < 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareDiagnostic(a, b source.Diagnostic) int {
|
||||||
|
if r := span.CompareURI(a.URI, b.URI); r != 0 {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
if a.Message < b.Message {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if a.Message == b.Message {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func summarizeDiagnostics(i int, want []source.Diagnostic, got []source.Diagnostic, reason string, args ...interface{}) string {
|
||||||
|
msg := &bytes.Buffer{}
|
||||||
|
fmt.Fprint(msg, "diagnostics failed")
|
||||||
|
if i >= 0 {
|
||||||
|
fmt.Fprintf(msg, " at %d", i)
|
||||||
|
}
|
||||||
|
fmt.Fprint(msg, " because of ")
|
||||||
|
fmt.Fprintf(msg, reason, args...)
|
||||||
|
fmt.Fprint(msg, ":\nexpected:\n")
|
||||||
|
for _, d := range want {
|
||||||
|
fmt.Fprintf(msg, " %s:%v: %s\n", d.URI, d.Range, d.Message)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(msg, "got:\n")
|
||||||
|
for _, d := range got {
|
||||||
|
fmt.Fprintf(msg, " %s:%v: %s\n", d.URI, d.Range, d.Message)
|
||||||
|
}
|
||||||
|
return msg.String()
|
||||||
|
}
|
@ -2,13 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package tests exports functionality to be used across a variety of gopls tests.
|
||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -30,22 +29,26 @@ import (
|
|||||||
// We hardcode the expected number of test cases to ensure that all tests
|
// We hardcode the expected number of test cases to ensure that all tests
|
||||||
// are being executed. If a test is added, this number must be changed.
|
// are being executed. If a test is added, this number must be changed.
|
||||||
const (
|
const (
|
||||||
ExpectedCompletionsCount = 165
|
ExpectedCompletionsCount = 152
|
||||||
ExpectedCompletionSnippetCount = 35
|
ExpectedCompletionSnippetCount = 35
|
||||||
ExpectedDiagnosticsCount = 21
|
ExpectedUnimportedCompletionsCount = 1
|
||||||
ExpectedFormatCount = 6
|
ExpectedDeepCompletionsCount = 5
|
||||||
ExpectedImportCount = 2
|
ExpectedFuzzyCompletionsCount = 6
|
||||||
ExpectedSuggestedFixCount = 1
|
ExpectedRankedCompletionsCount = 1
|
||||||
ExpectedDefinitionsCount = 39
|
ExpectedDiagnosticsCount = 21
|
||||||
ExpectedTypeDefinitionsCount = 2
|
ExpectedFormatCount = 6
|
||||||
ExpectedFoldingRangesCount = 2
|
ExpectedImportCount = 2
|
||||||
ExpectedHighlightsCount = 2
|
ExpectedSuggestedFixCount = 1
|
||||||
ExpectedReferencesCount = 6
|
ExpectedDefinitionsCount = 39
|
||||||
ExpectedRenamesCount = 20
|
ExpectedTypeDefinitionsCount = 2
|
||||||
ExpectedPrepareRenamesCount = 8
|
ExpectedFoldingRangesCount = 2
|
||||||
ExpectedSymbolsCount = 1
|
ExpectedHighlightsCount = 2
|
||||||
ExpectedSignaturesCount = 21
|
ExpectedReferencesCount = 6
|
||||||
ExpectedLinksCount = 4
|
ExpectedRenamesCount = 20
|
||||||
|
ExpectedPrepareRenamesCount = 8
|
||||||
|
ExpectedSymbolsCount = 1
|
||||||
|
ExpectedSignaturesCount = 21
|
||||||
|
ExpectedLinksCount = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -61,6 +64,10 @@ type Diagnostics map[span.URI][]source.Diagnostic
|
|||||||
type CompletionItems map[token.Pos]*source.CompletionItem
|
type CompletionItems map[token.Pos]*source.CompletionItem
|
||||||
type Completions map[span.Span]Completion
|
type Completions map[span.Span]Completion
|
||||||
type CompletionSnippets map[span.Span]CompletionSnippet
|
type CompletionSnippets map[span.Span]CompletionSnippet
|
||||||
|
type UnimportedCompletions map[span.Span]Completion
|
||||||
|
type DeepCompletions map[span.Span]Completion
|
||||||
|
type FuzzyCompletions map[span.Span]Completion
|
||||||
|
type RankCompletions map[span.Span]Completion
|
||||||
type FoldingRanges []span.Span
|
type FoldingRanges []span.Span
|
||||||
type Formats []span.Span
|
type Formats []span.Span
|
||||||
type Imports []span.Span
|
type Imports []span.Span
|
||||||
@ -76,25 +83,29 @@ type Signatures map[span.Span]*source.SignatureInformation
|
|||||||
type Links map[span.URI][]Link
|
type Links map[span.URI][]Link
|
||||||
|
|
||||||
type Data struct {
|
type Data struct {
|
||||||
Config packages.Config
|
Config packages.Config
|
||||||
Exported *packagestest.Exported
|
Exported *packagestest.Exported
|
||||||
Diagnostics Diagnostics
|
Diagnostics Diagnostics
|
||||||
CompletionItems CompletionItems
|
CompletionItems CompletionItems
|
||||||
Completions Completions
|
Completions Completions
|
||||||
CompletionSnippets CompletionSnippets
|
CompletionSnippets CompletionSnippets
|
||||||
FoldingRanges FoldingRanges
|
UnimportedCompletions UnimportedCompletions
|
||||||
Formats Formats
|
DeepCompletions DeepCompletions
|
||||||
Imports Imports
|
FuzzyCompletions FuzzyCompletions
|
||||||
SuggestedFixes SuggestedFixes
|
RankCompletions RankCompletions
|
||||||
Definitions Definitions
|
FoldingRanges FoldingRanges
|
||||||
Highlights Highlights
|
Formats Formats
|
||||||
References References
|
Imports Imports
|
||||||
Renames Renames
|
SuggestedFixes SuggestedFixes
|
||||||
PrepareRenames PrepareRenames
|
Definitions Definitions
|
||||||
Symbols Symbols
|
Highlights Highlights
|
||||||
symbolsChildren SymbolsChildren
|
References References
|
||||||
Signatures Signatures
|
Renames Renames
|
||||||
Links Links
|
PrepareRenames PrepareRenames
|
||||||
|
Symbols Symbols
|
||||||
|
symbolsChildren SymbolsChildren
|
||||||
|
Signatures Signatures
|
||||||
|
Links Links
|
||||||
|
|
||||||
t testing.TB
|
t testing.TB
|
||||||
fragments map[string]string
|
fragments map[string]string
|
||||||
@ -107,7 +118,12 @@ type Data struct {
|
|||||||
|
|
||||||
type Tests interface {
|
type Tests interface {
|
||||||
Diagnostics(*testing.T, Diagnostics)
|
Diagnostics(*testing.T, Diagnostics)
|
||||||
Completion(*testing.T, Completions, CompletionSnippets, CompletionItems)
|
Completion(*testing.T, Completions, CompletionItems)
|
||||||
|
CompletionSnippets(*testing.T, CompletionSnippets, CompletionItems)
|
||||||
|
UnimportedCompletions(*testing.T, UnimportedCompletions, CompletionItems)
|
||||||
|
DeepCompletions(*testing.T, DeepCompletions, CompletionItems)
|
||||||
|
FuzzyCompletions(*testing.T, FuzzyCompletions, CompletionItems)
|
||||||
|
RankCompletions(*testing.T, RankCompletions, CompletionItems)
|
||||||
FoldingRange(*testing.T, FoldingRanges)
|
FoldingRange(*testing.T, FoldingRanges)
|
||||||
Format(*testing.T, Formats)
|
Format(*testing.T, Formats)
|
||||||
Import(*testing.T, Imports)
|
Import(*testing.T, Imports)
|
||||||
@ -132,16 +148,24 @@ type Definition struct {
|
|||||||
type CompletionTestType int
|
type CompletionTestType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Full means candidates in test must match full list of candidates.
|
// Default runs the standard completion tests.
|
||||||
CompletionFull CompletionTestType = iota
|
CompletionDefault = CompletionTestType(iota)
|
||||||
|
|
||||||
// Partial means candidates in test must be valid and in the right relative order.
|
// Unimported tests the autocompletion of unimported packages.
|
||||||
CompletionPartial
|
CompletionUnimported
|
||||||
|
|
||||||
|
// Deep tests deep completion.
|
||||||
|
CompletionDeep
|
||||||
|
|
||||||
|
// Fuzzy tests deep completion and fuzzy matching.
|
||||||
|
CompletionFuzzy
|
||||||
|
|
||||||
|
// CompletionRank candidates in test must be valid and in the right relative order.
|
||||||
|
CompletionRank
|
||||||
)
|
)
|
||||||
|
|
||||||
type Completion struct {
|
type Completion struct {
|
||||||
CompletionItems []token.Pos
|
CompletionItems []token.Pos
|
||||||
Type CompletionTestType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompletionSnippet struct {
|
type CompletionSnippet struct {
|
||||||
@ -166,23 +190,42 @@ func Context(t testing.TB) context.Context {
|
|||||||
return context.Background()
|
return context.Background()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DefaultOptions() source.Options {
|
||||||
|
o := source.DefaultOptions
|
||||||
|
o.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
|
||||||
|
source.Go: {
|
||||||
|
protocol.SourceOrganizeImports: true,
|
||||||
|
protocol.QuickFix: true,
|
||||||
|
},
|
||||||
|
source.Mod: {},
|
||||||
|
source.Sum: {},
|
||||||
|
}
|
||||||
|
o.HoverKind = source.SynopsisDocumentation
|
||||||
|
o.InsertTextFormat = protocol.SnippetTextFormat
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
data := &Data{
|
data := &Data{
|
||||||
Diagnostics: make(Diagnostics),
|
Diagnostics: make(Diagnostics),
|
||||||
CompletionItems: make(CompletionItems),
|
CompletionItems: make(CompletionItems),
|
||||||
Completions: make(Completions),
|
Completions: make(Completions),
|
||||||
CompletionSnippets: make(CompletionSnippets),
|
CompletionSnippets: make(CompletionSnippets),
|
||||||
Definitions: make(Definitions),
|
UnimportedCompletions: make(UnimportedCompletions),
|
||||||
Highlights: make(Highlights),
|
DeepCompletions: make(DeepCompletions),
|
||||||
References: make(References),
|
FuzzyCompletions: make(FuzzyCompletions),
|
||||||
Renames: make(Renames),
|
RankCompletions: make(RankCompletions),
|
||||||
PrepareRenames: make(PrepareRenames),
|
Definitions: make(Definitions),
|
||||||
Symbols: make(Symbols),
|
Highlights: make(Highlights),
|
||||||
symbolsChildren: make(SymbolsChildren),
|
References: make(References),
|
||||||
Signatures: make(Signatures),
|
Renames: make(Renames),
|
||||||
Links: make(Links),
|
PrepareRenames: make(PrepareRenames),
|
||||||
|
Symbols: make(Symbols),
|
||||||
|
symbolsChildren: make(SymbolsChildren),
|
||||||
|
Signatures: make(Signatures),
|
||||||
|
Links: make(Links),
|
||||||
|
|
||||||
t: t,
|
t: t,
|
||||||
dir: dir,
|
dir: dir,
|
||||||
@ -226,7 +269,7 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
data.Exported = packagestest.Export(t, exporter, modules)
|
data.Exported = packagestest.Export(t, exporter, modules)
|
||||||
for fragment, _ := range files {
|
for fragment := range files {
|
||||||
filename := data.Exported.File(testModule, fragment)
|
filename := data.Exported.File(testModule, fragment)
|
||||||
data.fragments[filename] = fragment
|
data.fragments[filename] = fragment
|
||||||
}
|
}
|
||||||
@ -252,25 +295,30 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
|||||||
|
|
||||||
// Collect any data that needs to be used by subsequent tests.
|
// Collect any data that needs to be used by subsequent tests.
|
||||||
if err := data.Exported.Expect(map[string]interface{}{
|
if err := data.Exported.Expect(map[string]interface{}{
|
||||||
"diag": data.collectDiagnostics,
|
"diag": data.collectDiagnostics,
|
||||||
"item": data.collectCompletionItems,
|
"item": data.collectCompletionItems,
|
||||||
"complete": data.collectCompletions(CompletionFull),
|
"complete": data.collectCompletions(CompletionDefault),
|
||||||
"completePartial": data.collectCompletions(CompletionPartial),
|
"unimported": data.collectCompletions(CompletionUnimported),
|
||||||
"fold": data.collectFoldingRanges,
|
"deep": data.collectCompletions(CompletionDeep),
|
||||||
"format": data.collectFormats,
|
"fuzzy": data.collectCompletions(CompletionFuzzy),
|
||||||
"import": data.collectImports,
|
"rank": data.collectCompletions(CompletionRank),
|
||||||
"godef": data.collectDefinitions,
|
"snippet": data.collectCompletionSnippets,
|
||||||
"typdef": data.collectTypeDefinitions,
|
"fold": data.collectFoldingRanges,
|
||||||
"hover": data.collectHoverDefinitions,
|
"format": data.collectFormats,
|
||||||
"highlight": data.collectHighlights,
|
"import": data.collectImports,
|
||||||
"refs": data.collectReferences,
|
"godef": data.collectDefinitions,
|
||||||
"rename": data.collectRenames,
|
"typdef": data.collectTypeDefinitions,
|
||||||
"prepare": data.collectPrepareRenames,
|
"hover": data.collectHoverDefinitions,
|
||||||
"symbol": data.collectSymbols,
|
"highlight": data.collectHighlights,
|
||||||
"signature": data.collectSignatures,
|
"refs": data.collectReferences,
|
||||||
"snippet": data.collectCompletionSnippets,
|
"rename": data.collectRenames,
|
||||||
"link": data.collectLinks,
|
"prepare": data.collectPrepareRenames,
|
||||||
"suggestedfix": data.collectSuggestedFixes,
|
"symbol": data.collectSymbols,
|
||||||
|
"signature": data.collectSignatures,
|
||||||
|
|
||||||
|
// LSP-only features.
|
||||||
|
"link": data.collectLinks,
|
||||||
|
"suggestedfix": data.collectSuggestedFixes,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -292,15 +340,56 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
|||||||
|
|
||||||
func Run(t *testing.T, tests Tests, data *Data) {
|
func Run(t *testing.T, tests Tests, data *Data) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
t.Run("Completion", func(t *testing.T) {
|
t.Run("Completion", func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if len(data.Completions) != ExpectedCompletionsCount {
|
if len(data.Completions) != ExpectedCompletionsCount {
|
||||||
t.Errorf("got %v completions expected %v", len(data.Completions), ExpectedCompletionsCount)
|
t.Errorf("got %v completions expected %v", len(data.Completions), ExpectedCompletionsCount)
|
||||||
}
|
}
|
||||||
|
tests.Completion(t, data.Completions, data.CompletionItems)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CompletionSnippets", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
if len(data.CompletionSnippets) != ExpectedCompletionSnippetCount {
|
if len(data.CompletionSnippets) != ExpectedCompletionSnippetCount {
|
||||||
t.Errorf("got %v snippets expected %v", len(data.CompletionSnippets), ExpectedCompletionSnippetCount)
|
t.Errorf("got %v snippets expected %v", len(data.CompletionSnippets), ExpectedCompletionSnippetCount)
|
||||||
}
|
}
|
||||||
tests.Completion(t, data.Completions, data.CompletionSnippets, data.CompletionItems)
|
if len(data.CompletionSnippets) != ExpectedCompletionSnippetCount {
|
||||||
|
t.Errorf("got %v snippets expected %v", len(data.CompletionSnippets), ExpectedCompletionSnippetCount)
|
||||||
|
}
|
||||||
|
tests.CompletionSnippets(t, data.CompletionSnippets, data.CompletionItems)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UnimportedCompletion", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
if len(data.UnimportedCompletions) != ExpectedUnimportedCompletionsCount {
|
||||||
|
t.Errorf("got %v unimported completions expected %v", len(data.UnimportedCompletions), ExpectedUnimportedCompletionsCount)
|
||||||
|
}
|
||||||
|
tests.UnimportedCompletions(t, data.UnimportedCompletions, data.CompletionItems)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeepCompletion", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
if len(data.DeepCompletions) != ExpectedDeepCompletionsCount {
|
||||||
|
t.Errorf("got %v deep completions expected %v", len(data.DeepCompletions), ExpectedDeepCompletionsCount)
|
||||||
|
}
|
||||||
|
tests.DeepCompletions(t, data.DeepCompletions, data.CompletionItems)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FuzzyCompletion", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
if len(data.FuzzyCompletions) != ExpectedFuzzyCompletionsCount {
|
||||||
|
t.Errorf("got %v fuzzy completions expected %v", len(data.FuzzyCompletions), ExpectedFuzzyCompletionsCount)
|
||||||
|
}
|
||||||
|
tests.FuzzyCompletions(t, data.FuzzyCompletions, data.CompletionItems)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("RankCompletions", func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
if len(data.RankCompletions) != ExpectedRankedCompletionsCount {
|
||||||
|
t.Errorf("got %v fuzzy completions expected %v", len(data.RankCompletions), ExpectedRankedCompletionsCount)
|
||||||
|
}
|
||||||
|
tests.RankCompletions(t, data.RankCompletions, data.CompletionItems)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Diagnostics", func(t *testing.T) {
|
t.Run("Diagnostics", func(t *testing.T) {
|
||||||
@ -528,90 +617,32 @@ func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg string) {
|
|||||||
data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want)
|
data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want)
|
||||||
}
|
}
|
||||||
|
|
||||||
// diffDiagnostics prints the diff between expected and actual diagnostics test
|
|
||||||
// results.
|
|
||||||
func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string {
|
|
||||||
sortDiagnostics(want)
|
|
||||||
sortDiagnostics(got)
|
|
||||||
|
|
||||||
if len(got) != len(want) {
|
|
||||||
return summarizeDiagnostics(-1, want, got, "different lengths got %v want %v", len(got), len(want))
|
|
||||||
}
|
|
||||||
for i, w := range want {
|
|
||||||
g := got[i]
|
|
||||||
if w.Message != g.Message {
|
|
||||||
return summarizeDiagnostics(i, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
|
|
||||||
}
|
|
||||||
if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
|
|
||||||
return summarizeDiagnostics(i, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
|
|
||||||
}
|
|
||||||
// Special case for diagnostics on parse errors.
|
|
||||||
if strings.Contains(string(uri), "noparse") {
|
|
||||||
if protocol.ComparePosition(g.Range.Start, g.Range.End) != 0 || protocol.ComparePosition(w.Range.Start, g.Range.End) != 0 {
|
|
||||||
return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.Start)
|
|
||||||
}
|
|
||||||
} else if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range.
|
|
||||||
if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
|
|
||||||
return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if w.Severity != g.Severity {
|
|
||||||
return summarizeDiagnostics(i, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
|
|
||||||
}
|
|
||||||
if w.Source != g.Source {
|
|
||||||
return summarizeDiagnostics(i, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortDiagnostics(d []source.Diagnostic) {
|
|
||||||
sort.Slice(d, func(i int, j int) bool {
|
|
||||||
return compareDiagnostic(d[i], d[j]) < 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareDiagnostic(a, b source.Diagnostic) int {
|
|
||||||
if r := span.CompareURI(a.URI, b.URI); r != 0 {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
if a.Message < b.Message {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if a.Message == b.Message {
|
|
||||||
return 0
|
|
||||||
} else {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func summarizeDiagnostics(i int, want []source.Diagnostic, got []source.Diagnostic, reason string, args ...interface{}) string {
|
|
||||||
msg := &bytes.Buffer{}
|
|
||||||
fmt.Fprint(msg, "diagnostics failed")
|
|
||||||
if i >= 0 {
|
|
||||||
fmt.Fprintf(msg, " at %d", i)
|
|
||||||
}
|
|
||||||
fmt.Fprint(msg, " because of ")
|
|
||||||
fmt.Fprintf(msg, reason, args...)
|
|
||||||
fmt.Fprint(msg, ":\nexpected:\n")
|
|
||||||
for _, d := range want {
|
|
||||||
fmt.Fprintf(msg, " %s:%v: %s\n", d.URI, d.Range, d.Message)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(msg, "got:\n")
|
|
||||||
for _, d := range got {
|
|
||||||
fmt.Fprintf(msg, " %s:%v: %s\n", d.URI, d.Range, d.Message)
|
|
||||||
}
|
|
||||||
return msg.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
|
func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
|
||||||
return func(src span.Span, expected []token.Pos) {
|
result := func(m map[span.Span]Completion, src span.Span, expected []token.Pos) {
|
||||||
data.Completions[src] = Completion{
|
m[src] = Completion{
|
||||||
CompletionItems: expected,
|
CompletionItems: expected,
|
||||||
Type: typ,
|
}
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case CompletionDeep:
|
||||||
|
return func(src span.Span, expected []token.Pos) {
|
||||||
|
result(data.DeepCompletions, src, expected)
|
||||||
|
}
|
||||||
|
case CompletionUnimported:
|
||||||
|
return func(src span.Span, expected []token.Pos) {
|
||||||
|
result(data.UnimportedCompletions, src, expected)
|
||||||
|
}
|
||||||
|
case CompletionFuzzy:
|
||||||
|
return func(src span.Span, expected []token.Pos) {
|
||||||
|
result(data.FuzzyCompletions, src, expected)
|
||||||
|
}
|
||||||
|
case CompletionRank:
|
||||||
|
return func(src span.Span, expected []token.Pos) {
|
||||||
|
result(data.RankCompletions, src, expected)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return func(src span.Span, expected []token.Pos) {
|
||||||
|
result(data.Completions, src, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user