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]) }