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