Skip to content

Commit aa7725f

Browse files
ethanalee-workgopherbot
authored andcommitted
gopls/internal/server: send notification when go.work file changes
- Update handleModuleChanges to handle go.work changes. - Ensure that hashes for go.work dependencies are evaluated. - Rename checkGoModDeps to checkDependencyChanges. Change-Id: I4fd777aeef3e15fb221bfea0106f03cba8cef508 Reviewed-on: https://go-review.googlesource.com/c/tools/+/743620 Auto-Submit: Ethan Lee <ethanalee@google.com> Reviewed-by: Hongxiang Jiang <hxjiang@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 01a0310 commit aa7725f

File tree

3 files changed

+146
-85
lines changed

3 files changed

+146
-85
lines changed

gopls/internal/server/text_synchronization.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,6 @@ func (s *server) didModifyFiles(ctx context.Context, modifications []file.Modifi
254254
// to their files.
255255
modifications = s.session.ExpandModificationsToDirectories(ctx, modifications)
256256

257-
// TODO: also handle go.work changes as well.
258257
s.handleModuleChanges(ctx, modifications, cause)
259258

260259
viewsToDiagnose, err := s.session.DidModifyFiles(ctx, modifications)
@@ -302,13 +301,13 @@ func (s *server) handleModuleChanges(ctx context.Context, modifications []file.M
302301

303302
uris := make(map[protocol.DocumentURI]struct{})
304303
for _, m := range modifications {
305-
if m.URI.Base() == "go.mod" && (m.Action == file.Create || m.Action == file.Save) {
304+
if (m.URI.Base() == "go.mod" || m.URI.Base() == "go.work") && (m.Action == file.Create || m.Action == file.Save) {
306305
uris[m.URI] = struct{}{}
307306
}
308307
}
309308

310309
for uri := range uris {
311-
go s.checkGoModDeps(ctx, uri)
310+
go s.checkDependencyChanges(ctx, uri)
312311
}
313312
}
314313

gopls/internal/server/vulncheck_prompt.go

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ import (
3131
)
3232

3333
const (
34-
// goModHashKind is the kind for the go.mod hash in the filecache.
35-
goModHashKind = "gomodhash"
36-
maxVulnsToShow = 10
34+
// dependencyHashKind is the kind for the dependency hash in the filecache.
35+
dependencyHashKind = "dephash"
36+
maxVulnsToShow = 10
3737
)
3838

3939
type vulncheckAction string
@@ -77,39 +77,72 @@ func computeGoModHash(file *modfile.File) (string, error) {
7777
return hex.EncodeToString(h.Sum(nil)), nil
7878
}
7979

80-
func getModFileHashes(uri protocol.DocumentURI) (contentHash string, pathHash [32]byte, err error) {
81-
content, err := os.ReadFile(uri.Path())
82-
if err != nil {
83-
return "", [32]byte{}, err
80+
// computeGoWorkHash computes the SHA256 hash of the go.work file's dependencies.
81+
// It only considers the Use and Replace directives and ignores other parts of
82+
// the file.
83+
func computeGoWorkHash(file *modfile.WorkFile) (string, error) {
84+
h := sha256.New()
85+
for _, use := range file.Use {
86+
if _, err := h.Write([]byte(use.Path + "\x00")); err != nil {
87+
return "", err
88+
}
8489
}
85-
newModFile, err := modfile.Parse("go.mod", content, nil)
86-
if err != nil {
87-
return "", [32]byte{}, err
90+
for _, rep := range file.Replace {
91+
if _, err := h.Write([]byte(rep.Old.Path + "\x00" + rep.Old.Version + "\x00" + rep.New.Path + "\x00" + rep.New.Version)); err != nil {
92+
return "", err
93+
}
8894
}
89-
contentHash, err = computeGoModHash(newModFile)
95+
return hex.EncodeToString(h.Sum(nil)), nil
96+
}
97+
98+
func getDependencyHashes(uri protocol.DocumentURI) (contentHash string, pathHash [32]byte, err error) {
99+
content, err := os.ReadFile(uri.Path())
90100
if err != nil {
91101
return "", [32]byte{}, err
92102
}
103+
filename := filepath.Base(uri.Path())
104+
switch filename {
105+
case "go.mod":
106+
modFile, err := modfile.Parse(filename, content, nil)
107+
if err != nil {
108+
return "", [32]byte{}, err
109+
}
110+
contentHash, err = computeGoModHash(modFile)
111+
if err != nil {
112+
return "", [32]byte{}, err
113+
}
114+
case "go.work":
115+
workFile, err := modfile.ParseWork(filename, content, nil)
116+
if err != nil {
117+
return "", [32]byte{}, err
118+
}
119+
contentHash, err = computeGoWorkHash(workFile)
120+
if err != nil {
121+
return "", [32]byte{}, err
122+
}
123+
default:
124+
return "", [32]byte{}, fmt.Errorf("unsupported file: %s", filename)
125+
}
93126
pathHash = sha256.Sum256([]byte(uri.Path()))
94127
return contentHash, pathHash, nil
95128
}
96129

97-
func (s *server) checkGoModDeps(ctx context.Context, uri protocol.DocumentURI) {
130+
func (s *server) checkDependencyChanges(ctx context.Context, uri protocol.DocumentURI) {
98131
if s.Options().Vulncheck != settings.ModeVulncheckPrompt {
99132
return
100133
}
101-
ctx, done := event.Start(ctx, "server.CheckGoModDeps")
134+
ctx, done := event.Start(ctx, "server.checkDependencyChanges")
102135
defer done()
103136

104-
newHash, pathHash, err := getModFileHashes(uri)
137+
newHash, pathHash, err := getDependencyHashes(uri)
105138
if err != nil {
106-
event.Error(ctx, "getting go.mod hashes failed", err)
139+
event.Error(ctx, "getting dependency hashes failed", err)
107140
return
108141
}
109142

110-
oldHashBytes, err := filecache.Get(goModHashKind, pathHash)
143+
oldHashBytes, err := filecache.Get(dependencyHashKind, pathHash)
111144
if err != nil && err != filecache.ErrNotFound {
112-
event.Error(ctx, "reading old go.mod hash from filecache failed", err)
145+
event.Error(ctx, "reading old dependency hash from filecache failed", err)
113146
return
114147
}
115148
oldHash := string(oldHashBytes)
@@ -136,7 +169,7 @@ func (s *server) checkGoModDeps(ctx context.Context, uri protocol.DocumentURI) {
136169
// invalid value, clear preference and prompt the user again.
137170
choice, err := showMessageRequest(ctx, s.client, protocol.Info, message, string(vulncheckActionYes), string(vulncheckActionNo), string(vulncheckActionAlways), string(vulncheckActionNever))
138171
if err != nil {
139-
event.Error(ctx, "showing go.mod changed notification failed", err)
172+
event.Error(ctx, "showing dependency changed notification failed", err)
140173
return
141174
}
142175
action = vulncheckAction(choice)
@@ -156,8 +189,8 @@ func (s *server) checkGoModDeps(ctx context.Context, uri protocol.DocumentURI) {
156189
}
157190
}
158191

159-
if err := filecache.Set(goModHashKind, pathHash, []byte(newHash)); err != nil {
160-
event.Error(ctx, "writing new go.mod hash to filecache failed", err)
192+
if err := filecache.Set(dependencyHashKind, pathHash, []byte(newHash)); err != nil {
193+
event.Error(ctx, "writing new dependency hash to filecache failed", err)
161194
return
162195
}
163196
}
@@ -312,12 +345,12 @@ func (s *server) upgradeModules(ctx context.Context, snapshot *cache.Snapshot, u
312345

313346
msg := fmt.Sprintf("Successfully upgraded vulnerable modules:\n %s", strings.Join(upgradedStrs, ",\n "))
314347
showMessage(ctx, s.client, protocol.Info, msg)
315-
if hash, pathHash, err := getModFileHashes(uri); err == nil {
316-
if err := filecache.Set(goModHashKind, pathHash, []byte(hash)); err != nil {
317-
event.Error(ctx, "failed to update go.mod hash after upgrade", err)
348+
if hash, pathHash, err := getDependencyHashes(uri); err == nil {
349+
if err := filecache.Set(dependencyHashKind, pathHash, []byte(hash)); err != nil {
350+
event.Error(ctx, "failed to update dependency hash after upgrade", err)
318351
}
319352
} else {
320-
event.Error(ctx, "failed to get go.mod hash after upgrade", err)
353+
event.Error(ctx, "failed to get dependency hash after upgrade", err)
321354
}
322355
return nil
323356
}

gopls/internal/server/vulncheck_prompt_test.go

Lines changed: 87 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,65 @@ func TestComputeGoModHash(t *testing.T) {
9797
}
9898
}
9999

100+
func TestComputeGoWorkHash(t *testing.T) {
101+
tests := []struct {
102+
name string
103+
content string
104+
want string
105+
wantErr bool
106+
}{
107+
{
108+
name: "empty file",
109+
content: "go 1.25",
110+
want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", // sha256 of empty string
111+
},
112+
{
113+
name: "with use",
114+
content: `
115+
go 1.25
116+
use (
117+
./foo
118+
./bar
119+
)
120+
`,
121+
want: func() string {
122+
h := sha256.New()
123+
h.Write([]byte("./foo\x00"))
124+
h.Write([]byte("./bar\x00"))
125+
return hex.EncodeToString(h.Sum(nil))
126+
}(),
127+
},
128+
{
129+
name: "with replace",
130+
content: `
131+
go 1.25
132+
replace golang.org/x/tools v0.1.0 => ./tools
133+
`,
134+
want: func() string {
135+
h := sha256.New()
136+
h.Write([]byte("golang.org/x/tools\x00v0.1.0\x00./tools\x00"))
137+
return hex.EncodeToString(h.Sum(nil))
138+
}(),
139+
},
140+
}
141+
for _, tt := range tests {
142+
t.Run(tt.name, func(t *testing.T) {
143+
workFile, err := modfile.ParseWork("go.work", []byte(tt.content), nil)
144+
if err != nil {
145+
t.Fatal(err)
146+
}
147+
got, err := computeGoWorkHash(workFile)
148+
if (err != nil) != tt.wantErr {
149+
t.Errorf("computeGoWorkHash() error = %v, wantErr %v", err, tt.wantErr)
150+
return
151+
}
152+
if got != tt.want {
153+
t.Errorf("computeGoWorkHash() = %v, want %v", got, tt.want)
154+
}
155+
})
156+
}
157+
}
158+
100159
type mockClient struct {
101160
protocol.Client
102161
showMessageRequest func(context.Context, *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error)
@@ -117,7 +176,7 @@ func (c *mockClient) Close() error {
117176
return nil
118177
}
119178

120-
func TestCheckGoModDeps(t *testing.T) {
179+
func TestCheckDependencyChanges(t *testing.T) {
121180
testenv.NeedsExec(t)
122181
const (
123182
yes = "Yes"
@@ -128,6 +187,7 @@ func TestCheckGoModDeps(t *testing.T) {
128187

129188
tests := []struct {
130189
name string
190+
filename string
131191
vulncheckMode settings.VulncheckMode
132192
oldContent string
133193
newContent string
@@ -136,7 +196,8 @@ func TestCheckGoModDeps(t *testing.T) {
136196
wantHashUpdated bool
137197
}{
138198
{
139-
name: "vulncheck disabled",
199+
name: "go.mod: vulncheck disabled",
200+
filename: "go.mod",
140201
vulncheckMode: settings.ModeVulncheckOff,
141202
oldContent: "module example.com",
142203
newContent: `
@@ -146,14 +207,16 @@ func TestCheckGoModDeps(t *testing.T) {
146207
wantPrompt: false,
147208
},
148209
{
149-
name: "no changes",
210+
name: "go.mod: no changes",
211+
filename: "go.mod",
150212
vulncheckMode: settings.ModeVulncheckPrompt,
151213
oldContent: "module example.com",
152214
newContent: "module example.com",
153215
wantPrompt: false,
154216
},
155217
{
156-
name: "user says yes",
218+
name: "go.mod: user says yes",
219+
filename: "go.mod",
157220
vulncheckMode: settings.ModeVulncheckPrompt,
158221
oldContent: "module example.com",
159222
newContent: `
@@ -165,51 +228,25 @@ func TestCheckGoModDeps(t *testing.T) {
165228
wantHashUpdated: true,
166229
},
167230
{
168-
name: "user says no",
169-
vulncheckMode: settings.ModeVulncheckPrompt,
170-
oldContent: "module example.com",
171-
newContent: `
172-
module example.com
173-
require golang.org/x/tools v0.1.0
174-
`,
175-
userAction: no,
176-
wantPrompt: true,
177-
wantHashUpdated: true,
178-
},
179-
{
180-
name: "user says always",
231+
name: "go.work: user says yes",
232+
filename: "go.work",
181233
vulncheckMode: settings.ModeVulncheckPrompt,
182-
oldContent: "module example.com",
234+
oldContent: "go 1.25",
183235
newContent: `
184-
module example.com
185-
require golang.org/x/tools v0.1.0
236+
go 1.25
237+
use ./foo
186238
`,
187-
userAction: always,
188-
wantPrompt: true,
189-
wantHashUpdated: true,
190-
},
191-
{
192-
name: "user says never",
193-
vulncheckMode: settings.ModeVulncheckPrompt,
194-
oldContent: "module example.com",
195-
newContent: `
196-
module example.com
197-
require golang.org/x/tools v0.1.0
198-
`,
199-
userAction: never,
239+
userAction: yes,
200240
wantPrompt: true,
201241
wantHashUpdated: true,
202242
},
203243
{
204-
name: "user dismisses prompt",
244+
name: "go.work: no changes",
245+
filename: "go.work",
205246
vulncheckMode: settings.ModeVulncheckPrompt,
206-
oldContent: "module example.com",
207-
newContent: `
208-
module example.com
209-
require golang.org/x/tools v0.1.0
210-
`,
211-
userAction: "",
212-
wantPrompt: true,
247+
oldContent: "go 1.25\nuse ./foo",
248+
newContent: "go 1.25\nuse ./foo",
249+
wantPrompt: false,
213250
},
214251
}
215252

@@ -251,48 +288,40 @@ func TestCheckGoModDeps(t *testing.T) {
251288
},
252289
}
253290
dir := t.TempDir()
254-
goModPath := filepath.Join(dir, "go.mod")
255-
if err := os.WriteFile(goModPath, []byte(tt.oldContent), 0644); err != nil {
291+
filePath := filepath.Join(dir, tt.filename)
292+
if err := os.WriteFile(filePath, []byte(tt.oldContent), 0644); err != nil {
256293
t.Fatal(err)
257294
}
258-
uri := protocol.URIFromPath(goModPath)
295+
uri := protocol.URIFromPath(filePath)
259296

260297
// Set the initial hash in the cache.
261-
oldModFile, err := modfile.Parse("go.mod", []byte(tt.oldContent), nil)
262-
if err != nil {
263-
t.Fatal(err)
264-
}
265-
oldHash, err := computeGoModHash(oldModFile)
298+
oldHash, _, err := getDependencyHashes(uri)
266299
if err != nil {
267300
t.Fatal(err)
268301
}
269302
pathHash := sha256.Sum256([]byte(uri.Path()))
270-
if err := filecache.Set(goModHashKind, pathHash, []byte(oldHash)); err != nil {
303+
if err := filecache.Set(dependencyHashKind, pathHash, []byte(oldHash)); err != nil {
271304
t.Fatal(err)
272305
}
273306

274307
// Simulate the file change.
275-
if err := os.WriteFile(goModPath, []byte(tt.newContent), 0644); err != nil {
308+
if err := os.WriteFile(filePath, []byte(tt.newContent), 0644); err != nil {
276309
t.Fatal(err)
277310
}
278311

279-
s.checkGoModDeps(ctx, uri)
312+
s.checkDependencyChanges(ctx, uri)
280313

281314
if promptShown != tt.wantPrompt {
282315
t.Errorf("promptShown = %v, want %v", promptShown, tt.wantPrompt)
283316
}
284317

285318
// Check if the hash was updated.
286-
newModFile, err := modfile.Parse("go.mod", []byte(tt.newContent), nil)
287-
if err != nil {
288-
t.Fatal(err)
289-
}
290-
newHash, err := computeGoModHash(newModFile)
319+
newHash, _, err := getDependencyHashes(uri)
291320
if err != nil {
292321
t.Fatal(err)
293322
}
294323

295-
cachedHashBytes, err := filecache.Get(goModHashKind, pathHash)
324+
cachedHashBytes, err := filecache.Get(dependencyHashKind, pathHash)
296325
if err != nil && err != filecache.ErrNotFound {
297326
t.Fatal(err)
298327
}

0 commit comments

Comments
 (0)