blob: 4147e17ce6da87a38bf9fdf80af21d709df99fa4 [file] [log] [blame]
Rebecca Stamblerccd37322019-04-05 15:56:08 -04001// 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
5package lsp
6
7import (
8 "context"
Rebecca Stamblere750c412019-06-27 21:26:42 -04009 "fmt"
Suzy Muellera0cf0542019-08-14 15:24:21 -040010 "sort"
Rebecca Stamblerccd37322019-04-05 15:56:08 -040011 "strings"
12
Rebecca Stambler58eba7e2020-07-15 00:19:10 -040013 "golang.org/x/tools/internal/event"
Suzy Mueller5f95ed52019-07-30 14:00:02 -040014 "golang.org/x/tools/internal/imports"
Rob Findley8aef11f2021-02-05 17:55:31 -050015 "golang.org/x/tools/internal/lsp/command"
Rebecca Stambler58eba7e2020-07-15 00:19:10 -040016 "golang.org/x/tools/internal/lsp/debug/tag"
Rebecca Stambler1f28ee62020-10-13 00:50:48 -040017 "golang.org/x/tools/internal/lsp/mod"
Rebecca Stamblerccd37322019-04-05 15:56:08 -040018 "golang.org/x/tools/internal/lsp/protocol"
19 "golang.org/x/tools/internal/lsp/source"
Rebecca Stamblere31b5682020-06-12 17:10:06 -040020 "golang.org/x/tools/internal/span"
Rebecca Stamblerccd37322019-04-05 15:56:08 -040021)
22
23func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
Heschi Kreinick412b8bd2020-07-02 18:34:10 -040024 snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
25 defer release()
Heschi Kreinick5916a502020-02-13 13:46:49 -050026 if !ok {
Rebecca Stambler80313e12019-11-15 12:43:45 -050027 return nil, err
28 }
Heschi Kreinickecd3fc42020-06-08 15:21:24 -040029 uri := fh.URI()
Rebecca Stambler57610ed2019-09-27 13:17:59 -040030
Rebecca Stambler1dcc99b2019-08-05 19:27:28 -040031 // Determine the supported actions for this file kind.
Robert Findley3f6aab12022-01-13 11:04:02 -050032 kind := snapshot.View().FileKind(fh)
pjw68b574a2022-01-07 19:06:25 -050033 supportedCodeActions, ok := snapshot.View().Options().SupportedCodeActions[kind]
Rebecca Stambler1dcc99b2019-08-05 19:27:28 -040034 if !ok {
pjw68b574a2022-01-07 19:06:25 -050035 return nil, fmt.Errorf("no supported code actions for %v file kind", kind)
Rebecca Stambler1dcc99b2019-08-05 19:27:28 -040036 }
Rebecca Stamblere750c412019-06-27 21:26:42 -040037
38 // The Only field of the context specifies which code actions the client wants.
Pontus Leitzler8c269732020-10-03 14:12:29 +020039 // If Only is empty, assume that the client wants all of the non-explicit code actions.
Rebecca Stamblere750c412019-06-27 21:26:42 -040040 var wanted map[protocol.CodeActionKind]bool
Pontus Leitzler8c269732020-10-03 14:12:29 +020041
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
Rebecca Stamblere750c412019-06-27 21:26:42 -040049 if len(params.Context.Only) == 0 {
Rebecca Stambler1dcc99b2019-08-05 19:27:28 -040050 wanted = supportedCodeActions
Rebecca Stamblere750c412019-06-27 21:26:42 -040051 } else {
52 wanted = make(map[protocol.CodeActionKind]bool)
53 for _, only := range params.Context.Only {
Suzy Mueller5e467252021-11-05 16:11:58 -040054 for k, v := range supportedCodeActions {
55 if only == k || strings.HasPrefix(string(k), string(only)+".") {
56 wanted[k] = wanted[k] || v
57 }
58 }
59 wanted[only] = wanted[only] || explicit[only]
Rebecca Stamblere750c412019-06-27 21:26:42 -040060 }
61 }
Peter Weinbergr7cab0ef2021-03-02 10:16:22 -050062 if len(supportedCodeActions) == 0 {
63 return nil, nil // not an error if there are none supported
64 }
Rebecca Stamblere750c412019-06-27 21:26:42 -040065 if len(wanted) == 0 {
Rebecca Stamblera7c05942020-03-30 12:45:15 -040066 return nil, fmt.Errorf("no supported code action to execute for %s, wanted %v", uri, params.Context.Only)
Rebecca Stamblere750c412019-06-27 21:26:42 -040067 }
68
Rebecca Stamblerccd37322019-04-05 15:56:08 -040069 var codeActions []protocol.CodeAction
pjw68b574a2022-01-07 19:06:25 -050070 switch kind {
Rebecca Stambler1081e672019-09-19 01:21:54 -040071 case source.Mod:
Rebecca Stambler6123e772020-07-20 16:56:12 -040072 if diagnostics := params.Context.Diagnostics; len(diagnostics) > 0 {
Heschi Kreinick376db572021-03-02 18:19:32 -050073 diags, err := mod.DiagnosticsForMod(ctx, snapshot, fh)
Rebecca Stambler5bd05382020-10-07 01:28:22 -040074 if source.IsNonFatalGoModError(err) {
Rebecca Stambler6123e772020-07-20 16:56:12 -040075 return nil, nil
76 }
Rebecca Stambler504fe872020-04-01 21:31:43 -040077 if err != nil {
78 return nil, err
79 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -050080 quickFixes, err := codeActionsMatchingDiagnostics(ctx, snapshot, diagnostics, diags)
Heschi Kreinick376db572021-03-02 18:19:32 -050081 if err != nil {
82 return nil, err
83 }
84 codeActions = append(codeActions, quickFixes...)
Rebecca Stambler6123e772020-07-20 16:56:12 -040085 }
Rebecca Stambler1081e672019-09-19 01:21:54 -040086 case source.Go:
Rebecca Stamblerab2804f2020-04-29 17:49:22 -040087 // Don't suggest fixes for generated files, since they are generally
88 // not useful and some editors may apply them automatically on save.
89 if source.IsGenerated(ctx, snapshot, uri) {
90 return nil, nil
91 }
Rohan Challae46a7b92020-03-15 15:41:57 -040092 diagnostics := params.Context.Diagnostics
93
Rebecca Stambler9d5940d2020-02-26 17:07:02 -050094 // First, process any missing imports and pair them with the
95 // diagnostics they fix.
96 if wantQuickFixes := wanted[protocol.QuickFix] && len(diagnostics) > 0; wantQuickFixes || wanted[protocol.SourceOrganizeImports] {
97 importEdits, importEditsPerFix, err := source.AllImportsFixes(ctx, snapshot, fh)
Rebecca Stambler1081e672019-09-19 01:21:54 -040098 if err != nil {
Rebecca Stamblerd5184952020-07-16 20:00:10 -040099 event.Error(ctx, "imports fixes", err, tag.File.Of(fh.URI().Filename()))
Rebecca Stambler1081e672019-09-19 01:21:54 -0400100 }
Rebecca Stambler9d5940d2020-02-26 17:07:02 -0500101 // Separate this into a set of codeActions per diagnostic, where
102 // each action is the addition, removal, or renaming of one import.
103 if wantQuickFixes {
104 for _, importFix := range importEditsPerFix {
105 fixes := importDiagnostics(importFix.Fix, diagnostics)
106 if len(fixes) == 0 {
107 continue
108 }
109 codeActions = append(codeActions, protocol.CodeAction{
110 Title: importFixTitle(importFix.Fix),
111 Kind: protocol.QuickFix,
112 Edit: protocol.WorkspaceEdit{
113 DocumentChanges: documentChanges(fh, importFix.Edits),
114 },
115 Diagnostics: fixes,
116 })
117 }
118 }
Heschi Kreinickbd5d1602020-11-10 18:20:37 -0500119
Rebecca Stambler9d5940d2020-02-26 17:07:02 -0500120 // Send all of the import edits as one code action if the file is
121 // being organized.
122 if wanted[protocol.SourceOrganizeImports] && len(importEdits) > 0 {
123 codeActions = append(codeActions, protocol.CodeAction{
124 Title: "Organize Imports",
125 Kind: protocol.SourceOrganizeImports,
126 Edit: protocol.WorkspaceEdit{
127 DocumentChanges: documentChanges(fh, importEdits),
128 },
129 })
130 }
131 }
Rebecca Stambler9d5940d2020-02-26 17:07:02 -0500132 if ctx.Err() != nil {
133 return nil, ctx.Err()
Rohan Challae46a7b92020-03-15 15:41:57 -0400134 }
Danish Dua8d73f172020-09-24 17:25:18 -0400135 pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckFull, source.WidestPackage)
Rebecca Stamblere31b5682020-06-12 17:10:06 -0400136 if err != nil {
137 return nil, err
138 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500139
140 pkgDiagnostics, err := snapshot.DiagnosePackage(ctx, pkg)
141 if err != nil {
142 return nil, err
143 }
144 analysisDiags, err := source.Analyze(ctx, snapshot, pkg, true)
145 if err != nil {
146 return nil, err
147 }
148 fileDiags := append(pkgDiagnostics[uri], analysisDiags[uri]...)
Heschi Kreinick376db572021-03-02 18:19:32 -0500149
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500150 // Split diagnostics into fixes, which must match incoming diagnostics,
151 // and non-fixes, which must match the requested range. Build actions
152 // for all of them.
153 var fixDiags, nonFixDiags []*source.Diagnostic
154 for _, d := range fileDiags {
155 if len(d.SuggestedFixes) == 0 {
156 continue
157 }
Rebecca Stambler09a00c12021-03-18 12:12:50 -1000158 var isFix bool
159 for _, fix := range d.SuggestedFixes {
160 if fix.ActionKind == protocol.QuickFix || fix.ActionKind == protocol.SourceFixAll {
161 isFix = true
162 break
163 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500164 }
Rebecca Stambler09a00c12021-03-18 12:12:50 -1000165 if isFix {
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500166 fixDiags = append(fixDiags, d)
167 } else {
168 nonFixDiags = append(nonFixDiags, d)
Rohan Challa6fdc5772020-01-22 14:23:12 -0500169 }
Rebecca Stambler1081e672019-09-19 01:21:54 -0400170 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500171
172 fixActions, err := codeActionsMatchingDiagnostics(ctx, snapshot, diagnostics, fixDiags)
173 if err != nil {
174 return nil, err
Pei Xian Chee9b20fe42020-06-03 18:56:29 +0000175 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500176 codeActions = append(codeActions, fixActions...)
177
178 for _, nonfix := range nonFixDiags {
179 // For now, only show diagnostics for matching lines. Maybe we should
180 // alter this behavior in the future, depending on the user experience.
181 if !protocol.Intersect(nonfix.Range, params.Range) {
182 continue
183 }
184 actions, err := codeActionsForDiagnostic(ctx, snapshot, nonfix, nil)
Rebecca Stamblere31b5682020-06-12 17:10:06 -0400185 if err != nil {
186 return nil, err
187 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500188 codeActions = append(codeActions, actions...)
Rebecca Stamblere31b5682020-06-12 17:10:06 -0400189 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500190
Josh Baum9c9572d2020-06-24 09:52:23 -0400191 if wanted[protocol.RefactorExtract] {
Heschi Kreinickb6476682020-07-22 11:32:32 -0400192 fixes, err := extractionFixes(ctx, snapshot, pkg, uri, params.Range)
Josh Baum9c9572d2020-06-24 09:52:23 -0400193 if err != nil {
194 return nil, err
195 }
196 codeActions = append(codeActions, fixes...)
197 }
Pontus Leitzler8c269732020-10-03 14:12:29 +0200198
199 if wanted[protocol.GoTest] {
200 fixes, err := goTest(ctx, snapshot, uri, params.Range)
201 if err != nil {
202 return nil, err
203 }
204 codeActions = append(codeActions, fixes...)
205 }
206
Rebecca Stambler1081e672019-09-19 01:21:54 -0400207 default:
208 // Unsupported file kind for a code action.
209 return nil, nil
210 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500211
212 var filtered []protocol.CodeAction
213 for _, action := range codeActions {
214 if wanted[action.Kind] {
215 filtered = append(filtered, action)
216 }
217 }
218 return filtered, nil
Rebecca Stamblerccd37322019-04-05 15:56:08 -0400219}
220
Rebecca Stambler1081e672019-09-19 01:21:54 -0400221func (s *Server) getSupportedCodeActions() []protocol.CodeActionKind {
222 allCodeActionKinds := make(map[protocol.CodeActionKind]struct{})
223 for _, kinds := range s.session.Options().SupportedCodeActions {
224 for kind := range kinds {
225 allCodeActionKinds[kind] = struct{}{}
226 }
227 }
228 var result []protocol.CodeActionKind
229 for kind := range allCodeActionKinds {
230 result = append(result, kind)
231 }
232 sort.Slice(result, func(i, j int) bool {
233 return result[i] < result[j]
234 })
235 return result
236}
237
Suzy Mueller5f95ed52019-07-30 14:00:02 -0400238func importFixTitle(fix *imports.ImportFix) string {
239 var str string
240 switch fix.FixType {
241 case imports.AddImport:
242 str = fmt.Sprintf("Add import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
243 case imports.DeleteImport:
244 str = fmt.Sprintf("Delete import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
245 case imports.SetImportName:
246 str = fmt.Sprintf("Rename import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
247 }
248 return str
249}
250
251func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic) (results []protocol.Diagnostic) {
252 for _, diagnostic := range diagnostics {
253 switch {
254 // "undeclared name: X" may be an unresolved import.
255 case strings.HasPrefix(diagnostic.Message, "undeclared name: "):
256 ident := strings.TrimPrefix(diagnostic.Message, "undeclared name: ")
257 if ident == fix.IdentName {
258 results = append(results, diagnostic)
259 }
260 // "could not import: X" may be an invalid import.
261 case strings.HasPrefix(diagnostic.Message, "could not import: "):
262 ident := strings.TrimPrefix(diagnostic.Message, "could not import: ")
263 if ident == fix.IdentName {
264 results = append(results, diagnostic)
265 }
266 // "X imported but not used" is an unused import.
267 // "X imported but not used as Y" is an unused import.
268 case strings.Contains(diagnostic.Message, " imported but not used"):
269 idx := strings.Index(diagnostic.Message, " imported but not used")
270 importPath := diagnostic.Message[:idx]
271 if importPath == fmt.Sprintf("%q", fix.StmtInfo.ImportPath) {
272 results = append(results, diagnostic)
273 }
274 }
Suzy Mueller5f95ed52019-07-30 14:00:02 -0400275 }
Suzy Mueller5f95ed52019-07-30 14:00:02 -0400276 return results
277}
278
Heschi Kreinickb6476682020-07-22 11:32:32 -0400279func extractionFixes(ctx context.Context, snapshot source.Snapshot, pkg source.Package, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
Rebecca Stambler92670832020-07-23 23:24:36 -0400280 if rng.Start == rng.End {
Josh Baum9c9572d2020-06-24 09:52:23 -0400281 return nil, nil
282 }
Rebecca Stambler92670832020-07-23 23:24:36 -0400283 fh, err := snapshot.GetFile(ctx, uri)
284 if err != nil {
285 return nil, err
286 }
Rob Findleya30116d2021-02-04 19:35:05 -0500287 _, pgf, err := source.GetParsedFile(ctx, snapshot, fh, source.NarrowestPackage)
288 if err != nil {
Robert Findley37590b32022-04-19 18:08:06 -0400289 return nil, fmt.Errorf("getting file for Identifier: %w", err)
Rob Findleya30116d2021-02-04 19:35:05 -0500290 }
291 srng, err := pgf.Mapper.RangeToSpanRange(rng)
292 if err != nil {
293 return nil, err
294 }
Rob Findley8aef11f2021-02-05 17:55:31 -0500295 puri := protocol.URIFromSpanURI(uri)
296 var commands []protocol.Command
Alan Donovanb929f3b2022-06-01 15:20:56 -0400297 if _, ok, methodOk, _ := source.CanExtractFunction(pgf.Tok, srng, pgf.Src, pgf.File); ok {
Suzy Mueller46d15222021-06-22 05:14:28 -0400298 cmd, err := command.NewApplyFixCommand("Extract function", command.ApplyFixArgs{
Rob Findley8aef11f2021-02-05 17:55:31 -0500299 URI: puri,
300 Fix: source.ExtractFunction,
301 Range: rng,
302 })
303 if err != nil {
304 return nil, err
305 }
306 commands = append(commands, cmd)
Suzy Mueller46d15222021-06-22 05:14:28 -0400307 if methodOk {
308 cmd, err := command.NewApplyFixCommand("Extract method", command.ApplyFixArgs{
309 URI: puri,
310 Fix: source.ExtractMethod,
311 Range: rng,
312 })
313 if err != nil {
314 return nil, err
315 }
316 commands = append(commands, cmd)
317 }
Rob Findleya30116d2021-02-04 19:35:05 -0500318 }
319 if _, _, ok, _ := source.CanExtractVariable(srng, pgf.File); ok {
Rob Findley8aef11f2021-02-05 17:55:31 -0500320 cmd, err := command.NewApplyFixCommand("Extract variable", command.ApplyFixArgs{
321 URI: puri,
322 Fix: source.ExtractVariable,
323 Range: rng,
324 })
325 if err != nil {
326 return nil, err
327 }
328 commands = append(commands, cmd)
Rob Findleya30116d2021-02-04 19:35:05 -0500329 }
Josh Baum6d307ed2020-07-10 12:13:40 -0400330 var actions []protocol.CodeAction
Suzy Muellerae0deb72021-06-24 11:53:41 -0400331 for i := range commands {
Josh Baum6d307ed2020-07-10 12:13:40 -0400332 actions = append(actions, protocol.CodeAction{
Suzy Muellerae0deb72021-06-24 11:53:41 -0400333 Title: commands[i].Title,
Rob Findley8aef11f2021-02-05 17:55:31 -0500334 Kind: protocol.RefactorExtract,
Suzy Muellerae0deb72021-06-24 11:53:41 -0400335 Command: &commands[i],
Josh Baum6d307ed2020-07-10 12:13:40 -0400336 })
337 }
338 return actions, nil
Josh Baum9c9572d2020-06-24 09:52:23 -0400339}
340
Heschi Kreinickc9619e82020-07-26 18:01:39 -0400341func documentChanges(fh source.VersionedFileHandle, edits []protocol.TextEdit) []protocol.TextDocumentEdit {
Rebecca Stamblere33b02e2019-11-12 17:58:37 -0500342 return []protocol.TextDocumentEdit{
343 {
pjwc3402e32021-01-24 14:00:12 -0500344 TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
Heschi Kreinickecd3fc42020-06-08 15:21:24 -0400345 Version: fh.Version(),
Rebecca Stamblere33b02e2019-11-12 17:58:37 -0500346 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
Heschi Kreinickecd3fc42020-06-08 15:21:24 -0400347 URI: protocol.URIFromSpanURI(fh.URI()),
Rebecca Stamblere33b02e2019-11-12 17:58:37 -0500348 },
349 },
350 Edits: edits,
351 },
352 }
353}
Rebecca Stambler6123e772020-07-20 16:56:12 -0400354
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500355func codeActionsMatchingDiagnostics(ctx context.Context, snapshot source.Snapshot, pdiags []protocol.Diagnostic, sdiags []*source.Diagnostic) ([]protocol.CodeAction, error) {
356 var actions []protocol.CodeAction
357 for _, sd := range sdiags {
Rebecca Stambler6123e772020-07-20 16:56:12 -0400358 var diag *protocol.Diagnostic
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500359 for _, pd := range pdiags {
360 if sameDiagnostic(pd, sd) {
361 diag = &pd
Rebecca Stambler6123e772020-07-20 16:56:12 -0400362 break
363 }
364 }
365 if diag == nil {
366 continue
367 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500368 diagActions, err := codeActionsForDiagnostic(ctx, snapshot, sd, diag)
369 if err != nil {
370 return nil, err
Rebecca Stambler6123e772020-07-20 16:56:12 -0400371 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500372 actions = append(actions, diagActions...)
373
Rebecca Stambler6123e772020-07-20 16:56:12 -0400374 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500375 return actions, nil
376}
377
378func codeActionsForDiagnostic(ctx context.Context, snapshot source.Snapshot, sd *source.Diagnostic, pd *protocol.Diagnostic) ([]protocol.CodeAction, error) {
379 var actions []protocol.CodeAction
380 for _, fix := range sd.SuggestedFixes {
Rebecca Stambler09a00c12021-03-18 12:12:50 -1000381 var changes []protocol.TextDocumentEdit
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500382 for uri, edits := range fix.Edits {
383 fh, err := snapshot.GetVersionedFile(ctx, uri)
384 if err != nil {
385 return nil, err
386 }
Rebecca Stambler09a00c12021-03-18 12:12:50 -1000387 changes = append(changes, protocol.TextDocumentEdit{
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500388 TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
389 Version: fh.Version(),
390 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
391 URI: protocol.URIFromSpanURI(uri),
392 },
393 },
394 Edits: edits,
395 })
396 }
Rebecca Stambler09a00c12021-03-18 12:12:50 -1000397 action := protocol.CodeAction{
398 Title: fix.Title,
399 Kind: fix.ActionKind,
400 Edit: protocol.WorkspaceEdit{
401 DocumentChanges: changes,
402 },
403 Command: fix.Command,
404 }
405 if pd != nil {
406 action.Diagnostics = []protocol.Diagnostic{*pd}
407 }
Heschi Kreinick11e8f6b2021-03-09 12:23:42 -0500408 actions = append(actions, action)
409 }
410 return actions, nil
Rebecca Stambler6123e772020-07-20 16:56:12 -0400411}
Rebecca Stamblerd4590502021-02-04 18:12:25 -0500412
Heschi Kreinick51ce8372021-01-28 20:03:37 -0500413func sameDiagnostic(pd protocol.Diagnostic, sd *source.Diagnostic) bool {
Heschi Kreinicke7dfe022021-01-28 21:08:30 -0500414 return pd.Message == sd.Message && protocol.CompareRange(pd.Range, sd.Range) == 0 && pd.Source == string(sd.Source)
Rebecca Stambler6123e772020-07-20 16:56:12 -0400415}
416
Pontus Leitzler8c269732020-10-03 14:12:29 +0200417func goTest(ctx context.Context, snapshot source.Snapshot, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
418 fh, err := snapshot.GetFile(ctx, uri)
419 if err != nil {
420 return nil, err
421 }
422 fns, err := source.TestsAndBenchmarks(ctx, snapshot, fh)
423 if err != nil {
424 return nil, err
425 }
426
427 var tests, benchmarks []string
428 for _, fn := range fns.Tests {
429 if !protocol.Intersect(fn.Rng, rng) {
430 continue
431 }
432 tests = append(tests, fn.Name)
433 }
434 for _, fn := range fns.Benchmarks {
435 if !protocol.Intersect(fn.Rng, rng) {
436 continue
437 }
438 benchmarks = append(benchmarks, fn.Name)
439 }
440
441 if len(tests) == 0 && len(benchmarks) == 0 {
442 return nil, nil
443 }
444
Rob Findley8aef11f2021-02-05 17:55:31 -0500445 cmd, err := command.NewTestCommand("Run tests and benchmarks", protocol.URIFromSpanURI(uri), tests, benchmarks)
Pontus Leitzler8c269732020-10-03 14:12:29 +0200446 if err != nil {
447 return nil, err
448 }
449 return []protocol.CodeAction{{
Rob Findley8aef11f2021-02-05 17:55:31 -0500450 Title: cmd.Title,
451 Kind: protocol.GoTest,
452 Command: &cmd,
Pontus Leitzler8c269732020-10-03 14:12:29 +0200453 }}, nil
454}