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  }