279 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			279 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package golinters
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"go/token"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/pkg/errors"
 | 
						|
	diffpkg "github.com/sourcegraph/go-diff/diff"
 | 
						|
 | 
						|
	"github.com/golangci/golangci-lint/pkg/config"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/lint/linter"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/logutils"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/result"
 | 
						|
)
 | 
						|
 | 
						|
type Change struct {
 | 
						|
	LineRange   result.Range
 | 
						|
	Replacement result.Replacement
 | 
						|
}
 | 
						|
 | 
						|
type diffLineType string
 | 
						|
 | 
						|
const (
 | 
						|
	diffLineAdded    diffLineType = "added"
 | 
						|
	diffLineOriginal diffLineType = "original"
 | 
						|
	diffLineDeleted  diffLineType = "deleted"
 | 
						|
)
 | 
						|
 | 
						|
type diffLine struct {
 | 
						|
	originalNumber int // 1-based original line number
 | 
						|
	typ            diffLineType
 | 
						|
	data           string // "+" or "-" stripped line
 | 
						|
}
 | 
						|
 | 
						|
type hunkChangesParser struct {
 | 
						|
	// needed because we merge currently added lines with the last original line
 | 
						|
	lastOriginalLine *diffLine
 | 
						|
 | 
						|
	// if the first line of diff is an adding we save all additions to replacementLinesToPrepend
 | 
						|
	replacementLinesToPrepend []string
 | 
						|
 | 
						|
	log logutils.Log
 | 
						|
 | 
						|
	lines []diffLine
 | 
						|
 | 
						|
	ret []Change
 | 
						|
}
 | 
						|
 | 
						|
func (p *hunkChangesParser) parseDiffLines(h *diffpkg.Hunk) {
 | 
						|
	lines := bytes.Split(h.Body, []byte{'\n'})
 | 
						|
	currentOriginalLineNumber := int(h.OrigStartLine)
 | 
						|
	var ret []diffLine
 | 
						|
 | 
						|
	for i, line := range lines {
 | 
						|
		dl := diffLine{
 | 
						|
			originalNumber: currentOriginalLineNumber,
 | 
						|
		}
 | 
						|
 | 
						|
		lineStr := string(line)
 | 
						|
 | 
						|
		if strings.HasPrefix(lineStr, "-") {
 | 
						|
			dl.typ = diffLineDeleted
 | 
						|
			dl.data = strings.TrimPrefix(lineStr, "-")
 | 
						|
			currentOriginalLineNumber++
 | 
						|
		} else if strings.HasPrefix(lineStr, "+") {
 | 
						|
			dl.typ = diffLineAdded
 | 
						|
			dl.data = strings.TrimPrefix(lineStr, "+")
 | 
						|
		} else {
 | 
						|
			if i == len(lines)-1 && lineStr == "" {
 | 
						|
				// handle last \n: don't add an empty original line
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			dl.typ = diffLineOriginal
 | 
						|
			dl.data = strings.TrimPrefix(lineStr, " ")
 | 
						|
			currentOriginalLineNumber++
 | 
						|
		}
 | 
						|
 | 
						|
		ret = append(ret, dl)
 | 
						|
	}
 | 
						|
 | 
						|
	p.lines = ret
 | 
						|
}
 | 
						|
 | 
						|
func (p *hunkChangesParser) handleOriginalLine(line diffLine, i *int) {
 | 
						|
	if len(p.replacementLinesToPrepend) == 0 {
 | 
						|
		p.lastOriginalLine = &line
 | 
						|
		*i++
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// check following added lines for the case:
 | 
						|
	// + added line 1
 | 
						|
	// original line
 | 
						|
	// + added line 2
 | 
						|
 | 
						|
	*i++
 | 
						|
	var followingAddedLines []string
 | 
						|
	for ; *i < len(p.lines) && p.lines[*i].typ == diffLineAdded; *i++ {
 | 
						|
		followingAddedLines = append(followingAddedLines, p.lines[*i].data)
 | 
						|
	}
 | 
						|
 | 
						|
	p.ret = append(p.ret, Change{
 | 
						|
		LineRange: result.Range{
 | 
						|
			From: line.originalNumber,
 | 
						|
			To:   line.originalNumber,
 | 
						|
		},
 | 
						|
		Replacement: result.Replacement{
 | 
						|
			NewLines: append(p.replacementLinesToPrepend, append([]string{line.data}, followingAddedLines...)...),
 | 
						|
		},
 | 
						|
	})
 | 
						|
	p.replacementLinesToPrepend = nil
 | 
						|
	p.lastOriginalLine = &line
 | 
						|
}
 | 
						|
 | 
						|
func (p *hunkChangesParser) handleDeletedLines(deletedLines []diffLine, addedLines []string) {
 | 
						|
	change := Change{
 | 
						|
		LineRange: result.Range{
 | 
						|
			From: deletedLines[0].originalNumber,
 | 
						|
			To:   deletedLines[len(deletedLines)-1].originalNumber,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	if len(addedLines) != 0 {
 | 
						|
		//nolint:gocritic
 | 
						|
		change.Replacement.NewLines = append(p.replacementLinesToPrepend, addedLines...)
 | 
						|
		if len(p.replacementLinesToPrepend) != 0 {
 | 
						|
			p.replacementLinesToPrepend = nil
 | 
						|
		}
 | 
						|
 | 
						|
		p.ret = append(p.ret, change)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// delete-only change with possible prepending
 | 
						|
	if len(p.replacementLinesToPrepend) != 0 {
 | 
						|
		change.Replacement.NewLines = p.replacementLinesToPrepend
 | 
						|
		p.replacementLinesToPrepend = nil
 | 
						|
	} else {
 | 
						|
		change.Replacement.NeedOnlyDelete = true
 | 
						|
	}
 | 
						|
 | 
						|
	p.ret = append(p.ret, change)
 | 
						|
}
 | 
						|
 | 
						|
func (p *hunkChangesParser) handleAddedOnlyLines(addedLines []string) {
 | 
						|
	if p.lastOriginalLine == nil {
 | 
						|
		// the first line is added; the diff looks like:
 | 
						|
		// 1. + ...
 | 
						|
		// 2. - ...
 | 
						|
		// or
 | 
						|
		// 1. + ...
 | 
						|
		// 2. ...
 | 
						|
 | 
						|
		p.replacementLinesToPrepend = addedLines
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// add-only change merged into the last original line with possible prepending
 | 
						|
	p.ret = append(p.ret, Change{
 | 
						|
		LineRange: result.Range{
 | 
						|
			From: p.lastOriginalLine.originalNumber,
 | 
						|
			To:   p.lastOriginalLine.originalNumber,
 | 
						|
		},
 | 
						|
		Replacement: result.Replacement{
 | 
						|
			NewLines: append(p.replacementLinesToPrepend, append([]string{p.lastOriginalLine.data}, addedLines...)...),
 | 
						|
		},
 | 
						|
	})
 | 
						|
	p.replacementLinesToPrepend = nil
 | 
						|
}
 | 
						|
 | 
						|
func (p *hunkChangesParser) parse(h *diffpkg.Hunk) []Change {
 | 
						|
	p.parseDiffLines(h)
 | 
						|
 | 
						|
	for i := 0; i < len(p.lines); {
 | 
						|
		line := p.lines[i]
 | 
						|
		if line.typ == diffLineOriginal {
 | 
						|
			p.handleOriginalLine(line, &i)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		var deletedLines []diffLine
 | 
						|
		for ; i < len(p.lines) && p.lines[i].typ == diffLineDeleted; i++ {
 | 
						|
			deletedLines = append(deletedLines, p.lines[i])
 | 
						|
		}
 | 
						|
 | 
						|
		var addedLines []string
 | 
						|
		for ; i < len(p.lines) && p.lines[i].typ == diffLineAdded; i++ {
 | 
						|
			addedLines = append(addedLines, p.lines[i].data)
 | 
						|
		}
 | 
						|
 | 
						|
		if len(deletedLines) != 0 {
 | 
						|
			p.handleDeletedLines(deletedLines, addedLines)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// no deletions, only additions
 | 
						|
		p.handleAddedOnlyLines(addedLines)
 | 
						|
	}
 | 
						|
 | 
						|
	if len(p.replacementLinesToPrepend) != 0 {
 | 
						|
		p.log.Infof("The diff contains only additions: no original or deleted lines: %#v", p.lines)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	return p.ret
 | 
						|
}
 | 
						|
 | 
						|
func getErrorTextForLinter(settings *config.LintersSettings, linterName string) string {
 | 
						|
	text := "File is not formatted"
 | 
						|
	switch linterName {
 | 
						|
	case gciName:
 | 
						|
		text = getErrorTextForGci(settings.Gci)
 | 
						|
	case gofumptName:
 | 
						|
		text = "File is not `gofumpt`-ed"
 | 
						|
		if settings.Gofumpt.ExtraRules {
 | 
						|
			text += " with `-extra`"
 | 
						|
		}
 | 
						|
	case gofmtName:
 | 
						|
		text = "File is not `gofmt`-ed"
 | 
						|
		if settings.Gofmt.Simplify {
 | 
						|
			text += " with `-s`"
 | 
						|
		}
 | 
						|
	case goimportsName:
 | 
						|
		text = "File is not `goimports`-ed"
 | 
						|
		if settings.Goimports.LocalPrefixes != "" {
 | 
						|
			text += " with -local " + settings.Goimports.LocalPrefixes
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return text
 | 
						|
}
 | 
						|
 | 
						|
func extractIssuesFromPatch(patch string, lintCtx *linter.Context, linterName string) ([]result.Issue, error) {
 | 
						|
	diffs, err := diffpkg.ParseMultiFileDiff([]byte(patch))
 | 
						|
	if err != nil {
 | 
						|
		return nil, errors.Wrap(err, "can't parse patch")
 | 
						|
	}
 | 
						|
 | 
						|
	if len(diffs) == 0 {
 | 
						|
		return nil, fmt.Errorf("got no diffs from patch parser: %v", diffs)
 | 
						|
	}
 | 
						|
 | 
						|
	var issues []result.Issue
 | 
						|
	for _, d := range diffs {
 | 
						|
		if len(d.Hunks) == 0 {
 | 
						|
			lintCtx.Log.Warnf("Got no hunks in diff %+v", d)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		for _, hunk := range d.Hunks {
 | 
						|
			p := hunkChangesParser{log: lintCtx.Log}
 | 
						|
 | 
						|
			changes := p.parse(hunk)
 | 
						|
 | 
						|
			for _, change := range changes {
 | 
						|
				change := change // fix scope
 | 
						|
				i := result.Issue{
 | 
						|
					FromLinter: linterName,
 | 
						|
					Pos: token.Position{
 | 
						|
						Filename: d.NewName,
 | 
						|
						Line:     change.LineRange.From,
 | 
						|
					},
 | 
						|
					Text:        getErrorTextForLinter(lintCtx.Settings(), linterName),
 | 
						|
					Replacement: &change.Replacement,
 | 
						|
				}
 | 
						|
				if change.LineRange.From != change.LineRange.To {
 | 
						|
					i.LineRange = &change.LineRange
 | 
						|
				}
 | 
						|
 | 
						|
				issues = append(issues, i)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return issues, nil
 | 
						|
}
 |