github.com/v2fly/tools@v0.100.0/internal/lsp/cache/errors.go (about) 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 5 package cache 6 7 import ( 8 "fmt" 9 "go/scanner" 10 "go/token" 11 "go/types" 12 "regexp" 13 "strconv" 14 "strings" 15 16 "github.com/v2fly/tools/go/analysis" 17 "github.com/v2fly/tools/go/packages" 18 "github.com/v2fly/tools/internal/analysisinternal" 19 "github.com/v2fly/tools/internal/lsp/command" 20 "github.com/v2fly/tools/internal/lsp/protocol" 21 "github.com/v2fly/tools/internal/lsp/source" 22 "github.com/v2fly/tools/internal/span" 23 "github.com/v2fly/tools/internal/typesinternal" 24 errors "golang.org/x/xerrors" 25 ) 26 27 func goPackagesErrorDiagnostics(snapshot *snapshot, pkg *pkg, e packages.Error) ([]*source.Diagnostic, error) { 28 if msg, spn, ok := parseGoListImportCycleError(snapshot, e, pkg); ok { 29 rng, err := spanToRange(pkg, spn) 30 if err != nil { 31 return nil, err 32 } 33 return []*source.Diagnostic{{ 34 URI: spn.URI(), 35 Range: rng, 36 Severity: protocol.SeverityError, 37 Source: source.TypeError, 38 Message: msg, 39 }}, nil 40 } 41 42 var spn span.Span 43 if e.Pos == "" { 44 spn = parseGoListError(e.Msg, pkg.m.config.Dir) 45 // We may not have been able to parse a valid span. Apply the errors to all files. 46 if _, err := spanToRange(pkg, spn); err != nil { 47 var diags []*source.Diagnostic 48 for _, cgf := range pkg.compiledGoFiles { 49 diags = append(diags, &source.Diagnostic{ 50 URI: cgf.URI, 51 Severity: protocol.SeverityError, 52 Source: source.ListError, 53 Message: e.Msg, 54 }) 55 } 56 return diags, nil 57 } 58 } else { 59 spn = span.ParseInDir(e.Pos, pkg.m.config.Dir) 60 } 61 62 rng, err := spanToRange(pkg, spn) 63 if err != nil { 64 return nil, err 65 } 66 return []*source.Diagnostic{{ 67 URI: spn.URI(), 68 Range: rng, 69 Severity: protocol.SeverityError, 70 Source: source.ListError, 71 Message: e.Msg, 72 }}, nil 73 } 74 75 func parseErrorDiagnostics(snapshot *snapshot, pkg *pkg, errList scanner.ErrorList) ([]*source.Diagnostic, error) { 76 // The first parser error is likely the root cause of the problem. 77 if errList.Len() <= 0 { 78 return nil, errors.Errorf("no errors in %v", errList) 79 } 80 e := errList[0] 81 pgf, err := pkg.File(span.URIFromPath(e.Pos.Filename)) 82 if err != nil { 83 return nil, err 84 } 85 pos := pgf.Tok.Pos(e.Pos.Offset) 86 spn, err := span.NewRange(snapshot.FileSet(), pos, pos).Span() 87 if err != nil { 88 return nil, err 89 } 90 rng, err := spanToRange(pkg, spn) 91 if err != nil { 92 return nil, err 93 } 94 return []*source.Diagnostic{{ 95 URI: spn.URI(), 96 Range: rng, 97 Severity: protocol.SeverityError, 98 Source: source.ParseError, 99 Message: e.Msg, 100 }}, nil 101 } 102 103 var importErrorRe = regexp.MustCompile(`could not import ([^\s]+)`) 104 105 func typeErrorDiagnostics(snapshot *snapshot, pkg *pkg, e extendedError) ([]*source.Diagnostic, error) { 106 code, spn, err := typeErrorData(snapshot.FileSet(), pkg, e.primary) 107 if err != nil { 108 return nil, err 109 } 110 rng, err := spanToRange(pkg, spn) 111 if err != nil { 112 return nil, err 113 } 114 diag := &source.Diagnostic{ 115 URI: spn.URI(), 116 Range: rng, 117 Severity: protocol.SeverityError, 118 Source: source.TypeError, 119 Message: e.primary.Msg, 120 } 121 if code != 0 { 122 diag.Code = code.String() 123 diag.CodeHref = typesCodeHref(snapshot, code) 124 } 125 126 for _, secondary := range e.secondaries { 127 _, secondarySpan, err := typeErrorData(snapshot.FileSet(), pkg, secondary) 128 if err != nil { 129 return nil, err 130 } 131 rng, err := spanToRange(pkg, secondarySpan) 132 if err != nil { 133 return nil, err 134 } 135 diag.Related = append(diag.Related, source.RelatedInformation{ 136 URI: secondarySpan.URI(), 137 Range: rng, 138 Message: secondary.Msg, 139 }) 140 } 141 142 if match := importErrorRe.FindStringSubmatch(e.primary.Msg); match != nil { 143 diag.SuggestedFixes, err = goGetQuickFixes(snapshot, spn.URI(), match[1]) 144 if err != nil { 145 return nil, err 146 } 147 } 148 return []*source.Diagnostic{diag}, nil 149 } 150 151 func goGetQuickFixes(snapshot *snapshot, uri span.URI, pkg string) ([]source.SuggestedFix, error) { 152 // Go get only supports module mode for now. 153 if snapshot.workspaceMode()&moduleMode == 0 { 154 return nil, nil 155 } 156 title := fmt.Sprintf("go get package %v", pkg) 157 cmd, err := command.NewGoGetPackageCommand(title, command.GoGetPackageArgs{ 158 URI: protocol.URIFromSpanURI(uri), 159 AddRequire: true, 160 Pkg: pkg, 161 }) 162 if err != nil { 163 return nil, err 164 } 165 return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)}, nil 166 } 167 168 func analysisDiagnosticDiagnostics(snapshot *snapshot, pkg *pkg, a *analysis.Analyzer, e *analysis.Diagnostic) ([]*source.Diagnostic, error) { 169 var srcAnalyzer *source.Analyzer 170 // Find the analyzer that generated this diagnostic. 171 for _, sa := range source.EnabledAnalyzers(snapshot) { 172 if a == sa.Analyzer { 173 srcAnalyzer = sa 174 break 175 } 176 } 177 178 spn, err := span.NewRange(snapshot.FileSet(), e.Pos, e.End).Span() 179 if err != nil { 180 return nil, err 181 } 182 rng, err := spanToRange(pkg, spn) 183 if err != nil { 184 return nil, err 185 } 186 kinds := srcAnalyzer.ActionKind 187 if len(srcAnalyzer.ActionKind) == 0 { 188 kinds = append(kinds, protocol.QuickFix) 189 } 190 fixes, err := suggestedAnalysisFixes(snapshot, pkg, e, kinds) 191 if err != nil { 192 return nil, err 193 } 194 if srcAnalyzer.Fix != "" { 195 cmd, err := command.NewApplyFixCommand(e.Message, command.ApplyFixArgs{ 196 URI: protocol.URIFromSpanURI(spn.URI()), 197 Range: rng, 198 Fix: srcAnalyzer.Fix, 199 }) 200 if err != nil { 201 return nil, err 202 } 203 for _, kind := range kinds { 204 fixes = append(fixes, source.SuggestedFixFromCommand(cmd, kind)) 205 } 206 } 207 related, err := relatedInformation(pkg, snapshot.FileSet(), e) 208 if err != nil { 209 return nil, err 210 } 211 diag := &source.Diagnostic{ 212 URI: spn.URI(), 213 Range: rng, 214 Severity: protocol.SeverityWarning, 215 Source: source.AnalyzerErrorKind(e.Category), 216 Message: e.Message, 217 Related: related, 218 SuggestedFixes: fixes, 219 Analyzer: srcAnalyzer, 220 } 221 // If the fixes only delete code, assume that the diagnostic is reporting dead code. 222 if onlyDeletions(fixes) { 223 diag.Tags = []protocol.DiagnosticTag{protocol.Unnecessary} 224 } 225 return []*source.Diagnostic{diag}, nil 226 } 227 228 // onlyDeletions returns true if all of the suggested fixes are deletions. 229 func onlyDeletions(fixes []source.SuggestedFix) bool { 230 for _, fix := range fixes { 231 for _, edits := range fix.Edits { 232 for _, edit := range edits { 233 if edit.NewText != "" { 234 return false 235 } 236 if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 { 237 return false 238 } 239 } 240 } 241 } 242 return len(fixes) > 0 243 } 244 245 func typesCodeHref(snapshot *snapshot, code typesinternal.ErrorCode) string { 246 target := snapshot.View().Options().LinkTarget 247 return source.BuildLink(target, "github.com/v2fly/tools/internal/typesinternal", code.String()) 248 } 249 250 func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic, kinds []protocol.CodeActionKind) ([]source.SuggestedFix, error) { 251 var fixes []source.SuggestedFix 252 for _, fix := range diag.SuggestedFixes { 253 edits := make(map[span.URI][]protocol.TextEdit) 254 for _, e := range fix.TextEdits { 255 spn, err := span.NewRange(snapshot.view.session.cache.fset, e.Pos, e.End).Span() 256 if err != nil { 257 return nil, err 258 } 259 rng, err := spanToRange(pkg, spn) 260 if err != nil { 261 return nil, err 262 } 263 edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{ 264 Range: rng, 265 NewText: string(e.NewText), 266 }) 267 } 268 for _, kind := range kinds { 269 fixes = append(fixes, source.SuggestedFix{ 270 Title: fix.Message, 271 Edits: edits, 272 ActionKind: kind, 273 }) 274 } 275 276 } 277 return fixes, nil 278 } 279 280 func relatedInformation(pkg *pkg, fset *token.FileSet, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) { 281 var out []source.RelatedInformation 282 for _, related := range diag.Related { 283 spn, err := span.NewRange(fset, related.Pos, related.End).Span() 284 if err != nil { 285 return nil, err 286 } 287 rng, err := spanToRange(pkg, spn) 288 if err != nil { 289 return nil, err 290 } 291 out = append(out, source.RelatedInformation{ 292 URI: spn.URI(), 293 Range: rng, 294 Message: related.Message, 295 }) 296 } 297 return out, nil 298 } 299 300 func typeErrorData(fset *token.FileSet, pkg *pkg, terr types.Error) (typesinternal.ErrorCode, span.Span, error) { 301 ecode, start, end, ok := typesinternal.ReadGo116ErrorData(terr) 302 if !ok { 303 start, end = terr.Pos, terr.Pos 304 ecode = 0 305 } 306 posn := fset.Position(start) 307 pgf, err := pkg.File(span.URIFromPath(posn.Filename)) 308 if err != nil { 309 return 0, span.Span{}, err 310 } 311 if !end.IsValid() || end == start { 312 end = analysisinternal.TypeErrorEndPos(fset, pgf.Src, start) 313 } 314 spn, err := parsedGoSpan(pgf, start, end) 315 if err != nil { 316 return 0, span.Span{}, err 317 } 318 return ecode, spn, nil 319 } 320 321 func parsedGoSpan(pgf *source.ParsedGoFile, start, end token.Pos) (span.Span, error) { 322 return span.FileSpan(pgf.Tok, pgf.Mapper.Converter, start, end) 323 } 324 325 // spanToRange converts a span.Span to a protocol.Range, 326 // assuming that the span belongs to the package whose diagnostics are being computed. 327 func spanToRange(pkg *pkg, spn span.Span) (protocol.Range, error) { 328 pgf, err := pkg.File(spn.URI()) 329 if err != nil { 330 return protocol.Range{}, err 331 } 332 return pgf.Mapper.Range(spn) 333 } 334 335 // parseGoListError attempts to parse a standard `go list` error message 336 // by stripping off the trailing error message. 337 // 338 // It works only on errors whose message is prefixed by colon, 339 // followed by a space (": "). For example: 340 // 341 // attributes.go:13:1: expected 'package', found 'type' 342 // 343 func parseGoListError(input, wd string) span.Span { 344 input = strings.TrimSpace(input) 345 msgIndex := strings.Index(input, ": ") 346 if msgIndex < 0 { 347 return span.Parse(input) 348 } 349 return span.ParseInDir(input[:msgIndex], wd) 350 } 351 352 func parseGoListImportCycleError(snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) { 353 re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`) 354 matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg)) 355 if len(matches) < 3 { 356 return e.Msg, span.Span{}, false 357 } 358 msg := matches[1] 359 importList := strings.Split(matches[2], " ") 360 // Since the error is relative to the current package. The import that is causing 361 // the import cycle error is the second one in the list. 362 if len(importList) < 2 { 363 return msg, span.Span{}, false 364 } 365 // Imports have quotation marks around them. 366 circImp := strconv.Quote(importList[1]) 367 for _, cgf := range pkg.compiledGoFiles { 368 // Search file imports for the import that is causing the import cycle. 369 for _, imp := range cgf.File.Imports { 370 if imp.Path.Value == circImp { 371 spn, err := span.NewRange(snapshot.view.session.cache.fset, imp.Pos(), imp.End()).Span() 372 if err != nil { 373 return msg, span.Span{}, false 374 } 375 return msg, spn, true 376 } 377 } 378 } 379 return msg, span.Span{}, false 380 }