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",