blob: 35bc0e432871c23db0d34163d2231e3bf6b19ce8 [file] [log] [blame]
Marwan Sulaiman63da46f2020-03-11 00:49:10 -04001// Copyright 2020 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
Rebecca Stambler1081e672019-09-19 01:21:54 -04005package lsp
6
7import (
Rob Findley1e23e482020-08-21 14:18:37 -04008 "bytes"
Rebecca Stambler1081e672019-09-19 01:21:54 -04009 "context"
Rob Findleyed71c572020-08-17 22:50:34 -040010 "encoding/json"
Robert Findley37590b32022-04-19 18:08:06 -040011 "errors"
Rebecca Stambler01425d72020-07-10 20:17:16 -040012 "fmt"
Martin Asquino2bc93b12020-05-05 19:43:51 +010013 "io"
Rob Findley463111b2020-09-18 17:26:33 -040014 "io/ioutil"
Heschi Kreinick706a59c2021-02-05 22:18:47 -050015 "os"
Hana (Hyang-Ah) Kimbceee4b2022-08-02 12:19:22 -040016 "os/exec"
Rob Findley463111b2020-09-18 17:26:33 -040017 "path/filepath"
Heschi Kreinick414ec9c2022-02-04 15:23:09 -050018 "sort"
Heschi Kreinickbd5d1602020-11-10 18:20:37 -050019 "strings"
Rebecca Stambler1081e672019-09-19 01:21:54 -040020
Rebecca Stambler57089f82020-12-22 23:58:35 -050021 "golang.org/x/mod/modfile"
Heschi Kreinick414ec9c2022-02-04 15:23:09 -050022 "golang.org/x/tools/go/ast/astutil"
Rebecca Stamblerc80dc572020-07-15 23:33:55 -040023 "golang.org/x/tools/internal/event"
Heschi Kreinick2b84a062020-10-26 15:13:16 -040024 "golang.org/x/tools/internal/gocommand"
Rob Findley8aef11f2021-02-05 17:55:31 -050025 "golang.org/x/tools/internal/lsp/command"
Rob Findley7c934842021-04-12 12:38:22 -040026 "golang.org/x/tools/internal/lsp/debug"
Rebecca Stambler116feae2021-05-12 17:00:35 -040027 "golang.org/x/tools/internal/lsp/progress"
Rebecca Stambler1081e672019-09-19 01:21:54 -040028 "golang.org/x/tools/internal/lsp/protocol"
29 "golang.org/x/tools/internal/lsp/source"
Rebecca Stambler6aa8f572020-06-11 18:25:17 -040030 "golang.org/x/tools/internal/span"
Marwan Sulaiman63da46f2020-03-11 00:49:10 -040031 "golang.org/x/tools/internal/xcontext"
Rebecca Stambler1081e672019-09-19 01:21:54 -040032)
33
34func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
Rob Findleya30116d2021-02-04 19:35:05 -050035 var found bool
Rebecca Stambler92670832020-07-23 23:24:36 -040036 for _, name := range s.session.Options().SupportedCommands {
Rob Findleya30116d2021-02-04 19:35:05 -050037 if name == params.Command {
38 found = true
Rebecca Stambler92670832020-07-23 23:24:36 -040039 break
40 }
41 }
Rob Findleya30116d2021-02-04 19:35:05 -050042 if !found {
43 return nil, fmt.Errorf("%s is not a supported command", params.Command)
Rob Findley463111b2020-09-18 17:26:33 -040044 }
Heschi Kreinicka83918f2020-11-20 17:12:49 -050045
Rob Findley8aef11f2021-02-05 17:55:31 -050046 handler := &commandHandler{
Rob Findleya30116d2021-02-04 19:35:05 -050047 s: s,
48 params: params,
Rob Findley5bbba662020-10-28 12:22:52 -040049 }
Rob Findley5fbed492021-02-09 17:25:52 -050050 return command.Dispatch(ctx, params, handler)
Rob Findleyed71c572020-08-17 22:50:34 -040051}
52
Rob Findleya30116d2021-02-04 19:35:05 -050053type commandHandler struct {
Rob Findleya30116d2021-02-04 19:35:05 -050054 s *Server
55 params *protocol.ExecuteCommandParams
Rob Findley60aba8a2020-09-16 11:14:28 -040056}
57
Rob Findleya30116d2021-02-04 19:35:05 -050058// commandConfig configures common command set-up and execution.
Rob Findleyd2671c42021-01-31 16:04:49 -050059type commandConfig struct {
Marwan Sulaiman40613122021-05-15 10:04:17 -040060 async bool // whether to run the command asynchronously. Async commands can only return errors.
Rob Findleyd2671c42021-01-31 16:04:49 -050061 requireSave bool // whether all files must be saved for the command to work
Rob Findleya30116d2021-02-04 19:35:05 -050062 progress string // title to use for progress reporting. If empty, no progress will be reported.
Rob Findleyd2671c42021-01-31 16:04:49 -050063 forURI protocol.DocumentURI // URI to resolve to a snapshot. If unset, snapshot will be nil.
64}
65
Rob Findleya30116d2021-02-04 19:35:05 -050066// commandDeps is evaluated from a commandConfig. Note that not all fields may
67// be populated, depending on which configuration is set. See comments in-line
68// for details.
69type commandDeps struct {
70 snapshot source.Snapshot // present if cfg.forURI was set
71 fh source.VersionedFileHandle // present if cfg.forURI was set
Rebecca Stambler116feae2021-05-12 17:00:35 -040072 work *progress.WorkDone // present cfg.progress was set
Rob Findleya30116d2021-02-04 19:35:05 -050073}
74
75type commandFunc func(context.Context, commandDeps) error
76
Rob Findley5fbed492021-02-09 17:25:52 -050077func (c *commandHandler) run(ctx context.Context, cfg commandConfig, run commandFunc) (err error) {
Rob Findleyd2671c42021-01-31 16:04:49 -050078 if cfg.requireSave {
Rebecca Stambler92b2fbe2021-08-06 14:10:12 -040079 var unsaved []string
Rob Findleya30116d2021-02-04 19:35:05 -050080 for _, overlay := range c.s.session.Overlays() {
Rob Findleyd2671c42021-01-31 16:04:49 -050081 if !overlay.Saved() {
Rebecca Stambler92b2fbe2021-08-06 14:10:12 -040082 unsaved = append(unsaved, overlay.URI().Filename())
Rob Findleyd2671c42021-01-31 16:04:49 -050083 }
84 }
Rebecca Stambler92b2fbe2021-08-06 14:10:12 -040085 if len(unsaved) > 0 {
Robert Findley37590b32022-04-19 18:08:06 -040086 return fmt.Errorf("All files must be saved first (unsaved: %v).", unsaved)
Rebecca Stambler92b2fbe2021-08-06 14:10:12 -040087 }
Rob Findleyd2671c42021-01-31 16:04:49 -050088 }
Rob Findleya30116d2021-02-04 19:35:05 -050089 var deps commandDeps
Rob Findleyd2671c42021-01-31 16:04:49 -050090 if cfg.forURI != "" {
Rob Findleya30116d2021-02-04 19:35:05 -050091 var ok bool
92 var release func()
Rob Findley5fbed492021-02-09 17:25:52 -050093 deps.snapshot, deps.fh, ok, release, err = c.s.beginFileRequest(ctx, cfg.forURI, source.UnknownKind)
Rob Findleyd2671c42021-01-31 16:04:49 -050094 defer release()
95 if !ok {
Hana54af36e2022-03-25 11:14:27 -040096 if err != nil {
97 return err
98 }
99 return fmt.Errorf("invalid file URL: %v", cfg.forURI)
Rob Findleyd2671c42021-01-31 16:04:49 -0500100 }
Rob Findleyd2671c42021-01-31 16:04:49 -0500101 }
Rob Findley5fbed492021-02-09 17:25:52 -0500102 ctx, cancel := context.WithCancel(xcontext.Detach(ctx))
Rob Findleya30116d2021-02-04 19:35:05 -0500103 if cfg.progress != "" {
Rebecca Stambler116feae2021-05-12 17:00:35 -0400104 deps.work = c.s.progress.Start(ctx, cfg.progress, "Running...", c.params.WorkDoneToken, cancel)
Rob Findleya30116d2021-02-04 19:35:05 -0500105 }
106 runcmd := func() error {
107 defer cancel()
108 err := run(ctx, deps)
Rebecca Stambler116feae2021-05-12 17:00:35 -0400109 if deps.work != nil {
110 switch {
111 case errors.Is(err, context.Canceled):
Robert Findley76325da2022-06-01 16:13:55 -0400112 deps.work.End(ctx, "canceled")
Rebecca Stambler116feae2021-05-12 17:00:35 -0400113 case err != nil:
114 event.Error(ctx, "command error", err)
Robert Findley76325da2022-06-01 16:13:55 -0400115 deps.work.End(ctx, "failed")
Rebecca Stambler116feae2021-05-12 17:00:35 -0400116 default:
Robert Findley76325da2022-06-01 16:13:55 -0400117 deps.work.End(ctx, "completed")
Rebecca Stambler116feae2021-05-12 17:00:35 -0400118 }
Rob Findleya30116d2021-02-04 19:35:05 -0500119 }
120 return err
121 }
122 if cfg.async {
Marwan Sulaiman40613122021-05-15 10:04:17 -0400123 go func() {
124 if err := runcmd(); err != nil {
125 if showMessageErr := c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
126 Type: protocol.Error,
127 Message: err.Error(),
128 }); showMessageErr != nil {
129 event.Error(ctx, fmt.Sprintf("failed to show message: %q", err.Error()), showMessageErr)
130 }
131 }
132 }()
Rob Findleya30116d2021-02-04 19:35:05 -0500133 return nil
134 }
135 return runcmd()
Rob Findleyd2671c42021-01-31 16:04:49 -0500136}
137
Rob Findley5fbed492021-02-09 17:25:52 -0500138func (c *commandHandler) ApplyFix(ctx context.Context, args command.ApplyFixArgs) error {
139 return c.run(ctx, commandConfig{
Rob Findleya30116d2021-02-04 19:35:05 -0500140 // Note: no progress here. Applying fixes should be quick.
Rob Findley8aef11f2021-02-05 17:55:31 -0500141 forURI: args.URI,
Rob Findleya30116d2021-02-04 19:35:05 -0500142 }, func(ctx context.Context, deps commandDeps) error {
Rob Findley8aef11f2021-02-05 17:55:31 -0500143 edits, err := source.ApplyFix(ctx, args.Fix, deps.snapshot, deps.fh, args.Range)
Rob Findleya30116d2021-02-04 19:35:05 -0500144 if err != nil {
Rob Findleyed71c572020-08-17 22:50:34 -0400145 return err
Rebecca Stambler92670832020-07-23 23:24:36 -0400146 }
Rob Findleya30116d2021-02-04 19:35:05 -0500147 r, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
148 Edit: protocol.WorkspaceEdit{
149 DocumentChanges: edits,
150 },
151 })
152 if err != nil {
153 return err
154 }
155 if !r.Applied {
156 return errors.New(r.FailureReason)
157 }
158 return nil
159 })
160}
161
Rob Findley5fbed492021-02-09 17:25:52 -0500162func (c *commandHandler) RegenerateCgo(ctx context.Context, args command.URIArg) error {
163 return c.run(ctx, commandConfig{
Rob Findley8aef11f2021-02-05 17:55:31 -0500164 progress: "Regenerating Cgo",
Rob Findleya30116d2021-02-04 19:35:05 -0500165 }, func(ctx context.Context, deps commandDeps) error {
Rebecca Stambler92670832020-07-23 23:24:36 -0400166 mod := source.FileModification{
Rob Findley8aef11f2021-02-05 17:55:31 -0500167 URI: args.URI.SpanURI(),
Rebecca Stambler92670832020-07-23 23:24:36 -0400168 Action: source.InvalidateMetadata,
169 }
Rob Findley5fbed492021-02-09 17:25:52 -0500170 return c.s.didModifyFiles(ctx, []source.FileModification{mod}, FromRegenerateCgo)
Rob Findleya30116d2021-02-04 19:35:05 -0500171 })
Rob Findleyd2671c42021-01-31 16:04:49 -0500172}
173
Rob Findley5fbed492021-02-09 17:25:52 -0500174func (c *commandHandler) CheckUpgrades(ctx context.Context, args command.CheckUpgradesArgs) error {
175 return c.run(ctx, commandConfig{
Rob Findley8aef11f2021-02-05 17:55:31 -0500176 forURI: args.URI,
177 progress: "Checking for upgrades",
Rob Findleya30116d2021-02-04 19:35:05 -0500178 }, func(ctx context.Context, deps commandDeps) error {
Rob Findley8aef11f2021-02-05 17:55:31 -0500179 upgrades, err := c.s.getUpgrades(ctx, deps.snapshot, args.URI.SpanURI(), args.Modules)
Rob Findleya30116d2021-02-04 19:35:05 -0500180 if err != nil {
Rebecca Stambler57089f82020-12-22 23:58:35 -0500181 return err
182 }
Rob Findleya30116d2021-02-04 19:35:05 -0500183 deps.snapshot.View().RegisterModuleUpgrades(upgrades)
184 // Re-diagnose the snapshot to publish the new module diagnostics.
185 c.s.diagnoseSnapshot(deps.snapshot, nil, false)
186 return nil
187 })
188}
189
Rob Findley5fbed492021-02-09 17:25:52 -0500190func (c *commandHandler) AddDependency(ctx context.Context, args command.DependencyArgs) error {
191 return c.GoGetModule(ctx, args)
Rob Findley8aef11f2021-02-05 17:55:31 -0500192}
193
Rob Findley5fbed492021-02-09 17:25:52 -0500194func (c *commandHandler) UpgradeDependency(ctx context.Context, args command.DependencyArgs) error {
195 return c.GoGetModule(ctx, args)
Rob Findley8aef11f2021-02-05 17:55:31 -0500196}
197
Rob Findley5fbed492021-02-09 17:25:52 -0500198func (c *commandHandler) GoGetModule(ctx context.Context, args command.DependencyArgs) error {
199 return c.run(ctx, commandConfig{
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500200 progress: "Running go get",
201 forURI: args.URI,
Rob Findleya30116d2021-02-04 19:35:05 -0500202 }, func(ctx context.Context, deps commandDeps) error {
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500203 return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI.SpanURI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
204 return runGoGetModule(invoke, args.AddRequire, args.GoCmdArgs)
205 })
Rob Findleya30116d2021-02-04 19:35:05 -0500206 })
207}
208
209// TODO(rFindley): UpdateGoSum, Tidy, and Vendor could probably all be one command.
Rebecca Stamblerd4590502021-02-04 18:12:25 -0500210func (c *commandHandler) UpdateGoSum(ctx context.Context, args command.URIArgs) error {
Rob Findley5fbed492021-02-09 17:25:52 -0500211 return c.run(ctx, commandConfig{
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500212 progress: "Updating go.sum",
Rob Findleya30116d2021-02-04 19:35:05 -0500213 }, func(ctx context.Context, deps commandDeps) error {
Rebecca Stamblerd4590502021-02-04 18:12:25 -0500214 for _, uri := range args.URIs {
215 snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, uri, source.UnknownKind)
216 defer release()
217 if !ok {
218 return err
219 }
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500220 if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
221 _, err := invoke("list", "all")
222 return err
223 }); err != nil {
Rebecca Stamblerd4590502021-02-04 18:12:25 -0500224 return err
225 }
226 }
227 return nil
Rob Findleya30116d2021-02-04 19:35:05 -0500228 })
229}
230
Rebecca Stamblerd4590502021-02-04 18:12:25 -0500231func (c *commandHandler) Tidy(ctx context.Context, args command.URIArgs) error {
Rob Findley5fbed492021-02-09 17:25:52 -0500232 return c.run(ctx, commandConfig{
Rob Findleya30116d2021-02-04 19:35:05 -0500233 requireSave: true,
Rob Findley8aef11f2021-02-05 17:55:31 -0500234 progress: "Running go mod tidy",
Rob Findleya30116d2021-02-04 19:35:05 -0500235 }, func(ctx context.Context, deps commandDeps) error {
Rebecca Stamblerd4590502021-02-04 18:12:25 -0500236 for _, uri := range args.URIs {
237 snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, uri, source.UnknownKind)
238 defer release()
239 if !ok {
240 return err
241 }
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500242 if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
243 _, err := invoke("mod", "tidy")
244 return err
245 }); err != nil {
Rebecca Stamblerd4590502021-02-04 18:12:25 -0500246 return err
247 }
248 }
249 return nil
Rob Findleya30116d2021-02-04 19:35:05 -0500250 })
251}
252
Rob Findley5fbed492021-02-09 17:25:52 -0500253func (c *commandHandler) Vendor(ctx context.Context, args command.URIArg) error {
254 return c.run(ctx, commandConfig{
Rob Findleya30116d2021-02-04 19:35:05 -0500255 requireSave: true,
Rob Findley8aef11f2021-02-05 17:55:31 -0500256 progress: "Running go mod vendor",
257 forURI: args.URI,
Rob Findleya30116d2021-02-04 19:35:05 -0500258 }, func(ctx context.Context, deps commandDeps) error {
Robert Findleyb2552ef2022-04-11 17:17:47 -0400259 // Use RunGoCommandPiped here so that we don't compete with any other go
260 // command invocations. go mod vendor deletes modules.txt before recreating
261 // it, and therefore can run into file locking issues on Windows if that
262 // file is in use by another process, such as go list.
263 //
264 // If golang/go#44119 is resolved, go mod vendor will instead modify
265 // modules.txt in-place. In that case we could theoretically allow this
266 // command to run concurrently.
267 err := deps.snapshot.RunGoCommandPiped(ctx, source.Normal|source.AllowNetwork, &gocommand.Invocation{
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500268 Verb: "mod",
269 Args: []string{"vendor"},
270 WorkingDir: filepath.Dir(args.URI.SpanURI().Filename()),
Robert Findleyb2552ef2022-04-11 17:17:47 -0400271 }, &bytes.Buffer{}, &bytes.Buffer{})
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500272 return err
Rob Findleya30116d2021-02-04 19:35:05 -0500273 })
274}
275
Suzy Muellerc2ddf3d2022-02-18 16:05:08 -0700276func (c *commandHandler) EditGoDirective(ctx context.Context, args command.EditGoDirectiveArgs) error {
277 return c.run(ctx, commandConfig{
278 requireSave: true, // if go.mod isn't saved it could cause a problem
279 forURI: args.URI,
280 }, func(ctx context.Context, deps commandDeps) error {
Suzy Mueller4a5e7f02022-03-02 11:22:35 -0700281 snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, args.URI, source.UnknownKind)
282 defer release()
283 if !ok {
284 return err
285 }
286 if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
287 _, err := invoke("mod", "edit", "-go", args.Version)
288 return err
289 }); err != nil {
290 return err
291 }
292 return nil
Suzy Muellerc2ddf3d2022-02-18 16:05:08 -0700293 })
294}
295
Rob Findley5fbed492021-02-09 17:25:52 -0500296func (c *commandHandler) RemoveDependency(ctx context.Context, args command.RemoveDependencyArgs) error {
297 return c.run(ctx, commandConfig{
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500298 progress: "Removing dependency",
299 forURI: args.URI,
Rob Findleya30116d2021-02-04 19:35:05 -0500300 }, func(ctx context.Context, deps commandDeps) error {
Rebecca Stambler57089f82020-12-22 23:58:35 -0500301 // If the module is tidied apart from the one unused diagnostic, we can
302 // run `go get module@none`, and then run `go mod tidy`. Otherwise, we
303 // must make textual edits.
304 // TODO(rstambler): In Go 1.17+, we will be able to use the go command
305 // without checking if the module is tidy.
Rob Findley8aef11f2021-02-05 17:55:31 -0500306 if args.OnlyDiagnostic {
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500307 return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI.SpanURI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
308 if err := runGoGetModule(invoke, false, []string{args.ModulePath + "@none"}); err != nil {
309 return err
310 }
311 _, err := invoke("mod", "tidy")
Rebecca Stambler57089f82020-12-22 23:58:35 -0500312 return err
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500313 })
Rebecca Stambler57089f82020-12-22 23:58:35 -0500314 }
Rob Findleya30116d2021-02-04 19:35:05 -0500315 pm, err := deps.snapshot.ParseMod(ctx, deps.fh)
Rebecca Stambler57089f82020-12-22 23:58:35 -0500316 if err != nil {
317 return err
318 }
Rob Findley8aef11f2021-02-05 17:55:31 -0500319 edits, err := dropDependency(deps.snapshot, pm, args.ModulePath)
Rebecca Stambler57089f82020-12-22 23:58:35 -0500320 if err != nil {
321 return err
322 }
Rob Findleya30116d2021-02-04 19:35:05 -0500323 response, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
Rebecca Stambler57089f82020-12-22 23:58:35 -0500324 Edit: protocol.WorkspaceEdit{
325 DocumentChanges: []protocol.TextDocumentEdit{{
pjwc3402e32021-01-24 14:00:12 -0500326 TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
Rob Findleya30116d2021-02-04 19:35:05 -0500327 Version: deps.fh.Version(),
Rebecca Stambler57089f82020-12-22 23:58:35 -0500328 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
Rob Findleya30116d2021-02-04 19:35:05 -0500329 URI: protocol.URIFromSpanURI(deps.fh.URI()),
Rebecca Stambler57089f82020-12-22 23:58:35 -0500330 },
331 },
332 Edits: edits,
333 }},
334 },
335 })
336 if err != nil {
337 return err
338 }
339 if !response.Applied {
340 return fmt.Errorf("edits not applied because of %s", response.FailureReason)
341 }
Rob Findleyd2671c42021-01-31 16:04:49 -0500342 return nil
343 })
Rebecca Stambler1081e672019-09-19 01:21:54 -0400344}
Marwan Sulaiman63da46f2020-03-11 00:49:10 -0400345
Rebecca Stambler57089f82020-12-22 23:58:35 -0500346// dropDependency returns the edits to remove the given require from the go.mod
347// file.
348func dropDependency(snapshot source.Snapshot, pm *source.ParsedModule, modulePath string) ([]protocol.TextEdit, error) {
349 // We need a private copy of the parsed go.mod file, since we're going to
350 // modify it.
351 copied, err := modfile.Parse("", pm.Mapper.Content, nil)
352 if err != nil {
353 return nil, err
354 }
355 if err := copied.DropRequire(modulePath); err != nil {
356 return nil, err
357 }
358 copied.Cleanup()
359 newContent, err := copied.Format()
360 if err != nil {
361 return nil, err
362 }
363 // Calculate the edits to be made due to the change.
364 diff, err := snapshot.View().Options().ComputeEdits(pm.URI, string(pm.Mapper.Content), string(newContent))
365 if err != nil {
366 return nil, err
367 }
368 return source.ToProtocolEdits(pm.Mapper, diff)
369}
370
Rob Findley5fbed492021-02-09 17:25:52 -0500371func (c *commandHandler) Test(ctx context.Context, uri protocol.DocumentURI, tests, benchmarks []string) error {
372 return c.RunTests(ctx, command.RunTestsArgs{
Rob Findley8aef11f2021-02-05 17:55:31 -0500373 URI: uri,
374 Tests: tests,
375 Benchmarks: benchmarks,
376 })
377}
378
Rob Findley5fbed492021-02-09 17:25:52 -0500379func (c *commandHandler) RunTests(ctx context.Context, args command.RunTestsArgs) error {
380 return c.run(ctx, commandConfig{
Rob Findleya30116d2021-02-04 19:35:05 -0500381 async: true,
Rob Findley8aef11f2021-02-05 17:55:31 -0500382 progress: "Running go test",
Rob Findleya30116d2021-02-04 19:35:05 -0500383 requireSave: true,
Rob Findley8aef11f2021-02-05 17:55:31 -0500384 forURI: args.URI,
Rob Findleya30116d2021-02-04 19:35:05 -0500385 }, func(ctx context.Context, deps commandDeps) error {
Rob Findley8aef11f2021-02-05 17:55:31 -0500386 if err := c.runTests(ctx, deps.snapshot, deps.work, args.URI, args.Tests, args.Benchmarks); err != nil {
Robert Findley37590b32022-04-19 18:08:06 -0400387 return fmt.Errorf("running tests failed: %w", err)
Rob Findleya30116d2021-02-04 19:35:05 -0500388 }
Rob Findleya30116d2021-02-04 19:35:05 -0500389 return nil
390 })
391}
392
Rebecca Stambler116feae2021-05-12 17:00:35 -0400393func (c *commandHandler) runTests(ctx context.Context, snapshot source.Snapshot, work *progress.WorkDone, uri protocol.DocumentURI, tests, benchmarks []string) error {
Rob Findleya30116d2021-02-04 19:35:05 -0500394 // TODO: fix the error reporting when this runs async.
Robert Findleye5f719f2021-09-03 16:03:51 -0400395 pkgs, err := snapshot.PackagesForFile(ctx, uri.SpanURI(), source.TypecheckWorkspace, false)
Brayden Cloudc7ca5262020-07-30 22:48:51 +0000396 if err != nil {
397 return err
398 }
399 if len(pkgs) == 0 {
400 return fmt.Errorf("package could not be found for file: %s", uri.SpanURI().Filename())
401 }
Pontus Leitzler368bee82020-12-06 21:01:19 +0100402 pkgPath := pkgs[0].ForTest()
Brayden Cloudc7ca5262020-07-30 22:48:51 +0000403
404 // create output
Rob Findley1e23e482020-08-21 14:18:37 -0400405 buf := &bytes.Buffer{}
Rebecca Stambler116feae2021-05-12 17:00:35 -0400406 ew := progress.NewEventWriter(ctx, "test")
Robert Findley459e2b82022-07-12 17:13:32 -0400407 out := io.MultiWriter(ew, progress.NewWorkDoneWriter(ctx, work), buf)
Martin Asquino2bc93b12020-05-05 19:43:51 +0100408
Rob Findleyed71c572020-08-17 22:50:34 -0400409 // Run `go test -run Func` on each test.
Brayden Cloudc7ca5262020-07-30 22:48:51 +0000410 var failedTests int
411 for _, funcName := range tests {
Heschi Kreinick2b84a062020-10-26 15:13:16 -0400412 inv := &gocommand.Invocation{
413 Verb: "test",
414 Args: []string{pkgPath, "-v", "-count=1", "-run", fmt.Sprintf("^%s$", funcName)},
415 WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
416 }
Heschi Kreinick49729132020-10-26 17:57:45 -0400417 if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, out, out); err != nil {
Brayden Cloudc7ca5262020-07-30 22:48:51 +0000418 if errors.Is(err, context.Canceled) {
419 return err
420 }
421 failedTests++
Rebecca Stambler9fe02d12020-06-12 14:57:48 -0400422 }
Rebecca Stambler9fe02d12020-06-12 14:57:48 -0400423 }
Brayden Cloudc7ca5262020-07-30 22:48:51 +0000424
Rob Findleyed71c572020-08-17 22:50:34 -0400425 // Run `go test -run=^$ -bench Func` on each test.
Brayden Cloudc7ca5262020-07-30 22:48:51 +0000426 var failedBenchmarks int
Rob Findley1e23e482020-08-21 14:18:37 -0400427 for _, funcName := range benchmarks {
Heschi Kreinick2b84a062020-10-26 15:13:16 -0400428 inv := &gocommand.Invocation{
429 Verb: "test",
430 Args: []string{pkgPath, "-v", "-run=^$", "-bench", fmt.Sprintf("^%s$", funcName)},
431 WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
432 }
Heschi Kreinick49729132020-10-26 17:57:45 -0400433 if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, out, out); err != nil {
Brayden Cloudc7ca5262020-07-30 22:48:51 +0000434 if errors.Is(err, context.Canceled) {
435 return err
436 }
437 failedBenchmarks++
438 }
439 }
440
Rob Findley1e23e482020-08-21 14:18:37 -0400441 var title string
442 if len(tests) > 0 && len(benchmarks) > 0 {
443 title = "tests and benchmarks"
444 } else if len(tests) > 0 {
445 title = "tests"
446 } else if len(benchmarks) > 0 {
447 title = "benchmarks"
448 } else {
449 return errors.New("No functions were provided")
Brayden Cloudc7ca5262020-07-30 22:48:51 +0000450 }
Rob Findley1e23e482020-08-21 14:18:37 -0400451 message := fmt.Sprintf("all %s passed", title)
Brayden Cloudc7ca5262020-07-30 22:48:51 +0000452 if failedTests > 0 && failedBenchmarks > 0 {
453 message = fmt.Sprintf("%d / %d tests failed and %d / %d benchmarks failed", failedTests, len(tests), failedBenchmarks, len(benchmarks))
454 } else if failedTests > 0 {
455 message = fmt.Sprintf("%d / %d tests failed", failedTests, len(tests))
456 } else if failedBenchmarks > 0 {
457 message = fmt.Sprintf("%d / %d benchmarks failed", failedBenchmarks, len(benchmarks))
458 }
Rob Findley1e23e482020-08-21 14:18:37 -0400459 if failedTests > 0 || failedBenchmarks > 0 {
Rob Findley1e23e482020-08-21 14:18:37 -0400460 message += "\n" + buf.String()
461 }
Brayden Cloudc7ca5262020-07-30 22:48:51 +0000462
Rob Findleya30116d2021-02-04 19:35:05 -0500463 return c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
Pontus Leitzler69daaf92020-11-11 13:12:26 +0100464 Type: protocol.Info,
Martin Asquino2bc93b12020-05-05 19:43:51 +0100465 Message: message,
466 })
467}
468
Rob Findley5fbed492021-02-09 17:25:52 -0500469func (c *commandHandler) Generate(ctx context.Context, args command.GenerateArgs) error {
Rob Findley8aef11f2021-02-05 17:55:31 -0500470 title := "Running go generate ."
471 if args.Recursive {
472 title = "Running go generate ./..."
473 }
Rob Findley5fbed492021-02-09 17:25:52 -0500474 return c.run(ctx, commandConfig{
Rob Findleya30116d2021-02-04 19:35:05 -0500475 requireSave: true,
Rob Findley8aef11f2021-02-05 17:55:31 -0500476 progress: title,
477 forURI: args.Dir,
Rob Findleya30116d2021-02-04 19:35:05 -0500478 }, func(ctx context.Context, deps commandDeps) error {
Rebecca Stambler116feae2021-05-12 17:00:35 -0400479 er := progress.NewEventWriter(ctx, "generate")
Rebecca Stamblerc80dc572020-07-15 23:33:55 -0400480
Rob Findleya30116d2021-02-04 19:35:05 -0500481 pattern := "."
Rob Findley8aef11f2021-02-05 17:55:31 -0500482 if args.Recursive {
Rob Findleya30116d2021-02-04 19:35:05 -0500483 pattern = "./..."
484 }
485 inv := &gocommand.Invocation{
486 Verb: "generate",
487 Args: []string{"-x", pattern},
Rob Findley8aef11f2021-02-05 17:55:31 -0500488 WorkingDir: args.Dir.SpanURI().Filename(),
Rob Findleya30116d2021-02-04 19:35:05 -0500489 }
Robert Findley459e2b82022-07-12 17:13:32 -0400490 stderr := io.MultiWriter(er, progress.NewWorkDoneWriter(ctx, deps.work))
Rob Findleya30116d2021-02-04 19:35:05 -0500491 if err := deps.snapshot.RunGoCommandPiped(ctx, source.Normal, inv, er, stderr); err != nil {
492 return err
493 }
494 return nil
Heschi Kreinickbd5d1602020-11-10 18:20:37 -0500495 })
Heschi Kreinickbd5d1602020-11-10 18:20:37 -0500496}
497
Rob Findley5fbed492021-02-09 17:25:52 -0500498func (c *commandHandler) GoGetPackage(ctx context.Context, args command.GoGetPackageArgs) error {
499 return c.run(ctx, commandConfig{
Rob Findley8aef11f2021-02-05 17:55:31 -0500500 forURI: args.URI,
501 progress: "Running go get",
Rob Findleya30116d2021-02-04 19:35:05 -0500502 }, func(ctx context.Context, deps commandDeps) error {
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500503 // Run on a throwaway go.mod, otherwise it'll write to the real one.
Rob Findleya30116d2021-02-04 19:35:05 -0500504 stdout, err := deps.snapshot.RunGoCommandDirect(ctx, source.WriteTemporaryModFile|source.AllowNetwork, &gocommand.Invocation{
505 Verb: "list",
Rob Findley8aef11f2021-02-05 17:55:31 -0500506 Args: []string{"-f", "{{.Module.Path}}@{{.Module.Version}}", args.Pkg},
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500507 WorkingDir: filepath.Dir(args.URI.SpanURI().Filename()),
Rob Findleya30116d2021-02-04 19:35:05 -0500508 })
509 if err != nil {
510 return err
511 }
512 ver := strings.TrimSpace(stdout.String())
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500513 return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI.SpanURI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
Rebecca Stambler9eb35352021-02-18 11:30:08 -0500514 if args.AddRequire {
515 if err := addModuleRequire(invoke, []string{ver}); err != nil {
516 return err
517 }
518 }
519 _, err := invoke(append([]string{"get", "-d"}, args.Pkg)...)
520 return err
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500521 })
Rob Findleya30116d2021-02-04 19:35:05 -0500522 })
523}
524
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500525func (s *Server) runGoModUpdateCommands(ctx context.Context, snapshot source.Snapshot, uri span.URI, run func(invoke func(...string) (*bytes.Buffer, error)) error) error {
526 tmpModfile, newModBytes, newSumBytes, err := snapshot.RunGoCommands(ctx, true, filepath.Dir(uri.Filename()), run)
527 if err != nil {
528 return err
529 }
530 if !tmpModfile {
531 return nil
532 }
533 modURI := snapshot.GoModForFile(uri)
534 sumURI := span.URIFromPath(strings.TrimSuffix(modURI.Filename(), ".mod") + ".sum")
535 modEdits, err := applyFileEdits(ctx, snapshot, modURI, newModBytes)
536 if err != nil {
537 return err
538 }
539 sumEdits, err := applyFileEdits(ctx, snapshot, sumURI, newSumBytes)
540 if err != nil {
541 return err
542 }
543 changes := append(sumEdits, modEdits...)
544 if len(changes) == 0 {
545 return nil
546 }
547 response, err := s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
548 Edit: protocol.WorkspaceEdit{
549 DocumentChanges: changes,
550 },
551 })
552 if err != nil {
553 return err
554 }
555 if !response.Applied {
556 return fmt.Errorf("edits not applied because of %s", response.FailureReason)
557 }
558 return nil
559}
560
561func applyFileEdits(ctx context.Context, snapshot source.Snapshot, uri span.URI, newContent []byte) ([]protocol.TextDocumentEdit, error) {
562 fh, err := snapshot.GetVersionedFile(ctx, uri)
563 if err != nil {
564 return nil, err
565 }
566 oldContent, err := fh.Read()
567 if err != nil && !os.IsNotExist(err) {
568 return nil, err
569 }
570 if bytes.Equal(oldContent, newContent) {
571 return nil, nil
572 }
573
574 // Sending a workspace edit to a closed file causes VS Code to open the
575 // file and leave it unsaved. We would rather apply the changes directly,
576 // especially to go.sum, which should be mostly invisible to the user.
577 if !snapshot.IsOpen(uri) {
578 err := ioutil.WriteFile(uri.Filename(), newContent, 0666)
579 return nil, err
580 }
581
Robert Findley9d7bf952022-05-12 23:23:17 -0400582 m := protocol.NewColumnMapper(fh.URI(), oldContent)
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500583 diff, err := snapshot.View().Options().ComputeEdits(uri, string(oldContent), string(newContent))
584 if err != nil {
585 return nil, err
586 }
587 edits, err := source.ToProtocolEdits(m, diff)
588 if err != nil {
589 return nil, err
590 }
591 return []protocol.TextDocumentEdit{{
592 TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
593 Version: fh.Version(),
594 TextDocumentIdentifier: protocol.TextDocumentIdentifier{
595 URI: protocol.URIFromSpanURI(uri),
596 },
597 },
598 Edits: edits,
599 }}, nil
600}
601
602func runGoGetModule(invoke func(...string) (*bytes.Buffer, error), addRequire bool, args []string) error {
Heschi Kreinicka9763ab2020-11-10 18:20:37 -0500603 if addRequire {
Rebecca Stambler9eb35352021-02-18 11:30:08 -0500604 if err := addModuleRequire(invoke, args); err != nil {
Heschi Kreinicka9763ab2020-11-10 18:20:37 -0500605 return err
606 }
607 }
Heschi Kreinick706a59c2021-02-05 22:18:47 -0500608 _, err := invoke(append([]string{"get", "-d"}, args...)...)
Heschi Kreinicka9763ab2020-11-10 18:20:37 -0500609 return err
610}
Heschi Kreinickf1f686b2021-01-25 15:51:00 -0500611
Rebecca Stambler9eb35352021-02-18 11:30:08 -0500612func addModuleRequire(invoke func(...string) (*bytes.Buffer, error), args []string) error {
613 // Using go get to create a new dependency results in an
614 // `// indirect` comment we may not want. The only way to avoid it
615 // is to add the require as direct first. Then we can use go get to
616 // update go.sum and tidy up.
617 _, err := invoke(append([]string{"mod", "edit", "-require"}, args...)...)
618 return err
619}
620
Heschi Kreinickf1f686b2021-01-25 15:51:00 -0500621func (s *Server) getUpgrades(ctx context.Context, snapshot source.Snapshot, uri span.URI, modules []string) (map[string]string, error) {
622 stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal|source.AllowNetwork, &gocommand.Invocation{
623 Verb: "list",
624 Args: append([]string{"-m", "-u", "-json"}, modules...),
625 WorkingDir: filepath.Dir(uri.Filename()),
Heschi Kreinickf7e8e242021-04-20 14:27:24 -0400626 ModFlag: "readonly",
Heschi Kreinickf1f686b2021-01-25 15:51:00 -0500627 })
628 if err != nil {
629 return nil, err
630 }
631
632 upgrades := map[string]string{}
633 for dec := json.NewDecoder(stdout); dec.More(); {
634 mod := &gocommand.ModuleJSON{}
635 if err := dec.Decode(mod); err != nil {
636 return nil, err
637 }
638 if mod.Update == nil {
639 continue
640 }
641 upgrades[mod.Path] = mod.Update.Version
642 }
643 return upgrades, nil
644}
Rob Findleya30116d2021-02-04 19:35:05 -0500645
Rob Findley5fbed492021-02-09 17:25:52 -0500646func (c *commandHandler) GCDetails(ctx context.Context, uri protocol.DocumentURI) error {
647 return c.ToggleGCDetails(ctx, command.URIArg{URI: uri})
Rob Findley8aef11f2021-02-05 17:55:31 -0500648}
649
Rob Findley5fbed492021-02-09 17:25:52 -0500650func (c *commandHandler) ToggleGCDetails(ctx context.Context, args command.URIArg) error {
651 return c.run(ctx, commandConfig{
Rob Findleya30116d2021-02-04 19:35:05 -0500652 requireSave: true,
Rob Findley8aef11f2021-02-05 17:55:31 -0500653 progress: "Toggling GC Details",
654 forURI: args.URI,
Rob Findleya30116d2021-02-04 19:35:05 -0500655 }, func(ctx context.Context, deps commandDeps) error {
Heschi Kreinick2ac05c82021-02-28 00:10:04 -0500656 pkg, err := deps.snapshot.PackageForFile(ctx, deps.fh.URI(), source.TypecheckWorkspace, source.NarrowestPackage)
657 if err != nil {
658 return err
659 }
Rob Findleya30116d2021-02-04 19:35:05 -0500660 c.s.gcOptimizationDetailsMu.Lock()
Heschi Kreinick2ac05c82021-02-28 00:10:04 -0500661 if _, ok := c.s.gcOptimizationDetails[pkg.ID()]; ok {
662 delete(c.s.gcOptimizationDetails, pkg.ID())
Rob Findleya30116d2021-02-04 19:35:05 -0500663 c.s.clearDiagnosticSource(gcDetailsSource)
664 } else {
Heschi Kreinick2ac05c82021-02-28 00:10:04 -0500665 c.s.gcOptimizationDetails[pkg.ID()] = struct{}{}
Rob Findleya30116d2021-02-04 19:35:05 -0500666 }
667 c.s.gcOptimizationDetailsMu.Unlock()
668 c.s.diagnoseSnapshot(deps.snapshot, nil, false)
669 return nil
670 })
671}
672
Rob Findley5fbed492021-02-09 17:25:52 -0500673func (c *commandHandler) GenerateGoplsMod(ctx context.Context, args command.URIArg) error {
Rob Findley8aef11f2021-02-05 17:55:31 -0500674 // TODO: go back to using URI
Rob Findley5fbed492021-02-09 17:25:52 -0500675 return c.run(ctx, commandConfig{
Rob Findleya30116d2021-02-04 19:35:05 -0500676 requireSave: true,
Rob Findley8aef11f2021-02-05 17:55:31 -0500677 progress: "Generating gopls.mod",
Rob Findleya30116d2021-02-04 19:35:05 -0500678 }, func(ctx context.Context, deps commandDeps) error {
679 views := c.s.session.Views()
680 if len(views) != 1 {
681 return fmt.Errorf("cannot resolve view: have %d views", len(views))
682 }
683 v := views[0]
684 snapshot, release := v.Snapshot(ctx)
685 defer release()
Rebecca Stambler4b484fb2021-06-11 00:40:37 -0400686 modFile, err := snapshot.BuildGoplsMod(ctx)
Rob Findleya30116d2021-02-04 19:35:05 -0500687 if err != nil {
Robert Findley37590b32022-04-19 18:08:06 -0400688 return fmt.Errorf("getting workspace mod file: %w", err)
Rob Findleya30116d2021-02-04 19:35:05 -0500689 }
690 content, err := modFile.Format()
691 if err != nil {
Robert Findley37590b32022-04-19 18:08:06 -0400692 return fmt.Errorf("formatting mod file: %w", err)
Rob Findleya30116d2021-02-04 19:35:05 -0500693 }
Alan Donovan53ead672022-07-05 10:30:58 -0400694 filename := filepath.Join(v.Folder().Filename(), "gopls.mod")
Rob Findleya30116d2021-02-04 19:35:05 -0500695 if err := ioutil.WriteFile(filename, content, 0644); err != nil {
Robert Findley37590b32022-04-19 18:08:06 -0400696 return fmt.Errorf("writing mod file: %w", err)
Rob Findleya30116d2021-02-04 19:35:05 -0500697 }
698 return nil
699 })
700}
Rob Findley8316e562021-02-10 19:22:19 -0500701
702func (c *commandHandler) ListKnownPackages(ctx context.Context, args command.URIArg) (command.ListKnownPackagesResult, error) {
703 var result command.ListKnownPackagesResult
704 err := c.run(ctx, commandConfig{
Marwan Sulaiman40613122021-05-15 10:04:17 -0400705 progress: "Listing packages",
706 forURI: args.URI,
Rob Findley8316e562021-02-10 19:22:19 -0500707 }, func(ctx context.Context, deps commandDeps) error {
Marwan Sulaiman40613122021-05-15 10:04:17 -0400708 var err error
709 result.Packages, err = source.KnownPackages(ctx, deps.snapshot, deps.fh)
710 return err
Rob Findley8316e562021-02-10 19:22:19 -0500711 })
712 return result, err
713}
Heschi Kreinick414ec9c2022-02-04 15:23:09 -0500714
715func (c *commandHandler) ListImports(ctx context.Context, args command.URIArg) (command.ListImportsResult, error) {
716 var result command.ListImportsResult
717 err := c.run(ctx, commandConfig{
718 forURI: args.URI,
719 }, func(ctx context.Context, deps commandDeps) error {
720 pkg, err := deps.snapshot.PackageForFile(ctx, args.URI.SpanURI(), source.TypecheckWorkspace, source.NarrowestPackage)
721 if err != nil {
722 return err
723 }
724 pgf, err := pkg.File(args.URI.SpanURI())
725 if err != nil {
726 return err
727 }
728 for _, group := range astutil.Imports(deps.snapshot.FileSet(), pgf.File) {
729 for _, imp := range group {
730 if imp.Path == nil {
731 continue
732 }
733 var name string
734 if imp.Name != nil {
735 name = imp.Name.Name
736 }
737 result.Imports = append(result.Imports, command.FileImport{
738 Path: source.ImportPath(imp),
739 Name: name,
740 })
741 }
742 }
743 for _, imp := range pkg.Imports() {
744 result.PackageImports = append(result.PackageImports, command.PackageImport{
745 Path: imp.PkgPath(), // This might be the vendored path under GOPATH vendoring, in which case it's a bug.
746 })
747 }
748 sort.Slice(result.PackageImports, func(i, j int) bool {
749 return result.PackageImports[i].Path < result.PackageImports[j].Path
750 })
751 return nil
752 })
753 return result, err
754}
755
Marwan Sulaiman40613122021-05-15 10:04:17 -0400756func (c *commandHandler) AddImport(ctx context.Context, args command.AddImportArgs) error {
757 return c.run(ctx, commandConfig{
Rob Findley8316e562021-02-10 19:22:19 -0500758 progress: "Adding import",
759 forURI: args.URI,
760 }, func(ctx context.Context, deps commandDeps) error {
Marwan Sulaiman40613122021-05-15 10:04:17 -0400761 edits, err := source.AddImport(ctx, deps.snapshot, deps.fh, args.ImportPath)
762 if err != nil {
763 return fmt.Errorf("could not add import: %v", err)
764 }
765 if _, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
766 Edit: protocol.WorkspaceEdit{
767 DocumentChanges: documentChanges(deps.fh, edits),
768 },
769 }); err != nil {
770 return fmt.Errorf("could not apply import edits: %v", err)
771 }
Rob Findley8316e562021-02-10 19:22:19 -0500772 return nil
773 })
Rob Findley8316e562021-02-10 19:22:19 -0500774}
Rob Findley6d45e3d2021-03-09 14:09:48 -0500775
Rob Findley7c934842021-04-12 12:38:22 -0400776func (c *commandHandler) StartDebugging(ctx context.Context, args command.DebuggingArgs) (result command.DebuggingResult, _ error) {
777 addr := args.Addr
778 if addr == "" {
779 addr = "localhost:0"
780 }
781 di := debug.GetInstance(ctx)
782 if di == nil {
783 return result, errors.New("internal error: server has no debugging instance")
784 }
785 listenedAddr, err := di.Serve(ctx, addr)
786 if err != nil {
Robert Findley37590b32022-04-19 18:08:06 -0400787 return result, fmt.Errorf("starting debug server: %w", err)
Rob Findley7c934842021-04-12 12:38:22 -0400788 }
789 result.URLs = []string{"http://" + listenedAddr}
790 return result, nil
791}
Hanacd31eaa2022-03-24 12:24:17 -0400792
Hana (Hyang-Ah) Kimbceee4b2022-08-02 12:19:22 -0400793// Copy of pkgLoadConfig defined in internal/lsp/cmd/vulncheck.go
794// TODO(hyangah): decide where to define this.
795type pkgLoadConfig struct {
796 // BuildFlags is a list of command-line flags to be passed through to
797 // the build system's query tool.
798 BuildFlags []string
799
Hana (Hyang-Ah) Kimbceee4b2022-08-02 12:19:22 -0400800 // If Tests is set, the loader includes related test packages.
801 Tests bool
802}
803
804func (c *commandHandler) RunVulncheckExp(ctx context.Context, args command.VulncheckArgs) error {
Hana (Hyang-Ah) Kimb5fd0882022-08-02 22:39:32 -0400805 if args.URI == "" {
806 return errors.New("VulncheckArgs is missing URI field")
Hana (Hyang-Ah) Kimbceee4b2022-08-02 12:19:22 -0400807 }
Hanacd31eaa2022-03-24 12:24:17 -0400808 err := c.run(ctx, commandConfig{
Hana (Hyang-Ah) Kimbceee4b2022-08-02 12:19:22 -0400809 async: true, // need to be async to be cancellable
Hana (Hyang-Ah) Kim81c7dc42022-08-03 00:01:48 -0400810 progress: "govulncheck",
Hanacd31eaa2022-03-24 12:24:17 -0400811 requireSave: true,
Hana (Hyang-Ah) Kimb5fd0882022-08-02 22:39:32 -0400812 forURI: args.URI,
Hanacd31eaa2022-03-24 12:24:17 -0400813 }, func(ctx context.Context, deps commandDeps) error {
814 view := deps.snapshot.View()
815 opts := view.Options()
816 if opts == nil || opts.Hooks.Govulncheck == nil {
817 return errors.New("vulncheck feature is not available")
818 }
819
Hana (Hyang-Ah) Kimaf2a0a82022-08-02 23:28:18 -0400820 cmd := exec.CommandContext(ctx, os.Args[0], "vulncheck", "-config", args.Pattern)
Hana (Hyang-Ah) Kimb5fd0882022-08-02 22:39:32 -0400821 cmd.Dir = filepath.Dir(args.URI.SpanURI().Filename())
Hana (Hyang-Ah) Kimbceee4b2022-08-02 12:19:22 -0400822
Hanacd31eaa2022-03-24 12:24:17 -0400823 var viewEnv []string
824 if e := opts.EnvSlice(); e != nil {
825 viewEnv = append(os.Environ(), e...)
826 }
Hana (Hyang-Ah) Kimbceee4b2022-08-02 12:19:22 -0400827 cmd.Env = viewEnv
828
829 // stdin: gopls vulncheck expects JSON-encoded configuration from STDIN when -config flag is set.
830 var stdin bytes.Buffer
831 cmd.Stdin = &stdin
832
833 if err := json.NewEncoder(&stdin).Encode(pkgLoadConfig{
834 BuildFlags: opts.BuildFlags,
835 // TODO(hyangah): add `tests` flag in command.VulncheckArgs
836 }); err != nil {
837 return fmt.Errorf("failed to pass package load config: %v", err)
Hanacd31eaa2022-03-24 12:24:17 -0400838 }
Hana (Hyang-Ah) Kimbceee4b2022-08-02 12:19:22 -0400839
840 // stderr: stream gopls vulncheck's STDERR as progress reports
841 er := progress.NewEventWriter(ctx, "vulncheck")
842 stderr := io.MultiWriter(er, progress.NewWorkDoneWriter(ctx, deps.work))
843 cmd.Stderr = stderr
844 // TODO: can we stream stdout?
845 stdout, err := cmd.Output()
846 if err != nil {
847 return fmt.Errorf("failed to run govulncheck: %v", err)
848 }
849
850 var vulns command.VulncheckResult
851 if err := json.Unmarshal(stdout, &vulns); err != nil {
852 // TODO: for easy debugging, log the failed stdout somewhere?
853 return fmt.Errorf("failed to parse govulncheck output: %v", err)
854 }
855
Hana (Hyang-Ah) Kim6c277172022-08-02 23:15:52 -0400856 // TODO(jamalc,suzmue): convert the results to diagnostics & code actions.
857 // Or should we just write to a file (*.vulncheck.json) or text format
858 // and send "Show Document" request? If *.vulncheck.json is open,
859 // VSCode Go extension will open its custom editor.
860 set := make(map[string]bool)
861 for _, v := range vulns.Vuln {
862 if len(v.CallStackSummaries) > 0 {
863 set[v.ID] = true
864 }
865 }
866 if len(set) == 0 {
867 return c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
868 Type: protocol.Info,
869 Message: "No vulnerabilities found",
870 })
871 }
872
873 list := make([]string, 0, len(set))
874 for k := range set {
875 list = append(list, k)
876 }
877 sort.Strings(list)
878 return c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
879 Type: protocol.Warning,
880 Message: fmt.Sprintf("Found %v", strings.Join(list, ", ")),
881 })
Hanacd31eaa2022-03-24 12:24:17 -0400882 })
Hana (Hyang-Ah) Kimbceee4b2022-08-02 12:19:22 -0400883 return err
Hanacd31eaa2022-03-24 12:24:17 -0400884}