1
0
mirror of https://github.com/golang/go synced 2024-10-01 10:38:33 -06:00
go/internal/lsp/cache/errors.go
Rebecca Stambler 3057e18543 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>
2019-10-21 22:12:58 +00:00

186 lines
4.8 KiB
Go

package cache
import (
"bytes"
"context"
"go/scanner"
"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, pkg *pkg, e interface{}) (*source.Error, error) {
var (
spn span.Span
err error
msg, category string
kind source.ErrorKind
fixes []source.SuggestedFix
related []source.RelatedInformation
)
switch e := e.(type) {
case packages.Error:
if e.Pos == "" {
spn = parseGoListError(e.Msg)
} else {
spn = span.Parse(e.Pos)
}
msg = e.Msg
kind = toSourceErrorKind(e.Kind)
case *scanner.Error:
msg = e.Msg
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 = source.ParseError
}
case types.Error:
spn = span.Parse(pkg.snapshot.view.session.cache.fset.Position(e.Pos).String())
msg = e.Msg
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 == source.TypeError)
if err != nil {
return nil, err
}
return &source.Error{
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) {
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
}
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()
if offset < len(data) {
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)
}
// parseGoListError attempts to parse a standard `go list` error message
// by stripping off the trailing error message.
//
// It works only on errors whose message is prefixed by colon,
// followed by a space (": "). For example:
//
// attributes.go:13:1: expected 'package', found 'type'
//
func parseGoListError(input string) span.Span {
input = strings.TrimSpace(input)
msgIndex := strings.Index(input, ": ")
if msgIndex < 0 {
return span.Parse(input)
}
return span.Parse(input[:msgIndex])
}