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