github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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/powerman/golang-tools/go/analysis" 17 "github.com/powerman/golang-tools/go/packages" 18 "github.com/powerman/golang-tools/internal/analysisinternal" 19 "github.com/powerman/golang-tools/internal/lsp/command" 20 "github.com/powerman/golang-tools/internal/lsp/protocol" 21 "github.com/powerman/golang-tools/internal/lsp/source" 22 "github.com/powerman/golang-tools/internal/span" 23 "github.com/powerman/golang-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.ListError, 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 var unsupportedFeatureRe = regexp.MustCompile(`.*require.* go(\d+\.\d+) or later`) 105 106 func typeErrorDiagnostics(snapshot *snapshot, pkg *pkg, e extendedError) ([]*source.Diagnostic, error) { 107 code, spn, err := typeErrorData(snapshot.FileSet(), pkg, e.primary) 108 if err != nil { 109 return nil, err 110 } 111 rng, err := spanToRange(pkg, spn) 112 if err != nil { 113 return nil, err 114 } 115 diag := &source.Diagnostic{ 116 URI: spn.URI(), 117 Range: rng, 118 Severity: protocol.SeverityError, 119 Source: source.TypeError, 120 Message: e.primary.Msg, 121 } 122 if code != 0 { 123 diag.Code = code.String() 124 diag.CodeHref = typesCodeHref(snapshot, code) 125 } 126 127 for _, secondary := range e.secondaries { 128 _, secondarySpan, err := typeErrorData(snapshot.FileSet(), pkg, secondary) 129 if err != nil { 130 return nil, err 131 } 132 rng, err := spanToRange(pkg, secondarySpan) 133 if err != nil { 134 return nil, err 135 } 136 diag.Related = append(diag.Related, source.RelatedInformation{ 137 URI: secondarySpan.URI(), 138 Range: rng, 139 Message: secondary.Msg, 140 }) 141 } 142 143 if match := importErrorRe.FindStringSubmatch(e.primary.Msg); match != nil { 144 diag.SuggestedFixes, err = goGetQuickFixes(snapshot, spn.URI(), match[1]) 145 if err != nil { 146 return nil, err 147 } 148 } 149 if match := unsupportedFeatureRe.FindStringSubmatch(e.primary.Msg); match != nil { 150 diag.SuggestedFixes, err = editGoDirectiveQuickFix(snapshot, spn.URI(), match[1]) 151 if err != nil { 152 return nil, err 153 } 154 } 155 return []*source.Diagnostic{diag}, nil 156 } 157 158 func goGetQuickFixes(snapshot *snapshot, uri span.URI, pkg string) ([]source.SuggestedFix, error) { 159 // Go get only supports module mode for now. 160 if snapshot.workspaceMode()&moduleMode == 0 { 161 return nil, nil 162 } 163 title := fmt.Sprintf("go get package %v", pkg) 164 cmd, err := command.NewGoGetPackageCommand(title, command.GoGetPackageArgs{ 165 URI: protocol.URIFromSpanURI(uri), 166 AddRequire: true, 167 Pkg: pkg, 168 }) 169 if err != nil { 170 return nil, err 171 } 172 return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)}, nil 173 } 174 175 func editGoDirectiveQuickFix(snapshot *snapshot, uri span.URI, version string) ([]source.SuggestedFix, error) { 176 // Go mod edit only supports module mode. 177 if snapshot.workspaceMode()&moduleMode == 0 { 178 return nil, nil 179 } 180 title := fmt.Sprintf("go mod edit -go=%s", version) 181 cmd, err := command.NewEditGoDirectiveCommand(title, command.EditGoDirectiveArgs{ 182 URI: protocol.URIFromSpanURI(uri), 183 Version: version, 184 }) 185 if err != nil { 186 return nil, err 187 } 188 return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)}, nil 189 } 190 191 func analysisDiagnosticDiagnostics(snapshot *snapshot, pkg *pkg, a *analysis.Analyzer, e *analysis.Diagnostic) ([]*source.Diagnostic, error) { 192 var srcAnalyzer *source.Analyzer 193 // Find the analyzer that generated this diagnostic. 194 for _, sa := range source.EnabledAnalyzers(snapshot) { 195 if a == sa.Analyzer { 196 srcAnalyzer = sa 197 break 198 } 199 } 200 201 spn, err := span.NewRange(snapshot.FileSet(), e.Pos, e.End).Span() 202 if err != nil { 203 return nil, err 204 } 205 rng, err := spanToRange(pkg, spn) 206 if err != nil { 207 return nil, err 208 } 209 kinds := srcAnalyzer.ActionKind 210 if len(srcAnalyzer.ActionKind) == 0 { 211 kinds = append(kinds, protocol.QuickFix) 212 } 213 fixes, err := suggestedAnalysisFixes(snapshot, pkg, e, kinds) 214 if err != nil { 215 return nil, err 216 } 217 if srcAnalyzer.Fix != "" { 218 cmd, err := command.NewApplyFixCommand(e.Message, command.ApplyFixArgs{ 219 URI: protocol.URIFromSpanURI(spn.URI()), 220 Range: rng, 221 Fix: srcAnalyzer.Fix, 222 }) 223 if err != nil { 224 return nil, err 225 } 226 for _, kind := range kinds { 227 fixes = append(fixes, source.SuggestedFixFromCommand(cmd, kind)) 228 } 229 } 230 related, err := relatedInformation(pkg, snapshot.FileSet(), e) 231 if err != nil { 232 return nil, err 233 } 234 235 severity := srcAnalyzer.Severity 236 if severity == 0 { 237 severity = protocol.SeverityWarning 238 } 239 diag := &source.Diagnostic{ 240 URI: spn.URI(), 241 Range: rng, 242 Severity: severity, 243 Source: source.AnalyzerErrorKind(e.Category), 244 Message: e.Message, 245 Related: related, 246 SuggestedFixes: fixes, 247 Analyzer: srcAnalyzer, 248 } 249 // If the fixes only delete code, assume that the diagnostic is reporting dead code. 250 if onlyDeletions(fixes) { 251 diag.Tags = []protocol.DiagnosticTag{protocol.Unnecessary} 252 } 253 return []*source.Diagnostic{diag}, nil 254 } 255 256 // onlyDeletions returns true if all of the suggested fixes are deletions. 257 func onlyDeletions(fixes []source.SuggestedFix) bool { 258 for _, fix := range fixes { 259 if fix.Command != nil { 260 return false 261 } 262 for _, edits := range fix.Edits { 263 for _, edit := range edits { 264 if edit.NewText != "" { 265 return false 266 } 267 if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 { 268 return false 269 } 270 } 271 } 272 } 273 return len(fixes) > 0 274 } 275 276 func typesCodeHref(snapshot *snapshot, code typesinternal.ErrorCode) string { 277 target := snapshot.View().Options().LinkTarget 278 return source.BuildLink(target, "github.com/powerman/golang-tools/internal/typesinternal", code.String()) 279 } 280 281 func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic, kinds []protocol.CodeActionKind) ([]source.SuggestedFix, error) { 282 var fixes []source.SuggestedFix 283 for _, fix := range diag.SuggestedFixes { 284 edits := make(map[span.URI][]protocol.TextEdit) 285 for _, e := range fix.TextEdits { 286 spn, err := span.NewRange(snapshot.FileSet(), e.Pos, e.End).Span() 287 if err != nil { 288 return nil, err 289 } 290 rng, err := spanToRange(pkg, spn) 291 if err != nil { 292 return nil, err 293 } 294 edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{ 295 Range: rng, 296 NewText: string(e.NewText), 297 }) 298 } 299 for _, kind := range kinds { 300 fixes = append(fixes, source.SuggestedFix{ 301 Title: fix.Message, 302 Edits: edits, 303 ActionKind: kind, 304 }) 305 } 306 307 } 308 return fixes, nil 309 } 310 311 func relatedInformation(pkg *pkg, fset *token.FileSet, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) { 312 var out []source.RelatedInformation 313 for _, related := range diag.Related { 314 spn, err := span.NewRange(fset, related.Pos, related.End).Span() 315 if err != nil { 316 return nil, err 317 } 318 rng, err := spanToRange(pkg, spn) 319 if err != nil { 320 return nil, err 321 } 322 out = append(out, source.RelatedInformation{ 323 URI: spn.URI(), 324 Range: rng, 325 Message: related.Message, 326 }) 327 } 328 return out, nil 329 } 330 331 func typeErrorData(fset *token.FileSet, pkg *pkg, terr types.Error) (typesinternal.ErrorCode, span.Span, error) { 332 ecode, start, end, ok := typesinternal.ReadGo116ErrorData(terr) 333 if !ok { 334 start, end = terr.Pos, terr.Pos 335 ecode = 0 336 } 337 posn := fset.Position(start) 338 pgf, err := pkg.File(span.URIFromPath(posn.Filename)) 339 if err != nil { 340 return 0, span.Span{}, err 341 } 342 if !end.IsValid() || end == start { 343 end = analysisinternal.TypeErrorEndPos(fset, pgf.Src, start) 344 } 345 spn, err := parsedGoSpan(pgf, start, end) 346 if err != nil { 347 return 0, span.Span{}, err 348 } 349 return ecode, spn, nil 350 } 351 352 func parsedGoSpan(pgf *source.ParsedGoFile, start, end token.Pos) (span.Span, error) { 353 return span.FileSpan(pgf.Tok, pgf.Mapper.Converter, start, end) 354 } 355 356 // spanToRange converts a span.Span to a protocol.Range, 357 // assuming that the span belongs to the package whose diagnostics are being computed. 358 func spanToRange(pkg *pkg, spn span.Span) (protocol.Range, error) { 359 pgf, err := pkg.File(spn.URI()) 360 if err != nil { 361 return protocol.Range{}, err 362 } 363 return pgf.Mapper.Range(spn) 364 } 365 366 // parseGoListError attempts to parse a standard `go list` error message 367 // by stripping off the trailing error message. 368 // 369 // It works only on errors whose message is prefixed by colon, 370 // followed by a space (": "). For example: 371 // 372 // attributes.go:13:1: expected 'package', found 'type' 373 // 374 func parseGoListError(input, wd string) span.Span { 375 input = strings.TrimSpace(input) 376 msgIndex := strings.Index(input, ": ") 377 if msgIndex < 0 { 378 return span.Parse(input) 379 } 380 return span.ParseInDir(input[:msgIndex], wd) 381 } 382 383 func parseGoListImportCycleError(snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) { 384 re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`) 385 matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg)) 386 if len(matches) < 3 { 387 return e.Msg, span.Span{}, false 388 } 389 msg := matches[1] 390 importList := strings.Split(matches[2], " ") 391 // Since the error is relative to the current package. The import that is causing 392 // the import cycle error is the second one in the list. 393 if len(importList) < 2 { 394 return msg, span.Span{}, false 395 } 396 // Imports have quotation marks around them. 397 circImp := strconv.Quote(importList[1]) 398 for _, cgf := range pkg.compiledGoFiles { 399 // Search file imports for the import that is causing the import cycle. 400 for _, imp := range cgf.File.Imports { 401 if imp.Path.Value == circImp { 402 spn, err := span.NewRange(snapshot.FileSet(), imp.Pos(), imp.End()).Span() 403 if err != nil { 404 return msg, span.Span{}, false 405 } 406 return msg, spn, true 407 } 408 } 409 } 410 return msg, span.Span{}, false 411 }