Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / internal / lsp / tests / util.go
1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package tests
6
7 import (
8         "bytes"
9         "fmt"
10         "go/token"
11         "path/filepath"
12         "sort"
13         "strconv"
14         "strings"
15         "testing"
16
17         "golang.org/x/tools/internal/lsp/diff"
18         "golang.org/x/tools/internal/lsp/diff/myers"
19         "golang.org/x/tools/internal/lsp/protocol"
20         "golang.org/x/tools/internal/lsp/source"
21         "golang.org/x/tools/internal/lsp/source/completion"
22         "golang.org/x/tools/internal/span"
23 )
24
25 // DiffLinks takes the links we got and checks if they are located within the source or a Note.
26 // If the link is within a Note, the link is removed.
27 // Returns an diff comment if there are differences and empty string if no diffs.
28 func DiffLinks(mapper *protocol.ColumnMapper, wantLinks []Link, gotLinks []protocol.DocumentLink) string {
29         var notePositions []token.Position
30         links := make(map[span.Span]string, len(wantLinks))
31         for _, link := range wantLinks {
32                 links[link.Src] = link.Target
33                 notePositions = append(notePositions, link.NotePosition)
34         }
35         for _, link := range gotLinks {
36                 spn, err := mapper.RangeSpan(link.Range)
37                 if err != nil {
38                         return fmt.Sprintf("%v", err)
39                 }
40                 linkInNote := false
41                 for _, notePosition := range notePositions {
42                         // Drop the links found inside expectation notes arguments as this links are not collected by expect package.
43                         if notePosition.Line == spn.Start().Line() &&
44                                 notePosition.Column <= spn.Start().Column() {
45                                 delete(links, spn)
46                                 linkInNote = true
47                         }
48                 }
49                 if linkInNote {
50                         continue
51                 }
52                 if target, ok := links[spn]; ok {
53                         delete(links, spn)
54                         if target != link.Target {
55                                 return fmt.Sprintf("for %v want %v, got %v\n", spn, target, link.Target)
56                         }
57                 } else {
58                         return fmt.Sprintf("unexpected link %v:%v\n", spn, link.Target)
59                 }
60         }
61         for spn, target := range links {
62                 return fmt.Sprintf("missing link %v:%v\n", spn, target)
63         }
64         return ""
65 }
66
67 // DiffSymbols prints the diff between expected and actual symbols test results.
68 func DiffSymbols(t *testing.T, uri span.URI, want, got []protocol.DocumentSymbol) string {
69         sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name })
70         sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name })
71         if len(got) != len(want) {
72                 return summarizeSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want))
73         }
74         for i, w := range want {
75                 g := got[i]
76                 if w.Name != g.Name {
77                         return summarizeSymbols(i, want, got, "incorrect name got %v want %v", g.Name, w.Name)
78                 }
79                 if w.Kind != g.Kind {
80                         return summarizeSymbols(i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind)
81                 }
82                 if protocol.CompareRange(w.SelectionRange, g.SelectionRange) != 0 {
83                         return summarizeSymbols(i, want, got, "incorrect span got %v want %v", g.SelectionRange, w.SelectionRange)
84                 }
85                 if msg := DiffSymbols(t, uri, w.Children, g.Children); msg != "" {
86                         return fmt.Sprintf("children of %s: %s", w.Name, msg)
87                 }
88         }
89         return ""
90 }
91
92 func summarizeSymbols(i int, want, got []protocol.DocumentSymbol, reason string, args ...interface{}) string {
93         msg := &bytes.Buffer{}
94         fmt.Fprint(msg, "document symbols failed")
95         if i >= 0 {
96                 fmt.Fprintf(msg, " at %d", i)
97         }
98         fmt.Fprint(msg, " because of ")
99         fmt.Fprintf(msg, reason, args...)
100         fmt.Fprint(msg, ":\nexpected:\n")
101         for _, s := range want {
102                 fmt.Fprintf(msg, "  %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
103         }
104         fmt.Fprintf(msg, "got:\n")
105         for _, s := range got {
106                 fmt.Fprintf(msg, "  %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
107         }
108         return msg.String()
109 }
110
111 // FilterWorkspaceSymbols filters to got contained in the given dirs.
112 func FilterWorkspaceSymbols(got []protocol.SymbolInformation, dirs map[string]struct{}) []protocol.SymbolInformation {
113         var result []protocol.SymbolInformation
114         for _, si := range got {
115                 if _, ok := dirs[filepath.Dir(si.Location.URI.SpanURI().Filename())]; ok {
116                         result = append(result, si)
117                 }
118         }
119         return result
120 }
121
122 // DiffWorkspaceSymbols prints the diff between expected and actual workspace
123 // symbols test results.
124 func DiffWorkspaceSymbols(want, got []protocol.SymbolInformation) string {
125         sort.Slice(want, func(i, j int) bool { return fmt.Sprintf("%v", want[i]) < fmt.Sprintf("%v", want[j]) })
126         sort.Slice(got, func(i, j int) bool { return fmt.Sprintf("%v", got[i]) < fmt.Sprintf("%v", got[j]) })
127         if len(got) != len(want) {
128                 return summarizeWorkspaceSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want))
129         }
130         for i, w := range want {
131                 g := got[i]
132                 if w.Name != g.Name {
133                         return summarizeWorkspaceSymbols(i, want, got, "incorrect name got %v want %v", g.Name, w.Name)
134                 }
135                 if w.Kind != g.Kind {
136                         return summarizeWorkspaceSymbols(i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind)
137                 }
138                 if w.Location.URI != g.Location.URI {
139                         return summarizeWorkspaceSymbols(i, want, got, "incorrect uri got %v want %v", g.Location.URI, w.Location.URI)
140                 }
141                 if protocol.CompareRange(w.Location.Range, g.Location.Range) != 0 {
142                         return summarizeWorkspaceSymbols(i, want, got, "incorrect range got %v want %v", g.Location.Range, w.Location.Range)
143                 }
144         }
145         return ""
146 }
147
148 func summarizeWorkspaceSymbols(i int, want, got []protocol.SymbolInformation, reason string, args ...interface{}) string {
149         msg := &bytes.Buffer{}
150         fmt.Fprint(msg, "workspace symbols failed")
151         if i >= 0 {
152                 fmt.Fprintf(msg, " at %d", i)
153         }
154         fmt.Fprint(msg, " because of ")
155         fmt.Fprintf(msg, reason, args...)
156         fmt.Fprint(msg, ":\nexpected:\n")
157         for _, s := range want {
158                 fmt.Fprintf(msg, "  %v %v %v:%v\n", s.Name, s.Kind, s.Location.URI, s.Location.Range)
159         }
160         fmt.Fprintf(msg, "got:\n")
161         for _, s := range got {
162                 fmt.Fprintf(msg, "  %v %v %v:%v\n", s.Name, s.Kind, s.Location.URI, s.Location.Range)
163         }
164         return msg.String()
165 }
166
167 // DiffDiagnostics prints the diff between expected and actual diagnostics test
168 // results.
169 func DiffDiagnostics(uri span.URI, want, got []*source.Diagnostic) string {
170         source.SortDiagnostics(want)
171         source.SortDiagnostics(got)
172
173         if len(got) != len(want) {
174                 return summarizeDiagnostics(-1, uri, want, got, "different lengths got %v want %v", len(got), len(want))
175         }
176         for i, w := range want {
177                 g := got[i]
178                 if w.Message != g.Message {
179                         return summarizeDiagnostics(i, uri, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
180                 }
181                 if w.Severity != g.Severity {
182                         return summarizeDiagnostics(i, uri, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
183                 }
184                 if w.Source != g.Source {
185                         return summarizeDiagnostics(i, uri, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
186                 }
187                 // Don't check the range on the badimport test.
188                 if strings.Contains(uri.Filename(), "badimport") {
189                         continue
190                 }
191                 if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
192                         return summarizeDiagnostics(i, uri, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
193                 }
194                 if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range.
195                         if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
196                                 return summarizeDiagnostics(i, uri, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
197                         }
198                 }
199         }
200         return ""
201 }
202
203 func summarizeDiagnostics(i int, uri span.URI, want, got []*source.Diagnostic, reason string, args ...interface{}) string {
204         msg := &bytes.Buffer{}
205         fmt.Fprint(msg, "diagnostics failed")
206         if i >= 0 {
207                 fmt.Fprintf(msg, " at %d", i)
208         }
209         fmt.Fprint(msg, " because of ")
210         fmt.Fprintf(msg, reason, args...)
211         fmt.Fprint(msg, ":\nexpected:\n")
212         for _, d := range want {
213                 fmt.Fprintf(msg, "  %s:%v: %s\n", uri, d.Range, d.Message)
214         }
215         fmt.Fprintf(msg, "got:\n")
216         for _, d := range got {
217                 fmt.Fprintf(msg, "  %s:%v: %s\n", uri, d.Range, d.Message)
218         }
219         return msg.String()
220 }
221
222 func DiffCodeLens(uri span.URI, want, got []protocol.CodeLens) string {
223         sortCodeLens(want)
224         sortCodeLens(got)
225
226         if len(got) != len(want) {
227                 return summarizeCodeLens(-1, uri, want, got, "different lengths got %v want %v", len(got), len(want))
228         }
229         for i, w := range want {
230                 g := got[i]
231                 if w.Command.Command != g.Command.Command {
232                         return summarizeCodeLens(i, uri, want, got, "incorrect Command Name got %v want %v", g.Command.Command, w.Command.Command)
233                 }
234                 if w.Command.Title != g.Command.Title {
235                         return summarizeCodeLens(i, uri, want, got, "incorrect Command Title got %v want %v", g.Command.Title, w.Command.Title)
236                 }
237                 if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
238                         return summarizeCodeLens(i, uri, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
239                 }
240                 if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the codelens returns a zero-length range.
241                         if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
242                                 return summarizeCodeLens(i, uri, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
243                         }
244                 }
245         }
246         return ""
247 }
248
249 func sortCodeLens(c []protocol.CodeLens) {
250         sort.Slice(c, func(i int, j int) bool {
251                 if r := protocol.CompareRange(c[i].Range, c[j].Range); r != 0 {
252                         return r < 0
253                 }
254                 if c[i].Command.Command < c[j].Command.Command {
255                         return true
256                 } else if c[i].Command.Command == c[j].Command.Command {
257                         return c[i].Command.Title < c[j].Command.Title
258                 } else {
259                         return false
260                 }
261         })
262 }
263
264 func summarizeCodeLens(i int, uri span.URI, want, got []protocol.CodeLens, reason string, args ...interface{}) string {
265         msg := &bytes.Buffer{}
266         fmt.Fprint(msg, "codelens failed")
267         if i >= 0 {
268                 fmt.Fprintf(msg, " at %d", i)
269         }
270         fmt.Fprint(msg, " because of ")
271         fmt.Fprintf(msg, reason, args...)
272         fmt.Fprint(msg, ":\nexpected:\n")
273         for _, d := range want {
274                 fmt.Fprintf(msg, "  %s:%v: %s | %s\n", uri, d.Range, d.Command.Command, d.Command.Title)
275         }
276         fmt.Fprintf(msg, "got:\n")
277         for _, d := range got {
278                 fmt.Fprintf(msg, "  %s:%v: %s | %s\n", uri, d.Range, d.Command.Command, d.Command.Title)
279         }
280         return msg.String()
281 }
282
283 func DiffSignatures(spn span.Span, want, got *protocol.SignatureHelp) string {
284         decorate := func(f string, args ...interface{}) string {
285                 return fmt.Sprintf("invalid signature at %s: %s", spn, fmt.Sprintf(f, args...))
286         }
287         if len(got.Signatures) != 1 {
288                 return decorate("wanted 1 signature, got %d", len(got.Signatures))
289         }
290         if got.ActiveSignature != 0 {
291                 return decorate("wanted active signature of 0, got %d", int(got.ActiveSignature))
292         }
293         if want.ActiveParameter != got.ActiveParameter {
294                 return decorate("wanted active parameter of %d, got %d", want.ActiveParameter, int(got.ActiveParameter))
295         }
296         g := got.Signatures[0]
297         w := want.Signatures[0]
298         if w.Label != g.Label {
299                 wLabel := w.Label + "\n"
300                 d := myers.ComputeEdits("", wLabel, g.Label+"\n")
301                 return decorate("mismatched labels:\n%q", diff.ToUnified("want", "got", wLabel, d))
302         }
303         var paramParts []string
304         for _, p := range g.Parameters {
305                 paramParts = append(paramParts, p.Label)
306         }
307         paramsStr := strings.Join(paramParts, ", ")
308         if !strings.Contains(g.Label, paramsStr) {
309                 return decorate("expected signature %q to contain params %q", g.Label, paramsStr)
310         }
311         return ""
312 }
313
314 // DiffCallHierarchyItems returns the diff between expected and actual call locations for incoming/outgoing call hierarchies
315 func DiffCallHierarchyItems(gotCalls []protocol.CallHierarchyItem, expectedCalls []protocol.CallHierarchyItem) string {
316         expected := make(map[protocol.Location]bool)
317         for _, call := range expectedCalls {
318                 expected[protocol.Location{URI: call.URI, Range: call.Range}] = true
319         }
320
321         got := make(map[protocol.Location]bool)
322         for _, call := range gotCalls {
323                 got[protocol.Location{URI: call.URI, Range: call.Range}] = true
324         }
325         if len(got) != len(expected) {
326                 return fmt.Sprintf("expected %d calls but got %d", len(expected), len(got))
327         }
328         for spn := range got {
329                 if !expected[spn] {
330                         return fmt.Sprintf("incorrect calls, expected locations %v but got locations %v", expected, got)
331                 }
332         }
333         return ""
334 }
335
336 func ToProtocolCompletionItems(items []completion.CompletionItem) []protocol.CompletionItem {
337         var result []protocol.CompletionItem
338         for _, item := range items {
339                 result = append(result, ToProtocolCompletionItem(item))
340         }
341         return result
342 }
343
344 func ToProtocolCompletionItem(item completion.CompletionItem) protocol.CompletionItem {
345         pItem := protocol.CompletionItem{
346                 Label:         item.Label,
347                 Kind:          item.Kind,
348                 Detail:        item.Detail,
349                 Documentation: item.Documentation,
350                 InsertText:    item.InsertText,
351                 TextEdit: &protocol.TextEdit{
352                         NewText: item.Snippet(),
353                 },
354                 // Negate score so best score has lowest sort text like real API.
355                 SortText: fmt.Sprint(-item.Score),
356         }
357         if pItem.InsertText == "" {
358                 pItem.InsertText = pItem.Label
359         }
360         return pItem
361 }
362
363 func FilterBuiltins(src span.Span, items []protocol.CompletionItem) []protocol.CompletionItem {
364         var (
365                 got          []protocol.CompletionItem
366                 wantBuiltins = strings.Contains(string(src.URI()), "builtins")
367                 wantKeywords = strings.Contains(string(src.URI()), "keywords")
368         )
369         for _, item := range items {
370                 if !wantBuiltins && isBuiltin(item.Label, item.Detail, item.Kind) {
371                         continue
372                 }
373
374                 if !wantKeywords && token.Lookup(item.Label).IsKeyword() {
375                         continue
376                 }
377
378                 got = append(got, item)
379         }
380         return got
381 }
382
383 func isBuiltin(label, detail string, kind protocol.CompletionItemKind) bool {
384         if detail == "" && kind == protocol.ClassCompletion {
385                 return true
386         }
387         // Remaining builtin constants, variables, interfaces, and functions.
388         trimmed := label
389         if i := strings.Index(trimmed, "("); i >= 0 {
390                 trimmed = trimmed[:i]
391         }
392         switch trimmed {
393         case "append", "cap", "close", "complex", "copy", "delete",
394                 "error", "false", "imag", "iota", "len", "make", "new",
395                 "nil", "panic", "print", "println", "real", "recover", "true":
396                 return true
397         }
398         return false
399 }
400
401 func CheckCompletionOrder(want, got []protocol.CompletionItem, strictScores bool) string {
402         var (
403                 matchedIdxs []int
404                 lastGotIdx  int
405                 lastGotSort float64
406                 inOrder     = true
407                 errorMsg    = "completions out of order"
408         )
409         for _, w := range want {
410                 var found bool
411                 for i, g := range got {
412                         if w.Label == g.Label && w.Detail == g.Detail && w.Kind == g.Kind {
413                                 matchedIdxs = append(matchedIdxs, i)
414                                 found = true
415
416                                 if i < lastGotIdx {
417                                         inOrder = false
418                                 }
419                                 lastGotIdx = i
420
421                                 sort, _ := strconv.ParseFloat(g.SortText, 64)
422                                 if strictScores && len(matchedIdxs) > 1 && sort <= lastGotSort {
423                                         inOrder = false
424                                         errorMsg = "candidate scores not strictly decreasing"
425                                 }
426                                 lastGotSort = sort
427
428                                 break
429                         }
430                 }
431                 if !found {
432                         return summarizeCompletionItems(-1, []protocol.CompletionItem{w}, got, "didn't find expected completion")
433                 }
434         }
435
436         sort.Ints(matchedIdxs)
437         matched := make([]protocol.CompletionItem, 0, len(matchedIdxs))
438         for _, idx := range matchedIdxs {
439                 matched = append(matched, got[idx])
440         }
441
442         if !inOrder {
443                 return summarizeCompletionItems(-1, want, matched, errorMsg)
444         }
445
446         return ""
447 }
448
449 func DiffSnippets(want string, got *protocol.CompletionItem) string {
450         if want == "" {
451                 if got != nil {
452                         x := got.TextEdit
453                         return fmt.Sprintf("expected no snippet but got %s", x.NewText)
454                 }
455         } else {
456                 if got == nil {
457                         return fmt.Sprintf("couldn't find completion matching %q", want)
458                 }
459                 x := got.TextEdit
460                 if want != x.NewText {
461                         return fmt.Sprintf("expected snippet %q, got %q", want, x.NewText)
462                 }
463         }
464         return ""
465 }
466
467 func FindItem(list []protocol.CompletionItem, want completion.CompletionItem) *protocol.CompletionItem {
468         for _, item := range list {
469                 if item.Label == want.Label {
470                         return &item
471                 }
472         }
473         return nil
474 }
475
476 // DiffCompletionItems prints the diff between expected and actual completion
477 // test results.
478 func DiffCompletionItems(want, got []protocol.CompletionItem) string {
479         if len(got) != len(want) {
480                 return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want))
481         }
482         for i, w := range want {
483                 g := got[i]
484                 if w.Label != g.Label {
485                         return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label)
486                 }
487                 if w.Detail != g.Detail {
488                         return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail)
489                 }
490                 if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") {
491                         if w.Documentation != g.Documentation {
492                                 return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation)
493                         }
494                 }
495                 if w.Kind != g.Kind {
496                         return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, w.Kind)
497                 }
498         }
499         return ""
500 }
501
502 func summarizeCompletionItems(i int, want, got []protocol.CompletionItem, reason string, args ...interface{}) string {
503         msg := &bytes.Buffer{}
504         fmt.Fprint(msg, "completion failed")
505         if i >= 0 {
506                 fmt.Fprintf(msg, " at %d", i)
507         }
508         fmt.Fprint(msg, " because of ")
509         fmt.Fprintf(msg, reason, args...)
510         fmt.Fprint(msg, ":\nexpected:\n")
511         for _, d := range want {
512                 fmt.Fprintf(msg, "  %v\n", d)
513         }
514         fmt.Fprintf(msg, "got:\n")
515         for _, d := range got {
516                 fmt.Fprintf(msg, "  %v\n", d)
517         }
518         return msg.String()
519 }
520
521 func EnableAllAnalyzers(view source.View, opts *source.Options) {
522         if opts.Analyses == nil {
523                 opts.Analyses = make(map[string]bool)
524         }
525         for _, a := range opts.DefaultAnalyzers {
526                 if !a.IsEnabled(view) {
527                         opts.Analyses[a.Analyzer.Name] = true
528                 }
529         }
530         for _, a := range opts.TypeErrorAnalyzers {
531                 if !a.IsEnabled(view) {
532                         opts.Analyses[a.Analyzer.Name] = true
533                 }
534         }
535         for _, a := range opts.ConvenienceAnalyzers {
536                 if !a.IsEnabled(view) {
537                         opts.Analyses[a.Analyzer.Name] = true
538                 }
539         }
540         for _, a := range opts.StaticcheckAnalyzers {
541                 if !a.IsEnabled(view) {
542                         opts.Analyses[a.Analyzer.Name] = true
543                 }
544         }
545 }
546
547 func Diff(want, got string) string {
548         if want == got {
549                 return ""
550         }
551         // Add newlines to avoid newline messages in diff.
552         want += "\n"
553         got += "\n"
554         d := myers.ComputeEdits("", want, got)
555         return fmt.Sprintf("%q", diff.ToUnified("want", "got", want, d))
556 }