diff --git a/internal/commands/run.go b/internal/commands/run.go index b1ee9e0e..29e9ec4a 100644 --- a/internal/commands/run.go +++ b/internal/commands/run.go @@ -86,6 +86,7 @@ func (e *Executor) initRun() { fmt.Sprintf("Use or not use default excludes: (%s)", strings.Join(config.DefaultExcludePatterns, "|"))) runCmd.Flags().IntVar(&rc.MaxIssuesPerLinter, "max-issues-per-linter", 50, "Maximum issues count per one linter. Set to 0 to disable") + runCmd.Flags().IntVar(&rc.MaxSameIssues, "max-same-issues", 3, "Maximum count of issues with the same text. 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`") @@ -225,6 +226,7 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (chan result. processors.NewDiff(e.cfg.Run.Diff, e.cfg.Run.DiffFromRevision, e.cfg.Run.DiffPatchFilePath), processors.NewMaxPerFileFromLinter(), processors.NewMaxFromLinter(e.cfg.Run.MaxIssuesPerLinter), + processors.NewMaxSameIssues(e.cfg.Run.MaxSameIssues), processors.NewPathPrettifier(), }, } diff --git a/pkg/config/config.go b/pkg/config/config.go index d1b963e3..8a1cd479 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -15,12 +15,21 @@ const ( var OutFormats = []string{OutFormatColoredLineNumber, OutFormatLineNumber, OutFormatJSON} var DefaultExcludePatterns = []string{ - "Error return value of `(os\\.Std(out|err)\\.Write|.*\\.Close)` is not checked", + // errcheck + "Error return value of .(os\\.Std(out|err)\\.*|.*\\.Close|std(out|err)\\..*|os\\.Remove(All)?|.*[pP]rintf?). is not checked", + + // golint "should have comment", "comment on exported method", - "G104", // disable what errcheck does: it reports on Close etc - "G204", // Subprocess launching should be audited: too lot false positives - "G304", // Potential file inclusion via variable: `src, err := ioutil.ReadFile(filename)` + + // gas + "G103:", // Use of unsafe calls should be audited + "G104:", // disable what errcheck does: it reports on Close etc + "G204:", // Subprocess launching should be audited: too lot false positives + "G304:", // Potential file inclusion via variable: `src, err := ioutil.ReadFile(filename)` + + // govet + "possible misuse of unsafe.Pointer", } type Common struct { @@ -92,6 +101,7 @@ type Run struct { // nolint:maligned Deadline time.Duration MaxIssuesPerLinter int + MaxSameIssues int DiffFromRevision string DiffPatchFilePath string diff --git a/pkg/printers/text.go b/pkg/printers/text.go index d6cac7ce..35203b0c 100644 --- a/pkg/printers/text.go +++ b/pkg/printers/text.go @@ -75,6 +75,10 @@ func (p Text) Print(issues chan result.Issue) (bool, error) { lineRange := i.GetLineRange() for line := lineRange.From; line <= lineRange.To; line++ { + if line == 0 { // some linters, e.g. gas can do it: it really means first line + line = 1 + } + zeroIndexedLine := line - 1 if zeroIndexedLine >= len(fc) { logrus.Warnf("No line %d in file %s", line, i.FilePath()) diff --git a/pkg/result/processors/max_same_issues.go b/pkg/result/processors/max_same_issues.go new file mode 100644 index 00000000..ceab692b --- /dev/null +++ b/pkg/result/processors/max_same_issues.go @@ -0,0 +1,46 @@ +package processors + +import ( + "github.com/golangci/golangci-lint/pkg/result" + "github.com/sirupsen/logrus" +) + +type textToCountMap map[string]int + +type MaxSameIssues struct { + tc textToCountMap + limit int +} + +var _ Processor = &MaxSameIssues{} + +func NewMaxSameIssues(limit int) *MaxSameIssues { + return &MaxSameIssues{ + tc: textToCountMap{}, + limit: limit, + } +} + +func (MaxSameIssues) Name() string { + return "max_same_issues" +} + +func (p *MaxSameIssues) Process(issues []result.Issue) ([]result.Issue, error) { + if p.limit <= 0 { // no limit + return issues, nil + } + + return filterIssues(issues, func(i *result.Issue) bool { + p.tc[i.Text]++ // always inc for stat + return p.tc[i.Text] <= p.limit + }), nil +} + +func (p MaxSameIssues) Finish() { + for text, count := range p.tc { + if count > p.limit { + logrus.Infof("%d/%d issues with text %q were hidden, use --max-same-issues", + count-p.limit, count, text) + } + } +} diff --git a/pkg/result/processors/max_same_issues_test.go b/pkg/result/processors/max_same_issues_test.go new file mode 100644 index 00000000..7f3486e2 --- /dev/null +++ b/pkg/result/processors/max_same_issues_test.go @@ -0,0 +1,21 @@ +package processors + +import ( + "testing" + + "github.com/golangci/golangci-lint/pkg/result" +) + +func TestMaxSameIssues(t *testing.T) { + p := NewMaxSameIssues(1) + i1 := result.Issue{ + Text: "1", + } + i2 := result.Issue{ + Text: "2", + } + + processAssertSame(t, p, i1) // ok + processAssertSame(t, p, i2) // ok: another + processAssertEmpty(t, p, i1) // skip +} diff --git a/pkg/result/processors/nolint.go b/pkg/result/processors/nolint.go index 170bd092..da28d6c2 100644 --- a/pkg/result/processors/nolint.go +++ b/pkg/result/processors/nolint.go @@ -1,10 +1,13 @@ package processors import ( + "bufio" + "bytes" "fmt" "go/ast" "go/parser" "go/token" + "io/ioutil" "strings" "github.com/golangci/golangci-lint/pkg/result" @@ -15,17 +18,21 @@ type comment struct { line int } type fileComments []comment -type commentsCache map[string]fileComments +type fileData struct { + comments fileComments + isGenerated bool +} +type filesCache map[string]*fileData type Nolint struct { fset *token.FileSet - cache commentsCache + cache filesCache } func NewNolint(fset *token.FileSet) *Nolint { return &Nolint{ fset: fset, - cache: commentsCache{}, + cache: filesCache{}, } } @@ -39,19 +46,53 @@ func (p *Nolint) Process(issues []result.Issue) ([]result.Issue, error) { return filterIssuesErr(issues, p.shouldPassIssue) } +var ( + genHdr = []byte("// Code generated ") + genFtr = []byte(" DO NOT EDIT.") +) + +// isGenerated reports whether the source file is generated code +// according the rules from https://golang.org/s/generatedcode. +func isGenerated(src []byte) bool { + sc := bufio.NewScanner(bytes.NewReader(src)) + for sc.Scan() { + b := sc.Bytes() + if bytes.HasPrefix(b, genHdr) && bytes.HasSuffix(b, genFtr) && len(b) >= len(genHdr)+len(genFtr) { + return true + } + } + return false +} + func (p *Nolint) shouldPassIssue(i *result.Issue) (bool, error) { - comments := p.cache[i.FilePath()] - if comments == nil { - file, err := parser.ParseFile(p.fset, i.FilePath(), nil, parser.ParseComments) + fd := p.cache[i.FilePath()] + if fd == nil { + fd = &fileData{} + p.cache[i.FilePath()] = fd + + src, err := ioutil.ReadFile(i.FilePath()) + if err != nil { + return false, fmt.Errorf("can't read file %s: %s", i.FilePath(), err) + } + + fd.isGenerated = isGenerated(src) + if fd.isGenerated { // don't report issues for autogenerated files + return false, nil + } + + file, err := parser.ParseFile(p.fset, i.FilePath(), src, parser.ParseComments) if err != nil { return false, fmt.Errorf("can't parse file %s", i.FilePath()) } - comments = extractFileComments(p.fset, file.Comments...) - p.cache[i.FilePath()] = comments + fd.comments = extractFileComments(p.fset, file.Comments...) } - for _, comment := range comments { + if fd.isGenerated { // don't report issues for autogenerated files + return false, nil + } + + for _, comment := range fd.comments { if comment.line != i.Line() { continue } diff --git a/pkg/result/processors/nolint_test.go b/pkg/result/processors/nolint_test.go index 775ed6c8..765b2172 100644 --- a/pkg/result/processors/nolint_test.go +++ b/pkg/result/processors/nolint_test.go @@ -29,3 +29,14 @@ func TestNolint(t *testing.T) { processAssertSame(t, p, newNolintFileIssue(1, "golint")) } + +func TestNoIssuesInAutogeneratedFile(t *testing.T) { + i := result.Issue{ + Pos: token.Position{ + Filename: filepath.Join("testdata", "nolint_autogenerated.go"), + Line: 4, + }, + } + p := NewNolint(token.NewFileSet()) + processAssertEmpty(t, p, i) +} diff --git a/pkg/result/processors/testdata/nolint_autogenerated.go b/pkg/result/processors/testdata/nolint_autogenerated.go new file mode 100644 index 00000000..110b77c4 --- /dev/null +++ b/pkg/result/processors/testdata/nolint_autogenerated.go @@ -0,0 +1,4 @@ +// Code generated by ... DO NOT EDIT. +package testdata + +var v int