blob: c8238a8eea6af4b489c40bfb800f116a03fb02e4 [file] [log] [blame]
package cache
import (
"bytes"
"context"
"go/scanner"
"go/types"
"strings"
"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) {
var (
spn span.Span
msg string
kind packages.ErrorKind
)
switch e := e.(type) {
case packages.Error:
if e.Pos == "" {
spn = parseGoListError(e.Msg)
} else {
spn = span.Parse(e.Pos)
}
msg = e.Msg
kind = e.Kind
case *scanner.Error:
msg = e.Msg
kind = packages.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
}
case types.Error:
spn = span.Parse(view.session.cache.fset.Position(e.Pos).String())
msg = e.Msg
kind = packages.TypeError
}
rng, err := spanToRange(ctx, pkg, spn, kind == packages.TypeError)
if err != nil {
return nil, err
}
return &source.Error{
URI: spn.URI(),
Range: rng,
Msg: msg,
Kind: kind,
}, 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, 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
}
data, _, err := ph.File().Read(ctx)
if err != nil {
return protocol.Range{}, err
}
if spn.IsPoint() && isTypeError {
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])
}