internal/lsp: change CompletionItem.{Command,TextEdit} to pointers

This is a continuation of the discussion on
https://github.com/microsoft/vscode-go/issues/2920. Add corresponding
checks to internal/lsp/cmd/capabilities_test.go.

Change-Id: I51af05dee9e7ecea0e40733dd4c5ca3dfb8f4dd8
Reviewed-on: https://go-review.googlesource.com/c/tools/+/209859
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/lsp/cmd/capabilities_test.go b/internal/lsp/cmd/capabilities_test.go
index 33ab47a..da906d8 100644
--- a/internal/lsp/cmd/capabilities_test.go
+++ b/internal/lsp/cmd/capabilities_test.go
@@ -114,6 +114,40 @@
 	}); err != nil {
 		t.Fatal(err)
 	}
+
+	// Send a completion request to validate expected types.
+	list, err := c.Server.Completion(ctx, &protocol.CompletionParams{
+		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
+			TextDocument: protocol.TextDocumentIdentifier{
+				URI: uri,
+			},
+			Position: protocol.Position{
+				Line:      0,
+				Character: 28,
+			},
+		},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, item := range list.Items {
+		// We expect the "editor.action.triggerParameterHints" command for functions and methods.
+		if item.Kind == protocol.MethodCompletion || item.Kind == protocol.FunctionCompletion {
+			continue
+		}
+		// All other completion items should have nil commands.
+		// An empty command will be treated as a command with the name '' by VS Code.
+		// This causes VS Code to report errors to users about invalid commands.
+		if item.Command != nil {
+			t.Errorf("unexpected command for non-function completion item")
+		}
+		// The item's TextEdit must be a pointer, as VS Code considers TextEdits
+		// that don't contain the cursor position to be invalid.
+		var textEdit interface{} = item.TextEdit
+		if _, ok := textEdit.(*protocol.TextEdit); !ok {
+			t.Errorf("textEdit is not a *protocol.TextEdit, instead it is %T", textEdit)
+		}
+	}
 }
 
 func validateCapabilities(result *protocol.InitializeResult) error {
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index b4634de..a10981f 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -114,7 +114,7 @@
 			Label:  candidate.Label,
 			Detail: candidate.Detail,
 			Kind:   candidate.Kind,
-			TextEdit: protocol.TextEdit{
+			TextEdit: &protocol.TextEdit{
 				NewText: insertText,
 				Range:   rng,
 			},
@@ -133,7 +133,7 @@
 		// since we show return types as well.
 		switch item.Kind {
 		case protocol.FunctionCompletion, protocol.MethodCompletion:
-			item.Command = protocol.Command{
+			item.Command = &protocol.Command{
 				Command: "editor.action.triggerParameterHints",
 			}
 		}
diff --git a/internal/lsp/protocol/tsprotocol.go b/internal/lsp/protocol/tsprotocol.go
index 1e1a306..3243a84 100644
--- a/internal/lsp/protocol/tsprotocol.go
+++ b/internal/lsp/protocol/tsprotocol.go
@@ -552,7 +552,7 @@
 	 * *Note:* The text edit's range must be a [single line] and it must contain the position
 	 * at which completion has been requested.
 	 */
-	TextEdit TextEdit `json:"textEdit,omitempty"`
+	TextEdit *TextEdit `json:"textEdit,omitempty"`
 	/**
 	 * An optional array of additional [text edits](#TextEdit) that are applied when
 	 * selecting this completion. Edits must not overlap (including the same insert position)
@@ -574,7 +574,7 @@
 	 * additional modifications to the current document should be described with the
 	 * [additionalTextEdits](#CompletionItem.additionalTextEdits)-property.
 	 */
-	Command Command `json:"command,omitempty"`
+	Command *Command `json:"command,omitempty"`
 	/**
 	 * An data entry field that is preserved on a completion item between
 	 * a [CompletionRequest](#CompletionRequest) and a [CompletionResolveRequest]
diff --git a/internal/lsp/tests/completion.go b/internal/lsp/tests/completion.go
index 316e20c..ba5ec70 100644
--- a/internal/lsp/tests/completion.go
+++ b/internal/lsp/tests/completion.go
@@ -26,7 +26,7 @@
 		Detail:        item.Detail,
 		Documentation: item.Documentation,
 		InsertText:    item.InsertText,
-		TextEdit: protocol.TextEdit{
+		TextEdit: &protocol.TextEdit{
 			NewText: item.Snippet(),
 		},
 		// Negate score so best score has lowest sort text like real API.