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 "golang.org/x/tools/internal/typesinternal" 26 errors "golang.org/x/xerrors" 27) 28 29func sourceError(ctx context.Context, snapshot *snapshot, pkg *pkg, e interface{}) (*source.Error, error) { 30 fset := snapshot.view.session.cache.fset 31 var ( 32 spn span.Span 33 err error 34 msg, category string 35 code typesinternal.ErrorCode 36 kind source.ErrorKind 37 fixes []source.SuggestedFix 38 related []source.RelatedInformation 39 ) 40 switch e := e.(type) { 41 case packages.Error: 42 kind = toSourceErrorKind(e.Kind) 43 var ok bool 44 if msg, spn, ok = parseGoListImportCycleError(ctx, snapshot, e, pkg); ok { 45 kind = source.TypeError 46 break 47 } 48 if e.Pos == "" { 49 spn = parseGoListError(e.Msg) 50 51 // We may not have been able to parse a valid span. 52 if _, err := spanToRange(snapshot, pkg, spn); err != nil { 53 return &source.Error{ 54 URI: spn.URI(), 55 Message: msg, 56 Kind: kind, 57 }, nil 58 } 59 } else { 60 spn = span.Parse(e.Pos) 61 } 62 case *scanner.Error: 63 msg = e.Msg 64 kind = source.ParseError 65 spn, err = scannerErrorRange(snapshot, pkg, e.Pos) 66 if err != nil { 67 if ctx.Err() != nil { 68 return nil, ctx.Err() 69 } 70 event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID())) 71 spn = span.Parse(e.Pos.String()) 72 } 73 74 case scanner.ErrorList: 75 // The first parser error is likely the root cause of the problem. 76 if e.Len() <= 0 { 77 return nil, errors.Errorf("no errors in %v", e) 78 } 79 msg = e[0].Msg 80 kind = source.ParseError 81 spn, err = scannerErrorRange(snapshot, pkg, e[0].Pos) 82 if err != nil { 83 if ctx.Err() != nil { 84 return nil, ctx.Err() 85 } 86 event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID())) 87 spn = span.Parse(e[0].Pos.String()) 88 } 89 case types.Error: 90 msg = e.Msg 91 kind = source.TypeError 92 if !e.Pos.IsValid() { 93 return nil, fmt.Errorf("invalid position for type error %v", e) 94 } 95 code, spn, err = typeErrorData(fset, pkg, e) 96 if err != nil { 97 return nil, err 98 } 99 case extendedError: 100 perr := e.primary 101 msg = perr.Msg 102 kind = source.TypeError 103 if !perr.Pos.IsValid() { 104 return nil, fmt.Errorf("invalid position for type error %v", e) 105 } 106 code, spn, err = typeErrorData(fset, pkg, e.primary) 107 if err != nil { 108 return nil, err 109 } 110 for _, s := range e.secondaries { 111 var x source.RelatedInformation 112 x.Message = s.Msg 113 _, xspn, err := typeErrorData(fset, pkg, s) 114 if err != nil { 115 return nil, fmt.Errorf("invalid position for type error %v", s) 116 } 117 x.URI = xspn.URI() 118 rng, err := spanToRange(snapshot, pkg, xspn) 119 if err != nil { 120 return nil, err 121 } 122 x.Range = rng 123 related = append(related, x) 124 } 125 case *analysis.Diagnostic: 126 spn, err = span.NewRange(fset, e.Pos, e.End).Span() 127 if err != nil { 128 return nil, err 129 } 130 msg = e.Message 131 kind = source.Analysis 132 category = e.Category 133 fixes, err = suggestedAnalysisFixes(snapshot, pkg, e) 134 if err != nil { 135 return nil, err 136 } 137 related, err = relatedInformation(snapshot, pkg, e) 138 if err != nil { 139 return nil, err 140 } 141 default: 142 panic(fmt.Sprintf("%T unexpected", e)) 143 } 144 rng, err := spanToRange(snapshot, pkg, spn) 145 if err != nil { 146 return nil, err 147 } 148 se := &source.Error{ 149 URI: spn.URI(), 150 Range: rng, 151 Message: msg, 152 Kind: kind, 153 Category: category, 154 SuggestedFixes: fixes, 155 Related: related, 156 } 157 if code != 0 { 158 se.Code = code.String() 159 se.CodeHref = typesCodeHref(snapshot, code) 160 } 161 return se, nil 162} 163 164func 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()) 167} 168 169func 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() 175 if err != nil { 176 return nil, err 177 } 178 rng, err := spanToRange(snapshot, pkg, spn) 179 if err != nil { 180 return nil, err 181 } 182 edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{ 183 Range: rng, 184 NewText: string(e.NewText), 185 }) 186 } 187 fixes = append(fixes, source.SuggestedFix{ 188 Title: fix.Message, 189 Edits: edits, 190 }) 191 } 192 return fixes, nil 193} 194 195func 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() 199 if err != nil { 200 return nil, err 201 } 202 rng, err := spanToRange(snapshot, pkg, spn) 203 if err != nil { 204 return nil, err 205 } 206 out = append(out, source.RelatedInformation{ 207 URI: spn.URI(), 208 Range: rng, 209 Message: related.Message, 210 }) 211 } 212 return out, nil 213} 214 215func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind { 216 switch kind { 217 case packages.ListError: 218 return source.ListError 219 case packages.ParseError: 220 return source.ParseError 221 case packages.TypeError: 222 return source.TypeError 223 default: 224 return source.UnknownError 225 } 226} 227 228func typeErrorData(fset *token.FileSet, pkg *pkg, terr types.Error) (typesinternal.ErrorCode, span.Span, error) { 229 ecode, start, end, ok := typesinternal.ReadGo116ErrorData(terr) 230 if !ok { 231 start, end = terr.Pos, terr.Pos 232 ecode = 0 233 } 234 posn := fset.Position(start) 235 pgf, err := pkg.File(span.URIFromPath(posn.Filename)) 236 if err != nil { 237 return 0, span.Span{}, err 238 } 239 if !end.IsValid() || end == start { 240 end = analysisinternal.TypeErrorEndPos(fset, pgf.Src, start) 241 } 242 spn, err := parsedGoSpan(pgf, start, end) 243 if err != nil { 244 return 0, span.Span{}, err 245 } 246 return ecode, spn, nil 247} 248 249func parsedGoSpan(pgf *source.ParsedGoFile, start, end token.Pos) (span.Span, error) { 250 return span.FileSpan(pgf.Tok, pgf.Mapper.Converter, start, end) 251} 252 253func 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)) 256 if err != nil { 257 return span.Span{}, err 258 } 259 pos := pgf.Tok.Pos(posn.Offset) 260 return span.NewRange(fset, pos, pos).Span() 261} 262 263// spanToRange converts a span.Span to a protocol.Range, 264// assuming that the span belongs to the package whose diagnostics are being computed. 265func spanToRange(snapshot *snapshot, pkg *pkg, spn span.Span) (protocol.Range, error) { 266 pgf, err := pkg.File(spn.URI()) 267 if err != nil { 268 return protocol.Range{}, err 269 } 270 return pgf.Mapper.Range(spn) 271} 272 273// parseGoListError attempts to parse a standard `go list` error message 274// by stripping off the trailing error message. 275// 276// It works only on errors whose message is prefixed by colon, 277// followed by a space (": "). For example: 278// 279// attributes.go:13:1: expected 'package', found 'type' 280// 281func parseGoListError(input string) span.Span { 282 input = strings.TrimSpace(input) 283 msgIndex := strings.Index(input, ": ") 284 if msgIndex < 0 { 285 return span.Parse(input) 286 } 287 return span.Parse(input[:msgIndex]) 288} 289 290func 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 295 } 296 msg := matches[1] 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 302 } 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() 310 if err != nil { 311 return msg, span.Span{}, false 312 } 313 return msg, spn, true 314 } 315 } 316 } 317 return msg, span.Span{}, false 318} 319