Skip to content

Commit 90e2e88

Browse files
committed
[gopls-release-branch.0.11] gopls/internal/lsp/mod: add Reset vulncheck result codelens
We hope this gives an option to more conveniently hide unactionable govulncheck diagnostics while we are still looking for a better option than placing "Run govulncheck to verify" and "Reset govulncheck result" in Quick Fix. The command for the code action is implemented by extending the existing go mod diagnostics reset command to accept an optional diagnostic source name. If the name is given, only the go.mod diagnostics that match the name will be reset. This CL also renamed "Run govulncheck" to "Run govulncheck to verify" codelens. Change-Id: Id4e809083d572437f86380a22c70547ec12f9976 Reviewed-on: https://go-review.googlesource.com/c/tools/+/456256 Reviewed-by: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]> gopls-CI: kokoro <[email protected]> Run-TryBot: Hyang-Ah Hana Kim <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/tools/+/456640
1 parent c9887ea commit 90e2e88

File tree

8 files changed

+109
-39
lines changed

8 files changed

+109
-39
lines changed

gopls/doc/commands.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,12 @@ Args:
276276

277277
```
278278
{
279-
// The file URI.
280-
"URI": string,
279+
"URIArg": {
280+
"URI": string,
281+
},
282+
// Optional: source of the diagnostics to reset.
283+
// If not set, all resettable go.mod diagnostics will be cleared.
284+
"DiagnosticSource": string,
281285
}
282286
```
283287

gopls/internal/lsp/command.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,17 +211,22 @@ func (c *commandHandler) UpgradeDependency(ctx context.Context, args command.Dep
211211
return c.GoGetModule(ctx, args)
212212
}
213213

214-
func (c *commandHandler) ResetGoModDiagnostics(ctx context.Context, uri command.URIArg) error {
214+
func (c *commandHandler) ResetGoModDiagnostics(ctx context.Context, args command.ResetGoModDiagnosticsArgs) error {
215215
return c.run(ctx, commandConfig{
216-
forURI: uri.URI,
216+
forURI: args.URI,
217217
}, func(ctx context.Context, deps commandDeps) error {
218-
deps.snapshot.View().ClearModuleUpgrades(uri.URI.SpanURI())
219-
deps.snapshot.View().SetVulnerabilities(uri.URI.SpanURI(), nil)
220218
// Clear all diagnostics coming from the upgrade check source and vulncheck.
221219
// This will clear the diagnostics in all go.mod files, but they
222220
// will be re-calculated when the snapshot is diagnosed again.
223-
c.s.clearDiagnosticSource(modCheckUpgradesSource)
224-
c.s.clearDiagnosticSource(modVulncheckSource)
221+
if args.DiagnosticSource == "" || args.DiagnosticSource == string(source.UpgradeNotification) {
222+
deps.snapshot.View().ClearModuleUpgrades(args.URI.SpanURI())
223+
c.s.clearDiagnosticSource(modCheckUpgradesSource)
224+
}
225+
226+
if args.DiagnosticSource == "" || args.DiagnosticSource == string(source.Vulncheck) {
227+
deps.snapshot.View().SetVulnerabilities(args.URI.SpanURI(), nil)
228+
c.s.clearDiagnosticSource(modVulncheckSource)
229+
}
225230

226231
// Re-diagnose the snapshot to remove the diagnostics.
227232
c.s.diagnoseSnapshot(deps.snapshot, nil, false)

gopls/internal/lsp/command/command_gen.go

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

gopls/internal/lsp/command/interface.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ type Interface interface {
102102
// ResetGoModDiagnostics: Reset go.mod diagnostics
103103
//
104104
// Reset diagnostics in the go.mod file of a module.
105-
ResetGoModDiagnostics(context.Context, URIArg) error
105+
ResetGoModDiagnostics(context.Context, ResetGoModDiagnosticsArgs) error
106106

107107
// GoGetPackage: go get a package
108108
//
@@ -309,6 +309,14 @@ type DebuggingResult struct {
309309
URLs []string
310310
}
311311

312+
type ResetGoModDiagnosticsArgs struct {
313+
URIArg
314+
315+
// Optional: source of the diagnostics to reset.
316+
// If not set, all resettable go.mod diagnostics will be cleared.
317+
DiagnosticSource string
318+
}
319+
312320
type VulncheckArgs struct {
313321
// Any document in the directory from which govulncheck will run.
314322
URI protocol.DocumentURI

gopls/internal/lsp/mod/code_lens.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.File
3232
return nil, err
3333
}
3434
uri := protocol.URIFromSpanURI(fh.URI())
35-
reset, err := command.NewResetGoModDiagnosticsCommand("Reset go.mod diagnostics", command.URIArg{URI: uri})
35+
reset, err := command.NewResetGoModDiagnosticsCommand("Reset go.mod diagnostics", command.ResetGoModDiagnosticsArgs{URIArg: command.URIArg{URI: uri}})
3636
if err != nil {
3737
return nil, err
3838
}
@@ -178,7 +178,7 @@ func vulncheckLenses(ctx context.Context, snapshot source.Snapshot, fh source.Fi
178178
return nil, err
179179
}
180180

181-
vulncheck, err := command.NewRunGovulncheckCommand("Run govulncheck", command.VulncheckArgs{
181+
vulncheck, err := command.NewRunGovulncheckCommand("Run govulncheck to verify", command.VulncheckArgs{
182182
URI: uri,
183183
Pattern: "./...",
184184
})

gopls/internal/lsp/mod/diagnostics.go

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -193,16 +193,11 @@ func ModVulnerabilityDiagnostics(ctx context.Context, snapshot source.Snapshot,
193193
return nil, nil
194194
}
195195

196-
vulncheck, err := command.NewRunGovulncheckCommand("Run govulncheck", command.VulncheckArgs{
197-
URI: protocol.DocumentURI(fh.URI()),
198-
Pattern: "./...",
199-
})
196+
suggestRunOrResetGovulncheck, err := suggestGovulncheckAction(fromGovulncheck, fh.URI())
200197
if err != nil {
201198
// must not happen
202199
return nil, err // TODO: bug report
203200
}
204-
suggestVulncheck := source.SuggestedFixFromCommand(vulncheck, protocol.QuickFix)
205-
206201
type modVuln struct {
207202
mod *govulncheck.Module
208203
vuln *govulncheck.Vuln
@@ -294,14 +289,12 @@ func ModVulnerabilityDiagnostics(ctx context.Context, snapshot source.Snapshot,
294289
if len(infoFixes) > 0 {
295290
infoFixes = append(infoFixes, sf)
296291
}
297-
if !fromGovulncheck {
298-
infoFixes = append(infoFixes, suggestVulncheck)
299-
}
300292

301293
sort.Strings(warning)
302294
sort.Strings(info)
303295

304296
if len(warning) > 0 {
297+
warningFixes = append(warningFixes, suggestRunOrResetGovulncheck)
305298
vulnDiagnostics = append(vulnDiagnostics, &source.Diagnostic{
306299
URI: fh.URI(),
307300
Range: rng,
@@ -313,6 +306,7 @@ func ModVulnerabilityDiagnostics(ctx context.Context, snapshot source.Snapshot,
313306
})
314307
}
315308
if len(info) > 0 {
309+
infoFixes = append(infoFixes, suggestRunOrResetGovulncheck)
316310
vulnDiagnostics = append(vulnDiagnostics, &source.Diagnostic{
317311
URI: fh.URI(),
318312
Range: rng,
@@ -355,20 +349,19 @@ func ModVulnerabilityDiagnostics(ctx context.Context, snapshot source.Snapshot,
355349
}
356350
}
357351
if len(warning) > 0 {
352+
fixes := []source.SuggestedFix{suggestRunOrResetGovulncheck}
358353
vulnDiagnostics = append(vulnDiagnostics, &source.Diagnostic{
359-
URI: fh.URI(),
360-
Range: rng,
361-
Severity: protocol.SeverityWarning,
362-
Source: source.Vulncheck,
363-
Message: getVulnMessage(stdlib, warning, true, fromGovulncheck),
364-
Related: relatedInfo,
354+
URI: fh.URI(),
355+
Range: rng,
356+
Severity: protocol.SeverityWarning,
357+
Source: source.Vulncheck,
358+
Message: getVulnMessage(stdlib, warning, true, fromGovulncheck),
359+
SuggestedFixes: fixes,
360+
Related: relatedInfo,
365361
})
366362
}
367363
if len(info) > 0 {
368-
var fixes []source.SuggestedFix
369-
if !fromGovulncheck {
370-
fixes = append(fixes, suggestVulncheck)
371-
}
364+
fixes := []source.SuggestedFix{suggestRunOrResetGovulncheck}
372365
vulnDiagnostics = append(vulnDiagnostics, &source.Diagnostic{
373366
URI: fh.URI(),
374367
Range: rng,
@@ -384,6 +377,31 @@ func ModVulnerabilityDiagnostics(ctx context.Context, snapshot source.Snapshot,
384377
return vulnDiagnostics, nil
385378
}
386379

380+
// suggestGovulncheckAction returns a code action that suggests either run govulncheck
381+
// for more accurate investigation (if the present vulncheck diagnostics are based on
382+
// analysis less accurate than govulncheck) or reset the existing govulncheck result
383+
// (if the present vulncheck diagnostics are already based on govulncheck run).
384+
func suggestGovulncheckAction(fromGovulncheck bool, uri span.URI) (source.SuggestedFix, error) {
385+
if fromGovulncheck {
386+
resetVulncheck, err := command.NewResetGoModDiagnosticsCommand("Reset govulncheck result", command.ResetGoModDiagnosticsArgs{
387+
URIArg: command.URIArg{URI: protocol.DocumentURI(uri)},
388+
DiagnosticSource: string(source.Vulncheck),
389+
})
390+
if err != nil {
391+
return source.SuggestedFix{}, err
392+
}
393+
return source.SuggestedFixFromCommand(resetVulncheck, protocol.QuickFix), nil
394+
}
395+
vulncheck, err := command.NewRunGovulncheckCommand("Run govulncheck to verify", command.VulncheckArgs{
396+
URI: protocol.DocumentURI(uri),
397+
Pattern: "./...",
398+
})
399+
if err != nil {
400+
return source.SuggestedFix{}, err
401+
}
402+
return source.SuggestedFixFromCommand(vulncheck, protocol.QuickFix), nil
403+
}
404+
387405
func getVulnMessage(mod string, vulns []string, used, fromGovulncheck bool) string {
388406
var b strings.Builder
389407
if used {
@@ -493,18 +511,21 @@ func upgradeTitle(fixedVersion string) string {
493511
// SelectUpgradeCodeActions takes a list of code actions for a required module
494512
// and returns a more selective list of upgrade code actions,
495513
// where the code actions have been deduped. Code actions unrelated to upgrade
496-
// remain untouched.
514+
// are deduplicated by the name.
497515
func SelectUpgradeCodeActions(actions []protocol.CodeAction) []protocol.CodeAction {
498516
if len(actions) <= 1 {
499517
return actions // return early if no sorting necessary
500518
}
501519
var others []protocol.CodeAction
502520

521+
seen := make(map[string]bool)
522+
503523
set := make(map[string]protocol.CodeAction)
504524
for _, action := range actions {
505525
if strings.HasPrefix(action.Title, upgradeCodeActionPrefix) {
506526
set[action.Command.Title] = action
507-
} else {
527+
} else if !seen[action.Command.Title] {
528+
seen[action.Command.Title] = true
508529
others = append(others, action)
509530
}
510531
}

gopls/internal/lsp/source/api_json.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gopls/internal/regtest/misc/vuln_test.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -507,14 +507,14 @@ func TestRunVulncheckPackageDiagnostics(t *testing.T) {
507507
codeActions: []string{
508508
"Upgrade to latest",
509509
"Upgrade to v1.0.6",
510-
"Run govulncheck",
510+
"Run govulncheck to verify",
511511
},
512512
},
513513
},
514514
codeActions: []string{
515515
"Upgrade to latest",
516516
"Upgrade to v1.0.6",
517-
"Run govulncheck",
517+
"Run govulncheck to verify",
518518
},
519519
hover: []string{"GO-2022-01", "Fixed in v1.0.4.", "GO-2022-03"},
520520
},
@@ -524,12 +524,12 @@ func TestRunVulncheckPackageDiagnostics(t *testing.T) {
524524
msg: "golang.org/bmod has a vulnerability GO-2022-02.",
525525
severity: protocol.SeverityInformation,
526526
codeActions: []string{
527-
"Run govulncheck",
527+
"Run govulncheck to verify",
528528
},
529529
},
530530
},
531531
codeActions: []string{
532-
"Run govulncheck",
532+
"Run govulncheck to verify",
533533
},
534534
hover: []string{"GO-2022-02", "This is a long description of this vulnerability.", "No fix is available."},
535535
},
@@ -667,6 +667,7 @@ func TestRunVulncheckWarning(t *testing.T) {
667667
codeActions: []string{
668668
"Upgrade to latest",
669669
"Upgrade to v1.0.4",
670+
"Reset govulncheck result",
670671
},
671672
relatedInfo: []vulnRelatedInfo{
672673
{"x.go", uint32(lineX.Line), "[GO-2022-01]"}, // avuln.VulnData.Vuln1
@@ -679,6 +680,7 @@ func TestRunVulncheckWarning(t *testing.T) {
679680
codeActions: []string{
680681
"Upgrade to latest",
681682
"Upgrade to v1.0.6",
683+
"Reset govulncheck result",
682684
},
683685
relatedInfo: []vulnRelatedInfo{
684686
{"x.go", uint32(lineX.Line), "[GO-2022-01]"}, // avuln.VulnData.Vuln1
@@ -689,6 +691,7 @@ func TestRunVulncheckWarning(t *testing.T) {
689691
codeActions: []string{
690692
"Upgrade to latest",
691693
"Upgrade to v1.0.6",
694+
"Reset govulncheck result",
692695
},
693696
hover: []string{"GO-2022-01", "Fixed in v1.0.4.", "GO-2022-03"},
694697
},
@@ -697,11 +700,17 @@ func TestRunVulncheckWarning(t *testing.T) {
697700
{
698701
msg: "golang.org/bmod has a vulnerability used in the code: GO-2022-02.",
699702
severity: protocol.SeverityWarning,
703+
codeActions: []string{
704+
"Reset govulncheck result", // no fix, but we should give an option to reset.
705+
},
700706
relatedInfo: []vulnRelatedInfo{
701707
{"y.go", uint32(lineY.Line), "[GO-2022-02]"}, // bvuln.Vuln
702708
},
703709
},
704710
},
711+
codeActions: []string{
712+
"Reset govulncheck result", // no fix, but we should give an option to reset.
713+
},
705714
hover: []string{"GO-2022-02", "This is a long description of this vulnerability.", "No fix is available."},
706715
},
707716
}
@@ -822,21 +831,44 @@ func TestGovulncheckInfo(t *testing.T) {
822831
{
823832
msg: "golang.org/bmod has a vulnerability GO-2022-02 that is not used in the code.",
824833
severity: protocol.SeverityInformation,
834+
codeActions: []string{
835+
"Reset govulncheck result",
836+
},
825837
},
826838
},
839+
codeActions: []string{
840+
"Reset govulncheck result",
841+
},
827842
hover: []string{"GO-2022-02", "This is a long description of this vulnerability.", "No fix is available."},
828843
},
829844
}
830845

846+
var allActions []protocol.CodeAction
831847
for mod, want := range wantDiagnostics {
832848
modPathDiagnostics := testVulnDiagnostics(t, env, mod, want, gotDiagnostics)
833849
// Check that the actions we get when including all diagnostics at a location return the same result
834850
gotActions := env.CodeAction("go.mod", modPathDiagnostics)
851+
allActions = append(allActions, gotActions...)
835852
if diff := diffCodeActions(gotActions, want.codeActions); diff != "" {
836853
t.Errorf("code actions for %q do not match, expected %v, got %v\n%v\n", mod, want.codeActions, gotActions, diff)
837854
continue
838855
}
839856
}
857+
858+
// Clear Diagnostics by using one of the reset code actions.
859+
var reset protocol.CodeAction
860+
for _, a := range allActions {
861+
if a.Title == "Reset govulncheck result" {
862+
reset = a
863+
break
864+
}
865+
}
866+
if reset.Title != "Reset govulncheck result" {
867+
t.Errorf("failed to find a 'Reset govulncheck result' code action, got %v", allActions)
868+
}
869+
env.ApplyCodeAction(reset)
870+
871+
env.Await(EmptyOrNoDiagnostics("go.mod"))
840872
})
841873
}
842874

0 commit comments

Comments
 (0)