Support excluding issues by source line regexp
See issues.exclude-rules[i].source. Also introduced file data and file lines cache.
This commit is contained in:
parent
7514bf8239
commit
3d2dfac47e
@ -193,7 +193,7 @@ issues:
|
|||||||
exclude:
|
exclude:
|
||||||
- abcdef
|
- abcdef
|
||||||
|
|
||||||
# Excluding configuration per-path and per-linter
|
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
# Exclude some linters from running on tests files.
|
# Exclude some linters from running on tests files.
|
||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
@ -203,28 +203,22 @@ issues:
|
|||||||
- dupl
|
- dupl
|
||||||
- gosec
|
- gosec
|
||||||
|
|
||||||
# Ease some gocritic warnings on test files.
|
|
||||||
- path: _test\.go
|
|
||||||
text: "(unnamedResult|exitAfterDefer)"
|
|
||||||
linters:
|
|
||||||
- gocritic
|
|
||||||
|
|
||||||
# Exclude known linters from partially hard-vendored code,
|
# Exclude known linters from partially hard-vendored code,
|
||||||
# which is impossible to exclude via "nolint" comments.
|
# which is impossible to exclude via "nolint" comments.
|
||||||
- path: internal/hmac/
|
- path: internal/hmac/
|
||||||
text: "weak cryptographic primitive"
|
text: "weak cryptographic primitive"
|
||||||
linters:
|
linters:
|
||||||
- gosec
|
- gosec
|
||||||
- path: internal/hmac/
|
|
||||||
text: "Write\\` is not checked"
|
|
||||||
linters:
|
|
||||||
- errcheck
|
|
||||||
|
|
||||||
# Ease linting on benchmarking code.
|
# Exclude some staticcheck messages
|
||||||
- path: cmd/stun-bench/
|
- linters:
|
||||||
linters:
|
- staticcheck
|
||||||
- gosec
|
text: "SA9003:"
|
||||||
- errcheck
|
|
||||||
|
# Exclude lll issues for long lines with go:generate
|
||||||
|
- linters:
|
||||||
|
- lll
|
||||||
|
source: "^//go:generate "
|
||||||
|
|
||||||
# Independently from option `exclude` we use default exclude patterns,
|
# Independently from option `exclude` we use default exclude patterns,
|
||||||
# it can be disabled by this option. To list all
|
# it can be disabled by this option. To list all
|
||||||
|
26
README.md
26
README.md
@ -723,7 +723,7 @@ issues:
|
|||||||
exclude:
|
exclude:
|
||||||
- abcdef
|
- abcdef
|
||||||
|
|
||||||
# Excluding configuration per-path and per-linter
|
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
# Exclude some linters from running on tests files.
|
# Exclude some linters from running on tests files.
|
||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
@ -733,28 +733,22 @@ issues:
|
|||||||
- dupl
|
- dupl
|
||||||
- gosec
|
- gosec
|
||||||
|
|
||||||
# Ease some gocritic warnings on test files.
|
|
||||||
- path: _test\.go
|
|
||||||
text: "(unnamedResult|exitAfterDefer)"
|
|
||||||
linters:
|
|
||||||
- gocritic
|
|
||||||
|
|
||||||
# Exclude known linters from partially hard-vendored code,
|
# Exclude known linters from partially hard-vendored code,
|
||||||
# which is impossible to exclude via "nolint" comments.
|
# which is impossible to exclude via "nolint" comments.
|
||||||
- path: internal/hmac/
|
- path: internal/hmac/
|
||||||
text: "weak cryptographic primitive"
|
text: "weak cryptographic primitive"
|
||||||
linters:
|
linters:
|
||||||
- gosec
|
- gosec
|
||||||
- path: internal/hmac/
|
|
||||||
text: "Write\\` is not checked"
|
|
||||||
linters:
|
|
||||||
- errcheck
|
|
||||||
|
|
||||||
# Ease linting on benchmarking code.
|
# Exclude some staticcheck messages
|
||||||
- path: cmd/stun-bench/
|
- linters:
|
||||||
linters:
|
- staticcheck
|
||||||
- gosec
|
text: "SA9003:"
|
||||||
- errcheck
|
|
||||||
|
# Exclude lll issues for long lines with go:generate
|
||||||
|
- linters:
|
||||||
|
- lll
|
||||||
|
source: "^//go:generate "
|
||||||
|
|
||||||
# Independently from option `exclude` we use default exclude patterns,
|
# Independently from option `exclude` we use default exclude patterns,
|
||||||
# it can be disabled by this option. To list all
|
# it can be disabled by this option. To list all
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
"github.com/golangci/golangci-lint/pkg/goutil"
|
"github.com/golangci/golangci-lint/pkg/goutil"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint"
|
"github.com/golangci/golangci-lint/pkg/lint"
|
||||||
@ -26,6 +28,8 @@ type Executor struct {
|
|||||||
EnabledLintersSet *lintersdb.EnabledSet
|
EnabledLintersSet *lintersdb.EnabledSet
|
||||||
contextLoader *lint.ContextLoader
|
contextLoader *lint.ContextLoader
|
||||||
goenv *goutil.Env
|
goenv *goutil.Env
|
||||||
|
fileCache *fsutils.FileCache
|
||||||
|
lineCache *fsutils.LineCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExecutor(version, commit, date string) *Executor {
|
func NewExecutor(version, commit, date string) *Executor {
|
||||||
@ -78,6 +82,8 @@ func NewExecutor(version, commit, date string) *Executor {
|
|||||||
lintersdb.NewValidator(e.DBManager), e.log.Child("lintersdb"), e.cfg)
|
lintersdb.NewValidator(e.DBManager), e.log.Child("lintersdb"), e.cfg)
|
||||||
e.goenv = goutil.NewEnv(e.log.Child("goenv"))
|
e.goenv = goutil.NewEnv(e.log.Child("goenv"))
|
||||||
e.contextLoader = lint.NewContextLoader(e.cfg, e.log.Child("loader"), e.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
|
return e
|
||||||
}
|
}
|
||||||
|
@ -278,13 +278,13 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan resul
|
|||||||
}
|
}
|
||||||
lintCtx.Log = e.log.Child("linters context")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuesCh := runner.Run(ctx, enabledLinters, lintCtx)
|
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
|
return fixer.Process(issuesCh), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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)
|
return fmt.Errorf("can't print %d issues: %s", len(issues), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.fileCache.PrintStats(e.log)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +235,7 @@ type ExcludeRule struct {
|
|||||||
Linters []string
|
Linters []string
|
||||||
Path string
|
Path string
|
||||||
Text string
|
Text string
|
||||||
|
Source string
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOptionalRegex(value string) error {
|
func validateOptionalRegex(value string) error {
|
||||||
@ -252,6 +253,9 @@ func (e ExcludeRule) Validate() error {
|
|||||||
if err := validateOptionalRegex(e.Text); err != nil {
|
if err := validateOptionalRegex(e.Text); err != nil {
|
||||||
return fmt.Errorf("invalid text regex: %v", err)
|
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
|
nonBlank := 0
|
||||||
if len(e.Linters) > 0 {
|
if len(e.Linters) > 0 {
|
||||||
nonBlank++
|
nonBlank++
|
||||||
@ -262,8 +266,11 @@ func (e ExcludeRule) Validate() error {
|
|||||||
if e.Text != "" {
|
if e.Text != "" {
|
||||||
nonBlank++
|
nonBlank++
|
||||||
}
|
}
|
||||||
|
if e.Source != "" {
|
||||||
|
nonBlank++
|
||||||
|
}
|
||||||
if nonBlank < 2 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
64
pkg/fsutils/filecache.go
Normal file
64
pkg/fsutils/filecache.go
Normal file
@ -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
pkg/fsutils/linecache.go
Normal file
69
pkg/fsutils/linecache.go
Normal file
@ -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
|
||||||
|
}
|
@ -9,6 +9,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/config"
|
"github.com/golangci/golangci-lint/pkg/config"
|
||||||
"github.com/golangci/golangci-lint/pkg/goutil"
|
"github.com/golangci/golangci-lint/pkg/goutil"
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
"github.com/golangci/golangci-lint/pkg/lint/astcache"
|
||||||
@ -25,7 +27,9 @@ type Runner struct {
|
|||||||
Log logutils.Log
|
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
|
icfg := cfg.Issues
|
||||||
excludePatterns := icfg.ExcludePatterns
|
excludePatterns := icfg.ExcludePatterns
|
||||||
if icfg.UseDefaultExcludes {
|
if icfg.UseDefaultExcludes {
|
||||||
@ -53,6 +57,7 @@ func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, g
|
|||||||
for _, r := range icfg.ExcludeRules {
|
for _, r := range icfg.ExcludeRules {
|
||||||
excludeRules = append(excludeRules, processors.ExcludeRule{
|
excludeRules = append(excludeRules, processors.ExcludeRule{
|
||||||
Text: r.Text,
|
Text: r.Text,
|
||||||
|
Source: r.Source,
|
||||||
Path: r.Path,
|
Path: r.Path,
|
||||||
Linters: r.Linters,
|
Linters: r.Linters,
|
||||||
})
|
})
|
||||||
@ -68,7 +73,7 @@ func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, g
|
|||||||
processors.NewAutogeneratedExclude(astCache),
|
processors.NewAutogeneratedExclude(astCache),
|
||||||
processors.NewIdentifierMarker(), // must be befor exclude
|
processors.NewIdentifierMarker(), // must be befor exclude
|
||||||
processors.NewExclude(excludeTotalPattern),
|
processors.NewExclude(excludeTotalPattern),
|
||||||
processors.NewExcludeRules(excludeRules),
|
processors.NewExcludeRules(excludeRules, lineCache, log.Child("exclude_rules")),
|
||||||
processors.NewNolint(astCache, log.Child("nolint")),
|
processors.NewNolint(astCache, log.Child("nolint")),
|
||||||
|
|
||||||
processors.NewUniqByLine(),
|
processors.NewUniqByLine(),
|
||||||
@ -76,7 +81,7 @@ func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, g
|
|||||||
processors.NewMaxPerFileFromLinter(cfg),
|
processors.NewMaxPerFileFromLinter(cfg),
|
||||||
processors.NewMaxSameIssues(icfg.MaxSameIssues, log.Child("max_same_issues"), cfg),
|
processors.NewMaxSameIssues(icfg.MaxSameIssues, log.Child("max_same_issues"), cfg),
|
||||||
processors.NewMaxFromLinter(icfg.MaxIssuesPerLinter, log.Child("max_from_linter"), 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.NewReplacementBuilder(log.Child("replacement_builder")), // must be after source code
|
||||||
processors.NewPathShortener(),
|
processors.NewPathShortener(),
|
||||||
},
|
},
|
||||||
|
@ -3,11 +3,16 @@ package processors
|
|||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
)
|
)
|
||||||
|
|
||||||
type excludeRule struct {
|
type excludeRule struct {
|
||||||
text *regexp.Regexp
|
text *regexp.Regexp
|
||||||
|
source *regexp.Regexp
|
||||||
path *regexp.Regexp
|
path *regexp.Regexp
|
||||||
linters []string
|
linters []string
|
||||||
}
|
}
|
||||||
@ -16,7 +21,80 @@ func (r *excludeRule) isEmpty() bool {
|
|||||||
return r.text == nil && r.path == nil && len(r.linters) == 0
|
return r.text == nil && r.path == nil && len(r.linters) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r excludeRule) Match(i *result.Issue) bool {
|
type ExcludeRule struct {
|
||||||
|
Text string
|
||||||
|
Source string
|
||||||
|
Path string
|
||||||
|
Linters []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExcludeRules struct {
|
||||||
|
rules []excludeRule
|
||||||
|
lineCache *fsutils.LineCache
|
||||||
|
log logutils.Log
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExcludeRules(rules []ExcludeRule, lineCache *fsutils.LineCache, log logutils.Log) *ExcludeRules {
|
||||||
|
r := &ExcludeRules{
|
||||||
|
lineCache: lineCache,
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rule := range rules {
|
||||||
|
parsedRule := excludeRule{
|
||||||
|
linters: rule.Linters,
|
||||||
|
}
|
||||||
|
if rule.Text != "" {
|
||||||
|
parsedRule.text = regexp.MustCompile("(?i)" + rule.Text)
|
||||||
|
}
|
||||||
|
if rule.Source != "" {
|
||||||
|
parsedRule.source = regexp.MustCompile("(?i)" + rule.Source)
|
||||||
|
}
|
||||||
|
if rule.Path != "" {
|
||||||
|
parsedRule.path = regexp.MustCompile(rule.Path)
|
||||||
|
}
|
||||||
|
r.rules = append(r.rules, parsedRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ExcludeRules) Process(issues []result.Issue) ([]result.Issue, error) {
|
||||||
|
if len(p.rules) == 0 {
|
||||||
|
return issues, nil
|
||||||
|
}
|
||||||
|
return filterIssues(issues, func(i *result.Issue) bool {
|
||||||
|
for _, rule := range p.rules {
|
||||||
|
rule := rule
|
||||||
|
if p.match(i, &rule) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ExcludeRules) matchLinter(i *result.Issue, r *excludeRule) bool {
|
||||||
|
for _, linter := range r.linters {
|
||||||
|
if linter == i.FromLinter {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ExcludeRules) matchSource(i *result.Issue, r *excludeRule) bool { //nolint:interfacer
|
||||||
|
sourceLine, err := p.lineCache.GetLine(i.FilePath(), i.Line())
|
||||||
|
if err != nil {
|
||||||
|
p.log.Warnf("Failed to get line %s:%d from line cache: %s", i.FilePath(), i.Line(), err)
|
||||||
|
return false // can't properly match
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.source.MatchString(sourceLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ExcludeRules) match(i *result.Issue, r *excludeRule) bool {
|
||||||
if r.isEmpty() {
|
if r.isEmpty() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -26,56 +104,16 @@ func (r excludeRule) Match(i *result.Issue) bool {
|
|||||||
if r.path != nil && !r.path.MatchString(i.FilePath()) {
|
if r.path != nil && !r.path.MatchString(i.FilePath()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(r.linters) == 0 {
|
if len(r.linters) != 0 && !p.matchLinter(i, r) {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
for _, l := range r.linters {
|
|
||||||
if l == i.FromLinter {
|
// the most heavyweight checking last
|
||||||
return true
|
if r.source != nil && !p.matchSource(i, r) {
|
||||||
}
|
return false
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExcludeRule struct {
|
return true
|
||||||
Text string
|
|
||||||
Path string
|
|
||||||
Linters []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewExcludeRules(rules []ExcludeRule) *ExcludeRules {
|
|
||||||
r := new(ExcludeRules)
|
|
||||||
for _, rule := range rules {
|
|
||||||
parsedRule := excludeRule{
|
|
||||||
linters: rule.Linters,
|
|
||||||
}
|
|
||||||
if rule.Text != "" {
|
|
||||||
parsedRule.text = regexp.MustCompile("(?i)" + rule.Text)
|
|
||||||
}
|
|
||||||
if rule.Path != "" {
|
|
||||||
parsedRule.path = regexp.MustCompile(rule.Path)
|
|
||||||
}
|
|
||||||
r.rules = append(r.rules, parsedRule)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExcludeRules struct {
|
|
||||||
rules []excludeRule
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r ExcludeRules) Process(issues []result.Issue) ([]result.Issue, error) {
|
|
||||||
if len(r.rules) == 0 {
|
|
||||||
return issues, nil
|
|
||||||
}
|
|
||||||
return filterIssues(issues, func(i *result.Issue) bool {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
if rule.Match(i) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ExcludeRules) Name() string { return "exclude-rules" }
|
func (ExcludeRules) Name() string { return "exclude-rules" }
|
||||||
|
@ -2,97 +2,108 @@ package processors
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExcludeRules(t *testing.T) {
|
func TestExcludeRulesMultiple(t *testing.T) {
|
||||||
t.Run("Multiple", func(t *testing.T) {
|
lineCache := fsutils.NewLineCache(fsutils.NewFileCache())
|
||||||
p := NewExcludeRules([]ExcludeRule{
|
p := NewExcludeRules([]ExcludeRule{
|
||||||
{
|
{
|
||||||
Text: "^exclude$",
|
Text: "^exclude$",
|
||||||
Linters: []string{"linter"},
|
Linters: []string{"linter"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Linters: []string{"testlinter"},
|
Linters: []string{"testlinter"},
|
||||||
Path: `_test\.go`,
|
Path: `_test\.go`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Text: "^testonly$",
|
Text: "^testonly$",
|
||||||
Path: `_test\.go`,
|
Path: `_test\.go`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Source: "^//go:generate ",
|
||||||
|
Linters: []string{"lll"},
|
||||||
|
},
|
||||||
|
}, lineCache, nil)
|
||||||
|
type issueCase struct {
|
||||||
|
Path string
|
||||||
|
Line int
|
||||||
|
Text string
|
||||||
|
Linter string
|
||||||
|
}
|
||||||
|
var newIssueCase = func(c issueCase) result.Issue {
|
||||||
|
return result.Issue{
|
||||||
|
Text: c.Text,
|
||||||
|
FromLinter: c.Linter,
|
||||||
|
Pos: token.Position{
|
||||||
|
Filename: c.Path,
|
||||||
|
Line: c.Line,
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cases := []issueCase{
|
||||||
|
{Path: "e.go", Text: "exclude", Linter: "linter"},
|
||||||
|
{Path: "e.go", Text: "some", Linter: "linter"},
|
||||||
|
{Path: "e_test.go", Text: "normal", Linter: "testlinter"},
|
||||||
|
{Path: "e_test.go", Text: "another", Linter: "linter"},
|
||||||
|
{Path: "e_test.go", Text: "testonly", Linter: "linter"},
|
||||||
|
{Path: filepath.Join("testdata", "exclude_rules.go"), Line: 3, Linter: "lll"},
|
||||||
|
}
|
||||||
|
var issues []result.Issue
|
||||||
|
for _, c := range cases {
|
||||||
|
issues = append(issues, newIssueCase(c))
|
||||||
|
}
|
||||||
|
processedIssues := process(t, p, issues...)
|
||||||
|
var resultingCases []issueCase
|
||||||
|
for _, i := range processedIssues {
|
||||||
|
resultingCases = append(resultingCases, issueCase{
|
||||||
|
Path: i.FilePath(),
|
||||||
|
Linter: i.FromLinter,
|
||||||
|
Text: i.Text,
|
||||||
|
Line: i.Line(),
|
||||||
})
|
})
|
||||||
type issueCase struct {
|
}
|
||||||
Path string
|
expectedCases := []issueCase{
|
||||||
Text string
|
{Path: "e.go", Text: "some", Linter: "linter"},
|
||||||
Linter string
|
{Path: "e_test.go", Text: "another", Linter: "linter"},
|
||||||
}
|
}
|
||||||
var newIssueCase = func(c issueCase) result.Issue {
|
assert.Equal(t, expectedCases, resultingCases)
|
||||||
return result.Issue{
|
}
|
||||||
Text: c.Text,
|
|
||||||
FromLinter: c.Linter,
|
func TestExcludeRulesText(t *testing.T) {
|
||||||
Pos: token.Position{
|
p := NewExcludeRules([]ExcludeRule{
|
||||||
Filename: c.Path,
|
{
|
||||||
},
|
Text: "^exclude$",
|
||||||
}
|
Linters: []string{
|
||||||
}
|
"linter",
|
||||||
cases := []issueCase{
|
},
|
||||||
{Path: "e.go", Text: "exclude", Linter: "linter"},
|
},
|
||||||
{Path: "e.go", Text: "some", Linter: "linter"},
|
}, nil, nil)
|
||||||
{Path: "e_test.go", Text: "normal", Linter: "testlinter"},
|
texts := []string{"excLude", "1", "", "exclud", "notexclude"}
|
||||||
{Path: "e_test.go", Text: "another", Linter: "linter"},
|
var issues []result.Issue
|
||||||
{Path: "e_test.go", Text: "testonly", Linter: "linter"},
|
for _, t := range texts {
|
||||||
}
|
issues = append(issues, result.Issue{
|
||||||
var issues []result.Issue
|
Text: t,
|
||||||
for _, c := range cases {
|
FromLinter: "linter",
|
||||||
issues = append(issues, newIssueCase(c))
|
})
|
||||||
}
|
}
|
||||||
processedIssues := process(t, p, issues...)
|
|
||||||
var resultingCases []issueCase
|
processedIssues := process(t, p, issues...)
|
||||||
for _, i := range processedIssues {
|
assert.Len(t, processedIssues, len(issues)-1)
|
||||||
resultingCases = append(resultingCases, issueCase{
|
|
||||||
Path: i.FilePath(),
|
var processedTexts []string
|
||||||
Linter: i.FromLinter,
|
for _, i := range processedIssues {
|
||||||
Text: i.Text,
|
processedTexts = append(processedTexts, i.Text)
|
||||||
})
|
}
|
||||||
}
|
assert.Equal(t, texts[1:], processedTexts)
|
||||||
expectedCases := []issueCase{
|
}
|
||||||
{Path: "e.go", Text: "some", Linter: "linter"},
|
func TestExcludeRulesEmpty(t *testing.T) {
|
||||||
{Path: "e_test.go", Text: "another", Linter: "linter"},
|
processAssertSame(t, NewExcludeRules(nil, nil, nil), newTextIssue("test"))
|
||||||
}
|
|
||||||
assert.Equal(t, expectedCases, resultingCases)
|
|
||||||
})
|
|
||||||
t.Run("Text", func(t *testing.T) {
|
|
||||||
p := NewExcludeRules([]ExcludeRule{
|
|
||||||
{
|
|
||||||
Text: "^exclude$",
|
|
||||||
Linters: []string{
|
|
||||||
"linter",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
texts := []string{"excLude", "1", "", "exclud", "notexclude"}
|
|
||||||
var issues []result.Issue
|
|
||||||
for _, t := range texts {
|
|
||||||
issues = append(issues, result.Issue{
|
|
||||||
Text: t,
|
|
||||||
FromLinter: "linter",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
processedIssues := process(t, p, issues...)
|
|
||||||
assert.Len(t, processedIssues, len(issues)-1)
|
|
||||||
|
|
||||||
var processedTexts []string
|
|
||||||
for _, i := range processedIssues {
|
|
||||||
processedTexts = append(processedTexts, i.Text)
|
|
||||||
}
|
|
||||||
assert.Equal(t, texts[1:], processedTexts)
|
|
||||||
})
|
|
||||||
t.Run("Empty", func(t *testing.T) {
|
|
||||||
processAssertSame(t, NewExcludeRules(nil), newTextIssue("test"))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,13 @@ package processors
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -18,12 +19,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Fixer struct {
|
type Fixer struct {
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
log logutils.Log
|
log logutils.Log
|
||||||
|
fileCache *fsutils.FileCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFixer(cfg *config.Config, log logutils.Log) *Fixer {
|
func NewFixer(cfg *config.Config, log logutils.Log, fileCache *fsutils.FileCache) *Fixer {
|
||||||
return &Fixer{cfg: cfg, log: log}
|
return &Fixer{cfg: cfg, log: log, fileCache: fileCache}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Fixer) Process(issues <-chan result.Issue) <-chan result.Issue {
|
func (f Fixer) Process(issues <-chan result.Issue) <-chan result.Issue {
|
||||||
@ -63,9 +65,9 @@ func (f Fixer) Process(issues <-chan result.Issue) <-chan result.Issue {
|
|||||||
func (f Fixer) fixIssuesInFile(filePath string, issues []result.Issue) error {
|
func (f Fixer) fixIssuesInFile(filePath string, issues []result.Issue) error {
|
||||||
// TODO: don't read the whole file into memory: read line by line;
|
// TODO: don't read the whole file into memory: read line by line;
|
||||||
// can't just use bufio.scanner: it has a line length limit
|
// can't just use bufio.scanner: it has a line length limit
|
||||||
origFileData, err := ioutil.ReadFile(filePath)
|
origFileData, err := f.fileCache.GetFileBytes(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed to read %s", filePath)
|
return errors.Wrapf(err, "failed to get file bytes for %s", filePath)
|
||||||
}
|
}
|
||||||
origFileLines := bytes.Split(origFileData, []byte("\n"))
|
origFileLines := bytes.Split(origFileData, []byte("\n"))
|
||||||
|
|
||||||
|
@ -1,28 +1,22 @@
|
|||||||
package processors
|
package processors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
)
|
)
|
||||||
|
|
||||||
type linesCache [][]byte
|
|
||||||
type filesLineCache map[string]linesCache
|
|
||||||
|
|
||||||
type SourceCode struct {
|
type SourceCode struct {
|
||||||
cache filesLineCache
|
lineCache *fsutils.LineCache
|
||||||
log logutils.Log
|
log logutils.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Processor = SourceCode{}
|
var _ Processor = SourceCode{}
|
||||||
|
|
||||||
func NewSourceCode(log logutils.Log) *SourceCode {
|
func NewSourceCode(lc *fsutils.LineCache, log logutils.Log) *SourceCode {
|
||||||
return &SourceCode{
|
return &SourceCode{
|
||||||
cache: filesLineCache{},
|
lineCache: lc,
|
||||||
log: log,
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,50 +26,21 @@ func (p SourceCode) Name() string {
|
|||||||
|
|
||||||
func (p SourceCode) Process(issues []result.Issue) ([]result.Issue, error) {
|
func (p SourceCode) Process(issues []result.Issue) ([]result.Issue, error) {
|
||||||
return transformIssues(issues, func(i *result.Issue) *result.Issue {
|
return transformIssues(issues, func(i *result.Issue) *result.Issue {
|
||||||
lines, err := p.getFileLinesForIssue(i)
|
|
||||||
if err != nil {
|
|
||||||
p.log.Warnf("Failed to get lines for file %s: %s", i.FilePath(), err)
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
newI := *i
|
newI := *i
|
||||||
|
|
||||||
lineRange := i.GetLineRange()
|
lineRange := i.GetLineRange()
|
||||||
var lineStr string
|
for lineNumber := lineRange.From; lineNumber <= lineRange.To; lineNumber++ {
|
||||||
for line := lineRange.From; line <= lineRange.To; line++ {
|
line, err := p.lineCache.GetLine(i.FilePath(), lineNumber)
|
||||||
if line == 0 { // some linters, e.g. gosec can do it: it really means first line
|
if err != nil {
|
||||||
line = 1
|
p.log.Warnf("Failed to get line %d for file %s: %s", i.FilePath(), lineNumber, err)
|
||||||
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
zeroIndexedLine := line - 1
|
newI.SourceLines = append(newI.SourceLines, line)
|
||||||
if zeroIndexedLine >= len(lines) {
|
|
||||||
p.log.Warnf("No line %d in file %s", line, i.FilePath())
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
lineStr = string(bytes.Trim(lines[zeroIndexedLine], "\r"))
|
|
||||||
newI.SourceLines = append(newI.SourceLines, lineStr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &newI
|
return &newI
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SourceCode) getFileLinesForIssue(i *result.Issue) (linesCache, error) {
|
|
||||||
fc := p.cache[i.FilePath()]
|
|
||||||
if fc != nil {
|
|
||||||
return fc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make more optimal algorithm: don't load all files into memory
|
|
||||||
fileBytes, err := ioutil.ReadFile(i.FilePath())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't read file %s for printing issued line: %s", i.FilePath(), err)
|
|
||||||
}
|
|
||||||
lines := bytes.Split(fileBytes, []byte("\n")) // TODO: what about \r\n?
|
|
||||||
fc = lines
|
|
||||||
p.cache[i.FilePath()] = fc
|
|
||||||
return fc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p SourceCode) Finish() {}
|
func (p SourceCode) Finish() {}
|
||||||
|
5
pkg/result/processors/testdata/exclude_rules.go
vendored
Normal file
5
pkg/result/processors/testdata/exclude_rules.go
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package testdata
|
||||||
|
|
||||||
|
//go:generate --long line --with a --lot of --arguments --that we --would like --to exclude --from lll --issues --by exclude-rules
|
||||||
|
|
||||||
|
// long line that we don't want to exclude from lll issues. Use the similar pattern: go:generate. This line should be reported by lll
|
Loading…
x
Reference in New Issue
Block a user