mirror of
https://github.com/golang/go
synced 2024-11-05 17:36:15 -07:00
internal/lsp: move error range computations into cache package
A continuation of CL 202298, only for analysis errors. Change-Id: I957d52cef31938ef66be73463e92695a5b56869c Reviewed-on: https://go-review.googlesource.com/c/tools/+/202540 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
d2fffb4b84
commit
3057e18543
41
internal/lsp/cache/analysis.go
vendored
41
internal/lsp/cache/analysis.go
vendored
@ -17,7 +17,7 @@ import (
|
||||
errors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func (s *snapshot) Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) (map[*analysis.Analyzer][]*analysis.Diagnostic, error) {
|
||||
func (s *snapshot) Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) (map[*analysis.Analyzer][]*source.Error, error) {
|
||||
var roots []*actionHandle
|
||||
|
||||
for _, a := range analyzers {
|
||||
@ -34,7 +34,7 @@ func (s *snapshot) Analyze(ctx context.Context, id string, analyzers []*analysis
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
results := make(map[*analysis.Analyzer][]*analysis.Diagnostic)
|
||||
results := make(map[*analysis.Analyzer][]*source.Error)
|
||||
for _, ah := range roots {
|
||||
diagnostics, _, err := ah.analyze(ctx)
|
||||
if err != nil {
|
||||
@ -63,7 +63,7 @@ type actionHandle struct {
|
||||
}
|
||||
|
||||
type actionData struct {
|
||||
diagnostics []*analysis.Diagnostic
|
||||
diagnostics []*source.Error
|
||||
result interface{}
|
||||
err error
|
||||
}
|
||||
@ -133,7 +133,7 @@ func (s *snapshot) actionHandle(ctx context.Context, id packageID, mode source.P
|
||||
return ah, nil
|
||||
}
|
||||
|
||||
func (act *actionHandle) analyze(ctx context.Context) ([]*analysis.Diagnostic, interface{}, error) {
|
||||
func (act *actionHandle) analyze(ctx context.Context) ([]*source.Error, interface{}, error) {
|
||||
v := act.handle.Get(ctx)
|
||||
if v == nil {
|
||||
return nil, nil, errors.Errorf("no analyses for %s", act.pkg.ID())
|
||||
@ -150,10 +150,10 @@ func (act *actionHandle) String() string {
|
||||
return fmt.Sprintf("%s@%s", act.analyzer, act.pkg.PkgPath())
|
||||
}
|
||||
|
||||
func execAll(ctx context.Context, fset *token.FileSet, actions []*actionHandle) (map[*actionHandle][]*analysis.Diagnostic, map[*actionHandle]interface{}, error) {
|
||||
func execAll(ctx context.Context, fset *token.FileSet, actions []*actionHandle) (map[*actionHandle][]*source.Error, map[*actionHandle]interface{}, error) {
|
||||
var (
|
||||
mu sync.Mutex
|
||||
diagnostics = make(map[*actionHandle][]*analysis.Diagnostic)
|
||||
diagnostics = make(map[*actionHandle][]*source.Error)
|
||||
results = make(map[*actionHandle]interface{})
|
||||
)
|
||||
|
||||
@ -178,7 +178,7 @@ func execAll(ctx context.Context, fset *token.FileSet, actions []*actionHandle)
|
||||
return diagnostics, results, g.Wait()
|
||||
}
|
||||
|
||||
func (act *actionHandle) exec(ctx context.Context, fset *token.FileSet) (diagnostics []*analysis.Diagnostic, result interface{}, err error) {
|
||||
func (act *actionHandle) exec(ctx context.Context, fset *token.FileSet) ([]*source.Error, interface{}, error) {
|
||||
// Analyze dependencies.
|
||||
_, depResults, err := execAll(ctx, fset, act.deps)
|
||||
if err != nil {
|
||||
@ -204,6 +204,8 @@ func (act *actionHandle) exec(ctx context.Context, fset *token.FileSet) (diagnos
|
||||
}
|
||||
}
|
||||
|
||||
var diagnostics []*analysis.Diagnostic
|
||||
|
||||
// Run the analysis.
|
||||
pass := &analysis.Pass{
|
||||
Analyzer: act.analyzer,
|
||||
@ -225,14 +227,13 @@ func (act *actionHandle) exec(ctx context.Context, fset *token.FileSet) (diagnos
|
||||
|
||||
if act.pkg.IsIllTyped() {
|
||||
return nil, nil, errors.Errorf("analysis skipped due to errors in package: %v", act.pkg.GetErrors())
|
||||
} else {
|
||||
result, err = pass.Analyzer.Run(pass)
|
||||
if err == nil {
|
||||
if got, want := reflect.TypeOf(result), pass.Analyzer.ResultType; got != want {
|
||||
err = errors.Errorf(
|
||||
"internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v",
|
||||
pass.Pkg.Path(), pass.Analyzer, got, want)
|
||||
}
|
||||
}
|
||||
result, err := pass.Analyzer.Run(pass)
|
||||
if err == nil {
|
||||
if got, want := reflect.TypeOf(result), pass.Analyzer.ResultType; got != want {
|
||||
err = errors.Errorf(
|
||||
"internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v",
|
||||
pass.Pkg.Path(), pass.Analyzer, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,7 +241,15 @@ func (act *actionHandle) exec(ctx context.Context, fset *token.FileSet) (diagnos
|
||||
pass.ExportObjectFact = nil
|
||||
pass.ExportPackageFact = nil
|
||||
|
||||
return diagnostics, result, err
|
||||
var errors []*source.Error
|
||||
for _, diag := range diagnostics {
|
||||
srcErr, err := sourceError(ctx, act.pkg, diag)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
errors = append(errors, srcErr)
|
||||
}
|
||||
return errors, result, err
|
||||
}
|
||||
|
||||
// inheritFacts populates act.facts with
|
||||
|
4
internal/lsp/cache/check.go
vendored
4
internal/lsp/cache/check.go
vendored
@ -337,11 +337,11 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle) (*p
|
||||
_ = check.Files(files)
|
||||
|
||||
for _, e := range rawErrors {
|
||||
srcErr, err := sourceError(ctx, imp.snapshot.view, pkg, e)
|
||||
srcErr, err := sourceError(ctx, pkg, e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg.errors = append(pkg.errors, *srcErr)
|
||||
pkg.errors = append(pkg.errors, srcErr)
|
||||
}
|
||||
|
||||
return pkg, nil
|
||||
|
118
internal/lsp/cache/errors.go
vendored
118
internal/lsp/cache/errors.go
vendored
@ -7,17 +7,21 @@ import (
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
func sourceError(ctx context.Context, view *view, pkg *pkg, e error) (*source.Error, error) {
|
||||
func sourceError(ctx context.Context, pkg *pkg, e interface{}) (*source.Error, error) {
|
||||
var (
|
||||
spn span.Span
|
||||
msg string
|
||||
kind packages.ErrorKind
|
||||
spn span.Span
|
||||
err error
|
||||
msg, category string
|
||||
kind source.ErrorKind
|
||||
fixes []source.SuggestedFix
|
||||
related []source.RelatedInformation
|
||||
)
|
||||
switch e := e.(type) {
|
||||
case packages.Error:
|
||||
@ -27,35 +31,113 @@ func sourceError(ctx context.Context, view *view, pkg *pkg, e error) (*source.Er
|
||||
spn = span.Parse(e.Pos)
|
||||
}
|
||||
msg = e.Msg
|
||||
kind = e.Kind
|
||||
kind = toSourceErrorKind(e.Kind)
|
||||
case *scanner.Error:
|
||||
msg = e.Msg
|
||||
kind = packages.ParseError
|
||||
kind = source.ParseError
|
||||
spn = span.Parse(e.Pos.String())
|
||||
case scanner.ErrorList:
|
||||
// The first parser error is likely the root cause of the problem.
|
||||
if e.Len() > 0 {
|
||||
spn = span.Parse(e[0].Pos.String())
|
||||
msg = e[0].Msg
|
||||
kind = packages.ParseError
|
||||
kind = source.ParseError
|
||||
}
|
||||
case types.Error:
|
||||
spn = span.Parse(view.session.cache.fset.Position(e.Pos).String())
|
||||
spn = span.Parse(pkg.snapshot.view.session.cache.fset.Position(e.Pos).String())
|
||||
msg = e.Msg
|
||||
kind = packages.TypeError
|
||||
kind = source.TypeError
|
||||
case *analysis.Diagnostic:
|
||||
spn, err = span.NewRange(pkg.snapshot.view.session.cache.fset, e.Pos, e.End).Span()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg = e.Message
|
||||
kind = source.Analysis
|
||||
category = e.Category
|
||||
fixes, err = suggestedFixes(ctx, pkg, e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
related, err = relatedInformation(ctx, pkg, e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rng, err := spanToRange(ctx, pkg, spn, kind == packages.TypeError)
|
||||
rng, err := spanToRange(ctx, pkg, spn, kind == source.TypeError)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &source.Error{
|
||||
URI: spn.URI(),
|
||||
Range: rng,
|
||||
Msg: msg,
|
||||
Kind: kind,
|
||||
URI: spn.URI(),
|
||||
Range: rng,
|
||||
Message: msg,
|
||||
Kind: kind,
|
||||
Category: category,
|
||||
SuggestedFixes: fixes,
|
||||
Related: related,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func suggestedFixes(ctx context.Context, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
|
||||
var fixes []source.SuggestedFix
|
||||
for _, fix := range diag.SuggestedFixes {
|
||||
edits := make(map[span.URI][]protocol.TextEdit)
|
||||
for _, e := range fix.TextEdits {
|
||||
spn, err := span.NewRange(pkg.Snapshot().View().Session().Cache().FileSet(), e.Pos, e.End).Span()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := spanToRange(ctx, pkg, spn, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
|
||||
Range: rng,
|
||||
NewText: string(e.NewText),
|
||||
})
|
||||
}
|
||||
fixes = append(fixes, source.SuggestedFix{
|
||||
Title: fix.Message,
|
||||
Edits: edits,
|
||||
})
|
||||
}
|
||||
return fixes, nil
|
||||
}
|
||||
|
||||
func relatedInformation(ctx context.Context, pkg *pkg, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
|
||||
var out []source.RelatedInformation
|
||||
for _, related := range diag.Related {
|
||||
spn, err := span.NewRange(pkg.Snapshot().View().Session().Cache().FileSet(), related.Pos, related.End).Span()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := spanToRange(ctx, pkg, spn, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, source.RelatedInformation{
|
||||
URI: spn.URI(),
|
||||
Range: rng,
|
||||
Message: related.Message,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind {
|
||||
switch kind {
|
||||
case packages.ListError:
|
||||
return source.ListError
|
||||
case packages.ParseError:
|
||||
return source.ParseError
|
||||
case packages.TypeError:
|
||||
return source.TypeError
|
||||
default:
|
||||
return source.UnknownError
|
||||
}
|
||||
}
|
||||
|
||||
// spanToRange converts a span.Span to a protocol.Range,
|
||||
// assuming that the span belongs to the package whose diagnostics are being computed.
|
||||
func spanToRange(ctx context.Context, pkg *pkg, spn span.Span, isTypeError bool) (protocol.Range, error) {
|
||||
@ -67,11 +149,11 @@ func spanToRange(ctx context.Context, pkg *pkg, spn span.Span, isTypeError bool)
|
||||
if err != nil {
|
||||
return protocol.Range{}, err
|
||||
}
|
||||
data, _, err := ph.File().Read(ctx)
|
||||
if err != nil {
|
||||
return protocol.Range{}, err
|
||||
}
|
||||
if spn.IsPoint() && isTypeError {
|
||||
data, _, err := ph.File().Read(ctx)
|
||||
if err != nil {
|
||||
return protocol.Range{}, err
|
||||
}
|
||||
if s, err := spn.WithOffset(m.Converter); err == nil {
|
||||
start := s.Start()
|
||||
offset := start.Offset()
|
||||
|
4
internal/lsp/cache/pkg.go
vendored
4
internal/lsp/cache/pkg.go
vendored
@ -27,7 +27,7 @@ type pkg struct {
|
||||
mode source.ParseMode
|
||||
|
||||
files []source.ParseGoHandle
|
||||
errors []source.Error
|
||||
errors []*source.Error
|
||||
imports map[packagePath]*pkg
|
||||
types *types.Package
|
||||
typesInfo *types.Info
|
||||
@ -79,7 +79,7 @@ func (p *pkg) GetSyntax(ctx context.Context) []*ast.File {
|
||||
return syntax
|
||||
}
|
||||
|
||||
func (p *pkg) GetErrors() []source.Error {
|
||||
func (p *pkg) GetErrors() []*source.Error {
|
||||
return p.errors
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,10 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/telemetry"
|
||||
"golang.org/x/tools/internal/span"
|
||||
@ -30,19 +28,17 @@ type Diagnostic struct {
|
||||
Related []RelatedInformation
|
||||
}
|
||||
|
||||
type SuggestedFix struct {
|
||||
Title string
|
||||
Edits map[span.URI][]protocol.TextEdit
|
||||
}
|
||||
|
||||
type RelatedInformation struct {
|
||||
URI span.URI
|
||||
Range protocol.Range
|
||||
Message string
|
||||
}
|
||||
|
||||
type DiagnosticSeverity int
|
||||
|
||||
const (
|
||||
SeverityWarning DiagnosticSeverity = iota
|
||||
SeverityError
|
||||
)
|
||||
|
||||
func Diagnostics(ctx context.Context, view View, f File, disabledAnalyses map[string]struct{}) (map[span.URI][]Diagnostic, string, error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.Diagnostics", telemetry.File.Of(f.URI()))
|
||||
defer done()
|
||||
@ -79,7 +75,7 @@ func Diagnostics(ctx context.Context, view View, f File, disabledAnalyses map[st
|
||||
|
||||
// Prepare any additional reports for the errors in this package.
|
||||
for _, err := range pkg.GetErrors() {
|
||||
if err.Kind != packages.ListError {
|
||||
if err.Kind != ListError {
|
||||
continue
|
||||
}
|
||||
clearReports(view, reports, err.URI)
|
||||
@ -119,7 +115,7 @@ func diagnostics(ctx context.Context, view View, pkg Package, reports map[span.U
|
||||
for _, err := range pkg.GetErrors() {
|
||||
diag := &Diagnostic{
|
||||
URI: err.URI,
|
||||
Message: err.Msg,
|
||||
Message: err.Message,
|
||||
Range: err.Range,
|
||||
Severity: protocol.SeverityError,
|
||||
}
|
||||
@ -129,13 +125,13 @@ func diagnostics(ctx context.Context, view View, pkg Package, reports map[span.U
|
||||
diagSets[diag.URI] = set
|
||||
}
|
||||
switch err.Kind {
|
||||
case packages.ParseError:
|
||||
case ParseError:
|
||||
set.parseErrors = append(set.parseErrors, diag)
|
||||
diag.Source = "syntax"
|
||||
case packages.TypeError:
|
||||
case TypeError:
|
||||
set.typeErrors = append(set.typeErrors, diag)
|
||||
diag.Source = "compiler"
|
||||
default:
|
||||
case ListError:
|
||||
set.listErrors = append(set.listErrors, diag)
|
||||
diag.Source = "go list"
|
||||
}
|
||||
@ -162,148 +158,39 @@ func diagnostics(ctx context.Context, view View, pkg Package, reports map[span.U
|
||||
}
|
||||
|
||||
func analyses(ctx context.Context, snapshot Snapshot, cph CheckPackageHandle, disabledAnalyses map[string]struct{}, reports map[span.URI][]Diagnostic) error {
|
||||
// Type checking and parsing succeeded. Run analyses.
|
||||
if err := runAnalyses(ctx, snapshot, cph, disabledAnalyses, func(diags []*analysis.Diagnostic, a *analysis.Analyzer) error {
|
||||
for _, diag := range diags {
|
||||
diagnostic, err := toDiagnostic(ctx, snapshot.View(), diag, a.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addReport(snapshot.View(), reports, diagnostic.URI, diagnostic)
|
||||
var analyzers []*analysis.Analyzer
|
||||
for _, a := range snapshot.View().Options().Analyzers {
|
||||
if _, ok := disabledAnalyses[a.Name]; ok {
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
analyzers = append(analyzers, a)
|
||||
}
|
||||
|
||||
diagnostics, err := snapshot.Analyze(ctx, cph.ID(), analyzers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For caching diagnostics.
|
||||
// TODO(https://golang.org/issue/32443): Cache diagnostics on the snapshot.
|
||||
pkg, err := cph.Check(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Report diagnostics and errors from root analyzers.
|
||||
var sdiags []Diagnostic
|
||||
for a, diags := range diagnostics {
|
||||
for _, diag := range diags {
|
||||
sdiag := toDiagnostic(diag, a.Name)
|
||||
addReport(snapshot.View(), reports, sdiag)
|
||||
sdiags = append(sdiags, sdiag)
|
||||
}
|
||||
pkg.SetDiagnostics(a, sdiags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func packageForSpan(ctx context.Context, view View, spn span.Span) (Package, error) {
|
||||
f, err := view.GetFile(ctx, spn.URI())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If the package has changed since these diagnostics were computed,
|
||||
// this may be incorrect. Should the package be associated with the diagnostic?
|
||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
return Diagnostic{}, err
|
||||
}
|
||||
pkg, err := packageForSpan(ctx, view, spn)
|
||||
if err != nil {
|
||||
return Diagnostic{}, err
|
||||
}
|
||||
ph, err := pkg.File(spn.URI())
|
||||
if err != nil {
|
||||
return Diagnostic{}, err
|
||||
}
|
||||
_, m, _, err := ph.Cached(ctx)
|
||||
if err != nil {
|
||||
return Diagnostic{}, err
|
||||
}
|
||||
rng, err := m.Range(spn)
|
||||
if err != nil {
|
||||
return Diagnostic{}, err
|
||||
}
|
||||
fixes, err := suggestedFixes(ctx, view, pkg, diag)
|
||||
if err != nil {
|
||||
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.
|
||||
// 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.
|
||||
var tags []protocol.DiagnosticTag
|
||||
if onlyDeletions(fixes) {
|
||||
tags = append(tags, protocol.Unnecessary)
|
||||
}
|
||||
if diag.Category != "" {
|
||||
category += "." + diag.Category
|
||||
}
|
||||
return Diagnostic{
|
||||
URI: spn.URI(),
|
||||
Range: rng,
|
||||
Source: category,
|
||||
Message: diag.Message,
|
||||
Severity: protocol.SeverityWarning,
|
||||
SuggestedFixes: fixes,
|
||||
Tags: tags,
|
||||
Related: related,
|
||||
}, 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
|
||||
}
|
||||
|
||||
// spanToRange converts a span.Span to a protocol.Range,
|
||||
// assuming that the span belongs to the package whose diagnostics are being computed.
|
||||
func spanToRange(ctx context.Context, view View, pkg Package, spn span.Span, isTypeError bool) (protocol.Range, error) {
|
||||
ph, err := pkg.File(spn.URI())
|
||||
if err != nil {
|
||||
return protocol.Range{}, err
|
||||
}
|
||||
_, m, _, err := ph.Cached(ctx)
|
||||
if err != nil {
|
||||
return protocol.Range{}, err
|
||||
}
|
||||
data, _, err := ph.File().Read(ctx)
|
||||
if err != nil {
|
||||
return protocol.Range{}, err
|
||||
}
|
||||
// Try to get a range for the diagnostic.
|
||||
// TODO: Don't just limit ranges to type errors.
|
||||
if spn.IsPoint() && isTypeError {
|
||||
if s, err := spn.WithOffset(m.Converter); err == nil {
|
||||
start := s.Start()
|
||||
offset := start.Offset()
|
||||
if width := bytes.IndexAny(data[offset:], " \n,():;[]"); width > 0 {
|
||||
spn = span.New(spn.URI(), start, span.NewPoint(start.Line(), start.Column()+width, offset+width))
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.Range(spn)
|
||||
}
|
||||
|
||||
func clearReports(v View, reports map[span.URI][]Diagnostic, uri span.URI) {
|
||||
if v.Ignore(uri) {
|
||||
return
|
||||
@ -311,12 +198,12 @@ func clearReports(v View, reports map[span.URI][]Diagnostic, uri span.URI) {
|
||||
reports[uri] = []Diagnostic{}
|
||||
}
|
||||
|
||||
func addReport(v View, reports map[span.URI][]Diagnostic, uri span.URI, diagnostic Diagnostic) {
|
||||
if v.Ignore(uri) {
|
||||
func addReport(v View, reports map[span.URI][]Diagnostic, diagnostic Diagnostic) {
|
||||
if v.Ignore(diagnostic.URI) {
|
||||
return
|
||||
}
|
||||
if _, ok := reports[uri]; ok {
|
||||
reports[uri] = append(reports[uri], diagnostic)
|
||||
if _, ok := reports[diagnostic.URI]; ok {
|
||||
reports[diagnostic.URI] = append(reports[diagnostic.URI], diagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,38 +219,42 @@ func singleDiagnostic(uri span.URI, format string, a ...interface{}) map[span.UR
|
||||
}
|
||||
}
|
||||
|
||||
func runAnalyses(ctx context.Context, snapshot Snapshot, cph CheckPackageHandle, disabledAnalyses map[string]struct{}, report func(diag []*analysis.Diagnostic, a *analysis.Analyzer) error) error {
|
||||
var analyzers []*analysis.Analyzer
|
||||
for _, a := range snapshot.View().Options().Analyzers {
|
||||
if _, ok := disabledAnalyses[a.Name]; ok {
|
||||
continue
|
||||
}
|
||||
analyzers = append(analyzers, a)
|
||||
func toDiagnostic(e *Error, category string) Diagnostic {
|
||||
// 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.
|
||||
// TODO(golang/go/#34508): Return these codes from the diagnostics themselves.
|
||||
var tags []protocol.DiagnosticTag
|
||||
if onlyDeletions(e.SuggestedFixes) {
|
||||
tags = append(tags, protocol.Unnecessary)
|
||||
}
|
||||
|
||||
diagnostics, err := snapshot.Analyze(ctx, cph.ID(), analyzers)
|
||||
if err != nil {
|
||||
return err
|
||||
if e.Category != "" {
|
||||
category += "." + e.Category
|
||||
}
|
||||
|
||||
// Report diagnostics and errors from root analyzers.
|
||||
var sdiags []Diagnostic
|
||||
for a, diags := range diagnostics {
|
||||
if err := report(diags, a); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, diag := range diags {
|
||||
sdiag, err := toDiagnostic(ctx, snapshot.View(), diag, a.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sdiags = append(sdiags, sdiag)
|
||||
}
|
||||
pkg, err := cph.Check(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkg.SetDiagnostics(a, sdiags)
|
||||
return Diagnostic{
|
||||
URI: e.URI,
|
||||
Range: e.Range,
|
||||
Message: e.Message,
|
||||
Source: category,
|
||||
Severity: protocol.SeverityWarning,
|
||||
Tags: tags,
|
||||
SuggestedFixes: e.SuggestedFixes,
|
||||
Related: e.Related,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// onlyDeletions returns true if all of the suggested fixes are deletions.
|
||||
func onlyDeletions(fixes []SuggestedFix) bool {
|
||||
for _, fix := range fixes {
|
||||
for _, edits := range fix.Edits {
|
||||
for _, edit := range edits {
|
||||
if edit.NewText != "" {
|
||||
return false
|
||||
}
|
||||
if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"context"
|
||||
"go/format"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/imports"
|
||||
"golang.org/x/tools/internal/lsp/diff"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
@ -269,7 +268,7 @@ func CandidateImports(ctx context.Context, view View, filename string) (pkgs []i
|
||||
// hasParseErrors returns true if the given file has parse errors.
|
||||
func hasParseErrors(pkg Package, uri span.URI) bool {
|
||||
for _, err := range pkg.GetErrors() {
|
||||
if err.URI == uri && err.Kind == packages.ParseError {
|
||||
if err.URI == uri && err.Kind == ParseError {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -278,7 +277,7 @@ func hasParseErrors(pkg Package, uri span.URI) bool {
|
||||
|
||||
func hasListErrors(pkg Package) bool {
|
||||
for _, err := range pkg.GetErrors() {
|
||||
if err.Kind == packages.ListError {
|
||||
if err.Kind == ListError {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +1 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
type SuggestedFix struct {
|
||||
Title string
|
||||
Edits map[span.URI][]protocol.TextEdit
|
||||
}
|
||||
|
||||
func suggestedFixes(ctx context.Context, view View, pkg Package, diag *analysis.Diagnostic) ([]SuggestedFix, error) {
|
||||
var fixes []SuggestedFix
|
||||
for _, fix := range diag.SuggestedFixes {
|
||||
edits := make(map[span.URI][]protocol.TextEdit)
|
||||
for _, e := range fix.TextEdits {
|
||||
posn := view.Session().Cache().FileSet().Position(e.Pos)
|
||||
uri := span.FileURI(posn.Filename)
|
||||
ph, _, err := pkg.FindFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, m, _, err := ph.Cached(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mrng, err := posToRange(ctx, view, m, e.Pos, e.End)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := mrng.Range()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
edits[uri] = append(edits[uri], protocol.TextEdit{
|
||||
Range: rng,
|
||||
NewText: string(e.NewText),
|
||||
})
|
||||
}
|
||||
fixes = append(fixes, SuggestedFix{
|
||||
Title: fix.Message,
|
||||
Edits: edits,
|
||||
})
|
||||
}
|
||||
return fixes, nil
|
||||
}
|
||||
|
||||
// onlyDeletions returns true if all of the suggested fixes are deletions.
|
||||
func onlyDeletions(fixes []SuggestedFix) bool {
|
||||
for _, fix := range fixes {
|
||||
for _, edits := range fix.Edits {
|
||||
for _, edit := range edits {
|
||||
if edit.NewText != "" {
|
||||
return false
|
||||
}
|
||||
if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ type Snapshot interface {
|
||||
View() View
|
||||
|
||||
// Analyze runs the analyses for the given package at this snapshot.
|
||||
Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) (map[*analysis.Analyzer][]*analysis.Diagnostic, error)
|
||||
Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) (map[*analysis.Analyzer][]*Error, error)
|
||||
}
|
||||
|
||||
// File represents a source file of any type.
|
||||
@ -280,7 +280,7 @@ type Package interface {
|
||||
Files() []ParseGoHandle
|
||||
File(uri span.URI) (ParseGoHandle, error)
|
||||
GetSyntax(context.Context) []*ast.File
|
||||
GetErrors() []Error
|
||||
GetErrors() []*Error
|
||||
GetTypes() *types.Package
|
||||
GetTypesInfo() *types.Info
|
||||
GetTypesSizes() types.Sizes
|
||||
@ -298,14 +298,27 @@ type Package interface {
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Msg string
|
||||
URI span.URI
|
||||
Range protocol.Range
|
||||
Kind packages.ErrorKind
|
||||
URI span.URI
|
||||
Range protocol.Range
|
||||
Kind ErrorKind
|
||||
Message string
|
||||
Category string
|
||||
SuggestedFixes []SuggestedFix
|
||||
Related []RelatedInformation
|
||||
}
|
||||
|
||||
type ErrorKind int
|
||||
|
||||
const (
|
||||
UnknownError = ErrorKind(iota)
|
||||
ListError
|
||||
ParseError
|
||||
TypeError
|
||||
Analysis
|
||||
)
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%s:%s: %s", e.URI, e.Range, e.Msg)
|
||||
return fmt.Sprintf("%s:%s: %s", e.URI, e.Range, e.Message)
|
||||
}
|
||||
|
||||
type BuiltinPackage interface {
|
||||
|
Loading…
Reference in New Issue
Block a user