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.
12 . "golang.org/x/tools/gopls/internal/regtest"
14 "golang.org/x/tools/internal/lsp/fake"
15 "golang.org/x/tools/internal/lsp/protocol"
16 "golang.org/x/tools/internal/testenv"
19 func TestMain(m *testing.M) {
24 -- example.com@v1.2.3/go.mod --
28 -- example.com@v1.2.3/blah/blah.go --
32 -- random.org@v1.2.3/go.mod --
36 -- random.org@v1.2.3/blah/blah.go --
42 func TestPackageCompletion(t *testing.T) {
43 testenv.NeedsGo1Point(t, 14)
56 -- fruits/testfile.go --
60 this is a multiline comment
67 -- fruits/testfile2.go --
70 -- fruits/testfile3.go --
75 testfile5 = "/*a comment*/ "
76 testfile6 = "/*a comment*/\n"
78 for _, tc := range []struct {
87 name: "package completion at valid position",
88 filename: "fruits/testfile.go",
89 triggerRegexp: "\n()",
90 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"},
94 name: "package completion in a comment",
95 filename: "fruits/testfile.go",
96 triggerRegexp: "th(i)s",
100 name: "package completion in a multiline comment",
101 filename: "fruits/testfile.go",
102 triggerRegexp: `\/\*\n()`,
106 name: "package completion at invalid position",
107 filename: "fruits/testfile.go",
108 triggerRegexp: "import \"fmt\"\n()",
112 name: "package completion after keyword 'package'",
113 filename: "fruits/testfile2.go",
114 triggerRegexp: "package()",
115 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"},
116 editRegexp: "package\n",
119 name: "package completion with 'pac' prefix",
120 filename: "fruits/testfile3.go",
121 triggerRegexp: "pac()",
122 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"},
126 name: "package completion for empty file",
127 filename: "fruits/testfile4.go",
130 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"},
134 name: "package completion without terminal newline",
135 filename: "fruits/testfile5.go",
136 triggerRegexp: `\*\/ ()`,
138 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"},
139 editRegexp: `\*\/ ()`,
142 name: "package completion on terminal newline",
143 filename: "fruits/testfile6.go",
144 triggerRegexp: `\*\/\n()`,
146 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"},
147 editRegexp: `\*\/\n()`,
150 t.Run(tc.name, func(t *testing.T) {
151 Run(t, files, func(t *testing.T, env *Env) {
152 if tc.content != nil {
153 env.WriteWorkspaceFile(tc.filename, *tc.content)
155 env.DoneWithChangeWatchedFiles(),
158 env.OpenFile(tc.filename)
159 completions := env.Completion(tc.filename, env.RegexpSearch(tc.filename, tc.triggerRegexp))
161 // Check that the completion item suggestions are in the range
163 lineCount := len(strings.Split(env.Editor.BufferText(tc.filename), "\n"))
164 for _, item := range completions.Items {
165 if start := int(item.TextEdit.Range.Start.Line); start >= lineCount {
166 t.Fatalf("unexpected text edit range start line number: got %d, want less than %d", start, lineCount)
168 if end := int(item.TextEdit.Range.End.Line); end >= lineCount {
169 t.Fatalf("unexpected text edit range end line number: got %d, want less than %d", end, lineCount)
174 start, end := env.RegexpRange(tc.filename, tc.editRegexp)
175 expectedRng := protocol.Range{
176 Start: fake.Pos.ToProtocolPosition(start),
177 End: fake.Pos.ToProtocolPosition(end),
179 for _, item := range completions.Items {
180 gotRng := item.TextEdit.Range
181 if expectedRng != gotRng {
182 t.Errorf("unexpected completion range for completion item %s: got %v, want %v",
183 item.Label, gotRng, expectedRng)
188 diff := compareCompletionResults(tc.want, completions.Items)
197 func TestPackageNameCompletion(t *testing.T) {
207 want := []string{"ma", "ma_test", "main", "math", "math_test"}
208 Run(t, files, func(t *testing.T, env *Env) {
209 env.OpenFile("math/add.go")
210 completions := env.Completion("math/add.go", fake.Pos{
215 diff := compareCompletionResults(want, completions.Items)
222 func compareCompletionResults(want []string, gotItems []protocol.CompletionItem) string {
223 if len(gotItems) != len(want) {
224 return fmt.Sprintf("got %v completion(s), want %v", len(gotItems), len(want))
228 for _, item := range gotItems {
229 got = append(got, item.Label)
232 for i, v := range got {
234 return fmt.Sprintf("completion results are not the same: got %v, want %v", got, want)
241 func TestUnimportedCompletion(t *testing.T) {
242 testenv.NeedsGo1Point(t, 14)
250 require example.com v1.2.3
252 example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY=
253 example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
263 import "example.com/blah"
271 ).Run(t, mod, func(t *testing.T, env *Env) {
272 // Make sure the dependency is in the module cache and accessible for
273 // unimported completions, and then remove it before proceeding.
274 env.RemoveWorkspaceFile("main2.go")
275 env.RunGoCommand("mod", "tidy")
276 env.Await(env.DoneWithChangeWatchedFiles())
278 // Trigger unimported completions for the example.com/blah package.
279 env.OpenFile("main.go")
280 env.Await(env.DoneWithOpen())
281 pos := env.RegexpSearch("main.go", "ah")
282 completions := env.Completion("main.go", pos)
283 if len(completions.Items) == 0 {
284 t.Fatalf("no completion items")
286 env.AcceptCompletion("main.go", pos, completions.Items[0])
287 env.Await(env.DoneWithChange())
289 // Trigger completions once again for the blah.<> selector.
290 env.RegexpReplace("main.go", "_ = blah", "_ = blah.")
291 env.Await(env.DoneWithChange())
292 pos = env.RegexpSearch("main.go", "\n}")
293 completions = env.Completion("main.go", pos)
294 if len(completions.Items) != 1 {
295 t.Fatalf("expected 1 completion item, got %v", len(completions.Items))
297 item := completions.Items[0]
298 if item.Label != "Name" {
299 t.Fatalf("expected completion item blah.Name, got %v", item.Label)
301 env.AcceptCompletion("main.go", pos, item)
303 // Await the diagnostics to add example.com/blah to the go.mod file.
304 env.SaveBufferWithoutActions("main.go")
306 env.DiagnosticAtRegexp("main.go", `"example.com/blah"`),
311 // Test that completions still work with an undownloaded module, golang/go#43333.
312 func TestUndownloadedModule(t *testing.T) {
313 // mod.com depends on example.com, but only in a file that's hidden by a
314 // build tag, so the IWL won't download example.com. That will cause errors
315 // in the go list -m call performed by the imports package.
322 require example.com v1.2.3
324 example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY=
325 example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
330 import "example.com/blah"
332 -- mainmod/mainmod.go --
335 const Name = "mainmod"
337 WithOptions(ProxyFiles(proxy)).Run(t, files, func(t *testing.T, env *Env) {
338 env.CreateBuffer("import.go", "package pkg\nvar _ = mainmod.Name\n")
339 env.SaveBuffer("import.go")
340 content := env.ReadWorkspaceFile("import.go")
341 if !strings.Contains(content, `import "mod.com/mainmod`) {
342 t.Errorf("expected import of mod.com/mainmod in %q", content)
347 // Test that we can doctor the source code enough so the file is
348 // parseable and completion works as expected.
349 func TestSourceFixup(t *testing.T) {
368 Run(t, files, func(t *testing.T, env *Env) {
369 env.OpenFile("foo.go")
370 completions := env.Completion("foo.go", env.RegexpSearch("foo.go", `if s\.()`))
371 diff := compareCompletionResults([]string{"i"}, completions.Items)