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 case extendedError: 98 perr := e.primary 99 msg = perr.Msg 100 kind = source.TypeError 101 if !perr.Pos.IsValid() { 102 return nil, fmt.Errorf("invalid position for type error %v", e) 103 } 104 spn, err = typeErrorRange(snapshot, fset, pkg, perr.Pos) 105 if err != nil { 106 return nil, err 107 } 108 for _, s := range e.secondaries { 109 var x source.RelatedInformation 110 x.Message = s.Msg 111 xspn, err := typeErrorRange(snapshot, fset, pkg, s.Pos) 112 if err != nil { 113 return nil, fmt.Errorf("invalid position for type error %v", s) 114 } 115 x.URI = xspn.URI() 116 rng, err := spanToRange(snapshot, pkg, xspn) 117 if err != nil { 118 return nil, err 119 } 120 x.Range = rng 121 related = append(related, x) 122 } 123 case *analysis.Diagnostic: 124 spn, err = span.NewRange(fset, e.Pos, e.End).Span() 125 if err != nil { 126 return nil, err 127 } 128 msg = e.Message 129 kind = source.Analysis 130 category = e.Category 131 fixes, err = suggestedAnalysisFixes(snapshot, pkg, e) 132 if err != nil { 133 return nil, err 134 } 135 related, err = relatedInformation(snapshot, pkg, e) 136 if err != nil { 137 return nil, err 138 } 139 default: 140 panic(fmt.Sprintf("%T unexpected", e)) 141 } 142 rng, err := spanToRange(snapshot, pkg, spn) 143 if err != nil { 144 return nil, err 145 } 146 return &source.Error{ 147 URI: spn.URI(), 148 Range: rng, 149 Message: msg, 150 Kind: kind, 151 Category: category, 152 SuggestedFixes: fixes, 153 Related: related, 154 }, nil 155} 156 157func suggestedAnalysisFixes(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() 163 if err != nil { 164 return nil, err 165 } 166 rng, err := spanToRange(snapshot, pkg, spn) 167 if err != nil { 168 return nil, err 169 } 170 edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{ 171 Range: rng, 172 NewText: string(e.NewText), 173 }) 174 } 175 fixes = append(fixes, source.SuggestedFix{ 176 Title: fix.Message, 177 Edits: edits, 178 }) 179 } 180 return fixes, nil 181} 182 183func 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() 187 if err != nil { 188 return nil, err 189 } 190 rng, err := spanToRange(snapshot, pkg, spn) 191 if err != nil { 192 return nil, err 193 } 194 out = append(out, source.RelatedInformation{ 195 URI: spn.URI(), 196 Range: rng, 197 Message: related.Message, 198 }) 199 } 200 return out, nil 201} 202 203func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind { 204 switch kind { 205 case packages.ListError: 206 return source.ListError 207 case packages.ParseError: 208 return source.ParseError 209 case packages.TypeError: 210 return source.TypeError 211 default: 212 return source.UnknownError 213 } 214} 215 216func 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)) 219 if err != nil { 220 return span.Span{}, err 221 } 222 return span.Range{ 223 FileSet: fset, 224 Start: pos, 225 End: analysisinternal.TypeErrorEndPos(fset, pgf.Src, pos), 226 Converter: pgf.Mapper.Converter, 227 }.Span() 228} 229 230func 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)) 233 if err != nil { 234 return span.Span{}, err 235 } 236 pos := pgf.Tok.Pos(posn.Offset) 237 return span.NewRange(fset, pos, pos).Span() 238} 239 240// spanToRange converts a span.Span to a protocol.Range, 241// assuming that the span belongs to the package whose diagnostics are being computed. 242func spanToRange(snapshot *snapshot, pkg *pkg, spn span.Span) (protocol.Range, error) { 243 pgf, err := pkg.File(spn.URI()) 244 if err != nil { 245 return protocol.Range{}, err 246 } 247 return pgf.Mapper.Range(spn) 248} 249 250// parseGoListError attempts to parse a standard `go list` error message 251// by stripping off the trailing error message. 252// 253// It works only on errors whose message is prefixed by colon, 254// followed by a space (": "). For example: 255// 256// attributes.go:13:1: expected 'package', found 'type' 257// 258func parseGoListError(input string) span.Span { 259 input = strings.TrimSpace(input) 260 msgIndex := strings.Index(input, ": ") 261 if msgIndex < 0 { 262 return span.Parse(input) 263 } 264 return span.Parse(input[:msgIndex]) 265} 266 267func 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 272 } 273 msg := matches[1] 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 279 } 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() 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