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