.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / 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/internal/event"
14         "golang.org/x/tools/internal/imports"
15         "golang.org/x/tools/internal/lsp/command"
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         errors "golang.org/x/xerrors"
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                         diags, err := mod.DiagnosticsForMod(ctx, snapshot, fh)
66                         if source.IsNonFatalGoModError(err) {
67                                 return nil, nil
68                         }
69                         if err != nil {
70                                 return nil, err
71                         }
72                         quickFixes, err := codeActionsMatchingDiagnostics(ctx, snapshot, diagnostics, diags)
73                         if err != nil {
74                                 return nil, err
75                         }
76                         codeActions = append(codeActions, quickFixes...)
77                 }
78         case source.Go:
79                 // Don't suggest fixes for generated files, since they are generally
80                 // not useful and some editors may apply them automatically on save.
81                 if source.IsGenerated(ctx, snapshot, uri) {
82                         return nil, nil
83                 }
84                 diagnostics := params.Context.Diagnostics
85
86                 // First, process any missing imports and pair them with the
87                 // diagnostics they fix.
88                 if wantQuickFixes := wanted[protocol.QuickFix] && len(diagnostics) > 0; wantQuickFixes || wanted[protocol.SourceOrganizeImports] {
89                         importEdits, importEditsPerFix, err := source.AllImportsFixes(ctx, snapshot, fh)
90                         if err != nil {
91                                 event.Error(ctx, "imports fixes", err, tag.File.Of(fh.URI().Filename()))
92                         }
93                         // Separate this into a set of codeActions per diagnostic, where
94                         // each action is the addition, removal, or renaming of one import.
95                         if wantQuickFixes {
96                                 for _, importFix := range importEditsPerFix {
97                                         fixes := importDiagnostics(importFix.Fix, diagnostics)
98                                         if len(fixes) == 0 {
99                                                 continue
100                                         }
101                                         codeActions = append(codeActions, protocol.CodeAction{
102                                                 Title: importFixTitle(importFix.Fix),
103                                                 Kind:  protocol.QuickFix,
104                                                 Edit: protocol.WorkspaceEdit{
105                                                         DocumentChanges: documentChanges(fh, importFix.Edits),
106                                                 },
107                                                 Diagnostics: fixes,
108                                         })
109                                 }
110                         }
111
112                         // Send all of the import edits as one code action if the file is
113                         // being organized.
114                         if wanted[protocol.SourceOrganizeImports] && len(importEdits) > 0 {
115                                 codeActions = append(codeActions, protocol.CodeAction{
116                                         Title: "Organize Imports",
117                                         Kind:  protocol.SourceOrganizeImports,
118                                         Edit: protocol.WorkspaceEdit{
119                                                 DocumentChanges: documentChanges(fh, importEdits),
120                                         },
121                                 })
122                         }
123                 }
124                 if ctx.Err() != nil {
125                         return nil, ctx.Err()
126                 }
127                 pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckFull, source.WidestPackage)
128                 if err != nil {
129                         return nil, err
130                 }
131
132                 pkgDiagnostics, err := snapshot.DiagnosePackage(ctx, pkg)
133                 if err != nil {
134                         return nil, err
135                 }
136                 analysisDiags, err := source.Analyze(ctx, snapshot, pkg, true)
137                 if err != nil {
138                         return nil, err
139                 }
140                 fileDiags := append(pkgDiagnostics[uri], analysisDiags[uri]...)
141
142                 // Split diagnostics into fixes, which must match incoming diagnostics,
143                 // and non-fixes, which must match the requested range. Build actions
144                 // for all of them.
145                 var fixDiags, nonFixDiags []*source.Diagnostic
146                 for _, d := range fileDiags {
147                         if len(d.SuggestedFixes) == 0 {
148                                 continue
149                         }
150                         kind := protocol.QuickFix
151                         if d.Analyzer != nil && d.Analyzer.ActionKind != "" {
152                                 kind = d.Analyzer.ActionKind
153                         }
154                         if kind == protocol.QuickFix || kind == protocol.SourceFixAll {
155                                 fixDiags = append(fixDiags, d)
156                         } else {
157                                 nonFixDiags = append(nonFixDiags, d)
158                         }
159                 }
160
161                 fixActions, err := codeActionsMatchingDiagnostics(ctx, snapshot, diagnostics, fixDiags)
162                 if err != nil {
163                         return nil, err
164                 }
165                 codeActions = append(codeActions, fixActions...)
166
167                 for _, nonfix := range nonFixDiags {
168                         // For now, only show diagnostics for matching lines. Maybe we should
169                         // alter this behavior in the future, depending on the user experience.
170                         if !protocol.Intersect(nonfix.Range, params.Range) {
171                                 continue
172                         }
173                         actions, err := codeActionsForDiagnostic(ctx, snapshot, nonfix, nil)
174                         if err != nil {
175                                 return nil, err
176                         }
177                         codeActions = append(codeActions, actions...)
178                 }
179
180                 if wanted[protocol.RefactorExtract] {
181                         fixes, err := extractionFixes(ctx, snapshot, pkg, uri, params.Range)
182                         if err != nil {
183                                 return nil, err
184                         }
185                         codeActions = append(codeActions, fixes...)
186                 }
187
188                 if wanted[protocol.GoTest] {
189                         fixes, err := goTest(ctx, snapshot, uri, params.Range)
190                         if err != nil {
191                                 return nil, err
192                         }
193                         codeActions = append(codeActions, fixes...)
194                 }
195
196         default:
197                 // Unsupported file kind for a code action.
198                 return nil, nil
199         }
200
201         var filtered []protocol.CodeAction
202         for _, action := range codeActions {
203                 if wanted[action.Kind] {
204                         filtered = append(filtered, action)
205                 }
206         }
207         return filtered, nil
208 }
209
210 func (s *Server) getSupportedCodeActions() []protocol.CodeActionKind {
211         allCodeActionKinds := make(map[protocol.CodeActionKind]struct{})
212         for _, kinds := range s.session.Options().SupportedCodeActions {
213                 for kind := range kinds {
214                         allCodeActionKinds[kind] = struct{}{}
215                 }
216         }
217         var result []protocol.CodeActionKind
218         for kind := range allCodeActionKinds {
219                 result = append(result, kind)
220         }
221         sort.Slice(result, func(i, j int) bool {
222                 return result[i] < result[j]
223         })
224         return result
225 }
226
227 func importFixTitle(fix *imports.ImportFix) string {
228         var str string
229         switch fix.FixType {
230         case imports.AddImport:
231                 str = fmt.Sprintf("Add import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
232         case imports.DeleteImport:
233                 str = fmt.Sprintf("Delete import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
234         case imports.SetImportName:
235                 str = fmt.Sprintf("Rename import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
236         }
237         return str
238 }
239
240 func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic) (results []protocol.Diagnostic) {
241         for _, diagnostic := range diagnostics {
242                 switch {
243                 // "undeclared name: X" may be an unresolved import.
244                 case strings.HasPrefix(diagnostic.Message, "undeclared name: "):
245                         ident := strings.TrimPrefix(diagnostic.Message, "undeclared name: ")
246                         if ident == fix.IdentName {
247                                 results = append(results, diagnostic)
248                         }
249                 // "could not import: X" may be an invalid import.
250                 case strings.HasPrefix(diagnostic.Message, "could not import: "):
251                         ident := strings.TrimPrefix(diagnostic.Message, "could not import: ")
252                         if ident == fix.IdentName {
253                                 results = append(results, diagnostic)
254                         }
255                 // "X imported but not used" is an unused import.
256                 // "X imported but not used as Y" is an unused import.
257                 case strings.Contains(diagnostic.Message, " imported but not used"):
258                         idx := strings.Index(diagnostic.Message, " imported but not used")
259                         importPath := diagnostic.Message[:idx]
260                         if importPath == fmt.Sprintf("%q", fix.StmtInfo.ImportPath) {
261                                 results = append(results, diagnostic)
262                         }
263                 }
264         }
265         return results
266 }
267
268 func extractionFixes(ctx context.Context, snapshot source.Snapshot, pkg source.Package, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
269         if rng.Start == rng.End {
270                 return nil, nil
271         }
272         fh, err := snapshot.GetFile(ctx, uri)
273         if err != nil {
274                 return nil, err
275         }
276         _, pgf, err := source.GetParsedFile(ctx, snapshot, fh, source.NarrowestPackage)
277         if err != nil {
278                 return nil, errors.Errorf("getting file for Identifier: %w", err)
279         }
280         srng, err := pgf.Mapper.RangeToSpanRange(rng)
281         if err != nil {
282                 return nil, err
283         }
284         puri := protocol.URIFromSpanURI(uri)
285         var commands []protocol.Command
286         if _, ok, _ := source.CanExtractFunction(snapshot.FileSet(), srng, pgf.Src, pgf.File); ok {
287                 cmd, err := command.NewApplyFixCommand("Extract to function", command.ApplyFixArgs{
288                         URI:   puri,
289                         Fix:   source.ExtractFunction,
290                         Range: rng,
291                 })
292                 if err != nil {
293                         return nil, err
294                 }
295                 commands = append(commands, cmd)
296         }
297         if _, _, ok, _ := source.CanExtractVariable(srng, pgf.File); ok {
298                 cmd, err := command.NewApplyFixCommand("Extract variable", command.ApplyFixArgs{
299                         URI:   puri,
300                         Fix:   source.ExtractVariable,
301                         Range: rng,
302                 })
303                 if err != nil {
304                         return nil, err
305                 }
306                 commands = append(commands, cmd)
307         }
308         var actions []protocol.CodeAction
309         for _, cmd := range commands {
310                 actions = append(actions, protocol.CodeAction{
311                         Title:   cmd.Title,
312                         Kind:    protocol.RefactorExtract,
313                         Command: &cmd,
314                 })
315         }
316         return actions, nil
317 }
318
319 func documentChanges(fh source.VersionedFileHandle, edits []protocol.TextEdit) []protocol.TextDocumentEdit {
320         return []protocol.TextDocumentEdit{
321                 {
322                         TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
323                                 Version: fh.Version(),
324                                 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
325                                         URI: protocol.URIFromSpanURI(fh.URI()),
326                                 },
327                         },
328                         Edits: edits,
329                 },
330         }
331 }
332
333 func codeActionsMatchingDiagnostics(ctx context.Context, snapshot source.Snapshot, pdiags []protocol.Diagnostic, sdiags []*source.Diagnostic) ([]protocol.CodeAction, error) {
334         var actions []protocol.CodeAction
335         for _, sd := range sdiags {
336                 var diag *protocol.Diagnostic
337                 for _, pd := range pdiags {
338                         if sameDiagnostic(pd, sd) {
339                                 diag = &pd
340                                 break
341                         }
342                 }
343                 if diag == nil {
344                         continue
345                 }
346                 diagActions, err := codeActionsForDiagnostic(ctx, snapshot, sd, diag)
347                 if err != nil {
348                         return nil, err
349                 }
350                 actions = append(actions, diagActions...)
351
352         }
353         return actions, nil
354 }
355
356 func codeActionsForDiagnostic(ctx context.Context, snapshot source.Snapshot, sd *source.Diagnostic, pd *protocol.Diagnostic) ([]protocol.CodeAction, error) {
357         var actions []protocol.CodeAction
358         for _, fix := range sd.SuggestedFixes {
359                 action := protocol.CodeAction{
360                         Title:   fix.Title,
361                         Kind:    protocol.QuickFix,
362                         Edit:    protocol.WorkspaceEdit{},
363                         Command: fix.Command,
364                 }
365                 if pd != nil {
366                         action.Diagnostics = []protocol.Diagnostic{*pd}
367                 }
368                 if sd.Analyzer != nil && sd.Analyzer.ActionKind != "" {
369                         action.Kind = sd.Analyzer.ActionKind
370                 }
371
372                 for uri, edits := range fix.Edits {
373                         fh, err := snapshot.GetVersionedFile(ctx, uri)
374                         if err != nil {
375                                 return nil, err
376                         }
377                         action.Edit.DocumentChanges = append(action.Edit.DocumentChanges, protocol.TextDocumentEdit{
378                                 TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
379                                         Version: fh.Version(),
380                                         TextDocumentIdentifier: protocol.TextDocumentIdentifier{
381                                                 URI: protocol.URIFromSpanURI(uri),
382                                         },
383                                 },
384                                 Edits: edits,
385                         })
386                 }
387                 actions = append(actions, action)
388         }
389         return actions, nil
390 }
391
392 func sameDiagnostic(pd protocol.Diagnostic, sd *source.Diagnostic) bool {
393         return pd.Message == sd.Message && protocol.CompareRange(pd.Range, sd.Range) == 0 && pd.Source == string(sd.Source)
394 }
395
396 func goTest(ctx context.Context, snapshot source.Snapshot, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
397         fh, err := snapshot.GetFile(ctx, uri)
398         if err != nil {
399                 return nil, err
400         }
401         fns, err := source.TestsAndBenchmarks(ctx, snapshot, fh)
402         if err != nil {
403                 return nil, err
404         }
405
406         var tests, benchmarks []string
407         for _, fn := range fns.Tests {
408                 if !protocol.Intersect(fn.Rng, rng) {
409                         continue
410                 }
411                 tests = append(tests, fn.Name)
412         }
413         for _, fn := range fns.Benchmarks {
414                 if !protocol.Intersect(fn.Rng, rng) {
415                         continue
416                 }
417                 benchmarks = append(benchmarks, fn.Name)
418         }
419
420         if len(tests) == 0 && len(benchmarks) == 0 {
421                 return nil, nil
422         }
423
424         cmd, err := command.NewTestCommand("Run tests and benchmarks", protocol.URIFromSpanURI(uri), tests, benchmarks)
425         if err != nil {
426                 return nil, err
427         }
428         return []protocol.CodeAction{{
429                 Title:   cmd.Title,
430                 Kind:    protocol.GoTest,
431                 Command: &cmd,
432         }}, nil
433 }