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