golang.org/x/tools/gopls@v0.15.3/internal/test/integration/misc/hover_test.go (about)

     1  // Copyright 2021 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 misc
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  	"testing"
    11  
    12  	"golang.org/x/tools/gopls/internal/protocol"
    13  	. "golang.org/x/tools/gopls/internal/test/integration"
    14  	"golang.org/x/tools/gopls/internal/test/integration/fake"
    15  	"golang.org/x/tools/internal/testenv"
    16  )
    17  
    18  func TestHoverUnexported(t *testing.T) {
    19  	const proxy = `
    20  -- golang.org/x/structs@v1.0.0/go.mod --
    21  module golang.org/x/structs
    22  
    23  go 1.12
    24  
    25  -- golang.org/x/structs@v1.0.0/types.go --
    26  package structs
    27  
    28  type Mixed struct {
    29  	// Exported comment
    30  	Exported   int
    31  	unexported string
    32  }
    33  
    34  func printMixed(m Mixed) {
    35  	println(m)
    36  }
    37  `
    38  	const mod = `
    39  -- go.mod --
    40  module mod.com
    41  
    42  go 1.12
    43  
    44  require golang.org/x/structs v1.0.0
    45  -- go.sum --
    46  golang.org/x/structs v1.0.0 h1:Ito/a7hBYZaNKShFrZKjfBA/SIPvmBrcPCBWPx5QeKk=
    47  golang.org/x/structs v1.0.0/go.mod h1:47gkSIdo5AaQaWJS0upVORsxfEr1LL1MWv9dmYF3iq4=
    48  -- main.go --
    49  package main
    50  
    51  import "golang.org/x/structs"
    52  
    53  func main() {
    54  	var m structs.Mixed
    55  	_ = m.Exported
    56  }
    57  `
    58  
    59  	// TODO: use a nested workspace folder here.
    60  	WithOptions(
    61  		ProxyFiles(proxy),
    62  	).Run(t, mod, func(t *testing.T, env *Env) {
    63  		env.OpenFile("main.go")
    64  		mixedLoc := env.RegexpSearch("main.go", "Mixed")
    65  		got, _ := env.Hover(mixedLoc)
    66  		if !strings.Contains(got.Value, "unexported") {
    67  			t.Errorf("Workspace hover: missing expected field 'unexported'. Got:\n%q", got.Value)
    68  		}
    69  
    70  		cacheLoc := env.GoToDefinition(mixedLoc)
    71  		cacheFile := env.Sandbox.Workdir.URIToPath(cacheLoc.URI)
    72  		argLoc := env.RegexpSearch(cacheFile, "printMixed.*(Mixed)")
    73  		got, _ = env.Hover(argLoc)
    74  		if !strings.Contains(got.Value, "unexported") {
    75  			t.Errorf("Non-workspace hover: missing expected field 'unexported'. Got:\n%q", got.Value)
    76  		}
    77  
    78  		exportedFieldLoc := env.RegexpSearch("main.go", "Exported")
    79  		got, _ = env.Hover(exportedFieldLoc)
    80  		if !strings.Contains(got.Value, "comment") {
    81  			t.Errorf("Workspace hover: missing comment for field 'Exported'. Got:\n%q", got.Value)
    82  		}
    83  	})
    84  }
    85  
    86  func TestHoverIntLiteral(t *testing.T) {
    87  	const source = `
    88  -- main.go --
    89  package main
    90  
    91  var (
    92  	bigBin = 0b1001001
    93  )
    94  
    95  var hex = 0xe34e
    96  
    97  func main() {
    98  }
    99  `
   100  	Run(t, source, func(t *testing.T, env *Env) {
   101  		env.OpenFile("main.go")
   102  		hexExpected := "58190"
   103  		got, _ := env.Hover(env.RegexpSearch("main.go", "0xe"))
   104  		if got != nil && !strings.Contains(got.Value, hexExpected) {
   105  			t.Errorf("Hover: missing expected field '%s'. Got:\n%q", hexExpected, got.Value)
   106  		}
   107  
   108  		binExpected := "73"
   109  		got, _ = env.Hover(env.RegexpSearch("main.go", "0b1"))
   110  		if got != nil && !strings.Contains(got.Value, binExpected) {
   111  			t.Errorf("Hover: missing expected field '%s'. Got:\n%q", binExpected, got.Value)
   112  		}
   113  	})
   114  }
   115  
   116  // Tests that hovering does not trigger the panic in golang/go#48249.
   117  func TestPanicInHoverBrokenCode(t *testing.T) {
   118  	// Note: this test can not be expressed as a marker test, as it must use
   119  	// content without a trailing newline.
   120  	const source = `
   121  -- main.go --
   122  package main
   123  
   124  type Example struct`
   125  	Run(t, source, func(t *testing.T, env *Env) {
   126  		env.OpenFile("main.go")
   127  		env.Editor.Hover(env.Ctx, env.RegexpSearch("main.go", "Example"))
   128  	})
   129  }
   130  
   131  func TestHoverRune_48492(t *testing.T) {
   132  	const files = `
   133  -- go.mod --
   134  module mod.com
   135  
   136  go 1.18
   137  -- main.go --
   138  package main
   139  `
   140  	Run(t, files, func(t *testing.T, env *Env) {
   141  		env.OpenFile("main.go")
   142  		env.EditBuffer("main.go", fake.NewEdit(0, 0, 1, 0, "package main\nfunc main() {\nconst x = `\nfoo\n`\n}"))
   143  		env.Editor.Hover(env.Ctx, env.RegexpSearch("main.go", "foo"))
   144  	})
   145  }
   146  
   147  func TestHoverImport(t *testing.T) {
   148  	const packageDoc1 = "Package lib1 hover documentation"
   149  	const packageDoc2 = "Package lib2 hover documentation"
   150  	tests := []struct {
   151  		hoverPackage string
   152  		want         string
   153  		wantError    bool
   154  	}{
   155  		{
   156  			"mod.com/lib1",
   157  			packageDoc1,
   158  			false,
   159  		},
   160  		{
   161  			"mod.com/lib2",
   162  			packageDoc2,
   163  			false,
   164  		},
   165  		{
   166  			"mod.com/lib3",
   167  			"",
   168  			false,
   169  		},
   170  		{
   171  			"mod.com/lib4",
   172  			"",
   173  			true,
   174  		},
   175  	}
   176  	source := fmt.Sprintf(`
   177  -- go.mod --
   178  module mod.com
   179  
   180  go 1.12
   181  -- lib1/a.go --
   182  // %s
   183  package lib1
   184  
   185  const C = 1
   186  
   187  -- lib1/b.go --
   188  package lib1
   189  
   190  const D = 1
   191  
   192  -- lib2/a.go --
   193  // %s
   194  package lib2
   195  
   196  const E = 1
   197  
   198  -- lib3/a.go --
   199  package lib3
   200  
   201  const F = 1
   202  
   203  -- main.go --
   204  package main
   205  
   206  import (
   207  	"mod.com/lib1"
   208  	"mod.com/lib2"
   209  	"mod.com/lib3"
   210  	"mod.com/lib4"
   211  )
   212  
   213  func main() {
   214  	println("Hello")
   215  }
   216  	`, packageDoc1, packageDoc2)
   217  	Run(t, source, func(t *testing.T, env *Env) {
   218  		env.OpenFile("main.go")
   219  		for _, test := range tests {
   220  			got, _, err := env.Editor.Hover(env.Ctx, env.RegexpSearch("main.go", test.hoverPackage))
   221  			if test.wantError {
   222  				if err == nil {
   223  					t.Errorf("Hover(%q) succeeded unexpectedly", test.hoverPackage)
   224  				}
   225  			} else if !strings.Contains(got.Value, test.want) {
   226  				t.Errorf("Hover(%q): got:\n%q\nwant:\n%q", test.hoverPackage, got.Value, test.want)
   227  			}
   228  		}
   229  	})
   230  }
   231  
   232  // for x/tools/gopls: unhandled named anchor on the hover #57048
   233  func TestHoverTags(t *testing.T) {
   234  	const source = `
   235  -- go.mod --
   236  module mod.com
   237  
   238  go 1.19
   239  
   240  -- lib/a.go --
   241  
   242  // variety of execution modes.
   243  //
   244  // # Test package setup
   245  //
   246  // The regression test package uses a couple of uncommon patterns to reduce
   247  package lib
   248  
   249  -- a.go --
   250  	package main
   251  	import "mod.com/lib"
   252  
   253  	const A = 1
   254  
   255  }
   256  `
   257  	Run(t, source, func(t *testing.T, env *Env) {
   258  		t.Run("tags", func(t *testing.T) {
   259  			env.OpenFile("a.go")
   260  			z := env.RegexpSearch("a.go", "lib")
   261  			t.Logf("%#v", z)
   262  			got, _ := env.Hover(env.RegexpSearch("a.go", "lib"))
   263  			if strings.Contains(got.Value, "{#hdr-") {
   264  				t.Errorf("Hover: got {#hdr- tag:\n%q", got)
   265  			}
   266  		})
   267  	})
   268  }
   269  
   270  // This is a regression test for Go issue #57625.
   271  func TestHoverModMissingModuleStmt(t *testing.T) {
   272  	const source = `
   273  -- go.mod --
   274  go 1.16
   275  `
   276  	Run(t, source, func(t *testing.T, env *Env) {
   277  		env.OpenFile("go.mod")
   278  		env.Hover(env.RegexpSearch("go.mod", "go")) // no panic
   279  	})
   280  }
   281  
   282  func TestHoverCompletionMarkdown(t *testing.T) {
   283  	testenv.NeedsGo1Point(t, 19)
   284  	const source = `
   285  -- go.mod --
   286  module mod.com
   287  go 1.19
   288  -- main.go --
   289  package main
   290  // Just says [hello].
   291  //
   292  // [hello]: https://en.wikipedia.org/wiki/Hello
   293  func Hello() string {
   294  	Hello() //Here
   295      return "hello"
   296  }
   297  `
   298  	Run(t, source, func(t *testing.T, env *Env) {
   299  		// Hover, Completion, and SignatureHelp should all produce markdown
   300  		// check that the markdown for SignatureHelp and Completion are
   301  		// the same, and contained in that for Hover (up to trailing \n)
   302  		env.OpenFile("main.go")
   303  		loc := env.RegexpSearch("main.go", "func (Hello)")
   304  		hover, _ := env.Hover(loc)
   305  		hoverContent := hover.Value
   306  
   307  		loc = env.RegexpSearch("main.go", "//Here")
   308  		loc.Range.Start.Character -= 3 // Hello(_) //Here
   309  		completions := env.Completion(loc)
   310  		signatures := env.SignatureHelp(loc)
   311  
   312  		if len(completions.Items) != 1 {
   313  			t.Errorf("got %d completions, expected 1", len(completions.Items))
   314  		}
   315  		if len(signatures.Signatures) != 1 {
   316  			t.Errorf("got %d signatures, expected 1", len(signatures.Signatures))
   317  		}
   318  		item := completions.Items[0].Documentation.Value
   319  		var itemContent string
   320  		if x, ok := item.(protocol.MarkupContent); !ok || x.Kind != protocol.Markdown {
   321  			t.Fatalf("%#v is not markdown", item)
   322  		} else {
   323  			itemContent = strings.Trim(x.Value, "\n")
   324  		}
   325  		sig := signatures.Signatures[0].Documentation.Value
   326  		var sigContent string
   327  		if x, ok := sig.(protocol.MarkupContent); !ok || x.Kind != protocol.Markdown {
   328  			t.Fatalf("%#v is not markdown", item)
   329  		} else {
   330  			sigContent = x.Value
   331  		}
   332  		if itemContent != sigContent {
   333  			t.Errorf("item:%q not sig:%q", itemContent, sigContent)
   334  		}
   335  		if !strings.Contains(hoverContent, itemContent) {
   336  			t.Errorf("hover:%q does not containt sig;%q", hoverContent, sigContent)
   337  		}
   338  	})
   339  }
   340  
   341  // Test that the generated markdown contains links for Go references.
   342  // https://github.com/golang/go/issues/58352
   343  func TestHoverLinks(t *testing.T) {
   344  	testenv.NeedsGo1Point(t, 19)
   345  	const input = `
   346  -- go.mod --
   347  go 1.19
   348  module mod.com
   349  -- main.go --
   350  package main
   351  // [fmt]
   352  var A int
   353  // [fmt.Println]
   354  var B int
   355  // [golang.org/x/tools/go/packages.Package.String]
   356  var C int
   357  `
   358  	var tests = []struct {
   359  		pat string
   360  		ans string
   361  	}{
   362  		{"A", "fmt"},
   363  		{"B", "fmt#Println"},
   364  		{"C", "golang.org/x/tools/go/packages#Package.String"},
   365  	}
   366  	for _, test := range tests {
   367  		Run(t, input, func(t *testing.T, env *Env) {
   368  			env.OpenFile("main.go")
   369  			loc := env.RegexpSearch("main.go", test.pat)
   370  			hover, _ := env.Hover(loc)
   371  			hoverContent := hover.Value
   372  			want := fmt.Sprintf("%s/%s", "https://pkg.go.dev", test.ans)
   373  			if !strings.Contains(hoverContent, want) {
   374  				t.Errorf("hover:%q does not contain link %q", hoverContent, want)
   375  			}
   376  		})
   377  	}
   378  }
   379  
   380  const linknameHover = `
   381  -- go.mod --
   382  module mod.com
   383  
   384  -- upper/upper.go --
   385  package upper
   386  
   387  import (
   388  	_ "unsafe"
   389  	_ "mod.com/lower"
   390  )
   391  
   392  //go:linkname foo mod.com/lower.bar
   393  func foo() string
   394  
   395  -- lower/lower.go --
   396  package lower
   397  
   398  // bar does foo.
   399  func bar() string {
   400  	return "foo by bar"
   401  }`
   402  
   403  func TestHoverLinknameDirective(t *testing.T) {
   404  	Run(t, linknameHover, func(t *testing.T, env *Env) {
   405  		// Jump from directives 2nd arg.
   406  		env.OpenFile("upper/upper.go")
   407  		from := env.RegexpSearch("upper/upper.go", `lower.bar`)
   408  
   409  		hover, _ := env.Hover(from)
   410  		content := hover.Value
   411  
   412  		expect := "bar does foo"
   413  		if !strings.Contains(content, expect) {
   414  			t.Errorf("hover: %q does not contain: %q", content, expect)
   415  		}
   416  	})
   417  }
   418  
   419  func TestHoverGoWork_Issue60821(t *testing.T) {
   420  	const files = `
   421  -- go.work --
   422  go 1.19
   423  
   424  use (
   425  	moda
   426  	modb
   427  )
   428  -- moda/go.mod --
   429  
   430  `
   431  	Run(t, files, func(t *testing.T, env *Env) {
   432  		env.OpenFile("go.work")
   433  		// Neither of the requests below should crash gopls.
   434  		_, _, _ = env.Editor.Hover(env.Ctx, env.RegexpSearch("go.work", "moda"))
   435  		_, _, _ = env.Editor.Hover(env.Ctx, env.RegexpSearch("go.work", "modb"))
   436  	})
   437  }
   438  
   439  const embedHover = `
   440  -- go.mod --
   441  module mod.com
   442  go 1.19
   443  -- main.go --
   444  package main
   445  
   446  import "embed"
   447  
   448  //go:embed *.txt
   449  var foo embed.FS
   450  
   451  func main() {
   452  }
   453  -- foo.txt --
   454  FOO
   455  -- bar.txt --
   456  BAR
   457  -- baz.txt --
   458  BAZ
   459  -- other.sql --
   460  SKIPPED
   461  -- dir.txt/skip.txt --
   462  SKIPPED
   463  `
   464  
   465  func TestHoverEmbedDirective(t *testing.T) {
   466  	testenv.NeedsGo1Point(t, 19)
   467  	Run(t, embedHover, func(t *testing.T, env *Env) {
   468  		env.OpenFile("main.go")
   469  		from := env.RegexpSearch("main.go", `\*.txt`)
   470  
   471  		got, _ := env.Hover(from)
   472  		if got == nil {
   473  			t.Fatalf("hover over //go:embed arg not found")
   474  		}
   475  		content := got.Value
   476  
   477  		wants := []string{"foo.txt", "bar.txt", "baz.txt"}
   478  		for _, want := range wants {
   479  			if !strings.Contains(content, want) {
   480  				t.Errorf("hover: %q does not contain: %q", content, want)
   481  			}
   482  		}
   483  
   484  		// A directory should never be matched, even if it happens to have a matching name.
   485  		// Content in subdirectories should not match on only one asterisk.
   486  		skips := []string{"other.sql", "dir.txt", "skip.txt"}
   487  		for _, skip := range skips {
   488  			if strings.Contains(content, skip) {
   489  				t.Errorf("hover: %q should not contain: %q", content, skip)
   490  			}
   491  		}
   492  	})
   493  }
   494  
   495  func TestHoverBrokenImport_Issue60592(t *testing.T) {
   496  	const files = `
   497  -- go.mod --
   498  module testdata
   499  go 1.18
   500  
   501  -- p.go --
   502  package main
   503  
   504  import foo "a"
   505  
   506  func _() {
   507  	foo.Print()
   508  }
   509  
   510  `
   511  	Run(t, files, func(t *testing.T, env *Env) {
   512  		env.OpenFile("p.go")
   513  		// This request should not crash gopls.
   514  		_, _, _ = env.Editor.Hover(env.Ctx, env.RegexpSearch("p.go", "foo[.]"))
   515  	})
   516  }