Skip to content

Commit

Permalink
Support excluding issues by source line regexp
Browse files Browse the repository at this point in the history
See issues.exclude-rules[i].source.
Also introduced file data and file lines cache.
  • Loading branch information
jirfag committed Mar 17, 2019
1 parent 7514bf8 commit 3d2dfac
Show file tree
Hide file tree
Showing 13 changed files with 365 additions and 203 deletions.
26 changes: 10 additions & 16 deletions .golangci.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ issues:
exclude:
- abcdef

# Excluding configuration per-path and per-linter
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test\.go
Expand All @@ -203,28 +203,22 @@ issues:
- dupl
- gosec

# Ease some gocritic warnings on test files.
- path: _test\.go
text: "(unnamedResult|exitAfterDefer)"
linters:
- gocritic

# Exclude known linters from partially hard-vendored code,
# which is impossible to exclude via "nolint" comments.
- path: internal/hmac/
text: "weak cryptographic primitive"
linters:
- gosec
- path: internal/hmac/
text: "Write\\` is not checked"
linters:
- errcheck

# Ease linting on benchmarking code.
- path: cmd/stun-bench/
linters:
- gosec
- errcheck
# Exclude some staticcheck messages
- linters:
- staticcheck
text: "SA9003:"

# Exclude lll issues for long lines with go:generate
- linters:
- lll
source: "^//go:generate "

# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
Expand Down
26 changes: 10 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ issues:
exclude:
- abcdef
# Excluding configuration per-path and per-linter
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test\.go
Expand All @@ -733,28 +733,22 @@ issues:
- dupl
- gosec
# Ease some gocritic warnings on test files.
- path: _test\.go
text: "(unnamedResult|exitAfterDefer)"
linters:
- gocritic
# Exclude known linters from partially hard-vendored code,
# which is impossible to exclude via "nolint" comments.
- path: internal/hmac/
text: "weak cryptographic primitive"
linters:
- gosec
- path: internal/hmac/
text: "Write\\` is not checked"
linters:
- errcheck
# Ease linting on benchmarking code.
- path: cmd/stun-bench/
linters:
- gosec
- errcheck
# Exclude some staticcheck messages
- linters:
- staticcheck
text: "SA9003:"
# Exclude lll issues for long lines with go:generate
- linters:
- lll
source: "^//go:generate "
# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
Expand Down
6 changes: 6 additions & 0 deletions pkg/commands/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/golangci/golangci-lint/pkg/fsutils"

"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/goutil"
"github.com/golangci/golangci-lint/pkg/lint"
Expand All @@ -26,6 +28,8 @@ type Executor struct {
EnabledLintersSet *lintersdb.EnabledSet
contextLoader *lint.ContextLoader
goenv *goutil.Env
fileCache *fsutils.FileCache
lineCache *fsutils.LineCache
}

func NewExecutor(version, commit, date string) *Executor {
Expand Down Expand Up @@ -78,6 +82,8 @@ func NewExecutor(version, commit, date string) *Executor {
lintersdb.NewValidator(e.DBManager), e.log.Child("lintersdb"), e.cfg)
e.goenv = goutil.NewEnv(e.log.Child("goenv"))
e.contextLoader = lint.NewContextLoader(e.cfg, e.log.Child("loader"), e.goenv)
e.fileCache = fsutils.NewFileCache()
e.lineCache = fsutils.NewLineCache(e.fileCache)

return e
}
Expand Down
6 changes: 4 additions & 2 deletions pkg/commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,13 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan resul
}
lintCtx.Log = e.log.Child("linters context")

runner, err := lint.NewRunner(lintCtx.ASTCache, e.cfg, e.log.Child("runner"), e.goenv)
runner, err := lint.NewRunner(lintCtx.ASTCache, e.cfg, e.log.Child("runner"), e.goenv, e.lineCache)
if err != nil {
return nil, err
}

issuesCh := runner.Run(ctx, enabledLinters, lintCtx)
fixer := processors.NewFixer(e.cfg, e.log)
fixer := processors.NewFixer(e.cfg, e.log, e.fileCache)
return fixer.Process(issuesCh), nil
}

Expand Down Expand Up @@ -350,6 +350,8 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
return fmt.Errorf("can't print %d issues: %s", len(issues), err)
}

e.fileCache.PrintStats(e.log)

return nil
}

Expand Down
9 changes: 8 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ type ExcludeRule struct {
Linters []string
Path string
Text string
Source string
}

func validateOptionalRegex(value string) error {
Expand All @@ -252,6 +253,9 @@ func (e ExcludeRule) Validate() error {
if err := validateOptionalRegex(e.Text); err != nil {
return fmt.Errorf("invalid text regex: %v", err)
}
if err := validateOptionalRegex(e.Source); err != nil {
return fmt.Errorf("invalid source regex: %v", err)
}
nonBlank := 0
if len(e.Linters) > 0 {
nonBlank++
Expand All @@ -262,8 +266,11 @@ func (e ExcludeRule) Validate() error {
if e.Text != "" {
nonBlank++
}
if e.Source != "" {
nonBlank++
}
if nonBlank < 2 {
return errors.New("at least 2 of (text, path, linters) should be set")
return errors.New("at least 2 of (text, source, path, linters) should be set")
}
return nil
}
Expand Down
64 changes: 64 additions & 0 deletions pkg/fsutils/filecache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package fsutils

import (
"fmt"
"io/ioutil"

"github.com/golangci/golangci-lint/pkg/logutils"

"github.com/pkg/errors"
)

type FileCache struct {
files map[string][]byte
}

func NewFileCache() *FileCache {
return &FileCache{
files: map[string][]byte{},
}
}

func (fc *FileCache) GetFileBytes(filePath string) ([]byte, error) {
cachedBytes := fc.files[filePath]
if cachedBytes != nil {
return cachedBytes, nil
}

fileBytes, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, errors.Wrapf(err, "can't read file %s", filePath)
}

fc.files[filePath] = fileBytes
return fileBytes, nil
}

func prettifyBytesCount(n int) string {
const (
Multiplexer = 1024
KiB = 1 * Multiplexer
MiB = KiB * Multiplexer
GiB = MiB * Multiplexer
)

if n >= GiB {
return fmt.Sprintf("%.1fGiB", float64(n)/GiB)
}
if n >= MiB {
return fmt.Sprintf("%.1fMiB", float64(n)/MiB)
}
if n >= KiB {
return fmt.Sprintf("%.1fKiB", float64(n)/KiB)
}
return fmt.Sprintf("%dB", n)
}

func (fc *FileCache) PrintStats(log logutils.Log) {
var size int
for _, fileBytes := range fc.files {
size += len(fileBytes)
}

log.Infof("File cache stats: %d entries of total size %s", len(fc.files), prettifyBytesCount(size))
}
69 changes: 69 additions & 0 deletions pkg/fsutils/linecache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package fsutils

import (
"bytes"
"fmt"

"github.com/pkg/errors"
)

type fileLinesCache [][]byte

type LineCache struct {
files map[string]fileLinesCache
fileCache *FileCache
}

func NewLineCache(fc *FileCache) *LineCache {
return &LineCache{
files: map[string]fileLinesCache{},
fileCache: fc,
}
}

// GetLine returns a index1-th (1-based index) line from the file on filePath
func (lc *LineCache) GetLine(filePath string, index1 int) (string, error) {
if index1 == 0 { // some linters, e.g. gosec can do it: it really means first line
index1 = 1
}

rawLine, err := lc.getRawLine(filePath, index1-1)
if err != nil {
return "", err
}

return string(bytes.Trim(rawLine, "\r")), nil
}

func (lc *LineCache) getRawLine(filePath string, index0 int) ([]byte, error) {
fc, err := lc.getFileCache(filePath)
if err != nil {
return nil, errors.Wrapf(err, "failed to get file %s lines cache", filePath)
}

if index0 < 0 {
return nil, fmt.Errorf("invalid file line index0 < 0: %d", index0)
}

if index0 >= len(fc) {
return nil, fmt.Errorf("invalid file line index0 (%d) >= len(fc) (%d)", index0, len(fc))
}

return fc[index0], nil
}

func (lc *LineCache) getFileCache(filePath string) (fileLinesCache, error) {
fc := lc.files[filePath]
if fc != nil {
return fc, nil
}

fileBytes, err := lc.fileCache.GetFileBytes(filePath)
if err != nil {
return nil, errors.Wrapf(err, "can't get file %s bytes from cache", filePath)
}

fc = bytes.Split(fileBytes, []byte("\n"))
lc.files[filePath] = fc
return fc, nil
}
11 changes: 8 additions & 3 deletions pkg/lint/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"sync"
"time"

"github.com/golangci/golangci-lint/pkg/fsutils"

"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/goutil"
"github.com/golangci/golangci-lint/pkg/lint/astcache"
Expand All @@ -25,7 +27,9 @@ type Runner struct {
Log logutils.Log
}

func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, goenv *goutil.Env) (*Runner, error) {
func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, goenv *goutil.Env,
lineCache *fsutils.LineCache) (*Runner, error) {

icfg := cfg.Issues
excludePatterns := icfg.ExcludePatterns
if icfg.UseDefaultExcludes {
Expand Down Expand Up @@ -53,6 +57,7 @@ func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, g
for _, r := range icfg.ExcludeRules {
excludeRules = append(excludeRules, processors.ExcludeRule{
Text: r.Text,
Source: r.Source,
Path: r.Path,
Linters: r.Linters,
})
Expand All @@ -68,15 +73,15 @@ func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, g
processors.NewAutogeneratedExclude(astCache),
processors.NewIdentifierMarker(), // must be befor exclude
processors.NewExclude(excludeTotalPattern),
processors.NewExcludeRules(excludeRules),
processors.NewExcludeRules(excludeRules, lineCache, log.Child("exclude_rules")),
processors.NewNolint(astCache, log.Child("nolint")),

processors.NewUniqByLine(),
processors.NewDiff(icfg.Diff, icfg.DiffFromRevision, icfg.DiffPatchFilePath),
processors.NewMaxPerFileFromLinter(cfg),
processors.NewMaxSameIssues(icfg.MaxSameIssues, log.Child("max_same_issues"), cfg),
processors.NewMaxFromLinter(icfg.MaxIssuesPerLinter, log.Child("max_from_linter"), cfg),
processors.NewSourceCode(log.Child("source_code")),
processors.NewSourceCode(lineCache, log.Child("source_code")),
processors.NewReplacementBuilder(log.Child("replacement_builder")), // must be after source code
processors.NewPathShortener(),
},
Expand Down
Loading

0 comments on commit 3d2dfac

Please sign in to comment.