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  }