1 // Copyright 2019 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.
14 "golang.org/x/tools/internal/lsp"
15 "golang.org/x/tools/internal/lsp/cache"
16 "golang.org/x/tools/internal/lsp/protocol"
17 errors "golang.org/x/xerrors"
20 // TestCapabilities does some minimal validation of the server's adherence to the LSP.
21 // The checks in the test are added as changes are made and errors noticed.
22 func TestCapabilities(t *testing.T) {
23 tmpDir, err := ioutil.TempDir("", "fake")
27 tmpFile := filepath.Join(tmpDir, "fake.go")
28 if err := ioutil.WriteFile(tmpFile, []byte(""), 0775); err != nil {
31 if err := ioutil.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte("module fake\n\ngo 1.12\n"), 0775); err != nil {
34 defer os.RemoveAll(tmpDir)
36 app := New("gopls-test", tmpDir, os.Environ(), nil)
37 c := newConnection(app)
38 ctx := context.Background()
39 defer c.terminate(ctx)
41 params := &protocol.ParamInitialize{}
42 params.RootURI = protocol.URIFromPath(c.Client.app.wd)
43 params.Capabilities.Workspace.Configuration = true
45 // Send an initialize request to the server.
46 c.Server = lsp.NewServer(cache.New(ctx, app.options).NewSession(ctx), c.Client)
47 result, err := c.Server.Initialize(ctx, params)
51 // Validate initialization result.
52 if err := validateCapabilities(result); err != nil {
55 // Complete initialization of server.
56 if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
60 // Open the file on the server side.
61 uri := protocol.URIFromPath(tmpFile)
62 if err := c.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
63 TextDocument: protocol.TextDocumentItem{
67 Text: `package main; func main() {};`,
73 // If we are sending a full text change, the change.Range must be nil.
74 // It is not enough for the Change to be empty, as that is ambiguous.
75 if err := c.Server.DidChange(ctx, &protocol.DidChangeTextDocumentParams{
76 TextDocument: protocol.VersionedTextDocumentIdentifier{
77 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
82 ContentChanges: []protocol.TextDocumentContentChangeEvent{
85 Text: `package main; func main() { fmt.Println("") }`,
92 // Send a code action request to validate expected types.
93 actions, err := c.Server.CodeAction(ctx, &protocol.CodeActionParams{
94 TextDocument: protocol.TextDocumentIdentifier{
101 for _, action := range actions {
102 // Validate that an empty command is sent along with import organization responses.
103 if action.Kind == protocol.SourceOrganizeImports && action.Command != nil {
104 t.Errorf("unexpected command for import organization")
108 if err := c.Server.DidSave(ctx, &protocol.DidSaveTextDocumentParams{
109 TextDocument: protocol.VersionedTextDocumentIdentifier{
111 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
115 // LSP specifies that a file can be saved with optional text, so this field must be nil.
121 // Send a completion request to validate expected types.
122 list, err := c.Server.Completion(ctx, &protocol.CompletionParams{
123 TextDocumentPositionParams: protocol.TextDocumentPositionParams{
124 TextDocument: protocol.TextDocumentIdentifier{
127 Position: protocol.Position{
136 for _, item := range list.Items {
137 // All other completion items should have nil commands.
138 // An empty command will be treated as a command with the name '' by VS Code.
139 // This causes VS Code to report errors to users about invalid commands.
140 if item.Command != nil {
141 t.Errorf("unexpected command for completion item")
143 // The item's TextEdit must be a pointer, as VS Code considers TextEdits
144 // that don't contain the cursor position to be invalid.
145 var textEdit interface{} = item.TextEdit
146 if _, ok := textEdit.(*protocol.TextEdit); !ok {
147 t.Errorf("textEdit is not a *protocol.TextEdit, instead it is %T", textEdit)
150 if err := c.Server.Shutdown(ctx); err != nil {
153 if err := c.Server.Exit(ctx); err != nil {
158 func validateCapabilities(result *protocol.InitializeResult) error {
159 // If the client sends "false" for RenameProvider.PrepareSupport,
160 // the server must respond with a boolean.
161 if v, ok := result.Capabilities.RenameProvider.(bool); !ok {
162 return errors.Errorf("RenameProvider must be a boolean if PrepareSupport is false (got %T)", v)
164 // The same goes for CodeActionKind.ValueSet.
165 if v, ok := result.Capabilities.CodeActionProvider.(bool); !ok {
166 return errors.Errorf("CodeActionSupport must be a boolean if CodeActionKind.ValueSet has length 0 (got %T)", v)