1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package cache
6
7import (
8	"context"
9	"fmt"
10	"go/scanner"
11	"go/token"
12	"go/types"
13	"regexp"
14	"strconv"
15	"strings"
16
17	"golang.org/x/tools/go/analysis"
18	"golang.org/x/tools/go/packages"
19	"golang.org/x/tools/internal/analysisinternal"
20	"golang.org/x/tools/internal/event"
21	"golang.org/x/tools/internal/lsp/debug/tag"
22	"golang.org/x/tools/internal/lsp/protocol"
23	"golang.org/x/tools/internal/lsp/source"
24	"golang.org/x/tools/internal/span"
25	errors "golang.org/x/xerrors"
26)
27
28func sourceError(ctx context.Context, snapshot *snapshot, pkg *pkg, e interface{}) (*source.Error, error) {
29	fset := snapshot.view.session.cache.fset
30	var (
31		spn           span.Span
32		err           error
33		msg, category string
34		kind          source.ErrorKind
35		fixes         []source.SuggestedFix
36		related       []source.RelatedInformation
37	)
38	switch e := e.(type) {
39	case packages.Error:
40		kind = toSourceErrorKind(e.Kind)
41		var ok bool
42		if msg, spn, ok = parseGoListImportCycleError(ctx, snapshot, e, pkg); ok {
43			kind = source.TypeError
44			break
45		}
46		if e.Pos == "" {
47			spn = parseGoListError(e.Msg)
48
49			// We may not have been able to parse a valid span.
50			if _, err := spanToRange(snapshot, pkg, spn); err != nil {
51				return &source.Error{
52					URI:     spn.URI(),
53					Message: msg,
54					Kind:    kind,
55				}, nil
56			}
57		} else {
58			spn = span.Parse(e.Pos)
59		}
60	case *scanner.Error:
61		msg = e.Msg
62		kind = source.ParseError
63		spn, err = scannerErrorRange(snapshot, pkg, e.Pos)
64		if err != nil {
65			if ctx.Err() != nil {
66				return nil, ctx.Err()
67			}
68			event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID()))
69			spn = span.Parse(e.Pos.String())
70		}
71
72	case scanner.ErrorList:
73		// The first parser error is likely the root cause of the problem.
74		if e.Len() <= 0 {
75			return nil, errors.Errorf("no errors in %v", e)
76		}
77		msg = e[0].Msg
78		kind = source.ParseError
79		spn, err = scannerErrorRange(snapshot, pkg, e[0].Pos)
80		if err != nil {
81			if ctx.Err() != nil {
82				return nil, ctx.Err()
83			}
84			event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID()))
85			spn = span.Parse(e[0].Pos.String())
86		}
87	case types.Error:
88		msg = e.Msg
89		kind = source.TypeError
90		if !e.Pos.IsValid() {
91			return nil, fmt.Errorf("invalid position for type error %v", e)
92		}
93		spn, err = typeErrorRange(snapshot, fset, pkg, e.Pos)
94		if err != nil {
95			return nil, err
96		}
97
98	case *analysis.Diagnostic:
99		spn, err = span.NewRange(fset, e.Pos, e.End).Span()
100		if err != nil {
101			return nil, err
102		}
103		msg = e.Message
104		kind = source.Analysis
105		category = e.Category
106		fixes, err = suggestedFixes(snapshot, pkg, e)
107		if err != nil {
108			return nil, err
109		}
110		related, err = relatedInformation(snapshot, pkg, e)
111		if err != nil {
112			return nil, err
113		}
114	}
115	rng, err := spanToRange(snapshot, pkg, spn)
116	if err != nil {
117		return nil, err
118	}
119	return &source.Error{
120		URI:            spn.URI(),
121		Range:          rng,
122		Message:        msg,
123		Kind:           kind,
124		Category:       category,
125		SuggestedFixes: fixes,
126		Related:        related,
127	}, nil
128}
129
130func suggestedFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
131	var fixes []source.SuggestedFix
132	for _, fix := range diag.SuggestedFixes {
133		edits := make(map[span.URI][]protocol.TextEdit)
134		for _, e := range fix.TextEdits {
135			spn, err := span.NewRange(snapshot.view.session.cache.fset, e.Pos, e.End).Span()
136			if err != nil {
137				return nil, err
138			}
139			rng, err := spanToRange(snapshot, pkg, spn)
140			if err != nil {
141				return nil, err
142			}
143			edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
144				Range:   rng,
145				NewText: string(e.NewText),
146			})
147		}
148		fixes = append(fixes, source.SuggestedFix{
149			Title: fix.Message,
150			Edits: edits,
151		})
152	}
153	return fixes, nil
154}
155
156func relatedInformation(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
157	var out []source.RelatedInformation
158	for _, related := range diag.Related {
159		spn, err := span.NewRange(snapshot.view.session.cache.fset, related.Pos, related.End).Span()
160		if err != nil {
161			return nil, err
162		}
163		rng, err := spanToRange(snapshot, pkg, spn)
164		if err != nil {
165			return nil, err
166		}
167		out = append(out, source.RelatedInformation{
168			URI:     spn.URI(),
169			Range:   rng,
170			Message: related.Message,
171		})
172	}
173	return out, nil
174}
175
176func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind {
177	switch kind {
178	case packages.ListError:
179		return source.ListError
180	case packages.ParseError:
181		return source.ParseError
182	case packages.TypeError:
183		return source.TypeError
184	default:
185		return source.UnknownError
186	}
187}
188
189func typeErrorRange(snapshot *snapshot, fset *token.FileSet, pkg *pkg, pos token.Pos) (span.Span, error) {
190	posn := fset.Position(pos)
191	pgf, err := pkg.File(span.URIFromPath(posn.Filename))
192	if err != nil {
193		return span.Span{}, err
194	}
195	return span.Range{
196		FileSet:   fset,
197		Start:     pos,
198		End:       analysisinternal.TypeErrorEndPos(fset, pgf.Src, pos),
199		Converter: pgf.Mapper.Converter,
200	}.Span()
201}
202
203func scannerErrorRange(snapshot *snapshot, pkg *pkg, posn token.Position) (span.Span, error) {
204	fset := snapshot.view.session.cache.fset
205	pgf, err := pkg.File(span.URIFromPath(posn.Filename))
206	if err != nil {
207		return span.Span{}, err
208	}
209	pos := pgf.Tok.Pos(posn.Offset)
210	return span.NewRange(fset, pos, pos).Span()
211}
212
213// spanToRange converts a span.Span to a protocol.Range,
214// assuming that the span belongs to the package whose diagnostics are being computed.
215func spanToRange(snapshot *snapshot, pkg *pkg, spn span.Span) (protocol.Range, error) {
216	pgf, err := pkg.File(spn.URI())
217	if err != nil {
218		return protocol.Range{}, err
219	}
220	return pgf.Mapper.Range(spn)
221}
222
223// parseGoListError attempts to parse a standard `go list` error message
224// by stripping off the trailing error message.
225//
226// It works only on errors whose message is prefixed by colon,
227// followed by a space (": "). For example:
228//
229//   attributes.go:13:1: expected 'package', found 'type'
230//
231func parseGoListError(input string) span.Span {
232	input = strings.TrimSpace(input)
233	msgIndex := strings.Index(input, ": ")
234	if msgIndex < 0 {
235		return span.Parse(input)
236	}
237	return span.Parse(input[:msgIndex])
238}
239
240func parseGoListImportCycleError(ctx context.Context, snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) {
241	re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`)
242	matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg))
243	if len(matches) < 3 {
244		return e.Msg, span.Span{}, false
245	}
246	msg := matches[1]
247	importList := strings.Split(matches[2], " ")
248	// Since the error is relative to the current package. The import that is causing
249	// the import cycle error is the second one in the list.
250	if len(importList) < 2 {
251		return msg, span.Span{}, false
252	}
253	// Imports have quotation marks around them.
254	circImp := strconv.Quote(importList[1])
255	for _, cgf := range pkg.compiledGoFiles {
256		// Search file imports for the import that is causing the import cycle.
257		for _, imp := range cgf.File.Imports {
258			if imp.Path.Value == circImp {
259				spn, err := span.NewRange(snapshot.view.session.cache.fset, imp.Pos(), imp.End()).Span()
260				if err != nil {
261					return msg, span.Span{}, false
262				}
263				return msg, spn, true
264			}
265		}
266	}
267	return msg, span.Span{}, false
268}
269