gopls/internal/lsp: Replace input text when completing a definition

Completing a definition is based on a parsed identifier and should
replace the range of that identifier with the completion.

Fixes golang/go#56852

Change-Id: I44c751d1db6d55f90113fb918dda344bb7d62f87
Reviewed-on: https://go-review.googlesource.com/c/tools/+/453335
TryBot-Result: Gopher Robot <gobot@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Peter Weinberger <pjw@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Peter Weinberger <pjw@google.com>
diff --git a/gopls/internal/lsp/source/completion/definition.go b/gopls/internal/lsp/source/completion/definition.go
index fe41c55..cc7b42e 100644
--- a/gopls/internal/lsp/source/completion/definition.go
+++ b/gopls/internal/lsp/source/completion/definition.go
@@ -36,11 +36,12 @@
 		// can't happen
 		return nil, nil
 	}
-	pos := path[0].Pos()
+	start := path[0].Pos()
+	end := path[0].End()
 	sel := &Selection{
 		content: "",
-		cursor:  pos,
-		rng:     span.NewRange(tokFile, pos, pos),
+		cursor:  start,
+		rng:     span.NewRange(tokFile, start, end),
 	}
 	var ans []CompletionItem
 
diff --git a/gopls/internal/regtest/completion/completion_test.go b/gopls/internal/regtest/completion/completion_test.go
index caa70e8..3d5afe4 100644
--- a/gopls/internal/regtest/completion/completion_test.go
+++ b/gopls/internal/regtest/completion/completion_test.go
@@ -12,6 +12,7 @@
 	"golang.org/x/tools/gopls/internal/hooks"
 	. "golang.org/x/tools/gopls/internal/lsp/regtest"
 	"golang.org/x/tools/internal/bug"
+	"golang.org/x/tools/internal/testenv"
 
 	"golang.org/x/tools/gopls/internal/lsp/fake"
 	"golang.org/x/tools/gopls/internal/lsp/protocol"
@@ -596,6 +597,90 @@
 	})
 }
 
+// Test that completing a definition replaces source text when applied, golang/go#56852.
+// Note: With go <= 1.16 the completions does not add parameters and fails these tests.
+func TestDefinitionReplaceRange(t *testing.T) {
+	testenv.NeedsGo1Point(t, 17)
+
+	const mod = `
+-- go.mod --
+module mod.com
+
+go 1.17
+`
+
+	tests := []struct {
+		name          string
+		before, after string
+	}{
+		{
+			name: "func TestMa",
+			before: `
+package foo_test
+
+func TestMa
+`,
+			after: `
+package foo_test
+
+func TestMain(m *testing.M)
+`,
+		},
+		{
+			name: "func TestSome",
+			before: `
+package foo_test
+
+func TestSome
+`,
+			after: `
+package foo_test
+
+func TestSome(t *testing.T)
+`,
+		},
+		{
+			name: "func Bench",
+			before: `
+package foo_test
+
+func Bench
+`,
+			// Note: Snippet with escaped }.
+			after: `
+package foo_test
+
+func Benchmark${1:Xxx}(b *testing.B) {
+$0
+\}
+`,
+		},
+	}
+
+	Run(t, mod, func(t *testing.T, env *Env) {
+		env.CreateBuffer("foo_test.go", "")
+
+		for _, tst := range tests {
+			tst.before = strings.Trim(tst.before, "\n")
+			tst.after = strings.Trim(tst.after, "\n")
+			env.SetBufferContent("foo_test.go", tst.before)
+
+			pos := env.RegexpSearch("foo_test.go", tst.name)
+			pos.Column = len(tst.name)
+			completions := env.Completion("foo_test.go", pos)
+			if len(completions.Items) == 0 {
+				t.Fatalf("no completion items")
+			}
+
+			env.AcceptCompletion("foo_test.go", pos, completions.Items[0])
+			env.Await(env.DoneWithChange())
+			if buf := env.Editor.BufferText("foo_test.go"); buf != tst.after {
+				t.Errorf("incorrect completion: got %q, want %q", buf, tst.after)
+			}
+		}
+	})
+}
+
 func TestGoWorkCompletion(t *testing.T) {
 	const files = `
 -- go.work --