Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / internal / lsp / code_action.go
1 // Copyright 2018 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         "context"
9         "fmt"
10         "sort"
11         "strings"
12
13         "golang.org/x/tools/go/analysis"
14         "golang.org/x/tools/internal/event"
15         "golang.org/x/tools/internal/imports"
16         "golang.org/x/tools/internal/lsp/debug/tag"
17         "golang.org/x/tools/internal/lsp/protocol"
18         "golang.org/x/tools/internal/lsp/source"
19         "golang.org/x/tools/internal/span"
20 )
21
22 func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
23         snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
24         defer release()
25         if !ok {
26                 return nil, err
27         }
28         uri := fh.URI()
29
30         // Determine the supported actions for this file kind.
31         supportedCodeActions, ok := snapshot.View().Options().SupportedCodeActions[fh.Kind()]
32         if !ok {
33                 return nil, fmt.Errorf("no supported code actions for %v file kind", fh.Kind())
34         }
35
36         // The Only field of the context specifies which code actions the client wants.
37         // If Only is empty, assume that the client wants all of the non-explicit code actions.
38         var wanted map[protocol.CodeActionKind]bool
39
40         // Explicit Code Actions are opt-in and shouldn't be returned to the client unless
41         // requested using Only.
42         // TODO: Add other CodeLenses such as GoGenerate, RegenerateCgo, etc..
43         explicit := map[protocol.CodeActionKind]bool{
44                 protocol.GoTest: true,
45         }
46
47         if len(params.Context.Only) == 0 {
48                 wanted = supportedCodeActions
49         } else {
50                 wanted = make(map[protocol.CodeActionKind]bool)
51                 for _, only := range params.Context.Only {
52                         wanted[only] = supportedCodeActions[only] || explicit[only]
53                 }
54         }
55         if len(wanted) == 0 {
56                 return nil, fmt.Errorf("no supported code action to execute for %s, wanted %v", uri, params.Context.Only)
57         }
58
59         var codeActions []protocol.CodeAction
60         switch fh.Kind() {
61         case source.Mod:
62                 if diagnostics := params.Context.Diagnostics; len(diagnostics) > 0 {
63                         modQuickFixes, err := moduleQuickFixes(ctx, snapshot, fh, diagnostics)
64                         if source.IsNonFatalGoModError(err) {
65                                 return nil, nil
66                         }
67                         if err != nil {
68                                 return nil, err
69                         }
70                         codeActions = append(codeActions, modQuickFixes...)
71                 }
72                 if wanted[protocol.SourceOrganizeImports] {
73                         action, err := goModTidy(ctx, snapshot, fh)
74                         if source.IsNonFatalGoModError(err) {
75                                 return nil, nil
76                         }
77                         if err != nil {
78                                 return nil, err
79                         }
80                         codeActions = append(codeActions, *action)
81                 }
82         case source.Go:
83                 // Don't suggest fixes for generated files, since they are generally
84                 // not useful and some editors may apply them automatically on save.
85                 if source.IsGenerated(ctx, snapshot, uri) {
86                         return nil, nil
87                 }
88                 diagnostics := params.Context.Diagnostics
89
90                 // First, process any missing imports and pair them with the
91                 // diagnostics they fix.
92                 if wantQuickFixes := wanted[protocol.QuickFix] && len(diagnostics) > 0; wantQuickFixes || wanted[protocol.SourceOrganizeImports] {
93                         importEdits, importEditsPerFix, err := source.AllImportsFixes(ctx, snapshot, fh)
94                         if err != nil {
95                                 event.Error(ctx, "imports fixes", err, tag.File.Of(fh.URI().Filename()))
96                         }
97                         // Separate this into a set of codeActions per diagnostic, where
98                         // each action is the addition, removal, or renaming of one import.
99                         if wantQuickFixes {
100                                 for _, importFix := range importEditsPerFix {
101                                         fixes := importDiagnostics(importFix.Fix, diagnostics)
102                                         if len(fixes) == 0 {
103                                                 continue
104                                         }
105                                         codeActions = append(codeActions, protocol.CodeAction{
106                                                 Title: importFixTitle(importFix.Fix),
107                                                 Kind:  protocol.QuickFix,
108                                                 Edit: protocol.WorkspaceEdit{
109                                                         DocumentChanges: documentChanges(fh, importFix.Edits),
110                                                 },
111                                                 Diagnostics: fixes,
112                                         })
113                                 }
114                         }
115                         // Send all of the import edits as one code action if the file is
116                         // being organized.
117                         if wanted[protocol.SourceOrganizeImports] && len(importEdits) > 0 {
118                                 codeActions = append(codeActions, protocol.CodeAction{
119                                         Title: "Organize Imports",
120                                         Kind:  protocol.SourceOrganizeImports,
121                                         Edit: protocol.WorkspaceEdit{
122                                                 DocumentChanges: documentChanges(fh, importEdits),
123                                         },
124                                 })
125                         }
126                 }
127                 if ctx.Err() != nil {
128                         return nil, ctx.Err()
129                 }
130                 pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckFull, source.WidestPackage)
131                 if err != nil {
132                         return nil, err
133                 }
134                 if (wanted[protocol.QuickFix] || wanted[protocol.SourceFixAll]) && len(diagnostics) > 0 {
135                         analysisQuickFixes, highConfidenceEdits, err := analysisFixes(ctx, snapshot, pkg, diagnostics)
136                         if err != nil {
137                                 return nil, err
138                         }
139                         if wanted[protocol.QuickFix] {
140                                 // Add the quick fixes reported by go/analysis.
141                                 codeActions = append(codeActions, analysisQuickFixes...)
142
143                                 // If there are any diagnostics relating to the go.mod file,
144                                 // add their corresponding quick fixes.
145                                 modQuickFixes, err := moduleQuickFixes(ctx, snapshot, fh, diagnostics)
146                                 if source.IsNonFatalGoModError(err) {
147                                         // Not a fatal error.
148                                         event.Error(ctx, "module suggested fixes failed", err, tag.Directory.Of(snapshot.View().Folder()))
149                                 }
150                                 codeActions = append(codeActions, modQuickFixes...)
151                         }
152                         if wanted[protocol.SourceFixAll] && len(highConfidenceEdits) > 0 {
153                                 codeActions = append(codeActions, protocol.CodeAction{
154                                         Title: "Simplifications",
155                                         Kind:  protocol.SourceFixAll,
156                                         Edit: protocol.WorkspaceEdit{
157                                                 DocumentChanges: highConfidenceEdits,
158                                         },
159                                 })
160                         }
161                 }
162                 if ctx.Err() != nil {
163                         return nil, ctx.Err()
164                 }
165                 // Add any suggestions that do not necessarily fix any diagnostics.
166                 if wanted[protocol.RefactorRewrite] {
167                         fixes, err := convenienceFixes(ctx, snapshot, pkg, uri, params.Range)
168                         if err != nil {
169                                 return nil, err
170                         }
171                         codeActions = append(codeActions, fixes...)
172                 }
173                 if wanted[protocol.RefactorExtract] {
174                         fixes, err := extractionFixes(ctx, snapshot, pkg, uri, params.Range)
175                         if err != nil {
176                                 return nil, err
177                         }
178                         codeActions = append(codeActions, fixes...)
179                 }
180
181                 if wanted[protocol.GoTest] {
182                         fixes, err := goTest(ctx, snapshot, uri, params.Range)
183                         if err != nil {
184                                 return nil, err
185                         }
186                         codeActions = append(codeActions, fixes...)
187                 }
188
189         default:
190                 // Unsupported file kind for a code action.
191                 return nil, nil
192         }
193         return codeActions, nil
194 }
195
196 func (s *Server) getSupportedCodeActions() []protocol.CodeActionKind {
197         allCodeActionKinds := make(map[protocol.CodeActionKind]struct{})
198         for _, kinds := range s.session.Options().SupportedCodeActions {
199                 for kind := range kinds {
200                         allCodeActionKinds[kind] = struct{}{}
201                 }
202         }
203         var result []protocol.CodeActionKind
204         for kind := range allCodeActionKinds {
205                 result = append(result, kind)
206         }
207         sort.Slice(result, func(i, j int) bool {
208                 return result[i] < result[j]
209         })
210         return result
211 }
212
213 func importFixTitle(fix *imports.ImportFix) string {
214         var str string
215         switch fix.FixType {
216         case imports.AddImport:
217                 str = fmt.Sprintf("Add import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
218         case imports.DeleteImport:
219                 str = fmt.Sprintf("Delete import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
220         case imports.SetImportName:
221                 str = fmt.Sprintf("Rename import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
222         }
223         return str
224 }
225
226 func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic) (results []protocol.Diagnostic) {
227         for _, diagnostic := range diagnostics {
228                 switch {
229                 // "undeclared name: X" may be an unresolved import.
230                 case strings.HasPrefix(diagnostic.Message, "undeclared name: "):
231                         ident := strings.TrimPrefix(diagnostic.Message, "undeclared name: ")
232                         if ident == fix.IdentName {
233                                 results = append(results, diagnostic)
234                         }
235                 // "could not import: X" may be an invalid import.
236                 case strings.HasPrefix(diagnostic.Message, "could not import: "):
237                         ident := strings.TrimPrefix(diagnostic.Message, "could not import: ")
238                         if ident == fix.IdentName {
239                                 results = append(results, diagnostic)
240                         }
241                 // "X imported but not used" is an unused import.
242                 // "X imported but not used as Y" is an unused import.
243                 case strings.Contains(diagnostic.Message, " imported but not used"):
244                         idx := strings.Index(diagnostic.Message, " imported but not used")
245                         importPath := diagnostic.Message[:idx]
246                         if importPath == fmt.Sprintf("%q", fix.StmtInfo.ImportPath) {
247                                 results = append(results, diagnostic)
248                         }
249                 }
250         }
251         return results
252 }
253
254 func analysisFixes(ctx context.Context, snapshot source.Snapshot, pkg source.Package, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, []protocol.TextDocumentEdit, error) {
255         if len(diagnostics) == 0 {
256                 return nil, nil, nil
257         }
258         var (
259                 codeActions       []protocol.CodeAction
260                 sourceFixAllEdits []protocol.TextDocumentEdit
261         )
262         for _, diag := range diagnostics {
263                 srcErr, analyzer, ok := findSourceError(ctx, snapshot, pkg.ID(), diag)
264                 if !ok {
265                         continue
266                 }
267                 // If the suggested fix for the diagnostic is expected to be separate,
268                 // see if there are any supported commands available.
269                 if analyzer.Command != nil {
270                         action, err := diagnosticToCommandCodeAction(ctx, snapshot, srcErr, &diag, protocol.QuickFix)
271                         if err != nil {
272                                 return nil, nil, err
273                         }
274                         codeActions = append(codeActions, *action)
275                         continue
276                 }
277                 for _, fix := range srcErr.SuggestedFixes {
278                         action := protocol.CodeAction{
279                                 Title:       fix.Title,
280                                 Kind:        protocol.QuickFix,
281                                 Diagnostics: []protocol.Diagnostic{diag},
282                                 Edit:        protocol.WorkspaceEdit{},
283                         }
284                         for uri, edits := range fix.Edits {
285                                 fh, err := snapshot.GetVersionedFile(ctx, uri)
286                                 if err != nil {
287                                         return nil, nil, err
288                                 }
289                                 docChanges := documentChanges(fh, edits)
290                                 if analyzer.HighConfidence {
291                                         sourceFixAllEdits = append(sourceFixAllEdits, docChanges...)
292                                 }
293                                 action.Edit.DocumentChanges = append(action.Edit.DocumentChanges, docChanges...)
294                         }
295                         codeActions = append(codeActions, action)
296                 }
297         }
298         return codeActions, sourceFixAllEdits, nil
299 }
300
301 func findSourceError(ctx context.Context, snapshot source.Snapshot, pkgID string, diag protocol.Diagnostic) (*source.Error, source.Analyzer, bool) {
302         analyzer := diagnosticToAnalyzer(snapshot, diag.Source, diag.Message)
303         if analyzer == nil {
304                 return nil, source.Analyzer{}, false
305         }
306         analysisErrors, err := snapshot.Analyze(ctx, pkgID, analyzer.Analyzer)
307         if err != nil {
308                 return nil, source.Analyzer{}, false
309         }
310         for _, err := range analysisErrors {
311                 if err.Message != diag.Message {
312                         continue
313                 }
314                 if protocol.CompareRange(err.Range, diag.Range) != 0 {
315                         continue
316                 }
317                 if err.Category != analyzer.Analyzer.Name {
318                         continue
319                 }
320                 // The error matches.
321                 return err, *analyzer, true
322         }
323         return nil, source.Analyzer{}, false
324 }
325
326 // diagnosticToAnalyzer return the analyzer associated with a given diagnostic.
327 // It assumes that the diagnostic's source will be the name of the analyzer.
328 // If this changes, this approach will need to be reworked.
329 func diagnosticToAnalyzer(snapshot source.Snapshot, src, msg string) (analyzer *source.Analyzer) {
330         // Make sure that the analyzer we found is enabled.
331         defer func() {
332                 if analyzer != nil && !analyzer.IsEnabled(snapshot.View()) {
333                         analyzer = nil
334                 }
335         }()
336         if a, ok := snapshot.View().Options().DefaultAnalyzers[src]; ok {
337                 return &a
338         }
339         if a, ok := snapshot.View().Options().StaticcheckAnalyzers[src]; ok {
340                 return &a
341         }
342         if a, ok := snapshot.View().Options().ConvenienceAnalyzers[src]; ok {
343                 return &a
344         }
345         // Hack: We publish diagnostics with the source "compiler" for type errors,
346         // but these analyzers have different names. Try both possibilities.
347         if a, ok := snapshot.View().Options().TypeErrorAnalyzers[src]; ok {
348                 return &a
349         }
350         if src != "compiler" {
351                 return nil
352         }
353         for _, a := range snapshot.View().Options().TypeErrorAnalyzers {
354                 if a.FixesError(msg) {
355                         return &a
356                 }
357         }
358         return nil
359 }
360
361 func convenienceFixes(ctx context.Context, snapshot source.Snapshot, pkg source.Package, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
362         var analyzers []*analysis.Analyzer
363         for _, a := range snapshot.View().Options().ConvenienceAnalyzers {
364                 if !a.IsEnabled(snapshot.View()) {
365                         continue
366                 }
367                 if a.Command == nil {
368                         event.Error(ctx, "convenienceFixes", fmt.Errorf("no suggested fixes for convenience analyzer %s", a.Analyzer.Name))
369                         continue
370                 }
371                 analyzers = append(analyzers, a.Analyzer)
372         }
373         diagnostics, err := snapshot.Analyze(ctx, pkg.ID(), analyzers...)
374         if err != nil {
375                 return nil, err
376         }
377         var codeActions []protocol.CodeAction
378         for _, d := range diagnostics {
379                 // For now, only show diagnostics for matching lines. Maybe we should
380                 // alter this behavior in the future, depending on the user experience.
381                 if d.URI != uri {
382                         continue
383                 }
384
385                 if !protocol.Intersect(d.Range, rng) {
386                         continue
387                 }
388                 action, err := diagnosticToCommandCodeAction(ctx, snapshot, d, nil, protocol.RefactorRewrite)
389                 if err != nil {
390                         return nil, err
391                 }
392                 codeActions = append(codeActions, *action)
393         }
394         return codeActions, nil
395 }
396
397 func diagnosticToCommandCodeAction(ctx context.Context, snapshot source.Snapshot, e *source.Error, d *protocol.Diagnostic, kind protocol.CodeActionKind) (*protocol.CodeAction, error) {
398         // The fix depends on the category of the analyzer. The diagnostic may be
399         // nil, so use the error's category.
400         analyzer := diagnosticToAnalyzer(snapshot, e.Category, e.Message)
401         if analyzer == nil {
402                 return nil, fmt.Errorf("no convenience analyzer for category %s", e.Category)
403         }
404         if analyzer.Command == nil {
405                 return nil, fmt.Errorf("no command for convenience analyzer %s", analyzer.Analyzer.Name)
406         }
407         jsonArgs, err := source.MarshalArgs(e.URI, e.Range)
408         if err != nil {
409                 return nil, err
410         }
411         var diagnostics []protocol.Diagnostic
412         if d != nil {
413                 diagnostics = append(diagnostics, *d)
414         }
415         return &protocol.CodeAction{
416                 Title:       e.Message,
417                 Kind:        kind,
418                 Diagnostics: diagnostics,
419                 Command: &protocol.Command{
420                         Command:   analyzer.Command.ID(),
421                         Title:     e.Message,
422                         Arguments: jsonArgs,
423                 },
424         }, nil
425 }
426
427 func extractionFixes(ctx context.Context, snapshot source.Snapshot, pkg source.Package, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
428         if rng.Start == rng.End {
429                 return nil, nil
430         }
431         fh, err := snapshot.GetFile(ctx, uri)
432         if err != nil {
433                 return nil, err
434         }
435         jsonArgs, err := source.MarshalArgs(uri, rng)
436         if err != nil {
437                 return nil, err
438         }
439         var actions []protocol.CodeAction
440         for _, command := range []*source.Command{
441                 source.CommandExtractFunction,
442                 source.CommandExtractVariable,
443         } {
444                 if !command.Applies(ctx, snapshot, fh, rng) {
445                         continue
446                 }
447                 actions = append(actions, protocol.CodeAction{
448                         Title: command.Title,
449                         Kind:  protocol.RefactorExtract,
450                         Command: &protocol.Command{
451                                 Command:   command.ID(),
452                                 Arguments: jsonArgs,
453                         },
454                 })
455         }
456         return actions, nil
457 }
458
459 func documentChanges(fh source.VersionedFileHandle, edits []protocol.TextEdit) []protocol.TextDocumentEdit {
460         return []protocol.TextDocumentEdit{
461                 {
462                         TextDocument: protocol.VersionedTextDocumentIdentifier{
463                                 Version: fh.Version(),
464                                 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
465                                         URI: protocol.URIFromSpanURI(fh.URI()),
466                                 },
467                         },
468                         Edits: edits,
469                 },
470         }
471 }
472
473 func moduleQuickFixes(ctx context.Context, snapshot source.Snapshot, fh source.VersionedFileHandle, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
474         var modFH source.VersionedFileHandle
475         switch fh.Kind() {
476         case source.Mod:
477                 modFH = fh
478         case source.Go:
479                 modURI := snapshot.GoModForFile(ctx, fh.URI())
480                 if modURI == "" {
481                         return nil, nil
482                 }
483                 var err error
484                 modFH, err = snapshot.GetVersionedFile(ctx, modURI)
485                 if err != nil {
486                         return nil, err
487                 }
488         }
489         tidied, err := snapshot.ModTidy(ctx, modFH)
490         if err != nil {
491                 return nil, err
492         }
493         var quickFixes []protocol.CodeAction
494         for _, e := range tidied.Errors {
495                 var diag *protocol.Diagnostic
496                 for _, d := range diagnostics {
497                         if sameDiagnostic(d, e) {
498                                 diag = &d
499                                 break
500                         }
501                 }
502                 if diag == nil {
503                         continue
504                 }
505                 for _, fix := range e.SuggestedFixes {
506                         action := protocol.CodeAction{
507                                 Title:       fix.Title,
508                                 Kind:        protocol.QuickFix,
509                                 Diagnostics: []protocol.Diagnostic{*diag},
510                                 Edit:        protocol.WorkspaceEdit{},
511                         }
512                         for uri, edits := range fix.Edits {
513                                 if uri != modFH.URI() {
514                                         continue
515                                 }
516                                 action.Edit.DocumentChanges = append(action.Edit.DocumentChanges, protocol.TextDocumentEdit{
517                                         TextDocument: protocol.VersionedTextDocumentIdentifier{
518                                                 Version: modFH.Version(),
519                                                 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
520                                                         URI: protocol.URIFromSpanURI(modFH.URI()),
521                                                 },
522                                         },
523                                         Edits: edits,
524                                 })
525                         }
526                         quickFixes = append(quickFixes, action)
527                 }
528         }
529         return quickFixes, nil
530 }
531
532 func sameDiagnostic(d protocol.Diagnostic, e source.Error) bool {
533         return d.Message == e.Message && protocol.CompareRange(d.Range, e.Range) == 0 && d.Source == e.Category
534 }
535
536 func goModTidy(ctx context.Context, snapshot source.Snapshot, fh source.VersionedFileHandle) (*protocol.CodeAction, error) {
537         tidied, err := snapshot.ModTidy(ctx, fh)
538         if err != nil {
539                 return nil, err
540         }
541         left, err := fh.Read()
542         if err != nil {
543                 return nil, err
544         }
545         right := tidied.TidiedContent
546         edits := snapshot.View().Options().ComputeEdits(fh.URI(), string(left), string(right))
547         protocolEdits, err := source.ToProtocolEdits(tidied.Parsed.Mapper, edits)
548         if err != nil {
549                 return nil, err
550         }
551         return &protocol.CodeAction{
552                 Title: "Tidy",
553                 Kind:  protocol.SourceOrganizeImports,
554                 Edit: protocol.WorkspaceEdit{
555                         DocumentChanges: []protocol.TextDocumentEdit{{
556                                 TextDocument: protocol.VersionedTextDocumentIdentifier{
557                                         Version: fh.Version(),
558                                         TextDocumentIdentifier: protocol.TextDocumentIdentifier{
559                                                 URI: protocol.URIFromSpanURI(fh.URI()),
560                                         },
561                                 },
562                                 Edits: protocolEdits,
563                         }},
564                 },
565         }, err
566 }
567
568 func goTest(ctx context.Context, snapshot source.Snapshot, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
569         fh, err := snapshot.GetFile(ctx, uri)
570         if err != nil {
571                 return nil, err
572         }
573         fns, err := source.TestsAndBenchmarks(ctx, snapshot, fh)
574         if err != nil {
575                 return nil, err
576         }
577
578         var tests, benchmarks []string
579         for _, fn := range fns.Tests {
580                 if !protocol.Intersect(fn.Rng, rng) {
581                         continue
582                 }
583                 tests = append(tests, fn.Name)
584         }
585         for _, fn := range fns.Benchmarks {
586                 if !protocol.Intersect(fn.Rng, rng) {
587                         continue
588                 }
589                 benchmarks = append(benchmarks, fn.Name)
590         }
591
592         if len(tests) == 0 && len(benchmarks) == 0 {
593                 return nil, nil
594         }
595
596         jsonArgs, err := source.MarshalArgs(uri, tests, benchmarks)
597         if err != nil {
598                 return nil, err
599         }
600         return []protocol.CodeAction{{
601                 Title: source.CommandTest.Name,
602                 Kind:  protocol.GoTest,
603                 Command: &protocol.Command{
604                         Title:     source.CommandTest.Title,
605                         Command:   source.CommandTest.ID(),
606                         Arguments: jsonArgs,
607                 },
608         }}, nil
609 }