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	case extendedError:
98		perr := e.primary
99		msg = perr.Msg
100		kind = source.TypeError
101		if !perr.Pos.IsValid() {
102			return nil, fmt.Errorf("invalid position for type error %v", e)
103		}
104		spn, err = typeErrorRange(snapshot, fset, pkg, perr.Pos)
105		if err != nil {
106			return nil, err
107		}
108		for _, s := range e.secondaries {
109			var x source.RelatedInformation
110			x.Message = s.Msg
111			xspn, err := typeErrorRange(snapshot, fset, pkg, s.Pos)
112			if err != nil {
113				return nil, fmt.Errorf("invalid position for type error %v", s)
114			}
115			x.URI = xspn.URI()
116			rng, err := spanToRange(snapshot, pkg, xspn)
117			if err != nil {
118				return nil, err
119			}
120			x.Range = rng
121			related = append(related, x)
122		}
123	case *analysis.Diagnostic:
124		spn, err = span.NewRange(fset, e.Pos, e.End).Span()
125		if err != nil {
126			return nil, err
127		}
128		msg = e.Message
129		kind = source.Analysis
130		category = e.Category
131		fixes, err = suggestedAnalysisFixes(snapshot, pkg, e)
132		if err != nil {
133			return nil, err
134		}
135		related, err = relatedInformation(snapshot, pkg, e)
136		if err != nil {
137			return nil, err
138		}
139	default:
140		panic(fmt.Sprintf("%T unexpected", e))
141	}
142	rng, err := spanToRange(snapshot, pkg, spn)
143	if err != nil {
144		return nil, err
145	}
146	return &source.Error{
147		URI:            spn.URI(),
148		Range:          rng,
149		Message:        msg,
150		Kind:           kind,
151		Category:       category,
152		SuggestedFixes: fixes,
153		Related:        related,
154	}, nil
155}
156
157func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
158	var fixes []source.SuggestedFix
159	for _, fix := range diag.SuggestedFixes {
160		edits := make(map[span.URI][]protocol.TextEdit)
161		for _, e := range fix.TextEdits {
162			spn, err := span.NewRange(snapshot.view.session.cache.fset, e.Pos, e.End).Span()
163			if err != nil {
164				return nil, err
165			}
166			rng, err := spanToRange(snapshot, pkg, spn)
167			if err != nil {
168				return nil, err
169			}
170			edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
171				Range:   rng,
172				NewText: string(e.NewText),
173			})
174		}
175		fixes = append(fixes, source.SuggestedFix{
176			Title: fix.Message,
177			Edits: edits,
178		})
179	}
180	return fixes, nil
181}
182
183func relatedInformation(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
184	var out []source.RelatedInformation
185	for _, related := range diag.Related {
186		spn, err := span.NewRange(snapshot.view.session.cache.fset, related.Pos, related.End).Span()
187		if err != nil {
188			return nil, err
189		}
190		rng, err := spanToRange(snapshot, pkg, spn)
191		if err != nil {
192			return nil, err
193		}
194		out = append(out, source.RelatedInformation{
195			URI:     spn.URI(),
196			Range:   rng,
197			Message: related.Message,
198		})
199	}
200	return out, nil
201}
202
203func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind {
204	switch kind {
205	case packages.ListError:
206		return source.ListError
207	case packages.ParseError:
208		return source.ParseError
209	case packages.TypeError:
210		return source.TypeError
211	default:
212		return source.UnknownError
213	}
214}
215
216func typeErrorRange(snapshot *snapshot, fset *token.FileSet, pkg *pkg, pos token.Pos) (span.Span, error) {
217	posn := fset.Position(pos)
218	pgf, err := pkg.File(span.URIFromPath(posn.Filename))
219	if err != nil {
220		return span.Span{}, err
221	}
222	return span.Range{
223		FileSet:   fset,
224		Start:     pos,
225		End:       analysisinternal.TypeErrorEndPos(fset, pgf.Src, pos),
226		Converter: pgf.Mapper.Converter,
227	}.Span()
228}
229
230func scannerErrorRange(snapshot *snapshot, pkg *pkg, posn token.Position) (span.Span, error) {
231	fset := snapshot.view.session.cache.fset
232	pgf, err := pkg.File(span.URIFromPath(posn.Filename))
233	if err != nil {
234		return span.Span{}, err
235	}
236	pos := pgf.Tok.Pos(posn.Offset)
237	return span.NewRange(fset, pos, pos).Span()
238}
239
240// spanToRange converts a span.Span to a protocol.Range,
241// assuming that the span belongs to the package whose diagnostics are being computed.
242func spanToRange(snapshot *snapshot, pkg *pkg, spn span.Span) (protocol.Range, error) {
243	pgf, err := pkg.File(spn.URI())
244	if err != nil {
245		return protocol.Range{}, err
246	}
247	return pgf.Mapper.Range(spn)
248}
249
250// parseGoListError attempts to parse a standard `go list` error message
251// by stripping off the trailing error message.
252//
253// It works only on errors whose message is prefixed by colon,
254// followed by a space (": "). For example:
255//
256//   attributes.go:13:1: expected 'package', found 'type'
257//
258func parseGoListError(input string) span.Span {
259	input = strings.TrimSpace(input)
260	msgIndex := strings.Index(input, ": ")
261	if msgIndex < 0 {
262		return span.Parse(input)
263	}
264	return span.Parse(input[:msgIndex])
265}
266
267func parseGoListImportCycleError(ctx context.Context, snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) {
268	re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`)
269	matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg))
270	if len(matches) < 3 {
271		return e.Msg, span.Span{}, false
272	}
273	msg := matches[1]
274	importList := strings.Split(matches[2], " ")
275	// Since the error is relative to the current package. The import that is causing
276	// the import cycle error is the second one in the list.
277	if len(importList) < 2 {
278		return msg, span.Span{}, false
279	}
280	// Imports have quotation marks around them.
281	circImp := strconv.Quote(importList[1])
282	for _, cgf := range pkg.compiledGoFiles {
283		// Search file imports for the import that is causing the import cycle.
284		for _, imp := range cgf.File.Imports {
285			if imp.Path.Value == circImp {
286				spn, err := span.NewRange(snapshot.view.session.cache.fset, imp.Pos(), imp.End()).Span()
287				if err != nil {
288					return msg, span.Span{}, false
289				}
290				return msg, spn, true
291			}
292		}
293	}
294	return msg, span.Span{}, false
295}
296