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 }