support diff options
This commit is contained in:
		
							parent
							
								
									393733fa6a
								
							
						
					
					
						commit
						073ad51ed9
					
				
							
								
								
									
										14
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							@ -11,12 +11,6 @@
 | 
			
		||||
  revision = "a9de4d6c1589158e002cc336c495bf11fbf3ea06"
 | 
			
		||||
  source = "github.com/golangci/gas"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/bradleyfalzon/revgrep"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "c04006dc3307c8768bda7a33c7c15d1c6f664e14"
 | 
			
		||||
  version = "v0.3"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/davecgh/go-spew"
 | 
			
		||||
  packages = ["spew"]
 | 
			
		||||
@ -121,6 +115,12 @@
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "b1d89398deca2fd3f8578e5a9551e819bd01ca5f"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/golangci/revgrep"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "dfd919b445ba350862d7a6e7fe81989000b84020"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/golangci/unconvert"
 | 
			
		||||
@ -296,6 +296,6 @@
 | 
			
		||||
[solve-meta]
 | 
			
		||||
  analyzer-name = "dep"
 | 
			
		||||
  analyzer-version = 1
 | 
			
		||||
  inputs-digest = "f77d7467814715b13829f36bfcd792b9d8e18c1194f44e41ba9967c2c4397374"
 | 
			
		||||
  inputs-digest = "76708ccce918c7352a0a1ac338ffaae892409a10ef8c7cf23724bf56e7877caf"
 | 
			
		||||
  solver-name = "gps-cdcl"
 | 
			
		||||
  solver-version = 1
 | 
			
		||||
 | 
			
		||||
@ -26,8 +26,8 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/bradleyfalzon/revgrep"
 | 
			
		||||
  version = "0.3.0"
 | 
			
		||||
  name = "github.com/golangci/revgrep"
 | 
			
		||||
  branch = "master"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/stretchr/testify"
 | 
			
		||||
 | 
			
		||||
@ -83,6 +83,10 @@ func (e *Executor) initRun() {
 | 
			
		||||
	runCmd.Flags().StringSliceVarP(&rc.ExcludePatterns, "exclude", "e", config.DefaultExcludePatterns, "Exclude issue by regexp")
 | 
			
		||||
 | 
			
		||||
	runCmd.Flags().IntVar(&rc.MaxIssuesPerLinter, "max-issues-per-linter", 50, "Maximum issues count per one linter. Set to 0 to disable")
 | 
			
		||||
 | 
			
		||||
	runCmd.Flags().BoolVarP(&rc.Diff, "new", "n", false, "Show only new issues: if there are unstaged changes or untracked files, only those changes are shown, else only changes in HEAD~ are shown")
 | 
			
		||||
	runCmd.Flags().StringVar(&rc.DiffFromRevision, "new-from-rev", "", "Show only new issues created after git revision `REV`")
 | 
			
		||||
	runCmd.Flags().StringVar(&rc.DiffPatchFilePath, "new-from-patch", "", "Show only new issues created in git patch with file path `PATH`")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isFullImportNeeded(linters []pkg.Linter) bool {
 | 
			
		||||
@ -196,10 +200,11 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (chan result.
 | 
			
		||||
 | 
			
		||||
	runner := pkg.SimpleRunner{
 | 
			
		||||
		Processors: []processors.Processor{
 | 
			
		||||
			processors.NewMaxPerFileFromLinter(),
 | 
			
		||||
			processors.NewExclude(fmt.Sprintf("(%s)", strings.Join(e.cfg.Run.ExcludePatterns, "|"))),
 | 
			
		||||
			processors.NewNolint(lintCtx.Program.Fset),
 | 
			
		||||
			processors.NewUniqByLine(),
 | 
			
		||||
			processors.NewDiff(e.cfg.Run.Diff, e.cfg.Run.DiffFromRevision, e.cfg.Run.DiffPatchFilePath),
 | 
			
		||||
			processors.NewMaxPerFileFromLinter(),
 | 
			
		||||
			processors.NewMaxFromLinter(e.cfg.Run.MaxIssuesPerLinter),
 | 
			
		||||
			processors.NewPathPrettifier(),
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,10 @@ type Run struct {
 | 
			
		||||
	Deadline time.Duration
 | 
			
		||||
 | 
			
		||||
	MaxIssuesPerLinter int
 | 
			
		||||
 | 
			
		||||
	DiffFromRevision  string
 | 
			
		||||
	DiffPatchFilePath string
 | 
			
		||||
	Diff              bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,14 @@ type Issue struct {
 | 
			
		||||
	HunkPos    int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i Issue) FilePath() string {
 | 
			
		||||
	return i.File
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i Issue) Line() int {
 | 
			
		||||
	return i.LineNumber
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewIssue(fromLinter, text, file string, lineNumber, hunkPos int) Issue {
 | 
			
		||||
	return Issue{
 | 
			
		||||
		FromLinter: fromLinter,
 | 
			
		||||
 | 
			
		||||
@ -1,92 +1,66 @@
 | 
			
		||||
package processors
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/bradleyfalzon/revgrep"
 | 
			
		||||
	"github.com/golangci/golangci-lint/pkg/result"
 | 
			
		||||
	"github.com/golangci/revgrep"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DiffProcessor struct {
 | 
			
		||||
	patch string
 | 
			
		||||
type Diff struct {
 | 
			
		||||
	onlyNew       bool
 | 
			
		||||
	fromRev       string
 | 
			
		||||
	patchFilePath string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewDiffProcessor(patch string) *DiffProcessor {
 | 
			
		||||
	return &DiffProcessor{
 | 
			
		||||
		patch: patch,
 | 
			
		||||
var _ Processor = Diff{}
 | 
			
		||||
 | 
			
		||||
func NewDiff(onlyNew bool, fromRev, patchFilePath string) *Diff {
 | 
			
		||||
	return &Diff{
 | 
			
		||||
		onlyNew:       onlyNew,
 | 
			
		||||
		fromRev:       fromRev,
 | 
			
		||||
		patchFilePath: patchFilePath,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p DiffProcessor) Name() string {
 | 
			
		||||
func (p Diff) Name() string {
 | 
			
		||||
	return "diff"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p DiffProcessor) processResult(res result.Result) (*result.Result, error) {
 | 
			
		||||
	// Make mapping to restore original issues metadata later
 | 
			
		||||
	fli := makeFilesToLinesToIssuesMap([]result.Result{res})
 | 
			
		||||
 | 
			
		||||
	rIssues, err := p.runRevgrepOnIssues(res.Issues)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
func (p Diff) Process(issues []result.Issue) ([]result.Issue, error) {
 | 
			
		||||
	if !p.onlyNew && p.fromRev == "" && p.patchFilePath == "" { // no need to work
 | 
			
		||||
		return issues, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	newIssues := []result.Issue{}
 | 
			
		||||
	for _, ri := range rIssues {
 | 
			
		||||
		if fli[ri.File] == nil {
 | 
			
		||||
			return nil, fmt.Errorf("can't get original issue file for %v", ri)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		oi := fli[ri.File][ri.LineNo]
 | 
			
		||||
		if len(oi) != 1 {
 | 
			
		||||
			return nil, fmt.Errorf("can't get original issue for %v: %v", ri, oi)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		i := result.Issue{
 | 
			
		||||
			File:       ri.File,
 | 
			
		||||
			LineNumber: ri.LineNo,
 | 
			
		||||
			Text:       ri.Message,
 | 
			
		||||
			HunkPos:    ri.HunkPos,
 | 
			
		||||
			FromLinter: oi[0].FromLinter,
 | 
			
		||||
		}
 | 
			
		||||
		newIssues = append(newIssues, i)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.Issues = newIssues
 | 
			
		||||
	return &res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p DiffProcessor) Process(results []result.Result) ([]result.Result, error) {
 | 
			
		||||
	retResults := []result.Result{}
 | 
			
		||||
	for _, res := range results {
 | 
			
		||||
		newRes, err := p.processResult(res)
 | 
			
		||||
	var patchReader io.Reader
 | 
			
		||||
	if p.patchFilePath != "" {
 | 
			
		||||
		patch, err := ioutil.ReadFile(p.patchFilePath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("can't filter only new issues for result %+v: %s", res, err)
 | 
			
		||||
			return nil, fmt.Errorf("can't read from pathc file %s: %s", p.patchFilePath, err)
 | 
			
		||||
		}
 | 
			
		||||
		retResults = append(retResults, *newRes)
 | 
			
		||||
		patchReader = bytes.NewReader(patch)
 | 
			
		||||
	}
 | 
			
		||||
	c := revgrep.Checker{
 | 
			
		||||
		Patch:        patchReader,
 | 
			
		||||
		RevisionFrom: p.fromRev,
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.Prepare(); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("can't prepare diff by revgrep: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return retResults, nil
 | 
			
		||||
	return transformIssues(issues, func(i *result.Issue) *result.Issue {
 | 
			
		||||
		hunkPos, isNew := c.IsNewIssue(i)
 | 
			
		||||
		if !isNew {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newI := *i
 | 
			
		||||
		newI.HunkPos = hunkPos
 | 
			
		||||
		return &newI
 | 
			
		||||
	}), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p DiffProcessor) runRevgrepOnIssues(issues []result.Issue) ([]revgrep.Issue, error) {
 | 
			
		||||
	// TODO: change revgrep to accept interface with line number, file name
 | 
			
		||||
	fakeIssuesLines := []string{}
 | 
			
		||||
	for _, i := range issues {
 | 
			
		||||
		line := fmt.Sprintf("%s:%d:%d: %s", i.File, i.LineNumber, 0, i.Text)
 | 
			
		||||
		fakeIssuesLines = append(fakeIssuesLines, line)
 | 
			
		||||
	}
 | 
			
		||||
	fakeIssuesOut := strings.Join(fakeIssuesLines, "\n")
 | 
			
		||||
 | 
			
		||||
	checker := revgrep.Checker{
 | 
			
		||||
		Patch:  strings.NewReader(p.patch),
 | 
			
		||||
		Regexp: `^([^:]+):(\d+):(\d+)?:?\s*(.*)$`,
 | 
			
		||||
	}
 | 
			
		||||
	rIssues, err := checker.Check(strings.NewReader(fakeIssuesOut), ioutil.Discard)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("can't filter only new issues by revgrep: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return rIssues, nil
 | 
			
		||||
}
 | 
			
		||||
func (Diff) Finish() {}
 | 
			
		||||
 | 
			
		||||
@ -2,23 +2,6 @@ package processors
 | 
			
		||||
 | 
			
		||||
import "github.com/golangci/golangci-lint/pkg/result"
 | 
			
		||||
 | 
			
		||||
type linesToIssuesMap map[int][]result.Issue
 | 
			
		||||
type filesToLinesToIssuesMap map[string]linesToIssuesMap
 | 
			
		||||
 | 
			
		||||
func makeFilesToLinesToIssuesMap(results []result.Result) filesToLinesToIssuesMap {
 | 
			
		||||
	fli := filesToLinesToIssuesMap{}
 | 
			
		||||
	for _, res := range results {
 | 
			
		||||
		for _, i := range res.Issues {
 | 
			
		||||
			if fli[i.File] == nil {
 | 
			
		||||
				fli[i.File] = linesToIssuesMap{}
 | 
			
		||||
			}
 | 
			
		||||
			li := fli[i.File]
 | 
			
		||||
			li[i.LineNumber] = append(li[i.LineNumber], i)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fli
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func filterIssues(issues []result.Issue, filter func(i *result.Issue) bool) []result.Issue {
 | 
			
		||||
	retIssues := make([]result.Issue, 0, len(issues))
 | 
			
		||||
	for _, i := range issues {
 | 
			
		||||
 | 
			
		||||
@ -40,6 +40,9 @@ type Checker struct {
 | 
			
		||||
	// relative in order to match patch file. If not set, current working
 | 
			
		||||
	// directory is used.
 | 
			
		||||
	AbsPath string
 | 
			
		||||
 | 
			
		||||
	// Calculated changes for next calls to IsNewIssue
 | 
			
		||||
	changes map[string][]pos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Issue contains metadata about an issue found.
 | 
			
		||||
@ -61,6 +64,78 @@ type Issue struct {
 | 
			
		||||
	Message string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Checker) preparePatch() error {
 | 
			
		||||
	// Check if patch is supplied, if not, retrieve from VCS
 | 
			
		||||
	if c.Patch == nil {
 | 
			
		||||
		var err error
 | 
			
		||||
		c.Patch, c.NewFiles, err = GitPatch(c.RevisionFrom, c.RevisionTo)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("could not read git repo: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		if c.Patch == nil {
 | 
			
		||||
			return errors.New("no version control repository found")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InputIssue interface {
 | 
			
		||||
	FilePath() string
 | 
			
		||||
	Line() int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type simpleInputIssue struct {
 | 
			
		||||
	filePath   string
 | 
			
		||||
	lineNumber int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i simpleInputIssue) FilePath() string {
 | 
			
		||||
	return i.filePath
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i simpleInputIssue) Line() int {
 | 
			
		||||
	return i.lineNumber
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Checker) Prepare() error {
 | 
			
		||||
	returnErr := c.preparePatch()
 | 
			
		||||
	c.changes = c.linesChanged()
 | 
			
		||||
	return returnErr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Checker) IsNewIssue(i InputIssue) (hunkPos int, isNew bool) {
 | 
			
		||||
	fchanges, ok := c.changes[i.FilePath()]
 | 
			
		||||
	if !ok { // file wasn't changed
 | 
			
		||||
		return 0, false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		fpos    pos
 | 
			
		||||
		changed bool
 | 
			
		||||
	)
 | 
			
		||||
	// found file, see if lines matched
 | 
			
		||||
	for _, pos := range fchanges {
 | 
			
		||||
		if pos.lineNo == int(i.Line()) {
 | 
			
		||||
			fpos = pos
 | 
			
		||||
			changed = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if changed || fchanges == nil {
 | 
			
		||||
		// either file changed or it's a new file
 | 
			
		||||
		hunkPos := fpos.lineNo
 | 
			
		||||
		if changed { // existing file changed
 | 
			
		||||
			hunkPos = fpos.hunkPos
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return hunkPos, true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check scans reader and writes any lines to writer that have been added in
 | 
			
		||||
// Checker.Patch.
 | 
			
		||||
//
 | 
			
		||||
@ -72,22 +147,8 @@ type Issue struct {
 | 
			
		||||
// File paths in reader must be relative to current working directory or
 | 
			
		||||
// absolute.
 | 
			
		||||
func (c Checker) Check(reader io.Reader, writer io.Writer) (issues []Issue, err error) {
 | 
			
		||||
	// Check if patch is supplied, if not, retrieve from VCS
 | 
			
		||||
	var (
 | 
			
		||||
		writeAll  bool
 | 
			
		||||
		returnErr error
 | 
			
		||||
	)
 | 
			
		||||
	if c.Patch == nil {
 | 
			
		||||
		c.Patch, c.NewFiles, err = GitPatch(c.RevisionFrom, c.RevisionTo)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			writeAll = true
 | 
			
		||||
			returnErr = fmt.Errorf("could not read git repo: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		if c.Patch == nil {
 | 
			
		||||
			writeAll = true
 | 
			
		||||
			returnErr = errors.New("no version control repository found")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	returnErr := c.Prepare()
 | 
			
		||||
	writeAll := returnErr != nil
 | 
			
		||||
 | 
			
		||||
	// file.go:lineNo:colNo:message
 | 
			
		||||
	// colNo is optional, strip spaces before message
 | 
			
		||||
@ -101,8 +162,7 @@ func (c Checker) Check(reader io.Reader, writer io.Writer) (issues []Issue, err
 | 
			
		||||
 | 
			
		||||
	// TODO consider lazy loading this, if there's nothing in stdin, no point
 | 
			
		||||
	// checking for recent changes
 | 
			
		||||
	linesChanged := c.linesChanged()
 | 
			
		||||
	c.debugf("lines changed: %+v", linesChanged)
 | 
			
		||||
	c.debugf("lines changed: %+v", c.changes)
 | 
			
		||||
 | 
			
		||||
	absPath := c.AbsPath
 | 
			
		||||
	if absPath == "" {
 | 
			
		||||
@ -154,38 +214,23 @@ func (c Checker) Check(reader io.Reader, writer io.Writer) (issues []Issue, err
 | 
			
		||||
		msg := string(line[4])
 | 
			
		||||
 | 
			
		||||
		c.debugf("path: %q, lineNo: %v, colNo: %v, msg: %q", path, lno, cno, msg)
 | 
			
		||||
 | 
			
		||||
		var (
 | 
			
		||||
			fpos    pos
 | 
			
		||||
			changed bool
 | 
			
		||||
		)
 | 
			
		||||
		if fchanges, ok := linesChanged[path]; ok {
 | 
			
		||||
			// found file, see if lines matched
 | 
			
		||||
			for _, pos := range fchanges {
 | 
			
		||||
				if pos.lineNo == int(lno) {
 | 
			
		||||
					fpos = pos
 | 
			
		||||
					changed = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if changed || fchanges == nil {
 | 
			
		||||
				// either file changed or it's a new file
 | 
			
		||||
				issue := Issue{
 | 
			
		||||
					File:    path,
 | 
			
		||||
					LineNo:  fpos.lineNo,
 | 
			
		||||
					ColNo:   int(cno),
 | 
			
		||||
					HunkPos: fpos.lineNo,
 | 
			
		||||
					Issue:   scanner.Text(),
 | 
			
		||||
					Message: msg,
 | 
			
		||||
				}
 | 
			
		||||
				if changed {
 | 
			
		||||
					// existing file changed
 | 
			
		||||
					issue.HunkPos = fpos.hunkPos
 | 
			
		||||
				}
 | 
			
		||||
				issues = append(issues, issue)
 | 
			
		||||
				fmt.Fprintln(writer, scanner.Text())
 | 
			
		||||
			}
 | 
			
		||||
		i := simpleInputIssue{
 | 
			
		||||
			filePath:   path,
 | 
			
		||||
			lineNumber: int(lno),
 | 
			
		||||
		}
 | 
			
		||||
		if !changed {
 | 
			
		||||
		hunkPos, changed := c.IsNewIssue(i)
 | 
			
		||||
		if changed {
 | 
			
		||||
			issue := Issue{
 | 
			
		||||
				File:    path,
 | 
			
		||||
				LineNo:  int(lno),
 | 
			
		||||
				ColNo:   int(cno),
 | 
			
		||||
				HunkPos: hunkPos,
 | 
			
		||||
				Issue:   scanner.Text(),
 | 
			
		||||
				Message: msg,
 | 
			
		||||
			}
 | 
			
		||||
			issues = append(issues, issue)
 | 
			
		||||
			fmt.Fprintln(writer, scanner.Text())
 | 
			
		||||
		} else {
 | 
			
		||||
			c.debugf("unchanged: %s", scanner.Text())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user