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