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:
|
||||
- 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
|
||||
@ -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
|
||||
|
26
README.md
26
README.md
@ -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
|
||||
@ -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
|
||||
|
@ -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"
|
||||
@ -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 {
|
||||
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
@ -235,6 +235,7 @@ type ExcludeRule struct {
|
||||
Linters []string
|
||||
Path string
|
||||
Text string
|
||||
Source string
|
||||
}
|
||||
|
||||
func validateOptionalRegex(value string) error {
|
||||
@ -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++
|
||||
@ -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
|
||||
}
|
||||
|
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"
|
||||
"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"
|
||||
@ -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 {
|
||||
@ -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,
|
||||
})
|
||||
@ -68,7 +73,7 @@ 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(),
|
||||
@ -76,7 +81,7 @@ func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, g
|
||||
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(),
|
||||
},
|
||||
|
@ -3,11 +3,16 @@ package processors
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type excludeRule struct {
|
||||
text *regexp.Regexp
|
||||
source *regexp.Regexp
|
||||
path *regexp.Regexp
|
||||
linters []string
|
||||
}
|
||||
@ -16,7 +21,80 @@ func (r *excludeRule) isEmpty() bool {
|
||||
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() {
|
||||
return false
|
||||
}
|
||||
@ -26,56 +104,16 @@ func (r excludeRule) Match(i *result.Issue) bool {
|
||||
if r.path != nil && !r.path.MatchString(i.FilePath()) {
|
||||
return false
|
||||
}
|
||||
if len(r.linters) == 0 {
|
||||
return true
|
||||
if len(r.linters) != 0 && !p.matchLinter(i, r) {
|
||||
return false
|
||||
}
|
||||
for _, l := range r.linters {
|
||||
if l == i.FromLinter {
|
||||
return true
|
||||
}
|
||||
|
||||
// the most heavyweight checking last
|
||||
if r.source != nil && !p.matchSource(i, r) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ExcludeRule struct {
|
||||
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
|
||||
return true
|
||||
}
|
||||
|
||||
func (ExcludeRules) Name() string { return "exclude-rules" }
|
||||
|
@ -2,97 +2,108 @@ package processors
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
func TestExcludeRules(t *testing.T) {
|
||||
t.Run("Multiple", func(t *testing.T) {
|
||||
p := NewExcludeRules([]ExcludeRule{
|
||||
{
|
||||
Text: "^exclude$",
|
||||
Linters: []string{"linter"},
|
||||
},
|
||||
{
|
||||
Linters: []string{"testlinter"},
|
||||
Path: `_test\.go`,
|
||||
},
|
||||
{
|
||||
Text: "^testonly$",
|
||||
Path: `_test\.go`,
|
||||
func TestExcludeRulesMultiple(t *testing.T) {
|
||||
lineCache := fsutils.NewLineCache(fsutils.NewFileCache())
|
||||
p := NewExcludeRules([]ExcludeRule{
|
||||
{
|
||||
Text: "^exclude$",
|
||||
Linters: []string{"linter"},
|
||||
},
|
||||
{
|
||||
Linters: []string{"testlinter"},
|
||||
Path: `_test\.go`,
|
||||
},
|
||||
{
|
||||
Text: "^testonly$",
|
||||
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
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
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"},
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
||||
expectedCases := []issueCase{
|
||||
{Path: "e.go", Text: "some", Linter: "linter"},
|
||||
{Path: "e_test.go", Text: "another", Linter: "linter"},
|
||||
}
|
||||
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"))
|
||||
})
|
||||
}
|
||||
expectedCases := []issueCase{
|
||||
{Path: "e.go", Text: "some", Linter: "linter"},
|
||||
{Path: "e_test.go", Text: "another", Linter: "linter"},
|
||||
}
|
||||
assert.Equal(t, expectedCases, resultingCases)
|
||||
}
|
||||
|
||||
func TestExcludeRulesText(t *testing.T) {
|
||||
p := NewExcludeRules([]ExcludeRule{
|
||||
{
|
||||
Text: "^exclude$",
|
||||
Linters: []string{
|
||||
"linter",
|
||||
},
|
||||
},
|
||||
}, nil, nil)
|
||||
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)
|
||||
}
|
||||
func TestExcludeRulesEmpty(t *testing.T) {
|
||||
processAssertSame(t, NewExcludeRules(nil, nil, nil), newTextIssue("test"))
|
||||
}
|
||||
|
@ -3,12 +3,13 @@ package processors
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@ -18,12 +19,13 @@ import (
|
||||
)
|
||||
|
||||
type Fixer struct {
|
||||
cfg *config.Config
|
||||
log logutils.Log
|
||||
cfg *config.Config
|
||||
log logutils.Log
|
||||
fileCache *fsutils.FileCache
|
||||
}
|
||||
|
||||
func NewFixer(cfg *config.Config, log logutils.Log) *Fixer {
|
||||
return &Fixer{cfg: cfg, log: log}
|
||||
func NewFixer(cfg *config.Config, log logutils.Log, fileCache *fsutils.FileCache) *Fixer {
|
||||
return &Fixer{cfg: cfg, log: log, fileCache: fileCache}
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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
|
||||
origFileData, err := ioutil.ReadFile(filePath)
|
||||
origFileData, err := f.fileCache.GetFileBytes(filePath)
|
||||
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"))
|
||||
|
||||
|
@ -1,28 +1,22 @@
|
||||
package processors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/fsutils"
|
||||
"github.com/golangci/golangci-lint/pkg/logutils"
|
||||
"github.com/golangci/golangci-lint/pkg/result"
|
||||
)
|
||||
|
||||
type linesCache [][]byte
|
||||
type filesLineCache map[string]linesCache
|
||||
|
||||
type SourceCode struct {
|
||||
cache filesLineCache
|
||||
log logutils.Log
|
||||
lineCache *fsutils.LineCache
|
||||
log logutils.Log
|
||||
}
|
||||
|
||||
var _ Processor = SourceCode{}
|
||||
|
||||
func NewSourceCode(log logutils.Log) *SourceCode {
|
||||
func NewSourceCode(lc *fsutils.LineCache, log logutils.Log) *SourceCode {
|
||||
return &SourceCode{
|
||||
cache: filesLineCache{},
|
||||
log: log,
|
||||
lineCache: lc,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,50 +26,21 @@ func (p SourceCode) Name() string {
|
||||
|
||||
func (p SourceCode) Process(issues []result.Issue) ([]result.Issue, error) {
|
||||
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
|
||||
|
||||
lineRange := i.GetLineRange()
|
||||
var lineStr string
|
||||
for line := lineRange.From; line <= lineRange.To; line++ {
|
||||
if line == 0 { // some linters, e.g. gosec can do it: it really means first line
|
||||
line = 1
|
||||
for lineNumber := lineRange.From; lineNumber <= lineRange.To; lineNumber++ {
|
||||
line, err := p.lineCache.GetLine(i.FilePath(), lineNumber)
|
||||
if err != nil {
|
||||
p.log.Warnf("Failed to get line %d for file %s: %s", i.FilePath(), lineNumber, err)
|
||||
return i
|
||||
}
|
||||
|
||||
zeroIndexedLine := line - 1
|
||||
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)
|
||||
newI.SourceLines = append(newI.SourceLines, line)
|
||||
}
|
||||
|
||||
return &newI
|
||||
}), 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() {}
|
||||
|
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