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