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.
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"
28 func sourceError(ctx context.Context, snapshot *snapshot, pkg *pkg, e interface{}) (*source.Error, error) {
29 fset := snapshot.view.session.cache.fset
35 fixes []source.SuggestedFix
36 related []source.RelatedInformation
38 switch e := e.(type) {
40 kind = toSourceErrorKind(e.Kind)
42 if msg, spn, ok = parseGoListImportCycleError(ctx, snapshot, e, pkg); ok {
43 kind = source.TypeError
47 spn = parseGoListError(e.Msg)
49 // We may not have been able to parse a valid span.
50 if _, err := spanToRange(snapshot, pkg, spn); err != nil {
58 spn = span.Parse(e.Pos)
62 kind = source.ParseError
63 spn, err = scannerErrorRange(snapshot, pkg, e.Pos)
68 event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID()))
69 spn = span.Parse(e.Pos.String())
72 case scanner.ErrorList:
73 // The first parser error is likely the root cause of the problem.
75 return nil, errors.Errorf("no errors in %v", e)
78 kind = source.ParseError
79 spn, err = scannerErrorRange(snapshot, pkg, e[0].Pos)
84 event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID()))
85 spn = span.Parse(e[0].Pos.String())
89 kind = source.TypeError
91 return nil, fmt.Errorf("invalid position for type error %v", e)
93 spn, err = typeErrorRange(snapshot, fset, pkg, e.Pos)
100 kind = source.TypeError
101 if !perr.Pos.IsValid() {
102 return nil, fmt.Errorf("invalid position for type error %v", e)
104 spn, err = typeErrorRange(snapshot, fset, pkg, perr.Pos)
108 for _, s := range e.secondaries {
109 var x source.RelatedInformation
111 xspn, err := typeErrorRange(snapshot, fset, pkg, s.Pos)
113 return nil, fmt.Errorf("invalid position for type error %v", s)
116 rng, err := spanToRange(snapshot, pkg, xspn)
121 related = append(related, x)
123 case *analysis.Diagnostic:
124 spn, err = span.NewRange(fset, e.Pos, e.End).Span()
129 kind = source.Analysis
130 category = e.Category
131 fixes, err = suggestedFixes(snapshot, pkg, e)
135 related, err = relatedInformation(snapshot, pkg, e)
140 panic(fmt.Sprintf("%T unexpected", e))
142 rng, err := spanToRange(snapshot, pkg, spn)
146 return &source.Error{
152 SuggestedFixes: fixes,
157 func suggestedFixes(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()
166 rng, err := spanToRange(snapshot, pkg, spn)
170 edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
172 NewText: string(e.NewText),
175 fixes = append(fixes, source.SuggestedFix{
183 func 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()
190 rng, err := spanToRange(snapshot, pkg, spn)
194 out = append(out, source.RelatedInformation{
197 Message: related.Message,
203 func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind {
205 case packages.ListError:
206 return source.ListError
207 case packages.ParseError:
208 return source.ParseError
209 case packages.TypeError:
210 return source.TypeError
212 return source.UnknownError
216 func 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))
220 return span.Span{}, err
225 End: analysisinternal.TypeErrorEndPos(fset, pgf.Src, pos),
226 Converter: pgf.Mapper.Converter,
230 func 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))
234 return span.Span{}, err
236 pos := pgf.Tok.Pos(posn.Offset)
237 return span.NewRange(fset, pos, pos).Span()
240 // spanToRange converts a span.Span to a protocol.Range,
241 // assuming that the span belongs to the package whose diagnostics are being computed.
242 func spanToRange(snapshot *snapshot, pkg *pkg, spn span.Span) (protocol.Range, error) {
243 pgf, err := pkg.File(spn.URI())
245 return protocol.Range{}, err
247 return pgf.Mapper.Range(spn)
250 // parseGoListError attempts to parse a standard `go list` error message
251 // by stripping off the trailing error message.
253 // It works only on errors whose message is prefixed by colon,
254 // followed by a space (": "). For example:
256 // attributes.go:13:1: expected 'package', found 'type'
258 func parseGoListError(input string) span.Span {
259 input = strings.TrimSpace(input)
260 msgIndex := strings.Index(input, ": ")
262 return span.Parse(input)
264 return span.Parse(input[:msgIndex])
267 func 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
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
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()
288 return msg, span.Span{}, false
290 return msg, spn, true
294 return msg, span.Span{}, false