mirror of
https://github.com/golang/go
synced 2024-11-18 08:54:45 -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:
parent
7ef8a99cf3
commit
e8e6be9f45
1
go.mod
1
go.mod
@ -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
3
go.sum
@ -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=
|
||||||
|
6
internal/lsp/cache/pkg.go
vendored
6
internal/lsp/cache/pkg.go
vendored
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
10
internal/lsp/source/suggested_fix.go
Normal file
10
internal/lsp/source/suggested_fix.go
Normal 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
|
||||||
|
}
|
26
internal/lsp/source/suggested_fix_experimental.go
Normal file
26
internal/lsp/source/suggested_fix_experimental.go
Normal 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
|
||||||
|
}
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user