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