internal/lsp: add quick fix for unsupported feature

Adds a command to run go mod edit -go to allow
users to easily upgrade their go directive.

Doing this change also revealed that changing
the go directive does not invalidate the type check
data and there may be stale diagnostics for a package.

Updates golang/go#51086

Change-Id: I659a216059c489a88e29cd51b944c3a0274f3700
Reviewed-on: https://go-review.googlesource.com/c/tools/+/386875
Trust: Suzy Mueller <suzmue@golang.org>
Run-TryBot: Suzy Mueller <suzmue@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Suzy Mueller 2022-02-18 16:05:08 -07:00
parent 0e44f7a8a7
commit c2ddf3dda2
6 changed files with 93 additions and 0 deletions

View File

@ -84,6 +84,22 @@ Args:
}
```
### **Run go mod edit -go=version**
Identifier: `gopls.edit_go_directive`
Runs `go mod edit -go=version` for a module.
Args:
```
{
// Any document URI within the relevant module.
"URI": string,
// The version to pass to `go mod edit -go`.
"Version": string,
}
```
### **Toggle gc_details**
Identifier: `gopls.gc_details`

View File

@ -101,6 +101,7 @@ func parseErrorDiagnostics(snapshot *snapshot, pkg *pkg, errList scanner.ErrorLi
}
var importErrorRe = regexp.MustCompile(`could not import ([^\s]+)`)
var unsupportedFeatureRe = regexp.MustCompile(`.*require go(\d+\.\d+) or later`)
func typeErrorDiagnostics(snapshot *snapshot, pkg *pkg, e extendedError) ([]*source.Diagnostic, error) {
code, spn, err := typeErrorData(snapshot.FileSet(), pkg, e.primary)
@ -145,6 +146,14 @@ func typeErrorDiagnostics(snapshot *snapshot, pkg *pkg, e extendedError) ([]*sou
return nil, err
}
}
if code == typesinternal.UnsupportedFeature {
if match := unsupportedFeatureRe.FindStringSubmatch(e.primary.Msg); match != nil {
diag.SuggestedFixes, err = editGoDirectiveQuickFix(snapshot, spn.URI(), match[1])
if err != nil {
return nil, err
}
}
}
return []*source.Diagnostic{diag}, nil
}
@ -165,6 +174,22 @@ func goGetQuickFixes(snapshot *snapshot, uri span.URI, pkg string) ([]source.Sug
return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)}, nil
}
func editGoDirectiveQuickFix(snapshot *snapshot, uri span.URI, version string) ([]source.SuggestedFix, error) {
// Go mod edit only supports module mode.
if snapshot.workspaceMode()&moduleMode == 0 {
return nil, nil
}
title := fmt.Sprintf("go mod edit -go=%s", version)
cmd, err := command.NewEditGoDirectiveCommand(title, command.EditGoDirectiveArgs{
URI: protocol.URIFromSpanURI(uri),
Version: version,
})
if err != nil {
return nil, err
}
return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)}, nil
}
func analysisDiagnosticDiagnostics(snapshot *snapshot, pkg *pkg, a *analysis.Analyzer, e *analysis.Diagnostic) ([]*source.Diagnostic, error) {
var srcAnalyzer *source.Analyzer
// Find the analyzer that generated this diagnostic.

View File

@ -261,6 +261,20 @@ func (c *commandHandler) Vendor(ctx context.Context, args command.URIArg) error
})
}
func (c *commandHandler) EditGoDirective(ctx context.Context, args command.EditGoDirectiveArgs) error {
return c.run(ctx, commandConfig{
requireSave: true, // if go.mod isn't saved it could cause a problem
forURI: args.URI,
}, func(ctx context.Context, deps commandDeps) error {
_, err := deps.snapshot.RunGoCommandDirect(ctx, source.Normal, &gocommand.Invocation{
Verb: "mod",
Args: []string{"edit", "-go", args.Version},
WorkingDir: filepath.Dir(args.URI.SpanURI().Filename()),
})
return err
})
}
func (c *commandHandler) RemoveDependency(ctx context.Context, args command.RemoveDependencyArgs) error {
return c.run(ctx, commandConfig{
progress: "Removing dependency",

View File

@ -23,6 +23,7 @@ const (
AddImport Command = "add_import"
ApplyFix Command = "apply_fix"
CheckUpgrades Command = "check_upgrades"
EditGoDirective Command = "edit_go_directive"
GCDetails Command = "gc_details"
Generate Command = "generate"
GenerateGoplsMod Command = "generate_gopls_mod"
@ -46,6 +47,7 @@ var Commands = []Command{
AddImport,
ApplyFix,
CheckUpgrades,
EditGoDirective,
GCDetails,
Generate,
GenerateGoplsMod,
@ -90,6 +92,12 @@ func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Inte
return nil, err
}
return nil, s.CheckUpgrades(ctx, a0)
case "gopls.edit_go_directive":
var a0 EditGoDirectiveArgs
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
return nil, err
}
return nil, s.EditGoDirective(ctx, a0)
case "gopls.gc_details":
var a0 protocol.DocumentURI
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
@ -240,6 +248,18 @@ func NewCheckUpgradesCommand(title string, a0 CheckUpgradesArgs) (protocol.Comma
}, nil
}
func NewEditGoDirectiveCommand(title string, a0 EditGoDirectiveArgs) (protocol.Command, error) {
args, err := MarshalArgs(a0)
if err != nil {
return protocol.Command{}, err
}
return protocol.Command{
Title: title,
Command: "gopls.edit_go_directive",
Arguments: args,
}, nil
}
func NewGCDetailsCommand(title string, a0 protocol.DocumentURI) (protocol.Command, error) {
args, err := MarshalArgs(a0)
if err != nil {

View File

@ -68,6 +68,11 @@ type Interface interface {
// Runs `go mod vendor` for a module.
Vendor(context.Context, URIArg) error
// EditGoDirective: Run go mod edit -go=version
//
// Runs `go mod edit -go=version` for a module.
EditGoDirective(context.Context, EditGoDirectiveArgs) error
// UpdateGoSum: Update go.sum
//
// Updates the go.sum file for a module.
@ -204,6 +209,13 @@ type RemoveDependencyArgs struct {
OnlyDiagnostic bool
}
type EditGoDirectiveArgs struct {
// Any document URI within the relevant module.
URI protocol.DocumentURI
// The version to pass to `go mod edit -go`.
Version string
}
type GoGetPackageArgs struct {
// Any document URI within the relevant module.
URI protocol.DocumentURI

View File

@ -603,6 +603,12 @@ var GeneratedAPIJSON = &APIJSON{
Doc: "Checks for module upgrades.",
ArgDoc: "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// The modules to check.\n\t\"Modules\": []string,\n}",
},
{
Command: "gopls.edit_go_directive",
Title: "Run go mod edit -go=version",
Doc: "Runs `go mod edit -go=version` for a module.",
ArgDoc: "{\n\t// Any document URI within the relevant module.\n\t\"URI\": string,\n\t// The version to pass to `go mod edit -go`.\n\t\"Version\": string,\n}",
},
{
Command: "gopls.gc_details",
Title: "Toggle gc_details",