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.
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/lsp/command"
21 "golang.org/x/tools/internal/lsp/protocol"
22 "golang.org/x/tools/internal/lsp/source"
23 "golang.org/x/tools/internal/span"
24 "golang.org/x/tools/internal/typesinternal"
25 errors "golang.org/x/xerrors"
28 func goPackagesErrorDiagnostics(ctx context.Context, snapshot *snapshot, pkg *pkg, e packages.Error) ([]*source.Diagnostic, error) {
29 if msg, spn, ok := parseGoListImportCycleError(ctx, snapshot, e, pkg); ok {
30 rng, err := spanToRange(snapshot, pkg, spn)
34 return []*source.Diagnostic{{
37 Severity: protocol.SeverityError,
38 Source: source.TypeError,
45 spn = parseGoListError(e.Msg, pkg.m.config.Dir)
46 // We may not have been able to parse a valid span. Apply the errors to all files.
47 if _, err := spanToRange(snapshot, pkg, spn); err != nil {
48 var diags []*source.Diagnostic
49 for _, cgf := range pkg.compiledGoFiles {
50 diags = append(diags, &source.Diagnostic{
52 Severity: protocol.SeverityError,
53 Source: source.ListError,
60 spn = span.ParseInDir(e.Pos, pkg.m.config.Dir)
63 rng, err := spanToRange(snapshot, pkg, spn)
67 return []*source.Diagnostic{{
70 Severity: protocol.SeverityError,
71 Source: source.ListError,
76 func parseErrorDiagnostics(ctx context.Context, snapshot *snapshot, pkg *pkg, errList scanner.ErrorList) ([]*source.Diagnostic, error) {
77 // The first parser error is likely the root cause of the problem.
78 if errList.Len() <= 0 {
79 return nil, errors.Errorf("no errors in %v", errList)
82 pgf, err := pkg.File(span.URIFromPath(e.Pos.Filename))
86 pos := pgf.Tok.Pos(e.Pos.Offset)
87 spn, err := span.NewRange(snapshot.FileSet(), pos, pos).Span()
91 rng, err := spanToRange(snapshot, pkg, spn)
95 return []*source.Diagnostic{{
98 Severity: protocol.SeverityError,
99 Source: source.ParseError,
104 var importErrorRe = regexp.MustCompile(`could not import ([^\s]+)`)
106 func typeErrorDiagnostics(ctx context.Context, snapshot *snapshot, pkg *pkg, e extendedError) ([]*source.Diagnostic, error) {
107 code, spn, err := typeErrorData(snapshot.FileSet(), pkg, e.primary)
111 rng, err := spanToRange(snapshot, pkg, spn)
115 diag := &source.Diagnostic{
118 Severity: protocol.SeverityError,
119 Source: source.TypeError,
120 Message: e.primary.Msg,
123 diag.Code = code.String()
124 diag.CodeHref = typesCodeHref(snapshot, code)
127 for _, secondary := range e.secondaries {
128 _, secondarySpan, err := typeErrorData(snapshot.FileSet(), pkg, secondary)
132 rng, err := spanToRange(snapshot, pkg, secondarySpan)
136 diag.Related = append(diag.Related, source.RelatedInformation{
137 URI: secondarySpan.URI(),
139 Message: secondary.Msg,
143 if match := importErrorRe.FindStringSubmatch(e.primary.Msg); match != nil {
144 diag.SuggestedFixes, err = goGetQuickFixes(snapshot, spn.URI(), match[1])
149 return []*source.Diagnostic{diag}, nil
152 func goGetQuickFixes(snapshot *snapshot, uri span.URI, pkg string) ([]source.SuggestedFix, error) {
153 // Go get only supports module mode for now.
154 if snapshot.workspaceMode()&moduleMode == 0 {
157 title := fmt.Sprintf("go get package %v", pkg)
158 cmd, err := command.NewGoGetPackageCommand(title, command.GoGetPackageArgs{
159 URI: protocol.URIFromSpanURI(uri),
166 return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)}, nil
169 func analysisDiagnosticDiagnostics(ctx context.Context, snapshot *snapshot, pkg *pkg, a *analysis.Analyzer, e *analysis.Diagnostic) ([]*source.Diagnostic, error) {
170 var srcAnalyzer *source.Analyzer
171 // Find the analyzer that generated this diagnostic.
172 for _, sa := range source.EnabledAnalyzers(snapshot) {
173 if a == sa.Analyzer {
179 spn, err := span.NewRange(snapshot.FileSet(), e.Pos, e.End).Span()
183 rng, err := spanToRange(snapshot, pkg, spn)
187 fixes, err := suggestedAnalysisFixes(snapshot, pkg, e)
191 if srcAnalyzer.Fix != "" {
192 cmd, err := command.NewApplyFixCommand(e.Message, command.ApplyFixArgs{
193 URI: protocol.URIFromSpanURI(spn.URI()),
195 Fix: srcAnalyzer.Fix,
200 fixes = append(fixes, source.SuggestedFixFromCommand(cmd))
202 related, err := relatedInformation(snapshot, pkg, e)
206 diag := &source.Diagnostic{
209 Severity: protocol.SeverityWarning,
210 Source: source.AnalyzerErrorKind(e.Category),
213 SuggestedFixes: fixes,
214 Analyzer: srcAnalyzer,
216 // If the fixes only delete code, assume that the diagnostic is reporting dead code.
217 if onlyDeletions(fixes) {
218 diag.Tags = []protocol.DiagnosticTag{protocol.Unnecessary}
220 return []*source.Diagnostic{diag}, nil
223 // onlyDeletions returns true if all of the suggested fixes are deletions.
224 func onlyDeletions(fixes []source.SuggestedFix) bool {
225 for _, fix := range fixes {
226 for _, edits := range fix.Edits {
227 for _, edit := range edits {
228 if edit.NewText != "" {
231 if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 {
237 return len(fixes) > 0
240 func typesCodeHref(snapshot *snapshot, code typesinternal.ErrorCode) string {
241 target := snapshot.View().Options().LinkTarget
242 return fmt.Sprintf("https://%s/golang.org/x/tools/internal/typesinternal#%s", target, code.String())
245 func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
246 var fixes []source.SuggestedFix
247 for _, fix := range diag.SuggestedFixes {
248 edits := make(map[span.URI][]protocol.TextEdit)
249 for _, e := range fix.TextEdits {
250 spn, err := span.NewRange(snapshot.view.session.cache.fset, e.Pos, e.End).Span()
254 rng, err := spanToRange(snapshot, pkg, spn)
258 edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
260 NewText: string(e.NewText),
263 fixes = append(fixes, source.SuggestedFix{
271 func relatedInformation(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
272 var out []source.RelatedInformation
273 for _, related := range diag.Related {
274 spn, err := span.NewRange(snapshot.view.session.cache.fset, related.Pos, related.End).Span()
278 rng, err := spanToRange(snapshot, pkg, spn)
282 out = append(out, source.RelatedInformation{
285 Message: related.Message,
291 func typeErrorData(fset *token.FileSet, pkg *pkg, terr types.Error) (typesinternal.ErrorCode, span.Span, error) {
292 ecode, start, end, ok := typesinternal.ReadGo116ErrorData(terr)
294 start, end = terr.Pos, terr.Pos
297 posn := fset.Position(start)
298 pgf, err := pkg.File(span.URIFromPath(posn.Filename))
300 return 0, span.Span{}, err
302 if !end.IsValid() || end == start {
303 end = analysisinternal.TypeErrorEndPos(fset, pgf.Src, start)
305 spn, err := parsedGoSpan(pgf, start, end)
307 return 0, span.Span{}, err
309 return ecode, spn, nil
312 func parsedGoSpan(pgf *source.ParsedGoFile, start, end token.Pos) (span.Span, error) {
313 return span.FileSpan(pgf.Tok, pgf.Mapper.Converter, start, end)
316 // spanToRange converts a span.Span to a protocol.Range,
317 // assuming that the span belongs to the package whose diagnostics are being computed.
318 func spanToRange(snapshot *snapshot, pkg *pkg, spn span.Span) (protocol.Range, error) {
319 pgf, err := pkg.File(spn.URI())
321 return protocol.Range{}, err
323 return pgf.Mapper.Range(spn)
326 // parseGoListError attempts to parse a standard `go list` error message
327 // by stripping off the trailing error message.
329 // It works only on errors whose message is prefixed by colon,
330 // followed by a space (": "). For example:
332 // attributes.go:13:1: expected 'package', found 'type'
334 func parseGoListError(input, wd string) span.Span {
335 input = strings.TrimSpace(input)
336 msgIndex := strings.Index(input, ": ")
338 return span.Parse(input)
340 return span.ParseInDir(input[:msgIndex], wd)
343 func parseGoListImportCycleError(ctx context.Context, snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) {
344 re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`)
345 matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg))
346 if len(matches) < 3 {
347 return e.Msg, span.Span{}, false
350 importList := strings.Split(matches[2], " ")
351 // Since the error is relative to the current package. The import that is causing
352 // the import cycle error is the second one in the list.
353 if len(importList) < 2 {
354 return msg, span.Span{}, false
356 // Imports have quotation marks around them.
357 circImp := strconv.Quote(importList[1])
358 for _, cgf := range pkg.compiledGoFiles {
359 // Search file imports for the import that is causing the import cycle.
360 for _, imp := range cgf.File.Imports {
361 if imp.Path.Value == circImp {
362 spn, err := span.NewRange(snapshot.view.session.cache.fset, imp.Pos(), imp.End()).Span()
364 return msg, span.Span{}, false
366 return msg, spn, true
370 return msg, span.Span{}, false