cuelang.org/go@v0.13.0/internal/golangorgx/gopls/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  // This file defines routines to convert diagnostics from go list, go
     8  // get, go/packages, parsing, type checking, and analysis into
     9  // golang.Diagnostic form, and suggesting quick fixes.
    10  
    11  import (
    12  	"fmt"
    13  	"go/scanner"
    14  	"path/filepath"
    15  	"regexp"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    20  	"cuelang.org/go/internal/golangorgx/gopls/protocol/command"
    21  	"cuelang.org/go/internal/golangorgx/gopls/settings"
    22  	"cuelang.org/go/internal/golangorgx/gopls/util/bug"
    23  	"cuelang.org/go/internal/golangorgx/tools/typesinternal"
    24  	"golang.org/x/tools/go/packages"
    25  )
    26  
    27  func parseErrorDiagnostics(pkg *syntaxPackage, errList scanner.ErrorList) ([]*Diagnostic, error) {
    28  	// The first parser error is likely the root cause of the problem.
    29  	if errList.Len() <= 0 {
    30  		return nil, fmt.Errorf("no errors in %v", errList)
    31  	}
    32  	e := errList[0]
    33  	pgf, err := pkg.File(protocol.URIFromPath(e.Pos.Filename))
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	rng, err := pgf.Mapper.OffsetRange(e.Pos.Offset, e.Pos.Offset)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	return []*Diagnostic{{
    42  		URI:      pgf.URI,
    43  		Range:    rng,
    44  		Severity: protocol.SeverityError,
    45  		Source:   ParseError,
    46  		Message:  e.Msg,
    47  	}}, nil
    48  }
    49  
    50  var importErrorRe = regexp.MustCompile(`could not import ([^\s]+)`)
    51  var unsupportedFeatureRe = regexp.MustCompile(`.*require.* go(\d+\.\d+) or later`)
    52  
    53  func goGetQuickFixes(moduleMode bool, uri protocol.DocumentURI, pkg string) []SuggestedFix {
    54  	// Go get only supports module mode for now.
    55  	if !moduleMode {
    56  		return nil
    57  	}
    58  	title := fmt.Sprintf("go get package %v", pkg)
    59  	cmd, err := command.NewGoGetPackageCommand(title, command.GoGetPackageArgs{
    60  		URI:        uri,
    61  		AddRequire: true,
    62  		Pkg:        pkg,
    63  	})
    64  	if err != nil {
    65  		bug.Reportf("internal error building 'go get package' fix: %v", err)
    66  		return nil
    67  	}
    68  	return []SuggestedFix{SuggestedFixFromCommand(cmd, protocol.QuickFix)}
    69  }
    70  
    71  func editGoDirectiveQuickFix(moduleMode bool, uri protocol.DocumentURI, version string) []SuggestedFix {
    72  	// Go mod edit only supports module mode.
    73  	if !moduleMode {
    74  		return nil
    75  	}
    76  	title := fmt.Sprintf("go mod edit -go=%s", version)
    77  	cmd, err := command.NewEditGoDirectiveCommand(title, command.EditGoDirectiveArgs{
    78  		URI:     uri,
    79  		Version: version,
    80  	})
    81  	if err != nil {
    82  		bug.Reportf("internal error constructing 'edit go directive' fix: %v", err)
    83  		return nil
    84  	}
    85  	return []SuggestedFix{SuggestedFixFromCommand(cmd, protocol.QuickFix)}
    86  }
    87  
    88  // encodeDiagnostics gob-encodes the given diagnostics.
    89  func encodeDiagnostics(srcDiags []*Diagnostic) []byte {
    90  	var gobDiags []gobDiagnostic
    91  	for _, srcDiag := range srcDiags {
    92  		var gobFixes []gobSuggestedFix
    93  		for _, srcFix := range srcDiag.SuggestedFixes {
    94  			gobFix := gobSuggestedFix{
    95  				Message:    srcFix.Title,
    96  				ActionKind: srcFix.ActionKind,
    97  			}
    98  			for uri, srcEdits := range srcFix.Edits {
    99  				for _, srcEdit := range srcEdits {
   100  					gobFix.TextEdits = append(gobFix.TextEdits, gobTextEdit{
   101  						Location: protocol.Location{
   102  							URI:   uri,
   103  							Range: srcEdit.Range,
   104  						},
   105  						NewText: []byte(srcEdit.NewText),
   106  					})
   107  				}
   108  			}
   109  			if srcCmd := srcFix.Command; srcCmd != nil {
   110  				gobFix.Command = &gobCommand{
   111  					Title:     srcCmd.Title,
   112  					Command:   srcCmd.Command,
   113  					Arguments: srcCmd.Arguments,
   114  				}
   115  			}
   116  			gobFixes = append(gobFixes, gobFix)
   117  		}
   118  		var gobRelated []gobRelatedInformation
   119  		for _, srcRel := range srcDiag.Related {
   120  			gobRel := gobRelatedInformation(srcRel)
   121  			gobRelated = append(gobRelated, gobRel)
   122  		}
   123  		gobDiag := gobDiagnostic{
   124  			Location: protocol.Location{
   125  				URI:   srcDiag.URI,
   126  				Range: srcDiag.Range,
   127  			},
   128  			Severity:       srcDiag.Severity,
   129  			Code:           srcDiag.Code,
   130  			CodeHref:       srcDiag.CodeHref,
   131  			Source:         string(srcDiag.Source),
   132  			Message:        srcDiag.Message,
   133  			SuggestedFixes: gobFixes,
   134  			Related:        gobRelated,
   135  			Tags:           srcDiag.Tags,
   136  		}
   137  		gobDiags = append(gobDiags, gobDiag)
   138  	}
   139  	return diagnosticsCodec.Encode(gobDiags)
   140  }
   141  
   142  // decodeDiagnostics decodes the given gob-encoded diagnostics.
   143  func decodeDiagnostics(data []byte) []*Diagnostic {
   144  	var gobDiags []gobDiagnostic
   145  	diagnosticsCodec.Decode(data, &gobDiags)
   146  	var srcDiags []*Diagnostic
   147  	for _, gobDiag := range gobDiags {
   148  		var srcFixes []SuggestedFix
   149  		for _, gobFix := range gobDiag.SuggestedFixes {
   150  			srcFix := SuggestedFix{
   151  				Title:      gobFix.Message,
   152  				ActionKind: gobFix.ActionKind,
   153  			}
   154  			for _, gobEdit := range gobFix.TextEdits {
   155  				if srcFix.Edits == nil {
   156  					srcFix.Edits = make(map[protocol.DocumentURI][]protocol.TextEdit)
   157  				}
   158  				srcEdit := protocol.TextEdit{
   159  					Range:   gobEdit.Location.Range,
   160  					NewText: string(gobEdit.NewText),
   161  				}
   162  				uri := gobEdit.Location.URI
   163  				srcFix.Edits[uri] = append(srcFix.Edits[uri], srcEdit)
   164  			}
   165  			if gobCmd := gobFix.Command; gobCmd != nil {
   166  				srcFix.Command = &protocol.Command{
   167  					Title:     gobCmd.Title,
   168  					Command:   gobCmd.Command,
   169  					Arguments: gobCmd.Arguments,
   170  				}
   171  			}
   172  			srcFixes = append(srcFixes, srcFix)
   173  		}
   174  		var srcRelated []protocol.DiagnosticRelatedInformation
   175  		for _, gobRel := range gobDiag.Related {
   176  			srcRel := protocol.DiagnosticRelatedInformation(gobRel)
   177  			srcRelated = append(srcRelated, srcRel)
   178  		}
   179  		srcDiag := &Diagnostic{
   180  			URI:            gobDiag.Location.URI,
   181  			Range:          gobDiag.Location.Range,
   182  			Severity:       gobDiag.Severity,
   183  			Code:           gobDiag.Code,
   184  			CodeHref:       gobDiag.CodeHref,
   185  			Source:         DiagnosticSource(gobDiag.Source),
   186  			Message:        gobDiag.Message,
   187  			Tags:           gobDiag.Tags,
   188  			Related:        srcRelated,
   189  			SuggestedFixes: srcFixes,
   190  		}
   191  		srcDiags = append(srcDiags, srcDiag)
   192  	}
   193  	return srcDiags
   194  }
   195  
   196  // toSourceDiagnostic converts a gobDiagnostic to "source" form.
   197  func toSourceDiagnostic(srcAnalyzer *settings.Analyzer, gobDiag *gobDiagnostic) *Diagnostic {
   198  	var related []protocol.DiagnosticRelatedInformation
   199  	for _, gobRelated := range gobDiag.Related {
   200  		related = append(related, protocol.DiagnosticRelatedInformation(gobRelated))
   201  	}
   202  
   203  	severity := srcAnalyzer.Severity
   204  	if severity == 0 {
   205  		severity = protocol.SeverityWarning
   206  	}
   207  
   208  	diag := &Diagnostic{
   209  		URI:      gobDiag.Location.URI,
   210  		Range:    gobDiag.Location.Range,
   211  		Severity: severity,
   212  		Code:     gobDiag.Code,
   213  		CodeHref: gobDiag.CodeHref,
   214  		Source:   DiagnosticSource(gobDiag.Source),
   215  		Message:  gobDiag.Message,
   216  		Related:  related,
   217  		Tags:     srcAnalyzer.Tag,
   218  	}
   219  
   220  	// If the fixes only delete code, assume that the diagnostic is reporting dead code.
   221  	if onlyDeletions(diag.SuggestedFixes) {
   222  		diag.Tags = append(diag.Tags, protocol.Unnecessary)
   223  	}
   224  	return diag
   225  }
   226  
   227  // onlyDeletions returns true if fixes is non-empty and all of the suggested
   228  // fixes are deletions.
   229  func onlyDeletions(fixes []SuggestedFix) bool {
   230  	for _, fix := range fixes {
   231  		if fix.Command != nil {
   232  			return false
   233  		}
   234  		for _, edits := range fix.Edits {
   235  			for _, edit := range edits {
   236  				if edit.NewText != "" {
   237  					return false
   238  				}
   239  				if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 {
   240  					return false
   241  				}
   242  			}
   243  		}
   244  	}
   245  	return len(fixes) > 0
   246  }
   247  
   248  func typesCodeHref(linkTarget string, code typesinternal.ErrorCode) string {
   249  	return BuildLink(linkTarget, "cuelang.org/go/internal/golangorgx/tools/typesinternal", code.String())
   250  }
   251  
   252  // BuildLink constructs a URL with the given target, path, and anchor.
   253  func BuildLink(target, path, anchor string) string {
   254  	link := fmt.Sprintf("https://%s/%s", target, path)
   255  	if anchor == "" {
   256  		return link
   257  	}
   258  	return link + "#" + anchor
   259  }
   260  
   261  func parseGoListError(e packages.Error, dir string) (filename string, line, col8 int) {
   262  	input := e.Pos
   263  	if input == "" {
   264  		// No position. Attempt to parse one out of a
   265  		// go list error of the form "file:line:col:
   266  		// message" by stripping off the message.
   267  		input = strings.TrimSpace(e.Msg)
   268  		if i := strings.Index(input, ": "); i >= 0 {
   269  			input = input[:i]
   270  		}
   271  	}
   272  
   273  	filename, line, col8 = splitFileLineCol(input)
   274  	if !filepath.IsAbs(filename) {
   275  		filename = filepath.Join(dir, filename)
   276  	}
   277  	return filename, line, col8
   278  }
   279  
   280  // splitFileLineCol splits s into "filename:line:col",
   281  // where line and col consist of decimal digits.
   282  func splitFileLineCol(s string) (file string, line, col8 int) {
   283  	// Beware that the filename may contain colon on Windows.
   284  
   285  	// stripColonDigits removes a ":%d" suffix, if any.
   286  	stripColonDigits := func(s string) (rest string, num int) {
   287  		if i := strings.LastIndex(s, ":"); i >= 0 {
   288  			if v, err := strconv.ParseInt(s[i+1:], 10, 32); err == nil {
   289  				return s[:i], int(v)
   290  			}
   291  		}
   292  		return s, -1
   293  	}
   294  
   295  	// strip col ":%d"
   296  	s, n1 := stripColonDigits(s)
   297  	if n1 < 0 {
   298  		return s, 0, 0 // "filename"
   299  	}
   300  
   301  	// strip line ":%d"
   302  	s, n2 := stripColonDigits(s)
   303  	if n2 < 0 {
   304  		return s, n1, 0 // "filename:line"
   305  	}
   306  
   307  	return s, n2, n1 // "filename:line:col"
   308  }