Skip to content

Commit faf5f4b

Browse files
committed
dramatically reduce memory usage
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 d63d235 commit faf5f4b

Some content is hidden

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

72 files changed

+2471
-2418
lines changed

README.md

+11-10
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,13 @@ $ golangci-lint help linters
177177
Enabled by default linters:
178178
deadcode: Finds unused code [fast: true, auto-fix: false]
179179
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]
180-
gosimple: Linter for Go source code that specializes in simplifying a code [fast: false, auto-fix: false]
181-
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]
180+
gosimple (megacheck): Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false]
181+
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]
182182
ineffassign: Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
183-
staticcheck: Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false, auto-fix: false]
183+
staticcheck (megacheck): Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
184184
structcheck: Finds unused struct fields [fast: true, auto-fix: false]
185185
typecheck: Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
186-
unused: Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
186+
unused (megacheck): Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
187187
varcheck: Finds unused global variables and constants [fast: true, auto-fix: false]
188188
```
189189
@@ -193,12 +193,12 @@ and the following linters are disabled by default:
193193
$ golangci-lint help linters
194194
...
195195
Disabled by default linters:
196-
bodyclose: checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
196+
bodyclose: checks whether HTTP response body is closed successfully [fast: true, auto-fix: false]
197197
depguard: Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
198198
dogsled: Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
199199
dupl: Tool for code clone detection [fast: true, auto-fix: false]
200200
funlen: Tool for detection of long functions [fast: true, auto-fix: false]
201-
gochecknoglobals: Checks that no globals are present in Go code [fast: true, auto-fix: false]
201+
gochecknoglobals: Tool for detection of long functions [fast: true, auto-fix: false]
202202
gochecknoinits: Checks that no init functions are present in Go code [fast: true, auto-fix: false]
203203
goconst: Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
204204
gocritic: The most opinionated Go source code linter [fast: true, auto-fix: false]
@@ -208,16 +208,16 @@ gofmt: Gofmt checks whether code was gofmt-ed. By default this tool runs with -s
208208
goimports: Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
209209
golint: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
210210
gosec (gas): Inspects source code for security problems [fast: true, auto-fix: false]
211-
interfacer: Linter that suggests narrower interface types [fast: false, auto-fix: false]
211+
interfacer: Linter that suggests narrower interface types [fast: true, auto-fix: false]
212212
lll: Reports long lines [fast: true, auto-fix: false]
213213
maligned: Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false]
214214
misspell: Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
215215
nakedret: Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
216216
prealloc: Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
217217
scopelint: Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
218-
stylecheck: Stylecheck is a replacement for golint [fast: false, auto-fix: false]
218+
stylecheck: Stylecheck is a replacement for golint [fast: true, auto-fix: false]
219219
unconvert: Remove unnecessary type conversions [fast: true, auto-fix: false]
220-
unparam: Reports unused function parameters [fast: false, auto-fix: false]
220+
unparam: Reports unused function parameters [fast: true, auto-fix: false]
221221
whitespace: Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
222222
```
223223
@@ -461,7 +461,7 @@ golangci-lint help linters
461461
- [scopelint](https://github.com/kyoh86/scopelint) - Scopelint checks for unpinned variables in go programs
462462
- [gocritic](https://github.com/go-critic/go-critic) - The most opinionated Go source code linter
463463
- [gochecknoinits](https://github.com/leighmcculloch/gochecknoinits) - Checks that no init functions are present in Go code
464-
- [gochecknoglobals](https://github.com/leighmcculloch/gochecknoglobals) - Checks that no globals are present in Go code
464+
- [gochecknoglobals](https://github.com/leighmcculloch/gochecknoglobals) - Tool for detection of long functions
465465
- [godox](https://github.com/matoous/godox) - Tool for detection of FIXME, TODO and other comment keywords
466466
- [funlen](https://github.com/ultraware/funlen) - Tool for detection of long functions
467467
- [whitespace](https://github.com/ultraware/whitespace) - Tool for detection of leading and trailing whitespace
@@ -562,6 +562,7 @@ Global Flags:
562562
--mem-profile-path string Path to memory profile output file
563563
--trace-path string Path to trace output file
564564
-v, --verbose verbose output
565+
--version Print version
565566
566567
```
567568

cmd/golangci-lint/mod_version.go

+1-1
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

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ require (
1515
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee
1616
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98
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

+2-2
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98 h1:0OkFarm1Zy2CjCiD
9292
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
9393
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI=
9494
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
95-
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217 h1:En/tZdwhAn0JNwLuXzP3k2RVtMqMmOEK7Yu/g3tmtJE=
96-
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
95+
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89 h1:664ewjIQUXDvinFMbAsoH2V2Yvaro/X8BoYpIMTWGXI=
96+
github.com/golangci/lint-1 v0.0.0-20190930103755-fad67e08aa89/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
9797
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
9898
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
9999
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk=

pkg/commands/run.go

+8-22
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

+1-1
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

+39-29
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

+65-41
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)