github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/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/jhump/golang-x-tools/go/analysis"
    17  	"github.com/jhump/golang-x-tools/go/packages"
    18  	"github.com/jhump/golang-x-tools/internal/analysisinternal"
    19  	"github.com/jhump/golang-x-tools/internal/lsp/command"
    20  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    21  	"github.com/jhump/golang-x-tools/internal/lsp/source"
    22  	"github.com/jhump/golang-x-tools/internal/span"
    23  	"github.com/jhump/golang-x-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  
   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  
   212  	severity := srcAnalyzer.Severity
   213  	if severity == 0 {
   214  		severity = protocol.SeverityWarning
   215  	}
   216  	diag := &source.Diagnostic{
   217  		URI:            spn.URI(),
   218  		Range:          rng,
   219  		Severity:       severity,
   220  		Source:         source.AnalyzerErrorKind(e.Category),
   221  		Message:        e.Message,
   222  		Related:        related,
   223  		SuggestedFixes: fixes,
   224  		Analyzer:       srcAnalyzer,
   225  	}
   226  	// If the fixes only delete code, assume that the diagnostic is reporting dead code.
   227  	if onlyDeletions(fixes) {
   228  		diag.Tags = []protocol.DiagnosticTag{protocol.Unnecessary}
   229  	}
   230  	return []*source.Diagnostic{diag}, nil
   231  }
   232  
   233  // onlyDeletions returns true if all of the suggested fixes are deletions.
   234  func onlyDeletions(fixes []source.SuggestedFix) bool {
   235  	for _, fix := range fixes {
   236  		if fix.Command != nil {
   237  			return false
   238  		}
   239  		for _, edits := range fix.Edits {
   240  			for _, edit := range edits {
   241  				if edit.NewText != "" {
   242  					return false
   243  				}
   244  				if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 {
   245  					return false
   246  				}
   247  			}
   248  		}
   249  	}
   250  	return len(fixes) > 0
   251  }
   252  
   253  func typesCodeHref(snapshot *snapshot, code typesinternal.ErrorCode) string {
   254  	target := snapshot.View().Options().LinkTarget
   255  	return source.BuildLink(target, "github.com/jhump/golang-x-tools/internal/typesinternal", code.String())
   256  }
   257  
   258  func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic, kinds []protocol.CodeActionKind) ([]source.SuggestedFix, error) {
   259  	var fixes []source.SuggestedFix
   260  	for _, fix := range diag.SuggestedFixes {
   261  		edits := make(map[span.URI][]protocol.TextEdit)
   262  		for _, e := range fix.TextEdits {
   263  			spn, err := span.NewRange(snapshot.FileSet(), e.Pos, e.End).Span()
   264  			if err != nil {
   265  				return nil, err
   266  			}
   267  			rng, err := spanToRange(pkg, spn)
   268  			if err != nil {
   269  				return nil, err
   270  			}
   271  			edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
   272  				Range:   rng,
   273  				NewText: string(e.NewText),
   274  			})
   275  		}
   276  		for _, kind := range kinds {
   277  			fixes = append(fixes, source.SuggestedFix{
   278  				Title:      fix.Message,
   279  				Edits:      edits,
   280  				ActionKind: kind,
   281  			})
   282  		}
   283  
   284  	}
   285  	return fixes, nil
   286  }
   287  
   288  func relatedInformation(pkg *pkg, fset *token.FileSet, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
   289  	var out []source.RelatedInformation
   290  	for _, related := range diag.Related {
   291  		spn, err := span.NewRange(fset, related.Pos, related.End).Span()
   292  		if err != nil {
   293  			return nil, err
   294  		}
   295  		rng, err := spanToRange(pkg, spn)
   296  		if err != nil {
   297  			return nil, err
   298  		}
   299  		out = append(out, source.RelatedInformation{
   300  			URI:     spn.URI(),
   301  			Range:   rng,
   302  			Message: related.Message,
   303  		})
   304  	}
   305  	return out, nil
   306  }
   307  
   308  func typeErrorData(fset *token.FileSet, pkg *pkg, terr types.Error) (typesinternal.ErrorCode, span.Span, error) {
   309  	ecode, start, end, ok := typesinternal.ReadGo116ErrorData(terr)
   310  	if !ok {
   311  		start, end = terr.Pos, terr.Pos
   312  		ecode = 0
   313  	}
   314  	posn := fset.Position(start)
   315  	pgf, err := pkg.File(span.URIFromPath(posn.Filename))
   316  	if err != nil {
   317  		return 0, span.Span{}, err
   318  	}
   319  	if !end.IsValid() || end == start {
   320  		end = analysisinternal.TypeErrorEndPos(fset, pgf.Src, start)
   321  	}
   322  	spn, err := parsedGoSpan(pgf, start, end)
   323  	if err != nil {
   324  		return 0, span.Span{}, err
   325  	}
   326  	return ecode, spn, nil
   327  }
   328  
   329  func parsedGoSpan(pgf *source.ParsedGoFile, start, end token.Pos) (span.Span, error) {
   330  	return span.FileSpan(pgf.Tok, pgf.Mapper.Converter, start, end)
   331  }
   332  
   333  // spanToRange converts a span.Span to a protocol.Range,
   334  // assuming that the span belongs to the package whose diagnostics are being computed.
   335  func spanToRange(pkg *pkg, spn span.Span) (protocol.Range, error) {
   336  	pgf, err := pkg.File(spn.URI())
   337  	if err != nil {
   338  		return protocol.Range{}, err
   339  	}
   340  	return pgf.Mapper.Range(spn)
   341  }
   342  
   343  // parseGoListError attempts to parse a standard `go list` error message
   344  // by stripping off the trailing error message.
   345  //
   346  // It works only on errors whose message is prefixed by colon,
   347  // followed by a space (": "). For example:
   348  //
   349  //   attributes.go:13:1: expected 'package', found 'type'
   350  //
   351  func parseGoListError(input, wd string) span.Span {
   352  	input = strings.TrimSpace(input)
   353  	msgIndex := strings.Index(input, ": ")
   354  	if msgIndex < 0 {
   355  		return span.Parse(input)
   356  	}
   357  	return span.ParseInDir(input[:msgIndex], wd)
   358  }
   359  
   360  func parseGoListImportCycleError(snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) {
   361  	re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`)
   362  	matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg))
   363  	if len(matches) < 3 {
   364  		return e.Msg, span.Span{}, false
   365  	}
   366  	msg := matches[1]
   367  	importList := strings.Split(matches[2], " ")
   368  	// Since the error is relative to the current package. The import that is causing
   369  	// the import cycle error is the second one in the list.
   370  	if len(importList) < 2 {
   371  		return msg, span.Span{}, false
   372  	}
   373  	// Imports have quotation marks around them.
   374  	circImp := strconv.Quote(importList[1])
   375  	for _, cgf := range pkg.compiledGoFiles {
   376  		// Search file imports for the import that is causing the import cycle.
   377  		for _, imp := range cgf.File.Imports {
   378  			if imp.Path.Value == circImp {
   379  				spn, err := span.NewRange(snapshot.FileSet(), imp.Pos(), imp.End()).Span()
   380  				if err != nil {
   381  					return msg, span.Span{}, false
   382  				}
   383  				return msg, spn, true
   384  			}
   385  		}
   386  	}
   387  	return msg, span.Span{}, false
   388  }