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})
|
||||
func (p Diff) Process(issues []result.Issue) ([]result.Issue, error) {
|
||||
if !p.onlyNew && p.fromRev == "" && p.patchFilePath == "" { // no need to work
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
rIssues, err := p.runRevgrepOnIssues(res.Issues)
|
||||
var patchReader io.Reader
|
||||
if p.patchFilePath != "" {
|
||||
patch, err := ioutil.ReadFile(p.patchFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("can't read from pathc file %s: %s", p.patchFilePath, err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
return transformIssues(issues, func(i *result.Issue) *result.Issue {
|
||||
hunkPos, isNew := c.IsNewIssue(i)
|
||||
if !isNew {
|
||||
return nil
|
||||
}
|
||||
|
||||
oi := fli[ri.File][ri.LineNo]
|
||||
if len(oi) != 1 {
|
||||
return nil, fmt.Errorf("can't get original issue for %v: %v", ri, oi)
|
||||
newI := *i
|
||||
newI.HunkPos = hunkPos
|
||||
return &newI
|
||||
}), nil
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't filter only new issues for result %+v: %s", res, err)
|
||||
}
|
||||
retResults = append(retResults, *newRes)
|
||||
}
|
||||
|
||||
return retResults, 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
|
||||
i := simpleInputIssue{
|
||||
filePath: path,
|
||||
lineNumber: int(lno),
|
||||
}
|
||||
}
|
||||
if changed || fchanges == nil {
|
||||
// either file changed or it's a new file
|
||||
hunkPos, changed := c.IsNewIssue(i)
|
||||
if changed {
|
||||
issue := Issue{
|
||||
File: path,
|
||||
LineNo: fpos.lineNo,
|
||||
LineNo: int(lno),
|
||||
ColNo: int(cno),
|
||||
HunkPos: fpos.lineNo,
|
||||
HunkPos: hunkPos,
|
||||
Issue: scanner.Text(),
|
||||
Message: msg,
|
||||
}
|
||||
if changed {
|
||||
// existing file changed
|
||||
issue.HunkPos = fpos.hunkPos
|
||||
}
|
||||
issues = append(issues, issue)
|
||||
fmt.Fprintln(writer, scanner.Text())
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
} else {
|
||||
c.debugf("unchanged: %s", scanner.Text())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user