1
0
mirror of https://github.com/golang/go synced 2024-11-18 05:04:47 -07:00

internal/lsp: plumb suggested fixes through the LSP

Change-Id: Ia9e077e6b9cf8a817103d90481768ae99409c574
Reviewed-on: https://go-review.googlesource.com/c/tools/+/183264
Run-TryBot: Michael Matloob <matloob@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Michael Matloob 2019-06-20 16:57:45 -04:00
parent 7ef8a99cf3
commit e8e6be9f45
9 changed files with 142 additions and 46 deletions

1
go.mod
View File

@ -5,4 +5,5 @@ go 1.11
require ( require (
golang.org/x/net v0.0.0-20190311183353-d8887717615a golang.org/x/net v0.0.0-20190311183353-d8887717615a
golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/tools/gopls v0.1.0 // indirect
) )

3
go.sum
View File

@ -5,3 +5,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEha
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190612231717-10539ce30318/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools/gopls v0.1.0 h1:e5o2xK2HU//kzIRypLBw6/8pXdWuYDd8pliYpnQuNw8=
golang.org/x/tools/gopls v0.1.0/go.mod h1:p8Q0IUu6EEeGxqmoN/g6Et3gReLCGA7PtNRdyOxcWJE=

View File

@ -37,7 +37,7 @@ type pkg struct {
analyses map[*analysis.Analyzer]*analysisEntry analyses map[*analysis.Analyzer]*analysisEntry
diagMu sync.Mutex diagMu sync.Mutex
diagnostics []analysis.Diagnostic diagnostics []source.Diagnostic
} }
// packageID is a type that abstracts a package ID. // packageID is a type that abstracts a package ID.
@ -193,13 +193,13 @@ func (pkg *pkg) GetImport(pkgPath string) source.Package {
return nil return nil
} }
func (pkg *pkg) SetDiagnostics(diags []analysis.Diagnostic) { func (pkg *pkg) SetDiagnostics(diags []source.Diagnostic) {
pkg.diagMu.Lock() pkg.diagMu.Lock()
defer pkg.diagMu.Unlock() defer pkg.diagMu.Unlock()
pkg.diagnostics = diags pkg.diagnostics = diags
} }
func (pkg *pkg) GetDiagnostics() []analysis.Diagnostic { func (pkg *pkg) GetDiagnostics() []source.Diagnostic {
pkg.diagMu.Lock() pkg.diagMu.Lock()
defer pkg.diagMu.Unlock() defer pkg.diagMu.Unlock()
return pkg.diagnostics return pkg.diagnostics

View File

@ -16,7 +16,7 @@ import (
func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
uri := span.NewURI(params.TextDocument.URI) uri := span.NewURI(params.TextDocument.URI)
view := s.session.ViewOf(uri) view := s.session.ViewOf(uri)
_, m, err := getSourceFile(ctx, view, uri) gof, m, err := getGoFile(ctx, view, uri)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -57,6 +57,25 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
}, },
}) })
} }
diags := gof.GetPackage(ctx).GetDiagnostics()
for _, diag := range diags {
pdiag, err := toProtocolDiagnostic(ctx, view, diag)
if err != nil {
return nil, err
}
for _, ca := range diag.SuggestedFixes {
codeActions = append(codeActions, protocol.CodeAction{
Title: ca.Title,
Kind: protocol.QuickFix, // TODO(matloob): Be more accurate about these?
Edit: &protocol.WorkspaceEdit{
Changes: &map[string][]protocol.TextEdit{
string(spn.URI()): edits,
},
},
Diagnostics: []protocol.Diagnostic{pdiag},
})
}
}
} }
return codeActions, nil return codeActions, nil
} }

View File

@ -71,27 +71,35 @@ func (s *Server) publishDiagnostics(ctx context.Context, view source.View, uri s
func toProtocolDiagnostics(ctx context.Context, v source.View, diagnostics []source.Diagnostic) ([]protocol.Diagnostic, error) { func toProtocolDiagnostics(ctx context.Context, v source.View, diagnostics []source.Diagnostic) ([]protocol.Diagnostic, error) {
reports := []protocol.Diagnostic{} reports := []protocol.Diagnostic{}
for _, diag := range diagnostics { for _, diag := range diagnostics {
_, m, err := getSourceFile(ctx, v, diag.Span.URI()) diagnostic, err := toProtocolDiagnostic(ctx, v, diag)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var severity protocol.DiagnosticSeverity reports = append(reports, diagnostic)
switch diag.Severity {
case source.SeverityError:
severity = protocol.SeverityError
case source.SeverityWarning:
severity = protocol.SeverityWarning
}
rng, err := m.Range(diag.Span)
if err != nil {
return nil, err
}
reports = append(reports, protocol.Diagnostic{
Message: strings.TrimSpace(diag.Message), // go list returns errors prefixed by newline
Range: rng,
Severity: severity,
Source: diag.Source,
})
} }
return reports, nil return reports, nil
} }
func toProtocolDiagnostic(ctx context.Context, v source.View, diag source.Diagnostic) (protocol.Diagnostic, error) {
_, m, err := getSourceFile(ctx, v, diag.Span.URI())
if err != nil {
return protocol.Diagnostic{}, err
}
var severity protocol.DiagnosticSeverity
switch diag.Severity {
case source.SeverityError:
severity = protocol.SeverityError
case source.SeverityWarning:
severity = protocol.SeverityWarning
}
rng, err := m.Range(diag.Span)
if err != nil {
return protocol.Diagnostic{}, err
}
return protocol.Diagnostic{
Message: strings.TrimSpace(diag.Message), // go list returns errors prefixed by newline
Range: rng,
Severity: severity,
Source: diag.Source,
}, nil
}

View File

@ -42,6 +42,13 @@ type Diagnostic struct {
Message string Message string
Source string Source string
Severity DiagnosticSeverity Severity DiagnosticSeverity
SuggestedFixes []SuggestedFixes
}
type SuggestedFixes struct {
Title string
Edits []TextEdit
} }
type DiagnosticSeverity int type DiagnosticSeverity int
@ -59,7 +66,7 @@ func Diagnostics(ctx context.Context, view View, f GoFile, disabledAnalyses map[
// Prepare the reports we will send for the files in this package. // Prepare the reports we will send for the files in this package.
reports := make(map[span.URI][]Diagnostic) reports := make(map[span.URI][]Diagnostic)
for _, filename := range pkg.GetFilenames() { for _, filename := range pkg.GetFilenames() {
addReport(view, reports, span.FileURI(filename), nil) clearReports(view, reports, span.FileURI(filename))
} }
// Prepare any additional reports for the errors in this package. // Prepare any additional reports for the errors in this package.
@ -67,7 +74,7 @@ func Diagnostics(ctx context.Context, view View, f GoFile, disabledAnalyses map[
if err.Kind != packages.ListError { if err.Kind != packages.ListError {
continue continue
} }
addReport(view, reports, packagesErrorSpan(err).URI(), nil) clearReports(view, reports, packagesErrorSpan(err).URI())
} }
// Run diagnostics for the package that this URI belongs to. // Run diagnostics for the package that this URI belongs to.
@ -85,7 +92,7 @@ func Diagnostics(ctx context.Context, view View, f GoFile, disabledAnalyses map[
continue continue
} }
for _, filename := range pkg.GetFilenames() { for _, filename := range pkg.GetFilenames() {
addReport(view, reports, span.FileURI(filename), nil) clearReports(view, reports, span.FileURI(filename))
} }
diagnostics(ctx, view, pkg, reports) diagnostics(ctx, view, pkg, reports)
} }
@ -146,22 +153,11 @@ func diagnostics(ctx context.Context, v View, pkg Package, reports map[span.URI]
func analyses(ctx context.Context, v View, pkg Package, disabledAnalyses map[string]struct{}, reports map[span.URI][]Diagnostic) error { func analyses(ctx context.Context, v View, pkg Package, disabledAnalyses map[string]struct{}, reports map[span.URI][]Diagnostic) error {
// Type checking and parsing succeeded. Run analyses. // Type checking and parsing succeeded. Run analyses.
if err := runAnalyses(ctx, v, pkg, disabledAnalyses, func(a *analysis.Analyzer, diag analysis.Diagnostic) error { if err := runAnalyses(ctx, v, pkg, disabledAnalyses, func(a *analysis.Analyzer, diag analysis.Diagnostic) error {
r := span.NewRange(v.Session().Cache().FileSet(), diag.Pos, diag.End) diagnostic, err := toDiagnostic(a, v, diag)
s, err := r.Span()
if err != nil { if err != nil {
// The diagnostic has an invalid position, so we don't have a valid span.
return err return err
} }
category := a.Name addReport(v, reports, diagnostic.Span.URI(), diagnostic)
if diag.Category != "" {
category += "." + category
}
addReport(v, reports, s.URI(), &Diagnostic{
Source: category,
Span: s,
Message: diag.Message,
Severity: SeverityWarning,
})
return nil return nil
}); err != nil { }); err != nil {
return err return err
@ -169,15 +165,42 @@ func analyses(ctx context.Context, v View, pkg Package, disabledAnalyses map[str
return nil return nil
} }
func addReport(v View, reports map[span.URI][]Diagnostic, uri span.URI, diagnostic *Diagnostic) { func toDiagnostic(a *analysis.Analyzer, v View, diag analysis.Diagnostic) (Diagnostic, error) {
r := span.NewRange(v.Session().Cache().FileSet(), diag.Pos, diag.End)
s, err := r.Span()
if err != nil {
// The diagnostic has an invalid position, so we don't have a valid span.
return Diagnostic{}, err
}
category := a.Name
if diag.Category != "" {
category += "." + category
}
ca, err := getCodeActions(v.Session().Cache().FileSet(), diag)
if err != nil {
return Diagnostic{}, err
}
return Diagnostic{
Source: category,
Span: s,
Message: diag.Message,
Severity: SeverityWarning,
SuggestedFixes: ca,
}, nil
}
func clearReports(v View, reports map[span.URI][]Diagnostic, uri span.URI) {
if v.Ignore(uri) { if v.Ignore(uri) {
return return
} }
if diagnostic == nil { reports[uri] = []Diagnostic{}
reports[uri] = []Diagnostic{} }
} else {
reports[uri] = append(reports[uri], *diagnostic) func addReport(v View, reports map[span.URI][]Diagnostic, uri span.URI, diagnostic Diagnostic) {
if v.Ignore(uri) {
return
} }
reports[uri] = append(reports[uri], diagnostic)
} }
func packagesErrorSpan(err packages.Error) span.Span { func packagesErrorSpan(err packages.Error) span.Span {
@ -294,6 +317,7 @@ func runAnalyses(ctx context.Context, v View, pkg Package, disabledAnalyses map[
// Report diagnostics and errors from root analyzers. // Report diagnostics and errors from root analyzers.
for _, r := range roots { for _, r := range roots {
var sdiags []Diagnostic
for _, diag := range r.diagnostics { for _, diag := range r.diagnostics {
if r.err != nil { if r.err != nil {
// TODO(matloob): This isn't quite right: we might return a failed prerequisites error, // TODO(matloob): This isn't quite right: we might return a failed prerequisites error,
@ -303,8 +327,13 @@ func runAnalyses(ctx context.Context, v View, pkg Package, disabledAnalyses map[
if err := report(r.Analyzer, diag); err != nil { if err := report(r.Analyzer, diag); err != nil {
return err return err
} }
sdiag, err := toDiagnostic(r.Analyzer, v, diag)
if err != nil {
return err
}
sdiags = append(sdiags, sdiag)
} }
pkg.SetDiagnostics(r.diagnostics) pkg.SetDiagnostics(sdiags)
} }
return nil return nil
} }

View File

@ -0,0 +1,10 @@
// +build !experimental
package source
import "go/token"
import "golang.org/x/tools/go/analysis"
func getCodeActions(fset *token.FileSet, diag analysis.Diagnostic) ([]SuggestedFixes, error) {
return nil, nil
}

View File

@ -0,0 +1,26 @@
// +build experimental
package source
import (
"go/token"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/span"
)
func getCodeActions(fset *token.FileSet, diag analysis.Diagnostic) ([]CodeAction, error) {
var cas []CodeAction
for _, fix := range diag.SuggestedFixes {
var ca CodeAction
ca.Title = fix.Message
for _, te := range fix.TextEdits {
span, err := span.NewRange(fset, te.Pos, te.End).Span()
if err != nil {
return nil, err
}
ca.Edits = append(ca.Edits, TextEdit{span, string(te.NewText)})
}
cas = append(cas, ca)
}
return cas, nil
}

View File

@ -264,8 +264,8 @@ type Package interface {
IsIllTyped() bool IsIllTyped() bool
GetActionGraph(ctx context.Context, a *analysis.Analyzer) (*Action, error) GetActionGraph(ctx context.Context, a *analysis.Analyzer) (*Action, error)
GetImport(pkgPath string) Package GetImport(pkgPath string) Package
GetDiagnostics() []analysis.Diagnostic GetDiagnostics() []Diagnostic
SetDiagnostics(diags []analysis.Diagnostic) SetDiagnostics(diags []Diagnostic)
} }
// TextEdit represents a change to a section of a document. // TextEdit represents a change to a section of a document.