Skip to content

Commit 95ec0cf

Browse files
authored
dramatically reduce memory usage (#758)
Run all linters per package. It allows unloading package data when it's processed. It dramatically reduces memory (and CPU because of GC) usage. Relates: #337
1 parent fe494af commit 95ec0cf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+2488
-2430
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ clean:
2929
test: export GOLANGCI_LINT_INSTALLED = true
3030
test: build
3131
GL_TEST_RUN=1 time ./golangci-lint run -v
32+
time go run ./cmd/golangci-lint/main.go run -v
3233
GL_TEST_RUN=1 time ./golangci-lint run --fast --no-config -v --skip-dirs 'test/testdata_etc,internal/(cache|renameio|robustio)'
3334
GL_TEST_RUN=1 time ./golangci-lint run --no-config -v --skip-dirs 'test/testdata_etc,internal/(cache|renameio|robustio)'
3435
GL_TEST_RUN=1 time go test -v ./...

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,13 @@ $ golangci-lint help linters
178178
Enabled by default linters:
179179
deadcode: Finds unused code [fast: true, auto-fix: false]
180180
errcheck: Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false]
181-
gosimple: Linter for Go source code that specializes in simplifying a code [fast: false, auto-fix: false]
182-
govet (vet, vetshadow): Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false]
181+
gosimple (megacheck): Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false]
182+
govet (vet, vetshadow): Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true, auto-fix: false]
183183
ineffassign: Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
184-
staticcheck: Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false, auto-fix: false]
184+
staticcheck (megacheck): Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
185185
structcheck: Finds unused struct fields [fast: true, auto-fix: false]
186186
typecheck: Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
187-
unused: Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
187+
unused (megacheck): Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
188188
varcheck: Finds unused global variables and constants [fast: true, auto-fix: false]
189189
```
190190
@@ -194,12 +194,12 @@ and the following linters are disabled by default:
194194
$ golangci-lint help linters
195195
...
196196
Disabled by default linters:
197-
bodyclose: checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
197+
bodyclose: checks whether HTTP response body is closed successfully [fast: true, auto-fix: false]
198198
depguard: Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
199199
dogsled: Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
200200
dupl: Tool for code clone detection [fast: true, auto-fix: false]
201201
funlen: Tool for detection of long functions [fast: true, auto-fix: false]
202-
gochecknoglobals: Checks that no globals are present in Go code [fast: true, auto-fix: false]
202+
gochecknoglobals: Tool for detection of long functions [fast: true, auto-fix: false]
203203
gochecknoinits: Checks that no init functions are present in Go code [fast: true, auto-fix: false]
204204
goconst: Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
205205
gocritic: The most opinionated Go source code linter [fast: true, auto-fix: false]
@@ -209,16 +209,16 @@ gofmt: Gofmt checks whether code was gofmt-ed. By default this tool runs with -s
209209
goimports: Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
210210
golint: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
211211
gosec (gas): Inspects source code for security problems [fast: true, auto-fix: false]
212-
interfacer: Linter that suggests narrower interface types [fast: false, auto-fix: false]
212+
interfacer: Linter that suggests narrower interface types [fast: true, auto-fix: false]
213213
lll: Reports long lines [fast: true, auto-fix: false]
214214
maligned: Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false]
215215
misspell: Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
216216
nakedret: Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
217217
prealloc: Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
218218
scopelint: Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
219-
stylecheck: Stylecheck is a replacement for golint [fast: false, auto-fix: false]
219+
stylecheck: Stylecheck is a replacement for golint [fast: true, auto-fix: false]
220220
unconvert: Remove unnecessary type conversions [fast: true, auto-fix: false]
221-
unparam: Reports unused function parameters [fast: false, auto-fix: false]
221+
unparam: Reports unused function parameters [fast: true, auto-fix: false]
222222
whitespace: Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
223223
```
224224
@@ -462,7 +462,7 @@ golangci-lint help linters
462462
- [scopelint](https://github.com/kyoh86/scopelint) - Scopelint checks for unpinned variables in go programs
463463
- [gocritic](https://github.com/go-critic/go-critic) - The most opinionated Go source code linter
464464
- [gochecknoinits](https://github.com/leighmcculloch/gochecknoinits) - Checks that no init functions are present in Go code
465-
- [gochecknoglobals](https://github.com/leighmcculloch/gochecknoglobals) - Checks that no globals are present in Go code
465+
- [gochecknoglobals](https://github.com/leighmcculloch/gochecknoglobals) - Tool for detection of long functions
466466
- [godox](https://github.com/matoous/godox) - Tool for detection of FIXME, TODO and other comment keywords
467467
- [funlen](https://github.com/ultraware/funlen) - Tool for detection of long functions
468468
- [whitespace](https://github.com/ultraware/whitespace) - Tool for detection of leading and trailing whitespace
@@ -563,6 +563,7 @@ Global Flags:
563563
--mem-profile-path string Path to memory profile output file
564564
--trace-path string Path to trace output file
565565
-v, --verbose verbose output
566+
--version Print version
566567
567568
```
568569

cmd/golangci-lint/mod_version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
//nolint:gochecknoinits
1111
func init() {
1212
if info, available := debug.ReadBuildInfo(); available {
13-
if date == "" && info.Main.Version != "(devel)" {
13+
if date == "" {
1414
version = info.Main.Version
1515
commit = fmt.Sprintf("(unknown, mod sum: %q)", info.Main.Sum)
1616
date = "(unknown)"

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ require (
1313
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613
1414
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3
1515
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee
16-
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98
16+
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a
1717
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc
18-
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217
18+
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89
1919
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca
2020
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770
2121
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgO
8686
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
8787
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8=
8888
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
89-
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98 h1:0OkFarm1Zy2CjCiDKfK9XHgmc2wbDlRMD2hD8anAJHU=
90-
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
89+
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
90+
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
9191
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
9292
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
93-
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217 h1:En/tZdwhAn0JNwLuXzP3k2RVtMqMmOEK7Yu/g3tmtJE=
94-
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
93+
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89 h1:664ewjIQUXDvinFMbAsoH2V2Yvaro/X8BoYpIMTWGXI=
94+
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
9595
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
9696
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
9797
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk=

pkg/commands/run.go

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ func fixSlicesFlags(fs *pflag.FlagSet) {
265265
})
266266
}
267267

268-
func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan result.Issue, error) {
268+
func (e *Executor) runAnalysis(ctx context.Context, args []string) ([]result.Issue, error) {
269269
e.cfg.Run.Args = args
270270

271271
enabledLinters, err := e.EnabledLintersSet.Get(true)
@@ -296,9 +296,9 @@ func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan resul
296296
return nil, err
297297
}
298298

299-
issuesCh := runner.Run(ctx, enabledLinters, lintCtx)
299+
issues := runner.Run(ctx, enabledLinters, lintCtx)
300300
fixer := processors.NewFixer(e.cfg, e.log, e.fileCache)
301-
return fixer.Process(issuesCh), nil
301+
return fixer.Process(issues), nil
302302
}
303303

304304
func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
@@ -313,24 +313,10 @@ func (e *Executor) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
313313
return
314314
}
315315

316-
func (e *Executor) setExitCodeIfIssuesFound(issues <-chan result.Issue) <-chan result.Issue {
317-
resCh := make(chan result.Issue, 1024)
318-
319-
go func() {
320-
issuesFound := false
321-
for i := range issues {
322-
issuesFound = true
323-
resCh <- i
324-
}
325-
326-
if issuesFound {
327-
e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
328-
}
329-
330-
close(resCh)
331-
}()
332-
333-
return resCh
316+
func (e *Executor) setExitCodeIfIssuesFound(issues []result.Issue) {
317+
if len(issues) != 0 {
318+
e.exitCode = e.cfg.Run.ExitCodeIfIssuesFound
319+
}
334320
}
335321

336322
func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
@@ -357,7 +343,7 @@ func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
357343
return err
358344
}
359345

360-
issues = e.setExitCodeIfIssuesFound(issues)
346+
e.setExitCodeIfIssuesFound(issues)
361347

362348
if err = p.Print(ctx, issues); err != nil {
363349
return fmt.Errorf("can't print %d issues: %s", len(issues), err)

pkg/golinters/bodyclose.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ func NewBodyclose() *goanalysis.Linter {
1717
"checks whether HTTP response body is closed successfully",
1818
analyzers,
1919
nil,
20-
)
20+
).WithLoadMode(goanalysis.LoadModeTypesInfo)
2121
}

pkg/golinters/deadcode.go

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,52 @@
11
package golinters
22

33
import (
4-
"context"
54
"fmt"
5+
"sync"
66

77
deadcodeAPI "github.com/golangci/go-misc/deadcode"
8+
"golang.org/x/tools/go/analysis"
89

10+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
911
"github.com/golangci/golangci-lint/pkg/lint/linter"
1012
"github.com/golangci/golangci-lint/pkg/result"
1113
)
1214

13-
type Deadcode struct{}
14-
15-
func (Deadcode) Name() string {
16-
return "deadcode"
17-
}
18-
19-
func (Deadcode) Desc() string {
20-
return "Finds unused code"
21-
}
22-
23-
func (d Deadcode) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
24-
issues, err := deadcodeAPI.Run(lintCtx.Program)
25-
if err != nil {
26-
return nil, err
27-
}
28-
29-
if len(issues) == 0 {
30-
return nil, nil
31-
}
32-
33-
res := make([]result.Issue, 0, len(issues))
34-
for _, i := range issues {
35-
res = append(res, result.Issue{
36-
Pos: i.Pos,
37-
Text: fmt.Sprintf("%s is unused", formatCode(i.UnusedIdentName, lintCtx.Cfg)),
38-
FromLinter: d.Name(),
39-
})
15+
func NewDeadcode() *goanalysis.Linter {
16+
const linterName = "deadcode"
17+
var mu sync.Mutex
18+
var resIssues []result.Issue
19+
20+
analyzer := &analysis.Analyzer{
21+
Name: goanalysis.TheOnlyAnalyzerName,
22+
Doc: goanalysis.TheOnlyanalyzerDoc,
23+
Run: func(pass *analysis.Pass) (interface{}, error) {
24+
prog := goanalysis.MakeFakeLoaderProgram(pass)
25+
issues, err := deadcodeAPI.Run(prog)
26+
if err != nil {
27+
return nil, err
28+
}
29+
res := make([]result.Issue, 0, len(issues))
30+
for _, i := range issues {
31+
res = append(res, result.Issue{
32+
Pos: i.Pos,
33+
Text: fmt.Sprintf("%s is unused", formatCode(i.UnusedIdentName, nil)),
34+
FromLinter: linterName,
35+
})
36+
}
37+
mu.Lock()
38+
resIssues = append(resIssues, res...)
39+
mu.Unlock()
40+
41+
return nil, nil
42+
},
4043
}
41-
return res, nil
44+
return goanalysis.NewLinter(
45+
linterName,
46+
"Finds unused code",
47+
[]*analysis.Analyzer{analyzer},
48+
nil,
49+
).WithIssuesReporter(func(*linter.Context) []result.Issue {
50+
return resIssues
51+
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
4252
}

pkg/golinters/depguard.go

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
package golinters
22

33
import (
4-
"context"
54
"fmt"
65
"strings"
6+
"sync"
7+
8+
"golang.org/x/tools/go/analysis"
9+
"golang.org/x/tools/go/loader"
10+
11+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
712

813
depguardAPI "github.com/OpenPeeDeeP/depguard"
914

1015
"github.com/golangci/golangci-lint/pkg/lint/linter"
1116
"github.com/golangci/golangci-lint/pkg/result"
1217
)
1318

14-
type Depguard struct{}
15-
16-
func (Depguard) Name() string {
17-
return "depguard"
18-
}
19-
2019
func setDepguardListType(dg *depguardAPI.Depguard, lintCtx *linter.Context) error {
2120
listType := lintCtx.Settings().Depguard.ListType
2221
var found bool
@@ -49,42 +48,67 @@ func setupDepguardPackages(dg *depguardAPI.Depguard, lintCtx *linter.Context) {
4948
}
5049
}
5150

52-
func (Depguard) Desc() string {
53-
return "Go linter that checks if package imports are in a list of acceptable packages"
54-
}
51+
func NewDepguard() *goanalysis.Linter {
52+
const linterName = "depguard"
53+
var mu sync.Mutex
54+
var resIssues []result.Issue
5555

56-
func (d Depguard) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
57-
dg := &depguardAPI.Depguard{
58-
Packages: lintCtx.Settings().Depguard.Packages,
59-
IncludeGoRoot: lintCtx.Settings().Depguard.IncludeGoRoot,
60-
}
61-
if err := setDepguardListType(dg, lintCtx); err != nil {
62-
return nil, err
56+
analyzer := &analysis.Analyzer{
57+
Name: goanalysis.TheOnlyAnalyzerName,
58+
Doc: goanalysis.TheOnlyanalyzerDoc,
6359
}
64-
setupDepguardPackages(dg, lintCtx)
60+
return goanalysis.NewLinter(
61+
linterName,
62+
"Go linter that checks if package imports are in a list of acceptable packages",
63+
[]*analysis.Analyzer{analyzer},
64+
nil,
65+
).WithContextSetter(func(lintCtx *linter.Context) {
66+
dgSettings := &lintCtx.Settings().Depguard
67+
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
68+
prog := goanalysis.MakeFakeLoaderProgram(pass)
69+
dg := &depguardAPI.Depguard{
70+
Packages: dgSettings.Packages,
71+
IncludeGoRoot: dgSettings.IncludeGoRoot,
72+
}
73+
if err := setDepguardListType(dg, lintCtx); err != nil {
74+
return nil, err
75+
}
76+
setupDepguardPackages(dg, lintCtx)
6577

66-
issues, err := dg.Run(lintCtx.LoaderConfig, lintCtx.Program)
67-
if err != nil {
68-
return nil, err
69-
}
70-
if len(issues) == 0 {
71-
return nil, nil
72-
}
73-
msgSuffix := "is in the blacklist"
74-
if dg.ListType == depguardAPI.LTWhitelist {
75-
msgSuffix = "is not in the whitelist"
76-
}
77-
res := make([]result.Issue, 0, len(issues))
78-
for _, i := range issues {
79-
userSuppliedMsgSuffix := lintCtx.Settings().Depguard.PackagesWithErrorMessage[i.PackageName]
80-
if userSuppliedMsgSuffix != "" {
81-
userSuppliedMsgSuffix = ": " + userSuppliedMsgSuffix
78+
loadConfig := &loader.Config{
79+
Cwd: "", // fallbacked to os.Getcwd
80+
Build: nil, // fallbacked to build.Default
81+
}
82+
issues, err := dg.Run(loadConfig, prog)
83+
if err != nil {
84+
return nil, err
85+
}
86+
if len(issues) == 0 {
87+
return nil, nil
88+
}
89+
msgSuffix := "is in the blacklist"
90+
if dg.ListType == depguardAPI.LTWhitelist {
91+
msgSuffix = "is not in the whitelist"
92+
}
93+
res := make([]result.Issue, 0, len(issues))
94+
for _, i := range issues {
95+
userSuppliedMsgSuffix := dgSettings.PackagesWithErrorMessage[i.PackageName]
96+
if userSuppliedMsgSuffix != "" {
97+
userSuppliedMsgSuffix = ": " + userSuppliedMsgSuffix
98+
}
99+
res = append(res, result.Issue{
100+
Pos: i.Position,
101+
Text: fmt.Sprintf("%s %s%s", formatCode(i.PackageName, lintCtx.Cfg), msgSuffix, userSuppliedMsgSuffix),
102+
FromLinter: linterName,
103+
})
104+
}
105+
mu.Lock()
106+
resIssues = append(resIssues, res...)
107+
mu.Unlock()
108+
109+
return nil, nil
82110
}
83-
res = append(res, result.Issue{
84-
Pos: i.Position,
85-
Text: fmt.Sprintf("%s %s%s", formatCode(i.PackageName, lintCtx.Cfg), msgSuffix, userSuppliedMsgSuffix),
86-
FromLinter: d.Name(),
87-
})
88-
}
89-
return res, nil
111+
}).WithIssuesReporter(func(*linter.Context) []result.Issue {
112+
return resIssues
113+
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
90114
}

0 commit comments

Comments
 (0)