github.com/jd-ly/tools@v0.5.7/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  	"context"
     9  	"fmt"
    10  	"go/scanner"
    11  	"go/token"
    12  	"go/types"
    13  	"regexp"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/jd-ly/tools/go/analysis"
    18  	"github.com/jd-ly/tools/go/packages"
    19  	"github.com/jd-ly/tools/internal/analysisinternal"
    20  	"github.com/jd-ly/tools/internal/event"
    21  	"github.com/jd-ly/tools/internal/lsp/debug/tag"
    22  	"github.com/jd-ly/tools/internal/lsp/protocol"
    23  	"github.com/jd-ly/tools/internal/lsp/source"
    24  	"github.com/jd-ly/tools/internal/span"
    25  	errors "golang.org/x/xerrors"
    26  )
    27  
    28  func sourceError(ctx context.Context, snapshot *snapshot, pkg *pkg, e interface{}) (*source.Error, error) {
    29  	fset := snapshot.view.session.cache.fset
    30  	var (
    31  		spn           span.Span
    32  		err           error
    33  		msg, category string
    34  		kind          source.ErrorKind
    35  		fixes         []source.SuggestedFix
    36  		related       []source.RelatedInformation
    37  	)
    38  	switch e := e.(type) {
    39  	case packages.Error:
    40  		kind = toSourceErrorKind(e.Kind)
    41  		var ok bool
    42  		if msg, spn, ok = parseGoListImportCycleError(ctx, snapshot, e, pkg); ok {
    43  			kind = source.TypeError
    44  			break
    45  		}
    46  		if e.Pos == "" {
    47  			spn = parseGoListError(e.Msg)
    48  
    49  			// We may not have been able to parse a valid span.
    50  			if _, err := spanToRange(snapshot, pkg, spn); err != nil {
    51  				return &source.Error{
    52  					URI:     spn.URI(),
    53  					Message: msg,
    54  					Kind:    kind,
    55  				}, nil
    56  			}
    57  		} else {
    58  			spn = span.Parse(e.Pos)
    59  		}
    60  	case *scanner.Error:
    61  		msg = e.Msg
    62  		kind = source.ParseError
    63  		spn, err = scannerErrorRange(snapshot, pkg, e.Pos)
    64  		if err != nil {
    65  			if ctx.Err() != nil {
    66  				return nil, ctx.Err()
    67  			}
    68  			event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID()))
    69  			spn = span.Parse(e.Pos.String())
    70  		}
    71  
    72  	case scanner.ErrorList:
    73  		// The first parser error is likely the root cause of the problem.
    74  		if e.Len() <= 0 {
    75  			return nil, errors.Errorf("no errors in %v", e)
    76  		}
    77  		msg = e[0].Msg
    78  		kind = source.ParseError
    79  		spn, err = scannerErrorRange(snapshot, pkg, e[0].Pos)
    80  		if err != nil {
    81  			if ctx.Err() != nil {
    82  				return nil, ctx.Err()
    83  			}
    84  			event.Error(ctx, "no span for scanner.Error pos", err, tag.Package.Of(pkg.ID()))
    85  			spn = span.Parse(e[0].Pos.String())
    86  		}
    87  	case types.Error:
    88  		msg = e.Msg
    89  		kind = source.TypeError
    90  		if !e.Pos.IsValid() {
    91  			return nil, fmt.Errorf("invalid position for type error %v", e)
    92  		}
    93  		spn, err = typeErrorRange(snapshot, fset, pkg, e.Pos)
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  	case extendedError:
    98  		perr := e.primary
    99  		msg = perr.Msg
   100  		kind = source.TypeError
   101  		if !perr.Pos.IsValid() {
   102  			return nil, fmt.Errorf("invalid position for type error %v", e)
   103  		}
   104  		spn, err = typeErrorRange(snapshot, fset, pkg, perr.Pos)
   105  		if err != nil {
   106  			return nil, err
   107  		}
   108  		for _, s := range e.secondaries {
   109  			var x source.RelatedInformation
   110  			x.Message = s.Msg
   111  			xspn, err := typeErrorRange(snapshot, fset, pkg, s.Pos)
   112  			if err != nil {
   113  				return nil, fmt.Errorf("invalid position for type error %v", s)
   114  			}
   115  			x.URI = xspn.URI()
   116  			rng, err := spanToRange(snapshot, pkg, xspn)
   117  			if err != nil {
   118  				return nil, err
   119  			}
   120  			x.Range = rng
   121  			related = append(related, x)
   122  		}
   123  	case *analysis.Diagnostic:
   124  		spn, err = span.NewRange(fset, e.Pos, e.End).Span()
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  		msg = e.Message
   129  		kind = source.Analysis
   130  		category = e.Category
   131  		fixes, err = suggestedAnalysisFixes(snapshot, pkg, e)
   132  		if err != nil {
   133  			return nil, err
   134  		}
   135  		related, err = relatedInformation(snapshot, pkg, e)
   136  		if err != nil {
   137  			return nil, err
   138  		}
   139  	default:
   140  		panic(fmt.Sprintf("%T unexpected", e))
   141  	}
   142  	rng, err := spanToRange(snapshot, pkg, spn)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	return &source.Error{
   147  		URI:            spn.URI(),
   148  		Range:          rng,
   149  		Message:        msg,
   150  		Kind:           kind,
   151  		Category:       category,
   152  		SuggestedFixes: fixes,
   153  		Related:        related,
   154  	}, nil
   155  }
   156  
   157  func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
   158  	var fixes []source.SuggestedFix
   159  	for _, fix := range diag.SuggestedFixes {
   160  		edits := make(map[span.URI][]protocol.TextEdit)
   161  		for _, e := range fix.TextEdits {
   162  			spn, err := span.NewRange(snapshot.view.session.cache.fset, e.Pos, e.End).Span()
   163  			if err != nil {
   164  				return nil, err
   165  			}
   166  			rng, err := spanToRange(snapshot, pkg, spn)
   167  			if err != nil {
   168  				return nil, err
   169  			}
   170  			edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
   171  				Range:   rng,
   172  				NewText: string(e.NewText),
   173  			})
   174  		}
   175  		fixes = append(fixes, source.SuggestedFix{
   176  			Title: fix.Message,
   177  			Edits: edits,
   178  		})
   179  	}
   180  	return fixes, nil
   181  }
   182  
   183  func relatedInformation(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
   184  	var out []source.RelatedInformation
   185  	for _, related := range diag.Related {
   186  		spn, err := span.NewRange(snapshot.view.session.cache.fset, related.Pos, related.End).Span()
   187  		if err != nil {
   188  			return nil, err
   189  		}
   190  		rng, err := spanToRange(snapshot, pkg, spn)
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  		out = append(out, source.RelatedInformation{
   195  			URI:     spn.URI(),
   196  			Range:   rng,
   197  			Message: related.Message,
   198  		})
   199  	}
   200  	return out, nil
   201  }
   202  
   203  func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind {
   204  	switch kind {
   205  	case packages.ListError:
   206  		return source.ListError
   207  	case packages.ParseError:
   208  		return source.ParseError
   209  	case packages.TypeError:
   210  		return source.TypeError
   211  	default:
   212  		return source.UnknownError
   213  	}
   214  }
   215  
   216  func typeErrorRange(snapshot *snapshot, fset *token.FileSet, pkg *pkg, pos token.Pos) (span.Span, error) {
   217  	posn := fset.Position(pos)
   218  	pgf, err := pkg.File(span.URIFromPath(posn.Filename))
   219  	if err != nil {
   220  		return span.Span{}, err
   221  	}
   222  	return span.Range{
   223  		FileSet:   fset,
   224  		Start:     pos,
   225  		End:       analysisinternal.TypeErrorEndPos(fset, pgf.Src, pos),
   226  		Converter: pgf.Mapper.Converter,
   227  	}.Span()
   228  }
   229  
   230  func scannerErrorRange(snapshot *snapshot, pkg *pkg, posn token.Position) (span.Span, error) {
   231  	fset := snapshot.view.session.cache.fset
   232  	pgf, err := pkg.File(span.URIFromPath(posn.Filename))
   233  	if err != nil {
   234  		return span.Span{}, err
   235  	}
   236  	pos := pgf.Tok.Pos(posn.Offset)
   237  	return span.NewRange(fset, pos, pos).Span()
   238  }
   239  
   240  // spanToRange converts a span.Span to a protocol.Range,
   241  // assuming that the span belongs to the package whose diagnostics are being computed.
   242  func spanToRange(snapshot *snapshot, pkg *pkg, spn span.Span) (protocol.Range, error) {
   243  	pgf, err := pkg.File(spn.URI())
   244  	if err != nil {
   245  		return protocol.Range{}, err
   246  	}
   247  	return pgf.Mapper.Range(spn)
   248  }
   249  
   250  // parseGoListError attempts to parse a standard `go list` error message
   251  // by stripping off the trailing error message.
   252  //
   253  // It works only on errors whose message is prefixed by colon,
   254  // followed by a space (": "). For example:
   255  //
   256  //   attributes.go:13:1: expected 'package', found 'type'
   257  //
   258  func parseGoListError(input string) span.Span {
   259  	input = strings.TrimSpace(input)
   260  	msgIndex := strings.Index(input, ": ")
   261  	if msgIndex < 0 {
   262  		return span.Parse(input)
   263  	}
   264  	return span.Parse(input[:msgIndex])
   265  }
   266  
   267  func parseGoListImportCycleError(ctx context.Context, snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) {
   268  	re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`)
   269  	matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg))
   270  	if len(matches) < 3 {
   271  		return e.Msg, span.Span{}, false
   272  	}
   273  	msg := matches[1]
   274  	importList := strings.Split(matches[2], " ")
   275  	// Since the error is relative to the current package. The import that is causing
   276  	// the import cycle error is the second one in the list.
   277  	if len(importList) < 2 {
   278  		return msg, span.Span{}, false
   279  	}
   280  	// Imports have quotation marks around them.
   281  	circImp := strconv.Quote(importList[1])
   282  	for _, cgf := range pkg.compiledGoFiles {
   283  		// Search file imports for the import that is causing the import cycle.
   284  		for _, imp := range cgf.File.Imports {
   285  			if imp.Path.Value == circImp {
   286  				spn, err := span.NewRange(snapshot.view.session.cache.fset, imp.Pos(), imp.End()).Span()
   287  				if err != nil {
   288  					return msg, span.Span{}, false
   289  				}
   290  				return msg, spn, true
   291  			}
   292  		}
   293  	}
   294  	return msg, span.Span{}, false
   295  }