From c2ddf3dda23a11a1895fb258dbec5609319f6520 Mon Sep 17 00:00:00 2001 From: Suzy Mueller Date: Fri, 18 Feb 2022 16:05:08 -0700 Subject: [PATCH] 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 Run-TryBot: Suzy Mueller Reviewed-by: Robert Findley gopls-CI: kokoro TryBot-Result: Gopher Robot --- gopls/doc/commands.md | 16 ++++++++++++++++ internal/lsp/cache/errors.go | 25 +++++++++++++++++++++++++ internal/lsp/command.go | 14 ++++++++++++++ internal/lsp/command/command_gen.go | 20 ++++++++++++++++++++ internal/lsp/command/interface.go | 12 ++++++++++++ internal/lsp/source/api_json.go | 6 ++++++ 6 files changed, 93 insertions(+) diff --git a/gopls/doc/commands.md b/gopls/doc/commands.md index b70b0afec8..37a4ca4108 100644 --- a/gopls/doc/commands.md +++ b/gopls/doc/commands.md @@ -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` diff --git a/internal/lsp/cache/errors.go b/internal/lsp/cache/errors.go index 67909389f9..155b7a40bc 100644 --- a/internal/lsp/cache/errors.go +++ b/internal/lsp/cache/errors.go @@ -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. diff --git a/internal/lsp/command.go b/internal/lsp/command.go index d8f9d2ce4f..e9d61d5ce4 100644 --- a/internal/lsp/command.go +++ b/internal/lsp/command.go @@ -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", diff --git a/internal/lsp/command/command_gen.go b/internal/lsp/command/command_gen.go index c814bfe58c..55696936ba 100644 --- a/internal/lsp/command/command_gen.go +++ b/internal/lsp/command/command_gen.go @@ -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 { diff --git a/internal/lsp/command/interface.go b/internal/lsp/command/interface.go index d5f520dd76..6058c72f11 100644 --- a/internal/lsp/command/interface.go +++ b/internal/lsp/command/interface.go @@ -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 diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go index 9af5bd64dd..f37cc80d29 100755 --- a/internal/lsp/source/api_json.go +++ b/internal/lsp/source/api_json.go @@ -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",