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 5package misc 6 7import ( 8 "path" 9 "strings" 10 "testing" 11 12 . "golang.org/x/tools/internal/lsp/regtest" 13 14 "golang.org/x/tools/internal/lsp/fake" 15 "golang.org/x/tools/internal/lsp/tests" 16) 17 18const internalDefinition = ` 19-- go.mod -- 20module mod.com 21 22go 1.12 23-- main.go -- 24package main 25 26import "fmt" 27 28func main() { 29 fmt.Println(message) 30} 31-- const.go -- 32package main 33 34const message = "Hello World." 35` 36 37func TestGoToInternalDefinition(t *testing.T) { 38 Run(t, internalDefinition, func(t *testing.T, env *Env) { 39 env.OpenFile("main.go") 40 name, pos := env.GoToDefinition("main.go", env.RegexpSearch("main.go", "message")) 41 if want := "const.go"; name != want { 42 t.Errorf("GoToDefinition: got file %q, want %q", name, want) 43 } 44 if want := env.RegexpSearch("const.go", "message"); pos != want { 45 t.Errorf("GoToDefinition: got position %v, want %v", pos, want) 46 } 47 }) 48} 49 50const stdlibDefinition = ` 51-- go.mod -- 52module mod.com 53 54go 1.12 55-- main.go -- 56package main 57 58import "fmt" 59 60func main() { 61 fmt.Printf() 62}` 63 64func TestGoToStdlibDefinition_Issue37045(t *testing.T) { 65 Run(t, stdlibDefinition, func(t *testing.T, env *Env) { 66 env.OpenFile("main.go") 67 name, pos := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `fmt.(Printf)`)) 68 if got, want := path.Base(name), "print.go"; got != want { 69 t.Errorf("GoToDefinition: got file %q, want %q", name, want) 70 } 71 72 // Test that we can jump to definition from outside our workspace. 73 // See golang.org/issues/37045. 74 newName, newPos := env.GoToDefinition(name, pos) 75 if newName != name { 76 t.Errorf("GoToDefinition is not idempotent: got %q, want %q", newName, name) 77 } 78 if newPos != pos { 79 t.Errorf("GoToDefinition is not idempotent: got %v, want %v", newPos, pos) 80 } 81 }) 82} 83 84func TestUnexportedStdlib_Issue40809(t *testing.T) { 85 Run(t, stdlibDefinition, func(t *testing.T, env *Env) { 86 env.OpenFile("main.go") 87 name, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `fmt.(Printf)`)) 88 env.OpenFile(name) 89 90 pos := env.RegexpSearch(name, `:=\s*(newPrinter)\(\)`) 91 92 // Check that we can find references on a reference 93 refs := env.References(name, pos) 94 if len(refs) < 5 { 95 t.Errorf("expected 5+ references to newPrinter, found: %#v", refs) 96 } 97 98 name, pos = env.GoToDefinition(name, pos) 99 content, _ := env.Hover(name, pos) 100 if !strings.Contains(content.Value, "newPrinter") { 101 t.Fatal("definition of newPrinter went to the incorrect place") 102 } 103 // And on the definition too. 104 refs = env.References(name, pos) 105 if len(refs) < 5 { 106 t.Errorf("expected 5+ references to newPrinter, found: %#v", refs) 107 } 108 }) 109} 110 111// Test the hover on an error's Error function. 112// This can't be done via the marker tests because Error is a builtin. 113func TestHoverOnError(t *testing.T) { 114 const mod = ` 115-- go.mod -- 116module mod.com 117 118go 1.12 119-- main.go -- 120package main 121 122func main() { 123 var err error 124 err.Error() 125}` 126 Run(t, mod, func(t *testing.T, env *Env) { 127 env.OpenFile("main.go") 128 content, _ := env.Hover("main.go", env.RegexpSearch("main.go", "Error")) 129 if content == nil { 130 t.Fatalf("nil hover content for Error") 131 } 132 want := "```go\nfunc (error).Error() string\n```" 133 if content.Value != want { 134 t.Fatalf("hover failed:\n%s", tests.Diff(t, want, content.Value)) 135 } 136 }) 137} 138 139func TestImportShortcut(t *testing.T) { 140 const mod = ` 141-- go.mod -- 142module mod.com 143 144go 1.12 145-- main.go -- 146package main 147 148import "fmt" 149 150func main() {} 151` 152 for _, tt := range []struct { 153 wantLinks int 154 wantDef bool 155 importShortcut string 156 }{ 157 {1, false, "Link"}, 158 {0, true, "Definition"}, 159 {1, true, "Both"}, 160 } { 161 t.Run(tt.importShortcut, func(t *testing.T) { 162 WithOptions( 163 EditorConfig{ 164 ImportShortcut: tt.importShortcut, 165 }, 166 ).Run(t, mod, func(t *testing.T, env *Env) { 167 env.OpenFile("main.go") 168 file, pos := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `"fmt"`)) 169 if !tt.wantDef && (file != "" || pos != (fake.Pos{})) { 170 t.Fatalf("expected no definition, got one: %s:%v", file, pos) 171 } else if tt.wantDef && file == "" && pos == (fake.Pos{}) { 172 t.Fatalf("expected definition, got none") 173 } 174 links := env.DocumentLink("main.go") 175 if len(links) != tt.wantLinks { 176 t.Fatalf("expected %v links, got %v", tt.wantLinks, len(links)) 177 } 178 }) 179 }) 180 } 181} 182 183func TestGoToTypeDefinition_Issue38589(t *testing.T) { 184 const mod = ` 185-- go.mod -- 186module mod.com 187 188go 1.12 189-- main.go -- 190package main 191 192type Int int 193 194type Struct struct{} 195 196func F1() {} 197func F2() (int, error) { return 0, nil } 198func F3() (**Struct, bool, *Int, error) { return nil, false, nil, nil } 199func F4() (**Struct, bool, *float64, error) { return nil, false, nil, nil } 200 201func main() {} 202` 203 204 for _, tt := range []struct { 205 re string 206 wantError bool 207 wantTypeRe string 208 }{ 209 {re: `F1`, wantError: true}, 210 {re: `F2`, wantError: true}, 211 {re: `F3`, wantError: true}, 212 {re: `F4`, wantError: false, wantTypeRe: `type (Struct)`}, 213 } { 214 t.Run(tt.re, func(t *testing.T) { 215 Run(t, mod, func(t *testing.T, env *Env) { 216 env.OpenFile("main.go") 217 218 _, pos, err := env.Editor.GoToTypeDefinition(env.Ctx, "main.go", env.RegexpSearch("main.go", tt.re)) 219 if tt.wantError { 220 if err == nil { 221 t.Fatal("expected error, got nil") 222 } 223 return 224 } 225 if err != nil { 226 t.Fatalf("expected nil error, got %s", err) 227 } 228 229 typePos := env.RegexpSearch("main.go", tt.wantTypeRe) 230 if pos != typePos { 231 t.Errorf("invalid pos: want %+v, got %+v", typePos, pos) 232 } 233 }) 234 }) 235 } 236} 237