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  }