.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.0 / internal / lsp / command.go
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 lsp
6
7 import (
8         "bytes"
9         "context"
10         "encoding/json"
11         "fmt"
12         "io"
13         "io/ioutil"
14         "path/filepath"
15         "strings"
16
17         "golang.org/x/mod/modfile"
18         "golang.org/x/tools/internal/event"
19         "golang.org/x/tools/internal/gocommand"
20         "golang.org/x/tools/internal/lsp/cache"
21         "golang.org/x/tools/internal/lsp/protocol"
22         "golang.org/x/tools/internal/lsp/source"
23         "golang.org/x/tools/internal/span"
24         "golang.org/x/tools/internal/xcontext"
25         errors "golang.org/x/xerrors"
26 )
27
28 func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
29         var command *source.Command
30         for _, c := range source.Commands {
31                 if c.ID() == params.Command {
32                         command = c
33                         break
34                 }
35         }
36         if command == nil {
37                 return nil, fmt.Errorf("no known command")
38         }
39         var match bool
40         for _, name := range s.session.Options().SupportedCommands {
41                 if command.ID() == name {
42                         match = true
43                         break
44                 }
45         }
46         if !match {
47                 return nil, fmt.Errorf("%s is not a supported command", command.ID())
48         }
49         // Some commands require that all files are saved to disk. If we detect
50         // unsaved files, warn the user instead of running the commands.
51         unsaved := false
52         for _, overlay := range s.session.Overlays() {
53                 if !overlay.Saved() {
54                         unsaved = true
55                         break
56                 }
57         }
58         if unsaved {
59                 switch params.Command {
60                 case source.CommandTest.ID(),
61                         source.CommandGenerate.ID(),
62                         source.CommandToggleDetails.ID(),
63                         source.CommandAddDependency.ID(),
64                         source.CommandUpgradeDependency.ID(),
65                         source.CommandRemoveDependency.ID(),
66                         source.CommandVendor.ID():
67                         // TODO(PJW): for Toggle, not an error if it is being disabled
68                         err := errors.New("All files must be saved first")
69                         s.showCommandError(ctx, command.Title, err)
70                         return nil, nil
71                 }
72         }
73         ctx, cancel := context.WithCancel(xcontext.Detach(ctx))
74
75         var work *workDone
76         // Don't show progress for suggested fixes. They should be quick.
77         if !command.IsSuggestedFix() {
78                 // Start progress prior to spinning off a goroutine specifically so that
79                 // clients are aware of the work item before the command completes. This
80                 // matters for regtests, where having a continuous thread of work is
81                 // convenient for assertions.
82                 work = s.progress.start(ctx, command.Title, "Running...", params.WorkDoneToken, cancel)
83         }
84
85         run := func() {
86                 defer cancel()
87                 err := s.runCommand(ctx, work, command, params.Arguments)
88                 switch {
89                 case errors.Is(err, context.Canceled):
90                         work.end(command.Title + ": canceled")
91                 case err != nil:
92                         event.Error(ctx, fmt.Sprintf("%s: command error", command.Title), err)
93                         work.end(command.Title + ": failed")
94                         // Show a message when work completes with error, because the progress end
95                         // message is typically dismissed immediately by LSP clients.
96                         s.showCommandError(ctx, command.Title, err)
97                 default:
98                         work.end(command.ID() + ": completed")
99                 }
100         }
101         if command.Async {
102                 go run()
103         } else {
104                 run()
105         }
106         // Errors running the command are displayed to the user above, so don't
107         // return them.
108         return nil, nil
109 }
110
111 func (s *Server) runSuggestedFixCommand(ctx context.Context, command *source.Command, args []json.RawMessage) error {
112         var uri protocol.DocumentURI
113         var rng protocol.Range
114         if err := source.UnmarshalArgs(args, &uri, &rng); err != nil {
115                 return err
116         }
117         snapshot, fh, ok, release, err := s.beginFileRequest(ctx, uri, source.Go)
118         defer release()
119         if !ok {
120                 return err
121         }
122         edits, err := command.SuggestedFix(ctx, snapshot, fh, rng)
123         if err != nil {
124                 return err
125         }
126         r, err := s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
127                 Edit: protocol.WorkspaceEdit{
128                         DocumentChanges: edits,
129                 },
130         })
131         if err != nil {
132                 return err
133         }
134         if !r.Applied {
135                 return errors.New(r.FailureReason)
136         }
137         return nil
138 }
139
140 func (s *Server) showCommandError(ctx context.Context, title string, err error) {
141         // Command error messages should not be cancelable.
142         ctx = xcontext.Detach(ctx)
143         if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
144                 Type:    protocol.Error,
145                 Message: fmt.Sprintf("%s failed: %v", title, err),
146         }); err != nil {
147                 event.Error(ctx, title+": failed to show message", err)
148         }
149 }
150
151 func (s *Server) runCommand(ctx context.Context, work *workDone, command *source.Command, args []json.RawMessage) (err error) {
152         // If the command has a suggested fix function available, use it and apply
153         // the edits to the workspace.
154         if command.IsSuggestedFix() {
155                 return s.runSuggestedFixCommand(ctx, command, args)
156         }
157         switch command {
158         case source.CommandTest:
159                 var uri protocol.DocumentURI
160                 var tests, benchmarks []string
161                 if err := source.UnmarshalArgs(args, &uri, &tests, &benchmarks); err != nil {
162                         return err
163                 }
164                 snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
165                 defer release()
166                 if !ok {
167                         return err
168                 }
169                 return s.runTests(ctx, snapshot, uri, work, tests, benchmarks)
170         case source.CommandGenerate:
171                 var uri protocol.DocumentURI
172                 var recursive bool
173                 if err := source.UnmarshalArgs(args, &uri, &recursive); err != nil {
174                         return err
175                 }
176                 snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
177                 defer release()
178                 if !ok {
179                         return err
180                 }
181                 return s.runGoGenerate(ctx, snapshot, uri.SpanURI(), recursive, work)
182         case source.CommandRegenerateCgo:
183                 var uri protocol.DocumentURI
184                 if err := source.UnmarshalArgs(args, &uri); err != nil {
185                         return err
186                 }
187                 mod := source.FileModification{
188                         URI:    uri.SpanURI(),
189                         Action: source.InvalidateMetadata,
190                 }
191                 return s.didModifyFiles(ctx, []source.FileModification{mod}, FromRegenerateCgo)
192         case source.CommandTidy, source.CommandVendor:
193                 var uri protocol.DocumentURI
194                 if err := source.UnmarshalArgs(args, &uri); err != nil {
195                         return err
196                 }
197                 snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
198                 defer release()
199                 if !ok {
200                         return err
201                 }
202                 // The flow for `go mod tidy` and `go mod vendor` is almost identical,
203                 // so we combine them into one case for convenience.
204                 action := "tidy"
205                 if command == source.CommandVendor {
206                         action = "vendor"
207                 }
208                 return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile|source.AllowNetwork, uri.SpanURI(), "mod", []string{action})
209         case source.CommandUpdateGoSum:
210                 var uri protocol.DocumentURI
211                 if err := source.UnmarshalArgs(args, &uri); err != nil {
212                         return err
213                 }
214                 snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
215                 defer release()
216                 if !ok {
217                         return err
218                 }
219                 return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile|source.AllowNetwork, uri.SpanURI(), "list", []string{"all"})
220         case source.CommandAddDependency, source.CommandUpgradeDependency:
221                 var uri protocol.DocumentURI
222                 var goCmdArgs []string
223                 var addRequire bool
224                 if err := source.UnmarshalArgs(args, &uri, &addRequire, &goCmdArgs); err != nil {
225                         return err
226                 }
227                 snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
228                 defer release()
229                 if !ok {
230                         return err
231                 }
232                 return s.runGoGetModule(ctx, snapshot, uri.SpanURI(), addRequire, goCmdArgs)
233         case source.CommandRemoveDependency:
234                 var uri protocol.DocumentURI
235                 var modulePath string
236                 var onlyError bool
237                 if err := source.UnmarshalArgs(args, &uri, &onlyError, &modulePath); err != nil {
238                         return err
239                 }
240                 snapshot, fh, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
241                 defer release()
242                 if !ok {
243                         return err
244                 }
245                 // If the module is tidied apart from the one unused diagnostic, we can
246                 // run `go get module@none`, and then run `go mod tidy`. Otherwise, we
247                 // must make textual edits.
248                 // TODO(rstambler): In Go 1.17+, we will be able to use the go command
249                 // without checking if the module is tidy.
250                 if onlyError {
251                         if err := s.runGoGetModule(ctx, snapshot, uri.SpanURI(), false, []string{modulePath + "@none"}); err != nil {
252                                 return err
253                         }
254                         return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile|source.AllowNetwork, uri.SpanURI(), "mod", []string{"tidy"})
255                 }
256                 pm, err := snapshot.ParseMod(ctx, fh)
257                 if err != nil {
258                         return err
259                 }
260                 edits, err := dropDependency(snapshot, pm, modulePath)
261                 if err != nil {
262                         return err
263                 }
264                 response, err := s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
265                         Edit: protocol.WorkspaceEdit{
266                                 DocumentChanges: []protocol.TextDocumentEdit{{
267                                         TextDocument: protocol.VersionedTextDocumentIdentifier{
268                                                 Version: fh.Version(),
269                                                 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
270                                                         URI: protocol.URIFromSpanURI(fh.URI()),
271                                                 },
272                                         },
273                                         Edits: edits,
274                                 }},
275                         },
276                 })
277                 if err != nil {
278                         return err
279                 }
280                 if !response.Applied {
281                         return fmt.Errorf("edits not applied because of %s", response.FailureReason)
282                 }
283         case source.CommandGoGetPackage:
284                 var uri protocol.DocumentURI
285                 var pkg string
286                 if err := source.UnmarshalArgs(args, &uri, &pkg); err != nil {
287                         return err
288                 }
289                 snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
290                 defer release()
291                 if !ok {
292                         return err
293                 }
294                 return s.runGoGetPackage(ctx, snapshot, uri.SpanURI(), pkg)
295
296         case source.CommandToggleDetails:
297                 var fileURI protocol.DocumentURI
298                 if err := source.UnmarshalArgs(args, &fileURI); err != nil {
299                         return err
300                 }
301                 pkgDir := span.URIFromPath(filepath.Dir(fileURI.SpanURI().Filename()))
302                 s.gcOptimizationDetailsMu.Lock()
303                 if _, ok := s.gcOptimizationDetails[pkgDir]; ok {
304                         delete(s.gcOptimizationDetails, pkgDir)
305                         s.clearDiagnosticSource(gcDetailsSource)
306                 } else {
307                         s.gcOptimizationDetails[pkgDir] = struct{}{}
308                 }
309                 s.gcOptimizationDetailsMu.Unlock()
310                 // need to recompute diagnostics.
311                 // so find the snapshot
312                 snapshot, _, ok, release, err := s.beginFileRequest(ctx, fileURI, source.UnknownKind)
313                 defer release()
314                 if !ok {
315                         return err
316                 }
317                 s.diagnoseSnapshot(snapshot, nil, false)
318         case source.CommandGenerateGoplsMod:
319                 var v source.View
320                 if len(args) == 0 {
321                         views := s.session.Views()
322                         if len(views) != 1 {
323                                 return fmt.Errorf("cannot resolve view: have %d views", len(views))
324                         }
325                         v = views[0]
326                 } else {
327                         var uri protocol.DocumentURI
328                         if err := source.UnmarshalArgs(args, &uri); err != nil {
329                                 return err
330                         }
331                         var err error
332                         v, err = s.session.ViewOf(uri.SpanURI())
333                         if err != nil {
334                                 return err
335                         }
336                 }
337                 snapshot, release := v.Snapshot(ctx)
338                 defer release()
339                 modFile, err := cache.BuildGoplsMod(ctx, v.Folder(), snapshot)
340                 if err != nil {
341                         return errors.Errorf("getting workspace mod file: %w", err)
342                 }
343                 content, err := modFile.Format()
344                 if err != nil {
345                         return errors.Errorf("formatting mod file: %w", err)
346                 }
347                 filename := filepath.Join(v.Folder().Filename(), "gopls.mod")
348                 if err := ioutil.WriteFile(filename, content, 0644); err != nil {
349                         return errors.Errorf("writing mod file: %w", err)
350                 }
351         default:
352                 return fmt.Errorf("unsupported command: %s", command.ID())
353         }
354         return nil
355 }
356
357 // dropDependency returns the edits to remove the given require from the go.mod
358 // file.
359 func dropDependency(snapshot source.Snapshot, pm *source.ParsedModule, modulePath string) ([]protocol.TextEdit, error) {
360         // We need a private copy of the parsed go.mod file, since we're going to
361         // modify it.
362         copied, err := modfile.Parse("", pm.Mapper.Content, nil)
363         if err != nil {
364                 return nil, err
365         }
366         if err := copied.DropRequire(modulePath); err != nil {
367                 return nil, err
368         }
369         copied.Cleanup()
370         newContent, err := copied.Format()
371         if err != nil {
372                 return nil, err
373         }
374         // Calculate the edits to be made due to the change.
375         diff, err := snapshot.View().Options().ComputeEdits(pm.URI, string(pm.Mapper.Content), string(newContent))
376         if err != nil {
377                 return nil, err
378         }
379         return source.ToProtocolEdits(pm.Mapper, diff)
380 }
381
382 func (s *Server) runTests(ctx context.Context, snapshot source.Snapshot, uri protocol.DocumentURI, work *workDone, tests, benchmarks []string) error {
383         pkgs, err := snapshot.PackagesForFile(ctx, uri.SpanURI(), source.TypecheckWorkspace)
384         if err != nil {
385                 return err
386         }
387         if len(pkgs) == 0 {
388                 return fmt.Errorf("package could not be found for file: %s", uri.SpanURI().Filename())
389         }
390         pkgPath := pkgs[0].ForTest()
391
392         // create output
393         buf := &bytes.Buffer{}
394         ew := &eventWriter{ctx: ctx, operation: "test"}
395         out := io.MultiWriter(ew, workDoneWriter{work}, buf)
396
397         // Run `go test -run Func` on each test.
398         var failedTests int
399         for _, funcName := range tests {
400                 inv := &gocommand.Invocation{
401                         Verb:       "test",
402                         Args:       []string{pkgPath, "-v", "-count=1", "-run", fmt.Sprintf("^%s$", funcName)},
403                         WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
404                 }
405                 if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, out, out); err != nil {
406                         if errors.Is(err, context.Canceled) {
407                                 return err
408                         }
409                         failedTests++
410                 }
411         }
412
413         // Run `go test -run=^$ -bench Func` on each test.
414         var failedBenchmarks int
415         for _, funcName := range benchmarks {
416                 inv := &gocommand.Invocation{
417                         Verb:       "test",
418                         Args:       []string{pkgPath, "-v", "-run=^$", "-bench", fmt.Sprintf("^%s$", funcName)},
419                         WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
420                 }
421                 if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, out, out); err != nil {
422                         if errors.Is(err, context.Canceled) {
423                                 return err
424                         }
425                         failedBenchmarks++
426                 }
427         }
428
429         var title string
430         if len(tests) > 0 && len(benchmarks) > 0 {
431                 title = "tests and benchmarks"
432         } else if len(tests) > 0 {
433                 title = "tests"
434         } else if len(benchmarks) > 0 {
435                 title = "benchmarks"
436         } else {
437                 return errors.New("No functions were provided")
438         }
439         message := fmt.Sprintf("all %s passed", title)
440         if failedTests > 0 && failedBenchmarks > 0 {
441                 message = fmt.Sprintf("%d / %d tests failed and %d / %d benchmarks failed", failedTests, len(tests), failedBenchmarks, len(benchmarks))
442         } else if failedTests > 0 {
443                 message = fmt.Sprintf("%d / %d tests failed", failedTests, len(tests))
444         } else if failedBenchmarks > 0 {
445                 message = fmt.Sprintf("%d / %d benchmarks failed", failedBenchmarks, len(benchmarks))
446         }
447         if failedTests > 0 || failedBenchmarks > 0 {
448                 message += "\n" + buf.String()
449         }
450
451         return s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
452                 Type:    protocol.Info,
453                 Message: message,
454         })
455 }
456
457 func (s *Server) runGoGenerate(ctx context.Context, snapshot source.Snapshot, dir span.URI, recursive bool, work *workDone) error {
458         ctx, cancel := context.WithCancel(ctx)
459         defer cancel()
460
461         er := &eventWriter{ctx: ctx, operation: "generate"}
462
463         pattern := "."
464         if recursive {
465                 pattern = "./..."
466         }
467
468         inv := &gocommand.Invocation{
469                 Verb:       "generate",
470                 Args:       []string{"-x", pattern},
471                 WorkingDir: dir.Filename(),
472         }
473         stderr := io.MultiWriter(er, workDoneWriter{work})
474         if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, er, stderr); err != nil {
475                 return err
476         }
477         return nil
478 }
479
480 func (s *Server) runGoGetPackage(ctx context.Context, snapshot source.Snapshot, uri span.URI, pkg string) error {
481         stdout, err := snapshot.RunGoCommandDirect(ctx, source.WriteTemporaryModFile|source.AllowNetwork, &gocommand.Invocation{
482                 Verb:       "list",
483                 Args:       []string{"-f", "{{.Module.Path}}@{{.Module.Version}}", pkg},
484                 WorkingDir: filepath.Dir(uri.Filename()),
485         })
486         if err != nil {
487                 return err
488         }
489         ver := strings.TrimSpace(stdout.String())
490         return s.runGoGetModule(ctx, snapshot, uri, true, []string{ver})
491 }
492
493 func (s *Server) runGoGetModule(ctx context.Context, snapshot source.Snapshot, uri span.URI, addRequire bool, args []string) error {
494         if addRequire {
495                 // Using go get to create a new dependency results in an
496                 // `// indirect` comment we may not want. The only way to avoid it
497                 // is to add the require as direct first. Then we can use go get to
498                 // update go.sum and tidy up.
499                 if err := runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile, uri, "mod", append([]string{"edit", "-require"}, args...)); err != nil {
500                         return err
501                 }
502         }
503         return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile|source.AllowNetwork, uri, "get", append([]string{"-d"}, args...))
504 }
505
506 func runSimpleGoCommand(ctx context.Context, snapshot source.Snapshot, mode source.InvocationFlags, uri span.URI, verb string, args []string) error {
507         _, err := snapshot.RunGoCommandDirect(ctx, mode, &gocommand.Invocation{
508                 Verb:       verb,
509                 Args:       args,
510                 WorkingDir: filepath.Dir(uri.Filename()),
511         })
512         return err
513 }