golang.org/x/tools/gopls@v0.15.3/internal/test/integration/misc/definition_test.go (about) 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 misc 6 7 import ( 8 "os" 9 "path" 10 "path/filepath" 11 "strings" 12 "testing" 13 14 "golang.org/x/tools/gopls/internal/protocol" 15 "golang.org/x/tools/gopls/internal/test/compare" 16 . "golang.org/x/tools/gopls/internal/test/integration" 17 ) 18 19 const internalDefinition = ` 20 -- go.mod -- 21 module mod.com 22 23 go 1.12 24 -- main.go -- 25 package main 26 27 import "fmt" 28 29 func main() { 30 fmt.Println(message) 31 } 32 -- const.go -- 33 package main 34 35 const message = "Hello World." 36 ` 37 38 func TestGoToInternalDefinition(t *testing.T) { 39 Run(t, internalDefinition, func(t *testing.T, env *Env) { 40 env.OpenFile("main.go") 41 loc := env.GoToDefinition(env.RegexpSearch("main.go", "message")) 42 name := env.Sandbox.Workdir.URIToPath(loc.URI) 43 if want := "const.go"; name != want { 44 t.Errorf("GoToDefinition: got file %q, want %q", name, want) 45 } 46 if want := env.RegexpSearch("const.go", "message"); loc != want { 47 t.Errorf("GoToDefinition: got location %v, want %v", loc, want) 48 } 49 }) 50 } 51 52 const linknameDefinition = ` 53 -- go.mod -- 54 module mod.com 55 56 -- upper/upper.go -- 57 package upper 58 59 import ( 60 _ "unsafe" 61 62 _ "mod.com/middle" 63 ) 64 65 //go:linkname foo mod.com/lower.bar 66 func foo() string 67 68 -- middle/middle.go -- 69 package middle 70 71 import ( 72 _ "mod.com/lower" 73 ) 74 75 -- lower/lower.s -- 76 77 -- lower/lower.go -- 78 package lower 79 80 func bar() string { 81 return "bar as foo" 82 }` 83 84 func TestGoToLinknameDefinition(t *testing.T) { 85 Run(t, linknameDefinition, func(t *testing.T, env *Env) { 86 env.OpenFile("upper/upper.go") 87 88 // Jump from directives 2nd arg. 89 start := env.RegexpSearch("upper/upper.go", `lower.bar`) 90 loc := env.GoToDefinition(start) 91 name := env.Sandbox.Workdir.URIToPath(loc.URI) 92 if want := "lower/lower.go"; name != want { 93 t.Errorf("GoToDefinition: got file %q, want %q", name, want) 94 } 95 if want := env.RegexpSearch("lower/lower.go", `bar`); loc != want { 96 t.Errorf("GoToDefinition: got position %v, want %v", loc, want) 97 } 98 }) 99 } 100 101 const linknameDefinitionReverse = ` 102 -- go.mod -- 103 module mod.com 104 105 -- upper/upper.s -- 106 107 -- upper/upper.go -- 108 package upper 109 110 import ( 111 _ "mod.com/middle" 112 ) 113 114 func foo() string 115 116 -- middle/middle.go -- 117 package middle 118 119 import ( 120 _ "mod.com/lower" 121 ) 122 123 -- lower/lower.go -- 124 package lower 125 126 import _ "unsafe" 127 128 //go:linkname bar mod.com/upper.foo 129 func bar() string { 130 return "bar as foo" 131 }` 132 133 func TestGoToLinknameDefinitionInReverseDep(t *testing.T) { 134 Run(t, linknameDefinitionReverse, func(t *testing.T, env *Env) { 135 env.OpenFile("lower/lower.go") 136 137 // Jump from directives 2nd arg. 138 start := env.RegexpSearch("lower/lower.go", `upper.foo`) 139 loc := env.GoToDefinition(start) 140 name := env.Sandbox.Workdir.URIToPath(loc.URI) 141 if want := "upper/upper.go"; name != want { 142 t.Errorf("GoToDefinition: got file %q, want %q", name, want) 143 } 144 if want := env.RegexpSearch("upper/upper.go", `foo`); loc != want { 145 t.Errorf("GoToDefinition: got position %v, want %v", loc, want) 146 } 147 }) 148 } 149 150 // The linkname directive connects two packages not related in the import graph. 151 const linknameDefinitionDisconnected = ` 152 -- go.mod -- 153 module mod.com 154 155 -- a/a.go -- 156 package a 157 158 import ( 159 _ "unsafe" 160 ) 161 162 //go:linkname foo mod.com/b.bar 163 func foo() string 164 165 -- b/b.go -- 166 package b 167 168 func bar() string { 169 return "bar as foo" 170 }` 171 172 func TestGoToLinknameDefinitionDisconnected(t *testing.T) { 173 Run(t, linknameDefinitionDisconnected, func(t *testing.T, env *Env) { 174 env.OpenFile("a/a.go") 175 176 // Jump from directives 2nd arg. 177 start := env.RegexpSearch("a/a.go", `b.bar`) 178 loc := env.GoToDefinition(start) 179 name := env.Sandbox.Workdir.URIToPath(loc.URI) 180 if want := "b/b.go"; name != want { 181 t.Errorf("GoToDefinition: got file %q, want %q", name, want) 182 } 183 if want := env.RegexpSearch("b/b.go", `bar`); loc != want { 184 t.Errorf("GoToDefinition: got position %v, want %v", loc, want) 185 } 186 }) 187 } 188 189 const stdlibDefinition = ` 190 -- go.mod -- 191 module mod.com 192 193 go 1.12 194 -- main.go -- 195 package main 196 197 import "fmt" 198 199 func main() { 200 fmt.Printf() 201 }` 202 203 func TestGoToStdlibDefinition_Issue37045(t *testing.T) { 204 Run(t, stdlibDefinition, func(t *testing.T, env *Env) { 205 env.OpenFile("main.go") 206 loc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt.(Printf)`)) 207 name := env.Sandbox.Workdir.URIToPath(loc.URI) 208 if got, want := path.Base(name), "print.go"; got != want { 209 t.Errorf("GoToDefinition: got file %q, want %q", name, want) 210 } 211 212 // Test that we can jump to definition from outside our workspace. 213 // See golang.org/issues/37045. 214 newLoc := env.GoToDefinition(loc) 215 newName := env.Sandbox.Workdir.URIToPath(newLoc.URI) 216 if newName != name { 217 t.Errorf("GoToDefinition is not idempotent: got %q, want %q", newName, name) 218 } 219 if newLoc != loc { 220 t.Errorf("GoToDefinition is not idempotent: got %v, want %v", newLoc, loc) 221 } 222 }) 223 } 224 225 func TestUnexportedStdlib_Issue40809(t *testing.T) { 226 Run(t, stdlibDefinition, func(t *testing.T, env *Env) { 227 env.OpenFile("main.go") 228 loc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt.(Printf)`)) 229 name := env.Sandbox.Workdir.URIToPath(loc.URI) 230 231 loc = env.RegexpSearch(name, `:=\s*(newPrinter)\(\)`) 232 233 // Check that we can find references on a reference 234 refs := env.References(loc) 235 if len(refs) < 5 { 236 t.Errorf("expected 5+ references to newPrinter, found: %#v", refs) 237 } 238 239 loc = env.GoToDefinition(loc) 240 content, _ := env.Hover(loc) 241 if !strings.Contains(content.Value, "newPrinter") { 242 t.Fatal("definition of newPrinter went to the incorrect place") 243 } 244 // And on the definition too. 245 refs = env.References(loc) 246 if len(refs) < 5 { 247 t.Errorf("expected 5+ references to newPrinter, found: %#v", refs) 248 } 249 }) 250 } 251 252 // Test the hover on an error's Error function. 253 // This can't be done via the marker tests because Error is a builtin. 254 func TestHoverOnError(t *testing.T) { 255 const mod = ` 256 -- go.mod -- 257 module mod.com 258 259 go 1.12 260 -- main.go -- 261 package main 262 263 func main() { 264 var err error 265 err.Error() 266 }` 267 Run(t, mod, func(t *testing.T, env *Env) { 268 env.OpenFile("main.go") 269 content, _ := env.Hover(env.RegexpSearch("main.go", "Error")) 270 if content == nil { 271 t.Fatalf("nil hover content for Error") 272 } 273 want := "```go\nfunc (error).Error() string\n```" 274 if content.Value != want { 275 t.Fatalf("hover failed:\n%s", compare.Text(want, content.Value)) 276 } 277 }) 278 } 279 280 func TestImportShortcut(t *testing.T) { 281 const mod = ` 282 -- go.mod -- 283 module mod.com 284 285 go 1.12 286 -- main.go -- 287 package main 288 289 import "fmt" 290 291 func main() {} 292 ` 293 for _, tt := range []struct { 294 wantLinks int 295 importShortcut string 296 }{ 297 {1, "Link"}, 298 {0, "Definition"}, 299 {1, "Both"}, 300 } { 301 t.Run(tt.importShortcut, func(t *testing.T) { 302 WithOptions( 303 Settings{"importShortcut": tt.importShortcut}, 304 ).Run(t, mod, func(t *testing.T, env *Env) { 305 env.OpenFile("main.go") 306 loc := env.GoToDefinition(env.RegexpSearch("main.go", `"fmt"`)) 307 if loc == (protocol.Location{}) { 308 t.Fatalf("expected definition, got none") 309 } 310 links := env.DocumentLink("main.go") 311 if len(links) != tt.wantLinks { 312 t.Fatalf("expected %v links, got %v", tt.wantLinks, len(links)) 313 } 314 }) 315 }) 316 } 317 } 318 319 func TestGoToTypeDefinition_Issue38589(t *testing.T) { 320 const mod = ` 321 -- go.mod -- 322 module mod.com 323 324 go 1.12 325 -- main.go -- 326 package main 327 328 type Int int 329 330 type Struct struct{} 331 332 func F1() {} 333 func F2() (int, error) { return 0, nil } 334 func F3() (**Struct, bool, *Int, error) { return nil, false, nil, nil } 335 func F4() (**Struct, bool, *float64, error) { return nil, false, nil, nil } 336 337 func main() {} 338 ` 339 340 for _, tt := range []struct { 341 re string 342 wantError bool 343 wantTypeRe string 344 }{ 345 {re: `F1`, wantError: true}, 346 {re: `F2`, wantError: true}, 347 {re: `F3`, wantError: true}, 348 {re: `F4`, wantError: false, wantTypeRe: `type (Struct)`}, 349 } { 350 t.Run(tt.re, func(t *testing.T) { 351 Run(t, mod, func(t *testing.T, env *Env) { 352 env.OpenFile("main.go") 353 354 loc, err := env.Editor.TypeDefinition(env.Ctx, env.RegexpSearch("main.go", tt.re)) 355 if tt.wantError { 356 if err == nil { 357 t.Fatal("expected error, got nil") 358 } 359 return 360 } 361 if err != nil { 362 t.Fatalf("expected nil error, got %s", err) 363 } 364 365 typeLoc := env.RegexpSearch("main.go", tt.wantTypeRe) 366 if loc != typeLoc { 367 t.Errorf("invalid pos: want %+v, got %+v", typeLoc, loc) 368 } 369 }) 370 }) 371 } 372 } 373 374 func TestGoToTypeDefinition_Issue60544(t *testing.T) { 375 const mod = ` 376 -- go.mod -- 377 module mod.com 378 379 go 1.19 380 -- main.go -- 381 package main 382 383 func F[T comparable]() {} 384 ` 385 386 Run(t, mod, func(t *testing.T, env *Env) { 387 env.OpenFile("main.go") 388 389 _ = env.TypeDefinition(env.RegexpSearch("main.go", "comparable")) // must not panic 390 }) 391 } 392 393 // Test for golang/go#47825. 394 func TestImportTestVariant(t *testing.T) { 395 const mod = ` 396 -- go.mod -- 397 module mod.com 398 399 go 1.12 400 -- client/test/role.go -- 401 package test 402 403 import _ "mod.com/client" 404 405 type RoleSetup struct{} 406 -- client/client_role_test.go -- 407 package client_test 408 409 import ( 410 "testing" 411 _ "mod.com/client" 412 ctest "mod.com/client/test" 413 ) 414 415 func TestClient(t *testing.T) { 416 _ = ctest.RoleSetup{} 417 } 418 -- client/client_test.go -- 419 package client 420 421 import "testing" 422 423 func TestClient(t *testing.T) {} 424 -- client.go -- 425 package client 426 ` 427 Run(t, mod, func(t *testing.T, env *Env) { 428 env.OpenFile("client/client_role_test.go") 429 env.GoToDefinition(env.RegexpSearch("client/client_role_test.go", "RoleSetup")) 430 }) 431 } 432 433 // This test exercises a crashing pattern from golang/go#49223. 434 func TestGoToCrashingDefinition_Issue49223(t *testing.T) { 435 Run(t, "", func(t *testing.T, env *Env) { 436 params := &protocol.DefinitionParams{} 437 params.TextDocument.URI = protocol.DocumentURI("fugitive%3A///Users/user/src/mm/ems/.git//0/pkg/domain/treasury/provider.go") 438 params.Position.Character = 18 439 params.Position.Line = 0 440 env.Editor.Server.Definition(env.Ctx, params) 441 }) 442 } 443 444 // TestVendoringInvalidatesMetadata ensures that gopls uses the 445 // correct metadata even after an external 'go mod vendor' command 446 // causes packages to move; see issue #55995. 447 // See also TestImplementationsInVendor, which tests the same fix. 448 func TestVendoringInvalidatesMetadata(t *testing.T) { 449 t.Skip("golang/go#56169: file watching does not capture vendor dirs") 450 451 const proxy = ` 452 -- other.com/b@v1.0.0/go.mod -- 453 module other.com/b 454 go 1.14 455 456 -- other.com/b@v1.0.0/b.go -- 457 package b 458 const K = 0 459 ` 460 const src = ` 461 -- go.mod -- 462 module example.com/a 463 go 1.14 464 require other.com/b v1.0.0 465 466 -- go.sum -- 467 other.com/b v1.0.0 h1:1wb3PMGdet5ojzrKl+0iNksRLnOM9Jw+7amBNqmYwqk= 468 other.com/b v1.0.0/go.mod h1:TgHQFucl04oGT+vrUm/liAzukYHNxCwKNkQZEyn3m9g= 469 470 -- a.go -- 471 package a 472 import "other.com/b" 473 const _ = b.K 474 475 ` 476 WithOptions( 477 ProxyFiles(proxy), 478 Modes(Default), // fails in 'experimental' mode 479 ).Run(t, src, func(t *testing.T, env *Env) { 480 // Enable to debug go.sum mismatch, which may appear as 481 // "module lookup disabled by GOPROXY=off", confusingly. 482 if false { 483 env.DumpGoSum(".") 484 } 485 486 env.OpenFile("a.go") 487 refLoc := env.RegexpSearch("a.go", "K") // find "b.K" reference 488 489 // Initially, b.K is defined in the module cache. 490 gotLoc := env.GoToDefinition(refLoc) 491 gotFile := env.Sandbox.Workdir.URIToPath(gotLoc.URI) 492 wantCache := filepath.ToSlash(env.Sandbox.GOPATH()) + "/pkg/mod/other.com/b@v1.0.0/b.go" 493 if gotFile != wantCache { 494 t.Errorf("GoToDefinition, before: got file %q, want %q", gotFile, wantCache) 495 } 496 497 // Run 'go mod vendor' outside the editor. 498 env.RunGoCommand("mod", "vendor") 499 500 // Synchronize changes to watched files. 501 env.Await(env.DoneWithChangeWatchedFiles()) 502 503 // Now, b.K is defined in the vendor tree. 504 gotLoc = env.GoToDefinition(refLoc) 505 wantVendor := "vendor/other.com/b/b.go" 506 if gotFile != wantVendor { 507 t.Errorf("GoToDefinition, after go mod vendor: got file %q, want %q", gotFile, wantVendor) 508 } 509 510 // Delete the vendor tree. 511 if err := os.RemoveAll(env.Sandbox.Workdir.AbsPath("vendor")); err != nil { 512 t.Fatal(err) 513 } 514 // Notify the server of the deletion. 515 if err := env.Sandbox.Workdir.CheckForFileChanges(env.Ctx); err != nil { 516 t.Fatal(err) 517 } 518 519 // Synchronize again. 520 env.Await(env.DoneWithChangeWatchedFiles()) 521 522 // b.K is once again defined in the module cache. 523 gotLoc = env.GoToDefinition(gotLoc) 524 gotFile = env.Sandbox.Workdir.URIToPath(gotLoc.URI) 525 if gotFile != wantCache { 526 t.Errorf("GoToDefinition, after rm -rf vendor: got file %q, want %q", gotFile, wantCache) 527 } 528 }) 529 } 530 531 const embedDefinition = ` 532 -- go.mod -- 533 module mod.com 534 535 -- main.go -- 536 package main 537 538 import ( 539 "embed" 540 ) 541 542 //go:embed *.txt 543 var foo embed.FS 544 545 func main() {} 546 547 -- skip.sql -- 548 SKIP 549 550 -- foo.txt -- 551 FOO 552 553 -- skip.bat -- 554 SKIP 555 ` 556 557 func TestGoToEmbedDefinition(t *testing.T) { 558 Run(t, embedDefinition, func(t *testing.T, env *Env) { 559 env.OpenFile("main.go") 560 561 start := env.RegexpSearch("main.go", `\*.txt`) 562 loc := env.GoToDefinition(start) 563 564 name := env.Sandbox.Workdir.URIToPath(loc.URI) 565 if want := "foo.txt"; name != want { 566 t.Errorf("GoToDefinition: got file %q, want %q", name, want) 567 } 568 }) 569 } 570 571 func TestDefinitionOfErrorErrorMethod(t *testing.T) { 572 const src = `Regression test for a panic in definition of error.Error (of course). 573 golang/go#64086 574 575 -- go.mod -- 576 module mod.com 577 go 1.18 578 579 -- a.go -- 580 package a 581 582 func _(err error) { 583 _ = err.Error() 584 } 585 586 ` 587 Run(t, src, func(t *testing.T, env *Env) { 588 env.OpenFile("a.go") 589 590 start := env.RegexpSearch("a.go", `Error`) 591 loc := env.GoToDefinition(start) 592 593 if !strings.HasSuffix(string(loc.URI), "builtin.go") { 594 t.Errorf("GoToDefinition(err.Error) = %#v, want builtin.go", loc) 595 } 596 }) 597 }