Skip to content

Commit f1f686b

Browse files
committed
internal/lsp: re-enable upgrades for individual dependencies
In CL 271297, I disabled the constantly-running upgrade check, which removed the upgrade commands for individual dependencies. This seems to have been a relatively popular feature. Re-introduce it, but requiring explicit user interaction. We now run an upgrade check when the user clicks "Check for upgrades". Those results are stored on the View and used to show diagnostics on any requires they apply to. Right now we only check the go.mod the user has open; in multi-module workspaces it might make sense to check all of them, but I'm not sure. Fixes golang/go#42969. Change-Id: I65205dc99a4fa9daafdb83145b0294b6f3be5336 Reviewed-on: https://go-review.googlesource.com/c/tools/+/286474 Trust: Heschi Kreinick <[email protected]> Run-TryBot: Heschi Kreinick <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent d8a2a07 commit f1f686b

File tree

11 files changed

+180
-16
lines changed

11 files changed

+180
-16
lines changed

gopls/doc/commands.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ Identifier: `gopls.go_get_package`
5353
go_get_package runs `go get` to fetch a package.
5454

5555

56+
### **Check for upgrades**
57+
Identifier: `gopls.check_upgrades`
58+
59+
check_upgrades checks for module upgrades.
60+
61+
5662
### **Add dependency**
5763
Identifier: `gopls.add_dependency`
5864

gopls/internal/regtest/codelens/codelens_test.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@ func main() {
116116
_ = hi.Goodbye
117117
}
118118
`
119+
120+
const wantGoMod = `module mod.com
121+
122+
go 1.12
123+
124+
require golang.org/x/hello v1.3.3
125+
`
126+
119127
for _, commandTitle := range []string{
120128
"Upgrade transitive dependencies",
121129
"Upgrade direct dependencies",
@@ -143,19 +151,26 @@ func main() {
143151
t.Fatal(err)
144152
}
145153
env.Await(env.DoneWithChangeWatchedFiles())
146-
got := env.Editor.BufferText("go.mod")
147-
const wantGoMod = `module mod.com
148-
149-
go 1.12
150-
151-
require golang.org/x/hello v1.3.3
152-
`
153-
if got != wantGoMod {
154+
if got := env.Editor.BufferText("go.mod"); got != wantGoMod {
154155
t.Fatalf("go.mod upgrade failed:\n%s", tests.Diff(t, wantGoMod, got))
155156
}
156157
})
157158
})
158159
}
160+
t.Run("Upgrade individual dependency", func(t *testing.T) {
161+
WithOptions(ProxyFiles(proxyWithLatest)).Run(t, shouldUpdateDep, func(t *testing.T, env *Env) {
162+
env.OpenFile("go.mod")
163+
env.ExecuteCodeLensCommand("go.mod", source.CommandCheckUpgrades)
164+
d := &protocol.PublishDiagnosticsParams{}
165+
env.Await(OnceMet(env.DiagnosticAtRegexpWithMessage("go.mod", `require`, "can be upgraded"),
166+
ReadDiagnostics("go.mod", d)))
167+
env.ApplyQuickFixes("go.mod", d.Diagnostics)
168+
env.Await(env.DoneWithChangeWatchedFiles())
169+
if got := env.Editor.BufferText("go.mod"); got != wantGoMod {
170+
t.Fatalf("go.mod upgrade failed:\n%s", tests.Diff(t, wantGoMod, got))
171+
}
172+
})
173+
})
159174
}
160175

161176
func TestUnusedDependenciesCodelens(t *testing.T) {

internal/gocommand/vendor.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,23 @@ import (
1212
"path/filepath"
1313
"regexp"
1414
"strings"
15+
"time"
1516

1617
"golang.org/x/mod/semver"
1718
)
1819

1920
// ModuleJSON holds information about a module.
2021
type ModuleJSON struct {
2122
Path string // module path
23+
Version string // module version
24+
Versions []string // available module versions (with -versions)
2225
Replace *ModuleJSON // replaced by this module
26+
Time *time.Time // time version was created
27+
Update *ModuleJSON // available update, if any (with -u)
2328
Main bool // is this the main module?
2429
Indirect bool // is this module only an indirect dependency of main module?
2530
Dir string // directory holding files for this module, if any
26-
GoMod string // path to go.mod file for this module, if any
31+
GoMod string // path to go.mod file used when loading this module, if any
2732
GoVersion string // go version used in module
2833
}
2934

internal/lsp/cache/session.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,9 @@ func (s *Session) createView(ctx context.Context, name string, folder, tempWorks
200200
baseCtx: baseCtx,
201201
name: name,
202202
folder: folder,
203-
filesByURI: make(map[span.URI]*fileBase),
204-
filesByBase: make(map[string][]*fileBase),
203+
moduleUpgrades: map[string]string{},
204+
filesByURI: map[span.URI]*fileBase{},
205+
filesByBase: map[string][]*fileBase{},
205206
rootURI: root,
206207
workspaceInformation: *ws,
207208
tempWorkspace: tempWorkspace,

internal/lsp/cache/view.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ type View struct {
6060

6161
importsState *importsState
6262

63+
// moduleUpgrades tracks known upgrades for module paths.
64+
moduleUpgrades map[string]string
65+
6366
// keep track of files by uri and by basename, a single file may be mapped
6467
// to multiple uris, and the same basename may map to multiple files
6568
filesByURI map[span.URI]*fileBase
@@ -863,6 +866,26 @@ func (v *View) IsGoPrivatePath(target string) bool {
863866
return globsMatchPath(v.goprivate, target)
864867
}
865868

869+
func (v *View) ModuleUpgrades() map[string]string {
870+
v.mu.Lock()
871+
defer v.mu.Unlock()
872+
873+
upgrades := map[string]string{}
874+
for mod, ver := range v.moduleUpgrades {
875+
upgrades[mod] = ver
876+
}
877+
return upgrades
878+
}
879+
880+
func (v *View) RegisterModuleUpgrades(upgrades map[string]string) {
881+
v.mu.Lock()
882+
defer v.mu.Unlock()
883+
884+
for mod, ver := range upgrades {
885+
v.moduleUpgrades[mod] = ver
886+
}
887+
}
888+
866889
// Copied from
867890
// https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/str/path.go;l=58;drc=2910c5b4a01a573ebc97744890a07c1a3122c67a
868891
func globsMatchPath(globs, target string) bool {

internal/lsp/command.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,25 @@ func (s *Server) runCommand(ctx context.Context, work *workDone, command *source
217217
return err
218218
}
219219
return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile|source.AllowNetwork, uri.SpanURI(), "list", []string{"all"})
220+
case source.CommandCheckUpgrades:
221+
var uri protocol.DocumentURI
222+
var modules []string
223+
if err := source.UnmarshalArgs(args, &uri, &modules); err != nil {
224+
return err
225+
}
226+
snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
227+
defer release()
228+
if !ok {
229+
return err
230+
}
231+
upgrades, err := s.getUpgrades(ctx, snapshot, uri.SpanURI(), modules)
232+
if err != nil {
233+
return err
234+
}
235+
snapshot.View().RegisterModuleUpgrades(upgrades)
236+
// Re-diagnose the snapshot to publish the new module diagnostics.
237+
s.diagnoseSnapshot(snapshot, nil, false)
238+
return nil
220239
case source.CommandAddDependency, source.CommandUpgradeDependency:
221240
var uri protocol.DocumentURI
222241
var goCmdArgs []string
@@ -511,3 +530,27 @@ func runSimpleGoCommand(ctx context.Context, snapshot source.Snapshot, mode sour
511530
})
512531
return err
513532
}
533+
534+
func (s *Server) getUpgrades(ctx context.Context, snapshot source.Snapshot, uri span.URI, modules []string) (map[string]string, error) {
535+
stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal|source.AllowNetwork, &gocommand.Invocation{
536+
Verb: "list",
537+
Args: append([]string{"-m", "-u", "-json"}, modules...),
538+
WorkingDir: filepath.Dir(uri.Filename()),
539+
})
540+
if err != nil {
541+
return nil, err
542+
}
543+
544+
upgrades := map[string]string{}
545+
for dec := json.NewDecoder(stdout); dec.More(); {
546+
mod := &gocommand.ModuleJSON{}
547+
if err := dec.Decode(mod); err != nil {
548+
return nil, err
549+
}
550+
if mod.Update == nil {
551+
continue
552+
}
553+
upgrades[mod.Path] = mod.Update.Version
554+
}
555+
return upgrades, nil
556+
}

internal/lsp/mod/code_lens.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.File
4242
for _, req := range pm.File.Require {
4343
requires = append(requires, req.Mod.Path)
4444
}
45+
checkUpgradeArgs, err := source.MarshalArgs(fh.URI(), requires)
46+
if err != nil {
47+
return nil, err
48+
}
4549
upgradeDirectArgs, err := source.MarshalArgs(fh.URI(), false, requires)
4650
if err != nil {
4751
return nil, err
@@ -51,7 +55,16 @@ func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.File
5155
if err != nil {
5256
return nil, err
5357
}
58+
5459
return []protocol.CodeLens{
60+
{
61+
Range: rng,
62+
Command: protocol.Command{
63+
Title: "Check for upgrades",
64+
Command: source.CommandCheckUpgrades.ID(),
65+
Arguments: checkUpgradeArgs,
66+
},
67+
},
5568
{
5669
Range: rng,
5770
Command: protocol.Command{
@@ -69,7 +82,6 @@ func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.File
6982
},
7083
},
7184
}, nil
72-
7385
}
7486

7587
func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {

internal/lsp/mod/diagnostics.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package mod
88

99
import (
1010
"context"
11+
"fmt"
1112

1213
"golang.org/x/tools/internal/event"
1314
"golang.org/x/tools/internal/lsp/debug/tag"
@@ -36,9 +37,12 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.Vers
3637
Range: e.Range,
3738
Source: e.Category,
3839
}
39-
if e.Category == "syntax" || e.Kind == source.ListError {
40+
switch {
41+
case e.Category == "syntax", e.Kind == source.ListError:
4042
d.Severity = protocol.SeverityError
41-
} else {
43+
case e.Kind == source.UpgradeNotification:
44+
d.Severity = protocol.SeverityInformation
45+
default:
4246
d.Severity = protocol.SeverityWarning
4347
}
4448
fh, err := snapshot.GetVersionedFile(ctx, e.URI)
@@ -59,13 +63,49 @@ func ErrorsForMod(ctx context.Context, snapshot source.Snapshot, fh source.FileH
5963
}
6064
return pm.ParseErrors, nil
6165
}
66+
67+
var errors []*source.Error
68+
69+
// Add upgrade quick fixes for individual modules if we know about them.
70+
upgrades := snapshot.View().ModuleUpgrades()
71+
for _, req := range pm.File.Require {
72+
ver, ok := upgrades[req.Mod.Path]
73+
if !ok || req.Mod.Version == ver {
74+
continue
75+
}
76+
rng, err := lineToRange(pm.Mapper, fh.URI(), req.Syntax.Start, req.Syntax.End)
77+
if err != nil {
78+
return nil, err
79+
}
80+
// Upgrade to the exact version we offer the user, not the most recent.
81+
args, err := source.MarshalArgs(fh.URI(), false, []string{req.Mod.Path + "@" + ver})
82+
if err != nil {
83+
return nil, err
84+
}
85+
errors = append(errors, &source.Error{
86+
URI: fh.URI(),
87+
Range: rng,
88+
Kind: source.UpgradeNotification,
89+
Message: fmt.Sprintf("%v can be upgraded", req.Mod.Path),
90+
SuggestedFixes: []source.SuggestedFix{{
91+
Title: fmt.Sprintf("Upgrade to %v", ver),
92+
Command: &protocol.Command{
93+
Title: fmt.Sprintf("Upgrade to %v", ver),
94+
Command: source.CommandUpgradeDependency.ID(),
95+
Arguments: args,
96+
},
97+
}},
98+
})
99+
}
100+
62101
tidied, err := snapshot.ModTidy(ctx, pm)
63102

64103
if source.IsNonFatalGoModError(err) {
65-
return nil, nil
104+
return errors, nil
66105
}
67106
if err != nil {
68107
return nil, err
69108
}
70-
return tidied.Errors, nil
109+
errors = append(errors, tidied.Errors...)
110+
return errors, nil
71111
}

internal/lsp/source/api_json.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/lsp/source/command.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var Commands = []*Command{
6565
CommandUpdateGoSum,
6666
CommandUndeclaredName,
6767
CommandGoGetPackage,
68+
CommandCheckUpgrades,
6869
CommandAddDependency,
6970
CommandUpgradeDependency,
7071
CommandRemoveDependency,
@@ -113,6 +114,12 @@ var (
113114
Title: "Update go.sum",
114115
}
115116

117+
// CommandCheckUpgrades checks for module upgrades.
118+
CommandCheckUpgrades = &Command{
119+
Name: "check_upgrades",
120+
Title: "Check for upgrades",
121+
}
122+
116123
// CommandAddDependency adds a dependency.
117124
CommandAddDependency = &Command{
118125
Name: "add_dependency",

0 commit comments

Comments
 (0)