 60613dc3eb
			
		
	
	
		60613dc3eb
		
			
		
	
	
	
	
		
			
			* Introduce gci as new linter Signed-off-by: Xiang Dai <long0dai@foxmail.com> * use goimports setting if not specified Signed-off-by: Xiang Dai <long0dai@foxmail.com>
		
			
				
	
	
		
			287 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			287 lines
		
	
	
		
			6.9 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/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'})
 | |
| 	currentOriginalLineNumer := int(h.OrigStartLine)
 | |
| 	var ret []diffLine
 | |
| 
 | |
| 	for i, line := range lines {
 | |
| 		dl := diffLine{
 | |
| 			originalNumber: currentOriginalLineNumer,
 | |
| 		}
 | |
| 
 | |
| 		lineStr := string(line)
 | |
| 
 | |
| 		if strings.HasPrefix(lineStr, "-") {
 | |
| 			dl.typ = diffLineDeleted
 | |
| 			dl.data = strings.TrimPrefix(lineStr, "-")
 | |
| 			currentOriginalLineNumer++
 | |
| 		} 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, " ")
 | |
| 			currentOriginalLineNumer++
 | |
| 		}
 | |
| 
 | |
| 		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) //nolint:scopelint
 | |
| 			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(lintCtx *linter.Context, linterName string) string {
 | |
| 	text := "File is not formatted"
 | |
| 	switch linterName {
 | |
| 	case gofumptName:
 | |
| 		text = "File is not `gofumpt`-ed"
 | |
| 		if lintCtx.Settings().Gofumpt.ExtraRules {
 | |
| 			text += " with `-extra`"
 | |
| 		}
 | |
| 	case gofmtName:
 | |
| 		text = "File is not `gofmt`-ed"
 | |
| 		if lintCtx.Settings().Gofmt.Simplify {
 | |
| 			text += " with `-s`"
 | |
| 		}
 | |
| 	case goimportsName:
 | |
| 		text = "File is not `goimports`-ed"
 | |
| 		if lintCtx.Settings().Goimports.LocalPrefixes != "" {
 | |
| 			text += " with -local " + lintCtx.Settings().Goimports.LocalPrefixes
 | |
| 		}
 | |
| 	case gciName:
 | |
| 		text = "File is not `gci`-ed"
 | |
| 		localPrefixes := lintCtx.Settings().Gci.LocalPrefixes
 | |
| 		goimportsFlag := lintCtx.Settings().Goimports.LocalPrefixes
 | |
| 		if localPrefixes == "" && goimportsFlag != "" {
 | |
| 			localPrefixes = goimportsFlag
 | |
| 		}
 | |
| 
 | |
| 		if localPrefixes != "" {
 | |
| 			text += " with -local " + localPrefixes
 | |
| 		}
 | |
| 	}
 | |
| 	return text
 | |
| }
 | |
| 
 | |
| func extractIssuesFromPatch(patch string, log logutils.Log, 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)
 | |
| 	}
 | |
| 
 | |
| 	issues := []result.Issue{}
 | |
| 	for _, d := range diffs {
 | |
| 		if len(d.Hunks) == 0 {
 | |
| 			log.Warnf("Got no hunks in diff %+v", d)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		for _, hunk := range d.Hunks {
 | |
| 			p := hunkChangesParser{
 | |
| 				log: 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, linterName),
 | |
| 					Replacement: &change.Replacement,
 | |
| 				}
 | |
| 				if change.LineRange.From != change.LineRange.To {
 | |
| 					i.LineRange = &change.LineRange
 | |
| 				}
 | |
| 
 | |
| 				issues = append(issues, i)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return issues, nil
 | |
| }
 |