go/analysis, internal/lsp: add support for related information

This CL adds support for "related information", which allows
associating additional source positions and messages with a
diagnostic.

Change-Id: Ifc0634f68c9f3724b6508dc6331c62c819a24f78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/200597
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Dominik Honnef 2019-10-11 11:39:09 +02:00
parent 747b8b11d4
commit ce0314c87e
4 changed files with 91 additions and 17 deletions

View File

@ -22,6 +22,19 @@ type Diagnostic struct {
// Diagnostics should not contain SuggestedFixes that overlap. // Diagnostics should not contain SuggestedFixes that overlap.
// Experimental: This API is experimental and may change in the future. // Experimental: This API is experimental and may change in the future.
SuggestedFixes []SuggestedFix // optional SuggestedFixes []SuggestedFix // optional
// Experimental: This API is experimental and may change in the future.
Related []RelatedInformation // optional
}
// RelatedInformation contains information related to a diagnostic.
// For example, a diagnostic that flags duplicated declarations of a
// variable may include one RelatedInformation per existing
// declaration.
type RelatedInformation struct {
Pos token.Pos
End token.Pos
Message string
} }
// A SuggestedFix is a code change associated with a Diagnostic that a user can choose // A SuggestedFix is a code change associated with a Diagnostic that a user can choose

View File

@ -78,11 +78,17 @@ func run(pass *analysis.Pass) (interface{}, error) {
if ident.Name == from { if ident.Name == from {
msg := fmt.Sprintf("renaming %q to %q", from, to) msg := fmt.Sprintf("renaming %q to %q", from, to)
pass.Report(analysis.Diagnostic{ pass.Report(analysis.Diagnostic{
ident.Pos(), ident.End(), "", msg, Pos: ident.Pos(),
[]analysis.SuggestedFix{ End: ident.End(),
{msg, []analysis.TextEdit{ Message: msg,
{ident.Pos(), ident.End(), []byte(to)}}, SuggestedFixes: []analysis.SuggestedFix{{
Message: msg,
TextEdits: []analysis.TextEdit{{
Pos: ident.Pos(),
End: ident.End(),
NewText: []byte(to),
}}, }},
}},
}) })
} }
}) })

View File

@ -78,12 +78,23 @@ func (s *Server) publishDiagnostics(ctx context.Context, uri span.URI, diagnosti
func toProtocolDiagnostics(ctx context.Context, diagnostics []source.Diagnostic) []protocol.Diagnostic { func toProtocolDiagnostics(ctx context.Context, diagnostics []source.Diagnostic) []protocol.Diagnostic {
reports := []protocol.Diagnostic{} reports := []protocol.Diagnostic{}
for _, diag := range diagnostics { for _, diag := range diagnostics {
related := make([]protocol.DiagnosticRelatedInformation, 0, len(diag.Related))
for _, rel := range diag.Related {
related = append(related, protocol.DiagnosticRelatedInformation{
Location: protocol.Location{
URI: protocol.NewURI(rel.URI),
Range: rel.Range,
},
Message: rel.Message,
})
}
reports = append(reports, protocol.Diagnostic{ reports = append(reports, protocol.Diagnostic{
Message: strings.TrimSpace(diag.Message), // go list returns errors prefixed by newline Message: strings.TrimSpace(diag.Message), // go list returns errors prefixed by newline
Range: diag.Range, Range: diag.Range,
Severity: diag.Severity, Severity: diag.Severity,
Source: diag.Source, Source: diag.Source,
Tags: diag.Tags, Tags: diag.Tags,
RelatedInformation: related,
}) })
} }
return reports return reports

View File

@ -26,6 +26,13 @@ type Diagnostic struct {
Tags []protocol.DiagnosticTag Tags []protocol.DiagnosticTag
SuggestedFixes []SuggestedFix SuggestedFixes []SuggestedFix
Related []RelatedInformation
}
type RelatedInformation struct {
URI span.URI
Range protocol.Range
Message string
} }
type DiagnosticSeverity int type DiagnosticSeverity int
@ -170,26 +177,30 @@ func analyses(ctx context.Context, snapshot Snapshot, cph CheckPackageHandle, di
return nil return nil
} }
func toDiagnostic(ctx context.Context, view View, diag *analysis.Diagnostic, category string) (Diagnostic, error) { func packageForSpan(ctx context.Context, view View, spn span.Span) (Package, error) {
spn, err := span.NewRange(view.Session().Cache().FileSet(), diag.Pos, diag.End).Span()
if err != nil {
return Diagnostic{}, err
}
f, err := view.GetFile(ctx, spn.URI()) f, err := view.GetFile(ctx, spn.URI())
if err != nil { if err != nil {
return Diagnostic{}, err return nil, err
} }
// If the package has changed since these diagnostics were computed, // If the package has changed since these diagnostics were computed,
// this may be incorrect. Should the package be associated with the diagnostic? // this may be incorrect. Should the package be associated with the diagnostic?
_, cphs, err := view.CheckPackageHandles(ctx, f) _, cphs, err := view.CheckPackageHandles(ctx, f)
if err != nil { if err != nil {
return Diagnostic{}, err return nil, err
} }
cph, err := NarrowestCheckPackageHandle(cphs) cph, err := NarrowestCheckPackageHandle(cphs)
if err != nil {
return nil, err
}
return cph.Cached(ctx)
}
func toDiagnostic(ctx context.Context, view View, diag *analysis.Diagnostic, category string) (Diagnostic, error) {
spn, err := span.NewRange(view.Session().Cache().FileSet(), diag.Pos, diag.End).Span()
if err != nil { if err != nil {
return Diagnostic{}, err return Diagnostic{}, err
} }
pkg, err := cph.Cached(ctx) pkg, err := packageForSpan(ctx, view, spn)
if err != nil { if err != nil {
return Diagnostic{}, err return Diagnostic{}, err
} }
@ -209,6 +220,12 @@ func toDiagnostic(ctx context.Context, view View, diag *analysis.Diagnostic, cat
if err != nil { if err != nil {
return Diagnostic{}, err return Diagnostic{}, err
} }
related, err := relatedInformation(ctx, view, diag)
if err != nil {
return Diagnostic{}, err
}
// This is a bit of a hack, but clients > 3.15 will be able to grey out unnecessary code. // This is a bit of a hack, but clients > 3.15 will be able to grey out unnecessary code.
// If we are deleting code as part of all of our suggested fixes, assume that this is dead code. // If we are deleting code as part of all of our suggested fixes, assume that this is dead code.
// TODO(golang/go/#34508): Return these codes from the diagnostics themselves. // TODO(golang/go/#34508): Return these codes from the diagnostics themselves.
@ -227,9 +244,36 @@ func toDiagnostic(ctx context.Context, view View, diag *analysis.Diagnostic, cat
Severity: protocol.SeverityWarning, Severity: protocol.SeverityWarning,
SuggestedFixes: fixes, SuggestedFixes: fixes,
Tags: tags, Tags: tags,
Related: related,
}, nil }, nil
} }
func relatedInformation(ctx context.Context, view View, diag *analysis.Diagnostic) ([]RelatedInformation, error) {
var out []RelatedInformation
for _, related := range diag.Related {
r := span.NewRange(view.Session().Cache().FileSet(), related.Pos, related.End)
spn, err := r.Span()
if err != nil {
return nil, err
}
pkg, err := packageForSpan(ctx, view, spn)
if err != nil {
return nil, err
}
rng, err := spanToRange(ctx, view, pkg, spn, false)
if err != nil {
return nil, err
}
out = append(out, RelatedInformation{
URI: spn.URI(),
Range: rng,
Message: related.Message,
})
}
return out, nil
}
func clearReports(v View, reports map[span.URI][]Diagnostic, uri span.URI) { func clearReports(v View, reports map[span.URI][]Diagnostic, uri span.URI) {
if v.Ignore(uri) { if v.Ignore(uri) {
return return