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  }