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	"golang.org/x/tools/internal/typesinternal"
26	errors "golang.org/x/xerrors"
27)
28
29func sourceError(ctx context.Context, snapshot *snapshot, pkg *pkg, e interface{}) (*source.Error, error) {
30	fset := snapshot.view.session.cache.fset
31	var (
32		spn           span.Span
33		err           error
34		msg, category string
35		code          typesinternal.ErrorCode
36		kind          source.ErrorKind
37		fixes         []source.SuggestedFix
38		related       []source.RelatedInformation
39	)
40	switch e := e.(type) {
41	case packages.Error:
42		kind = toSourceErrorKind(e.Kind)
43		var ok bool
44		if msg, spn, ok = parseGoListImportCycleError(ctx, snapshot, e, pkg); ok {
45			kind = source.TypeError
46			break
47		}
48		if e.Pos == "" {
49			spn = parseGoListError(e.Msg)
50
51			// We may not have been able to parse a valid span.
52			if _, err := spanToRange(snapshot, pkg, spn); err != nil {
53				return &source.Error{
54					URI:     spn.URI(),
55					Message: msg,
56					Kind:    kind,
57				}, nil
58			}
59		} else {
60			spn = span.Parse(e.Pos)
61		}
62	case *scanner.Error:
63		msg = e.Msg
64		kind = source.ParseError
65		spn, err = scannerErrorRange(snapshot, pkg, e.Pos)
66		if err != nil {
67			if ctx.Err() != nil {
68				return nil, ctx.Err()
69			}
70			event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID()))
71			spn = span.Parse(e.Pos.String())
72		}
73
74	case scanner.ErrorList:
75		// The first parser error is likely the root cause of the problem.
76		if e.Len() <= 0 {
77			return nil, errors.Errorf("no errors in %v", e)
78		}
79		msg = e[0].Msg
80		kind = source.ParseError
81		spn, err = scannerErrorRange(snapshot, pkg, e[0].Pos)
82		if err != nil {
83			if ctx.Err() != nil {
84				return nil, ctx.Err()
85			}
86			event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID()))
87			spn = span.Parse(e[0].Pos.String())
88		}
89	case types.Error:
90		msg = e.Msg
91		kind = source.TypeError
92		if !e.Pos.IsValid() {
93			return nil, fmt.Errorf("invalid position for type error %v", e)
94		}
95		code, spn, err = typeErrorData(fset, pkg, e)
96		if err != nil {
97			return nil, err
98		}
99	case extendedError:
100		perr := e.primary
101		msg = perr.Msg
102		kind = source.TypeError
103		if !perr.Pos.IsValid() {
104			return nil, fmt.Errorf("invalid position for type error %v", e)
105		}
106		code, spn, err = typeErrorData(fset, pkg, e.primary)
107		if err != nil {
108			return nil, err
109		}
110		for _, s := range e.secondaries {
111			var x source.RelatedInformation
112			x.Message = s.Msg
113			_, xspn, err := typeErrorData(fset, pkg, s)
114			if err != nil {
115				return nil, fmt.Errorf("invalid position for type error %v", s)
116			}
117			x.URI = xspn.URI()
118			rng, err := spanToRange(snapshot, pkg, xspn)
119			if err != nil {
120				return nil, err
121			}
122			x.Range = rng
123			related = append(related, x)
124		}
125	case *analysis.Diagnostic:
126		spn, err = span.NewRange(fset, e.Pos, e.End).Span()
127		if err != nil {
128			return nil, err
129		}
130		msg = e.Message
131		kind = source.Analysis
132		category = e.Category
133		fixes, err = suggestedAnalysisFixes(snapshot, pkg, e)
134		if err != nil {
135			return nil, err
136		}
137		related, err = relatedInformation(snapshot, pkg, e)
138		if err != nil {
139			return nil, err
140		}
141	default:
142		panic(fmt.Sprintf("%T unexpected", e))
143	}
144	rng, err := spanToRange(snapshot, pkg, spn)
145	if err != nil {
146		return nil, err
147	}
148	se := &source.Error{
149		URI:            spn.URI(),
150		Range:          rng,
151		Message:        msg,
152		Kind:           kind,
153		Category:       category,
154		SuggestedFixes: fixes,
155		Related:        related,
156	}
157	if code != 0 {
158		se.Code = code.String()
159		se.CodeHref = typesCodeHref(snapshot, code)
160	}
161	return se, nil
162}
163
164func typesCodeHref(snapshot *snapshot, code typesinternal.ErrorCode) string {
165	target := snapshot.View().Options().LinkTarget
166	return fmt.Sprintf("%s/golang.org/x/tools/internal/typesinternal#%s", target, code.String())
167}
168
169func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
170	var fixes []source.SuggestedFix
171	for _, fix := range diag.SuggestedFixes {
172		edits := make(map[span.URI][]protocol.TextEdit)
173		for _, e := range fix.TextEdits {
174			spn, err := span.NewRange(snapshot.view.session.cache.fset, e.Pos, e.End).Span()
175			if err != nil {
176				return nil, err
177			}
178			rng, err := spanToRange(snapshot, pkg, spn)
179			if err != nil {
180				return nil, err
181			}
182			edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
183				Range:   rng,
184				NewText: string(e.NewText),
185			})
186		}
187		fixes = append(fixes, source.SuggestedFix{
188			Title: fix.Message,
189			Edits: edits,
190		})
191	}
192	return fixes, nil
193}
194
195func relatedInformation(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
196	var out []source.RelatedInformation
197	for _, related := range diag.Related {
198		spn, err := span.NewRange(snapshot.view.session.cache.fset, related.Pos, related.End).Span()
199		if err != nil {
200			return nil, err
201		}
202		rng, err := spanToRange(snapshot, pkg, spn)
203		if err != nil {
204			return nil, err
205		}
206		out = append(out, source.RelatedInformation{
207			URI:     spn.URI(),
208			Range:   rng,
209			Message: related.Message,
210		})
211	}
212	return out, nil
213}
214
215func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind {
216	switch kind {
217	case packages.ListError:
218		return source.ListError
219	case packages.ParseError:
220		return source.ParseError
221	case packages.TypeError:
222		return source.TypeError
223	default:
224		return source.UnknownError
225	}
226}
227
228func typeErrorData(fset *token.FileSet, pkg *pkg, terr types.Error) (typesinternal.ErrorCode, span.Span, error) {
229	ecode, start, end, ok := typesinternal.ReadGo116ErrorData(terr)
230	if !ok {
231		start, end = terr.Pos, terr.Pos
232		ecode = 0
233	}
234	posn := fset.Position(start)
235	pgf, err := pkg.File(span.URIFromPath(posn.Filename))
236	if err != nil {
237		return 0, span.Span{}, err
238	}
239	if !end.IsValid() || end == start {
240		end = analysisinternal.TypeErrorEndPos(fset, pgf.Src, start)
241	}
242	spn, err := parsedGoSpan(pgf, start, end)
243	if err != nil {
244		return 0, span.Span{}, err
245	}
246	return ecode, spn, nil
247}
248
249func parsedGoSpan(pgf *source.ParsedGoFile, start, end token.Pos) (span.Span, error) {
250	return span.FileSpan(pgf.Tok, pgf.Mapper.Converter, start, end)
251}
252
253func scannerErrorRange(snapshot *snapshot, pkg *pkg, posn token.Position) (span.Span, error) {
254	fset := snapshot.view.session.cache.fset
255	pgf, err := pkg.File(span.URIFromPath(posn.Filename))
256	if err != nil {
257		return span.Span{}, err
258	}
259	pos := pgf.Tok.Pos(posn.Offset)
260	return span.NewRange(fset, pos, pos).Span()
261}
262
263// spanToRange converts a span.Span to a protocol.Range,
264// assuming that the span belongs to the package whose diagnostics are being computed.
265func spanToRange(snapshot *snapshot, pkg *pkg, spn span.Span) (protocol.Range, error) {
266	pgf, err := pkg.File(spn.URI())
267	if err != nil {
268		return protocol.Range{}, err
269	}
270	return pgf.Mapper.Range(spn)
271}
272
273// parseGoListError attempts to parse a standard `go list` error message
274// by stripping off the trailing error message.
275//
276// It works only on errors whose message is prefixed by colon,
277// followed by a space (": "). For example:
278//
279//   attributes.go:13:1: expected 'package', found 'type'
280//
281func parseGoListError(input string) span.Span {
282	input = strings.TrimSpace(input)
283	msgIndex := strings.Index(input, ": ")
284	if msgIndex < 0 {
285		return span.Parse(input)
286	}
287	return span.Parse(input[:msgIndex])
288}
289
290func parseGoListImportCycleError(ctx context.Context, snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) {
291	re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`)
292	matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg))
293	if len(matches) < 3 {
294		return e.Msg, span.Span{}, false
295	}
296	msg := matches[1]
297	importList := strings.Split(matches[2], " ")
298	// Since the error is relative to the current package. The import that is causing
299	// the import cycle error is the second one in the list.
300	if len(importList) < 2 {
301		return msg, span.Span{}, false
302	}
303	// Imports have quotation marks around them.
304	circImp := strconv.Quote(importList[1])
305	for _, cgf := range pkg.compiledGoFiles {
306		// Search file imports for the import that is causing the import cycle.
307		for _, imp := range cgf.File.Imports {
308			if imp.Path.Value == circImp {
309				spn, err := span.NewRange(snapshot.view.session.cache.fset, imp.Pos(), imp.End()).Span()
310				if err != nil {
311					return msg, span.Span{}, false
312				}
313				return msg, spn, true
314			}
315		}
316	}
317	return msg, span.Span{}, false
318}
319