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