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 "golang.org/x/tools/internal/typesinternal"
26 errors "golang.org/x/xerrors"
29 func sourceError(ctx context.Context, snapshot *snapshot, pkg *pkg, e interface{}) (*source.Error, error) {
30 fset := snapshot.view.session.cache.fset
35 code typesinternal.ErrorCode
37 fixes []source.SuggestedFix
38 related []source.RelatedInformation
40 switch e := e.(type) {
42 kind = toSourceErrorKind(e.Kind)
44 if msg, spn, ok = parseGoListImportCycleError(ctx, snapshot, e, pkg); ok {
45 kind = source.TypeError
49 spn = parseGoListError(e.Msg)
51 // We may not have been able to parse a valid span.
52 if _, err := spanToRange(snapshot, pkg, spn); err != nil {
60 spn = span.Parse(e.Pos)
64 kind = source.ParseError
65 spn, err = scannerErrorRange(snapshot, pkg, e.Pos)
70 event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID()))
71 spn = span.Parse(e.Pos.String())
74 case scanner.ErrorList:
75 // The first parser error is likely the root cause of the problem.
77 return nil, errors.Errorf("no errors in %v", e)
80 kind = source.ParseError
81 spn, err = scannerErrorRange(snapshot, pkg, e[0].Pos)
86 event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID()))
87 spn = span.Parse(e[0].Pos.String())
91 kind = source.TypeError
93 return nil, fmt.Errorf("invalid position for type error %v", e)
95 code, spn, err = typeErrorData(fset, pkg, e)
102 kind = source.TypeError
103 if !perr.Pos.IsValid() {
104 return nil, fmt.Errorf("invalid position for type error %v", e)
106 code, spn, err = typeErrorData(fset, pkg, e.primary)
110 for _, s := range e.secondaries {
111 var x source.RelatedInformation
113 _, xspn, err := typeErrorData(fset, pkg, s)
115 return nil, fmt.Errorf("invalid position for type error %v", s)
118 rng, err := spanToRange(snapshot, pkg, xspn)
123 related = append(related, x)
125 case *analysis.Diagnostic:
126 spn, err = span.NewRange(fset, e.Pos, e.End).Span()
131 kind = source.Analysis
132 category = e.Category
133 fixes, err = suggestedAnalysisFixes(snapshot, pkg, e)
137 related, err = relatedInformation(snapshot, pkg, e)
142 panic(fmt.Sprintf("%T unexpected", e))
144 rng, err := spanToRange(snapshot, pkg, spn)
154 SuggestedFixes: fixes,
158 se.Code = code.String()
159 se.CodeHref = typesCodeHref(snapshot, code)
164 func 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())
169 func 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()
178 rng, err := spanToRange(snapshot, pkg, spn)
182 edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
184 NewText: string(e.NewText),
187 fixes = append(fixes, source.SuggestedFix{
195 func 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()
202 rng, err := spanToRange(snapshot, pkg, spn)
206 out = append(out, source.RelatedInformation{
209 Message: related.Message,
215 func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind {
217 case packages.ListError:
218 return source.ListError
219 case packages.ParseError:
220 return source.ParseError
221 case packages.TypeError:
222 return source.TypeError
224 return source.UnknownError
228 func typeErrorData(fset *token.FileSet, pkg *pkg, terr types.Error) (typesinternal.ErrorCode, span.Span, error) {
229 ecode, start, end, ok := typesinternal.ReadGo116ErrorData(terr)
231 start, end = terr.Pos, terr.Pos
234 posn := fset.Position(start)
235 pgf, err := pkg.File(span.URIFromPath(posn.Filename))
237 return 0, span.Span{}, err
239 if !end.IsValid() || end == start {
240 end = analysisinternal.TypeErrorEndPos(fset, pgf.Src, start)
242 spn, err := parsedGoSpan(pgf, start, end)
244 return 0, span.Span{}, err
246 return ecode, spn, nil
249 func parsedGoSpan(pgf *source.ParsedGoFile, start, end token.Pos) (span.Span, error) {
250 return span.FileSpan(pgf.Tok, pgf.Mapper.Converter, start, end)
253 func 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))
257 return span.Span{}, err
259 pos := pgf.Tok.Pos(posn.Offset)
260 return span.NewRange(fset, pos, pos).Span()
263 // spanToRange converts a span.Span to a protocol.Range,
264 // assuming that the span belongs to the package whose diagnostics are being computed.
265 func spanToRange(snapshot *snapshot, pkg *pkg, spn span.Span) (protocol.Range, error) {
266 pgf, err := pkg.File(spn.URI())
268 return protocol.Range{}, err
270 return pgf.Mapper.Range(spn)
273 // parseGoListError attempts to parse a standard `go list` error message
274 // by stripping off the trailing error message.
276 // It works only on errors whose message is prefixed by colon,
277 // followed by a space (": "). For example:
279 // attributes.go:13:1: expected 'package', found 'type'
281 func parseGoListError(input string) span.Span {
282 input = strings.TrimSpace(input)
283 msgIndex := strings.Index(input, ": ")
285 return span.Parse(input)
287 return span.Parse(input[:msgIndex])
290 func 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
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
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()
311 return msg, span.Span{}, false
313 return msg, spn, true
317 return msg, span.Span{}, false