Skip to content

Commit 3d2dfac

Browse files
committed
Support excluding issues by source line regexp
See issues.exclude-rules[i].source. Also introduced file data and file lines cache.
1 parent 7514bf8 commit 3d2dfac

File tree

13 files changed

+365
-203
lines changed

13 files changed

+365
-203
lines changed

.golangci.example.yml

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ issues:
193193
exclude:
194194
- abcdef
195195

196-
# Excluding configuration per-path and per-linter
196+
# Excluding configuration per-path, per-linter, per-text and per-source
197197
exclude-rules:
198198
# Exclude some linters from running on tests files.
199199
- path: _test\.go
@@ -203,28 +203,22 @@ issues:
203203
- dupl
204204
- gosec
205205

206-
# Ease some gocritic warnings on test files.
207-
- path: _test\.go
208-
text: "(unnamedResult|exitAfterDefer)"
209-
linters:
210-
- gocritic
211-
212206
# Exclude known linters from partially hard-vendored code,
213207
# which is impossible to exclude via "nolint" comments.
214208
- path: internal/hmac/
215209
text: "weak cryptographic primitive"
216210
linters:
217211
- gosec
218-
- path: internal/hmac/
219-
text: "Write\\` is not checked"
220-
linters:
221-
- errcheck
222212

223-
# Ease linting on benchmarking code.
224-
- path: cmd/stun-bench/
225-
linters:
226-
- gosec
227-
- errcheck
213+
# Exclude some staticcheck messages
214+
- linters:
215+
- staticcheck
216+
text: "SA9003:"
217+
218+
# Exclude lll issues for long lines with go:generate
219+
- linters:
220+
- lll
221+
source: "^//go:generate "
228222

229223
# Independently from option `exclude` we use default exclude patterns,
230224
# it can be disabled by this option. To list all

README.md

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ issues:
723723
exclude:
724724
- abcdef
725725
726-
# Excluding configuration per-path and per-linter
726+
# Excluding configuration per-path, per-linter, per-text and per-source
727727
exclude-rules:
728728
# Exclude some linters from running on tests files.
729729
- path: _test\.go
@@ -733,28 +733,22 @@ issues:
733733
- dupl
734734
- gosec
735735
736-
# Ease some gocritic warnings on test files.
737-
- path: _test\.go
738-
text: "(unnamedResult|exitAfterDefer)"
739-
linters:
740-
- gocritic
741-
742736
# Exclude known linters from partially hard-vendored code,
743737
# which is impossible to exclude via "nolint" comments.
744738
- path: internal/hmac/
745739
text: "weak cryptographic primitive"
746740
linters:
747741
- gosec
748-
- path: internal/hmac/
749-
text: "Write\\` is not checked"
750-
linters:
751-
- errcheck
752742
753-
# Ease linting on benchmarking code.
754-
- path: cmd/stun-bench/
755-
linters:
756-
- gosec
757-
- errcheck
743+
# Exclude some staticcheck messages
744+
- linters:
745+
- staticcheck
746+
text: "SA9003:"
747+
748+
# Exclude lll issues for long lines with go:generate
749+
- linters:
750+
- lll
751+
source: "^//go:generate "
758752
759753
# Independently from option `exclude` we use default exclude patterns,
760754
# it can be disabled by this option. To list all

pkg/commands/executor.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"github.com/spf13/cobra"
55
"github.com/spf13/pflag"
66

7+
"github.com/golangci/golangci-lint/pkg/fsutils"
8+
79
"github.com/golangci/golangci-lint/pkg/config"
810
"github.com/golangci/golangci-lint/pkg/goutil"
911
"github.com/golangci/golangci-lint/pkg/lint"
@@ -26,6 +28,8 @@ type Executor struct {
2628
EnabledLintersSet *lintersdb.EnabledSet
2729
contextLoader *lint.ContextLoader
2830
goenv *goutil.Env
31+
fileCache *fsutils.FileCache
32+
lineCache *fsutils.LineCache
2933
}
3034

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

8288
return e
8389
}

pkg/commands/run.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,13 +278,13 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan resul
278278
}
279279
lintCtx.Log = e.log.Child("linters context")
280280

281-
runner, err := lint.NewRunner(lintCtx.ASTCache, e.cfg, e.log.Child("runner"), e.goenv)
281+
runner, err := lint.NewRunner(lintCtx.ASTCache, e.cfg, e.log.Child("runner"), e.goenv, e.lineCache)
282282
if err != nil {
283283
return nil, err
284284
}
285285

286286
issuesCh := runner.Run(ctx, enabledLinters, lintCtx)
287-
fixer := processors.NewFixer(e.cfg, e.log)
287+
fixer := processors.NewFixer(e.cfg, e.log, e.fileCache)
288288
return fixer.Process(issuesCh), nil
289289
}
290290

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

353+
e.fileCache.PrintStats(e.log)
354+
353355
return nil
354356
}
355357

pkg/config/config.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ type ExcludeRule struct {
235235
Linters []string
236236
Path string
237237
Text string
238+
Source string
238239
}
239240

240241
func validateOptionalRegex(value string) error {
@@ -252,6 +253,9 @@ func (e ExcludeRule) Validate() error {
252253
if err := validateOptionalRegex(e.Text); err != nil {
253254
return fmt.Errorf("invalid text regex: %v", err)
254255
}
256+
if err := validateOptionalRegex(e.Source); err != nil {
257+
return fmt.Errorf("invalid source regex: %v", err)
258+
}
255259
nonBlank := 0
256260
if len(e.Linters) > 0 {
257261
nonBlank++
@@ -262,8 +266,11 @@ func (e ExcludeRule) Validate() error {
262266
if e.Text != "" {
263267
nonBlank++
264268
}
269+
if e.Source != "" {
270+
nonBlank++
271+
}
265272
if nonBlank < 2 {
266-
return errors.New("at least 2 of (text, path, linters) should be set")
273+
return errors.New("at least 2 of (text, source, path, linters) should be set")
267274
}
268275
return nil
269276
}

pkg/fsutils/filecache.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package fsutils
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
7+
"github.com/golangci/golangci-lint/pkg/logutils"
8+
9+
"github.com/pkg/errors"
10+
)
11+
12+
type FileCache struct {
13+
files map[string][]byte
14+
}
15+
16+
func NewFileCache() *FileCache {
17+
return &FileCache{
18+
files: map[string][]byte{},
19+
}
20+
}
21+
22+
func (fc *FileCache) GetFileBytes(filePath string) ([]byte, error) {
23+
cachedBytes := fc.files[filePath]
24+
if cachedBytes != nil {
25+
return cachedBytes, nil
26+
}
27+
28+
fileBytes, err := ioutil.ReadFile(filePath)
29+
if err != nil {
30+
return nil, errors.Wrapf(err, "can't read file %s", filePath)
31+
}
32+
33+
fc.files[filePath] = fileBytes
34+
return fileBytes, nil
35+
}
36+
37+
func prettifyBytesCount(n int) string {
38+
const (
39+
Multiplexer = 1024
40+
KiB = 1 * Multiplexer
41+
MiB = KiB * Multiplexer
42+
GiB = MiB * Multiplexer
43+
)
44+
45+
if n >= GiB {
46+
return fmt.Sprintf("%.1fGiB", float64(n)/GiB)
47+
}
48+
if n >= MiB {
49+
return fmt.Sprintf("%.1fMiB", float64(n)/MiB)
50+
}
51+
if n >= KiB {
52+
return fmt.Sprintf("%.1fKiB", float64(n)/KiB)
53+
}
54+
return fmt.Sprintf("%dB", n)
55+
}
56+
57+
func (fc *FileCache) PrintStats(log logutils.Log) {
58+
var size int
59+
for _, fileBytes := range fc.files {
60+
size += len(fileBytes)
61+
}
62+
63+
log.Infof("File cache stats: %d entries of total size %s", len(fc.files), prettifyBytesCount(size))
64+
}

pkg/fsutils/linecache.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package fsutils
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
7+
"github.com/pkg/errors"
8+
)
9+
10+
type fileLinesCache [][]byte
11+
12+
type LineCache struct {
13+
files map[string]fileLinesCache
14+
fileCache *FileCache
15+
}
16+
17+
func NewLineCache(fc *FileCache) *LineCache {
18+
return &LineCache{
19+
files: map[string]fileLinesCache{},
20+
fileCache: fc,
21+
}
22+
}
23+
24+
// GetLine returns a index1-th (1-based index) line from the file on filePath
25+
func (lc *LineCache) GetLine(filePath string, index1 int) (string, error) {
26+
if index1 == 0 { // some linters, e.g. gosec can do it: it really means first line
27+
index1 = 1
28+
}
29+
30+
rawLine, err := lc.getRawLine(filePath, index1-1)
31+
if err != nil {
32+
return "", err
33+
}
34+
35+
return string(bytes.Trim(rawLine, "\r")), nil
36+
}
37+
38+
func (lc *LineCache) getRawLine(filePath string, index0 int) ([]byte, error) {
39+
fc, err := lc.getFileCache(filePath)
40+
if err != nil {
41+
return nil, errors.Wrapf(err, "failed to get file %s lines cache", filePath)
42+
}
43+
44+
if index0 < 0 {
45+
return nil, fmt.Errorf("invalid file line index0 < 0: %d", index0)
46+
}
47+
48+
if index0 >= len(fc) {
49+
return nil, fmt.Errorf("invalid file line index0 (%d) >= len(fc) (%d)", index0, len(fc))
50+
}
51+
52+
return fc[index0], nil
53+
}
54+
55+
func (lc *LineCache) getFileCache(filePath string) (fileLinesCache, error) {
56+
fc := lc.files[filePath]
57+
if fc != nil {
58+
return fc, nil
59+
}
60+
61+
fileBytes, err := lc.fileCache.GetFileBytes(filePath)
62+
if err != nil {
63+
return nil, errors.Wrapf(err, "can't get file %s bytes from cache", filePath)
64+
}
65+
66+
fc = bytes.Split(fileBytes, []byte("\n"))
67+
lc.files[filePath] = fc
68+
return fc, nil
69+
}

pkg/lint/runner.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"sync"
1010
"time"
1111

12+
"github.com/golangci/golangci-lint/pkg/fsutils"
13+
1214
"github.com/golangci/golangci-lint/pkg/config"
1315
"github.com/golangci/golangci-lint/pkg/goutil"
1416
"github.com/golangci/golangci-lint/pkg/lint/astcache"
@@ -25,7 +27,9 @@ type Runner struct {
2527
Log logutils.Log
2628
}
2729

28-
func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, goenv *goutil.Env) (*Runner, error) {
30+
func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, goenv *goutil.Env,
31+
lineCache *fsutils.LineCache) (*Runner, error) {
32+
2933
icfg := cfg.Issues
3034
excludePatterns := icfg.ExcludePatterns
3135
if icfg.UseDefaultExcludes {
@@ -53,6 +57,7 @@ func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, g
5357
for _, r := range icfg.ExcludeRules {
5458
excludeRules = append(excludeRules, processors.ExcludeRule{
5559
Text: r.Text,
60+
Source: r.Source,
5661
Path: r.Path,
5762
Linters: r.Linters,
5863
})
@@ -68,15 +73,15 @@ func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log, g
6873
processors.NewAutogeneratedExclude(astCache),
6974
processors.NewIdentifierMarker(), // must be befor exclude
7075
processors.NewExclude(excludeTotalPattern),
71-
processors.NewExcludeRules(excludeRules),
76+
processors.NewExcludeRules(excludeRules, lineCache, log.Child("exclude_rules")),
7277
processors.NewNolint(astCache, log.Child("nolint")),
7378

7479
processors.NewUniqByLine(),
7580
processors.NewDiff(icfg.Diff, icfg.DiffFromRevision, icfg.DiffPatchFilePath),
7681
processors.NewMaxPerFileFromLinter(cfg),
7782
processors.NewMaxSameIssues(icfg.MaxSameIssues, log.Child("max_same_issues"), cfg),
7883
processors.NewMaxFromLinter(icfg.MaxIssuesPerLinter, log.Child("max_from_linter"), cfg),
79-
processors.NewSourceCode(log.Child("source_code")),
84+
processors.NewSourceCode(lineCache, log.Child("source_code")),
8085
processors.NewReplacementBuilder(log.Child("replacement_builder")), // must be after source code
8186
processors.NewPathShortener(),
8287
},

0 commit comments

Comments
 (0)