golang.org/x/tools/gopls@v0.15.3/internal/test/integration/completion/completion_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 completion 6 7 import ( 8 "fmt" 9 "sort" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/google/go-cmp/cmp" 15 "golang.org/x/tools/gopls/internal/hooks" 16 "golang.org/x/tools/gopls/internal/protocol" 17 . "golang.org/x/tools/gopls/internal/test/integration" 18 "golang.org/x/tools/gopls/internal/test/integration/fake" 19 "golang.org/x/tools/gopls/internal/util/bug" 20 "golang.org/x/tools/internal/testenv" 21 ) 22 23 func TestMain(m *testing.M) { 24 bug.PanicOnBugs = true 25 Main(m, hooks.Options) 26 } 27 28 const proxy = ` 29 -- example.com@v1.2.3/go.mod -- 30 module example.com 31 32 go 1.12 33 -- example.com@v1.2.3/blah/blah.go -- 34 package blah 35 36 const Name = "Blah" 37 -- random.org@v1.2.3/go.mod -- 38 module random.org 39 40 go 1.12 41 -- random.org@v1.2.3/blah/blah.go -- 42 package hello 43 44 const Name = "Hello" 45 ` 46 47 func TestPackageCompletion(t *testing.T) { 48 const files = ` 49 -- go.mod -- 50 module mod.com 51 52 go 1.12 53 -- fruits/apple.go -- 54 package apple 55 56 fun apple() int { 57 return 0 58 } 59 60 -- fruits/testfile.go -- 61 // this is a comment 62 63 /* 64 this is a multiline comment 65 */ 66 67 import "fmt" 68 69 func test() {} 70 71 -- fruits/testfile2.go -- 72 package 73 74 -- fruits/testfile3.go -- 75 pac 76 -- 123f_r.u~its-123/testfile.go -- 77 package 78 79 -- .invalid-dir@-name/testfile.go -- 80 package 81 ` 82 var ( 83 testfile4 = "" 84 testfile5 = "/*a comment*/ " 85 testfile6 = "/*a comment*/\n" 86 ) 87 for _, tc := range []struct { 88 name string 89 filename string 90 content *string 91 triggerRegexp string 92 want []string 93 editRegexp string 94 }{ 95 { 96 name: "package completion at valid position", 97 filename: "fruits/testfile.go", 98 triggerRegexp: "\n()", 99 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, 100 editRegexp: "\n()", 101 }, 102 { 103 name: "package completion in a comment", 104 filename: "fruits/testfile.go", 105 triggerRegexp: "th(i)s", 106 want: nil, 107 }, 108 { 109 name: "package completion in a multiline comment", 110 filename: "fruits/testfile.go", 111 triggerRegexp: `\/\*\n()`, 112 want: nil, 113 }, 114 { 115 name: "package completion at invalid position", 116 filename: "fruits/testfile.go", 117 triggerRegexp: "import \"fmt\"\n()", 118 want: nil, 119 }, 120 { 121 name: "package completion after keyword 'package'", 122 filename: "fruits/testfile2.go", 123 triggerRegexp: "package()", 124 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, 125 editRegexp: "package\n", 126 }, 127 { 128 name: "package completion with 'pac' prefix", 129 filename: "fruits/testfile3.go", 130 triggerRegexp: "pac()", 131 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, 132 editRegexp: "pac", 133 }, 134 { 135 name: "package completion for empty file", 136 filename: "fruits/testfile4.go", 137 triggerRegexp: "^$", 138 content: &testfile4, 139 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, 140 editRegexp: "^$", 141 }, 142 { 143 name: "package completion without terminal newline", 144 filename: "fruits/testfile5.go", 145 triggerRegexp: `\*\/ ()`, 146 content: &testfile5, 147 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, 148 editRegexp: `\*\/ ()`, 149 }, 150 { 151 name: "package completion on terminal newline", 152 filename: "fruits/testfile6.go", 153 triggerRegexp: `\*\/\n()`, 154 content: &testfile6, 155 want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, 156 editRegexp: `\*\/\n()`, 157 }, 158 // Issue golang/go#44680 159 { 160 name: "package completion for dir name with punctuation", 161 filename: "123f_r.u~its-123/testfile.go", 162 triggerRegexp: "package()", 163 want: []string{"package fruits123", "package fruits123_test", "package main"}, 164 editRegexp: "package\n", 165 }, 166 { 167 name: "package completion for invalid dir name", 168 filename: ".invalid-dir@-name/testfile.go", 169 triggerRegexp: "package()", 170 want: []string{"package main"}, 171 editRegexp: "package\n", 172 }, 173 } { 174 t.Run(tc.name, func(t *testing.T) { 175 Run(t, files, func(t *testing.T, env *Env) { 176 if tc.content != nil { 177 env.WriteWorkspaceFile(tc.filename, *tc.content) 178 env.Await(env.DoneWithChangeWatchedFiles()) 179 } 180 env.OpenFile(tc.filename) 181 completions := env.Completion(env.RegexpSearch(tc.filename, tc.triggerRegexp)) 182 183 // Check that the completion item suggestions are in the range 184 // of the file. {Start,End}.Line are zero-based. 185 lineCount := len(strings.Split(env.BufferText(tc.filename), "\n")) 186 for _, item := range completions.Items { 187 if start := int(item.TextEdit.Range.Start.Line); start > lineCount { 188 t.Fatalf("unexpected text edit range start line number: got %d, want <= %d", start, lineCount) 189 } 190 if end := int(item.TextEdit.Range.End.Line); end > lineCount { 191 t.Fatalf("unexpected text edit range end line number: got %d, want <= %d", end, lineCount) 192 } 193 } 194 195 if tc.want != nil { 196 expectedLoc := env.RegexpSearch(tc.filename, tc.editRegexp) 197 for _, item := range completions.Items { 198 gotRng := item.TextEdit.Range 199 if expectedLoc.Range != gotRng { 200 t.Errorf("unexpected completion range for completion item %s: got %v, want %v", 201 item.Label, gotRng, expectedLoc.Range) 202 } 203 } 204 } 205 206 diff := compareCompletionLabels(tc.want, completions.Items) 207 if diff != "" { 208 t.Error(diff) 209 } 210 }) 211 }) 212 } 213 } 214 215 func TestPackageNameCompletion(t *testing.T) { 216 const files = ` 217 -- go.mod -- 218 module mod.com 219 220 go 1.12 221 -- math/add.go -- 222 package ma 223 ` 224 225 want := []string{"ma", "ma_test", "main", "math", "math_test"} 226 Run(t, files, func(t *testing.T, env *Env) { 227 env.OpenFile("math/add.go") 228 completions := env.Completion(env.RegexpSearch("math/add.go", "package ma()")) 229 230 diff := compareCompletionLabels(want, completions.Items) 231 if diff != "" { 232 t.Fatal(diff) 233 } 234 }) 235 } 236 237 // TODO(rfindley): audit/clean up call sites for this helper, to ensure 238 // consistent test errors. 239 func compareCompletionLabels(want []string, gotItems []protocol.CompletionItem) string { 240 var got []string 241 for _, item := range gotItems { 242 got = append(got, item.Label) 243 if item.Label != item.InsertText && item.TextEdit == nil { 244 // Label should be the same as InsertText, if InsertText is to be used 245 return fmt.Sprintf("label not the same as InsertText %#v", item) 246 } 247 } 248 249 if len(got) == 0 && len(want) == 0 { 250 return "" // treat nil and the empty slice as equivalent 251 } 252 253 if diff := cmp.Diff(want, got); diff != "" { 254 return fmt.Sprintf("completion item mismatch (-want +got):\n%s", diff) 255 } 256 return "" 257 } 258 259 func TestUnimportedCompletion(t *testing.T) { 260 const mod = ` 261 -- go.mod -- 262 module mod.com 263 264 go 1.14 265 266 require example.com v1.2.3 267 -- go.sum -- 268 example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= 269 example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= 270 -- main.go -- 271 package main 272 273 func main() { 274 _ = blah 275 } 276 -- main2.go -- 277 package main 278 279 import "example.com/blah" 280 281 func _() { 282 _ = blah.Hello 283 } 284 ` 285 WithOptions( 286 ProxyFiles(proxy), 287 ).Run(t, mod, func(t *testing.T, env *Env) { 288 // Make sure the dependency is in the module cache and accessible for 289 // unimported completions, and then remove it before proceeding. 290 env.RemoveWorkspaceFile("main2.go") 291 env.RunGoCommand("mod", "tidy") 292 env.Await(env.DoneWithChangeWatchedFiles()) 293 294 // Trigger unimported completions for the example.com/blah package. 295 env.OpenFile("main.go") 296 env.Await(env.DoneWithOpen()) 297 loc := env.RegexpSearch("main.go", "ah") 298 completions := env.Completion(loc) 299 if len(completions.Items) == 0 { 300 t.Fatalf("no completion items") 301 } 302 env.AcceptCompletion(loc, completions.Items[0]) // adds blah import to main.go 303 env.Await(env.DoneWithChange()) 304 305 // Trigger completions once again for the blah.<> selector. 306 env.RegexpReplace("main.go", "_ = blah", "_ = blah.") 307 env.Await(env.DoneWithChange()) 308 loc = env.RegexpSearch("main.go", "\n}") 309 completions = env.Completion(loc) 310 if len(completions.Items) != 1 { 311 t.Fatalf("expected 1 completion item, got %v", len(completions.Items)) 312 } 313 item := completions.Items[0] 314 if item.Label != "Name" { 315 t.Fatalf("expected completion item blah.Name, got %v", item.Label) 316 } 317 env.AcceptCompletion(loc, item) 318 319 // Await the diagnostics to add example.com/blah to the go.mod file. 320 env.AfterChange( 321 Diagnostics(env.AtRegexp("main.go", `"example.com/blah"`)), 322 ) 323 }) 324 } 325 326 // Test that completions still work with an undownloaded module, golang/go#43333. 327 func TestUndownloadedModule(t *testing.T) { 328 // mod.com depends on example.com, but only in a file that's hidden by a 329 // build tag, so the IWL won't download example.com. That will cause errors 330 // in the go list -m call performed by the imports package. 331 const files = ` 332 -- go.mod -- 333 module mod.com 334 335 go 1.14 336 337 require example.com v1.2.3 338 -- go.sum -- 339 example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= 340 example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= 341 -- useblah.go -- 342 // +build hidden 343 344 package pkg 345 import "example.com/blah" 346 var _ = blah.Name 347 -- mainmod/mainmod.go -- 348 package mainmod 349 350 const Name = "mainmod" 351 ` 352 WithOptions(ProxyFiles(proxy)).Run(t, files, func(t *testing.T, env *Env) { 353 env.CreateBuffer("import.go", "package pkg\nvar _ = mainmod.Name\n") 354 env.SaveBuffer("import.go") 355 content := env.ReadWorkspaceFile("import.go") 356 if !strings.Contains(content, `import "mod.com/mainmod`) { 357 t.Errorf("expected import of mod.com/mainmod in %q", content) 358 } 359 }) 360 } 361 362 // Test that we can doctor the source code enough so the file is 363 // parseable and completion works as expected. 364 func TestSourceFixup(t *testing.T) { 365 const files = ` 366 -- go.mod -- 367 module mod.com 368 369 go 1.12 370 -- foo.go -- 371 package foo 372 373 func _() { 374 var s S 375 if s. 376 } 377 378 type S struct { 379 i int 380 } 381 ` 382 383 Run(t, files, func(t *testing.T, env *Env) { 384 env.OpenFile("foo.go") 385 completions := env.Completion(env.RegexpSearch("foo.go", `if s\.()`)) 386 diff := compareCompletionLabels([]string{"i"}, completions.Items) 387 if diff != "" { 388 t.Fatal(diff) 389 } 390 }) 391 } 392 393 func TestCompletion_Issue45510(t *testing.T) { 394 const files = ` 395 -- go.mod -- 396 module mod.com 397 398 go 1.12 399 -- main.go -- 400 package main 401 402 func _() { 403 type a *a 404 var aaaa1, aaaa2 a 405 var _ a = aaaa 406 407 type b a 408 var bbbb1, bbbb2 b 409 var _ b = bbbb 410 } 411 412 type ( 413 c *d 414 d *e 415 e **c 416 ) 417 418 func _() { 419 var ( 420 xxxxc c 421 xxxxd d 422 xxxxe e 423 ) 424 425 var _ c = xxxx 426 var _ d = xxxx 427 var _ e = xxxx 428 } 429 ` 430 431 Run(t, files, func(t *testing.T, env *Env) { 432 env.OpenFile("main.go") 433 434 tests := []struct { 435 re string 436 want []string 437 }{ 438 {`var _ a = aaaa()`, []string{"aaaa1", "aaaa2"}}, 439 {`var _ b = bbbb()`, []string{"bbbb1", "bbbb2"}}, 440 {`var _ c = xxxx()`, []string{"xxxxc", "xxxxd", "xxxxe"}}, 441 {`var _ d = xxxx()`, []string{"xxxxc", "xxxxd", "xxxxe"}}, 442 {`var _ e = xxxx()`, []string{"xxxxc", "xxxxd", "xxxxe"}}, 443 } 444 for _, tt := range tests { 445 completions := env.Completion(env.RegexpSearch("main.go", tt.re)) 446 diff := compareCompletionLabels(tt.want, completions.Items) 447 if diff != "" { 448 t.Errorf("%s: %s", tt.re, diff) 449 } 450 } 451 }) 452 } 453 454 func TestCompletionDeprecation(t *testing.T) { 455 const files = ` 456 -- go.mod -- 457 module test.com 458 459 go 1.16 460 -- prog.go -- 461 package waste 462 // Deprecated, use newFoof 463 func fooFunc() bool { 464 return false 465 } 466 467 // Deprecated 468 const badPi = 3.14 469 470 func doit() { 471 if fooF 472 panic() 473 x := badP 474 } 475 ` 476 Run(t, files, func(t *testing.T, env *Env) { 477 env.OpenFile("prog.go") 478 loc := env.RegexpSearch("prog.go", "if fooF") 479 loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte("if fooF"))) 480 completions := env.Completion(loc) 481 diff := compareCompletionLabels([]string{"fooFunc"}, completions.Items) 482 if diff != "" { 483 t.Error(diff) 484 } 485 if completions.Items[0].Tags == nil { 486 t.Errorf("expected Tags to show deprecation %#v", completions.Items[0].Tags) 487 } 488 loc = env.RegexpSearch("prog.go", "= badP") 489 loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte("= badP"))) 490 completions = env.Completion(loc) 491 diff = compareCompletionLabels([]string{"badPi"}, completions.Items) 492 if diff != "" { 493 t.Error(diff) 494 } 495 if completions.Items[0].Tags == nil { 496 t.Errorf("expected Tags to show deprecation %#v", completions.Items[0].Tags) 497 } 498 }) 499 } 500 501 func TestUnimportedCompletion_VSCodeIssue1489(t *testing.T) { 502 const src = ` 503 -- go.mod -- 504 module mod.com 505 506 go 1.14 507 508 -- main.go -- 509 package main 510 511 import "fmt" 512 513 func main() { 514 fmt.Println("a") 515 math.Sqr 516 } 517 ` 518 WithOptions( 519 WindowsLineEndings(), 520 Settings{"ui.completion.usePlaceholders": true}, 521 ).Run(t, src, func(t *testing.T, env *Env) { 522 // Trigger unimported completions for the mod.com package. 523 env.OpenFile("main.go") 524 env.Await(env.DoneWithOpen()) 525 loc := env.RegexpSearch("main.go", "Sqr()") 526 completions := env.Completion(loc) 527 if len(completions.Items) == 0 { 528 t.Fatalf("no completion items") 529 } 530 env.AcceptCompletion(loc, completions.Items[0]) 531 env.Await(env.DoneWithChange()) 532 got := env.BufferText("main.go") 533 want := "package main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"math\"\r\n)\r\n\r\nfunc main() {\r\n\tfmt.Println(\"a\")\r\n\tmath.Sqrt(${1:x float64})\r\n}\r\n" 534 if diff := cmp.Diff(want, got); diff != "" { 535 t.Errorf("unimported completion (-want +got):\n%s", diff) 536 } 537 }) 538 } 539 540 func TestUnimportedCompletionHasPlaceholders60269(t *testing.T) { 541 // We can't express this as a marker test because it doesn't support AcceptCompletion. 542 const src = ` 543 -- go.mod -- 544 module example.com 545 go 1.12 546 547 -- a/a.go -- 548 package a 549 550 var _ = b.F 551 552 -- b/b.go -- 553 package b 554 555 func F0(a, b int, c float64) {} 556 func F1(int, chan *string) {} 557 func F2[K, V any](map[K]V, chan V) {} // missing type parameters was issue #60959 558 func F3[K comparable, V any](map[K]V, chan V) {} 559 ` 560 WithOptions( 561 WindowsLineEndings(), 562 Settings{"ui.completion.usePlaceholders": true}, 563 ).Run(t, src, func(t *testing.T, env *Env) { 564 env.OpenFile("a/a.go") 565 env.Await(env.DoneWithOpen()) 566 567 // The table lists the expected completions of b.F as they appear in Items. 568 const common = "package a\r\n\r\nimport \"example.com/b\"\r\n\r\nvar _ = " 569 for i, want := range []string{ 570 common + "b.F0(${1:a int}, ${2:b int}, ${3:c float64})\r\n", 571 common + "b.F1(${1:_ int}, ${2:_ chan *string})\r\n", 572 common + "b.F2[${1:K any}, ${2:V any}](${3:_ map[K]V}, ${4:_ chan V})\r\n", 573 common + "b.F3[${1:K comparable}, ${2:V any}](${3:_ map[K]V}, ${4:_ chan V})\r\n", 574 } { 575 loc := env.RegexpSearch("a/a.go", "b.F()") 576 completions := env.Completion(loc) 577 if len(completions.Items) == 0 { 578 t.Fatalf("no completion items") 579 } 580 saved := env.BufferText("a/a.go") 581 env.AcceptCompletion(loc, completions.Items[i]) 582 env.Await(env.DoneWithChange()) 583 got := env.BufferText("a/a.go") 584 if diff := cmp.Diff(want, got); diff != "" { 585 t.Errorf("%d: unimported completion (-want +got):\n%s", i, diff) 586 } 587 env.SetBufferContent("a/a.go", saved) // restore 588 } 589 }) 590 } 591 592 func TestPackageMemberCompletionAfterSyntaxError(t *testing.T) { 593 // This test documents the current broken behavior due to golang/go#58833. 594 const src = ` 595 -- go.mod -- 596 module mod.com 597 598 go 1.14 599 600 -- main.go -- 601 package main 602 603 import "math" 604 605 func main() { 606 math.Sqrt(,0) 607 math.Ldex 608 } 609 ` 610 Run(t, src, func(t *testing.T, env *Env) { 611 env.OpenFile("main.go") 612 env.Await(env.DoneWithOpen()) 613 loc := env.RegexpSearch("main.go", "Ldex()") 614 completions := env.Completion(loc) 615 if len(completions.Items) == 0 { 616 t.Fatalf("no completion items") 617 } 618 env.AcceptCompletion(loc, completions.Items[0]) 619 env.Await(env.DoneWithChange()) 620 got := env.BufferText("main.go") 621 // The completion of math.Ldex after the syntax error on the 622 // previous line is not "math.Ldexp" but "math.Ldexmath.Abs". 623 // (In VSCode, "Abs" wrongly appears in the completion menu.) 624 // This is a consequence of poor error recovery in the parser 625 // causing "math.Ldex" to become a BadExpr. 626 want := "package main\n\nimport \"math\"\n\nfunc main() {\n\tmath.Sqrt(,0)\n\tmath.Ldexmath.Abs(${1:})\n}\n" 627 if diff := cmp.Diff(want, got); diff != "" { 628 t.Errorf("unimported completion (-want +got):\n%s", diff) 629 } 630 }) 631 } 632 633 func TestCompleteAllFields(t *testing.T) { 634 // This test verifies that completion results always include all struct fields. 635 // See golang/go#53992. 636 637 const src = ` 638 -- go.mod -- 639 module mod.com 640 641 go 1.18 642 643 -- p/p.go -- 644 package p 645 646 import ( 647 "fmt" 648 649 . "net/http" 650 . "runtime" 651 . "go/types" 652 . "go/parser" 653 . "go/ast" 654 ) 655 656 type S struct { 657 a, b, c, d, e, f, g, h, i, j, k, l, m int 658 n, o, p, q, r, s, t, u, v, w, x, y, z int 659 } 660 661 func _() { 662 var s S 663 fmt.Println(s.) 664 } 665 ` 666 667 WithOptions(Settings{ 668 "completionBudget": "1ns", // must be non-zero as 0 => infinity 669 }).Run(t, src, func(t *testing.T, env *Env) { 670 wantFields := make(map[string]bool) 671 for c := 'a'; c <= 'z'; c++ { 672 wantFields[string(c)] = true 673 } 674 675 env.OpenFile("p/p.go") 676 // Make an arbitrary edit to ensure we're not hitting the cache. 677 env.EditBuffer("p/p.go", fake.NewEdit(0, 0, 0, 0, fmt.Sprintf("// current time: %v\n", time.Now()))) 678 loc := env.RegexpSearch("p/p.go", `s\.()`) 679 completions := env.Completion(loc) 680 gotFields := make(map[string]bool) 681 for _, item := range completions.Items { 682 if item.Kind == protocol.FieldCompletion { 683 gotFields[item.Label] = true 684 } 685 } 686 687 if diff := cmp.Diff(wantFields, gotFields); diff != "" { 688 t.Errorf("Completion(...) returned mismatching fields (-want +got):\n%s", diff) 689 } 690 }) 691 } 692 693 func TestDefinition(t *testing.T) { 694 files := ` 695 -- go.mod -- 696 module mod.com 697 698 go 1.18 699 -- a_test.go -- 700 package foo 701 ` 702 tests := []struct { 703 line string // the sole line in the buffer after the package statement 704 pat string // the pattern to search for 705 want []string // expected completions 706 }{ 707 {"func T", "T", []string{"TestXxx(t *testing.T)", "TestMain(m *testing.M)"}}, 708 {"func T()", "T", []string{"TestMain", "Test"}}, 709 {"func TestM", "TestM", []string{"TestMain(m *testing.M)", "TestM(t *testing.T)"}}, 710 {"func TestM()", "TestM", []string{"TestMain"}}, 711 {"func TestMi", "TestMi", []string{"TestMi(t *testing.T)"}}, 712 {"func TestMi()", "TestMi", nil}, 713 {"func TestG", "TestG", []string{"TestG(t *testing.T)"}}, 714 {"func TestG(", "TestG", nil}, 715 {"func Ben", "B", []string{"BenchmarkXxx(b *testing.B)"}}, 716 {"func Ben(", "Ben", []string{"Benchmark"}}, 717 {"func BenchmarkFoo", "BenchmarkFoo", []string{"BenchmarkFoo(b *testing.B)"}}, 718 {"func BenchmarkFoo(", "BenchmarkFoo", nil}, 719 {"func Fuz", "F", []string{"FuzzXxx(f *testing.F)"}}, 720 {"func Fuz(", "Fuz", []string{"Fuzz"}}, 721 {"func Testx", "Testx", nil}, 722 {"func TestMe(t *testing.T)", "TestMe", nil}, 723 {"func Te(t *testing.T)", "Te", []string{"TestMain", "Test"}}, 724 } 725 fname := "a_test.go" 726 Run(t, files, func(t *testing.T, env *Env) { 727 env.OpenFile(fname) 728 env.Await(env.DoneWithOpen()) 729 for _, test := range tests { 730 env.SetBufferContent(fname, "package foo\n"+test.line) 731 loc := env.RegexpSearch(fname, test.pat) 732 loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte(test.pat))) 733 completions := env.Completion(loc) 734 if diff := compareCompletionLabels(test.want, completions.Items); diff != "" { 735 t.Error(diff) 736 } 737 } 738 }) 739 } 740 741 // Test that completing a definition replaces source text when applied, golang/go#56852. 742 func TestDefinitionReplaceRange(t *testing.T) { 743 const mod = ` 744 -- go.mod -- 745 module mod.com 746 747 go 1.17 748 ` 749 750 tests := []struct { 751 name string 752 before, after string 753 }{ 754 { 755 name: "func TestMa", 756 before: ` 757 package foo_test 758 759 func TestMa 760 `, 761 after: ` 762 package foo_test 763 764 func TestMain(m *testing.M) 765 `, 766 }, 767 { 768 name: "func TestSome", 769 before: ` 770 package foo_test 771 772 func TestSome 773 `, 774 after: ` 775 package foo_test 776 777 func TestSome(t *testing.T) 778 `, 779 }, 780 { 781 name: "func Bench", 782 before: ` 783 package foo_test 784 785 func Bench 786 `, 787 // Note: Snippet with escaped }. 788 after: ` 789 package foo_test 790 791 func Benchmark${1:Xxx}(b *testing.B) { 792 $0 793 \} 794 `, 795 }, 796 } 797 798 Run(t, mod, func(t *testing.T, env *Env) { 799 env.CreateBuffer("foo_test.go", "") 800 801 for _, tst := range tests { 802 tst.before = strings.Trim(tst.before, "\n") 803 tst.after = strings.Trim(tst.after, "\n") 804 env.SetBufferContent("foo_test.go", tst.before) 805 806 loc := env.RegexpSearch("foo_test.go", tst.name) 807 loc.Range.Start.Character = uint32(protocol.UTF16Len([]byte(tst.name))) 808 completions := env.Completion(loc) 809 if len(completions.Items) == 0 { 810 t.Fatalf("no completion items") 811 } 812 813 env.AcceptCompletion(loc, completions.Items[0]) 814 env.Await(env.DoneWithChange()) 815 if buf := env.BufferText("foo_test.go"); buf != tst.after { 816 t.Errorf("%s:incorrect completion: got %q, want %q", tst.name, buf, tst.after) 817 } 818 } 819 }) 820 } 821 822 func TestGoWorkCompletion(t *testing.T) { 823 const files = ` 824 -- go.work -- 825 go 1.18 826 827 use ./a 828 use ./a/ba 829 use ./a/b/ 830 use ./dir/foo 831 use ./dir/foobar/ 832 use ./missing/ 833 -- a/go.mod -- 834 -- go.mod -- 835 -- a/bar/go.mod -- 836 -- a/b/c/d/e/f/go.mod -- 837 -- dir/bar -- 838 -- dir/foobar/go.mod -- 839 ` 840 841 Run(t, files, func(t *testing.T, env *Env) { 842 env.OpenFile("go.work") 843 844 tests := []struct { 845 re string 846 want []string 847 }{ 848 {`use ()\.`, []string{".", "./a", "./a/bar", "./dir/foobar"}}, 849 {`use \.()`, []string{"", "/a", "/a/bar", "/dir/foobar"}}, 850 {`use \./()`, []string{"a", "a/bar", "dir/foobar"}}, 851 {`use ./a()`, []string{"", "/b/c/d/e/f", "/bar"}}, 852 {`use ./a/b()`, []string{"/c/d/e/f", "ar"}}, 853 {`use ./a/b/()`, []string{`c/d/e/f`}}, 854 {`use ./a/ba()`, []string{"r"}}, 855 {`use ./dir/foo()`, []string{"bar"}}, 856 {`use ./dir/foobar/()`, []string{}}, 857 {`use ./missing/()`, []string{}}, 858 } 859 for _, tt := range tests { 860 completions := env.Completion(env.RegexpSearch("go.work", tt.re)) 861 diff := compareCompletionLabels(tt.want, completions.Items) 862 if diff != "" { 863 t.Errorf("%s: %s", tt.re, diff) 864 } 865 } 866 }) 867 } 868 869 func TestBuiltinCompletion(t *testing.T) { 870 const files = ` 871 -- go.mod -- 872 module mod.com 873 874 go 1.18 875 -- a.go -- 876 package a 877 878 func _() { 879 // here 880 } 881 ` 882 883 Run(t, files, func(t *testing.T, env *Env) { 884 env.OpenFile("a.go") 885 result := env.Completion(env.RegexpSearch("a.go", `// here`)) 886 builtins := []string{ 887 "any", "append", "bool", "byte", "cap", "close", 888 "comparable", "complex", "complex128", "complex64", "copy", "delete", 889 "error", "false", "float32", "float64", "imag", "int", "int16", "int32", 890 "int64", "int8", "len", "make", "new", "panic", "print", "println", "real", 891 "recover", "rune", "string", "true", "uint", "uint16", "uint32", "uint64", 892 "uint8", "uintptr", "nil", 893 } 894 if testenv.Go1Point() >= 21 { 895 builtins = append(builtins, "clear", "max", "min") 896 } 897 sort.Strings(builtins) 898 var got []string 899 900 for _, item := range result.Items { 901 // TODO(rfindley): for flexibility, ignore zero while it is being 902 // implemented. Remove this if/when zero lands. 903 if item.Label != "zero" { 904 got = append(got, item.Label) 905 } 906 } 907 sort.Strings(got) 908 909 if diff := cmp.Diff(builtins, got); diff != "" { 910 t.Errorf("Completion: unexpected mismatch (-want +got):\n%s", diff) 911 } 912 }) 913 } 914 915 func TestOverlayCompletion(t *testing.T) { 916 const files = ` 917 -- go.mod -- 918 module foo.test 919 920 go 1.18 921 922 -- foo/foo.go -- 923 package foo 924 925 type Foo struct{} 926 ` 927 928 Run(t, files, func(t *testing.T, env *Env) { 929 env.CreateBuffer("nodisk/nodisk.go", ` 930 package nodisk 931 932 import ( 933 "foo.test/foo" 934 ) 935 936 func _() { 937 foo.Foo() 938 } 939 `) 940 list := env.Completion(env.RegexpSearch("nodisk/nodisk.go", "foo.(Foo)")) 941 want := []string{"Foo"} 942 var got []string 943 for _, item := range list.Items { 944 got = append(got, item.Label) 945 } 946 if diff := cmp.Diff(want, got); diff != "" { 947 t.Errorf("Completion: unexpected mismatch (-want +got):\n%s", diff) 948 } 949 }) 950 } 951 952 // Fix for golang/go#60062: unimported completion included "golang.org/toolchain" results. 953 func TestToolchainCompletions(t *testing.T) { 954 const files = ` 955 -- go.mod -- 956 module foo.test/foo 957 958 go 1.21 959 960 -- foo.go -- 961 package foo 962 963 func _() { 964 os.Open 965 } 966 967 func _() { 968 strings 969 } 970 ` 971 972 const proxy = ` 973 -- golang.org/toolchain@v0.0.1-go1.21.1.linux-amd64/go.mod -- 974 module golang.org/toolchain 975 -- golang.org/toolchain@v0.0.1-go1.21.1.linux-amd64/src/os/os.go -- 976 package os 977 978 func Open() {} 979 -- golang.org/toolchain@v0.0.1-go1.21.1.linux-amd64/src/strings/strings.go -- 980 package strings 981 982 func Join() {} 983 ` 984 985 WithOptions( 986 ProxyFiles(proxy), 987 ).Run(t, files, func(t *testing.T, env *Env) { 988 env.RunGoCommand("mod", "download", "golang.org/toolchain@v0.0.1-go1.21.1.linux-amd64") 989 env.OpenFile("foo.go") 990 991 for _, pattern := range []string{"os.Open()", "string()"} { 992 loc := env.RegexpSearch("foo.go", pattern) 993 res := env.Completion(loc) 994 for _, item := range res.Items { 995 if strings.Contains(item.Detail, "golang.org/toolchain") { 996 t.Errorf("Completion(...) returned toolchain item %#v", item) 997 } 998 } 999 } 1000 }) 1001 }