github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/golinters/gofmt_common.go (about)

     1  package golinters
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/token"
     7  	"strings"
     8  
     9  	"github.com/pkg/errors"
    10  	diffpkg "github.com/sourcegraph/go-diff/diff"
    11  
    12  	"github.com/elek/golangci-lint/pkg/lint/linter"
    13  	"github.com/elek/golangci-lint/pkg/logutils"
    14  	"github.com/elek/golangci-lint/pkg/result"
    15  )
    16  
    17  type Change struct {
    18  	LineRange   result.Range
    19  	Replacement result.Replacement
    20  }
    21  
    22  type diffLineType string
    23  
    24  const (
    25  	diffLineAdded    diffLineType = "added"
    26  	diffLineOriginal diffLineType = "original"
    27  	diffLineDeleted  diffLineType = "deleted"
    28  )
    29  
    30  type diffLine struct {
    31  	originalNumber int // 1-based original line number
    32  	typ            diffLineType
    33  	data           string // "+" or "-" stripped line
    34  }
    35  
    36  type hunkChangesParser struct {
    37  	// needed because we merge currently added lines with the last original line
    38  	lastOriginalLine *diffLine
    39  
    40  	// if the first line of diff is an adding we save all additions to replacementLinesToPrepend
    41  	replacementLinesToPrepend []string
    42  
    43  	log logutils.Log
    44  
    45  	lines []diffLine
    46  
    47  	ret []Change
    48  }
    49  
    50  func (p *hunkChangesParser) parseDiffLines(h *diffpkg.Hunk) {
    51  	lines := bytes.Split(h.Body, []byte{'\n'})
    52  	currentOriginalLineNumer := int(h.OrigStartLine)
    53  	var ret []diffLine
    54  
    55  	for i, line := range lines {
    56  		dl := diffLine{
    57  			originalNumber: currentOriginalLineNumer,
    58  		}
    59  
    60  		lineStr := string(line)
    61  
    62  		if strings.HasPrefix(lineStr, "-") {
    63  			dl.typ = diffLineDeleted
    64  			dl.data = strings.TrimPrefix(lineStr, "-")
    65  			currentOriginalLineNumer++
    66  		} else if strings.HasPrefix(lineStr, "+") {
    67  			dl.typ = diffLineAdded
    68  			dl.data = strings.TrimPrefix(lineStr, "+")
    69  		} else {
    70  			if i == len(lines)-1 && lineStr == "" {
    71  				// handle last \n: don't add an empty original line
    72  				break
    73  			}
    74  
    75  			dl.typ = diffLineOriginal
    76  			dl.data = strings.TrimPrefix(lineStr, " ")
    77  			currentOriginalLineNumer++
    78  		}
    79  
    80  		ret = append(ret, dl)
    81  	}
    82  
    83  	p.lines = ret
    84  }
    85  
    86  func (p *hunkChangesParser) handleOriginalLine(line diffLine, i *int) {
    87  	if len(p.replacementLinesToPrepend) == 0 {
    88  		p.lastOriginalLine = &line
    89  		*i++
    90  		return
    91  	}
    92  
    93  	// check following added lines for the case:
    94  	// + added line 1
    95  	// original line
    96  	// + added line 2
    97  
    98  	*i++
    99  	var followingAddedLines []string
   100  	for ; *i < len(p.lines) && p.lines[*i].typ == diffLineAdded; *i++ {
   101  		followingAddedLines = append(followingAddedLines, p.lines[*i].data)
   102  	}
   103  
   104  	p.ret = append(p.ret, Change{
   105  		LineRange: result.Range{
   106  			From: line.originalNumber,
   107  			To:   line.originalNumber,
   108  		},
   109  		Replacement: result.Replacement{
   110  			NewLines: append(p.replacementLinesToPrepend, append([]string{line.data}, followingAddedLines...)...),
   111  		},
   112  	})
   113  	p.replacementLinesToPrepend = nil
   114  	p.lastOriginalLine = &line
   115  }
   116  
   117  func (p *hunkChangesParser) handleDeletedLines(deletedLines []diffLine, addedLines []string) {
   118  	change := Change{
   119  		LineRange: result.Range{
   120  			From: deletedLines[0].originalNumber,
   121  			To:   deletedLines[len(deletedLines)-1].originalNumber,
   122  		},
   123  	}
   124  
   125  	if len(addedLines) != 0 {
   126  		//nolint:gocritic
   127  		change.Replacement.NewLines = append(p.replacementLinesToPrepend, addedLines...)
   128  		if len(p.replacementLinesToPrepend) != 0 {
   129  			p.replacementLinesToPrepend = nil
   130  		}
   131  
   132  		p.ret = append(p.ret, change)
   133  		return
   134  	}
   135  
   136  	// delete-only change with possible prepending
   137  	if len(p.replacementLinesToPrepend) != 0 {
   138  		change.Replacement.NewLines = p.replacementLinesToPrepend
   139  		p.replacementLinesToPrepend = nil
   140  	} else {
   141  		change.Replacement.NeedOnlyDelete = true
   142  	}
   143  
   144  	p.ret = append(p.ret, change)
   145  }
   146  
   147  func (p *hunkChangesParser) handleAddedOnlyLines(addedLines []string) {
   148  	if p.lastOriginalLine == nil {
   149  		// the first line is added; the diff looks like:
   150  		// 1. + ...
   151  		// 2. - ...
   152  		// or
   153  		// 1. + ...
   154  		// 2. ...
   155  
   156  		p.replacementLinesToPrepend = addedLines
   157  		return
   158  	}
   159  
   160  	// add-only change merged into the last original line with possible prepending
   161  	p.ret = append(p.ret, Change{
   162  		LineRange: result.Range{
   163  			From: p.lastOriginalLine.originalNumber,
   164  			To:   p.lastOriginalLine.originalNumber,
   165  		},
   166  		Replacement: result.Replacement{
   167  			NewLines: append(p.replacementLinesToPrepend, append([]string{p.lastOriginalLine.data}, addedLines...)...),
   168  		},
   169  	})
   170  	p.replacementLinesToPrepend = nil
   171  }
   172  
   173  func (p *hunkChangesParser) parse(h *diffpkg.Hunk) []Change {
   174  	p.parseDiffLines(h)
   175  
   176  	for i := 0; i < len(p.lines); {
   177  		line := p.lines[i]
   178  		if line.typ == diffLineOriginal {
   179  			p.handleOriginalLine(line, &i)
   180  			continue
   181  		}
   182  
   183  		var deletedLines []diffLine
   184  		for ; i < len(p.lines) && p.lines[i].typ == diffLineDeleted; i++ {
   185  			deletedLines = append(deletedLines, p.lines[i])
   186  		}
   187  
   188  		var addedLines []string
   189  		for ; i < len(p.lines) && p.lines[i].typ == diffLineAdded; i++ {
   190  			addedLines = append(addedLines, p.lines[i].data)
   191  		}
   192  
   193  		if len(deletedLines) != 0 {
   194  			p.handleDeletedLines(deletedLines, addedLines)
   195  			continue
   196  		}
   197  
   198  		// no deletions, only additions
   199  		p.handleAddedOnlyLines(addedLines)
   200  	}
   201  
   202  	if len(p.replacementLinesToPrepend) != 0 {
   203  		p.log.Infof("The diff contains only additions: no original or deleted lines: %#v", p.lines)
   204  		return nil
   205  	}
   206  
   207  	return p.ret
   208  }
   209  
   210  func getErrorTextForLinter(lintCtx *linter.Context, linterName string) string {
   211  	text := "File is not formatted"
   212  	switch linterName {
   213  	case gofumptName:
   214  		text = "File is not `gofumpt`-ed"
   215  		if lintCtx.Settings().Gofumpt.ExtraRules {
   216  			text += " with `-extra`"
   217  		}
   218  	case gofmtName:
   219  		text = "File is not `gofmt`-ed"
   220  		if lintCtx.Settings().Gofmt.Simplify {
   221  			text += " with `-s`"
   222  		}
   223  	case goimportsName:
   224  		text = "File is not `goimports`-ed"
   225  		if lintCtx.Settings().Goimports.LocalPrefixes != "" {
   226  			text += " with -local " + lintCtx.Settings().Goimports.LocalPrefixes
   227  		}
   228  	case gciName:
   229  		text = "File is not `gci`-ed"
   230  		localPrefixes := lintCtx.Settings().Gci.LocalPrefixes
   231  		goimportsFlag := lintCtx.Settings().Goimports.LocalPrefixes
   232  		if localPrefixes == "" && goimportsFlag != "" {
   233  			localPrefixes = goimportsFlag
   234  		}
   235  
   236  		if localPrefixes != "" {
   237  			text += " with -local " + localPrefixes
   238  		}
   239  	}
   240  	return text
   241  }
   242  
   243  func extractIssuesFromPatch(patch string, log logutils.Log, lintCtx *linter.Context, linterName string) ([]result.Issue, error) {
   244  	diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch))
   245  	if err != nil {
   246  		return nil, errors.Wrap(err, "can't parse patch")
   247  	}
   248  
   249  	if len(diffs) == 0 {
   250  		return nil, fmt.Errorf("got no diffs from patch parser: %v", diffs)
   251  	}
   252  
   253  	issues := []result.Issue{}
   254  	for _, d := range diffs {
   255  		if len(d.Hunks) == 0 {
   256  			log.Warnf("Got no hunks in diff %+v", d)
   257  			continue
   258  		}
   259  
   260  		for _, hunk := range d.Hunks {
   261  			p := hunkChangesParser{
   262  				log: log,
   263  			}
   264  			changes := p.parse(hunk)
   265  			for _, change := range changes {
   266  				change := change // fix scope
   267  				i := result.Issue{
   268  					FromLinter: linterName,
   269  					Pos: token.Position{
   270  						Filename: d.NewName,
   271  						Line:     change.LineRange.From,
   272  					},
   273  					Text:        getErrorTextForLinter(lintCtx, linterName),
   274  					Replacement: &change.Replacement,
   275  				}
   276  				if change.LineRange.From != change.LineRange.To {
   277  					i.LineRange = &change.LineRange
   278  				}
   279  
   280  				issues = append(issues, i)
   281  			}
   282  		}
   283  	}
   284  
   285  	return issues, nil
   286  }