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** ### **Toggle gc_details**
Identifier: `gopls.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 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) { func typeErrorDiagnostics(snapshot *snapshot, pkg *pkg, e extendedError) ([]*source.Diagnostic, error) {
code, spn, err := typeErrorData(snapshot.FileSet(), pkg, e.primary) 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 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 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 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) { func analysisDiagnosticDiagnostics(snapshot *snapshot, pkg *pkg, a *analysis.Analyzer, e *analysis.Diagnostic) ([]*source.Diagnostic, error) {
var srcAnalyzer *source.Analyzer var srcAnalyzer *source.Analyzer
// Find the analyzer that generated this diagnostic. // 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 { func (c *commandHandler) RemoveDependency(ctx context.Context, args command.RemoveDependencyArgs) error {
return c.run(ctx, commandConfig{ return c.run(ctx, commandConfig{
progress: "Removing dependency", progress: "Removing dependency",

View File

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

View File

@ -68,6 +68,11 @@ type Interface interface {
// Runs `go mod vendor` for a module. // Runs `go mod vendor` for a module.
Vendor(context.Context, URIArg) error 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 // UpdateGoSum: Update go.sum
// //
// Updates the go.sum file for a module. // Updates the go.sum file for a module.
@ -204,6 +209,13 @@ type RemoveDependencyArgs struct {
OnlyDiagnostic bool 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 { type GoGetPackageArgs struct {
// Any document URI within the relevant module. // Any document URI within the relevant module.
URI protocol.DocumentURI URI protocol.DocumentURI

View File

@ -603,6 +603,12 @@ var GeneratedAPIJSON = &APIJSON{
Doc: "Checks for module upgrades.", 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}", 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", Command: "gopls.gc_details",
Title: "Toggle gc_details", Title: "Toggle gc_details",