mirror of https://github.com/golang/go.git
internal/lsp: add go get quick fix on failing imports
With -mod=readonly set, we no longer automatically add new requires to go.mod, even the temporary one. We have the go mod tidy code lens, but that only works on saved files, even in 1.16 due to golang/go#42491. Plus we may remove the code lens's network access in the future. Add a simple quick fix for import errors that runs (the moral equivalent of) go get on the missing import. Change-Id: Id5764a37ce7db0dce5370da9d648462aefa2042b Reviewed-on: https://go-review.googlesource.com/c/tools/+/274121 Trust: Heschi Kreinick <heschi@google.com> Run-TryBot: Heschi Kreinick <heschi@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
43adb69d7e
commit
bd5d160bec
|
|
@ -47,6 +47,12 @@ undeclared_name adds a variable declaration for an undeclared
|
|||
name.
|
||||
|
||||
|
||||
### **go get package**
|
||||
Identifier: `gopls.go_get_package`
|
||||
|
||||
go_get_package runs `go get` to fetch a package.
|
||||
|
||||
|
||||
### **Add dependency**
|
||||
Identifier: `gopls.add_dependency`
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,54 @@ func main() {
|
|||
})
|
||||
}
|
||||
|
||||
func TestGoGetFix(t *testing.T) {
|
||||
testenv.NeedsGo1Point(t, 14)
|
||||
const mod = `
|
||||
-- go.mod --
|
||||
module mod.com
|
||||
|
||||
go 1.12
|
||||
|
||||
-- main.go --
|
||||
package main
|
||||
|
||||
import "example.com/blah"
|
||||
|
||||
var _ = blah.Name
|
||||
`
|
||||
|
||||
const want = `module mod.com
|
||||
|
||||
go 1.12
|
||||
|
||||
require example.com v1.2.3
|
||||
`
|
||||
|
||||
runModfileTest(t, mod, proxy, func(t *testing.T, env *Env) {
|
||||
if strings.Contains(t.Name(), "workspace_module") {
|
||||
t.Skip("workspace module mode doesn't set -mod=readonly")
|
||||
}
|
||||
env.OpenFile("main.go")
|
||||
var d protocol.PublishDiagnosticsParams
|
||||
env.Await(
|
||||
OnceMet(
|
||||
env.DiagnosticAtRegexp("main.go", `"example.com/blah"`),
|
||||
ReadDiagnostics("main.go", &d),
|
||||
),
|
||||
)
|
||||
var goGetDiag protocol.Diagnostic
|
||||
for _, diag := range d.Diagnostics {
|
||||
if strings.Contains(diag.Message, "could not import") {
|
||||
goGetDiag = diag
|
||||
}
|
||||
}
|
||||
env.ApplyQuickFixes("main.go", []protocol.Diagnostic{goGetDiag})
|
||||
if got := env.ReadWorkspaceFile("go.mod"); got != want {
|
||||
t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(want, got))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Tests that multiple missing dependencies gives good single fixes.
|
||||
func TestMissingDependencyFixes(t *testing.T) {
|
||||
testenv.NeedsGo1Point(t, 14)
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ func sourceError(ctx context.Context, snapshot *snapshot, pkg *pkg, e interface{
|
|||
msg = e.Message
|
||||
kind = source.Analysis
|
||||
category = e.Category
|
||||
fixes, err = suggestedFixes(snapshot, pkg, e)
|
||||
fixes, err = suggestedAnalysisFixes(snapshot, pkg, e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -154,7 +154,7 @@ func sourceError(ctx context.Context, snapshot *snapshot, pkg *pkg, e interface{
|
|||
}, nil
|
||||
}
|
||||
|
||||
func suggestedFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
|
||||
func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
|
||||
var fixes []source.SuggestedFix
|
||||
for _, fix := range diag.SuggestedFixes {
|
||||
edits := make(map[span.URI][]protocol.TextEdit)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ package lsp
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
|
@ -103,6 +104,16 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Fix unresolved imports with "go get". This is separate from the
|
||||
// goimports fixes because goimports will not remove an import
|
||||
// that appears to be used, even if currently unresolved.
|
||||
actions, err := goGetFixes(ctx, snapshot, fh.URI(), diagnostics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codeActions = append(codeActions, actions...)
|
||||
|
||||
// Send all of the import edits as one code action if the file is
|
||||
// being organized.
|
||||
if wanted[protocol.SourceOrganizeImports] && len(importEdits) > 0 {
|
||||
|
|
@ -349,6 +360,38 @@ func diagnosticToAnalyzer(snapshot source.Snapshot, src, msg string) (analyzer *
|
|||
return nil
|
||||
}
|
||||
|
||||
var importErrorRe = regexp.MustCompile(`could not import ([^\s]+)`)
|
||||
|
||||
func goGetFixes(ctx context.Context, snapshot source.Snapshot, uri span.URI, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
|
||||
if snapshot.GoModForFile(ctx, uri) == "" {
|
||||
// Go get only supports module mode for now.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var actions []protocol.CodeAction
|
||||
for _, diag := range diagnostics {
|
||||
matches := importErrorRe.FindStringSubmatch(diag.Message)
|
||||
if len(matches) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
args, err := source.MarshalArgs(uri, matches[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actions = append(actions, protocol.CodeAction{
|
||||
Title: fmt.Sprintf("go get package %v", matches[1]),
|
||||
Diagnostics: []protocol.Diagnostic{diag},
|
||||
Kind: protocol.QuickFix,
|
||||
Command: &protocol.Command{
|
||||
Title: source.CommandGoGetPackage.Title,
|
||||
Command: source.CommandGoGetPackage.ID(),
|
||||
Arguments: args,
|
||||
},
|
||||
})
|
||||
}
|
||||
return actions, nil
|
||||
}
|
||||
|
||||
func convenienceFixes(ctx context.Context, snapshot source.Snapshot, pkg source.Package, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
|
||||
var analyzers []*analysis.Analyzer
|
||||
for _, a := range snapshot.View().Options().ConvenienceAnalyzers {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/internal/event"
|
||||
"golang.org/x/tools/internal/gocommand"
|
||||
|
|
@ -228,6 +229,19 @@ func (s *Server) runCommand(ctx context.Context, work *workDone, command *source
|
|||
return err
|
||||
}
|
||||
return s.runGoGetModule(ctx, snapshot, uri.SpanURI(), addRequire, goCmdArgs)
|
||||
case source.CommandGoGetPackage:
|
||||
var uri protocol.DocumentURI
|
||||
var pkg string
|
||||
if err := source.UnmarshalArgs(args, &uri, &pkg); err != nil {
|
||||
return err
|
||||
}
|
||||
snapshot, _, ok, release, err := s.beginFileRequest(ctx, uri, source.UnknownKind)
|
||||
defer release()
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
return s.runGoGetPackage(ctx, snapshot, uri.SpanURI(), pkg)
|
||||
|
||||
case source.CommandToggleDetails:
|
||||
var fileURI protocol.DocumentURI
|
||||
if err := source.UnmarshalArgs(args, &fileURI); err != nil {
|
||||
|
|
@ -387,6 +401,19 @@ func (s *Server) runGoGenerate(ctx context.Context, snapshot source.Snapshot, di
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) runGoGetPackage(ctx context.Context, snapshot source.Snapshot, uri span.URI, pkg string) error {
|
||||
stdout, err := snapshot.RunGoCommandDirect(ctx, source.WriteTemporaryModFile|source.AllowNetwork, &gocommand.Invocation{
|
||||
Verb: "list",
|
||||
Args: []string{"-f", "{{.Module.Path}}@{{.Module.Version}}", pkg},
|
||||
WorkingDir: filepath.Dir(uri.Filename()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ver := strings.TrimSpace(stdout.String())
|
||||
return s.runGoGetModule(ctx, snapshot, uri, true, []string{ver})
|
||||
}
|
||||
|
||||
func (s *Server) runGoGetModule(ctx context.Context, snapshot source.Snapshot, uri span.URI, addRequire bool, args []string) error {
|
||||
if addRequire {
|
||||
// Using go get to create a new dependency results in an
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -64,6 +64,7 @@ var Commands = []*Command{
|
|||
CommandTidy,
|
||||
CommandUpdateGoSum,
|
||||
CommandUndeclaredName,
|
||||
CommandGoGetPackage,
|
||||
CommandAddDependency,
|
||||
CommandUpgradeDependency,
|
||||
CommandRemoveDependency,
|
||||
|
|
@ -100,6 +101,12 @@ var (
|
|||
Title: "Run go mod vendor",
|
||||
}
|
||||
|
||||
// CommandGoGetPackage runs `go get` to fetch a package.
|
||||
CommandGoGetPackage = &Command{
|
||||
Name: "go_get_package",
|
||||
Title: "go get package",
|
||||
}
|
||||
|
||||
// CommandUpdateGoSum updates the go.sum file for a module.
|
||||
CommandUpdateGoSum = &Command{
|
||||
Name: "update_go_sum",
|
||||
|
|
|
|||
Loading…
Reference in New Issue